skillshelf 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +83 -20
  2. package/package.json +8 -2
  3. package/src/adapters/inference/agent.ts +23 -16
  4. package/src/cli.ts +39 -0
  5. package/src/commands/add.ts +624 -128
  6. package/src/commands/adopted.test.ts +144 -0
  7. package/src/commands/agents-config.test.ts +126 -0
  8. package/src/commands/agents.test.ts +96 -0
  9. package/src/commands/agents.ts +243 -0
  10. package/src/commands/drop.ts +21 -13
  11. package/src/commands/import.ts +44 -28
  12. package/src/commands/infer.ts +6 -6
  13. package/src/commands/link.test.ts +160 -0
  14. package/src/commands/link.ts +317 -0
  15. package/src/commands/ls.ts +136 -19
  16. package/src/commands/migrate.test.ts +157 -0
  17. package/src/commands/migrate.ts +260 -0
  18. package/src/commands/mode-surfacing.test.ts +110 -0
  19. package/src/commands/outdated.test.ts +55 -0
  20. package/src/commands/outdated.ts +166 -18
  21. package/src/commands/projects.test.ts +85 -0
  22. package/src/commands/projects.ts +80 -0
  23. package/src/commands/refresh.ts +133 -0
  24. package/src/commands/remediation.test.ts +149 -0
  25. package/src/commands/rename.test.ts +121 -0
  26. package/src/commands/rename.ts +64 -0
  27. package/src/commands/retag.ts +58 -0
  28. package/src/commands/retire.ts +39 -0
  29. package/src/commands/rm.test.ts +133 -0
  30. package/src/commands/rm.ts +107 -0
  31. package/src/commands/roots.ts +41 -0
  32. package/src/commands/scan.ts +122 -30
  33. package/src/commands/show.ts +130 -11
  34. package/src/commands/status.ts +43 -8
  35. package/src/commands/tag.test.ts +109 -0
  36. package/src/commands/tag.ts +68 -0
  37. package/src/commands/track.test.ts +170 -0
  38. package/src/commands/track.ts +340 -0
  39. package/src/commands/unretire.ts +33 -0
  40. package/src/commands/untag.ts +73 -0
  41. package/src/commands/untrack.ts +44 -0
  42. package/src/commands/update.test.ts +71 -0
  43. package/src/commands/update.ts +157 -15
  44. package/src/commands/use.test.ts +122 -0
  45. package/src/commands/use.ts +46 -23
  46. package/src/commands/where.ts +232 -0
  47. package/src/config.test.ts +198 -0
  48. package/src/config.ts +232 -10
  49. package/src/core/agents.test.ts +319 -0
  50. package/src/core/agents.ts +438 -0
  51. package/src/core/bundle.ts +12 -15
  52. package/src/core/core.test.ts +21 -8
  53. package/src/core/crawl.ts +22 -5
  54. package/src/core/dedupe.ts +36 -0
  55. package/src/core/deployments.test.ts +147 -0
  56. package/src/core/deployments.ts +208 -0
  57. package/src/core/fetch.ts +371 -75
  58. package/src/core/indexgen.ts +2 -0
  59. package/src/core/library.test.ts +41 -0
  60. package/src/core/library.ts +61 -16
  61. package/src/core/lifecycle.ts +252 -0
  62. package/src/core/surfaces.ts +46 -0
  63. package/src/core/taxonomy.test.ts +159 -0
  64. package/src/core/taxonomy.ts +190 -0
  65. package/src/lib/fs.ts +2 -2
  66. package/src/types.ts +155 -15
  67. package/src/core/overlay.ts +0 -63
