Skip to content

feat(beta): plugin management commands#426

Draft
fhacloid wants to merge 17 commits intodevelopfrom
fbh-feat-plugins
Draft

feat(beta): plugin management commands#426
fhacloid wants to merge 17 commits intodevelopfrom
fbh-feat-plugins

Conversation

@fhacloid
Copy link
Copy Markdown
Contributor

@fhacloid fhacloid commented Apr 9, 2026

Summary

Adds a full cy beta plugin command tree covering the plugin API surface:

  • Plugin installs (cy beta plugin …) — list, get, install, uninstall, upgrade (alias: update), logs
  • Plugin managers (cy beta plugin manager …) — list, get, create, accept, reject, delete
  • Plugin registries (cy beta plugin registry …) — list, get, add, delete
  • Registry plugins (cy beta plugin registry plugin …) — list, get, create, delete
  • Plugin versions (cy beta plugin registry plugin version …) — list, get, publish, delete, install, logs, retry

All commands live under the beta flag and follow existing cobra conventions.

Key design decisions

  • Dual-form shell completion — every <name-or-id> argument accepts both the name and the numeric ID via tab-completion. Resolvers handle both; ambiguous names (multiple IDs) produce a clear error listing the conflicts and asking the user to use a numeric ID.
  • Plugin config flags--config key=val (repeatable) and --config-file path.json for plugin install/upgrade configuration (map[string]string, distinct from StackForms).
  • upgrade / alias updatecy beta plugin upgrade is the canonical verb; update is an alias. --version-id is required.
  • Registry resolution order — numeric ID → URL (if value starts with http) → name.
  • Models sourced from backend — copied from youdeploy-http-api/gen/models/ (identical output to swagger generate client). The Makefile client-generate target now prunes the generated HTTP client dir, keeping only models/.
  • Shared helpersversion/helpers.go centralises the two-step registry+plugin resolution used by all version subcommands.

Not included (follow-ups)

  • cy beta plugin registry update (middleware method exists, no CLI command)
  • cy beta plugin registry plugin update (middleware method exists, no CLI command)
  • e2e tests (require a test environment with a running plugin setup)

Test plan

  • go build ./... — clean (verified locally)
  • cy beta plugin --help renders expected subcommand tree
  • Tab-completion on <registry> proposes name, URL, and numeric ID forms
  • cy beta plugin install <name> resolves by name; numeric ID also accepted
  • Ambiguous name produces error listing conflicting IDs

🤖 Generated with Claude Code

Implements CLI commands for the full plugin API surface under `cy beta plugin`:

- `cy beta plugin list/get/install/uninstall/upgrade/logs` — manage plugin installs
  (upgrade has alias: update; --version-id required; --config key=val / --config-file)
- `cy beta plugin manager list/get/create/accept/reject/delete` — manage plugin managers
  (PluginManager agents with invite flow: accept/reject pending invites)
- `cy beta plugin registry list/get/add/delete` — manage plugin registries
- `cy beta plugin registry plugin list/get/create/delete` — plugins within a registry
- `cy beta plugin registry plugin version list/get/publish/delete/install/logs/retry`

All commands support dual-form completion (name OR numeric ID). Resolvers handle
ambiguity: error message lists conflicting IDs and asks for a numeric ID.

Also adds:
- 19 plugin model files (copied from backend gen/models)
- middleware interface + implementation (plugins.go)
- cyargs helpers: AddPluginConfigFlags, GetPluginConfig, Complete*/Resolve* for all entities
- Makefile: prune generated client dir after swagger codegen, keep only models

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
xescugc
xescugc previously approved these changes Apr 9, 2026
Three plugin commands defined --url inline without going through cyargs.
Consolidate into AddURLFlag/GetURL in cyargs/plugins.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fhacloid and others added 15 commits April 11, 2026 22:21
…ion publish, and pipeline log commands

CLI-113: cy beta plugin install now accepts --version-id (optional) to pin a specific
plugin version at install time. cy beta plugin registry plugin version publish now accepts
--docker-image as a mutually exclusive alternative to --url, allowing Docker image
references (scheme-less URIs) to be published without strfmt.URI validation issues.
Refactored upgrade.go to use the shared cyargs.AddPluginVersionIDFlag helper.

CLI-114: Added cy pipeline builds logs (dump or --watch a build by ID) and
cy pipeline job logs (latest build logs, --watch tails future builds with --poll-interval).
Both commands reuse the existing buildwatch SSE streaming and formatting infrastructure
via a new StreamLogs function with ReadOnly=true and an idle-timeout reader for dump mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…WithOptions

Migrates 13 key resource command files (list + get) to use the new
cyout.PrintWithOptions helper with curated table column defaults:

- projects: Canonical, Name, Owner.Username (dot-notation)
- apikey: Canonical, Name, LastSeven, LastUsed
- roles: Canonical, Name, Description, Default
- catalogrepositories: Canonical, Name, URL, Branch, StackCount
- configrepositories: Canonical, Name, URL, Branch, Default
- builds: ID, Name, Status, JobName, StartTime
- credentials: Canonical, Name, Type, Path, Keys (Raw field excluded)
- environments: Canonical, Name
- members: Username, Email, GivenName, FamilyName
- pipelines: Name, Status, Paused, Project, Environment, Component (Transform)
- jobs: Name, Paused, PipelineName, CurrentBuildID (Transform)
- teams: Canonical, Name, MemberCount, Roles (Transform, join role names)
- components: Canonical, Name, Description, StackRef, UseCase, Version (Transform)

Also:
- Fixes table printer to handle []interface{} slices (interface unwrapping)
- Removes force-JSON hacks in multi-arg get commands (now typed slices)
- Adds cyout.RegisterModel to all list/get commands for --output completion
- Fixes pre-existing lint issues (misspell, staticcheck, unconvert)
- Updates tests to match new lipgloss table output format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ands

