skillrepo 4.4.0 → 4.5.1
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 -3
- package/bin/skillrepo.mjs +45 -0
- package/package.json +3 -2
- package/src/commands/add.mjs +15 -0
- package/src/commands/get.mjs +16 -0
- package/src/commands/list.mjs +328 -56
- package/src/commands/remove.mjs +13 -0
- package/src/lib/agent-registry.mjs +15 -1
- package/src/lib/cli-config.mjs +32 -5
- package/src/lib/crypto-shas.mjs +131 -0
- package/src/lib/detect-agents.mjs +42 -5
- package/src/lib/drift.mjs +175 -0
- package/src/lib/file-write.mjs +16 -1
- package/src/lib/npm-update-check.mjs +366 -0
- package/src/lib/paths.mjs +10 -0
- package/src/lib/placement-walk.mjs +285 -0
- package/src/lib/sync.mjs +256 -17
- package/src/test/commands/list.test.mjs +510 -2
- package/src/test/integration/update-list-contract.integration.test.mjs +1018 -0
- package/src/test/lib/cli-config.test.mjs +57 -6
- package/src/test/lib/crypto-shas.test.mjs +172 -0
- package/src/test/lib/detect-agents.test.mjs +37 -4
- package/src/test/lib/drift.test.mjs +289 -0
- package/src/test/lib/npm-update-check.test.mjs +670 -0
- package/src/test/lib/placement-walk.test.mjs +453 -0
- package/src/test/lib/sync.test.mjs +409 -1
package/README.md
CHANGED
|
@@ -179,14 +179,48 @@ One-shot fetch. Does NOT mutate your library or the server — just reads
|
|
|
179
179
|
`GET /api/v1/skills/{owner}/{name}` and writes the skill to disk. Use
|
|
180
180
|
this to preview or pin a specific skill without adding it to your library.
|
|
181
181
|
|
|
182
|
-
### `list` — show what's in your library
|
|
182
|
+
### `list` — show what's in your library and what needs syncing
|
|
183
183
|
|
|
184
184
|
```sh
|
|
185
185
|
skillrepo list [--json]
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
Renders your library as a table
|
|
189
|
-
|
|
188
|
+
Renders your library as a table with a per-row `Local` column showing
|
|
189
|
+
on-disk drift state for each detected vendor:
|
|
190
|
+
|
|
191
|
+
- `OK` — local copy matches the last sync.
|
|
192
|
+
- `STALE` — library has a newer version than what's on disk.
|
|
193
|
+
- `MISS` — library has the skill but no on-disk placement (or no sync
|
|
194
|
+
history yet — run `skillrepo update` to establish a baseline).
|
|
195
|
+
- `EDIT` — local files have been modified since the last sync (different
|
|
196
|
+
SHA than what was persisted).
|
|
197
|
+
|
|
198
|
+
When multiple vendors are detected, the column shows a worst-state-wins
|
|
199
|
+
rollup (`missing > edited > stale > current`). The `--json` output
|
|
200
|
+
includes a `placements[]` array per item with per-vendor states for
|
|
201
|
+
scripts that want the full breakdown.
|
|
202
|
+
|
|
203
|
+
A footer reports library-level sync state:
|
|
204
|
+
|
|
205
|
+
- `library in sync — local skills up to date` — everything is current.
|
|
206
|
+
- `library in sync — but N skills show local drift` — library hasn't
|
|
207
|
+
changed but some local placements need attention.
|
|
208
|
+
- `library has changed since last sync` — registry has new content; run
|
|
209
|
+
`skillrepo update`.
|
|
210
|
+
- `No sync history on this machine` — fresh install or
|
|
211
|
+
baseline-less state; run `skillrepo update`.
|
|
212
|
+
|
|
213
|
+
Glyphs (`✓` / `⚠`) are used in TTY contexts; ASCII fallbacks (`OK` /
|
|
214
|
+
`[!]` / `STALE` / `MISS` / `EDIT`) appear when stdout is not a TTY or
|
|
215
|
+
`NO_COLOR` is set, so piped output stays clean.
|
|
216
|
+
|
|
217
|
+
`--json` is a bare array of skill objects with the additional fields
|
|
218
|
+
`state` (rollup) and `placements[]`. Existing scripts that consume
|
|
219
|
+
the pre-#1555 `--json` shape keep working — the additions are purely
|
|
220
|
+
per-item, no top-level wrapper.
|
|
221
|
+
|
|
222
|
+
Uses the same cached ETag as `update` for the library-level footer
|
|
223
|
+
state.
|
|
190
224
|
|
|
191
225
|
### `search` — explore the registry
|
|
192
226
|
|
|
@@ -474,8 +508,28 @@ Two scenarios worth calling out:
|
|
|
474
508
|
| `SKILLREPO_ACCESS_KEY` | Access key for any command. Takes precedence over the config file but not CLI flags. |
|
|
475
509
|
| `SKILLREPO_URL` | Server URL. Same precedence as above. |
|
|
476
510
|
| `SKILLREPO_TIMEOUT_MS` | Per-request fetch timeout in milliseconds (default 30000). Set to `0` to disable. |
|
|
511
|
+
| `SKILLREPO_NO_UPDATE_CHECK` | Set to any non-empty truthy value (`1`, `yes`, `true`) to disable the post-command npm-registry self-staleness check. The check otherwise runs at most once per 24 hours, hits `registry.npmjs.org/skillrepo/latest`, and prints a one-line upgrade hint to stderr if a newer version is available. Auto-disabled when `CI=true`. |
|
|
477
512
|
| `NO_COLOR` | Set any non-empty value to disable ANSI color in CLI output. |
|
|
478
513
|
|
|
514
|
+
### Update nudge
|
|
515
|
+
|
|
516
|
+
After every command that isn't `--json`, the CLI does a best-effort
|
|
517
|
+
check against `https://registry.npmjs.org/skillrepo/latest` and prints
|
|
518
|
+
a one-line hint on stderr when a newer version is available:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
A newer skillrepo is available: 4.3.0 → 4.5.0
|
|
522
|
+
Upgrade: npm install -g skillrepo@latest
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
The check is cached at `~/.claude/skillrepo/.npm-version-check` for
|
|
526
|
+
24 hours on success and 1 hour on failure, has a 2-second fetch
|
|
527
|
+
timeout, and is fire-and-forget — every failure mode (network error,
|
|
528
|
+
non-2xx, parse failure, read-only FS) is swallowed silently. Set
|
|
529
|
+
`SKILLREPO_NO_UPDATE_CHECK=1` to disable. `CI=true` auto-disables.
|
|
530
|
+
Output is suppressed entirely when `--json` is on the parent command,
|
|
531
|
+
so structured pipelines aren't disturbed.
|
|
532
|
+
|
|
479
533
|
### Skill placement
|
|
480
534
|
|
|
481
535
|
Skills land at one of two project paths, depending on the agent:
|
package/bin/skillrepo.mjs
CHANGED
|
@@ -35,6 +35,8 @@ import { runSearch } from "../src/commands/search.mjs";
|
|
|
35
35
|
import { runUninstall } from "../src/commands/uninstall.mjs";
|
|
36
36
|
import { runSessionSync } from "../src/commands/session-sync.mjs";
|
|
37
37
|
import { CliError, EXIT_OK, EXIT_VALIDATION } from "../src/lib/errors.mjs";
|
|
38
|
+
import { checkForCliUpdate } from "../src/lib/npm-update-check.mjs";
|
|
39
|
+
import { getCliVersion } from "../src/lib/cli-version.mjs";
|
|
38
40
|
|
|
39
41
|
// ── Command registry ────────────────────────────────────────────────────
|
|
40
42
|
|
|
@@ -151,6 +153,49 @@ async function main(command, fullArgv) {
|
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
await COMMANDS[command].run(rest);
|
|
156
|
+
|
|
157
|
+
// After the primary command completes, do a best-effort npm-registry
|
|
158
|
+
// staleness check (#1554). Never blocks exit beyond a single tick:
|
|
159
|
+
// `Promise.race` against `setTimeout(0)` means if the fetch hasn't
|
|
160
|
+
// completed yet, we drop it and the user gets the nudge on the next
|
|
161
|
+
// invocation (when the cache file is more likely to be fresh).
|
|
162
|
+
//
|
|
163
|
+
// Suppressed entirely when the parent argv carries `--json` so we
|
|
164
|
+
// never inject text into a structured stream — not even on stderr,
|
|
165
|
+
// because some pipelines tee both streams.
|
|
166
|
+
if (!rest.includes("--json")) {
|
|
167
|
+
await runUpdateCheck();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Best-effort update check. On a CACHE HIT this resolves synchronously
|
|
173
|
+
* (within a microtask) and we emit the nudge before the 0ms timer
|
|
174
|
+
* fires — that's the steady-state behavior after the first invocation.
|
|
175
|
+
*
|
|
176
|
+
* On a cache miss we race against `setTimeout(0)` so the user's primary
|
|
177
|
+
* command exit isn't held up by the nudge logic when they don't have
|
|
178
|
+
* a cached answer yet. The in-flight fetch is INTENTIONALLY left
|
|
179
|
+
* running after the race resolves: it has its own 2s `AbortController`
|
|
180
|
+
* (inside `checkForCliUpdate`) and is silent on failure, so the worst
|
|
181
|
+
* case is the process appearing to take slightly longer on its very
|
|
182
|
+
* first invocation while the cache warms. Every subsequent invocation
|
|
183
|
+
* benefits from the 24h positive cache and exits immediately.
|
|
184
|
+
*
|
|
185
|
+
* Errors are swallowed at the source by `checkForCliUpdate` itself; the
|
|
186
|
+
* outer `.catch` is belt-and-braces for the rare case where loading
|
|
187
|
+
* the module synchronously throws.
|
|
188
|
+
*/
|
|
189
|
+
async function runUpdateCheck() {
|
|
190
|
+
let currentVersion;
|
|
191
|
+
try {
|
|
192
|
+
currentVersion = getCliVersion();
|
|
193
|
+
} catch {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const checker = checkForCliUpdate({ currentVersion }).catch(() => {});
|
|
197
|
+
const yieldOnce = new Promise((resolve) => setTimeout(resolve, 0));
|
|
198
|
+
await Promise.race([checker, yieldOnce]);
|
|
154
199
|
}
|
|
155
200
|
|
|
156
201
|
// ── Help printing ───────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillrepo",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.1",
|
|
4
4
|
"description": "Pull-based CLI for agent skills — init, sync, search, add, remove your library from any IDE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"license": "SEE LICENSE IN LICENSE",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"cli-table3": "^0.6.5",
|
|
29
|
-
"gray-matter": "^4.0.3"
|
|
29
|
+
"gray-matter": "^4.0.3",
|
|
30
|
+
"semver": "^7.6.3"
|
|
30
31
|
}
|
|
31
32
|
}
|
package/src/commands/add.mjs
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
effectiveVendors,
|
|
47
47
|
requireVendorTargets,
|
|
48
48
|
} from "../lib/cli-config.mjs";
|
|
49
|
+
import { upsertLastSyncEntry } from "../lib/sync.mjs";
|
|
49
50
|
import { parseIdentifier, formatIdentifier } from "../lib/identifier.mjs";
|
|
50
51
|
import { validationError } from "../lib/errors.mjs";
|
|
51
52
|
|
|
@@ -158,6 +159,20 @@ export async function runAdd(argv, io = {}) {
|
|
|
158
159
|
|
|
159
160
|
writeSkillDir(skill, { vendors, global: flags.global });
|
|
160
161
|
|
|
162
|
+
// Update `.last-sync` so `list` immediately recognizes this skill
|
|
163
|
+
// as `current`. Without this the user adds a skill, then runs
|
|
164
|
+
// `list`, and sees their freshly-added skill marked MISS — the
|
|
165
|
+
// exact same class of contract gap that PR #1574 fixed for the
|
|
166
|
+
// multi-vendor case in `update`/`list`. See `upsertLastSyncEntry`
|
|
167
|
+
// docstring for the etag-preservation rationale.
|
|
168
|
+
try {
|
|
169
|
+
upsertLastSyncEntry(skill);
|
|
170
|
+
} catch {
|
|
171
|
+
// Non-fatal: skill is on disk, the state file write failure
|
|
172
|
+
// does not change that. Same semantics as runSync's warn-and-
|
|
173
|
+
// proceed for last-sync persistence failures.
|
|
174
|
+
}
|
|
175
|
+
|
|
161
176
|
if (flags.json) {
|
|
162
177
|
stdout.write(
|
|
163
178
|
JSON.stringify(
|
package/src/commands/get.mjs
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
effectiveVendors,
|
|
37
37
|
requireVendorTargets,
|
|
38
38
|
} from "../lib/cli-config.mjs";
|
|
39
|
+
import { upsertLastSyncEntry } from "../lib/sync.mjs";
|
|
39
40
|
import { parseIdentifier, formatIdentifier } from "../lib/identifier.mjs";
|
|
40
41
|
import { validationError } from "../lib/errors.mjs";
|
|
41
42
|
|
|
@@ -109,6 +110,21 @@ export async function runGet(argv, io = {}) {
|
|
|
109
110
|
|
|
110
111
|
writeSkillDir(skill, { vendors, global: flags.global });
|
|
111
112
|
|
|
113
|
+
// Update `.last-sync` so `list` recognizes this skill as `current`
|
|
114
|
+
// afterward instead of reporting a stale `MISSING`. Best-effort:
|
|
115
|
+
// a state-file write failure is non-fatal (the files are already
|
|
116
|
+
// on disk), but writeLastSync throws diskError if it cannot
|
|
117
|
+
// persist — we catch and continue. Pre-PR #1574 `get` skipped
|
|
118
|
+
// this entirely, which surfaced every freshly-fetched skill as
|
|
119
|
+
// MISS in the very next `list` invocation.
|
|
120
|
+
try {
|
|
121
|
+
upsertLastSyncEntry(skill);
|
|
122
|
+
} catch {
|
|
123
|
+
// Same failure semantics as runSync's "couldn't persist last
|
|
124
|
+
// sync state" path: warn and proceed, the user's intent (files
|
|
125
|
+
// on disk) was honored.
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
if (flags.json) {
|
|
113
129
|
stdout.write(
|
|
114
130
|
JSON.stringify({
|