skillrepo 3.2.0 → 4.1.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 +137 -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-cohort-hooks.mjs +127 -0
- package/src/commands/init-session-sync.mjs +1 -1
- package/src/commands/init.mjs +480 -117
- package/src/commands/list.mjs +1 -1
- package/src/commands/remove.mjs +10 -2
- package/src/commands/uninstall.mjs +13 -2
- package/src/commands/update.mjs +112 -19
- package/src/lib/agent-hook-merge.mjs +203 -0
- package/src/lib/agent-registry.mjs +399 -0
- package/src/lib/artifact-registry.mjs +111 -2
- 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/fs-utils.mjs +16 -1
- package/src/lib/mcp-merge.mjs +17 -36
- package/src/lib/mergers/agent-hook-claude-shape.mjs +519 -0
- package/src/lib/mergers/agent-hook-cursor-shape.mjs +318 -0
- 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/removers/agent-hooks.mjs +83 -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 +508 -41
- package/src/test/commands/remove.test.mjs +4 -1
- package/src/test/commands/update.test.mjs +148 -3
- package/src/test/e2e/cli-agent-permutations.test.mjs +631 -0
- package/src/test/e2e/cli-cohort-hooks.test.mjs +393 -0
- package/src/test/e2e/cli-commands.test.mjs +39 -13
- package/src/test/integration/agent-hooks.integration.test.mjs +340 -0
- package/src/test/integration/file-write.integration.test.mjs +31 -10
- package/src/test/lib/agent-hook-merge.test.mjs +172 -0
- package/src/test/lib/agent-registry.test.mjs +215 -0
- package/src/test/lib/artifact-registry.test.mjs +39 -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/test/mergers/agent-hook-claude-shape.test.mjs +518 -0
- package/src/test/mergers/agent-hook-cursor-shape.test.mjs +306 -0
- package/src/test/removers/agent-hooks.test.mjs +206 -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,53 @@ 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": {
|
|
104
|
+
"action": "installed|unchanged|skipped|failed|not-applicable",
|
|
105
|
+
"path": "...",
|
|
106
|
+
"cohortHooks": [
|
|
107
|
+
{
|
|
108
|
+
"vendorKey": "cursor|gemini|codex|copilot",
|
|
109
|
+
"displayName": "...",
|
|
110
|
+
"path": "~/.cursor/hooks.json",
|
|
111
|
+
"action": "installed|updated|unchanged|failed",
|
|
112
|
+
"reason": "..."
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
"sync": {
|
|
117
|
+
"added": 0,
|
|
118
|
+
"updated": 0,
|
|
119
|
+
"removed": 0,
|
|
120
|
+
"notModified": false,
|
|
121
|
+
"fullSync": true,
|
|
122
|
+
"syncedAt": "2026-05-01T00:00:00.000Z"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Field notes:
|
|
128
|
+
- `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.
|
|
129
|
+
- `sessionSync.cohortHooks[]` reports per-vendor outcomes for the auto-refresh hooks installed alongside the Claude Code SessionStart hook (one entry per cohort vendor with a non-null `agentHook` registry spec — Cursor, Gemini CLI, Codex CLI, VS Code + Copilot). `reason` is present only when `action: "failed"`. Empty array when `--no-session-sync` was passed or no cohort vendor was selected.
|
|
130
|
+
- `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`).
|
|
131
|
+
- `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
132
|
|
|
70
133
|
`init` is idempotent: re-running with a valid existing config re-runs
|
|
71
134
|
detection + MCP merge + first sync without re-prompting for a key. If the
|
|
@@ -77,14 +140,17 @@ fresh key without leaving the terminal.
|
|
|
77
140
|
When stdin is not a TTY (CI, piped input), `init` skips the browser launch
|
|
78
141
|
and just prints the URL — paste the key via the upstream pipe.
|
|
79
142
|
|
|
80
|
-
**Headless /
|
|
81
|
-
|
|
82
|
-
|
|
143
|
+
**Headless / non-interactive:** under `--yes` the picker is skipped and
|
|
144
|
+
the pre-checked rows become the selection. With no detection signals,
|
|
145
|
+
both default rows are pre-checked — running `init --yes` on a fresh
|
|
146
|
+
clone configures both targets so CI bootstrap scripts always produce
|
|
147
|
+
a working library. Pass `--agent <target>` (`claude`, `agents`, `none`,
|
|
148
|
+
or a vendor name like `cursor`) to bypass the picker entirely.
|
|
83
149
|
|
|
84
150
|
### `update` — sync your library
|
|
85
151
|
|
|
86
152
|
```sh
|
|
87
|
-
skillrepo update [--global] [--
|
|
153
|
+
skillrepo update [--global] [--agent <list>] [--json] [--silent]
|
|
88
154
|
```
|
|
89
155
|
|
|
90
156
|
Pulls the latest state of your library from the server using a delta
|
|
@@ -93,10 +159,20 @@ from the library, and skips skills that are unchanged. Uses ETag
|
|
|
93
159
|
caching so repeat runs return `304 Not Modified` when nothing has
|
|
94
160
|
changed.
|
|
95
161
|
|
|
162
|
+
`--silent` suppresses normal output and writes a single `{}` line to
|
|
163
|
+
stdout on success. Designed for SessionStart hooks that pipe stdout
|
|
164
|
+
to their agent's session log — Gemini CLI specifically requires hook
|
|
165
|
+
stdout to be valid JSON, and the empty object satisfies that without
|
|
166
|
+
injecting model context. Sync progress and warnings still go to
|
|
167
|
+
stderr. On failure, the command exits with the appropriate non-zero
|
|
168
|
+
code and the error message goes to stderr (distinct from
|
|
169
|
+
`--session-hook`, which is Claude-Code-specific and exits 0 on every
|
|
170
|
+
error so a sync failure can't block a session start).
|
|
171
|
+
|
|
96
172
|
### `get` — fetch a single skill
|
|
97
173
|
|
|
98
174
|
```sh
|
|
99
|
-
skillrepo get <@owner/name> [--global] [--
|
|
175
|
+
skillrepo get <@owner/name> [--global] [--agent <list>] [--json]
|
|
100
176
|
```
|
|
101
177
|
|
|
102
178
|
One-shot fetch. Does NOT mutate your library or the server — just reads
|
|
@@ -125,7 +201,7 @@ but currently a no-op — semantic search is a planned backend feature.
|
|
|
125
201
|
### `add` — add a skill to your library
|
|
126
202
|
|
|
127
203
|
```sh
|
|
128
|
-
skillrepo add <@owner/name> [--global] [--
|
|
204
|
+
skillrepo add <@owner/name> [--global] [--agent <list>] [--json]
|
|
129
205
|
```
|
|
130
206
|
|
|
131
207
|
POSTs to `/api/v1/library`, then fetches the single skill directly and
|
|
@@ -136,7 +212,7 @@ consistent.
|
|
|
136
212
|
### `remove` — remove a skill from your library
|
|
137
213
|
|
|
138
214
|
```sh
|
|
139
|
-
skillrepo remove <@owner/name> [--global] [--
|
|
215
|
+
skillrepo remove <@owner/name> [--global] [--agent <list>] [--json]
|
|
140
216
|
```
|
|
141
217
|
|
|
142
218
|
DELETEs from `/api/v1/library` and deletes the local directory. Requires
|
|
@@ -154,6 +230,23 @@ Installs (or removes) a Claude Code [SessionStart hook](https://docs.claude.com/
|
|
|
154
230
|
|
|
155
231
|
By default `skillrepo init` prompts you to install this hook. If you said no (or passed `--no-session-sync`), run `session-sync enable` later to turn it on.
|
|
156
232
|
|
|
233
|
+
#### Auto-refresh hooks for other agents
|
|
234
|
+
|
|
235
|
+
For Cursor, Gemini CLI, Codex CLI, and VS Code + Copilot, `skillrepo init` writes a SessionStart hook to each agent's user-scope hook config so your library refreshes on every session start without a separate command. Each hook invokes `npx --yes skillrepo update --silent`, so it works without a global `skillrepo` install.
|
|
236
|
+
|
|
237
|
+
| Agent | Hook config path | Notes |
|
|
238
|
+
|-------|------------------|-------|
|
|
239
|
+
| Cursor | `~/.cursor/hooks.json` (`sessionStart` event) | `timeout: 60` (seconds) |
|
|
240
|
+
| Gemini CLI | `~/.gemini/settings.json` (`SessionStart` event) | `matcher: "*"` group filter, `timeout: 60000` (milliseconds), entry named `skillrepo-update` |
|
|
241
|
+
| Codex CLI | `~/.codex/hooks.json` (`SessionStart` event) | `timeout: 60` (seconds). Codex also reads `[hooks]` from `~/.codex/config.toml`; both sources coexist — Codex merges them at runtime, so a hand-written TOML entry won't conflict with our JSON. |
|
|
242
|
+
| VS Code + Copilot | `~/.copilot/hooks/skillrepo-update.json` (`sessionStart` event) | `timeout: 60` (seconds). **Preview status**: GitHub currently labels Copilot's hook system as Preview; the schema may shift before GA. |
|
|
243
|
+
|
|
244
|
+
`skillrepo init` writes these alongside the Claude Code hook for every selected vendor that publishes a SessionStart-equivalent event. `--no-session-sync` skips ALL of them. `skillrepo uninstall --global` removes them surgically — other tools' entries (1Password, Snyk, your own scripts) in those config files are preserved.
|
|
245
|
+
|
|
246
|
+
**Cloud-agent runners** (e.g. GitHub Codespaces, Copilot's cloud agent) read only the committed default-branch content. Because skills sync to a per-developer, gitignored cache, those runners do not see the local skill library — same documented limitation as `.agents/skills/` placement. The auto-refresh hooks above are per-developer; they don't run in cloud runners.
|
|
247
|
+
|
|
248
|
+
Auto-refresh hooks for Windsurf and Cline are not yet supported — those agents lack a documented SessionStart-equivalent event today. Run `skillrepo update` manually in those environments.
|
|
249
|
+
|
|
157
250
|
**Under `npx skillrepo init`, the CLI offers to install itself globally.** Session sync needs the binary at a stable absolute path (the `npx` cache path is transient and would break on the next cache eviction). Rather than skipping with a warning the way v3.1.1 did, v3.1.2 prompts during init: *"SkillRepo needs a global install to enable session sync. Install `skillrepo` globally now? (Y/n)"* — saying yes runs `npm install -g skillrepo@<version>` (pinned to the version you just invoked) and then installs the hook with the resulting absolute path. Under `--yes` the install runs without prompting; under `--no-session-sync` it's skipped entirely. If the install fails (permissions, network, registry), init prints actionable next-steps and continues; the rest of init still completes successfully.
|
|
158
251
|
|
|
159
252
|
`skillrepo session-sync enable` does **not** auto-install — it's an explicit, deliberate command and assumes you already have a global install. If invoked under `npx` without a global install present, it returns a clear "install globally first" message rather than mutating your global package set.
|
|
@@ -194,6 +287,13 @@ With `--global`, also removes:
|
|
|
194
287
|
- `mcpServers.skillrepo` from `~/.codeium/windsurf/mcp_config.json`
|
|
195
288
|
- The `~/.claude/skills/` global skill cache
|
|
196
289
|
- The `~/.claude/skillrepo/` directory (stored credentials + sync cache)
|
|
290
|
+
- The SkillRepo SessionStart entry from each cohort vendor's hook config —
|
|
291
|
+
`~/.cursor/hooks.json`, `~/.gemini/settings.json`, `~/.codex/hooks.json`,
|
|
292
|
+
and `~/.copilot/hooks/skillrepo-update.json`. Other tools' entries
|
|
293
|
+
(1Password, Snyk, Apiiro, the user's own hooks) and unrelated top-level
|
|
294
|
+
keys (theme, mcpServers, etc.) are preserved — only our entry, identified
|
|
295
|
+
by the canonical command `npx --yes skillrepo update --silent`, is
|
|
296
|
+
filtered out.
|
|
197
297
|
|
|
198
298
|
Flags:
|
|
199
299
|
|
|
@@ -275,11 +375,20 @@ Two scenarios worth calling out:
|
|
|
275
375
|
|
|
276
376
|
### Skill placement
|
|
277
377
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
378
|
+
Skills land at one of two project paths, depending on the agent:
|
|
379
|
+
|
|
380
|
+
| Agent | Project path | Personal (`--global`) path |
|
|
381
|
+
|---|---|---|
|
|
382
|
+
| Claude Code | `.claude/skills/<name>/` | `~/.claude/skills/<name>/` |
|
|
383
|
+
| Cursor / Windsurf / Gemini CLI / Codex CLI / Cline / GitHub Copilot | `.agents/skills/<name>/` | `~/.agents/skills/<name>/` |
|
|
384
|
+
| Windsurf (personal-scope override) | — | `~/.codeium/windsurf/skills/<name>/` |
|
|
385
|
+
|
|
386
|
+
The CLI auto-adds `.claude/skills/` and `.agents/skills/` to your project
|
|
387
|
+
`.gitignore` on first write — skills are a per-developer cache, not
|
|
388
|
+
committed content. The `--agent` flag overrides detection (e.g.
|
|
389
|
+
`--agent claude,agents` writes both paths). See
|
|
390
|
+
[`docs/vendor-paths.md`](docs/vendor-paths.md) for primary-source
|
|
391
|
+
citations on each agent's read paths.
|
|
283
392
|
|
|
284
393
|
## Exit codes
|
|
285
394
|
|
|
@@ -342,11 +451,12 @@ path — both the interactive prompt in `init` and the Bearer header
|
|
|
342
451
|
used by every subsequent command — so pasting from email or a
|
|
343
452
|
browser with a trailing newline is safe.
|
|
344
453
|
|
|
345
|
-
**
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
config
|
|
454
|
+
**Picker selected nothing under `--yes`** — Phase 3 (#1236) replaced
|
|
455
|
+
the old "no agents detected → refuse" branch with a friendlier
|
|
456
|
+
default: when no detection signals fire, both default rows are
|
|
457
|
+
pre-checked, so `--yes` configures both targets. If you want to skip
|
|
458
|
+
placement entirely under `--yes`, pass `--agent none` (config and
|
|
459
|
+
gitignore still happen; only the skill files are skipped).
|
|
350
460
|
|
|
351
461
|
**"Rate limit exceeded — retried automatically and still getting
|
|
352
462
|
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
|
);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `skillrepo init` step-6 sibling for cohort SessionStart hooks (#1240).
|
|
3
|
+
*
|
|
4
|
+
* Runs ALONGSIDE `installSessionSyncHook` (the legacy Claude-Code-only
|
|
5
|
+
* installer). Where the Claude installer has to resolve a stable
|
|
6
|
+
* absolute `binaryPath` for the hook command — three branches
|
|
7
|
+
* (non-npx, existing global, auto-install) — the cohort installer has
|
|
8
|
+
* no such concern: every cohort hook is `npx --yes skillrepo update
|
|
9
|
+
* --silent`, which works on any host without a global install.
|
|
10
|
+
*
|
|
11
|
+
* That's why this is a sibling step, not an extension of
|
|
12
|
+
* `installSessionSyncHook`. The two flows share almost no logic. The
|
|
13
|
+
* architect review on #1240 specifically called out conflating them
|
|
14
|
+
* as a structural mistake — `installSessionSyncHook` is named for
|
|
15
|
+
* Claude Code's hook mechanism, and folding cohort logic into it
|
|
16
|
+
* would create a function with four irreducible branches by Phase 4
|
|
17
|
+
* (#1241-#1244).
|
|
18
|
+
*
|
|
19
|
+
* ## Decision tree
|
|
20
|
+
*
|
|
21
|
+
* noSessionSync → skip all cohort hooks (mirrors
|
|
22
|
+
* Claude path's --no-session-sync
|
|
23
|
+
* semantics)
|
|
24
|
+
* no cohort vendors selected → no-op (nothing to install)
|
|
25
|
+
* cohort vendors selected → install hooks for each one;
|
|
26
|
+
* per-vendor failures don't abort
|
|
27
|
+
* siblings
|
|
28
|
+
*
|
|
29
|
+
* ## Return shape
|
|
30
|
+
*
|
|
31
|
+
* { hooks: AgentHookInstallResult[] }
|
|
32
|
+
*
|
|
33
|
+
* Each entry: `{ vendorKey, displayName, path, action, reason? }`. The
|
|
34
|
+
* caller (init.mjs) merges these into the `sessionSync.cohortHooks`
|
|
35
|
+
* field of the JSON summary.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { installAgentHooksForVendors } from "../lib/agent-hook-merge.mjs";
|
|
39
|
+
import { getAgentByKey } from "../lib/agent-registry.mjs";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {Object} CohortHookOptions
|
|
43
|
+
* @property {boolean} noSessionSync - True if `--no-session-sync` was
|
|
44
|
+
* passed. The flag covers BOTH the Claude session hook and
|
|
45
|
+
* the cohort hooks — semantically "skip all auto-refresh
|
|
46
|
+
* hooks." Pre-#1240 this was a Claude-only flag; the
|
|
47
|
+
* framing widened with the cohort feature.
|
|
48
|
+
* @property {string[]} vendors - Canonical vendor keys the user
|
|
49
|
+
* selected (via `--agent` or the picker). Vendors without
|
|
50
|
+
* an `agentHook` spec are silently skipped by the
|
|
51
|
+
* dispatcher; truly unknown keys surface as failures.
|
|
52
|
+
* @property {object} p - Init's printer (from `init.mjs:makePrinter`).
|
|
53
|
+
* Silenced under `--json`; otherwise renders one line per
|
|
54
|
+
* cohort hook installed/updated/failed.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run the cohort sibling step. Always returns a result; never throws
|
|
59
|
+
* on user-recoverable failures (per-vendor errors are captured in
|
|
60
|
+
* each result entry).
|
|
61
|
+
*
|
|
62
|
+
* @param {CohortHookOptions} options
|
|
63
|
+
* @returns {{ hooks: import("../lib/agent-hook-merge.mjs").AgentHookInstallResult[] }}
|
|
64
|
+
*/
|
|
65
|
+
export function installCohortHooks({ noSessionSync, vendors, p }) {
|
|
66
|
+
if (noSessionSync) {
|
|
67
|
+
// Match the Claude path's --no-session-sync semantics: silent
|
|
68
|
+
// skip. The Claude path already prints the warning, so we don't
|
|
69
|
+
// double-warn here. If somehow the user passes --no-session-sync
|
|
70
|
+
// AND no Claude target (so the Claude path doesn't print), the
|
|
71
|
+
// user got exactly what they asked for — explicit opt-out.
|
|
72
|
+
return { hooks: [] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Filter vendors to those with an agentHook spec. The dispatcher
|
|
76
|
+
// does this filtering internally too (silently skipping unknown
|
|
77
|
+
// entries), but doing it here lets us avoid the noise of "Skipped
|
|
78
|
+
// claudeCode (no agentHook)" lines for every vendor in the cohort
|
|
79
|
+
// list that isn't ours to install. Claude Code, Windsurf, and
|
|
80
|
+
// Cline all hit this filter; only the four cohort vendors with
|
|
81
|
+
// `agentHook != null` reach the dispatcher.
|
|
82
|
+
const eligible = vendors.filter((v) => {
|
|
83
|
+
const entry = getAgentByKey(v);
|
|
84
|
+
return entry && entry.agentHook !== null;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (eligible.length === 0) {
|
|
88
|
+
return { hooks: [] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const results = installAgentHooksForVendors({ vendors: eligible });
|
|
92
|
+
|
|
93
|
+
// Surface each result on the printer so the user sees what was
|
|
94
|
+
// written / refreshed / failed. Mirrors `installSessionSyncHook`'s
|
|
95
|
+
// per-action printing for Claude.
|
|
96
|
+
for (const r of results) {
|
|
97
|
+
if (r.action === "installed") {
|
|
98
|
+
p.success(`Cohort SessionStart hook installed for ${r.displayName} (${r.path})`);
|
|
99
|
+
} else if (r.action === "updated") {
|
|
100
|
+
p.success(`Cohort SessionStart hook updated for ${r.displayName} (${r.path})`);
|
|
101
|
+
} else if (r.action === "unchanged") {
|
|
102
|
+
p.success(`Cohort SessionStart hook already installed for ${r.displayName} (${r.path})`);
|
|
103
|
+
} else if (r.action === "failed") {
|
|
104
|
+
p.warning(
|
|
105
|
+
`Cohort SessionStart hook for ${r.displayName} failed: ${r.reason}. ` +
|
|
106
|
+
`Run \`skillrepo init\` again after fixing the issue.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// VS Code + Copilot's hook system is currently labelled Preview by
|
|
112
|
+
// GitHub (#1244). Surface that caveat once if Copilot was among the
|
|
113
|
+
// installed vendors so users know the hook schema may shift before
|
|
114
|
+
// GA. Skip the warning if Copilot's install actually failed — we
|
|
115
|
+
// already printed the failure line above and a Preview note would
|
|
116
|
+
// muddle the actionable signal.
|
|
117
|
+
const copilotResult = results.find((r) => r.vendorKey === "copilot");
|
|
118
|
+
if (copilotResult && copilotResult.action !== "failed") {
|
|
119
|
+
p.warning(
|
|
120
|
+
"Copilot's SessionStart hook system is currently labelled Preview by GitHub. " +
|
|
121
|
+
"The schema may shift before GA; re-run `skillrepo init` after upgrading the CLI " +
|
|
122
|
+
"if Copilot's hook config format changes.",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { hooks: results };
|
|
127
|
+
}
|
|
@@ -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
|