- projects get: accept multiple canonicals; loop returns []*models.Project
- roles get: accept multiple canonicals via positional args; --role flag keeps precedence
- credentials get: accept multiple canonicals when no --canonical/--path flags set
- environments get/delete: accept env canonicals as positional args; --env as fallback
- components get/delete: accept component canonicals as positional args; --component as fallback
- organizations get/delete/list: accept org canonicals as positional args; migrate to cyout
- members get/delete: accept numeric member IDs as positional args; --id as fallback

All delete commands migrated from factory.GetPrinter+SmartPrint to cyout.Print.
Also includes goimports formatting fixes across unrelated files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all remaining factory.GetPrinter + printer.SmartPrint boilerplate with
cyout.PrintWithOptions / cyout.Print across:
- projects, credentials (create/update/delete/list-env)
- teams (all), roles (all)
- environments, components, stacks (non-list/get)
- organizations, configrepositories, catalogrepositories
- apikey, events, members (invite/update/list-invites)
- externalbackends, login, terracost, status, version
- beta/config, beta/plugins (all subcommands)
- pipelines (pause/unpause/diff/update/clear-task-cache/build-trigger/etc.)

Also removes all "if output == 'table' { output = 'json' }" workarounds
(now handled uniformly by the new table printer and cyout).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s non-struct slices

- cyout.PrintWithOptions: use cmd.ErrOrStderr() for error output instead of
  cmd.OutOrStderr(). OutOrStderr falls back to cmd.outWriter which points to
  stdout in tests that call SetOut — causing error JSON to land on stdout.
  ErrOrStderr correctly uses the dedicated errWriter set by SetErr.

- printer/table: guard headersFromStruct against non-struct values (strings, ints).
  Returns nil when v.Kind() != Struct; Print() exits early with no output.
  Fixes panic when deleteProject returns []string of deleted canonicals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iption to project list

- BorderHeader(false): with HiddenBorder, the header separator renders as a line of
  spaces causing a blank line. Disabling it removes the gap entirely.
- projectTableOptions: add Description column (Canonical, Name, Description, Owner.Username).
  Component already had Description in its default columns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hidden-border with NormalBorder (all sides off except header
separator) to render a dim ─ line between header and data rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 2-space right padding per cell so columns don't run together
when column borders are disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CLAUDE.md: update command pattern to cyout.PrintWithOptions, add
  cyout package description, fix Hard Rule 6
- docs/architecture.md: update request lifecycle, add --output system
  section (grammar, field extraction, shell completion, cyout helper)
- docs/adding-a-command.md: update get.go example to use cyout pattern
  with RegisterModel and PrintWithOptions
- changelog: extend positional-args entry to cover environments,
  components, organizations, members, roles, credentials; add new
  output-format entry for table=cols/jq=/field extraction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--jq <expr> is sugar for --output jq=<expr>. GetOutput() checks the
--jq flag first; if set, it returns "jq="+expr, bypassing --output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sistent default

- Default table uses kubectl-style `─` header separator with `#` row index column
- New `table:border` option enables nushell-style rounded-border grid (╭─┬─╮)
- New `table:noindex` option suppresses the `#` row index column
- On wide terminals, extra struct fields are shown beyond curated defaults;
  as terminal narrows, extra columns are dropped first while curated columns survive
- Nested structs render as `{record N fields}` instead of the type name
- Numeric types (int, int32, float32, float64) render as formatted values
- New `cy output set/get/reset` commands persist the default output format
  to ~/.config/cycloid-cli/config.yaml
- Priority chain: --jq > --output > CY_OUTPUT > cy output set > "table"
- Delete commands now use Columns: []string{"Canonical"} for clean table output
- Added e2e tests for delete table output and bulk delete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The # index column was purely cosmetic and not usable for filtering
or referencing resources, which could mislead users into trying
commands like `cy plugin get 0`.

https://claude.ai/code/session_01NkqEDktWxTPxXBtjET5iZK
…nics

Audit of the entire output system (table, field, jq printers) uncovered
multiple crash paths when the table printer receives nil pointers, slices
containing nil elements, or unexpected types. These occur in production
when the API returns null objects or partial data.

Fixes:
- Print() now returns early for nil interface and typed nil pointers
- build() guards nil pointer before calling Elem()
- Slice iteration skips nil elements gracefully (empty row)
- expandColumns() nil-safe pointer/interface unwrap loops
- renderValue() handles invalid Value, nil slices, maps, interfaces
- headersFromStruct() guards invalid Value
- entryFromStruct() guards invalid Value
- Maps now render as "{N entries}" instead of "map"

Adds firstNonNilElem() and derefValue() helpers. 18 new test cases
covering every crash path found during audit.

https://claude.ai/code/session_01NkqEDktWxTPxXBtjET5iZK
Replace the broken path where API errors were rendered as table rows.
Errors now bypass the output printer entirely and are formatted as a
human-readable block on stderr regardless of --output flag.

API errors show:
- Status code, HTTP method, and request path
- Sanitized request body (credentials and secrets redacted to [REDACTED])
- Structured error payload: [Code] message with sub-detail lines
- Request-ID from the backend payload (for support/debugging copy-paste)
- Fallback to raw response body when no structured payload is available

Local errors (missing flags, bad args) show the command invocation hint
(command path + set flags) before the Error: summary line.

New sanitizeBody() in middleware recursively redacts sensitive JSON keys:
ssh_key, password, secret_key, access_key, client_secret, json_key,
token, ca_cert, raw, current — covering all credential and auth models.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants