skillrepo 3.2.0 → 4.0.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 +90 -27
- package/bin/skillrepo.mjs +5 -5
- package/package.json +1 -1
- package/src/commands/add.mjs +21 -6
- package/src/commands/get.mjs +20 -4
- package/src/commands/init-session-sync.mjs +1 -1
- package/src/commands/init.mjs +435 -111
- package/src/commands/list.mjs +1 -1
- package/src/commands/remove.mjs +10 -2
- package/src/commands/uninstall.mjs +1 -1
- package/src/commands/update.mjs +15 -3
- package/src/lib/agent-registry.mjs +215 -0
- package/src/lib/cli-config.mjs +146 -44
- package/src/lib/detect-agents.mjs +112 -0
- package/src/lib/file-write.mjs +162 -77
- package/src/lib/mcp-merge.mjs +17 -36
- package/src/lib/mergers/gitignore.mjs +55 -28
- package/src/lib/paths.mjs +27 -25
- package/src/lib/prompt-multiselect.mjs +324 -0
- package/src/lib/sync.mjs +18 -19
- package/src/test/commands/add.test.mjs +18 -3
- package/src/test/commands/init-picker.test.mjs +144 -0
- package/src/test/commands/init.test.mjs +228 -42
- package/src/test/commands/remove.test.mjs +4 -1
- package/src/test/commands/update.test.mjs +13 -3
- package/src/test/e2e/cli-agent-permutations.test.mjs +631 -0
- package/src/test/e2e/cli-commands.test.mjs +39 -13
- package/src/test/integration/file-write.integration.test.mjs +31 -10
- package/src/test/lib/agent-registry.test.mjs +215 -0
- package/src/test/lib/cli-config.test.mjs +222 -38
- package/src/test/lib/detect-agents.test.mjs +336 -0
- package/src/test/lib/file-write-placement.test.mjs +264 -0
- package/src/test/lib/file-write.test.mjs +231 -30
- package/src/test/lib/mcp-merge.test.mjs +23 -15
- package/src/test/lib/paths.test.mjs +53 -17
- package/src/test/lib/prompt-multiselect.test.mjs +448 -0
- package/src/test/lib/sync.test.mjs +157 -0
- package/src/lib/detect-ides.mjs +0 -44
- package/src/test/detect-ides.test.mjs +0 -65
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ A pull-based CLI for managing your agent-skills library from the terminal.
|
|
|
6
6
|
npx skillrepo init
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Validates your access key,
|
|
10
|
-
and pulls your library. Safe to re-run at any time.
|
|
9
|
+
Validates your access key, picks which agents to configure, wires up the
|
|
10
|
+
MCP config, and pulls your library. Safe to re-run at any time.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
@@ -34,8 +34,10 @@ Requires Node.js 18 or later.
|
|
|
34
34
|
the terminal. The page works headless too — copy the key from any
|
|
35
35
|
browser if your terminal can't auto-launch one.
|
|
36
36
|
2. **Your library is now on disk.** The CLI wrote skills into
|
|
37
|
-
`.claude/skills/` (
|
|
38
|
-
|
|
37
|
+
`.claude/skills/` (for Claude Code) and `.agents/skills/` (the
|
|
38
|
+
shared path for Cursor, Windsurf, Gemini CLI, Codex CLI, Cline,
|
|
39
|
+
VS Code + Copilot, and others). Both paths are added to
|
|
40
|
+
`.gitignore` — skills are a per-developer cache, not committed.
|
|
39
41
|
3. **From now on**, use `skillrepo update` to pull new versions,
|
|
40
42
|
`skillrepo add` to add skills to your library, and `skillrepo list` to
|
|
41
43
|
see what you have.
|
|
@@ -49,12 +51,30 @@ Requires Node.js 18 or later.
|
|
|
49
51
|
### `init` — first-run setup
|
|
50
52
|
|
|
51
53
|
```sh
|
|
52
|
-
skillrepo init [--key <key>] [--url <url>] [--yes] [--force] [--
|
|
54
|
+
skillrepo init [--key <key>] [--url <url>] [--yes] [--force] [--agent <list>] [--global] [--json] [--no-session-sync]
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
Validates your access key,
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
Validates your access key, picks which targets to configure (interactive
|
|
58
|
+
two-row picker by default — Claude Code at `.claude/skills/` and the
|
|
59
|
+
shared `.agents/skills/` cohort path for everything else), writes the
|
|
60
|
+
MCP config, installs the Claude Code SessionStart hook (opt-in — see
|
|
61
|
+
`session-sync` below), and runs the first library sync.
|
|
62
|
+
|
|
63
|
+
Detection drives the picker's pre-checked rows. Three signal types per
|
|
64
|
+
agent are probed:
|
|
65
|
+
- **Active session** env vars (e.g., `CLAUDECODE`, `CURSOR_AGENT`,
|
|
66
|
+
`GEMINI_CLI`, `CLINE_ACTIVE`) — strongest signal, indicates the
|
|
67
|
+
agent is currently running.
|
|
68
|
+
- **HOME traces** (e.g., `~/.claude/`, `~/.cursor/`, `~/.codeium/windsurf/`)
|
|
69
|
+
— installed-on-this-machine signal.
|
|
70
|
+
- **Project dotfiles** (e.g., `.claude/`, `.cursor/`, `.github/skills/`)
|
|
71
|
+
— configured-for-this-repo signal.
|
|
72
|
+
|
|
73
|
+
When any signal fires for either target, that row pre-checks. Fresh
|
|
74
|
+
clone with no detection: both rows still pre-checked — the product's
|
|
75
|
+
job is to set up skills, defaulting to "do nothing" because env vars
|
|
76
|
+
didn't fire is wrong. Pick the third row ("None") to skip placement
|
|
77
|
+
and just write the config + gitignore.
|
|
58
78
|
|
|
59
79
|
| Flag | Description |
|
|
60
80
|
|------|-------------|
|
|
@@ -62,10 +82,40 @@ below), and runs the first library sync.
|
|
|
62
82
|
| `--url, -u <url>` | Server URL. Defaults to `https://skillrepo.dev`. Use for self-hosted. |
|
|
63
83
|
| `--yes, -y` | Non-interactive. Skip all confirmation prompts. Required for CI. Installs the session-sync hook by default — pass `--no-session-sync` to opt out. Under `npx`, this also auto-runs `npm install -g skillrepo@<this-version>` if no global install is present (so the session-sync hook can use the resulting absolute path). |
|
|
64
84
|
| `--force` | Re-prompt for a new key even if `~/.claude/skillrepo/config.json` is valid. |
|
|
65
|
-
| `--
|
|
85
|
+
| `--agent <list>` | Comma-separated agent target override. Canonical tokens: `claude` (Claude Code), `agents` (every other supported vendor — Cursor, Windsurf, Gemini CLI, Codex CLI, Cline, VS Code + Copilot — sharing the `.agents/skills/` cohort path), `none` (skip placement entirely; init still writes config + gitignore). Vendor names like `cursor` or `claude-code` are accepted as silent aliases. |
|
|
66
86
|
| `--global` | Write skills to `~/.claude/skills/` (personal) instead of `.claude/skills/` (project). |
|
|
67
87
|
| `--no-session-sync` | Skip step 6 (SessionStart hook install). Works in both interactive and `--yes` modes. Use for CI scripts that bootstrap a project without ever starting a Claude Code session. |
|
|
68
|
-
| `--json` | Emit a structured JSON summary on success.
|
|
88
|
+
| `--json` | Emit a structured JSON summary on success. Requires `--yes`. See JSON output shape below. |
|
|
89
|
+
|
|
90
|
+
#### JSON output shape (`init --json --yes`)
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"action": "initialized",
|
|
95
|
+
"account": { "slug": "...", "id": "...", "tier": "free|pro|enterprise" },
|
|
96
|
+
"config": { "action": "created|updated|unchanged" },
|
|
97
|
+
"vendors": ["claudeCode", "cursor", "..."],
|
|
98
|
+
"mcp": {
|
|
99
|
+
"merged": [".mcp.json", ".cursor/mcp.json"],
|
|
100
|
+
"skipped": ["..."],
|
|
101
|
+
"failed": [{ "path": "...", "reason": "..." }]
|
|
102
|
+
},
|
|
103
|
+
"sessionSync": { "action": "installed|unchanged|skipped|failed|not-applicable", "path": "..." },
|
|
104
|
+
"sync": {
|
|
105
|
+
"added": 0,
|
|
106
|
+
"updated": 0,
|
|
107
|
+
"removed": 0,
|
|
108
|
+
"notModified": false,
|
|
109
|
+
"fullSync": true,
|
|
110
|
+
"syncedAt": "2026-05-01T00:00:00.000Z"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Field notes:
|
|
116
|
+
- `vendors` is the resolved canonical-key list, NOT the raw `--agent` input. `--agent agents` produces every cohort vendor (cursor, windsurf, gemini, codex, cline, copilot); `--agent none` produces an empty array.
|
|
117
|
+
- `sync.fullSync` is `true` for first-time syncs (no prior `.last-sync`), `false` for delta syncs, and `null` only on synthesized-failure summaries when the network call never completed (also signals via `sync.failureReason`).
|
|
118
|
+
- `sync.failureReason: string` is present only when the first sync failed but the rest of init succeeded — config is still saved; user should re-run `skillrepo update`.
|
|
69
119
|
|
|
70
120
|
`init` is idempotent: re-running with a valid existing config re-runs
|
|
71
121
|
detection + MCP merge + first sync without re-prompting for a key. If the
|
|
@@ -77,14 +127,17 @@ fresh key without leaving the terminal.
|
|
|
77
127
|
When stdin is not a TTY (CI, piped input), `init` skips the browser launch
|
|
78
128
|
and just prints the URL — paste the key via the upstream pipe.
|
|
79
129
|
|
|
80
|
-
**Headless /
|
|
81
|
-
|
|
82
|
-
|
|
130
|
+
**Headless / non-interactive:** under `--yes` the picker is skipped and
|
|
131
|
+
the pre-checked rows become the selection. With no detection signals,
|
|
132
|
+
both default rows are pre-checked — running `init --yes` on a fresh
|
|
133
|
+
clone configures both targets so CI bootstrap scripts always produce
|
|
134
|
+
a working library. Pass `--agent <target>` (`claude`, `agents`, `none`,
|
|
135
|
+
or a vendor name like `cursor`) to bypass the picker entirely.
|
|
83
136
|
|
|
84
137
|
### `update` — sync your library
|
|
85
138
|
|
|
86
139
|
```sh
|
|
87
|
-
skillrepo update [--global] [--
|
|
140
|
+
skillrepo update [--global] [--agent <list>] [--json]
|
|
88
141
|
```
|
|
89
142
|
|
|
90
143
|
Pulls the latest state of your library from the server using a delta
|
|
@@ -96,7 +149,7 @@ changed.
|
|
|
96
149
|
### `get` — fetch a single skill
|
|
97
150
|
|
|
98
151
|
```sh
|
|
99
|
-
skillrepo get <@owner/name> [--global] [--
|
|
152
|
+
skillrepo get <@owner/name> [--global] [--agent <list>] [--json]
|
|
100
153
|
```
|
|
101
154
|
|
|
102
155
|
One-shot fetch. Does NOT mutate your library or the server — just reads
|
|
@@ -125,7 +178,7 @@ but currently a no-op — semantic search is a planned backend feature.
|
|
|
125
178
|
### `add` — add a skill to your library
|
|
126
179
|
|
|
127
180
|
```sh
|
|
128
|
-
skillrepo add <@owner/name> [--global] [--
|
|
181
|
+
skillrepo add <@owner/name> [--global] [--agent <list>] [--json]
|
|
129
182
|
```
|
|
130
183
|
|
|
131
184
|
POSTs to `/api/v1/library`, then fetches the single skill directly and
|
|
@@ -136,7 +189,7 @@ consistent.
|
|
|
136
189
|
### `remove` — remove a skill from your library
|
|
137
190
|
|
|
138
191
|
```sh
|
|
139
|
-
skillrepo remove <@owner/name> [--global] [--
|
|
192
|
+
skillrepo remove <@owner/name> [--global] [--agent <list>] [--json]
|
|
140
193
|
```
|
|
141
194
|
|
|
142
195
|
DELETEs from `/api/v1/library` and deletes the local directory. Requires
|
|
@@ -275,11 +328,20 @@ Two scenarios worth calling out:
|
|
|
275
328
|
|
|
276
329
|
### Skill placement
|
|
277
330
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
331
|
+
Skills land at one of two project paths, depending on the agent:
|
|
332
|
+
|
|
333
|
+
| Agent | Project path | Personal (`--global`) path |
|
|
334
|
+
|---|---|---|
|
|
335
|
+
| Claude Code | `.claude/skills/<name>/` | `~/.claude/skills/<name>/` |
|
|
336
|
+
| Cursor / Windsurf / Gemini CLI / Codex CLI / Cline / GitHub Copilot | `.agents/skills/<name>/` | `~/.agents/skills/<name>/` |
|
|
337
|
+
| Windsurf (personal-scope override) | — | `~/.codeium/windsurf/skills/<name>/` |
|
|
338
|
+
|
|
339
|
+
The CLI auto-adds `.claude/skills/` and `.agents/skills/` to your project
|
|
340
|
+
`.gitignore` on first write — skills are a per-developer cache, not
|
|
341
|
+
committed content. The `--agent` flag overrides detection (e.g.
|
|
342
|
+
`--agent claude,agents` writes both paths). See
|
|
343
|
+
[`docs/vendor-paths.md`](docs/vendor-paths.md) for primary-source
|
|
344
|
+
citations on each agent's read paths.
|
|
283
345
|
|
|
284
346
|
## Exit codes
|
|
285
347
|
|
|
@@ -342,11 +404,12 @@ path — both the interactive prompt in `init` and the Bearer header
|
|
|
342
404
|
used by every subsequent command — so pasting from email or a
|
|
343
405
|
browser with a trailing newline is safe.
|
|
344
406
|
|
|
345
|
-
**
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
config
|
|
407
|
+
**Picker selected nothing under `--yes`** — Phase 3 (#1236) replaced
|
|
408
|
+
the old "no agents detected → refuse" branch with a friendlier
|
|
409
|
+
default: when no detection signals fire, both default rows are
|
|
410
|
+
pre-checked, so `--yes` configures both targets. If you want to skip
|
|
411
|
+
placement entirely under `--yes`, pass `--agent none` (config and
|
|
412
|
+
gitignore still happen; only the skill files are skipped).
|
|
350
413
|
|
|
351
414
|
**"Rate limit exceeded — retried automatically and still getting
|
|
352
415
|
429"** — The CLI already retried with backoff. Wait a minute and
|
package/bin/skillrepo.mjs
CHANGED
|
@@ -44,27 +44,27 @@ import { CliError, EXIT_OK, EXIT_VALIDATION } from "../src/lib/errors.mjs";
|
|
|
44
44
|
const COMMANDS = {
|
|
45
45
|
init: {
|
|
46
46
|
description: "Validate access key, configure detected IDEs, and run first sync",
|
|
47
|
-
usage: "skillrepo init [--key <key>] [--url <url>] [--yes]",
|
|
47
|
+
usage: "skillrepo init [--key <key>] [--url <url>] [--yes] [--agent <list>] [--global] [--force] [--no-session-sync] [--json]",
|
|
48
48
|
run: async (argv) => runInit(argv),
|
|
49
49
|
},
|
|
50
50
|
update: {
|
|
51
51
|
description: "Sync your library against the registry (delta + tombstones)",
|
|
52
|
-
usage: "skillrepo update [--global] [--
|
|
52
|
+
usage: "skillrepo update [--global] [--agent <list>] [--json]",
|
|
53
53
|
run: async (argv) => runUpdate(argv),
|
|
54
54
|
},
|
|
55
55
|
get: {
|
|
56
56
|
description: "Fetch a single skill and write it to disk (no library mutation)",
|
|
57
|
-
usage: "skillrepo get <@owner/name> [--global] [--
|
|
57
|
+
usage: "skillrepo get <@owner/name> [--global] [--agent <list>] [--json]",
|
|
58
58
|
run: async (argv) => runGet(argv),
|
|
59
59
|
},
|
|
60
60
|
add: {
|
|
61
61
|
description: "Add a skill to your library and pull it locally",
|
|
62
|
-
usage: "skillrepo add <@owner/name> [--global] [--
|
|
62
|
+
usage: "skillrepo add <@owner/name> [--global] [--agent <list>] [--json]",
|
|
63
63
|
run: async (argv) => runAdd(argv),
|
|
64
64
|
},
|
|
65
65
|
remove: {
|
|
66
66
|
description: "Remove a skill from your library and delete it locally",
|
|
67
|
-
usage: "skillrepo remove <@owner/name> [--global] [--
|
|
67
|
+
usage: "skillrepo remove <@owner/name> [--global] [--agent <list>] [--json]",
|
|
68
68
|
run: async (argv) => runRemove(argv),
|
|
69
69
|
},
|
|
70
70
|
list: {
|
package/package.json
CHANGED
package/src/commands/add.mjs
CHANGED
|
@@ -30,13 +30,22 @@
|
|
|
30
30
|
* - 403 plan-limit → validationError (via http.mjs) with billing hint
|
|
31
31
|
* - 403 scope → scopeError (via http.mjs) with write-key hint
|
|
32
32
|
*
|
|
33
|
-
* Flags: --global / --
|
|
33
|
+
* Flags: --global / --agent / --json / --key / --url
|
|
34
34
|
* Positional: <@owner/name>
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import { addSkillToLibrary, getSkill } from "../lib/http.mjs";
|
|
38
|
-
import {
|
|
39
|
-
|
|
38
|
+
import {
|
|
39
|
+
writeSkillDir,
|
|
40
|
+
cleanupOrphans,
|
|
41
|
+
placementTargetsFor,
|
|
42
|
+
describePlacementTarget,
|
|
43
|
+
} from "../lib/file-write.mjs";
|
|
44
|
+
import {
|
|
45
|
+
resolveFlags,
|
|
46
|
+
effectiveVendors,
|
|
47
|
+
requireVendorTargets,
|
|
48
|
+
} from "../lib/cli-config.mjs";
|
|
40
49
|
import { parseIdentifier, formatIdentifier } from "../lib/identifier.mjs";
|
|
41
50
|
import { validationError } from "../lib/errors.mjs";
|
|
42
51
|
|
|
@@ -73,6 +82,11 @@ export async function runAdd(argv, io = {}) {
|
|
|
73
82
|
|
|
74
83
|
const { owner, name } = parseIdentifier(identifier);
|
|
75
84
|
const vendors = effectiveVendors(flags);
|
|
85
|
+
// `add` writes specific files to disk — `--agent none` would make
|
|
86
|
+
// the command a documented no-op, which is never a useful intent.
|
|
87
|
+
// Reject early with an actionable hint pointing at the meaningful
|
|
88
|
+
// targets.
|
|
89
|
+
requireVendorTargets(vendors, "add");
|
|
76
90
|
|
|
77
91
|
// Pre-flight: clean orphans from prior crashes (same pattern as get.mjs)
|
|
78
92
|
cleanupOrphans({ vendors, global: flags.global });
|
|
@@ -161,9 +175,10 @@ export async function runAdd(argv, io = {}) {
|
|
|
161
175
|
return;
|
|
162
176
|
}
|
|
163
177
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
178
|
+
const targets = placementTargetsFor({ vendors, global: flags.global });
|
|
179
|
+
const where = `${flags.global ? "personal" : "project"} (${targets
|
|
180
|
+
.map(describePlacementTarget)
|
|
181
|
+
.join(", ")})`;
|
|
167
182
|
if (wasNewlyAdded) {
|
|
168
183
|
stdout.write(
|
|
169
184
|
`\n ✓ Added ${formatIdentifier({ owner, name })} to your library (${skill.files.length} files) → ${where}\n\n`,
|
package/src/commands/get.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Flags (via resolveFlags):
|
|
11
11
|
* --global Write to ~/.claude/skills/ instead of project-local
|
|
12
|
-
* --
|
|
12
|
+
* --agent <list> Comma-separated agent target list
|
|
13
13
|
* --key/--url Override credentials
|
|
14
14
|
*
|
|
15
15
|
* Positional:
|
|
@@ -25,8 +25,17 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { getSkill } from "../lib/http.mjs";
|
|
28
|
-
import {
|
|
29
|
-
|
|
28
|
+
import {
|
|
29
|
+
writeSkillDir,
|
|
30
|
+
cleanupOrphans,
|
|
31
|
+
placementTargetsFor,
|
|
32
|
+
describePlacementTarget,
|
|
33
|
+
} from "../lib/file-write.mjs";
|
|
34
|
+
import {
|
|
35
|
+
resolveFlags,
|
|
36
|
+
effectiveVendors,
|
|
37
|
+
requireVendorTargets,
|
|
38
|
+
} from "../lib/cli-config.mjs";
|
|
30
39
|
import { parseIdentifier, formatIdentifier } from "../lib/identifier.mjs";
|
|
31
40
|
import { validationError } from "../lib/errors.mjs";
|
|
32
41
|
|
|
@@ -64,6 +73,10 @@ export async function runGet(argv, io = {}) {
|
|
|
64
73
|
|
|
65
74
|
const { owner, name } = parseIdentifier(identifier);
|
|
66
75
|
const vendors = effectiveVendors(flags);
|
|
76
|
+
// `get` writes a specific skill to disk — `--agent none` would
|
|
77
|
+
// make the command a no-op, which is never a useful intent. Reject
|
|
78
|
+
// early.
|
|
79
|
+
requireVendorTargets(vendors, "get");
|
|
67
80
|
|
|
68
81
|
// Pre-flight: clean orphans from prior crashes before any new write
|
|
69
82
|
cleanupOrphans({ vendors, global: flags.global });
|
|
@@ -109,7 +122,10 @@ export async function runGet(argv, io = {}) {
|
|
|
109
122
|
return;
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
const
|
|
125
|
+
const targets = placementTargetsFor({ vendors, global: flags.global });
|
|
126
|
+
const where = `${flags.global ? "personal" : "project"} (${targets
|
|
127
|
+
.map(describePlacementTarget)
|
|
128
|
+
.join(", ")})`;
|
|
113
129
|
stdout.write(
|
|
114
130
|
`\n ✓ Fetched ${formatIdentifier({ owner, name })} (${skill.files.length} files) → ${where}\n\n`,
|
|
115
131
|
);
|
|
@@ -73,7 +73,7 @@ import { SessionSyncAction, isHookActive } from "./session-sync-actions.mjs";
|
|
|
73
73
|
* @typedef {Object} SessionSyncOptions
|
|
74
74
|
* @property {boolean} noSessionSync - True if `--no-session-sync`.
|
|
75
75
|
* @property {boolean} claudeTargeted - True if Claude Code is in the
|
|
76
|
-
* vendor target list (via `--
|
|
76
|
+
* vendor target list (via `--agent claude` or `--global` or
|
|
77
77
|
* auto-detection).
|
|
78
78
|
* @property {boolean} yes - True if `--yes`.
|
|
79
79
|
* @property {boolean} json - True if `--json`. Affects spawn output
|