-
Notifications
You must be signed in to change notification settings - Fork 2
apps cli subcommands
Active contributors: Nik Anand
skill-registry exposes seven headless subcommands. Each is a single file under cli/cmd/skill-registry/ registered on the root cobra command. They share one persistent flag (--json) and three pieces of shared infrastructure: config.Load, registry.New, and walkSkillIntoFiles / rekeyBySlug for file collection.
| Command | Purpose | Key flags |
--json payload |
|---|---|---|---|
bootstrap |
Headless onboarding — create repo, push every local skill, install agent docs. Useful in CI; the wizard supersedes it interactively. |
--repo, --visibility, --no-agents, --yes
|
(not yet structured; legacy human output) |
list |
Browse the registry as a list. With --plain or no TTY, prints a fixed-width table. |
--query/-q, --plain
|
[{slug, name, description}, …] |
get <slug> |
Download one skill into a local folder. Default destination is ./.agents/skills/<slug>/. |
--dest |
{slug, path} |
sync |
Push local dot-folder skills missing from the registry. Interactive multi-select unless --all. |
--yes/-y, --all
|
{pushed: [...], skipped: [...]} |
add <source> |
Clone an external source (./path, owner/repo, or full URL), multi-select skills, publish to your registry. |
--yes/-y, --all
|
{pushed: [...], skipped: [...]} |
publish <path> |
Upload one local folder as one skill. | --name |
{slug, sha, url} |
remove <slug> |
Delete a skill end-to-end: registry, MCP cache, every agent dot-folder. |
--yes/-y
|
{slug, removed_from: [...], sha, repo} |
The bootstrap subcommand predates the wizard (F2.x replaced the interactive parts) and is kept around for scripted invocations that don't want a TUI. Its long-form output is still human-readable text.
Every subcommand respects --json. The flag is bound once on the root via jsonout.BindFlag (see cli/internal/jsonout/jsonout.go); each subcommand checks jsonout.Enabled() at the top of its RunE and branches into a dedicated run<Foo>JSON path:
- No TUI. No Bubble Tea programs. No interactive prompts.
- Success →
jsonout.Print(struct{…}{…})writes one line of compact JSON to stdout. - Failure →
jsonout.PrintError(err)writes{"error": "..."}to stdout, thenos.Exit(1).
Empty arrays are always emitted as [] (never elided) so a consumer can jq 'length' without special-casing missing fields. See systems/json-output for the per-payload contracts and version stability rules.
Destructive commands (sync, add, remove) need a confirmation prompt by default. But an agent driving the CLI with piped stdin can't render a Bubble Tea prompt — it would hang.
shouldAutoYes() in cli/cmd/skill-registry/list.go resolves the ambiguity:
func shouldAutoYes() bool {
return jsonout.Enabled() && !isStdinTerminal()
}When --json is set AND stdin is not a TTY, every destructive subcommand promotes --yes automatically. Callers OR this into their yes flag (yes || shouldAutoYes()) so explicit --yes users keep their existing behavior and interactive --json runs (rare, but possible) still see the prompt.
isStdinTerminal is a var, not a func — tests stub it because go test's harness doesn't guarantee a TTY-attached stdin.
runListJSON lists every skill in the registry, filters by --query if provided, and emits the array. The plain-text path (runList with --plain or no TTY) prints a fixed-width table with SLUG / DESCRIPTION columns; descriptions are clipped to 80 runes (not bytes) so multi-byte UTF-8 doesn't get cut mid-character. The interactive path opens tui.NewList with mouse cell motion enabled.
DownloadSkill is the shared core (used by get, the list TUI's enter handler, and the hub's Manage card). Destination resolution: empty --dest → <cwd>/.agents/skills/<canonSlug>; explicit --dest whose basename slugifies to the canon → use as-is; otherwise treat --dest as a parent dir and append <canonSlug>. The parent dir is then scanned for an existing sibling that slugifies to the same canonical form so the "agp-9-upgrade vs agp_9_upgrade" duplicate-folder case stays consistent.
Both share selectSkills* and publishSkills helpers. The shape is the same: discover local skills (or scan a cloned source), client.Slugs(ctx) for the remote set, scan.DedupeAgainst for the missing partition, multi-select unless --all, optional confirmPush choice prompt unless --yes, then walk + publish one skill at a time. --json collapses both into "publish everything missing, no prompts" and emits {pushed, skipped}.
resolveSource accepts three input forms for add <source>: a local path (./, /, ~), a GitHub shorthand (owner/repo, validated by ghShorthandRe), or a full git URL. Shorthand is rewritten to https://github.com/<source>.git and cloned shallow into a tempdir; the tempdir is os.RemoveAll-ed on exit.
doPublish is the shared core. It validates the path is a directory containing SKILL.md, parses the frontmatter for the canonical name (override via --name), Slugifys it, walks the tree, applies the 2 MiB per-file cap (maxFileBytes, matching the Python SKILLS_MAX_FILE_BYTES default), and calls client.Publish(ctx, slug, files, msg). The JSON shape is {slug, sha, url} where sha is the full commit hash (not the 7-char short form printed to humans) and url is the GitHub tree view.
runRemove is the shared core invoked by both the subcommand and the hub's Remove dispatcher. It deletes from three locations in order:
-
Registry —
client.Delete(ctx, canonSlug)atomically drops the<slug>/subtree via the Git Data API (null-SHA entries in the new tree). -
Cache —
~/.cache/skills-mcp/skills/<slug>/and the sibling<slug>.meta.jsonare wiped. -
Dot-folders — every entry in
agents.All()is swept; any direct child whose name matches the slug literally or viaSlugifyis removed (handles symlinks and real directories alike).
The --json payload's removed_from array contains string constants ("registry", "cache", "dotfolders") — never free-form text — so consumers can match on stable values.
| File | Role |
|---|---|
cli/cmd/skill-registry/bootstrap.go |
Headless bootstrap flow + shared helpers (walkSkillIntoFiles, locateMCPBinary, requireGitForBootstrap). |
cli/cmd/skill-registry/list.go |
runListJSON + runList + printPlainList; also hosts isTerminal / isStdinTerminal / shouldAutoYes. |
cli/cmd/skill-registry/get.go |
runGetJSON + runGet + shared DownloadSkill + resolveDest. |
cli/cmd/skill-registry/sync.go |
runSyncJSON + runSync + planSync + publishSkills + confirmPush + promptSync. |
cli/cmd/skill-registry/add.go |
runAddJSON + runAdd + resolveSource + promptAddSelection. |
cli/cmd/skill-registry/publish.go |
runPublishJSON + runPublish + shared doPublish + collectFiles. |
cli/cmd/skill-registry/remove.go |
runRemoveCmd + runRemove (shared with hub) + removeFromCache + removeFromDotFolders. |
cli/internal/jsonout/jsonout.go |
BindFlag / Enabled / Print / PrintError. |
-
apps/cli/index — Cobra root, persistent
--json,bareRouteDecision. - apps/cli/wizard-and-hub — the interactive flows that reuse these handlers.
- systems/json-output — per-subcommand JSON contracts.
-
systems/registry-client — the
registry.Clientcalls every subcommand makes. - api/cli-commands — flag-level reference.