diff --git a/pkg/cmd/AGENTS.md b/pkg/cmd/AGENTS.md new file mode 100644 index 000000000..337be3897 --- /dev/null +++ b/pkg/cmd/AGENTS.md @@ -0,0 +1,82 @@ +# AGENTS.md — proboctl CLI + +## Overview + +`proboctl` is the Probo CLI built with [cobra](https://github.com/spf13/cobra). Entry point: `cmd/proboctl/main.go`. + +## Package layout + +| Package | Purpose | +|---|---| +| `cmd/proboctl` | Binary entry point — creates `Factory`, root command, and runs it | +| `pkg/cmd/root` | Root command — registers all top-level subcommands | +| `pkg/cmd/` | Command group (e.g. `risk`, `framework`, `webhook`) — wires subcommands | +| `pkg/cmd//` | Leaf command (e.g. `risk/create`, `risk/list`) — owns the `RunE` | +| `pkg/cmd/cmdutil` | Shared helpers: `Factory`, flag validators, table/JSON output, time formatting | +| `pkg/cmd/iostreams` | Terminal I/O abstraction (stdout, stderr, color, interactivity) | +| `pkg/cli/api` | GraphQL client (`Client`) and generic pagination (`Paginate[T]`) | +| `pkg/cli/config` | Config file management (hosts, tokens, default org) | + +## Adding a new resource command + +1. Create `pkg/cmd//.go` with a `NewCmd(f *cmdutil.Factory) *cobra.Command` that groups the subcommands. +2. Create a subpackage per verb (`list`, `create`, `view`, `update`, `delete`) each exporting `NewCmd(f *cmdutil.Factory) *cobra.Command`. +3. Register the group command in `pkg/cmd/root/root.go`. + +## Command structure pattern + +Every leaf command follows this pattern: + +```go +package verb + +func NewCmd(f *cmdutil.Factory) *cobra.Command { + var ( + flagOrg string + flagFoo string + // ... + ) + + cmd := &cobra.Command{ + Use: "", + Short: "One-line description", + Aliases: []string{"..."}, // optional, e.g. "ls" for list + Example: ` proboctl ...`, + RunE: func(cmd *cobra.Command, args []string) error { + // 1. Validate output flags (for list commands) + // 2. Load config, get host + token + // 3. Create api.Client + // 4. Resolve --org (flag → config default) + // 5. Interactive prompts if IOStreams.IsInteractive() and flags are missing + // 6. Call API via client.Do(query, variables) + // 7. Output: JSON via cmdutil.PrintJSON or table via cmdutil.NewTable + }, + } + + cmd.Flags().StringVar(&flagOrg, "org", "", "Organization ID") + // ... more flags ... + + return cmd +} +``` + +## Key conventions + +- **GraphQL queries/mutations** are `const` strings declared at package level in the leaf command file. +- **Response types** are unexported structs in the leaf command file, shaped to match the GraphQL response. +- **Organization resolution**: every command that needs an org checks `--org` flag first, then falls back to `hc.Organization` from config. If both are empty, return an error telling the user to pass `--org` or run `proboctl auth login`. +- **Interactive prompts** use `github.com/charmbracelet/huh`. Gate them behind `f.IOStreams.IsInteractive()`. Always support full non-interactive use via flags. +- **Output format**: list commands support `--output json|table` via `cmdutil.AddOutputFlag` / `cmdutil.ValidateOutputFlag`. Default is table. +- **Pagination**: list commands use `api.Paginate[T]` with a `--limit` / `-L` flag (default 30). Show "Showing X of Y" on stderr when results are truncated. +- **Table output**: use `cmdutil.NewTable("COL", ...).Rows(rows...)`. +- **View commands** print detailed formatted output with lipgloss-styled labels and sections. They support `--output json|table` like list commands. Use `lipgloss.NewStyle()` for bold titles and dimmed labels. +- **Create/update/delete** commands print a single confirmation line to stdout (e.g. `"Created risk %s (%s)\n"`). +- **Delete commands** prompt for confirmation interactively; skip the prompt when `--yes` / `-y` is passed. +- **Flag naming**: use kebab-case (`--order-by`, `--inherent-likelihood`). Use `StringVar` / `IntVar` (not positional args) for all inputs. + +## Dependencies + +- CLI framework: `github.com/spf13/cobra` +- Interactive prompts: `github.com/charmbracelet/huh` +- Styled terminal output: `github.com/charmbracelet/lipgloss` +- All other dependencies follow the root AGENTS.md (same module, same style rules) diff --git a/pkg/cmd/CLAUDE.md b/pkg/cmd/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/pkg/cmd/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file