Skip to content

feat: finish Phase 1 discovery (CLI envelope, get_kit, WebMCP mirror, contracts in CLI) - #97

Merged
mrgoonie merged 12 commits into
mainfrom
sx-phase1-discovery
Oct 20, 2026
Merged

mrgoonie merged 12 commits into
mainfrom
sx-phase1-discovery

Conversation

@mrgoonie

@mrgoonie mrgoonie commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Finishes the remaining Phase 1 discovery items of #33 and closes #48.

  • CLI envelope and exit codes for every command: discovery (search, use, find, inspect, check, report, config *, import), registry (validate, pack, publish, promote, release *, from feat(registry)!: release CLI, idempotent publish, promote, quarantine review, signed downloads and GitHub OIDC trusted publishing #99) and installer (install, update, uninstall, list, sync, from feat: collections API and verified CLI installer with lockfile, sync and local MCP (#35 phase 3) #98). All go through one CliError + runCommand in lib/output.ts / lib/run-command.ts and one exit-code table: 0 ok, 1 api_error/unexpected_error/integrity_error, 2 usage, 3 not found, 4 incompatible, 5 auth, 6 network, 7 rate_limited, 8 installer conflict. Parse errors exit 2 (with a usage_error envelope under --json). Only skillx mcp (an MCP stdio server) has no --json.
  • refactor(cli): call isCompatibilityActionable instead of mirroring the rule #48: the CLI imports @skillx/contracts (workspace devDependency, inlined by tsup) and isActionable delegates to isCompatibilityActionable; the mirrored rule is gone.
  • Remote MCP get_kit: the owner reads their own kit (any visibility) with the full revision document. Anyone else sees only a public kit whose effective revision is reviewed, activated and authored public, and then only metadata plus { digest, number, activatedAt } (the foreign-ancestor reference of the lock), else not_found. A suspended or revoked publisher answers publisher_revoked, like get_release. Flag off → tool error feature_disabled before any credential is read; an unexpected viewer failure is internal_error. Only get_kit reads credentials; the other four tools stay anonymous.
  • WebMCP get_package / get_release: in-page tools that post tools/call to /api/mcp and return its answer unchanged; a test holds names, schemas and descriptions equal to the remote definitions.
  • Fixed in passing: skillx find crashed on selection (read total/rating that the API does not return); it now reuses the use resolution. A plain TypeError is no longer misreported as a network outage.

#33 checklist → evidence

Item Evidence
--compatible <runtime> search filter Already on main: packages/cli/src/lib/search-api.ts, commands/search.ts (now on the envelope)
skillx inspect Already on main: commands/inspect.ts; now runCommand-based
skillx check --target commands/check.ts; built-cli.test.ts runs the built bundle against a stub server for all five statuses (exit 0/4) and a missing skill (exit 3)
Standardize CLI JSON/error envelope/exit codes This PR: lib/output.ts, lib/run-command.ts; release-cli.ts and install-errors.ts now reuse them; one table in docs/cli-reference.md#json-output-and-exit-codes; tests in output.test.ts, release-commands.test.ts, and built-cli.test.ts, which walks skillx --help recursively to assert --json on every leaf command (except mcp) and checks that failing install/list/validate/pack/promote/import answer one envelope with exit 2
Compatibility badges with evidence details Already on main: components/compatibility-panel.tsx
Remote MCP package/server Already on main: routes/api.mcp.ts, lib/mcp/
search_packages, get_package, get_release, check_compatibility, get_kit First four on main; get_kit in this PR: lib/mcp/handlers-kits.ts (+ handlers-kits.test.ts, api.mcp.test.ts auth/flag cases)
WebMCP feature detection and read tools Detection on main (lib/webmcp/model-context.ts); get_package/get_release mirror in this PR: lib/webmcp/mcp-mirror-tools.ts (+ test answering through the real route)
Route-level Markdown renderers Already on main: lib/markdown/skill-markdown*.ts
Link: rel=alternate / canonical Already on main: lib/markdown/skill-markdown-handler.ts, routes/skill-detail.tsx
Copy page as Markdown Already on main: components/copy-markdown-button.tsx
Authorization-aware Copy SKILL.md Already on main: components/copy-markdown-button.tsx
/llms.txt, /llms-full.txt Already on main: routes/llms-txt.ts, routes/llms-full-txt.ts
Publisher/harness landing docs Already on main: routes/docs-harness.tsx, routes/docs-publishers.tsx
Parity tests across API/CLI/MCP/Markdown lib/parity/compatibility-parity.test.ts; the "CLI cannot be reached" note is removed — CLI decisions for all five statuses are covered by skill-lookup.test.ts (against the contract) and built-cli.test.ts (against the bundle)

Verification

  • pnpm install --frozen-lockfile, pnpm typecheck: pass.
  • pnpm test: 116 files, 2357 passed, 2 skipped (rebased on fbd0333).
  • A registry 429 answers rate_limited with exit 7 on both install and promote (local stub, --json and human mode; also built-cli.test.ts and registry-client.test.ts).
  • pnpm --filter skillx-sh build and pnpm --filter web build: pass.
  • Built bundle contains no @skillx/ import (contracts inlined); node packages/cli/bin/skillx.js check <slug> --target <runtime> --json exercised by built-cli.test.ts against a stub server; npm pack --dry-run: 6 files, 168.8 kB.

Notes for reviewers

  • Behaviour change: Commander parse errors now exit 2 instead of 1, for every command including publish/release *. A bare skillx and a group without its subcommand (skillx config) print help and exit 2. --help, help <command> and --version exit 0. --json is only recognised before --.
  • use with no match is not_found (exit 3) in both modes; human mode used to print a yellow notice and exit 0, it now prints the message to stderr and exits 3. search --use and find (after a pick) share the same path.
  • Exit 4: check answers it on a success envelope with data.actionable: false; the installer fails with the incompatible error code. Both map to exit 4.
  • Rebase onto 2c5a892 (feat: collections API and verified CLI installer with lockfile, sync and local MCP (#35 phase 3) #98, feat(registry)!: release CLI, idempotent publish, promote, quarantine review, signed downloads and GitHub OIDC trusted publishing #99; now on fbd0333), non-trivial resolutions:
    • lib/output.ts: kept both additions: 7 rate_limited from this PR, and 8 conflict / integrity_error / incompatible from the installer.
    • lib/release-cli.ts: its private CliError (with a .failure envelope) and runCommand are replaced by the shared CliError (re-exported) and a thin wrapper over the shared runner; human failures keep the <label> failed: <message> wording via failureLabel. A registry 429 is now rate_limited (exit 7) instead of api_error.
    • lib/install/install-errors.ts: InstallError now extends the shared CliError (and can carry a hint).
    • commands/install-runner.ts: runInstaller is a thin wrapper over the shared runCommand. It sets process.exitCode instead of calling process.exit, so piped stdout drains. Human mode prints the hint, and details.conflicts is still listed after the message (through failureDetails).
    • lib/install/registry-client.ts: a 429 is rate_limited (exit 7) with the hint "Retry later.", matching publish/promote.
    • commands/import.ts (from feat(registry)!: release CLI, idempotent publish, promote, quarantine review, signed downloads and GitHub OIDC trusted publishing #99, still on process.exit + ora): moved onto runCommand, gained --json, and checks arguments before the API key, so a bad repo is usage_error (2) with or without a key. A 404 keeps its "Create a SKILL.md file in your repo and try again." hint.
    • commands/pack.ts: the shared pack failure names the command (cannot validate ./x, cannot publish ./x).
    • lib/release-cli.ts, docs/codebase-summary.md: the API key hint names skillx config set-key, not the missing config set api-key.
    • src/index.ts: main's new commands plus this PR's routeUsageErrors + parseAsync.
    • built-cli.test.ts: both test sets kept in one describe.
    • docs/cli-reference.md: the installer's separate exit table (which called 7 "reserved") and the registry paragraph's own code list now point to the one table.
  • config set-key and interactive find read stdin through promptLine: an empty or closed stdin is usage_error (exit 2) instead of hanging into Node's exit 13 with no envelope. A piped value without a trailing newline (printf 'key' |) is still read.
  • /api/mcp row in CLAUDE.md corrected: tools are anonymous except get_kit.

Closes #48
Refs #33

🤖 Generated with Claude Code

mrgoonie and others added 12 commits October 20, 2026 09:36
Bundle @skillx/contracts into the CLI (workspace devDependency, tsup
noExternal) and drop the hand-written mirror of the actionability rule.
The CLI now decides every status in COMPATIBILITY_STATUSES exactly as the
contract does, which the parity notes no longer have to excuse.

Closes #48

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add runCommand/emit/CliError so config, report, use and find join check,
inspect and search on the same --json envelope and exit codes. Parse
errors exit 2 with a usage_error envelope, 429 maps to rate_limited (7),
and a programming TypeError is no longer reported as a network outage.
Exit codes are set on process.exitCode so piped stdout drains.

find now reads the search API's real shape (avg_rating, results) and
opens the pick through use, instead of crashing on undefined fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get_kit reads one kit's effective revision with the personal kit API's
visibility: a public kit whose revision was authored public, reviewed and
activated is readable by anyone, an owner (session or unscoped API key)
reads their own kit, and everything else is not_found. With the feature
flag off the tool fails with feature_disabled. The other four tools still
never read the request's credentials.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The in-page tools post a tools/call to the same-origin /api/mcp route and
return that tool's answer, so access codes and protected-payload rules stay
on the server. Names, schemas and descriptions are held equal to the remote
definitions by a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… publishers

A stranger now sees a public kit as metadata and revision digest only, the
reference a foreign ancestor gets in the lock; the document stays with the
owner. A suspended or revoked publisher answers publisher_revoked. The route
checks the personal kits flag before reading credentials and reports an
unexpected viewer failure as internal_error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… read --json only before --

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…not_found everywhere

config set-key and find raced nothing against stdin closing, so an empty pipe
left an unsettled await and Node exited 13 without an envelope. Both now read
through promptLine and fail with usage_error. use, search --use and find share
one not_found path for an empty resolution. The incompatible error code is
gone: check reports exit 4 on a success envelope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed envelope

The registry commands had their own CliError and runner, and the installer
its own error type, so the envelope and exit codes could drift apart. Both
now use the shared CliError, runCommand and exit-code table; import moves off
process.exit and gains --json, and a 429 from the registry is rate_limited.
The docs keep one exit-code table (0-8), and a built-CLI test checks that
every command offers --json and fails with an envelope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…em on 429

Installer commands now finish through the shared runCommand: the exit code
is set on process.exitCode instead of process.exit, so piped stdout drains,
human mode prints the hint, and conflict details still follow the message.
A registry 429 is rate_limited (exit 7) for install, update and sync as it
already was for publish and promote. Also: the API key hint names
`skillx config set-key`, an import 404 hints at creating SKILL.md again, and
validate and publish name themselves when the source cannot be packed.

Co-Authored-By: Claude Opus 5.5 <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.

refactor(cli): call isCompatibilityActionable instead of mirroring the rule

1 participant