skillshelf 0.2.0 → 0.3.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.
- package/README.md +57 -19
- package/package.json +8 -2
- package/src/adapters/inference/agent.ts +23 -16
- package/src/cli.ts +31 -0
- package/src/commands/add.ts +624 -128
- package/src/commands/agents.ts +120 -0
- package/src/commands/drop.ts +21 -13
- package/src/commands/import.ts +44 -28
- package/src/commands/infer.ts +6 -6
- package/src/commands/link.test.ts +160 -0
- package/src/commands/link.ts +317 -0
- package/src/commands/ls.ts +118 -18
- package/src/commands/mode-surfacing.test.ts +110 -0
- package/src/commands/outdated.test.ts +55 -0
- package/src/commands/outdated.ts +138 -18
- package/src/commands/refresh.ts +133 -0
- package/src/commands/remediation.test.ts +149 -0
- package/src/commands/rename.test.ts +121 -0
- package/src/commands/rename.ts +64 -0
- package/src/commands/retag.ts +58 -0
- package/src/commands/retire.ts +39 -0
- package/src/commands/rm.test.ts +133 -0
- package/src/commands/rm.ts +107 -0
- package/src/commands/roots.ts +41 -0
- package/src/commands/scan.ts +122 -30
- package/src/commands/show.ts +4 -1
- package/src/commands/status.ts +43 -8
- package/src/commands/tag.test.ts +109 -0
- package/src/commands/tag.ts +68 -0
- package/src/commands/unretire.ts +33 -0
- package/src/commands/untag.ts +73 -0
- package/src/commands/update.test.ts +71 -0
- package/src/commands/update.ts +65 -15
- package/src/commands/use.test.ts +92 -0
- package/src/commands/use.ts +46 -23
- package/src/commands/where.ts +232 -0
- package/src/config.test.ts +69 -0
- package/src/config.ts +79 -10
- package/src/core/agents.test.ts +232 -0
- package/src/core/agents.ts +363 -0
- package/src/core/bundle.ts +12 -15
- package/src/core/core.test.ts +14 -1
- package/src/core/crawl.ts +22 -5
- package/src/core/dedupe.ts +36 -0
- package/src/core/deployments.test.ts +147 -0
- package/src/core/deployments.ts +208 -0
- package/src/core/fetch.ts +344 -70
- package/src/core/indexgen.ts +2 -0
- package/src/core/library.test.ts +41 -0
- package/src/core/library.ts +61 -16
- package/src/core/lifecycle.ts +252 -0
- package/src/core/surfaces.ts +46 -0
- package/src/core/taxonomy.test.ts +159 -0
- package/src/core/taxonomy.ts +190 -0
- package/src/lib/fs.ts +2 -2
- package/src/types.ts +85 -15
- package/src/core/overlay.ts +0 -63
package/README.md
CHANGED
|
@@ -7,11 +7,17 @@
|
|
|
7
7
|
[](https://github.com/Wang-Cankun/skillshelf/actions)
|
|
8
8
|
[](https://www.npmjs.com/package/skillshelf)
|
|
9
9
|
|
|
10
|
-
Your skills are scattered
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
@@ -22,6 +28,11 @@ actually use.
|
|
|
22
28
|
|
|
23
29
|
skillshelf runs on [Bun](https://bun.sh) (>= 1.0). No other runtime dependencies.
|
|
24
30
|
|
|
31
|
+
> **Bun is required, not optional.** The `skl` bin is a TypeScript entrypoint with a
|
|
32
|
+
> `#!/usr/bin/env bun` shebang — there is no compiled Node build. `npm i -g skillshelf`
|
|
33
|
+
> will *not* give you a working `skl` (a `preinstall` guard aborts with a pointer to Bun);
|
|
34
|
+
> use `bunx` or `bun add -g` instead.
|
|
35
|
+
|
|
25
36
|
```bash
|
|
26
37
|
# Run it without installing
|
|
27
38
|
bunx skillshelf <command>
|
|
@@ -82,6 +93,10 @@ skl import deploy-check --from ~/projects/web/.claude/skills/deploy-check --copy
|
|
|
82
93
|
# When two copies drifted and you've picked the winner, overwrite the loser:
|
|
83
94
|
skl import rnaseq-qc --from ~/projects/lab/.claude/skills/rnaseq-qc --force
|
|
84
95
|
|
|
96
|
+
# For a skill you actively develop in its own git repo, shelve a LINK instead of a copy —
|
|
97
|
+
# the repo stays canonical and edits show up live, no drift, no re-sync (ADR-0004):
|
|
98
|
+
skl link --from ~/Documents/GitHub/cairn/skill/cairn
|
|
99
|
+
|
|
85
100
|
# 3. Tag the now-populated library in one pass. Domain is tags, not folders, so this
|
|
86
101
|
# runs AFTER import with no reorg — no skill ever has to move because a tag changed.
|
|
87
102
|
skl infer --emit # hand the payload to your agent, then `skl infer --apply`
|
|
@@ -108,9 +123,17 @@ skillshelf separates *owning* a skill from *loading* it.
|
|
|
108
123
|
- **On-demand `show`** — prints only the SKILL.md instruction body and lists the paths of
|
|
109
124
|
any bundled reference files (without reading them). Progressive disclosure: cheap by
|
|
110
125
|
default, deep when you ask. Works mid-task with no reload.
|
|
111
|
-
- **
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
- **Owned vs linked entries** ([ADR-0004](./docs/adr/0004-owned-vs-linked-entries.md)) — the
|
|
127
|
+
library is a *bookshelf*: an entry either **owns** its bytes (a real copy; the library is
|
|
128
|
+
canonical — for downloads and stabilized skills) or is **linked** (a symlink to an external dev
|
|
129
|
+
repo that stays canonical — for skills you actively develop in their own git, e.g. `cairn`).
|
|
130
|
+
`skl link --from <dev-repo>` registers a linked entry; `skl where` shows it as a clean
|
|
131
|
+
`✓ source`; `skl update` / `outdated` skip linked entries so they never pull upstream into your
|
|
132
|
+
dev repo. The mode is derived from the filesystem (a symlink resolving outside the library),
|
|
133
|
+
never stored, so it can't go stale.
|
|
134
|
+
- **Updates never clobber your tags** — domain tags live in the central `taxonomy.json`
|
|
135
|
+
([ADR-0002](./docs/adr/0002-central-taxonomy-not-sidecars.md)), separate from the skill body, so
|
|
136
|
+
`skl update` can swap an owned skill's upstream `SKILL.md` cleanly while your taxonomy survives.
|
|
114
137
|
|
|
115
138
|
```
|
|
116
139
|
skl search / ls / show skl use <bundle>
|
|
@@ -130,22 +153,37 @@ See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) for the full design.
|
|
|
130
153
|
| Command | Summary | Key flags |
|
|
131
154
|
|---|---|---|
|
|
132
155
|
| `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
|
|
156
|
+
| `skl scan [roots…]` | Read-only discovery of skill candidates across roots (counts, duplicates, drift) | `--add-root <path>`, `--remove-root <path>` |
|
|
157
|
+
| `skl roots` | List the persisted scan roots (read-only; no crawl) | — |
|
|
158
|
+
| `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` |
|
|
159
|
+
| `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
160
|
| `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` |
|
|
161
|
+
| `skl ls [bundle]` | One-line listing of the library, or one bundle (`--json` carries `mode`/`linkTarget`) | `--all` |
|
|
137
162
|
| `skl search <kw...>` | Fuzzy match over name + description + domains across the library | — |
|
|
138
163
|
| `skl show <name>` | Print a skill's SKILL.md body; list reference-file paths (not contents) | — |
|
|
139
|
-
| `skl
|
|
140
|
-
| `skl
|
|
141
|
-
| `skl
|
|
142
|
-
| `skl
|
|
143
|
-
| `skl
|
|
144
|
-
| `skl
|
|
164
|
+
| `skl tag <name> <domain>…` | Add domain tag(s) to a skill in the central taxonomy (deterministic, no LLM) | — |
|
|
165
|
+
| `skl untag <name> <domain>` | Remove a domain tag from a skill | — |
|
|
166
|
+
| `skl retag <old> <new>` | Rename a domain across the whole library taxonomy (deterministic) | — |
|
|
167
|
+
| `skl rename <old> <new>` | Rename a skill slug atomically (dir + frontmatter + taxonomy + lock). Alias `skl mv` | — |
|
|
168
|
+
| `skl retire <name>` | Soft-delete a skill into `_retired/` (reversible; excluded from deploys) | — |
|
|
169
|
+
| `skl unretire <name>` | Restore a retired skill back to the active library | — |
|
|
170
|
+
| `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` |
|
|
171
|
+
| `skl status` | Show which library skills are linked into `./.claude/skills`; flags unmanaged real copies (drift-prone) | — |
|
|
172
|
+
| `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` |
|
|
173
|
+
| `skl use <bundle\|skill>` | Symlink a bundle (or a single skill) into `./.claude/skills/` (hot-loads) | — |
|
|
174
|
+
| `skl drop <bundle\|skill>` | Remove a bundle's (or single skill's) symlinks from `./.claude/skills/` | — |
|
|
175
|
+
| `skl refresh` | Re-sync this project's `./.claude/skills` symlinks to current library reality (repoint stale, prune vanished) | `--dry-run` |
|
|
176
|
+
| `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` |
|
|
177
|
+
| `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` |
|
|
178
|
+
| `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
179
|
| `skl index` | Regenerate `INDEX.md` (catalog grouped by domain) | — |
|
|
146
180
|
| `skl infer` | Re-run AI domain taxonomy over the library (emit/apply/provider modes) | see below |
|
|
147
181
|
|
|
148
|
-
Every command also accepts `--json`.
|
|
182
|
+
Every command also accepts `--json`. Destructive/edit verbs (`rm`, `retire`/`unretire`,
|
|
183
|
+
`rename`, `tag`/`untag`/`retag`, `scan --remove-root`, `where --prune`/`--fix`,
|
|
184
|
+
`refresh`) are the inverse + fine-grained-edit family from
|
|
185
|
+
[ADR-0005](./docs/adr/0005-inverse-and-edit-verbs.md): reversible by default, transactional
|
|
186
|
+
across the skill dir + `taxonomy.json` + `shelf.lock.json` + `INDEX.md`.
|
|
149
187
|
|
|
150
188
|
## AI taxonomy & inference
|
|
151
189
|
|
|
@@ -163,7 +201,7 @@ skl infer [--emit | --apply <file.json> | --provider <name>] \
|
|
|
163
201
|
- `--emit` — print a self-contained prompt + the library payload as JSON. Hand it to whatever
|
|
164
202
|
agent or model you already have open; it does the reasoning.
|
|
165
203
|
- `--apply <file.json>` — apply the taxonomy proposal the agent produced back into the library
|
|
166
|
-
(for review/approval),
|
|
204
|
+
(for review/approval), writing tags into the central `taxonomy.json`.
|
|
167
205
|
|
|
168
206
|
**API mode (skillshelf calls an OpenAI-compatible endpoint itself):**
|
|
169
207
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillshelf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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
|
|
8
|
-
//
|
|
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,
|
|
13
|
+
import type { InferenceCorpus, Skill } from "../../types.ts";
|
|
14
14
|
import { listDomains } from "../../core/library.ts";
|
|
15
|
-
import {
|
|
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.
|
|
24
|
-
* `domains` into
|
|
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
|
|
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
|
|
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
|
|
198
|
-
* skill's
|
|
199
|
-
*
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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,43 @@ 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";
|
|
31
32
|
import * as importCmd from "./commands/import.ts";
|
|
33
|
+
import * as link from "./commands/link.ts";
|
|
34
|
+
import * as where from "./commands/where.ts";
|
|
35
|
+
import * as agents from "./commands/agents.ts";
|
|
36
|
+
import * as tag from "./commands/tag.ts";
|
|
37
|
+
import * as untag from "./commands/untag.ts";
|
|
38
|
+
import * as retag from "./commands/retag.ts";
|
|
39
|
+
import * as rm from "./commands/rm.ts";
|
|
40
|
+
import * as retire from "./commands/retire.ts";
|
|
41
|
+
import * as unretire from "./commands/unretire.ts";
|
|
42
|
+
import * as rename from "./commands/rename.ts";
|
|
43
|
+
import * as refresh from "./commands/refresh.ts";
|
|
32
44
|
|
|
33
45
|
// Registration order = display order in help.
|
|
34
46
|
const MODULES: CommandModule[] = [
|
|
35
47
|
search,
|
|
36
48
|
ls,
|
|
37
49
|
status,
|
|
50
|
+
where,
|
|
51
|
+
agents,
|
|
38
52
|
show,
|
|
39
53
|
use,
|
|
40
54
|
drop,
|
|
55
|
+
refresh,
|
|
41
56
|
add,
|
|
42
57
|
scan,
|
|
58
|
+
roots,
|
|
43
59
|
importCmd,
|
|
60
|
+
link,
|
|
61
|
+
tag,
|
|
62
|
+
untag,
|
|
63
|
+
retag,
|
|
64
|
+
retire,
|
|
65
|
+
unretire,
|
|
66
|
+
rename,
|
|
67
|
+
rm,
|
|
44
68
|
outdated,
|
|
45
69
|
update,
|
|
46
70
|
init,
|
|
@@ -54,6 +78,13 @@ for (const mod of MODULES) {
|
|
|
54
78
|
COMMANDS.set(mod.meta.name, mod);
|
|
55
79
|
}
|
|
56
80
|
|
|
81
|
+
// Command aliases (not shown in the help listing; resolve to the canonical module).
|
|
82
|
+
const ALIASES: Record<string, string> = { mv: "rename" };
|
|
83
|
+
for (const [alias, target] of Object.entries(ALIASES)) {
|
|
84
|
+
const mod = COMMANDS.get(target);
|
|
85
|
+
if (mod) COMMANDS.set(alias, mod);
|
|
86
|
+
}
|
|
87
|
+
|
|
57
88
|
function helpText(): string {
|
|
58
89
|
const lines: string[] = [];
|
|
59
90
|
lines.push("skl — skillshelf: agent-first skill registry + manager");
|