package/README.md CHANGED
@@ -7,21 +7,57 @@
7
7
  [![CI](https://img.shields.io/github/actions/workflow/status/Wang-Cankun/skillshelf/ci.yml?branch=main)](https://github.com/Wang-Cankun/skillshelf/actions)
8
8
  [![npm](https://img.shields.io/npm/v/skillshelf.svg)](https://www.npmjs.com/package/skillshelf)
9
9
 
10
- Your skills are scattered: some in `~/.claude/skills`, some buried in Obsidian or notes
11
- vaults, more copied into a dozen per-project `.claude` directories. You forget which ones
12
- exist, rewrite ones you already have, and copies drift out of sync. The naive fix — dump
13
- everything into `~/.claude/skills` makes every session pay the token cost of loading
14
- hundreds of skill descriptions at once.
10
+ Your skills are scattered across **every agent you use** — some in `~/.claude/skills`, some in
11
+ `~/.codex/skills` or `~/.cursor/skills`, some buried in Obsidian or notes vaults, more copied into
12
+ a dozen per-project `.claude` / `.codex` directories. Each tool scatters its own copies and
13
+ symlinks; you forget which ones exist, rewrite ones you already have, and copies drift out of sync.
14
+ The naive fix dump everything into one agent's dir — makes every session pay the token cost of
15
+ loading hundreds of skill descriptions at once.
16
+
17
+ skillshelf is **agent-agnostic** (Claude Code, Codex, Cursor, and compatible agents): the library
18
+ is a neutral source, and `skl where` maps where every skill is actually deployed across all of
19
+ them — surfacing untracked copies, drift, and dead links. It's the curation layer *over* your
20
+ agent dirs, complementary to installers like [`vercel-labs/skills`](https://github.com/vercel-labs/skills).
15
21
 
16
22
  skillshelf is the middle path: a single git-backed **library** that is a *passive shelf*
17
23
  (nothing auto-loads), plus a CLI to **search, tag, bundle, and load** exactly the skills a
18
24
  project needs, exactly when it needs them. Find anything in one place; pay only for what you
19
25
  actually use.
20
26
 
27
+ ## Desktop app
28
+
29
+ A cross-platform desktop UI (React + Tauri) sits on top of the same engine — a **Library-first**
30
+ workbench for managing where every skill is deployed across your agents. It reads the **real**
31
+ `skl` library (no separate data store) and every toggle writes the same symlinks the CLI does.
32
+
33
+ ![skillshelf — Library view](docs/images/main.png)
34
+
35
+ - **Library-first, skill-centric list** — scan all skills, flip them on/off per agent inline; a
36
+ top **scope switcher** (Global · each project · + Add project) and a per-scope count bar.
37
+ - **Project scopes** — manage a project's loadout without leaving the app; globally-deployed
38
+ skills show an *"active via Global"* inherited state so you always know what's effectively live.
39
+ - **Two-tier toggles** — a clean on/off for the happy path, but drift / copy / dead / aliased
40
+ surface a resolve flow instead of silently doing the wrong thing (state is derived from the
41
+ filesystem, never stored — so the UI can't lie about what's deployed).
42
+
43
+ ![skillshelf — skill detail drawer](docs/images/detail.png)
44
+
45
+ The detail drawer shows a skill's body, tags, provenance, and an `agent × scope` deployment
46
+ matrix (Global + the projects where it's pinned), plus lifecycle actions.
47
+
48
+ > Browser/dev mode (`cd app && bun run dev`) renders synthetic fixtures with no backend; the
49
+ > packaged desktop app talks to your real `skl` engine. See [`docs/adr/0010-*`](docs/adr/) for the
50
+ > design.
51
+
21
52
  ## Install
22
53
 
23
54
  skillshelf runs on [Bun](https://bun.sh) (>= 1.0). No other runtime dependencies.
24
55
 
56
+ > **Bun is required, not optional.** The `skl` bin is a TypeScript entrypoint with a
57
+ > `#!/usr/bin/env bun` shebang — there is no compiled Node build. `npm i -g skillshelf`
58
+ > will *not* give you a working `skl` (a `preinstall` guard aborts with a pointer to Bun);
59
+ > use `bunx` or `bun add -g` instead.
60
+
25
61
  ```bash
26
62
  # Run it without installing
27
63
  bunx skillshelf <command>
@@ -74,7 +110,7 @@ skl scan # report every candidate + duplicate/drift group
74
110
  # 2. Adopt the ones you want, one at a time. Each import moves the skill into the
75
111
  # library and leaves a symlink behind so old paths keep resolving.
76
112
  skl import rnaseq-qc --from ~/.claude/skills/rnaseq-qc
77
- skl import xhs-title --from ~/notes/.agents/skills/xhs-title
113
+ skl import headline-picker --from ~/notes/.agents/skills/headline-picker
78
114
 
79
115
  # For a skill living inside a project repo, copy instead of move (no symlink left behind):
80
116
  skl import deploy-check --from ~/projects/web/.claude/skills/deploy-check --copy
@@ -82,6 +118,10 @@ skl import deploy-check --from ~/projects/web/.claude/skills/deploy-check --copy
82
118
  # When two copies drifted and you've picked the winner, overwrite the loser:
83
119
  skl import rnaseq-qc --from ~/projects/lab/.claude/skills/rnaseq-qc --force
84
120
 
121
+ # For a skill you actively develop in its own git repo, shelve a LINK instead of a copy —
122
+ # the repo stays canonical and edits show up live, no drift, no re-sync (ADR-0004):
123
+ skl link --from ~/Documents/GitHub/claim-log/skill/claim-log
124
+
85
125
  # 3. Tag the now-populated library in one pass. Domain is tags, not folders, so this
86
126
  # runs AFTER import with no reorg — no skill ever has to move because a tag changed.
87
127
  skl infer --emit # hand the payload to your agent, then `skl infer --apply`
@@ -108,9 +148,17 @@ skillshelf separates *owning* a skill from *loading* it.
108
148
  - **On-demand `show`** — prints only the SKILL.md instruction body and lists the paths of
109
149
  any bundled reference files (without reading them). Progressive disclosure: cheap by
110
150
  default, deep when you ask. Works mid-task with no reload.
111
- - **Sidecar overlay** installed third-party skills keep a pristine `upstream/` body plus a
112
- `<skill>.shelf.json` overlay holding *your* tags, bundle membership, and notes. `skl update`
113
- swaps the upstream body cleanly while your taxonomy survives updates never clobber your tags.
151
+ - **Owned vs linked entries** ([ADR-0004](./docs/adr/0004-owned-vs-linked-entries.md)) the
152
+ library is a *bookshelf*: an entry either **owns** its bytes (a real copy; the library is
153
+ canonical for downloads and stabilized skills) or is **linked** (a symlink to an external dev
154
+ repo that stays canonical — for skills you actively develop in their own git, e.g. `claim-log`).
155
+ `skl link --from <dev-repo>` registers a linked entry; `skl where` shows it as a clean
156
+ `✓ source`; `skl update` / `outdated` skip linked entries so they never pull upstream into your
157
+ dev repo. The mode is derived from the filesystem (a symlink resolving outside the library),
158
+ never stored, so it can't go stale.
159
+ - **Updates never clobber your tags** — domain tags live in the central `taxonomy.json`
160
+ ([ADR-0002](./docs/adr/0002-central-taxonomy-not-sidecars.md)), separate from the skill body, so
161
+ `skl update` can swap an owned skill's upstream `SKILL.md` cleanly while your taxonomy survives.
114
162
 
115
163
  ```
116
164
  skl search / ls / show skl use <bundle>
@@ -130,22 +178,37 @@ See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) for the full design.
130
178
  | Command | Summary | Key flags |
131
179
  |---|---|---|
132
180
  | `skl init` | Set up `~/.skillshelf` config + library and link the global-core skills | `--force` |
133
- | `skl scan [roots…]` | Read-only discovery of skill candidates across roots (counts, duplicates, drift) | `--add-root <path>` |
134
- | `skl import <name> --from <path>` | Adopt your own skill into the library (move + symlink-back, or `--copy`) | `--copy`, `--as <slug>`, `--force` |
181
+ | `skl scan [roots…]` | Read-only discovery of skill candidates across roots (counts, duplicates, drift) | `--add-root <path>`, `--remove-root <path>` |
182
+ | `skl roots` | List the persisted scan roots (read-only; no crawl) | |
183
+ | `skl import <name> --from <path>` | Adopt your own skill into the library as an OWNED copy (move + symlink-back, or `--copy`) | `--copy`, `--as <slug>`, `--force` |
184
+ | `skl link [<name>] --from <dev-repo>` | Shelve a dev-repo skill as a LINKED entry (library symlinks to it; the repo stays canonical). `--at <path>` instead collapses a stray copy into the library | `--from`, `--at`, `--force` |
135
185
  | `skl new <name>` | Scaffold a new skill dir + SKILL.md into the library | `--domain <d>`, `--desc "..."`, `--force` |
136
- | `skl ls [bundle]` | One-line listing of the library, or one bundle | `--all` |
186
+ | `skl ls [bundle]` | One-line listing of the library, or one bundle (`--json` carries `mode`/`linkTarget`) | `--all` |
137
187
  | `skl search <kw...>` | Fuzzy match over name + description + domains across the library | — |
138
188
  | `skl show <name>` | Print a skill's SKILL.md body; list reference-file paths (not contents) | — |
139
- | `skl status` | Show which library skills are linked into `./.claude/skills` | — |
140
- | `skl use <bundle>` | Symlink a bundle's skills into `./.claude/skills/` (hot-loads) | — |
141
- | `skl drop <bundle>` | Remove a bundle's symlinks from `./.claude/skills/` | — |
142
- | `skl add <src>` | Install a third-party skill (`github:`/registry), record provenance, auto-tag | `--domain <d>`, `--name <slug>`, `--no-infer`, `--force` |
143
- | `skl outdated [name]` | Check upstream ref per tracked skill and mark stale ones | — |
144
- | `skl update [name]` | Re-pull upstream body, preserve overlay, diff if local body diverged | `--force`, `--dry-run` |
189
+ | `skl tag <name> <domain>…` | Add domain tag(s) to a skill in the central taxonomy (deterministic, no LLM) | — |
190
+ | `skl untag <name> <domain>` | Remove a domain tag from a skill | — |
191
+ | `skl retag <old> <new>` | Rename a domain across the whole library taxonomy (deterministic) | — |
192
+ | `skl rename <old> <new>` | Rename a skill slug atomically (dir + frontmatter + taxonomy + lock). Alias `skl mv` | — |
193
+ | `skl retire <name>` | Soft-delete a skill into `_retired/` (reversible; excluded from deploys) | — |
194
+ | `skl unretire <name>` | Restore a retired skill back to the active library | |
195
+ | `skl rm <name>` | Delete a skill (dir/symlink + taxonomy + lock), re-index. Refuses a live OWNED skill without `--force`; a LINKED entry `rm`s freely (safe unlink) | `--force`, `--dry-run` |
196
+ | `skl status` | Show which library skills are linked into `./.claude/skills`; flags unmanaged real copies (drift-prone) | — |
197
+ | `skl where [name]` | Map where each skill is deployed across all agents (Claude, Codex, Cursor…); flags copies, drift, 2nd-sources, dead links — a dev repo a library entry links to shows as a clean `✓ source` | `--problems`, `--prune`, `--fix`, `--dry-run` |
198
+ | `skl use <bundle\|skill>` | Symlink a bundle (or a single skill) into `./.claude/skills/` (hot-loads) | — |
199
+ | `skl drop <bundle\|skill>` | Remove a bundle's (or single skill's) symlinks from `./.claude/skills/` | — |
200
+ | `skl refresh` | Re-sync this project's `./.claude/skills` symlinks to current library reality (repoint stale, prune vanished) | `--dry-run` |
201
+ | `skl add <src>` | Install third-party skill(s) into the library (librarian only — no agent-dir writes). One repo = **one clone**: a bare repo with several skills needs `--all`/`--skill`/`--list`; single-skill `add <repo>/<path>` is unchanged. `--list` discovers + prints; `--dry-run` previews drift (new/identical/differs); a `differs` skill is skipped without `--force` | `--all`, `--skill <a,b>`, `--list`, `--dry-run`, `--domain <d>`, `--name <slug>`, `--no-infer`, `--force` |
202
+ | `skl outdated [name]` | Check upstream ref per tracked skill and mark stale ones (LINKED dev-repo entries are reported, never probed); `--check-local` diffs the local body against its baseline offline | `--check-local` |
203
+ | `skl update [name]` | Re-pull upstream body, preserve domain tags, diff if local body diverged (LINKED entries are skipped — their own git owns versioning) | `--force`, `--dry-run` |
145
204
  | `skl index` | Regenerate `INDEX.md` (catalog grouped by domain) | — |
146
205
  | `skl infer` | Re-run AI domain taxonomy over the library (emit/apply/provider modes) | see below |
147
206
 
148
- Every command also accepts `--json`.
207
+ Every command also accepts `--json`. Destructive/edit verbs (`rm`, `retire`/`unretire`,
208
+ `rename`, `tag`/`untag`/`retag`, `scan --remove-root`, `where --prune`/`--fix`,
209
+ `refresh`) are the inverse + fine-grained-edit family from
210
+ [ADR-0005](./docs/adr/0005-inverse-and-edit-verbs.md): reversible by default, transactional
211
+ across the skill dir + `taxonomy.json` + `shelf.lock.json` + `INDEX.md`.
149
212
 
150
213
  ## AI taxonomy & inference
151
214
 
@@ -163,7 +226,7 @@ skl infer [--emit | --apply <file.json> | --provider <name>] \
163
226
  - `--emit` — print a self-contained prompt + the library payload as JSON. Hand it to whatever
164
227
  agent or model you already have open; it does the reasoning.
165
228
  - `--apply <file.json>` — apply the taxonomy proposal the agent produced back into the library
166
- (for review/approval), updating tags via the overlay.
229
+ (for review/approval), writing tags into the central `taxonomy.json`.
167
230
 
168
231
  **API mode (skillshelf calls an OpenAI-compatible endpoint itself):**
169
232
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillshelf",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Agent-first skill registry + manager for Claude Code and compatible agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  "skl": "./src/cli.ts"
26
26
  },
27
27
  "scripts": {
28
+ "preinstall": "bun --version >/dev/null 2>&1 || { echo '\\nskillshelf requires the Bun runtime (https://bun.sh) — its bin is a TypeScript entrypoint, not a compiled Node script.\\nInstall Bun, then: bun add -g skillshelf (or run without installing: bunx skillshelf <command>)\\n' >&2; exit 1; }",
28
29
  "skl": "bun run src/cli.ts",
29
30
  "test": "bun test"
30
31
  },
@@ -38,5 +39,10 @@
38
39
  "src",
39
40
  "README.md",
40
41
  "LICENSE"
41
- ]
42
+ ],
43
+ "devDependencies": {
44
+ "@types/bun": "^1.3.14",
45
+ "@types/node": "22",
46
+ "typescript": "5.6"
47
+ }
42
48
  }
@@ -4,15 +4,15 @@
4
4
  // - emit : assemble an InferenceCorpus + JSON schema + instruction and print it
5
5
  // to stdout so the HOST agent (Claude Code) can reason over it and
6
6
  // produce a proposal file.
7
- // - apply: read the agent's proposal JSON and write proposed domains/tags into
8
- // each skill's `<name>.shelf.json` overlay (never upstream SKILL.md).
7
+ // - apply: read the agent's proposal JSON and write proposed domains into the
8
+ // central taxonomy.json (never upstream SKILL.md).
9
9
  //
10
10
  // The api.ts adapter reuses buildCorpus() + applyProposal() to close the loop
11
11
  // automatically against any OpenAI-compatible LLM endpoint.
12
12
 
13
- import type { InferenceCorpus, Overlay, Skill } from "../../types.ts";
13
+ import type { InferenceCorpus, Skill } from "../../types.ts";
14
14
  import { listDomains } from "../../core/library.ts";
15
- import { readOverlay, writeOverlay } from "../../core/overlay.ts";
15
+ import { readTaxonomy, writeTaxonomy, domainsForName } from "../../core/taxonomy.ts";
16
16
  import { parseFrontmatter } from "../../lib/frontmatter.ts";
17
17
 
18
18
  /** Max characters of SKILL.md body included per skill in the corpus preview. */
@@ -20,8 +20,9 @@ const BODY_PREVIEW_CHARS = 1200;
20
20
 
21
21
  /**
22
22
  * The proposal shape the host agent (or the gateway) must return: a map of
23
- * skill name -> proposed domains, plus optional primary + notes. Authors apply
24
- * `domains` into each overlay (unioned with existing, never destructive).
23
+ * skill name -> proposed domains, plus optional primary + notes. Applying unions
24
+ * `domains` into the central taxonomy (per skill, never destructive). `notes` is
25
+ * accepted for backward compatibility but is no longer persisted (ADR-0002).
25
26
  */
26
27
  export interface InferenceProposalEntry {
27
28
  name: string;
@@ -82,7 +83,7 @@ export const INFER_INSTRUCTION = [
82
83
  "domain plus any honest secondary tags (a dual-use skill belongs to multiple).",
83
84
  "Keep domain tokens lowercase and hyphenated.",
84
85
  "Return ONE JSON object that validates against `schema` (no prose, no markdown",
85
- "fences). Then run `skl infer --apply <file.json>` to write it into the overlays.",
86
+ "fences). Then run `skl infer --apply <file.json>` to write it into taxonomy.json.",
86
87
  ].join(" ");
87
88
 
88
89
  /** Build the deterministic InferenceCorpus snapshot from loaded skills. */
@@ -185,7 +186,7 @@ export function normalizeProposal(raw: unknown): InferenceProposal {
185
186
  }
186
187
 
187
188
  export interface ApplyResult {
188
- /** skill name -> domains written into its overlay */
189
+ /** skill name -> domains written into the taxonomy for that skill */
189
190
  applied: Array<{ name: string; domains: string[]; added: string[] }>;
190
191
  /** assignment names with no matching skill in the library */
191
192
  unmatched: string[];
@@ -194,11 +195,13 @@ export interface ApplyResult {
194
195
  }
195
196
 
196
197
  /**
197
- * Apply a proposal into each skill's overlay. Domains are UNIONED with the
198
- * skill's existing effective domains (never destructive), written to
199
- * `<name>.shelf.json` only — upstream SKILL.md is never touched.
198
+ * Apply a proposal into the central taxonomy (`<library>/taxonomy.json`). For each
199
+ * assignment, domains are UNIONED with the skill's EXISTING taxonomy entry (never
200
+ * destructive); upstream SKILL.md is never touched. `notes` is dropped (ADR-0002).
201
+ * The taxonomy is read once and written once at the end.
200
202
  */
201
203
  export async function applyProposal(
204
+ libraryPath: string,
202
205
  skills: Skill[],
203
206
  proposal: InferenceProposal,
204
207
  ): Promise<ApplyResult> {
@@ -209,6 +212,8 @@ export async function applyProposal(
209
212
  if (!existing || (existing.mirrorOf && !s.mirrorOf)) byName.set(s.name, s);
210
213
  }
211
214
 
215
+ const tax = await readTaxonomy(libraryPath);
216
+
212
217
  const applied: ApplyResult["applied"] = [];
213
218
  const unmatched: string[] = [];
214
219
  const skipped: string[] = [];
@@ -232,8 +237,9 @@ export async function applyProposal(
232
237
  continue;
233
238
  }
234
239
 
235
- const prev = await readOverlay(skill);
236
- const existingDomains = Array.isArray(prev?.domains) ? prev!.domains : [];
240
+ // Union with the skill's EXISTING taxonomy domains (non-destructive). Resolve
241
+ // by the canonical skill name so mirror copies map to the same entry.
242
+ const existingDomains = domainsForName(tax, skill.name);
237
243
  const merged: string[] = [...existingDomains];
238
244
  const added: string[] = [];
239
245
  for (const d of ordered) {
@@ -243,11 +249,12 @@ export async function applyProposal(
243
249
  }
244
250
  }
245
251
 
246
- const next: Overlay = { ...(prev ?? {}), domains: merged };
247
- if (a.notes) next.notes = a.notes;
248
- await writeOverlay(skill, next);
252
+ tax.skills[skill.name] = merged;
249
253
  applied.push({ name: a.name, domains: merged, added });
250
254
  }
251
255
 
256
+ // Persist the whole taxonomy once.
257
+ await writeTaxonomy(libraryPath, tax);
258
+
252
259
  return { applied, unmatched, skipped };
253
260
  }
package/src/cli.ts CHANGED
@@ -28,19 +28,51 @@ import * as update from "./commands/update.ts";
28
28
  import * as infer from "./commands/infer.ts";
29
29
  import * as newCmd from "./commands/new.ts";
30
30
  import * as scan from "./commands/scan.ts";
31
+ import * as roots from "./commands/roots.ts";
32
+ import * as projects from "./commands/projects.ts";
31
33
  import * as importCmd from "./commands/import.ts";
34
+ import * as link from "./commands/link.ts";
35
+ import * as track from "./commands/track.ts";
36
+ import * as untrack from "./commands/untrack.ts";
37
+ import * as migrate from "./commands/migrate.ts";
38
+ import * as where from "./commands/where.ts";
39
+ import * as agents from "./commands/agents.ts";
40
+ import * as tag from "./commands/tag.ts";
41
+ import * as untag from "./commands/untag.ts";
42
+ import * as retag from "./commands/retag.ts";
43
+ import * as rm from "./commands/rm.ts";
44
+ import * as retire from "./commands/retire.ts";
45
+ import * as unretire from "./commands/unretire.ts";
46
+ import * as rename from "./commands/rename.ts";
47
+ import * as refresh from "./commands/refresh.ts";
32
48
 
33
49
  // Registration order = display order in help.
34
50
  const MODULES: CommandModule[] = [
35
51
  search,
36
52
  ls,
37
53
  status,
54
+ where,
55
+ agents,
38
56
  show,
39
57
  use,
40
58
  drop,
59
+ refresh,
41
60
  add,
42
61
  scan,
62
+ roots,
63
+ projects,
43
64
  importCmd,
65
+ link,
66
+ track,
67
+ untrack,
68
+ migrate,
69
+ tag,
70
+ untag,
71
+ retag,
72
+ retire,
73
+ unretire,
74
+ rename,
75
+ rm,
44
76
  outdated,
45
77
  update,
46
78
  init,
@@ -54,6 +86,13 @@ for (const mod of MODULES) {
54
86
  COMMANDS.set(mod.meta.name, mod);
55
87
  }
56
88
 
89
+ // Command aliases (not shown in the help listing; resolve to the canonical module).
90
+ const ALIASES: Record<string, string> = { mv: "rename" };
91
+ for (const [alias, target] of Object.entries(ALIASES)) {
92
+ const mod = COMMANDS.get(target);
93
+ if (mod) COMMANDS.set(alias, mod);
94
+ }
95
+
57
96
  function helpText(): string {
58
97
  const lines: string[] = [];
59
98
  lines.push("skl — skillshelf: agent-first skill registry + manager");