Skip to content

feat(plugin): plugin management commands (promoted from beta)#426

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

feat(plugin): plugin management commands (promoted from beta)#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

Full `cy plugin` command tree covering the entire plugin API surface. Commands were developed under `cy beta plugin` and are now promoted to top-level — the feature is in production.

Plugin installs (`cy plugin …`)

  • list, get, install (with optional `--version-id`), uninstall, upgrade, logs

Plugin managers (`cy plugin manager …`)

  • list, get, create, accept, reject, delete

Plugin registries (`cy plugin registry …`)

  • list, get, add, update (new), delete

Registry plugins (`cy plugin registry plugin …`)

  • list, get, create, update (new), delete

Plugin versions (`cy plugin registry plugin version …`)

  • list, get, publish, delete, install, logs, retry

Plugin widgets (`cy plugin widget …`) (new)

  • list, query

Component plugins (`cy plugin component …`) (new)

  • list, relation-set

Component plugin widgets (`cy plugin component widget …`) (new)

  • list, query

Key design decisions

  • Promoted from beta — `cy beta plugin` is removed; `cy plugin` is the canonical path. Feature was never GA under beta so no deprecation alias is needed.
  • Dual-form shell completion — every `` argument accepts both name and numeric ID via tab-completion. Resolvers handle both; ambiguous names produce a clear error.
  • Plugin config flags — `--config key=val` (repeatable) and `--config-file path.json` for install/upgrade configuration.
  • Widget query uses GET-with-body — verified `generic_client.go` passes body on all HTTP methods including GET.
  • iframe proxy omitted — iframe endpoints are consumed by the web console for embedding; CLI cannot render them. TF provider middleware import can still call them if needed.
  • Models sourced from swagger regen — `make client-generate` fetches from `youdeploy-http-api` and regenerates `client/models/` only. The generated HTTP operations client (`client/client/`) remains pruned.

API drift fixed (vs original PR baseline)

  • `PluginInstall.status`: enum `installed` → `running`
  • `PluginLogs` type: replaces the old `PluginInstallLog`; now returns `RuntimeLogs` + `InstallLogs`
  • Various unrelated model field changes from swagger regen (`Component.UseCase`, `MemberTeam.Username`, etc.) fixed across the codebase

Test plan

  • `go build ./...` — clean ✅ (verified locally)
  • `cy plugin --help` renders expected subcommand tree; `cy beta` does NOT list `plugin`
  • Tab-completion on `` proposes name, URL, and numeric ID forms
  • Smoke test against dev org (manual — see PR description on terraform-provider-cycloid#85 for combined checklist)
  • e2e tests deferred to coordinated CLI+TF cycle

🤖 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>
@fhacloid fhacloid self-assigned this Apr 27, 2026
@fhacloid fhacloid changed the title feat(beta): plugin management commands feat(plugin): plugin management commands (promoted from beta) Apr 29, 2026
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