skillpass 0.1.0 → 0.2.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 +18 -5
- package/dist/cli.mjs +583 -44
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ skillpass scan <path> [--json]
|
|
|
25
25
|
skillpass report <slug>[@version] [--json]
|
|
26
26
|
skillpass add <slug>[@version] [--target <tool> [--global] | --dir <path>] [--yes]
|
|
27
27
|
skillpass remove <slug> [--target <tool> [--global] | --dir <path>]
|
|
28
|
+
skillpass update <slug>[@version] [--target <tool> [--global]] [--yes]
|
|
29
|
+
skillpass outdated
|
|
28
30
|
skillpass list
|
|
29
31
|
skillpass --version | --help
|
|
30
32
|
```
|
|
@@ -63,11 +65,22 @@ skills in one verified step.
|
|
|
63
65
|
|
|
64
66
|
### Manage
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
`
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
Installs into the known skills areas are recorded in a per-area
|
|
69
|
+
`.skillpass.json` receipt, so the CLI knows what it installed and at which
|
|
70
|
+
version - your own hand-made skills in the same folders are left alone.
|
|
71
|
+
|
|
72
|
+
- `skillpass list` shows installed skills with their versions
|
|
73
|
+
- `skillpass outdated` compares them to the directory (exits 1 when updates
|
|
74
|
+
exist, handy in scripts)
|
|
75
|
+
- `skillpass update <slug>` re-runs the whole trust gate - including a
|
|
76
|
+
permission diff against the version you currently have - then swaps the
|
|
77
|
+
install atomically; updating a pack swaps, adds, and removes members as
|
|
78
|
+
the pack changed
|
|
79
|
+
- `skillpass remove <slug>` deletes an installed skill (a pack removes its
|
|
80
|
+
whole family); it refuses anything that does not look like an installed
|
|
81
|
+
skill, so a stray `--dir` can never wipe a real folder
|
|
82
|
+
- `skillpass scan <path>` runs the same validator the directory uses on any
|
|
83
|
+
local skill folder before you submit it
|
|
71
84
|
|
|
72
85
|
## Configuration
|
|
73
86
|
|
package/dist/cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
import { readFileSync as
|
|
8
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
9
9
|
import { createInterface } from "node:readline/promises";
|
|
10
10
|
|
|
11
11
|
// ../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
|
@@ -494,15 +494,15 @@ function unzipSync(data, opts) {
|
|
|
494
494
|
// src/add.ts
|
|
495
495
|
import {
|
|
496
496
|
existsSync,
|
|
497
|
-
mkdirSync,
|
|
497
|
+
mkdirSync as mkdirSync2,
|
|
498
498
|
readdirSync as readdirSync2,
|
|
499
499
|
renameSync,
|
|
500
500
|
rmdirSync,
|
|
501
501
|
rmSync,
|
|
502
502
|
statSync as statSync2,
|
|
503
|
-
writeFileSync
|
|
503
|
+
writeFileSync as writeFileSync2
|
|
504
504
|
} from "node:fs";
|
|
505
|
-
import { dirname, isAbsolute, join as
|
|
505
|
+
import { dirname, isAbsolute, join as join4, resolve as resolve2 } from "node:path";
|
|
506
506
|
|
|
507
507
|
// ../validator/src/load.ts
|
|
508
508
|
import { createHash } from "node:crypto";
|
|
@@ -16103,7 +16103,8 @@ async function validatePackage(dir, opts = {}) {
|
|
|
16103
16103
|
// src/api.ts
|
|
16104
16104
|
var DEFAULT_API_URL = "https://api.skillpass.dev";
|
|
16105
16105
|
function resolveApiUrl(override) {
|
|
16106
|
-
|
|
16106
|
+
const url2 = override ?? process.env.SKILLPASS_API;
|
|
16107
|
+
return (url2 || DEFAULT_API_URL).replace(/\/$/, "");
|
|
16107
16108
|
}
|
|
16108
16109
|
function parseSkillRef(ref) {
|
|
16109
16110
|
const at = ref.indexOf("@");
|
|
@@ -16177,6 +16178,53 @@ function resolvePackMembers(members, target) {
|
|
|
16177
16178
|
return { installs, skipped };
|
|
16178
16179
|
}
|
|
16179
16180
|
|
|
16181
|
+
// src/receipts.ts
|
|
16182
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
16183
|
+
import { join as join2 } from "node:path";
|
|
16184
|
+
var RECEIPT_FILE = ".skillpass.json";
|
|
16185
|
+
function looksLikeReceipt(value) {
|
|
16186
|
+
return typeof value === "object" && value !== null && typeof value.version === "string" && typeof value.sourceHash === "string";
|
|
16187
|
+
}
|
|
16188
|
+
function readReceipts(areaDir) {
|
|
16189
|
+
let parsed;
|
|
16190
|
+
try {
|
|
16191
|
+
parsed = JSON.parse(readFileSync2(join2(areaDir, RECEIPT_FILE), "utf8"));
|
|
16192
|
+
} catch {
|
|
16193
|
+
return {};
|
|
16194
|
+
}
|
|
16195
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
16196
|
+
return {};
|
|
16197
|
+
}
|
|
16198
|
+
const index = {};
|
|
16199
|
+
for (const [slug, receipt] of Object.entries(parsed)) {
|
|
16200
|
+
if (looksLikeReceipt(receipt)) {
|
|
16201
|
+
index[slug] = receipt;
|
|
16202
|
+
}
|
|
16203
|
+
}
|
|
16204
|
+
return index;
|
|
16205
|
+
}
|
|
16206
|
+
function writeReceipts(areaDir, index) {
|
|
16207
|
+
try {
|
|
16208
|
+
mkdirSync(areaDir, { recursive: true });
|
|
16209
|
+
writeFileSync(join2(areaDir, RECEIPT_FILE), `${JSON.stringify(index, null, " ")}
|
|
16210
|
+
`);
|
|
16211
|
+
} catch {
|
|
16212
|
+
}
|
|
16213
|
+
}
|
|
16214
|
+
function recordReceipt(areaDir, slug, receipt) {
|
|
16215
|
+
const index = readReceipts(areaDir);
|
|
16216
|
+
index[slug] = receipt;
|
|
16217
|
+
writeReceipts(areaDir, index);
|
|
16218
|
+
}
|
|
16219
|
+
function removeReceipt(areaDir, slug) {
|
|
16220
|
+
const index = readReceipts(areaDir);
|
|
16221
|
+
if (!(slug in index)) {
|
|
16222
|
+
return;
|
|
16223
|
+
}
|
|
16224
|
+
delete index[slug];
|
|
16225
|
+
writeReceipts(areaDir, index);
|
|
16226
|
+
}
|
|
16227
|
+
|
|
16180
16228
|
// src/style.ts
|
|
16181
16229
|
var wrap = (open, close) => (text) => `\x1B[${open}m${text}\x1B[${close}m`;
|
|
16182
16230
|
var ANSI = {
|
|
@@ -16285,11 +16333,11 @@ function renderDiff(diff) {
|
|
|
16285
16333
|
|
|
16286
16334
|
// src/targets.ts
|
|
16287
16335
|
import { homedir } from "node:os";
|
|
16288
|
-
import { join as
|
|
16336
|
+
import { join as join3, resolve } from "node:path";
|
|
16289
16337
|
function installAreas(home = homedir()) {
|
|
16290
16338
|
return {
|
|
16291
|
-
"claude-code": { project:
|
|
16292
|
-
codex: { project:
|
|
16339
|
+
"claude-code": { project: join3(".claude", "skills"), global: join3(home, ".claude", "skills") },
|
|
16340
|
+
codex: { project: join3(".agents", "skills") }
|
|
16293
16341
|
};
|
|
16294
16342
|
}
|
|
16295
16343
|
var MAPPED_TARGETS = Object.keys(installAreas());
|
|
@@ -16320,11 +16368,31 @@ function resolveTargetArea(target, global = false, home) {
|
|
|
16320
16368
|
}
|
|
16321
16369
|
function resolveTargetDir(target, slug, global = false, home) {
|
|
16322
16370
|
const area = resolveTargetArea(target, global, home);
|
|
16323
|
-
return area.ok ? { ok: true, dir:
|
|
16371
|
+
return area.ok ? { ok: true, dir: join3(area.dir, slug) } : area;
|
|
16324
16372
|
}
|
|
16325
16373
|
function mappableDeclaredTargets(declared) {
|
|
16326
16374
|
return declared.filter((t) => MAPPED_TARGETS.includes(t));
|
|
16327
16375
|
}
|
|
16376
|
+
function knownAreas(cwd, home) {
|
|
16377
|
+
const areas = [];
|
|
16378
|
+
const byTarget = installAreas(home);
|
|
16379
|
+
for (const tool of MAPPED_TARGETS) {
|
|
16380
|
+
const area = byTarget[tool];
|
|
16381
|
+
if (!area) {
|
|
16382
|
+
continue;
|
|
16383
|
+
}
|
|
16384
|
+
areas.push({
|
|
16385
|
+
tool,
|
|
16386
|
+
global: false,
|
|
16387
|
+
label: `${tool} project (${area.project})`,
|
|
16388
|
+
dir: resolve(cwd, area.project)
|
|
16389
|
+
});
|
|
16390
|
+
if (area.global) {
|
|
16391
|
+
areas.push({ tool, global: true, label: `${tool} user (${area.global})`, dir: area.global });
|
|
16392
|
+
}
|
|
16393
|
+
}
|
|
16394
|
+
return areas;
|
|
16395
|
+
}
|
|
16328
16396
|
|
|
16329
16397
|
// src/add.ts
|
|
16330
16398
|
function unsafeEntryPath(path) {
|
|
@@ -16391,11 +16459,11 @@ function writeTree(files, target) {
|
|
|
16391
16459
|
const tempDir = `${target}.tmp-${process.pid}`;
|
|
16392
16460
|
try {
|
|
16393
16461
|
for (const file2 of files) {
|
|
16394
|
-
const filePath =
|
|
16395
|
-
|
|
16396
|
-
|
|
16462
|
+
const filePath = join4(tempDir, file2.path);
|
|
16463
|
+
mkdirSync2(dirname(filePath), { recursive: true });
|
|
16464
|
+
writeFileSync2(filePath, file2.content);
|
|
16397
16465
|
}
|
|
16398
|
-
|
|
16466
|
+
mkdirSync2(dirname(target), { recursive: true });
|
|
16399
16467
|
if (existsSync(target)) {
|
|
16400
16468
|
rmdirSync(target);
|
|
16401
16469
|
}
|
|
@@ -16565,9 +16633,9 @@ async function runAdd(ref, opts = {}) {
|
|
|
16565
16633
|
}
|
|
16566
16634
|
}
|
|
16567
16635
|
if (packTool !== void 0) {
|
|
16568
|
-
const
|
|
16569
|
-
if (!
|
|
16570
|
-
push("", `error: ${
|
|
16636
|
+
const area2 = resolveTargetArea(packTool, packGlobal);
|
|
16637
|
+
if (!area2.ok) {
|
|
16638
|
+
push("", `error: ${area2.message}`);
|
|
16571
16639
|
return done(2);
|
|
16572
16640
|
}
|
|
16573
16641
|
const { installs, skipped } = resolvePackMembers(members, packTool);
|
|
@@ -16575,8 +16643,8 @@ async function runAdd(ref, opts = {}) {
|
|
|
16575
16643
|
push("", `error: none of this pack's skills support ${packTool}`);
|
|
16576
16644
|
return done(2);
|
|
16577
16645
|
}
|
|
16578
|
-
const areaAbs =
|
|
16579
|
-
const conflicts = installs.map((m) =>
|
|
16646
|
+
const areaAbs = resolve2(opts.cwd ?? process.cwd(), area2.dir);
|
|
16647
|
+
const conflicts = installs.map((m) => join4(areaAbs, m.name)).filter(
|
|
16580
16648
|
(dest) => existsSync(dest) && (!statSync2(dest).isDirectory() || readdirSync2(dest).length > 0)
|
|
16581
16649
|
);
|
|
16582
16650
|
if (conflicts.length > 0) {
|
|
@@ -16610,7 +16678,7 @@ async function runAdd(ref, opts = {}) {
|
|
|
16610
16678
|
const written = [];
|
|
16611
16679
|
try {
|
|
16612
16680
|
for (const plan of plans) {
|
|
16613
|
-
writeTree(plan.files,
|
|
16681
|
+
writeTree(plan.files, join4(areaAbs, plan.name));
|
|
16614
16682
|
written.push(plan.name);
|
|
16615
16683
|
}
|
|
16616
16684
|
} catch {
|
|
@@ -16620,6 +16688,14 @@ async function runAdd(ref, opts = {}) {
|
|
|
16620
16688
|
);
|
|
16621
16689
|
return done(2);
|
|
16622
16690
|
}
|
|
16691
|
+
for (const name of written) {
|
|
16692
|
+
recordReceipt(areaAbs, name, {
|
|
16693
|
+
version: preflight.version,
|
|
16694
|
+
sourceHash: preflight.sourceHash,
|
|
16695
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16696
|
+
pack: { slug, version: preflight.version }
|
|
16697
|
+
});
|
|
16698
|
+
}
|
|
16623
16699
|
push(
|
|
16624
16700
|
"",
|
|
16625
16701
|
`Installed ${written.length} skills to ${areaAbs}`,
|
|
@@ -16648,7 +16724,7 @@ async function runAdd(ref, opts = {}) {
|
|
|
16648
16724
|
}
|
|
16649
16725
|
}
|
|
16650
16726
|
}
|
|
16651
|
-
const target =
|
|
16727
|
+
const target = resolve2(opts.cwd ?? process.cwd(), targetDir);
|
|
16652
16728
|
if (existsSync(target)) {
|
|
16653
16729
|
if (!statSync2(target).isDirectory()) {
|
|
16654
16730
|
push("", `error: target ${target} already exists and is not a directory`);
|
|
@@ -16676,6 +16752,14 @@ async function runAdd(ref, opts = {}) {
|
|
|
16676
16752
|
push("", "error: could not write the install; nothing was installed");
|
|
16677
16753
|
return done(2);
|
|
16678
16754
|
}
|
|
16755
|
+
const area = knownAreas(opts.cwd ?? process.cwd()).find((a) => a.dir === dirname(target));
|
|
16756
|
+
if (area) {
|
|
16757
|
+
recordReceipt(area.dir, slug, {
|
|
16758
|
+
version: preflight.version,
|
|
16759
|
+
sourceHash: preflight.sourceHash,
|
|
16760
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16761
|
+
});
|
|
16762
|
+
}
|
|
16679
16763
|
push(
|
|
16680
16764
|
"",
|
|
16681
16765
|
`Installed ${download.files.length} file(s) to ${target}`,
|
|
@@ -16686,27 +16770,12 @@ async function runAdd(ref, opts = {}) {
|
|
|
16686
16770
|
|
|
16687
16771
|
// src/list.ts
|
|
16688
16772
|
import { existsSync as existsSync2, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
16689
|
-
import { join as
|
|
16690
|
-
function knownAreas(cwd, home) {
|
|
16691
|
-
const areas = [];
|
|
16692
|
-
const byTarget = installAreas(home);
|
|
16693
|
-
for (const tool of MAPPED_TARGETS) {
|
|
16694
|
-
const area = byTarget[tool];
|
|
16695
|
-
if (!area) {
|
|
16696
|
-
continue;
|
|
16697
|
-
}
|
|
16698
|
-
areas.push({ label: `${tool} project (${area.project})`, dir: resolve2(cwd, area.project) });
|
|
16699
|
-
if (area.global) {
|
|
16700
|
-
areas.push({ label: `${tool} user (${area.global})`, dir: area.global });
|
|
16701
|
-
}
|
|
16702
|
-
}
|
|
16703
|
-
return areas;
|
|
16704
|
-
}
|
|
16773
|
+
import { join as join5 } from "node:path";
|
|
16705
16774
|
function installedIn(dir) {
|
|
16706
16775
|
if (!existsSync2(dir)) {
|
|
16707
16776
|
return [];
|
|
16708
16777
|
}
|
|
16709
|
-
return readdirSync3(dir).sort().filter((name) => statSync3(
|
|
16778
|
+
return readdirSync3(dir).sort().filter((name) => statSync3(join5(dir, name)).isDirectory());
|
|
16710
16779
|
}
|
|
16711
16780
|
function runList(opts = {}) {
|
|
16712
16781
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -16720,8 +16789,10 @@ function runList(opts = {}) {
|
|
|
16720
16789
|
lines.push("");
|
|
16721
16790
|
}
|
|
16722
16791
|
lines.push(area.label);
|
|
16792
|
+
const receipts = readReceipts(area.dir);
|
|
16723
16793
|
for (const name of names) {
|
|
16724
|
-
|
|
16794
|
+
const version2 = receipts[name]?.version;
|
|
16795
|
+
lines.push(version2 ? ` ${name} ${version2}` : ` ${name}`);
|
|
16725
16796
|
}
|
|
16726
16797
|
}
|
|
16727
16798
|
if (lines.length === 0) {
|
|
@@ -16730,11 +16801,136 @@ function runList(opts = {}) {
|
|
|
16730
16801
|
return { lines, exitCode: 0 };
|
|
16731
16802
|
}
|
|
16732
16803
|
|
|
16804
|
+
// src/outdated.ts
|
|
16805
|
+
import { join as join6 } from "node:path";
|
|
16806
|
+
async function identifyByHash(fetchImpl, apiUrl, slug, dir) {
|
|
16807
|
+
let hash2;
|
|
16808
|
+
try {
|
|
16809
|
+
hash2 = loadPackage(dir).sourceHash;
|
|
16810
|
+
} catch {
|
|
16811
|
+
return void 0;
|
|
16812
|
+
}
|
|
16813
|
+
const detail = await getParsed(
|
|
16814
|
+
fetchImpl,
|
|
16815
|
+
`${apiUrl}/skills/${encodeURIComponent(slug)}`,
|
|
16816
|
+
publicSkillDetailSchema
|
|
16817
|
+
);
|
|
16818
|
+
if (!detail.ok) {
|
|
16819
|
+
return void 0;
|
|
16820
|
+
}
|
|
16821
|
+
for (const version2 of detail.data.versions) {
|
|
16822
|
+
const preflight = await getParsed(
|
|
16823
|
+
fetchImpl,
|
|
16824
|
+
`${apiUrl}/skills/${encodeURIComponent(slug)}/${encodeURIComponent(version2.version)}/preflight`,
|
|
16825
|
+
publicPreflightSchema
|
|
16826
|
+
);
|
|
16827
|
+
if (preflight.ok && preflight.data.sourceHash === hash2) {
|
|
16828
|
+
return version2.version;
|
|
16829
|
+
}
|
|
16830
|
+
}
|
|
16831
|
+
return void 0;
|
|
16832
|
+
}
|
|
16833
|
+
async function runOutdated(opts = {}) {
|
|
16834
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
16835
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
16836
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
16837
|
+
const fetched = await getParsed(fetchImpl, `${apiUrl}/skills`, publicSkillListSchema);
|
|
16838
|
+
if (!fetched.ok) {
|
|
16839
|
+
return { lines: [`error: ${fetched.message}`], exitCode: 2 };
|
|
16840
|
+
}
|
|
16841
|
+
const latestBySlug = new Map(fetched.data.map((s) => [s.slug, s]));
|
|
16842
|
+
const lines = [];
|
|
16843
|
+
let outdatedCount = 0;
|
|
16844
|
+
let tracked = 0;
|
|
16845
|
+
for (const area of knownAreas(cwd, opts.home)) {
|
|
16846
|
+
const names = installedIn(area.dir);
|
|
16847
|
+
if (names.length === 0) {
|
|
16848
|
+
continue;
|
|
16849
|
+
}
|
|
16850
|
+
const receipts = readReceipts(area.dir);
|
|
16851
|
+
const rows = [];
|
|
16852
|
+
const seenPacks = /* @__PURE__ */ new Set();
|
|
16853
|
+
let unrelated = 0;
|
|
16854
|
+
for (const name of names) {
|
|
16855
|
+
const receipt = receipts[name];
|
|
16856
|
+
if (receipt?.pack) {
|
|
16857
|
+
if (seenPacks.has(receipt.pack.slug)) {
|
|
16858
|
+
continue;
|
|
16859
|
+
}
|
|
16860
|
+
seenPacks.add(receipt.pack.slug);
|
|
16861
|
+
tracked += 1;
|
|
16862
|
+
const members = Object.values(receipts).filter(
|
|
16863
|
+
(r) => r.pack?.slug === receipt.pack?.slug
|
|
16864
|
+
).length;
|
|
16865
|
+
const latest = latestBySlug.get(receipt.pack.slug)?.version;
|
|
16866
|
+
if (latest === void 0) {
|
|
16867
|
+
rows.push(` ${receipt.pack.slug} ${receipt.pack.version} not in the directory`);
|
|
16868
|
+
} else if (latest === receipt.pack.version) {
|
|
16869
|
+
rows.push(` ${receipt.pack.slug} ${receipt.pack.version} current (pack, ${members} skills)`);
|
|
16870
|
+
} else {
|
|
16871
|
+
outdatedCount += 1;
|
|
16872
|
+
rows.push(
|
|
16873
|
+
` ${receipt.pack.slug} ${receipt.pack.version} -> ${latest} (pack, ${members} skills)`
|
|
16874
|
+
);
|
|
16875
|
+
}
|
|
16876
|
+
continue;
|
|
16877
|
+
}
|
|
16878
|
+
if (receipt) {
|
|
16879
|
+
tracked += 1;
|
|
16880
|
+
const latest = latestBySlug.get(name)?.version;
|
|
16881
|
+
if (latest === void 0) {
|
|
16882
|
+
rows.push(` ${name} ${receipt.version} not in the directory`);
|
|
16883
|
+
} else if (latest === receipt.version) {
|
|
16884
|
+
rows.push(` ${name} ${receipt.version} current`);
|
|
16885
|
+
} else {
|
|
16886
|
+
outdatedCount += 1;
|
|
16887
|
+
rows.push(` ${name} ${receipt.version} -> ${latest}`);
|
|
16888
|
+
}
|
|
16889
|
+
continue;
|
|
16890
|
+
}
|
|
16891
|
+
if (!latestBySlug.has(name)) {
|
|
16892
|
+
unrelated += 1;
|
|
16893
|
+
continue;
|
|
16894
|
+
}
|
|
16895
|
+
const identified = await identifyByHash(fetchImpl, apiUrl, name, join6(area.dir, name));
|
|
16896
|
+
if (identified !== void 0) {
|
|
16897
|
+
tracked += 1;
|
|
16898
|
+
const latest = latestBySlug.get(name)?.version;
|
|
16899
|
+
if (latest === identified) {
|
|
16900
|
+
rows.push(` ${name} ${identified} current (identified by hash)`);
|
|
16901
|
+
} else {
|
|
16902
|
+
outdatedCount += 1;
|
|
16903
|
+
rows.push(` ${name} ${identified} -> ${latest} (identified by hash)`);
|
|
16904
|
+
}
|
|
16905
|
+
continue;
|
|
16906
|
+
}
|
|
16907
|
+
rows.push(` ${name} ? no receipt - reinstall to track updates`);
|
|
16908
|
+
}
|
|
16909
|
+
if (rows.length > 0) {
|
|
16910
|
+
if (lines.length > 0) {
|
|
16911
|
+
lines.push("");
|
|
16912
|
+
}
|
|
16913
|
+
lines.push(area.label, ...rows);
|
|
16914
|
+
if (unrelated > 0) {
|
|
16915
|
+
lines.push(` (${unrelated} folder(s) not from the directory)`);
|
|
16916
|
+
}
|
|
16917
|
+
}
|
|
16918
|
+
}
|
|
16919
|
+
if (tracked === 0 && lines.length === 0) {
|
|
16920
|
+
return { lines: ["No skills installed in the known install areas."], exitCode: 0 };
|
|
16921
|
+
}
|
|
16922
|
+
lines.push(
|
|
16923
|
+
"",
|
|
16924
|
+
outdatedCount === 0 ? "Everything is current." : `${outdatedCount} update(s) available - skillpass update <slug>`
|
|
16925
|
+
);
|
|
16926
|
+
return { lines, exitCode: outdatedCount === 0 ? 0 : 1 };
|
|
16927
|
+
}
|
|
16928
|
+
|
|
16733
16929
|
// src/remove.ts
|
|
16734
16930
|
import { existsSync as existsSync3, rmSync as rmSync2, statSync as statSync4 } from "node:fs";
|
|
16735
|
-
import { join as
|
|
16931
|
+
import { dirname as dirname2, join as join7, resolve as resolve3 } from "node:path";
|
|
16736
16932
|
function looksLikeInstalledSkill(dir) {
|
|
16737
|
-
return existsSync3(
|
|
16933
|
+
return existsSync3(join7(dir, "SKILL.md")) || existsSync3(join7(dir, "skill.json"));
|
|
16738
16934
|
}
|
|
16739
16935
|
function candidateDirs(slug, cwd, home) {
|
|
16740
16936
|
const dirs = [];
|
|
@@ -16744,9 +16940,9 @@ function candidateDirs(slug, cwd, home) {
|
|
|
16744
16940
|
if (!area) {
|
|
16745
16941
|
continue;
|
|
16746
16942
|
}
|
|
16747
|
-
dirs.push(resolve3(cwd,
|
|
16943
|
+
dirs.push(resolve3(cwd, join7(area.project, slug)));
|
|
16748
16944
|
if (area.global) {
|
|
16749
|
-
dirs.push(
|
|
16945
|
+
dirs.push(join7(area.global, slug));
|
|
16750
16946
|
}
|
|
16751
16947
|
}
|
|
16752
16948
|
dirs.push(resolve3(cwd, slug));
|
|
@@ -16760,6 +16956,39 @@ function runRemove(slug, opts = {}) {
|
|
|
16760
16956
|
return { lines: ["error: --global needs --target (e.g. --target claude-code)"], exitCode: 2 };
|
|
16761
16957
|
}
|
|
16762
16958
|
const cwd = opts.cwd ?? process.cwd();
|
|
16959
|
+
if (!opts.dir) {
|
|
16960
|
+
let areas = knownAreas(cwd, opts.home);
|
|
16961
|
+
if (opts.target) {
|
|
16962
|
+
areas = areas.filter((a) => a.tool === opts.target && a.global === (opts.global ?? false));
|
|
16963
|
+
}
|
|
16964
|
+
const packHits = areas.map((area) => ({
|
|
16965
|
+
area,
|
|
16966
|
+
members: Object.entries(readReceipts(area.dir)).filter(([, r]) => r.pack?.slug === slug).map(([name]) => name)
|
|
16967
|
+
})).filter((hit) => hit.members.length > 0);
|
|
16968
|
+
if (packHits.length > 1) {
|
|
16969
|
+
return {
|
|
16970
|
+
lines: [
|
|
16971
|
+
`error: the ${slug} pack is installed in more than one place; pick one with --target:`,
|
|
16972
|
+
...packHits.map((hit) => ` ${hit.area.dir}`)
|
|
16973
|
+
],
|
|
16974
|
+
exitCode: 2
|
|
16975
|
+
};
|
|
16976
|
+
}
|
|
16977
|
+
if (packHits.length === 1) {
|
|
16978
|
+
const { area, members } = packHits[0];
|
|
16979
|
+
for (const name of members) {
|
|
16980
|
+
rmSync2(join7(area.dir, name), { recursive: true, force: true });
|
|
16981
|
+
removeReceipt(area.dir, name);
|
|
16982
|
+
}
|
|
16983
|
+
return {
|
|
16984
|
+
lines: [
|
|
16985
|
+
`Removed pack ${slug} (${members.length} skills) from ${area.dir}`,
|
|
16986
|
+
` ${members.sort().join(", ")}`
|
|
16987
|
+
],
|
|
16988
|
+
exitCode: 0
|
|
16989
|
+
};
|
|
16990
|
+
}
|
|
16991
|
+
}
|
|
16763
16992
|
let dir;
|
|
16764
16993
|
if (opts.target) {
|
|
16765
16994
|
const resolved = resolveTargetDir(opts.target, slug, opts.global, opts.home);
|
|
@@ -16800,6 +17029,7 @@ function runRemove(slug, opts = {}) {
|
|
|
16800
17029
|
};
|
|
16801
17030
|
}
|
|
16802
17031
|
rmSync2(dir, { recursive: true });
|
|
17032
|
+
removeReceipt(dirname2(dir), slug);
|
|
16803
17033
|
return { lines: [`Removed ${slug} from ${dir}`], exitCode: 0 };
|
|
16804
17034
|
}
|
|
16805
17035
|
|
|
@@ -16928,6 +17158,288 @@ async function runSearch(opts = {}) {
|
|
|
16928
17158
|
};
|
|
16929
17159
|
}
|
|
16930
17160
|
|
|
17161
|
+
// src/update.ts
|
|
17162
|
+
import { existsSync as existsSync4, readdirSync as readdirSync4, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
|
|
17163
|
+
import { join as join8 } from "node:path";
|
|
17164
|
+
async function locate(slug, opts, fetchImpl, apiUrl) {
|
|
17165
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
17166
|
+
let areas = knownAreas(cwd, opts.home);
|
|
17167
|
+
if (opts.target) {
|
|
17168
|
+
areas = areas.filter((a) => a.tool === opts.target && a.global === (opts.global ?? false));
|
|
17169
|
+
}
|
|
17170
|
+
const hits = [];
|
|
17171
|
+
for (const area of areas) {
|
|
17172
|
+
const receipts = readReceipts(area.dir);
|
|
17173
|
+
const receipt = receipts[slug];
|
|
17174
|
+
const dir = join8(area.dir, slug);
|
|
17175
|
+
if (receipt) {
|
|
17176
|
+
hits.push({
|
|
17177
|
+
area,
|
|
17178
|
+
dir,
|
|
17179
|
+
installedVersion: receipt.version,
|
|
17180
|
+
packSlug: receipt.pack?.slug
|
|
17181
|
+
});
|
|
17182
|
+
continue;
|
|
17183
|
+
}
|
|
17184
|
+
const packMember = Object.values(receipts).find((r) => r.pack?.slug === slug);
|
|
17185
|
+
if (packMember?.pack) {
|
|
17186
|
+
hits.push({ area, dir: area.dir, installedVersion: packMember.pack.version, packSlug: slug });
|
|
17187
|
+
continue;
|
|
17188
|
+
}
|
|
17189
|
+
if (existsSync4(dir)) {
|
|
17190
|
+
const identified = await identifyByHash(fetchImpl, apiUrl, slug, dir);
|
|
17191
|
+
if (identified !== void 0) {
|
|
17192
|
+
hits.push({ area, dir, installedVersion: identified });
|
|
17193
|
+
}
|
|
17194
|
+
}
|
|
17195
|
+
}
|
|
17196
|
+
return hits;
|
|
17197
|
+
}
|
|
17198
|
+
function permissionChanges(installed, target) {
|
|
17199
|
+
const flat = (sets) => /* @__PURE__ */ new Set([...sets.declared, ...sets.detected]);
|
|
17200
|
+
const before = flat(installed);
|
|
17201
|
+
const after = flat(target);
|
|
17202
|
+
return {
|
|
17203
|
+
added: [...after].filter((key) => !before.has(key)).sort(),
|
|
17204
|
+
removed: [...before].filter((key) => !after.has(key)).sort()
|
|
17205
|
+
};
|
|
17206
|
+
}
|
|
17207
|
+
function swapTree(files, dir) {
|
|
17208
|
+
const fresh = `${dir}.new-${process.pid}`;
|
|
17209
|
+
const aside = `${dir}.old-${process.pid}`;
|
|
17210
|
+
writeTree(files, fresh);
|
|
17211
|
+
renameSync2(dir, aside);
|
|
17212
|
+
try {
|
|
17213
|
+
renameSync2(fresh, dir);
|
|
17214
|
+
} catch (err2) {
|
|
17215
|
+
renameSync2(aside, dir);
|
|
17216
|
+
rmSync3(fresh, { recursive: true, force: true });
|
|
17217
|
+
throw err2;
|
|
17218
|
+
}
|
|
17219
|
+
rmSync3(aside, { recursive: true, force: true });
|
|
17220
|
+
}
|
|
17221
|
+
async function runUpdate(ref, opts = {}) {
|
|
17222
|
+
const lines = [];
|
|
17223
|
+
const streamed = opts.emit !== void 0;
|
|
17224
|
+
const push = (...next) => {
|
|
17225
|
+
lines.push(...next);
|
|
17226
|
+
if (next.length > 0) {
|
|
17227
|
+
opts.emit?.(next.join("\n"));
|
|
17228
|
+
}
|
|
17229
|
+
};
|
|
17230
|
+
const done = (exitCode) => ({ lines, exitCode, streamed });
|
|
17231
|
+
const st = opts.style ?? PLAIN;
|
|
17232
|
+
if (opts.global && !opts.target) {
|
|
17233
|
+
push("error: --global needs --target (e.g. --target claude-code)");
|
|
17234
|
+
return done(2);
|
|
17235
|
+
}
|
|
17236
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
17237
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17238
|
+
const { slug } = parseSkillRef(ref);
|
|
17239
|
+
if (!slug) {
|
|
17240
|
+
push(`error: invalid skill reference "${ref}"`);
|
|
17241
|
+
return done(2);
|
|
17242
|
+
}
|
|
17243
|
+
const hits = await locate(slug, opts, fetchImpl, apiUrl);
|
|
17244
|
+
const memberHit = hits.find((h) => h.packSlug !== void 0 && h.packSlug !== slug);
|
|
17245
|
+
if (memberHit?.packSlug !== void 0) {
|
|
17246
|
+
push(
|
|
17247
|
+
`error: ${slug} is part of the ${memberHit.packSlug} pack; run skillpass update ${memberHit.packSlug}`
|
|
17248
|
+
);
|
|
17249
|
+
return done(2);
|
|
17250
|
+
}
|
|
17251
|
+
if (hits.length === 0) {
|
|
17252
|
+
push(
|
|
17253
|
+
`error: ${slug} is not installed in a known skills area (only receipted or hash-identifiable installs can update)`
|
|
17254
|
+
);
|
|
17255
|
+
return done(2);
|
|
17256
|
+
}
|
|
17257
|
+
if (hits.length > 1) {
|
|
17258
|
+
push(
|
|
17259
|
+
`error: ${slug} is installed in more than one place; pick one with --target [--global]:`,
|
|
17260
|
+
...hits.map((h) => ` ${h.dir}`)
|
|
17261
|
+
);
|
|
17262
|
+
return done(2);
|
|
17263
|
+
}
|
|
17264
|
+
const installed = hits[0];
|
|
17265
|
+
const fetched = await fetchPreflight(fetchImpl, apiUrl, ref);
|
|
17266
|
+
if (!fetched.ok) {
|
|
17267
|
+
push(...fetched.result.lines);
|
|
17268
|
+
return done(fetched.result.exitCode);
|
|
17269
|
+
}
|
|
17270
|
+
const { detail, preflight } = fetched;
|
|
17271
|
+
if ((detail.packMembers?.length ?? 0) > 0 || installed.packSlug === slug) {
|
|
17272
|
+
return runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl, apiUrl, st, push, done);
|
|
17273
|
+
}
|
|
17274
|
+
if (installed.installedVersion === preflight.version) {
|
|
17275
|
+
push(`${slug} is already at ${preflight.version}.`);
|
|
17276
|
+
return done(0);
|
|
17277
|
+
}
|
|
17278
|
+
push(...renderPreflightReport(detail, preflight, opts.style));
|
|
17279
|
+
push("", ...await changesVsInstalled(fetchImpl, apiUrl, slug, installed, preflight, st));
|
|
17280
|
+
if (preflight.blocked) {
|
|
17281
|
+
return done(1);
|
|
17282
|
+
}
|
|
17283
|
+
if (preflight.riskLevel !== "low" && !opts.yes) {
|
|
17284
|
+
if (!opts.confirmImpl) {
|
|
17285
|
+
push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
|
|
17286
|
+
return done(2);
|
|
17287
|
+
}
|
|
17288
|
+
const confirmed = await opts.confirmImpl(
|
|
17289
|
+
`Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `
|
|
17290
|
+
);
|
|
17291
|
+
if (!confirmed) {
|
|
17292
|
+
push("", "Update aborted; the installed version was left in place.");
|
|
17293
|
+
return done(2);
|
|
17294
|
+
}
|
|
17295
|
+
}
|
|
17296
|
+
const download = await downloadVerified(fetchImpl, apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
17297
|
+
if (!download.ok) {
|
|
17298
|
+
push("", `error: ${download.message}`);
|
|
17299
|
+
return done(2);
|
|
17300
|
+
}
|
|
17301
|
+
try {
|
|
17302
|
+
swapTree(download.files, installed.dir);
|
|
17303
|
+
} catch {
|
|
17304
|
+
push("", "error: could not replace the install; the installed version was left in place");
|
|
17305
|
+
return done(2);
|
|
17306
|
+
}
|
|
17307
|
+
recordReceipt(installed.area.dir, slug, {
|
|
17308
|
+
version: preflight.version,
|
|
17309
|
+
sourceHash: preflight.sourceHash,
|
|
17310
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17311
|
+
});
|
|
17312
|
+
push(
|
|
17313
|
+
"",
|
|
17314
|
+
`Updated ${slug} ${installed.installedVersion ?? "?"} -> ${preflight.version} in ${installed.dir}`,
|
|
17315
|
+
"Source hash verified against the Skill Passport."
|
|
17316
|
+
);
|
|
17317
|
+
return done(0);
|
|
17318
|
+
}
|
|
17319
|
+
async function changesVsInstalled(fetchImpl, apiUrl, slug, installed, target, st) {
|
|
17320
|
+
if (installed.installedVersion === void 0) {
|
|
17321
|
+
return ["Changes vs installed: unknown (no version identified)"];
|
|
17322
|
+
}
|
|
17323
|
+
const before = await getParsed(
|
|
17324
|
+
fetchImpl,
|
|
17325
|
+
`${apiUrl}/skills/${encodeURIComponent(slug)}/${encodeURIComponent(installed.installedVersion)}/preflight`,
|
|
17326
|
+
publicPreflightSchema
|
|
17327
|
+
);
|
|
17328
|
+
if (!before.ok) {
|
|
17329
|
+
return [
|
|
17330
|
+
`Changes vs installed v${installed.installedVersion}: unavailable (that version is no longer published)`
|
|
17331
|
+
];
|
|
17332
|
+
}
|
|
17333
|
+
const { added, removed } = permissionChanges(before.data.permissions, target.permissions);
|
|
17334
|
+
if (added.length === 0 && removed.length === 0) {
|
|
17335
|
+
return [`Changes vs installed v${installed.installedVersion}: no permission changes`];
|
|
17336
|
+
}
|
|
17337
|
+
return [
|
|
17338
|
+
`Changes vs installed v${installed.installedVersion}`,
|
|
17339
|
+
...added.map((key) => st.red(` + ${key} (new)`)),
|
|
17340
|
+
...removed.map((key) => ` - ${key} (no longer requested)`)
|
|
17341
|
+
];
|
|
17342
|
+
}
|
|
17343
|
+
async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl, apiUrl, st, push, done) {
|
|
17344
|
+
if (installed.packSlug !== slug) {
|
|
17345
|
+
push(`error: ${slug} is a pack but this install is not tracked as one; reinstall with skillpass add`);
|
|
17346
|
+
return done(2);
|
|
17347
|
+
}
|
|
17348
|
+
if (installed.installedVersion === preflight.version) {
|
|
17349
|
+
push(`${slug} is already at ${preflight.version}.`);
|
|
17350
|
+
return done(0);
|
|
17351
|
+
}
|
|
17352
|
+
push(...renderPreflightReport(detail, preflight, opts.style));
|
|
17353
|
+
push("", ...await changesVsInstalled(fetchImpl, apiUrl, slug, installed, preflight, st));
|
|
17354
|
+
if (preflight.blocked) {
|
|
17355
|
+
return done(1);
|
|
17356
|
+
}
|
|
17357
|
+
if (preflight.riskLevel !== "low" && !opts.yes) {
|
|
17358
|
+
if (!opts.confirmImpl) {
|
|
17359
|
+
push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
|
|
17360
|
+
return done(2);
|
|
17361
|
+
}
|
|
17362
|
+
const confirmed = await opts.confirmImpl(
|
|
17363
|
+
`Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `
|
|
17364
|
+
);
|
|
17365
|
+
if (!confirmed) {
|
|
17366
|
+
push("", "Update aborted; the installed version was left in place.");
|
|
17367
|
+
return done(2);
|
|
17368
|
+
}
|
|
17369
|
+
}
|
|
17370
|
+
const areaDir = installed.area.dir;
|
|
17371
|
+
const { installs, skipped } = resolvePackMembers(detail.packMembers ?? [], installed.area.tool);
|
|
17372
|
+
if (installs.length === 0) {
|
|
17373
|
+
push("", `error: none of the new version's skills support ${installed.area.tool}`);
|
|
17374
|
+
return done(2);
|
|
17375
|
+
}
|
|
17376
|
+
const receipts = readReceipts(areaDir);
|
|
17377
|
+
const currentMembers = Object.entries(receipts).filter(([, r]) => r.pack?.slug === slug).map(([name]) => name);
|
|
17378
|
+
const newNames = new Set(installs.map((m) => m.name));
|
|
17379
|
+
const toRemove = currentMembers.filter((name) => !newNames.has(name));
|
|
17380
|
+
const additions = installs.filter((m) => !currentMembers.includes(m.name));
|
|
17381
|
+
const conflicts = additions.map((m) => join8(areaDir, m.name)).filter(
|
|
17382
|
+
(dest) => existsSync4(dest) && (!statSync5(dest).isDirectory() || readdirSync4(dest).length > 0)
|
|
17383
|
+
);
|
|
17384
|
+
if (conflicts.length > 0) {
|
|
17385
|
+
push(
|
|
17386
|
+
"",
|
|
17387
|
+
"error: these destinations already exist and are not empty; nothing was changed:",
|
|
17388
|
+
...conflicts.map((c) => ` ${c}`)
|
|
17389
|
+
);
|
|
17390
|
+
return done(2);
|
|
17391
|
+
}
|
|
17392
|
+
const download = await downloadVerified(fetchImpl, apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
17393
|
+
if (!download.ok) {
|
|
17394
|
+
push("", `error: ${download.message}`);
|
|
17395
|
+
return done(2);
|
|
17396
|
+
}
|
|
17397
|
+
const plans = installs.map((m) => ({ ...m, files: memberFiles(download.files, m.sourceDir) }));
|
|
17398
|
+
const missing = plans.find((p) => p.files.length === 0);
|
|
17399
|
+
if (missing) {
|
|
17400
|
+
push("", `error: the snapshot has no files for "${missing.name}"; nothing was changed`);
|
|
17401
|
+
return done(2);
|
|
17402
|
+
}
|
|
17403
|
+
for (const name of skipped) {
|
|
17404
|
+
push("", `note: ${name} does not support ${installed.area.tool}; skipped`);
|
|
17405
|
+
}
|
|
17406
|
+
const done1 = [];
|
|
17407
|
+
try {
|
|
17408
|
+
for (const plan of plans) {
|
|
17409
|
+
const dest = join8(areaDir, plan.name);
|
|
17410
|
+
if (existsSync4(dest)) {
|
|
17411
|
+
swapTree(plan.files, dest);
|
|
17412
|
+
} else {
|
|
17413
|
+
writeTree(plan.files, dest);
|
|
17414
|
+
}
|
|
17415
|
+
done1.push(plan.name);
|
|
17416
|
+
recordReceipt(areaDir, plan.name, {
|
|
17417
|
+
version: preflight.version,
|
|
17418
|
+
sourceHash: preflight.sourceHash,
|
|
17419
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17420
|
+
pack: { slug, version: preflight.version }
|
|
17421
|
+
});
|
|
17422
|
+
}
|
|
17423
|
+
} catch {
|
|
17424
|
+
push(
|
|
17425
|
+
"",
|
|
17426
|
+
`error: could not write ${plans[done1.length].name}; updated before the failure: ${done1.join(", ") || "none"}`
|
|
17427
|
+
);
|
|
17428
|
+
return done(2);
|
|
17429
|
+
}
|
|
17430
|
+
for (const name of toRemove) {
|
|
17431
|
+
rmSync3(join8(areaDir, name), { recursive: true, force: true });
|
|
17432
|
+
removeReceipt(areaDir, name);
|
|
17433
|
+
}
|
|
17434
|
+
push(
|
|
17435
|
+
"",
|
|
17436
|
+
`Updated ${slug} ${installed.installedVersion ?? "?"} -> ${preflight.version} in ${areaDir}`,
|
|
17437
|
+
` ${done1.length} skill(s)${additions.length > 0 ? `, added: ${additions.map((m) => m.name).join(", ")}` : ""}${toRemove.length > 0 ? `, removed: ${toRemove.join(", ")}` : ""}`,
|
|
17438
|
+
"Source hash verified against the Skill Passport."
|
|
17439
|
+
);
|
|
17440
|
+
return done(0);
|
|
17441
|
+
}
|
|
17442
|
+
|
|
16931
17443
|
// src/scan.ts
|
|
16932
17444
|
async function runScan(path, opts = {}) {
|
|
16933
17445
|
let report;
|
|
@@ -16970,7 +17482,7 @@ async function runScan(path, opts = {}) {
|
|
|
16970
17482
|
}
|
|
16971
17483
|
|
|
16972
17484
|
// src/index.ts
|
|
16973
|
-
var VERSION = JSON.parse(
|
|
17485
|
+
var VERSION = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
16974
17486
|
var USAGE = [
|
|
16975
17487
|
"skillpass - validate AI agent skills locally and inspect hosted passports",
|
|
16976
17488
|
"",
|
|
@@ -16984,6 +17496,9 @@ var USAGE = [
|
|
|
16984
17496
|
" skillpass remove <slug> [--target <tool> [--global] | --dir <path>]",
|
|
16985
17497
|
" remove an installed skill",
|
|
16986
17498
|
" skillpass list show installed skills in the known areas",
|
|
17499
|
+
" skillpass outdated compare installed skills to the directory",
|
|
17500
|
+
" skillpass update <slug>[@version] [--target <tool> [--global]] [--yes]",
|
|
17501
|
+
" update an installed skill through the gate",
|
|
16987
17502
|
"",
|
|
16988
17503
|
"Flags:",
|
|
16989
17504
|
" --json print machine-readable JSON instead of the readable report",
|
|
@@ -16998,7 +17513,8 @@ var USAGE = [
|
|
|
16998
17513
|
" --help show this message",
|
|
16999
17514
|
"",
|
|
17000
17515
|
"The search, report, and add commands read the API base URL from SKILLPASS_API.",
|
|
17001
|
-
"Exit codes: 0 ok/warning, 1 failed or blocked, 2 usage/load/network errors
|
|
17516
|
+
"Exit codes: 0 ok/warning, 1 failed or blocked, 2 usage/load/network errors",
|
|
17517
|
+
"(outdated exits 1 when updates are available)."
|
|
17002
17518
|
].join("\n");
|
|
17003
17519
|
var COMMAND_FLAGS = {
|
|
17004
17520
|
scan: ["--json"],
|
|
@@ -17006,6 +17522,8 @@ var COMMAND_FLAGS = {
|
|
|
17006
17522
|
add: ["--yes", "--target", "--dir", "--global"],
|
|
17007
17523
|
remove: ["--target", "--dir", "--global"],
|
|
17008
17524
|
list: [],
|
|
17525
|
+
outdated: [],
|
|
17526
|
+
update: ["--yes", "--target", "--global"],
|
|
17009
17527
|
search: ["--target", "--category", "--packs", "--json"]
|
|
17010
17528
|
};
|
|
17011
17529
|
function parseCliArgs(argv) {
|
|
@@ -17124,6 +17642,27 @@ async function run(argv) {
|
|
|
17124
17642
|
}
|
|
17125
17643
|
return runList();
|
|
17126
17644
|
}
|
|
17645
|
+
if (args.command === "update") {
|
|
17646
|
+
const [ref] = args.positional;
|
|
17647
|
+
if (!ref) {
|
|
17648
|
+
return { lines: ["error: update needs a skill slug", "", USAGE], exitCode: 2 };
|
|
17649
|
+
}
|
|
17650
|
+
const tty = Boolean(process.stdin.isTTY);
|
|
17651
|
+
return runUpdate(ref, {
|
|
17652
|
+
yes: args.yes,
|
|
17653
|
+
target: args.target,
|
|
17654
|
+
global: args.global,
|
|
17655
|
+
style: styler(Boolean(process.stdout.isTTY)),
|
|
17656
|
+
confirmImpl: tty ? confirmViaTty : void 0,
|
|
17657
|
+
emit: (text) => console.log(text)
|
|
17658
|
+
});
|
|
17659
|
+
}
|
|
17660
|
+
if (args.command === "outdated") {
|
|
17661
|
+
if (args.positional.length > 0) {
|
|
17662
|
+
return { lines: ["error: outdated takes no arguments", "", USAGE], exitCode: 2 };
|
|
17663
|
+
}
|
|
17664
|
+
return runOutdated();
|
|
17665
|
+
}
|
|
17127
17666
|
if (args.command === "report") {
|
|
17128
17667
|
const [ref] = args.positional;
|
|
17129
17668
|
if (!ref) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillpass",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Validation-first installer for AI agent skills: search the SkillPass directory, inspect a skill's passport, and install hash-verified skills and packs for Claude Code, Codex, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"esbuild": "^0.28.1",
|
|
37
37
|
"fflate": "^0.8.3",
|
|
38
38
|
"typescript": "^6.0.3",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
39
|
+
"validator": "0.3.0",
|
|
40
|
+
"skill-schema": "0.1.0"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.mjs --log-level=warning",
|