mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
[codex] Add skills CLI and catalog management (#6782)
## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies through company-scoped control-plane workflows. > - Agents need reusable, inspectable skills that can be installed, reset, audited, exported, and assigned without bespoke local setup. > - The existing skill truth model needed cleanup so bundled skills, optional catalog skills, runtime skills, and adapter-provided skills have clear provenance. > - Operators also need a practical CLI and board UI for discovering and managing company skills. > - This pull request adds the skills CLI, packaged skills catalog, company skills APIs, and catalog-aware board UI. > - The benefit is a more reusable Paperclip company setup where skills are portable, auditable, and easier for operators and agents to manage. ## What Changed - Added `paperclipai skills` CLI commands and coverage for catalog listing, installing, resetting, and inspecting company skills. - Added a packaged `@paperclipai/skills-catalog` workspace with bundled and optional skill content plus validation/build tests. - Added shared company-skill types and validators used across CLI, server, and UI contracts. - Added server catalog APIs/services for company skill catalog operations, reset semantics, audit behavior, and portability provenance. - Updated adapter skill handling so runtime/catalog provenance remains explicit across local adapters. - Added board UI support for browsing and managing catalog-backed company skills. - Updated docs for the skills CLI/catalog flow and the company skills Paperclip skill reference. - Rebased the branch onto current `paperclipai/paperclip:master`; no `pnpm-lock.yaml`, `.github/workflows`, or migration files are included in the final PR diff. ## Verification - Passed: `pnpm run preflight:workspace-links && pnpm exec vitest run cli/src/__tests__/skills.test.ts packages/skills-catalog/src/catalog-builder.test.ts packages/skills-catalog/src/shipped-catalog.test.ts packages/shared/src/validators/company-skill.test.ts packages/adapter-utils/src/server-utils.test.ts packages/plugins/create-paperclip-plugin/src/entrypoints.test.ts server/src/__tests__/company-skills-catalog-service.test.ts server/src/__tests__/company-skills-routes.test.ts server/src/__tests__/company-portability.test.ts`. - Passed: `pnpm exec vitest run server/src/__tests__/workspace-runtime.test.ts -t "default branch|origin/master|symbolic-ref"`. - Attempted: full `server/src/__tests__/workspace-runtime.test.ts`. Four provisioning tests failed while seeding an isolated worktree database from the local Paperclip instance because the local plugin schema dump contains a duplicate-column foreign key (`plugin_content_machine_18a7bc327b.content_case_signals`). The default-branch tests touched by the rebase conflict passed in the focused run above. - Checked final diff: no `pnpm-lock.yaml`, no `.github/workflows`, and no migration-file changes relative to `master`. ## Risks - Medium: this is a broad skills/catalog change touching CLI, server APIs, shared contracts, adapter skill sync, and UI. - Catalog validation and reset semantics need careful reviewer attention because they affect reusable company setup and portability. - No database migrations are included in this PR, so there is no migration ordering/idempotency risk in the final diff. - No lockfile is included by design; dependency resolution will be handled by the repository lockfile workflow. ## Model Used - OpenAI Codex coding agent based on GPT-5, running in Paperclip via the `codex_local` adapter with shell, git, GitHub CLI, and code-editing tool access. Exact hosted model build/context-window metadata is not exposed in this runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run targeted tests locally and documented the local workspace-runtime seed failure above - [x] I have added or updated tests where applicable - [x] If this change affects the UI, screenshots were intentionally omitted per PAP-10124 instructions; UI behavior is covered by tests and reviewer inspection - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
8da50dbcf8
commit
9eac727cf1
77 changed files with 9704 additions and 530 deletions
118
doc/CLI.md
118
doc/CLI.md
|
|
@ -143,6 +143,124 @@ pnpm paperclipai agent local-cli codexcoder --company-id <company-id>
|
|||
pnpm paperclipai agent local-cli claudecoder --company-id <company-id>
|
||||
```
|
||||
|
||||
## Skills Commands
|
||||
|
||||
`paperclipai skills` covers three distinct operations:
|
||||
|
||||
1. **Company install** — adds or updates a row in `company_skills` for the
|
||||
whole company. This is what `skills install`, `skills import`, `skills create`,
|
||||
and `skills scan-projects` do.
|
||||
2. **Agent attach** — replaces an agent's *desired* company skill set
|
||||
(`skills agent sync`/`clear`). This is a desired-state operation on the
|
||||
agent's adapter config; it does not change the company library.
|
||||
3. **Adapter runtime sync** — the adapter reconciles the desired skill set
|
||||
with files on disk and reports an `AgentSkillSnapshot` (`skills agent list`).
|
||||
`skills agent sync` triggers this automatically after updating desired state.
|
||||
|
||||
Required Paperclip runtime skills (heartbeat, etc.) remain server-enforced and
|
||||
are added on top of whatever the desired set names.
|
||||
|
||||
### Catalog (app-shipped skills)
|
||||
|
||||
The Paperclip app ships a curated catalog under `@paperclipai/skills-catalog`.
|
||||
Browse and inspect commands never mutate company state; `install` adds a catalog
|
||||
skill to the company library.
|
||||
|
||||
```sh
|
||||
pnpm paperclipai skills browse [--kind bundled|optional] [--category <slug>] [--query <text>]
|
||||
pnpm paperclipai skills search "<text>" [--kind bundled|optional] [--category <slug>]
|
||||
pnpm paperclipai skills inspect <catalog-id-or-key-or-slug>
|
||||
pnpm paperclipai skills install <catalog-id-or-key-or-slug> [--as <slug>] [--force] --company-id <company-id>
|
||||
```
|
||||
|
||||
Catalog semantics:
|
||||
|
||||
- **Bundled** skills live in `packages/skills-catalog/catalog/bundled/<category>/<slug>`
|
||||
and are recommended defaults for most companies. They use canonical key
|
||||
`paperclipai/bundled/<category>/<slug>`.
|
||||
- **Optional** skills live in `packages/skills-catalog/catalog/optional/<category>/<slug>`
|
||||
and are role-specific or domain-specific (browser, AWS ops, etc.). Same key
|
||||
shape with `optional` in place of `bundled`.
|
||||
- `skills install` materializes the catalog files into a company-managed skill
|
||||
directory and records provenance (`catalogId`, `catalogKey`, `packageVersion`,
|
||||
`originHash`, …) so future updates and audit decisions stay consistent.
|
||||
- `--as <slug>` overrides the company skill slug. `--force` may replace a
|
||||
same-key catalog-managed skill but never bypasses hard validation or hard-stop
|
||||
audit findings.
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
pnpm paperclipai skills browse --kind bundled --company-id <company-id>
|
||||
pnpm paperclipai skills search "pull request" --kind bundled
|
||||
pnpm paperclipai skills inspect github-pr-workflow
|
||||
pnpm paperclipai skills install github-pr-workflow --company-id <company-id>
|
||||
pnpm paperclipai skills install paperclipai:optional:browser:agent-browser --company-id <company-id>
|
||||
```
|
||||
|
||||
External GitHub, skills.sh, local-path, and URL sources still go through
|
||||
`skills import`; catalog commands are for the app-shipped catalog only.
|
||||
|
||||
### Company library
|
||||
|
||||
```sh
|
||||
pnpm paperclipai skills list --company-id <company-id>
|
||||
pnpm paperclipai skills show <skill-id-or-key-or-slug> --company-id <company-id>
|
||||
pnpm paperclipai skills file <skill-id-or-key-or-slug> [--path SKILL.md] --company-id <company-id>
|
||||
pnpm paperclipai skills import <source> --company-id <company-id>
|
||||
pnpm paperclipai skills create --name "Review PRs" [--slug review-prs] [--description "..."] [--body-file SKILL.md] --company-id <company-id>
|
||||
pnpm paperclipai skills scan-projects [--project-id <id>...] [--workspace-id <id>...] --company-id <company-id>
|
||||
pnpm paperclipai skills check [skill-id-or-key-or-slug] --company-id <company-id>
|
||||
pnpm paperclipai skills update <skill-id-or-key-or-slug> [--force] --company-id <company-id>
|
||||
pnpm paperclipai skills update --all [--force] --company-id <company-id>
|
||||
pnpm paperclipai skills audit [skill-id-or-key-or-slug] --company-id <company-id>
|
||||
pnpm paperclipai skills reset <skill-id-or-key-or-slug> [--yes] [--force] --company-id <company-id>
|
||||
pnpm paperclipai skills remove <skill-id-or-key-or-slug> --yes --company-id <company-id>
|
||||
```
|
||||
|
||||
`skills import <source>` accepts a skills.sh URL, the equivalent
|
||||
`<owner>/<repo>/<skill>` shorthand, a GitHub URL, a local path, or an
|
||||
`npx skills add …` command. See `references/company-skills.md` in the agent
|
||||
skill bundle for the source-type table.
|
||||
|
||||
`skills check`, `skills update`, `skills audit`, and `skills reset` are the
|
||||
maintenance loop for catalog-installed skills:
|
||||
|
||||
- `check` reports whether each skill's installed bytes match its pinned origin
|
||||
(`hasUpdate`, `installedHash`, `originHash`, `updateHoldReason`,
|
||||
`auditVerdict`).
|
||||
- `update` installs the pinned update through the existing install-update API.
|
||||
`--all` checks every company skill and updates only those with
|
||||
`hasUpdate=true`. `--force` discards local-modification or soft-audit holds;
|
||||
hard-stop audit findings still block the update.
|
||||
- `audit` re-scans installed bytes and reports findings without executing
|
||||
anything.
|
||||
- `reset` reinstalls a catalog-managed skill from its pinned origin, discarding
|
||||
local edits. Prompts in a TTY; requires `--yes` for non-interactive use.
|
||||
|
||||
### Agent attach
|
||||
|
||||
```sh
|
||||
pnpm paperclipai skills agent list <agent-id-or-shortname> --company-id <company-id>
|
||||
pnpm paperclipai skills agent sync <agent-id-or-shortname> --skill <skill-id-or-key-or-slug> [--skill <skill-id-or-key-or-slug>...] --company-id <company-id>
|
||||
pnpm paperclipai skills agent clear <agent-id-or-shortname> --yes --company-id <company-id>
|
||||
```
|
||||
|
||||
`skills agent sync` replaces the agent's non-required desired skill set (it is
|
||||
not additive) and returns the resulting adapter `AgentSkillSnapshot`.
|
||||
`skills agent clear` sends an empty desired list. Required Paperclip skills are
|
||||
still enforced by the server in both cases.
|
||||
|
||||
### Notes
|
||||
|
||||
- Skill references accept company skill `id`, canonical `key`, or unique
|
||||
`slug`; catalog references accept catalog `id`, `key`, or unique `slug`.
|
||||
- `skills file` prints raw file content in human mode so it can be piped.
|
||||
- `skills create --body-file -` reads the skill markdown body from stdin.
|
||||
- `skills remove`, `skills reset`, and `skills agent clear` prompt in a TTY and
|
||||
require `--yes` in non-interactive use.
|
||||
- `--json` prints the raw API result for each command.
|
||||
|
||||
## Secrets Commands
|
||||
|
||||
```sh
|
||||
|
|
|
|||
|
|
@ -420,6 +420,62 @@ eval "$(pnpm paperclipai worktree env)"
|
|||
|
||||
For project execution worktrees, Paperclip can also run a project-defined provision command after it creates or reuses an isolated git worktree. Configure this on the project's execution workspace policy (`workspaceStrategy.provisionCommand`). The command runs inside the derived worktree and receives `PAPERCLIP_WORKSPACE_*`, `PAPERCLIP_PROJECT_ID`, `PAPERCLIP_AGENT_ID`, and `PAPERCLIP_ISSUE_*` environment variables so each repo can bootstrap itself however it wants.
|
||||
|
||||
## App-Shipped Skills Catalog
|
||||
|
||||
The Paperclip app ships a curated catalog of company skills out of the box. The
|
||||
catalog is a workspace package at `packages/skills-catalog`:
|
||||
|
||||
```text
|
||||
packages/skills-catalog/
|
||||
catalog/
|
||||
bundled/<category>/<slug>/SKILL.md # recommended defaults
|
||||
optional/<category>/<slug>/SKILL.md # role/domain-specific
|
||||
generated/catalog.json # checked-in manifest
|
||||
scripts/
|
||||
build-catalog-manifest.ts # regenerate generated/catalog.json
|
||||
validate-catalog.ts # validation only
|
||||
src/ # builder + types consumed by server/CLI
|
||||
```
|
||||
|
||||
Server and CLI import the generated manifest; they do not crawl repository
|
||||
paths at request time. Root `skills/` remains reserved for Paperclip runtime
|
||||
skills and is not part of the catalog.
|
||||
|
||||
Validate the catalog without writing the manifest:
|
||||
|
||||
```sh
|
||||
pnpm --filter @paperclipai/skills-catalog validate
|
||||
```
|
||||
|
||||
Regenerate `generated/catalog.json` after editing any catalog `SKILL.md`,
|
||||
frontmatter, file inventory, category, or slug:
|
||||
|
||||
```sh
|
||||
pnpm --filter @paperclipai/skills-catalog build:manifest
|
||||
```
|
||||
|
||||
The package's `build` script runs `build:manifest` and then `tsc`; tests live
|
||||
under `pnpm --filter @paperclipai/skills-catalog test`. Validation fails when:
|
||||
|
||||
- a catalog entry is not under `catalog/bundled/<category>/<slug>` or
|
||||
`catalog/optional/<category>/<slug>`
|
||||
- `SKILL.md` is missing or the frontmatter `name`/`description` is empty
|
||||
- the frontmatter `key` disagrees with the generated canonical key
|
||||
- two catalog entries share an `id`, `key`, or `slug`
|
||||
- file inventory contains absolute paths, `..`, broken symlinks, or files
|
||||
outside the skill directory
|
||||
- the regenerated manifest differs from the checked-in
|
||||
`generated/catalog.json`
|
||||
|
||||
Trust level is derived from inventory: `markdown_only` (markdown + references
|
||||
only), `assets` (other non-script files), or `scripts_executables` (any
|
||||
executable script). The build contract is documented in
|
||||
`doc/plans/2026-05-26-skills-cli-catalog-contract.md`.
|
||||
|
||||
CI runs `pnpm --filter @paperclipai/skills-catalog validate` and the package's
|
||||
vitest suite, so always regenerate the manifest in the same commit as the
|
||||
catalog change.
|
||||
|
||||
## Quick Health Checks
|
||||
|
||||
In another terminal:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 2026-03-14 Adapter Skill Sync Rollout
|
||||
|
||||
Status: Proposed
|
||||
Status: Implemented for local adapters; gateway remains unsupported
|
||||
Date: 2026-03-14
|
||||
Audience: Product and engineering
|
||||
Related:
|
||||
|
|
@ -25,8 +25,10 @@ Paperclip currently has these adapters:
|
|||
|
||||
- `claude_local`
|
||||
- `codex_local`
|
||||
- `cursor_local`
|
||||
- `cursor`
|
||||
- `gemini_local`
|
||||
- `grok_local`
|
||||
- `acpx_local`
|
||||
- `opencode_local`
|
||||
- `pi_local`
|
||||
- `openclaw_gateway`
|
||||
|
|
@ -39,12 +41,14 @@ The current skill API supports:
|
|||
|
||||
Current implementation state:
|
||||
|
||||
- `codex_local`: implemented, `persistent`
|
||||
- `codex_local`: implemented, `ephemeral`
|
||||
- `claude_local`: implemented, `ephemeral`
|
||||
- `cursor_local`: not yet implemented, but technically suited to `persistent`
|
||||
- `gemini_local`: not yet implemented, but technically suited to `persistent`
|
||||
- `pi_local`: not yet implemented, but technically suited to `persistent`
|
||||
- `opencode_local`: not yet implemented; likely `persistent`, but with special handling because it currently injects into Claude’s shared skills home
|
||||
- `cursor`: implemented, `persistent`
|
||||
- `gemini_local`: implemented, `persistent`
|
||||
- `pi_local`: implemented, `persistent`
|
||||
- `opencode_local`: implemented, `persistent`, with shared Claude skills home caveats
|
||||
- `acpx_local`: implemented, `ephemeral` for Claude/Codex sub-agents and `unsupported` for custom commands
|
||||
- `grok_local`: implemented, `ephemeral`
|
||||
- `openclaw_gateway`: not yet implemented; blocked on gateway protocol support, so `unsupported` for now
|
||||
|
||||
## 3. Product Principles
|
||||
|
|
@ -64,8 +68,7 @@ These adapters have a stable local skills directory that Paperclip can read and
|
|||
|
||||
Candidates:
|
||||
|
||||
- `codex_local`
|
||||
- `cursor_local`
|
||||
- `cursor`
|
||||
- `gemini_local`
|
||||
- `pi_local`
|
||||
- `opencode_local` with caveats
|
||||
|
|
@ -84,7 +87,10 @@ These adapters do not have a meaningful Paperclip-owned persistent install state
|
|||
|
||||
Current adapter:
|
||||
|
||||
- `codex_local`
|
||||
- `claude_local`
|
||||
- `acpx_local` when configured for Claude or Codex
|
||||
- `grok_local`
|
||||
|
||||
Expected UX:
|
||||
|
||||
|
|
@ -99,6 +105,7 @@ These adapters cannot support skill sync without new external capabilities.
|
|||
|
||||
Current adapter:
|
||||
|
||||
- `acpx_local` when configured for custom commands
|
||||
- `openclaw_gateway`
|
||||
|
||||
Expected UX:
|
||||
|
|
@ -114,7 +121,7 @@ Expected UX:
|
|||
|
||||
Target mode:
|
||||
|
||||
- `persistent`
|
||||
- `ephemeral`
|
||||
|
||||
Current state:
|
||||
|
||||
|
|
@ -122,15 +129,15 @@ Current state:
|
|||
|
||||
Requirements to finish:
|
||||
|
||||
- keep as reference implementation
|
||||
- tighten tests around external custom skills and stale removal
|
||||
- ensure imported company skills can be attached and synced without manual path work
|
||||
- keep runtime-mounted snapshots separate from persistent install snapshots
|
||||
- ensure imported company skills can be attached and mounted without manual path work
|
||||
- keep `CODEX_HOME/skills` mutation scoped to heartbeat execution, not `skills/sync`
|
||||
|
||||
Success criteria:
|
||||
|
||||
- list installed managed and external skills
|
||||
- sync desired skills into `CODEX_HOME/skills`
|
||||
- preserve external user-managed skills
|
||||
- desired skills are stored in Paperclip
|
||||
- selected skills are linked into the effective `CODEX_HOME/skills` during runs
|
||||
- no persistent installed/stale state is reported from `skills/sync`
|
||||
|
||||
### 5.2 Claude Local
|
||||
|
||||
|
|
@ -162,18 +169,11 @@ Target mode:
|
|||
|
||||
Technical basis:
|
||||
|
||||
- runtime already injects Paperclip skills into `~/.cursor/skills`
|
||||
- Paperclip reconciles desired skills into `~/.cursor/skills`
|
||||
|
||||
Implementation work:
|
||||
Current state:
|
||||
|
||||
1. Add `listSkills` for Cursor.
|
||||
2. Add `syncSkills` for Cursor.
|
||||
3. Reuse the same managed-symlink pattern as Codex.
|
||||
4. Distinguish:
|
||||
- managed Paperclip skills
|
||||
- external skills already present
|
||||
- missing desired skills
|
||||
- stale managed skills
|
||||
- implemented
|
||||
|
||||
Testing:
|
||||
|
||||
|
|
@ -194,14 +194,11 @@ Target mode:
|
|||
|
||||
Technical basis:
|
||||
|
||||
- runtime already injects Paperclip skills into `~/.gemini/skills`
|
||||
- Paperclip reconciles desired skills into `~/.gemini/skills`
|
||||
|
||||
Implementation work:
|
||||
Current state:
|
||||
|
||||
1. Add `listSkills` for Gemini.
|
||||
2. Add `syncSkills` for Gemini.
|
||||
3. Reuse managed-symlink conventions from Codex/Cursor.
|
||||
4. Verify auth remains untouched while skills are reconciled.
|
||||
- implemented
|
||||
|
||||
Potential caveat:
|
||||
|
||||
|
|
@ -219,14 +216,11 @@ Target mode:
|
|||
|
||||
Technical basis:
|
||||
|
||||
- runtime already injects Paperclip skills into `~/.pi/agent/skills`
|
||||
- Paperclip reconciles desired skills into `~/.pi/agent/skills`
|
||||
|
||||
Implementation work:
|
||||
Current state:
|
||||
|
||||
1. Add `listSkills` for Pi.
|
||||
2. Add `syncSkills` for Pi.
|
||||
3. Reuse managed-symlink helpers.
|
||||
4. Verify session-file behavior remains independent from skill sync.
|
||||
- implemented
|
||||
|
||||
Success criteria:
|
||||
|
||||
|
|
@ -250,9 +244,7 @@ This is product-risky because:
|
|||
|
||||
Plan:
|
||||
|
||||
Phase 1:
|
||||
|
||||
- implement `listSkills` and `syncSkills`
|
||||
- implemented `listSkills` and `syncSkills`
|
||||
- treat it as `persistent`
|
||||
- explicitly label the home as shared in UI copy
|
||||
- only remove stale managed Paperclip skills that are clearly marked as Paperclip-managed
|
||||
|
|
@ -290,6 +282,30 @@ Future target:
|
|||
- likely a fourth truth model eventually, such as remote-managed persistent state
|
||||
- for now, keep the current API and treat gateway as unsupported
|
||||
|
||||
### 5.8 ACPX Local
|
||||
|
||||
Target mode:
|
||||
|
||||
- `ephemeral` for built-in Claude/Codex ACPX sub-agents
|
||||
- `unsupported` for custom ACP commands
|
||||
|
||||
Success criteria:
|
||||
|
||||
- Claude/Codex ACPX snapshots show skills as configured for the next session
|
||||
- custom command snapshots keep desired skills tracked only and do not imply runtime sync
|
||||
|
||||
### 5.9 Grok Local
|
||||
|
||||
Target mode:
|
||||
|
||||
- `ephemeral`
|
||||
|
||||
Success criteria:
|
||||
|
||||
- desired skills are stored in Paperclip
|
||||
- selected skills are copied into the execution workspace for the next run
|
||||
- no persistent installed/stale state is reported from `skills/sync`
|
||||
|
||||
## 6. API Plan
|
||||
|
||||
## 6.1 Keep the current minimal adapter API
|
||||
|
|
@ -333,14 +349,13 @@ Additional UI requirement for shared-home adapters:
|
|||
|
||||
Ship:
|
||||
|
||||
- `cursor_local`
|
||||
- `cursor`
|
||||
- `gemini_local`
|
||||
- `pi_local`
|
||||
|
||||
Rationale:
|
||||
Status:
|
||||
|
||||
- these are the closest to Codex in architecture
|
||||
- they already inject into stable local skill homes
|
||||
- implemented
|
||||
|
||||
### Phase 2: OpenCode shared-home support
|
||||
|
||||
|
|
@ -348,10 +363,9 @@ Ship:
|
|||
|
||||
- `opencode_local`
|
||||
|
||||
Rationale:
|
||||
Status:
|
||||
|
||||
- technically feasible now
|
||||
- needs slightly more careful product language because of the shared Claude skills home
|
||||
- implemented with shared Claude skills-home warning
|
||||
|
||||
### Phase 3: Gateway support decision
|
||||
|
||||
|
|
@ -390,10 +404,10 @@ Adapter-wide skill support is ready when all are true:
|
|||
|
||||
The recommended immediate order is:
|
||||
|
||||
1. `cursor_local`
|
||||
1. `cursor`
|
||||
2. `gemini_local`
|
||||
3. `pi_local`
|
||||
4. `opencode_local`
|
||||
5. defer `openclaw_gateway`
|
||||
|
||||
That gets Paperclip from “skills work for Codex and Claude” to “skills work for the whole local-adapter family,” which is the meaningful V1 milestone.
|
||||
The local-adapter family now has explicit truth models. The remaining V1 boundary is `openclaw_gateway`, which should stay unsupported until the gateway protocol can report real remote skill state.
|
||||
|
|
|
|||
486
doc/plans/2026-05-26-skills-cli-catalog-contract.md
Normal file
486
doc/plans/2026-05-26-skills-cli-catalog-contract.md
Normal file
|
|
@ -0,0 +1,486 @@
|
|||
# Skills CLI And Catalog Contract
|
||||
|
||||
Status: Phase A engineering contract
|
||||
Date: 2026-05-26
|
||||
Source plan: approved Paperclip skills CLI and catalog plan
|
||||
|
||||
This document freezes the first implementation contract for the `paperclipai skills`
|
||||
command group and the app-shipped skills catalog. It is intentionally a build
|
||||
contract, not a full product spec.
|
||||
|
||||
## Decisions
|
||||
|
||||
- `paperclipai skills` manages Paperclip company skills. It does not manage
|
||||
local adapter homes directly.
|
||||
- Installing a skill means adding or updating a company-scoped
|
||||
`company_skills` record.
|
||||
- Attaching a skill to an agent is a separate agent desired-state operation.
|
||||
- Adapter runtime sync is a third step handled through adapter skill APIs.
|
||||
- Root `skills/` remains reserved for Paperclip runtime and operational skills.
|
||||
- App-shipped catalog skills live in `packages/skills-catalog`, not root
|
||||
`skills/`.
|
||||
- Catalog skills are inspectable before install. Inspection never mutates company
|
||||
state.
|
||||
- External sources continue to use the existing company skill import API in the
|
||||
first release. No separate marketplace, tap, or source registry is part of this
|
||||
phase.
|
||||
- Agent desired skills continue to live in
|
||||
`adapterConfig.paperclipSkillSync.desiredSkills` for the first release. Do not
|
||||
add a normalized `agent_skills` table unless later implementation evidence
|
||||
requires it.
|
||||
|
||||
## Terms
|
||||
|
||||
- Company skill: a row in `company_skills`, owned by one company.
|
||||
- Catalog skill: an app-shipped skill entry in `@paperclipai/skills-catalog`.
|
||||
- Skill ref: a user-supplied company skill reference. The CLI accepts company
|
||||
skill `id`, canonical `key`, or unique `slug`.
|
||||
- Catalog ref: a user-supplied catalog reference. The CLI accepts catalog `id`,
|
||||
canonical `key`, or unique `slug`.
|
||||
- Desired skills: the skill key set stored on the agent adapter config.
|
||||
- Runtime snapshot: the adapter-reported `AgentSkillSnapshot` for desired,
|
||||
installed, missing, stale, external, required, or unsupported skills.
|
||||
|
||||
## CLI Contract
|
||||
|
||||
All skills commands use the existing client command stack:
|
||||
|
||||
- Global client options: `--data-dir`, `--config`, `--context`, `--profile`,
|
||||
`--api-base`, `--api-key`, and `--json`.
|
||||
- Company-scoped commands also accept `-C, --company-id <id>` and otherwise use
|
||||
`PAPERCLIP_COMPANY_ID` or the active context profile.
|
||||
- Human output goes to stdout. Errors go to stderr.
|
||||
- `--json` prints pretty JSON and no decorative labels.
|
||||
- Successful commands exit `0`. Validation, API, or conflict errors exit `1`.
|
||||
- API errors use the existing `API error <status>: <message>` formatting.
|
||||
- Mutating commands print a short summary in human mode and the raw result in
|
||||
JSON mode.
|
||||
- Commands that can delete or clear state must prompt in a TTY. In non-TTY mode
|
||||
they must require `--yes`.
|
||||
|
||||
### Company Skill Commands
|
||||
|
||||
These commands are Phase B and must work over existing APIs.
|
||||
|
||||
| Command | Behavior | JSON output |
|
||||
|---|---|---|
|
||||
| `skills list` | Lists company skills from `GET /api/companies/:companyId/skills`. Human rows include `id`, `key`, `slug`, `name`, `source`, `trust`, `compatibility`, and `attachedAgents`. | `CompanySkillListItem[]` |
|
||||
| `skills show <skill-ref>` | Resolves `id`, `key`, or unique `slug`, then reads detail. Ambiguous slugs are conflicts. | `CompanySkillDetail` |
|
||||
| `skills file <skill-ref> [--path <path>]` | Resolves the skill, reads a file with default `SKILL.md`, and prints raw file content in human mode. This command must remain pipeable. | `CompanySkillFileDetail` |
|
||||
| `skills import <source>` | Calls existing import API. Source may be a local path, GitHub URL, skills.sh URL or command, `owner/repo`, `owner/repo/skill`, or URL-like source already accepted by the server. | `CompanySkillImportResult` |
|
||||
| `skills create --name <name> [--slug <slug>] [--description <text>] [--body-file <path|->]` | Creates a managed local company skill. If `--body-file` is omitted, the server default body is used. `-` reads markdown from stdin. | `CompanySkill` |
|
||||
| `skills scan-projects [--project-id <id>...] [--workspace-id <id>...]` | Calls project scan. Repeated flags become arrays. With neither flag, scan all accessible project workspaces. | `CompanySkillProjectScanResult` |
|
||||
| `skills check [skill-ref]` | Reads update status for one skill, or for every listed company skill when no ref is provided. Unsupported statuses are shown, not hidden. | `CompanySkillCheckRow[]` |
|
||||
| `skills update <skill-ref>` | Installs the update for one skill through the existing install-update API. | `CompanySkillUpdateRow` |
|
||||
| `skills update --all` | Checks all skills, installs only those with `hasUpdate=true`, and reports skipped unsupported or current skills. | `CompanySkillUpdateRow[]` |
|
||||
| `skills remove <skill-ref> [--yes]` | Deletes one company skill after confirmation. | `CompanySkill` |
|
||||
|
||||
`CompanySkillCheckRow` is a CLI-side shape:
|
||||
|
||||
```ts
|
||||
interface CompanySkillCheckRow {
|
||||
skill: Pick<CompanySkillListItem, "id" | "key" | "slug" | "name">;
|
||||
status: CompanySkillUpdateStatus;
|
||||
}
|
||||
```
|
||||
|
||||
`CompanySkillUpdateRow` is a CLI-side shape:
|
||||
|
||||
```ts
|
||||
interface CompanySkillUpdateRow {
|
||||
skillRef: string;
|
||||
action: "updated" | "skipped" | "failed";
|
||||
skill?: CompanySkill;
|
||||
status?: CompanySkillUpdateStatus;
|
||||
reason?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Agent Skill Commands
|
||||
|
||||
These commands are Phase B and use existing agent skill APIs.
|
||||
|
||||
| Command | Behavior | JSON output |
|
||||
|---|---|---|
|
||||
| `skills agent list <agent-ref>` | Resolves the agent using existing agent reference behavior, then prints the adapter `AgentSkillSnapshot`. Human rows include `key`, `runtimeName`, `desired`, `managed`, `required`, `state`, `origin`, and `detail`. | `AgentSkillSnapshot` |
|
||||
| `skills agent sync <agent-ref> --skill <skill-ref>...` | Replaces the agent's non-required desired skill set with the supplied refs and triggers adapter sync. Required Paperclip skills remain enforced by the server. | `AgentSkillSnapshot` |
|
||||
| `skills agent clear <agent-ref> [--yes]` | Clears non-required desired skills by sending an empty desired list, then returns the adapter snapshot. | `AgentSkillSnapshot` |
|
||||
|
||||
The word `sync` is deliberate: it is a desired-state replacement, not an append.
|
||||
An additive command can be added later if operators need it.
|
||||
|
||||
### Catalog CLI Commands
|
||||
|
||||
These commands are Phase E and depend on the catalog APIs from Phase D.
|
||||
|
||||
| Command | Behavior | JSON output |
|
||||
|---|---|---|
|
||||
| `skills browse [--kind bundled|optional] [--category <slug>] [--query <text>]` | Lists app-shipped catalog skills. Human rows include `id`, `key`, `kind`, `category`, `slug`, `name`, `trust`, and `recommendedForRoles`. | `CatalogSkillListItem[]` |
|
||||
| `skills search <query> [--kind bundled|optional] [--category <slug>]` | Alias for catalog browse with `query`. | `CatalogSkillListItem[]` |
|
||||
| `skills inspect <catalog-ref>` | Shows app-shipped catalog detail and file inventory. Does not mutate company state. | `CatalogSkillDetail` |
|
||||
| `skills install <catalog-ref> [--as <slug>] [--force]` | Installs a catalog skill into a company library. `--as` overrides the company skill slug. `--force` may replace a same-key catalog skill but must not bypass hard validation or dangerous security findings. | `CompanySkillInstallCatalogResult` |
|
||||
|
||||
Catalog commands are for the app-shipped Paperclip catalog only. External GitHub,
|
||||
skills.sh, local path, and URL installs remain under `skills import <source>` in
|
||||
the first release.
|
||||
|
||||
## Catalog Package Contract
|
||||
|
||||
Add a workspace package:
|
||||
|
||||
```text
|
||||
packages/skills-catalog/
|
||||
package.json
|
||||
tsconfig.json
|
||||
src/
|
||||
index.ts
|
||||
types.ts
|
||||
catalog/
|
||||
bundled/
|
||||
<category>/
|
||||
<slug>/
|
||||
SKILL.md
|
||||
references/
|
||||
scripts/
|
||||
assets/
|
||||
optional/
|
||||
<category>/
|
||||
<slug>/
|
||||
SKILL.md
|
||||
references/
|
||||
scripts/
|
||||
assets/
|
||||
generated/
|
||||
catalog.json
|
||||
scripts/
|
||||
build-catalog-manifest.ts
|
||||
validate-catalog.ts
|
||||
```
|
||||
|
||||
Package name: `@paperclipai/skills-catalog`.
|
||||
|
||||
The package exports:
|
||||
|
||||
- `catalogManifest`
|
||||
- `catalogSkills`
|
||||
- `resolveCatalogSkillRef(ref)`
|
||||
- `getCatalogSkill(id)`
|
||||
- TypeScript types for every manifest shape
|
||||
|
||||
Server and CLI code must import the generated manifest. They must not crawl
|
||||
arbitrary repository paths at request time.
|
||||
|
||||
## Catalog Manifest
|
||||
|
||||
The generated artifact is `packages/skills-catalog/generated/catalog.json`.
|
||||
It is checked in and regenerated by the package build or validation script.
|
||||
|
||||
```ts
|
||||
interface CatalogManifest {
|
||||
schemaVersion: 1;
|
||||
packageName: "@paperclipai/skills-catalog";
|
||||
packageVersion: string;
|
||||
generatedAt: string;
|
||||
skills: CatalogSkill[];
|
||||
}
|
||||
|
||||
interface CatalogSkill {
|
||||
id: string;
|
||||
key: string;
|
||||
kind: "bundled" | "optional";
|
||||
category: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
path: string;
|
||||
entrypoint: "SKILL.md";
|
||||
trustLevel: "markdown_only" | "assets" | "scripts_executables";
|
||||
compatibility: "compatible" | "unknown" | "invalid";
|
||||
defaultInstall: boolean;
|
||||
recommendedForRoles: string[];
|
||||
requires: string[];
|
||||
tags: string[];
|
||||
files: CatalogSkillFile[];
|
||||
contentHash: string;
|
||||
}
|
||||
|
||||
interface CatalogSkillFile {
|
||||
path: string;
|
||||
kind: "skill" | "markdown" | "reference" | "script" | "asset" | "other";
|
||||
sizeBytes: number;
|
||||
sha256: string;
|
||||
}
|
||||
```
|
||||
|
||||
`id` is path-safe:
|
||||
|
||||
```text
|
||||
paperclipai:<kind>:<category>:<slug>
|
||||
```
|
||||
|
||||
`key` is the canonical company skill key installed into `company_skills`:
|
||||
|
||||
```text
|
||||
paperclipai/<kind>/<category>/<slug>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "paperclipai:bundled:software-development:github-pr-workflow",
|
||||
"key": "paperclipai/bundled/software-development/github-pr-workflow",
|
||||
"kind": "bundled",
|
||||
"category": "software-development",
|
||||
"slug": "github-pr-workflow",
|
||||
"name": "github-pr-workflow",
|
||||
"description": "Prepare pull requests, review responses, and verification notes.",
|
||||
"path": "catalog/bundled/software-development/github-pr-workflow",
|
||||
"entrypoint": "SKILL.md",
|
||||
"trustLevel": "markdown_only",
|
||||
"compatibility": "compatible",
|
||||
"defaultInstall": false,
|
||||
"recommendedForRoles": ["engineer"],
|
||||
"requires": [],
|
||||
"tags": ["github", "pull-requests"],
|
||||
"files": [
|
||||
{
|
||||
"path": "SKILL.md",
|
||||
"kind": "skill",
|
||||
"sizeBytes": 1200,
|
||||
"sha256": "..."
|
||||
}
|
||||
],
|
||||
"contentHash": "sha256:..."
|
||||
}
|
||||
```
|
||||
|
||||
## Catalog Skill Frontmatter
|
||||
|
||||
Each catalog `SKILL.md` must include:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: github-pr-workflow
|
||||
description: Prepare pull requests, review responses, and verification notes.
|
||||
key: paperclipai/bundled/software-development/github-pr-workflow
|
||||
recommendedForRoles:
|
||||
- engineer
|
||||
tags:
|
||||
- github
|
||||
- pull-requests
|
||||
---
|
||||
```
|
||||
|
||||
Optional frontmatter:
|
||||
|
||||
- `slug`
|
||||
- `defaultInstall`
|
||||
- `requires`
|
||||
- `metadata`
|
||||
|
||||
The manifest generator owns `kind`, `category`, `path`, `files`,
|
||||
`trustLevel`, `compatibility`, and `contentHash`.
|
||||
|
||||
## Catalog Validation Rules
|
||||
|
||||
Validation must fail when:
|
||||
|
||||
- A catalog entry is not under `catalog/bundled/<category>/<slug>` or
|
||||
`catalog/optional/<category>/<slug>`.
|
||||
- `SKILL.md` is missing.
|
||||
- `category` or `slug` is not a lowercase URL slug.
|
||||
- `name` or `description` frontmatter is missing or empty.
|
||||
- The frontmatter `key`, when present, does not equal the generated key.
|
||||
- Two catalog entries have the same `id`, `key`, or `slug`.
|
||||
- File inventory includes absolute paths, `..` segments, broken symlinks, or
|
||||
files outside the skill directory.
|
||||
- A file exceeds the package-level size limit chosen by implementation.
|
||||
- A skill marked `compatible` cannot be parsed as Agent Skills markdown.
|
||||
- The generated manifest differs from the checked-in
|
||||
`generated/catalog.json`.
|
||||
|
||||
Trust level is derived from inventory:
|
||||
|
||||
- `scripts_executables` when any file is classified as `script`.
|
||||
- `assets` when any file is classified as `asset` or `other` and no script is
|
||||
present.
|
||||
- `markdown_only` when all files are markdown, references, or `SKILL.md`.
|
||||
|
||||
Validation must report all discovered catalog errors when practical, not just
|
||||
the first one.
|
||||
|
||||
## Catalog API Contract
|
||||
|
||||
Phase D adds read APIs and one company install API.
|
||||
|
||||
```text
|
||||
GET /api/skills/catalog
|
||||
GET /api/skills/catalog/:catalogId
|
||||
GET /api/skills/catalog/:catalogId/files?path=SKILL.md
|
||||
POST /api/companies/:companyId/skills/install-catalog
|
||||
```
|
||||
|
||||
`GET /api/skills/catalog` accepts:
|
||||
|
||||
- `kind=bundled|optional`
|
||||
- `category=<slug>`
|
||||
- `q=<text>`
|
||||
|
||||
`catalogId` is the path-safe manifest `id`. The server should also support
|
||||
resolution by `key` or unique `slug` where the ref is carried in a query or body,
|
||||
but route parameters use `id` to avoid slash handling ambiguity.
|
||||
|
||||
Install request:
|
||||
|
||||
```ts
|
||||
interface CompanySkillInstallCatalogRequest {
|
||||
catalogSkillId: string;
|
||||
slug?: string | null;
|
||||
force?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
Install result:
|
||||
|
||||
```ts
|
||||
interface CompanySkillInstallCatalogResult {
|
||||
action: "created" | "updated" | "unchanged";
|
||||
skill: CompanySkill;
|
||||
catalogSkill: CatalogSkill;
|
||||
warnings: string[];
|
||||
}
|
||||
```
|
||||
|
||||
Install behavior:
|
||||
|
||||
- Creates or updates a company skill with `sourceType="catalog"`.
|
||||
- Uses catalog `key` as the company skill canonical key.
|
||||
- Uses catalog `slug` unless `slug` is provided.
|
||||
- Materializes the catalog files into a company-managed skill directory so
|
||||
existing skill file reads continue to work.
|
||||
- Stores provenance in metadata:
|
||||
- `catalogId`
|
||||
- `catalogKey`
|
||||
- `catalogKind`
|
||||
- `catalogCategory`
|
||||
- `catalogPath`
|
||||
- `packageName`
|
||||
- `packageVersion`
|
||||
- `originHash`
|
||||
- `originVersion`
|
||||
- `userModifiedAt`
|
||||
- `updateHoldReason`
|
||||
- Writes activity log entries for install and update.
|
||||
- Returns `409` for duplicate slug/key conflicts that cannot be resolved safely.
|
||||
- Returns `422` for invalid, incompatible, or hard-blocked catalog entries.
|
||||
- `force` may replace a same-key catalog-managed skill. It must not bypass
|
||||
company boundaries, permission checks, hard validation, or hard security
|
||||
findings.
|
||||
|
||||
## Error Semantics
|
||||
|
||||
Use existing HTTP semantics:
|
||||
|
||||
- `400`: invalid CLI arguments, invalid query/body shape, or malformed refs.
|
||||
- `401`: missing or invalid auth.
|
||||
- `403`: authenticated principal lacks access or mutation permission.
|
||||
- `404`: skill, catalog entry, agent, file, company, or source not found.
|
||||
- `409`: ambiguous slug, duplicate key/slug, update conflict, or unsafe overwrite.
|
||||
- `422`: semantic violation such as invalid skill content or unsupported source.
|
||||
- `500`: unexpected server failure.
|
||||
|
||||
CLI messages should name the next useful correction, for example:
|
||||
|
||||
- `Skill slug "review" is ambiguous. Use an id or key.`
|
||||
- `Company ID is required. Pass --company-id, set PAPERCLIP_COMPANY_ID, or set a context profile.`
|
||||
- `Catalog skill contains executable scripts and cannot be force-installed until security review semantics allow it.`
|
||||
|
||||
## Phase Acceptance Criteria
|
||||
|
||||
Phase A is complete when this contract is available in the repo and the issue
|
||||
thread links it.
|
||||
|
||||
Phase B, CLI MVP:
|
||||
|
||||
- `paperclipai skills --help` exposes the Phase B command group.
|
||||
- All Phase B commands work against existing company skills and agent skills
|
||||
APIs without schema or server changes.
|
||||
- Skill refs resolve by id, key, or unique slug.
|
||||
- Human and JSON output are covered by focused CLI tests.
|
||||
- `doc/CLI.md` documents company install vs agent desired sync vs runtime sync.
|
||||
|
||||
Phase C, catalog package:
|
||||
|
||||
- `packages/skills-catalog` is a workspace package.
|
||||
- Build or validation regenerates `generated/catalog.json`.
|
||||
- Validation covers frontmatter, id/key/slug uniqueness, directory shape, file
|
||||
inventory, trust derivation, and stale generated output.
|
||||
- Server and CLI can import the manifest without crawling arbitrary paths.
|
||||
- Root `skills/` is not expanded with the app-shipped catalog.
|
||||
|
||||
Phase D, catalog APIs:
|
||||
|
||||
- Catalog list/detail/file APIs are read-only and covered by tests.
|
||||
- Install-from-catalog creates auditable company-scoped skill records with
|
||||
provenance metadata and materialized files.
|
||||
- Company boundary and mutation permission checks match or exceed existing
|
||||
company skill mutations.
|
||||
- Duplicate and unsafe overwrite behavior is explicit and tested.
|
||||
|
||||
Phase E, catalog CLI:
|
||||
|
||||
- Operators can browse, search, inspect, and install app-shipped catalog skills.
|
||||
- External source behavior remains routed through `skills import`.
|
||||
- Output and errors follow the Phase B CLI conventions.
|
||||
- Catalog install is clearly distinct from agent attach/sync in help and docs.
|
||||
|
||||
Phase F, update/reset/audit:
|
||||
|
||||
- Security review records decisions for origin hash, user modification detection,
|
||||
reset, audit findings, and force behavior.
|
||||
- Implementation follows the review or records explicit deferrals.
|
||||
- Mutating reset/update actions are activity logged.
|
||||
- Tests cover dangerous findings, force behavior, and unchanged/current states.
|
||||
|
||||
Phase G, adapter truth model:
|
||||
|
||||
- Adapter snapshots accurately report `unsupported`, `persistent`, or
|
||||
`ephemeral`.
|
||||
- Desired, missing, installed, stale, external, and required states are tested.
|
||||
- External adapter plugins remain dynamically loaded. No hardcoded plugin imports
|
||||
are added.
|
||||
|
||||
Phase H, UI:
|
||||
|
||||
- The existing Company Skills page is extended rather than replaced.
|
||||
- UX guidance covers Company, Bundled, Optional, and External source views.
|
||||
- Install preview shows source, trust, provenance, update state, and file
|
||||
inventory.
|
||||
- Agent attach/detach states are clear.
|
||||
- Frontend handoff includes screenshots or equivalent browser evidence.
|
||||
|
||||
Phase I, initial skill content:
|
||||
|
||||
- Bundled and optional entries use the finalized frontmatter and category rules.
|
||||
- Skill descriptions are specific enough for browse/search.
|
||||
- No script-bearing skill lands without explicit security review evidence.
|
||||
- Validation fixtures or tests cover representative content.
|
||||
|
||||
Phase J, QA and docs:
|
||||
|
||||
- QA validates CLI, catalog APIs, UI install, agent sync, portability, and adapter
|
||||
snapshots against a dev instance.
|
||||
- Blocking defects are linked as first-class issues.
|
||||
- `doc/CLI.md`, `doc/DEVELOPING.md`, and skill workflow docs match shipped
|
||||
behavior.
|
||||
|
||||
## Deferrals
|
||||
|
||||
- No cloud marketplace.
|
||||
- No user-home tap registry.
|
||||
- No hidden curator or autonomous catalog mutator.
|
||||
- No normalized `agent_skills` table in the first release.
|
||||
- No skill sets or bundles in the first release.
|
||||
- No automatic install of every optional catalog skill.
|
||||
- No replacement of company import/export as the portability path.
|
||||
Loading…
Add table
Add a link
Reference in a new issue