skillpass 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/bin/skillpass.js +2 -0
- package/dist/{cli.mjs → cli.js} +985 -366
- package/package.json +3 -3
- package/bin/skillpass.mjs +0 -2
package/dist/{cli.mjs → cli.js}
RENAMED
|
@@ -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
|
|
@@ -493,16 +493,16 @@ function unzipSync(data, opts) {
|
|
|
493
493
|
|
|
494
494
|
// src/add.ts
|
|
495
495
|
import {
|
|
496
|
-
existsSync,
|
|
497
|
-
mkdirSync,
|
|
498
|
-
readdirSync as
|
|
496
|
+
existsSync as existsSync2,
|
|
497
|
+
mkdirSync as mkdirSync2,
|
|
498
|
+
readdirSync as readdirSync3,
|
|
499
499
|
renameSync,
|
|
500
500
|
rmdirSync,
|
|
501
501
|
rmSync,
|
|
502
|
-
statSync as
|
|
503
|
-
writeFileSync
|
|
502
|
+
statSync as statSync3,
|
|
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";
|
|
@@ -15281,10 +15281,10 @@ function parseManifest(input) {
|
|
|
15281
15281
|
}
|
|
15282
15282
|
|
|
15283
15283
|
// ../skill-schema/src/report.ts
|
|
15284
|
-
var reportFindingSchema = external_exports.
|
|
15284
|
+
var reportFindingSchema = external_exports.object({
|
|
15285
15285
|
code: external_exports.string().min(1),
|
|
15286
15286
|
message: external_exports.string().min(1),
|
|
15287
|
-
location: external_exports.
|
|
15287
|
+
location: external_exports.object({
|
|
15288
15288
|
path: external_exports.string().min(1),
|
|
15289
15289
|
line: external_exports.number().int().positive().optional(),
|
|
15290
15290
|
snippet: external_exports.string().optional()
|
|
@@ -15313,11 +15313,11 @@ var validationReportSchema = external_exports.strictObject({
|
|
|
15313
15313
|
});
|
|
15314
15314
|
|
|
15315
15315
|
// ../skill-schema/src/passport.ts
|
|
15316
|
-
var skillPassportSchema = external_exports.
|
|
15316
|
+
var skillPassportSchema = external_exports.object({
|
|
15317
15317
|
schemaVersion: external_exports.literal("0.1"),
|
|
15318
15318
|
validationStatus: validationStatusSchema,
|
|
15319
15319
|
riskLevel: riskLevelSchema,
|
|
15320
|
-
permissionsSummary: external_exports.
|
|
15320
|
+
permissionsSummary: external_exports.object({
|
|
15321
15321
|
declared: external_exports.array(permissionKeySchema),
|
|
15322
15322
|
detected: external_exports.array(permissionKeySchema)
|
|
15323
15323
|
}),
|
|
@@ -15338,7 +15338,7 @@ var skillPassportSchema = external_exports.strictObject({
|
|
|
15338
15338
|
|
|
15339
15339
|
// ../skill-schema/src/ai-review.ts
|
|
15340
15340
|
var aiReviewVerdictSchema = external_exports.enum(["clear", "caution", "concern"]);
|
|
15341
|
-
var aiReviewSchema = external_exports.
|
|
15341
|
+
var aiReviewSchema = external_exports.object({
|
|
15342
15342
|
summary: external_exports.string().min(1).max(600),
|
|
15343
15343
|
verdict: aiReviewVerdictSchema,
|
|
15344
15344
|
reasoning: external_exports.string().min(1).max(800),
|
|
@@ -15390,9 +15390,14 @@ var publishResultSchema = external_exports.object({
|
|
|
15390
15390
|
slug: external_exports.string().min(1),
|
|
15391
15391
|
version: external_exports.string().min(1)
|
|
15392
15392
|
});
|
|
15393
|
+
var MAX_SLUG_LENGTH = 60;
|
|
15394
|
+
function slugForSkill(name) {
|
|
15395
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SLUG_LENGTH).replace(/-+$/, "");
|
|
15396
|
+
return slug || "skill";
|
|
15397
|
+
}
|
|
15393
15398
|
|
|
15394
15399
|
// ../skill-schema/src/public.ts
|
|
15395
|
-
var publicSkillSummarySchema = external_exports.
|
|
15400
|
+
var publicSkillSummarySchema = external_exports.object({
|
|
15396
15401
|
slug: external_exports.string().min(1),
|
|
15397
15402
|
name: external_exports.string().min(1),
|
|
15398
15403
|
summary: external_exports.string().min(1),
|
|
@@ -15422,7 +15427,7 @@ var publicSkillSummarySchema = external_exports.strictObject({
|
|
|
15422
15427
|
publishedAt: external_exports.iso.datetime()
|
|
15423
15428
|
});
|
|
15424
15429
|
var publicSkillListSchema = external_exports.array(publicSkillSummarySchema);
|
|
15425
|
-
var publicSkillVersionSchema = external_exports.
|
|
15430
|
+
var publicSkillVersionSchema = external_exports.object({
|
|
15426
15431
|
version: external_exports.string().min(1),
|
|
15427
15432
|
validationStatus: validationStatusSchema,
|
|
15428
15433
|
riskLevel: riskLevelSchema,
|
|
@@ -15431,10 +15436,10 @@ var publicSkillVersionSchema = external_exports.strictObject({
|
|
|
15431
15436
|
var publicSkillDetailSchema = publicSkillSummarySchema.extend({
|
|
15432
15437
|
// Full member entries (incl. per-target variants) for a pack; the CLI's
|
|
15433
15438
|
// per-target install resolution reads exactly this. Null for single skills.
|
|
15434
|
-
packMembers: external_exports.array(skillEntrySchema).nullable().optional(),
|
|
15439
|
+
packMembers: external_exports.array(external_exports.object(skillEntrySchema.shape)).nullable().optional(),
|
|
15435
15440
|
githubRepoUrl: external_exports.string().min(1).nullable(),
|
|
15436
15441
|
passport: skillPassportSchema,
|
|
15437
|
-
maintainerInfo: external_exports.
|
|
15442
|
+
maintainerInfo: external_exports.object({
|
|
15438
15443
|
username: external_exports.string().min(1),
|
|
15439
15444
|
displayName: external_exports.string().min(1),
|
|
15440
15445
|
avatarUrl: external_exports.string().min(1)
|
|
@@ -15443,18 +15448,18 @@ var publicSkillDetailSchema = publicSkillSummarySchema.extend({
|
|
|
15443
15448
|
// The cached AI review for this version's source hash; null until generated.
|
|
15444
15449
|
aiReview: aiReviewSchema.nullable()
|
|
15445
15450
|
});
|
|
15446
|
-
var publicSkillSourceSchema = external_exports.
|
|
15451
|
+
var publicSkillSourceSchema = external_exports.object({
|
|
15447
15452
|
version: external_exports.string().min(1),
|
|
15448
15453
|
sourceHash: external_exports.string().min(1),
|
|
15449
|
-
files: external_exports.array(external_exports.
|
|
15454
|
+
files: external_exports.array(external_exports.object({ path: external_exports.string().min(1), content: external_exports.string() }))
|
|
15450
15455
|
});
|
|
15451
15456
|
|
|
15452
15457
|
// ../skill-schema/src/preflight.ts
|
|
15453
|
-
var permissionSetDiffSchema = external_exports.
|
|
15458
|
+
var permissionSetDiffSchema = external_exports.object({
|
|
15454
15459
|
added: external_exports.array(permissionKeySchema),
|
|
15455
15460
|
removed: external_exports.array(permissionKeySchema)
|
|
15456
15461
|
});
|
|
15457
|
-
var publicPreflightSchema = external_exports.
|
|
15462
|
+
var publicPreflightSchema = external_exports.object({
|
|
15458
15463
|
version: external_exports.string().min(1),
|
|
15459
15464
|
validationStatus: validationStatusSchema,
|
|
15460
15465
|
riskLevel: riskLevelSchema,
|
|
@@ -15462,11 +15467,11 @@ var publicPreflightSchema = external_exports.strictObject({
|
|
|
15462
15467
|
sourceVerified: external_exports.boolean(),
|
|
15463
15468
|
resolvedCommitSha: external_exports.string().min(1).nullable(),
|
|
15464
15469
|
generatedAt: external_exports.iso.datetime(),
|
|
15465
|
-
permissions: external_exports.
|
|
15470
|
+
permissions: external_exports.object({
|
|
15466
15471
|
declared: external_exports.array(permissionKeySchema),
|
|
15467
15472
|
detected: external_exports.array(permissionKeySchema)
|
|
15468
15473
|
}),
|
|
15469
|
-
diff: external_exports.
|
|
15474
|
+
diff: external_exports.object({
|
|
15470
15475
|
previousVersion: external_exports.string().min(1),
|
|
15471
15476
|
declared: permissionSetDiffSchema,
|
|
15472
15477
|
detected: permissionSetDiffSchema
|
|
@@ -15546,6 +15551,39 @@ var resolveReportInputSchema = external_exports.strictObject({
|
|
|
15546
15551
|
status: external_exports.enum(["reviewed", "actioned"])
|
|
15547
15552
|
});
|
|
15548
15553
|
|
|
15554
|
+
// ../skill-schema/src/maintainer.ts
|
|
15555
|
+
var SKILL_STATUSES = ["published", "draft", "private", "flagged"];
|
|
15556
|
+
var skillStatusSchema = external_exports.enum(SKILL_STATUSES);
|
|
15557
|
+
var maintainerSkillSchema = external_exports.strictObject({
|
|
15558
|
+
slug: external_exports.string().min(1),
|
|
15559
|
+
name: external_exports.string().min(1),
|
|
15560
|
+
status: skillStatusSchema,
|
|
15561
|
+
version: external_exports.string().nullable(),
|
|
15562
|
+
validationStatus: validationStatusSchema.nullable(),
|
|
15563
|
+
riskLevel: riskLevelSchema.nullable(),
|
|
15564
|
+
updatedAt: external_exports.iso.datetime()
|
|
15565
|
+
});
|
|
15566
|
+
var maintainerReportSchema = external_exports.strictObject({
|
|
15567
|
+
id: external_exports.number().int().positive(),
|
|
15568
|
+
skill: external_exports.strictObject({ slug: external_exports.string(), name: external_exports.string() }),
|
|
15569
|
+
reason: external_exports.string(),
|
|
15570
|
+
status: abuseReportStatusSchema,
|
|
15571
|
+
createdAt: external_exports.iso.datetime()
|
|
15572
|
+
});
|
|
15573
|
+
|
|
15574
|
+
// ../skill-schema/src/user.ts
|
|
15575
|
+
var USER_ROLES = ["user", "maintainer", "admin"];
|
|
15576
|
+
var userRoleSchema = external_exports.enum(USER_ROLES);
|
|
15577
|
+
var publicUserSchema = external_exports.object({
|
|
15578
|
+
id: external_exports.number().int().positive(),
|
|
15579
|
+
username: external_exports.string().min(1),
|
|
15580
|
+
displayName: external_exports.string().min(1),
|
|
15581
|
+
avatarUrl: external_exports.string().min(1),
|
|
15582
|
+
role: userRoleSchema,
|
|
15583
|
+
reputation: external_exports.number().int(),
|
|
15584
|
+
createdAt: external_exports.iso.datetime()
|
|
15585
|
+
});
|
|
15586
|
+
|
|
15549
15587
|
// ../validator/src/rules/permissions.ts
|
|
15550
15588
|
var PERMISSION_SIGNALS = [
|
|
15551
15589
|
{
|
|
@@ -15600,24 +15638,30 @@ var PERMISSION_SIGNALS = [
|
|
|
15600
15638
|
}
|
|
15601
15639
|
];
|
|
15602
15640
|
function detectPermissions(files) {
|
|
15603
|
-
const detected = /* @__PURE__ */ new
|
|
15641
|
+
const detected = /* @__PURE__ */ new Set();
|
|
15604
15642
|
for (const file2 of files) {
|
|
15605
|
-
file2.content.split("\n")
|
|
15643
|
+
for (const line of file2.content.split("\n")) {
|
|
15606
15644
|
for (const signal of PERMISSION_SIGNALS) {
|
|
15607
15645
|
if (!detected.has(signal.permission) && signal.pattern.test(line)) {
|
|
15608
|
-
detected.
|
|
15609
|
-
permission: signal.permission,
|
|
15610
|
-
description: signal.description,
|
|
15611
|
-
location: { path: file2.path, line: i2 + 1, snippet: line.trim() }
|
|
15612
|
-
});
|
|
15646
|
+
detected.add(signal.permission);
|
|
15613
15647
|
}
|
|
15614
15648
|
}
|
|
15615
|
-
}
|
|
15649
|
+
}
|
|
15616
15650
|
}
|
|
15617
|
-
return [...detected
|
|
15651
|
+
return [...detected];
|
|
15618
15652
|
}
|
|
15619
15653
|
|
|
15620
15654
|
// ../validator/src/load.ts
|
|
15655
|
+
var NUL_SCAN_BYTES = 8e3;
|
|
15656
|
+
function isBinary(bytes) {
|
|
15657
|
+
if (bytes.subarray(0, NUL_SCAN_BYTES).includes(0)) return true;
|
|
15658
|
+
try {
|
|
15659
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
15660
|
+
return false;
|
|
15661
|
+
} catch {
|
|
15662
|
+
return true;
|
|
15663
|
+
}
|
|
15664
|
+
}
|
|
15621
15665
|
var PackageReadError = class extends Error {
|
|
15622
15666
|
constructor(dir, cause) {
|
|
15623
15667
|
super(`cannot read skill package at "${dir}"`, { cause });
|
|
@@ -15626,19 +15670,23 @@ var PackageReadError = class extends Error {
|
|
|
15626
15670
|
};
|
|
15627
15671
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
15628
15672
|
function walk(root, rel = "") {
|
|
15629
|
-
const
|
|
15673
|
+
const tree = { files: [], binaries: [] };
|
|
15630
15674
|
for (const name of readdirSync(join(root, rel)).sort()) {
|
|
15631
15675
|
const relPath = rel === "" ? name : `${rel}/${name}`;
|
|
15632
15676
|
const stats = statSync(join(root, relPath));
|
|
15633
15677
|
if (stats.isDirectory()) {
|
|
15634
15678
|
if (!SKIP_DIRS.has(name)) {
|
|
15635
|
-
|
|
15679
|
+
const sub = walk(root, relPath);
|
|
15680
|
+
tree.files.push(...sub.files);
|
|
15681
|
+
tree.binaries.push(...sub.binaries);
|
|
15636
15682
|
}
|
|
15637
15683
|
} else {
|
|
15638
|
-
|
|
15684
|
+
const bytes = readFileSync(join(root, relPath));
|
|
15685
|
+
if (isBinary(bytes)) tree.binaries.push(relPath);
|
|
15686
|
+
else tree.files.push({ path: relPath, content: bytes.toString("utf8") });
|
|
15639
15687
|
}
|
|
15640
15688
|
}
|
|
15641
|
-
return
|
|
15689
|
+
return tree;
|
|
15642
15690
|
}
|
|
15643
15691
|
function hashFiles(files) {
|
|
15644
15692
|
const hash2 = createHash("sha256");
|
|
@@ -15665,7 +15713,7 @@ function readManifest(files) {
|
|
|
15665
15713
|
if (!result.success) {
|
|
15666
15714
|
return { state: "invalid", error: result.error };
|
|
15667
15715
|
}
|
|
15668
|
-
return { state: "ok", data: result.data
|
|
15716
|
+
return { state: "ok", data: result.data };
|
|
15669
15717
|
}
|
|
15670
15718
|
var FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/;
|
|
15671
15719
|
function readSkillMeta(content) {
|
|
@@ -15690,19 +15738,43 @@ function readSkillMeta(content) {
|
|
|
15690
15738
|
const text = block[1] === ">" ? gathered.join(" ").replace(/\s+/g, " ").trim() : gathered.join("\n").trim();
|
|
15691
15739
|
if (text) out[key] = text;
|
|
15692
15740
|
} else {
|
|
15693
|
-
|
|
15741
|
+
const parts = [kv[2]];
|
|
15742
|
+
for (let j = i2 + 1; j < lines.length && /^\s+\S/.test(lines[j]); j++) parts.push(lines[j].trim());
|
|
15743
|
+
out[key] = parts.join(" ").replace(/^["']|["']$/g, "");
|
|
15694
15744
|
}
|
|
15695
15745
|
}
|
|
15696
15746
|
}
|
|
15697
15747
|
if (!out.description) {
|
|
15698
|
-
const prose = body
|
|
15699
|
-
if (prose) out.description = prose
|
|
15748
|
+
const prose = firstParagraph(body);
|
|
15749
|
+
if (prose) out.description = prose;
|
|
15700
15750
|
}
|
|
15701
15751
|
return out;
|
|
15702
15752
|
}
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15753
|
+
var HTML_TAG_RE = /<\/?[a-zA-Z][^>]*>/g;
|
|
15754
|
+
var MD_LINK_RE = /\[([^\]]*)\]\([^)]*\)/g;
|
|
15755
|
+
var STRUCTURAL_LINE_RE = /^(#|!\[|\[!\[|---$|\*\*\*$|___$)|^<h[1-6][\s>]/i;
|
|
15756
|
+
var DESCRIPTION_MAX = 200;
|
|
15757
|
+
var cleanInline = (line) => line.replace(HTML_TAG_RE, " ").replace(MD_LINK_RE, "$1").replace(/^>\s?/, "").replace(/\s+/g, " ").trim();
|
|
15758
|
+
function clip(text, max2) {
|
|
15759
|
+
if (text.length <= max2) return text;
|
|
15760
|
+
const head = text.slice(0, max2);
|
|
15761
|
+
const sentenceEnd = Math.max(head.lastIndexOf(". "), head.lastIndexOf("! "), head.lastIndexOf("? "));
|
|
15762
|
+
if (sentenceEnd >= 60) return head.slice(0, sentenceEnd + 1);
|
|
15763
|
+
const wordEnd = head.lastIndexOf(" ");
|
|
15764
|
+
return (wordEnd > 0 ? head.slice(0, wordEnd) : head).replace(/[,;:]+$/, "").trim();
|
|
15765
|
+
}
|
|
15766
|
+
function firstParagraph(text) {
|
|
15767
|
+
const kept = [];
|
|
15768
|
+
for (const raw of text.split("\n")) {
|
|
15769
|
+
const source = raw.trim();
|
|
15770
|
+
const line = STRUCTURAL_LINE_RE.test(source) ? "" : cleanInline(source);
|
|
15771
|
+
if (!line) {
|
|
15772
|
+
if (kept.length > 0) break;
|
|
15773
|
+
continue;
|
|
15774
|
+
}
|
|
15775
|
+
kept.push(line);
|
|
15776
|
+
}
|
|
15777
|
+
return kept.length > 0 ? clip(kept.join(" "), DESCRIPTION_MAX) : void 0;
|
|
15706
15778
|
}
|
|
15707
15779
|
function inferTargets(files) {
|
|
15708
15780
|
const targets = /* @__PURE__ */ new Set();
|
|
@@ -15715,8 +15787,8 @@ function inferTargets(files) {
|
|
|
15715
15787
|
function inferManifest(files, fallbackName) {
|
|
15716
15788
|
const skillFile = files.find((f) => f.path === "SKILL.md");
|
|
15717
15789
|
const meta3 = skillFile ? readSkillMeta(skillFile.content) : {};
|
|
15718
|
-
const name =
|
|
15719
|
-
const permissions =
|
|
15790
|
+
const name = slugForSkill(meta3.name ?? fallbackName);
|
|
15791
|
+
const permissions = detectPermissions(files);
|
|
15720
15792
|
return {
|
|
15721
15793
|
schemaVersion: "0.1",
|
|
15722
15794
|
name,
|
|
@@ -15751,11 +15823,7 @@ function collectMemberFiles(files) {
|
|
|
15751
15823
|
}
|
|
15752
15824
|
function readmeProse(files) {
|
|
15753
15825
|
const readme = files.find((f) => f.path === "README.md");
|
|
15754
|
-
|
|
15755
|
-
const prose = readme.content.split("\n").map((line) => line.trim()).find(
|
|
15756
|
-
(line) => line && !line.startsWith("#") && !line.startsWith("![") && !line.startsWith("[![") && !line.startsWith("<")
|
|
15757
|
-
);
|
|
15758
|
-
return prose?.slice(0, 200);
|
|
15826
|
+
return readme ? firstParagraph(readme.content) : void 0;
|
|
15759
15827
|
}
|
|
15760
15828
|
function inferNestedManifest(files, fallbackName) {
|
|
15761
15829
|
const members = collectMemberFiles(files).sort(
|
|
@@ -15770,8 +15838,8 @@ function inferNestedManifest(files, fallbackName) {
|
|
|
15770
15838
|
const entryPath = member.claudePath ?? member.agentsPath ?? member.plainPath;
|
|
15771
15839
|
const file2 = files.find((f) => f.path === entryPath);
|
|
15772
15840
|
const meta3 = readSkillMeta(file2.content);
|
|
15773
|
-
let entryName =
|
|
15774
|
-
if (used.has(entryName)) entryName =
|
|
15841
|
+
let entryName = slugForSkill(meta3.name ?? member.folder);
|
|
15842
|
+
if (used.has(entryName)) entryName = slugForSkill(member.folder);
|
|
15775
15843
|
used.add(entryName);
|
|
15776
15844
|
const targets = member.plainPath ? inferTargets(files) : [
|
|
15777
15845
|
...member.claudePath ? ["claude-code"] : [],
|
|
@@ -15787,10 +15855,10 @@ function inferNestedManifest(files, fallbackName) {
|
|
|
15787
15855
|
};
|
|
15788
15856
|
});
|
|
15789
15857
|
const single = entries.length === 1 ? entries[0] : null;
|
|
15790
|
-
const permissions =
|
|
15858
|
+
const permissions = detectPermissions(files);
|
|
15791
15859
|
return {
|
|
15792
15860
|
schemaVersion: "0.1",
|
|
15793
|
-
name: single ? single.name :
|
|
15861
|
+
name: single ? single.name : slugForSkill(fallbackName),
|
|
15794
15862
|
description: single ? single.description ?? single.name : readmeProse(files) ?? `A pack of ${entries.length} skills`,
|
|
15795
15863
|
targets: [...packTargets],
|
|
15796
15864
|
permissions,
|
|
@@ -15820,82 +15888,33 @@ function resolveEntries(dir, manifest, files) {
|
|
|
15820
15888
|
]);
|
|
15821
15889
|
}
|
|
15822
15890
|
var byPath = (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
|
|
15823
|
-
function loadPackageFromFiles(files, name = "package") {
|
|
15891
|
+
function loadPackageFromFiles(files, name = "package", binaries = []) {
|
|
15824
15892
|
const sorted = [...files].sort(byPath);
|
|
15825
15893
|
let manifest = readManifest(sorted);
|
|
15826
15894
|
if (manifest.state === "missing") {
|
|
15827
15895
|
const data = sorted.some((f) => f.path === "SKILL.md") ? inferManifest(sorted, name) : inferNestedManifest(sorted, name);
|
|
15828
15896
|
if (data) {
|
|
15829
|
-
manifest = { state: "ok", inferred: true, data
|
|
15897
|
+
manifest = { state: "ok", inferred: true, data };
|
|
15830
15898
|
}
|
|
15831
15899
|
}
|
|
15832
15900
|
return {
|
|
15833
|
-
dir: name,
|
|
15834
15901
|
files: sorted,
|
|
15835
15902
|
manifest,
|
|
15836
15903
|
entries: resolveEntries(name, manifest, sorted),
|
|
15837
|
-
sourceHash: hashFiles(sorted)
|
|
15904
|
+
sourceHash: hashFiles(sorted),
|
|
15905
|
+
binaries
|
|
15838
15906
|
};
|
|
15839
15907
|
}
|
|
15840
15908
|
function loadPackage(dir) {
|
|
15841
|
-
let
|
|
15909
|
+
let tree;
|
|
15842
15910
|
try {
|
|
15843
|
-
|
|
15911
|
+
tree = walk(dir);
|
|
15844
15912
|
} catch (err2) {
|
|
15845
15913
|
throw new PackageReadError(dir, err2);
|
|
15846
15914
|
}
|
|
15847
|
-
return loadPackageFromFiles(files, dir);
|
|
15915
|
+
return loadPackageFromFiles(tree.files, dir, tree.binaries);
|
|
15848
15916
|
}
|
|
15849
15917
|
|
|
15850
|
-
// ../validator/src/rules/structure.ts
|
|
15851
|
-
var structureRule = (pkg) => {
|
|
15852
|
-
if (pkg.files.length === 0) {
|
|
15853
|
-
return [
|
|
15854
|
-
{
|
|
15855
|
-
severity: "failure",
|
|
15856
|
-
code: "empty-package",
|
|
15857
|
-
message: "the package contains no files"
|
|
15858
|
-
}
|
|
15859
|
-
];
|
|
15860
|
-
}
|
|
15861
|
-
const findings = [];
|
|
15862
|
-
if (pkg.manifest.state === "missing") {
|
|
15863
|
-
findings.push({
|
|
15864
|
-
severity: "warning",
|
|
15865
|
-
code: "missing-manifest",
|
|
15866
|
-
message: "no skill.json manifest; permissions and targets are undeclared"
|
|
15867
|
-
});
|
|
15868
|
-
} else if (pkg.manifest.state === "invalid") {
|
|
15869
|
-
findings.push({
|
|
15870
|
-
severity: "failure",
|
|
15871
|
-
code: "invalid-manifest",
|
|
15872
|
-
message: `skill.json is invalid: ${pkg.manifest.error}`,
|
|
15873
|
-
location: { path: "skill.json" }
|
|
15874
|
-
});
|
|
15875
|
-
}
|
|
15876
|
-
for (const entry of pkg.entries) {
|
|
15877
|
-
if (!entry.exists) {
|
|
15878
|
-
findings.push({
|
|
15879
|
-
severity: "failure",
|
|
15880
|
-
code: "missing-skill-file",
|
|
15881
|
-
message: `skill "${entry.skillName}" references "${entry.path}", which does not exist`,
|
|
15882
|
-
location: { path: entry.path }
|
|
15883
|
-
});
|
|
15884
|
-
} else {
|
|
15885
|
-
const file2 = pkg.files.find((f) => f.path === entry.path);
|
|
15886
|
-
if (file2 !== void 0 && file2.content.trim() === "") {
|
|
15887
|
-
findings.push({
|
|
15888
|
-
severity: "failure",
|
|
15889
|
-
code: "missing-skill-file",
|
|
15890
|
-
message: `skill "${entry.skillName}" entry "${entry.path}" is empty`,
|
|
15891
|
-
location: { path: entry.path }
|
|
15892
|
-
});
|
|
15893
|
-
}
|
|
15894
|
-
}
|
|
15895
|
-
}
|
|
15896
|
-
return findings;
|
|
15897
|
-
};
|
|
15898
|
-
|
|
15899
15918
|
// ../validator/src/rules/content.ts
|
|
15900
15919
|
var SECRET_PATTERNS = [
|
|
15901
15920
|
{
|
|
@@ -16037,28 +16056,113 @@ var ALL_ROWS = [
|
|
|
16037
16056
|
...advisory(MALWARE_PATTERNS),
|
|
16038
16057
|
...advisory(CREDENTIAL_PATTERNS)
|
|
16039
16058
|
];
|
|
16059
|
+
var DOCUMENTED_PLACEHOLDERS = ["AKIAIOSFODNN7EXAMPLE", "AKIAI44QH8DHBEXAMPLE"];
|
|
16060
|
+
var withoutPlaceholders = (line) => DOCUMENTED_PLACEHOLDERS.reduce((l, p) => l.replaceAll(p, " "), line);
|
|
16061
|
+
var REDACTIONS = ALL_ROWS.filter((row2) => row2.redact).map(
|
|
16062
|
+
(row2) => new RegExp(row2.pattern.source, `${row2.pattern.flags}g`)
|
|
16063
|
+
);
|
|
16064
|
+
var redactLine = (line) => REDACTIONS.reduce((l, re) => l.replace(re, "[redacted]"), line);
|
|
16040
16065
|
var contentRule = (pkg) => {
|
|
16041
16066
|
const findings = [];
|
|
16042
16067
|
for (const file2 of pkg.files) {
|
|
16043
|
-
file2.content.split("\n").forEach((
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16068
|
+
file2.content.split("\n").forEach((rawLine, i2) => {
|
|
16069
|
+
const line = withoutPlaceholders(rawLine);
|
|
16070
|
+
const hits = ALL_ROWS.filter((row2) => row2.pattern.test(line));
|
|
16071
|
+
if (hits.length === 0) return;
|
|
16072
|
+
const snippet = redactLine(line).trim();
|
|
16073
|
+
for (const row2 of hits) {
|
|
16074
|
+
findings.push({
|
|
16075
|
+
severity: row2.severity ?? "failure",
|
|
16076
|
+
code: row2.code,
|
|
16077
|
+
message: row2.message,
|
|
16078
|
+
location: { path: file2.path, line: i2 + 1, snippet }
|
|
16079
|
+
});
|
|
16080
|
+
}
|
|
16081
|
+
});
|
|
16082
|
+
}
|
|
16083
|
+
return findings;
|
|
16084
|
+
};
|
|
16085
|
+
|
|
16086
|
+
// ../validator/src/rules/structure.ts
|
|
16087
|
+
var structureRule = (pkg) => {
|
|
16088
|
+
if (pkg.files.length === 0) {
|
|
16089
|
+
return [
|
|
16090
|
+
{
|
|
16091
|
+
severity: "failure",
|
|
16092
|
+
code: "empty-package",
|
|
16093
|
+
message: "the package contains no files"
|
|
16054
16094
|
}
|
|
16095
|
+
];
|
|
16096
|
+
}
|
|
16097
|
+
const findings = [];
|
|
16098
|
+
if (pkg.manifest.state === "missing") {
|
|
16099
|
+
findings.push({
|
|
16100
|
+
severity: "warning",
|
|
16101
|
+
code: "missing-manifest",
|
|
16102
|
+
message: "no skill.json manifest; permissions and targets are undeclared"
|
|
16103
|
+
});
|
|
16104
|
+
} else if (pkg.manifest.state === "invalid") {
|
|
16105
|
+
findings.push({
|
|
16106
|
+
severity: "failure",
|
|
16107
|
+
code: "invalid-manifest",
|
|
16108
|
+
message: `skill.json is invalid: ${pkg.manifest.error}`,
|
|
16109
|
+
location: { path: "skill.json" }
|
|
16110
|
+
});
|
|
16111
|
+
}
|
|
16112
|
+
for (const path of pkg.binaries) {
|
|
16113
|
+
findings.push({
|
|
16114
|
+
severity: "warning",
|
|
16115
|
+
code: "binary-dropped",
|
|
16116
|
+
message: `"${path}" is not a text file and was left out of the snapshot; installs will not include it`,
|
|
16117
|
+
location: { path }
|
|
16055
16118
|
});
|
|
16056
16119
|
}
|
|
16120
|
+
for (const entry of pkg.entries) {
|
|
16121
|
+
if (!entry.exists) {
|
|
16122
|
+
findings.push({
|
|
16123
|
+
severity: "failure",
|
|
16124
|
+
code: "missing-skill-file",
|
|
16125
|
+
message: `skill "${entry.skillName}" references "${entry.path}", which does not exist`,
|
|
16126
|
+
location: { path: entry.path }
|
|
16127
|
+
});
|
|
16128
|
+
} else {
|
|
16129
|
+
const file2 = pkg.files.find((f) => f.path === entry.path);
|
|
16130
|
+
if (file2 !== void 0 && file2.content.trim() === "") {
|
|
16131
|
+
findings.push({
|
|
16132
|
+
severity: "failure",
|
|
16133
|
+
code: "missing-skill-file",
|
|
16134
|
+
message: `skill "${entry.skillName}" entry "${entry.path}" is empty`,
|
|
16135
|
+
location: { path: entry.path }
|
|
16136
|
+
});
|
|
16137
|
+
}
|
|
16138
|
+
}
|
|
16139
|
+
}
|
|
16057
16140
|
return findings;
|
|
16058
16141
|
};
|
|
16059
16142
|
|
|
16143
|
+
// ../validator/package.json
|
|
16144
|
+
var package_default = {
|
|
16145
|
+
name: "validator",
|
|
16146
|
+
version: "0.3.0",
|
|
16147
|
+
private: true,
|
|
16148
|
+
type: "module",
|
|
16149
|
+
exports: {
|
|
16150
|
+
".": "./src/index.ts"
|
|
16151
|
+
},
|
|
16152
|
+
scripts: {
|
|
16153
|
+
typecheck: "tsc --noEmit"
|
|
16154
|
+
},
|
|
16155
|
+
dependencies: {
|
|
16156
|
+
"skill-schema": "workspace:*"
|
|
16157
|
+
},
|
|
16158
|
+
devDependencies: {
|
|
16159
|
+
"@types/node": "^22.0.0",
|
|
16160
|
+
typescript: "^6.0.3"
|
|
16161
|
+
}
|
|
16162
|
+
};
|
|
16163
|
+
|
|
16060
16164
|
// ../validator/src/validate.ts
|
|
16061
|
-
var ENGINE_VERSION =
|
|
16165
|
+
var ENGINE_VERSION = package_default.version;
|
|
16062
16166
|
var RULES = [
|
|
16063
16167
|
{ key: "structure", label: "Check package structure", run: structureRule },
|
|
16064
16168
|
{ key: "content", label: "Scan content for risky patterns", run: contentRule }
|
|
@@ -16070,7 +16174,7 @@ function toReportFindings(findings) {
|
|
|
16070
16174
|
}
|
|
16071
16175
|
function buildReport(pkg, findings, opts = {}) {
|
|
16072
16176
|
const declared = pkg.manifest.state === "ok" ? pkg.manifest.data.permissions : [];
|
|
16073
|
-
const detected = detectPermissions(pkg.files).
|
|
16177
|
+
const detected = [...detectPermissions(pkg.files)].sort();
|
|
16074
16178
|
const status = findings.some((f) => f.severity === "failure") ? "failed" : findings.some((f) => f.severity === "warning") ? "warning" : "passed";
|
|
16075
16179
|
const report = {
|
|
16076
16180
|
schemaVersion: "0.1",
|
|
@@ -16103,7 +16207,8 @@ async function validatePackage(dir, opts = {}) {
|
|
|
16103
16207
|
// src/api.ts
|
|
16104
16208
|
var DEFAULT_API_URL = "https://api.skillpass.dev";
|
|
16105
16209
|
function resolveApiUrl(override) {
|
|
16106
|
-
|
|
16210
|
+
const url2 = override ?? process.env.SKILLPASS_API;
|
|
16211
|
+
return (url2 || DEFAULT_API_URL).replace(/\/$/, "");
|
|
16107
16212
|
}
|
|
16108
16213
|
function parseSkillRef(ref) {
|
|
16109
16214
|
const at = ref.indexOf("@");
|
|
@@ -16159,6 +16264,52 @@ async function fetchPreflight(fetchImpl, apiUrl, ref) {
|
|
|
16159
16264
|
return { ok: true, slug, detail: detail.data, preflight: preflight.data };
|
|
16160
16265
|
}
|
|
16161
16266
|
|
|
16267
|
+
// src/install.ts
|
|
16268
|
+
import { existsSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
16269
|
+
var GLOBAL_NEEDS_TARGET = "error: --global needs --target (e.g. --target claude-code)";
|
|
16270
|
+
var TARGET_OR_DIR = "error: pass --target or --dir, not both";
|
|
16271
|
+
function createOutput(emit) {
|
|
16272
|
+
const lines = [];
|
|
16273
|
+
const streamed = emit !== void 0;
|
|
16274
|
+
const push = (...next) => {
|
|
16275
|
+
lines.push(...next);
|
|
16276
|
+
if (next.length > 0) emit?.(next.join("\n"));
|
|
16277
|
+
};
|
|
16278
|
+
const done = (exitCode) => ({ lines, exitCode, streamed });
|
|
16279
|
+
return { push, done };
|
|
16280
|
+
}
|
|
16281
|
+
function isOccupied(path) {
|
|
16282
|
+
return existsSync(path) && (!statSync2(path).isDirectory() || readdirSync2(path).length > 0);
|
|
16283
|
+
}
|
|
16284
|
+
function receiptFor(preflight, packSlug) {
|
|
16285
|
+
return {
|
|
16286
|
+
version: preflight.version,
|
|
16287
|
+
sourceHash: preflight.sourceHash,
|
|
16288
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16289
|
+
...packSlug ? { pack: { slug: packSlug, version: preflight.version } } : {}
|
|
16290
|
+
};
|
|
16291
|
+
}
|
|
16292
|
+
function memberFiles(files, sourceDir) {
|
|
16293
|
+
if (sourceDir === ".") {
|
|
16294
|
+
return files;
|
|
16295
|
+
}
|
|
16296
|
+
const prefix = `${sourceDir}/`;
|
|
16297
|
+
return files.filter((f) => f.path.startsWith(prefix)).map((f) => ({ path: f.path.slice(prefix.length), content: f.content }));
|
|
16298
|
+
}
|
|
16299
|
+
function planMembers(installs, files) {
|
|
16300
|
+
return installs.map((m) => ({ ...m, files: memberFiles(files, m.sourceDir) }));
|
|
16301
|
+
}
|
|
16302
|
+
async function confirmRisk(preflight, opts, question, abortLine, push) {
|
|
16303
|
+
if (preflight.riskLevel === "low" || opts.yes) return true;
|
|
16304
|
+
if (!opts.confirmImpl) {
|
|
16305
|
+
push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
|
|
16306
|
+
return false;
|
|
16307
|
+
}
|
|
16308
|
+
if (await opts.confirmImpl(question)) return true;
|
|
16309
|
+
push("", abortLine);
|
|
16310
|
+
return false;
|
|
16311
|
+
}
|
|
16312
|
+
|
|
16162
16313
|
// src/pack.ts
|
|
16163
16314
|
import { posix } from "node:path";
|
|
16164
16315
|
function resolvePackMembers(members, target) {
|
|
@@ -16177,6 +16328,53 @@ function resolvePackMembers(members, target) {
|
|
|
16177
16328
|
return { installs, skipped };
|
|
16178
16329
|
}
|
|
16179
16330
|
|
|
16331
|
+
// src/receipts.ts
|
|
16332
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
16333
|
+
import { join as join2 } from "node:path";
|
|
16334
|
+
var RECEIPT_FILE = ".skillpass.json";
|
|
16335
|
+
function looksLikeReceipt(value) {
|
|
16336
|
+
return typeof value === "object" && value !== null && typeof value.version === "string" && typeof value.sourceHash === "string";
|
|
16337
|
+
}
|
|
16338
|
+
function readReceipts(areaDir) {
|
|
16339
|
+
let parsed;
|
|
16340
|
+
try {
|
|
16341
|
+
parsed = JSON.parse(readFileSync2(join2(areaDir, RECEIPT_FILE), "utf8"));
|
|
16342
|
+
} catch {
|
|
16343
|
+
return {};
|
|
16344
|
+
}
|
|
16345
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
16346
|
+
return {};
|
|
16347
|
+
}
|
|
16348
|
+
const index = {};
|
|
16349
|
+
for (const [slug, receipt] of Object.entries(parsed)) {
|
|
16350
|
+
if (looksLikeReceipt(receipt)) {
|
|
16351
|
+
index[slug] = receipt;
|
|
16352
|
+
}
|
|
16353
|
+
}
|
|
16354
|
+
return index;
|
|
16355
|
+
}
|
|
16356
|
+
function writeReceipts(areaDir, index) {
|
|
16357
|
+
try {
|
|
16358
|
+
mkdirSync(areaDir, { recursive: true });
|
|
16359
|
+
writeFileSync(join2(areaDir, RECEIPT_FILE), `${JSON.stringify(index, null, " ")}
|
|
16360
|
+
`);
|
|
16361
|
+
} catch {
|
|
16362
|
+
}
|
|
16363
|
+
}
|
|
16364
|
+
function recordReceipt(areaDir, slug, receipt) {
|
|
16365
|
+
const index = readReceipts(areaDir);
|
|
16366
|
+
index[slug] = receipt;
|
|
16367
|
+
writeReceipts(areaDir, index);
|
|
16368
|
+
}
|
|
16369
|
+
function removeReceipt(areaDir, slug) {
|
|
16370
|
+
const index = readReceipts(areaDir);
|
|
16371
|
+
if (!(slug in index)) {
|
|
16372
|
+
return;
|
|
16373
|
+
}
|
|
16374
|
+
delete index[slug];
|
|
16375
|
+
writeReceipts(areaDir, index);
|
|
16376
|
+
}
|
|
16377
|
+
|
|
16180
16378
|
// src/style.ts
|
|
16181
16379
|
var wrap = (open, close) => (text) => `\x1B[${open}m${text}\x1B[${close}m`;
|
|
16182
16380
|
var ANSI = {
|
|
@@ -16285,11 +16483,11 @@ function renderDiff(diff) {
|
|
|
16285
16483
|
|
|
16286
16484
|
// src/targets.ts
|
|
16287
16485
|
import { homedir } from "node:os";
|
|
16288
|
-
import { join as
|
|
16486
|
+
import { join as join3, resolve } from "node:path";
|
|
16289
16487
|
function installAreas(home = homedir()) {
|
|
16290
16488
|
return {
|
|
16291
|
-
"claude-code": { project:
|
|
16292
|
-
codex: { project:
|
|
16489
|
+
"claude-code": { project: join3(".claude", "skills"), global: join3(home, ".claude", "skills") },
|
|
16490
|
+
codex: { project: join3(".agents", "skills") }
|
|
16293
16491
|
};
|
|
16294
16492
|
}
|
|
16295
16493
|
var MAPPED_TARGETS = Object.keys(installAreas());
|
|
@@ -16320,11 +16518,31 @@ function resolveTargetArea(target, global = false, home) {
|
|
|
16320
16518
|
}
|
|
16321
16519
|
function resolveTargetDir(target, slug, global = false, home) {
|
|
16322
16520
|
const area = resolveTargetArea(target, global, home);
|
|
16323
|
-
return area.ok ? { ok: true, dir:
|
|
16521
|
+
return area.ok ? { ok: true, dir: join3(area.dir, slug) } : area;
|
|
16324
16522
|
}
|
|
16325
16523
|
function mappableDeclaredTargets(declared) {
|
|
16326
16524
|
return declared.filter((t) => MAPPED_TARGETS.includes(t));
|
|
16327
16525
|
}
|
|
16526
|
+
function knownAreas(cwd, home) {
|
|
16527
|
+
const areas = [];
|
|
16528
|
+
const byTarget = installAreas(home);
|
|
16529
|
+
for (const tool of MAPPED_TARGETS) {
|
|
16530
|
+
const area = byTarget[tool];
|
|
16531
|
+
if (!area) {
|
|
16532
|
+
continue;
|
|
16533
|
+
}
|
|
16534
|
+
areas.push({
|
|
16535
|
+
tool,
|
|
16536
|
+
global: false,
|
|
16537
|
+
label: `${tool} project (${area.project})`,
|
|
16538
|
+
dir: resolve(cwd, area.project)
|
|
16539
|
+
});
|
|
16540
|
+
if (area.global) {
|
|
16541
|
+
areas.push({ tool, global: true, label: `${tool} user (${area.global})`, dir: area.global });
|
|
16542
|
+
}
|
|
16543
|
+
}
|
|
16544
|
+
return areas;
|
|
16545
|
+
}
|
|
16328
16546
|
|
|
16329
16547
|
// src/add.ts
|
|
16330
16548
|
function unsafeEntryPath(path) {
|
|
@@ -16391,12 +16609,12 @@ function writeTree(files, target) {
|
|
|
16391
16609
|
const tempDir = `${target}.tmp-${process.pid}`;
|
|
16392
16610
|
try {
|
|
16393
16611
|
for (const file2 of files) {
|
|
16394
|
-
const filePath =
|
|
16395
|
-
|
|
16396
|
-
|
|
16612
|
+
const filePath = join4(tempDir, file2.path);
|
|
16613
|
+
mkdirSync2(dirname(filePath), { recursive: true });
|
|
16614
|
+
writeFileSync2(filePath, file2.content);
|
|
16397
16615
|
}
|
|
16398
|
-
|
|
16399
|
-
if (
|
|
16616
|
+
mkdirSync2(dirname(target), { recursive: true });
|
|
16617
|
+
if (existsSync2(target)) {
|
|
16400
16618
|
rmdirSync(target);
|
|
16401
16619
|
}
|
|
16402
16620
|
renameSync(tempDir, target);
|
|
@@ -16405,13 +16623,6 @@ function writeTree(files, target) {
|
|
|
16405
16623
|
throw err2;
|
|
16406
16624
|
}
|
|
16407
16625
|
}
|
|
16408
|
-
function memberFiles(files, sourceDir) {
|
|
16409
|
-
if (sourceDir === ".") {
|
|
16410
|
-
return files;
|
|
16411
|
-
}
|
|
16412
|
-
const prefix = `${sourceDir}/`;
|
|
16413
|
-
return files.filter((f) => f.path.startsWith(prefix)).map((f) => ({ path: f.path.slice(prefix.length), content: f.content }));
|
|
16414
|
-
}
|
|
16415
16626
|
function installChoices(declared, slug) {
|
|
16416
16627
|
const choices = [];
|
|
16417
16628
|
const ordered = [
|
|
@@ -16463,209 +16674,117 @@ function packChoices(declared, slug, count) {
|
|
|
16463
16674
|
choices.push({ label: `current directory (./${slug}, raw pack source)` });
|
|
16464
16675
|
return choices;
|
|
16465
16676
|
}
|
|
16466
|
-
async function
|
|
16467
|
-
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
if (
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
|
|
16476
|
-
if (
|
|
16477
|
-
|
|
16677
|
+
async function promptIndex(ask, count, push) {
|
|
16678
|
+
while (true) {
|
|
16679
|
+
const answer = (await ask(`Where should it go? [1-${count}, default 1] `)).trim();
|
|
16680
|
+
if (answer === "") return 0;
|
|
16681
|
+
const index = /^\d+$/.test(answer) ? Number.parseInt(answer, 10) - 1 : -1;
|
|
16682
|
+
if (index >= 0 && index < count) return index;
|
|
16683
|
+
push(` answer 1-${count}, or press enter for the default`);
|
|
16684
|
+
}
|
|
16685
|
+
}
|
|
16686
|
+
async function choosePackLocation(ctx, detail, memberCount, prompt) {
|
|
16687
|
+
if (prompt) {
|
|
16688
|
+
const choices = packChoices(detail.targets, ctx.slug, memberCount);
|
|
16689
|
+
ctx.push("", "Install location:");
|
|
16690
|
+
choices.forEach((choice, i2) => ctx.push(` ${i2 + 1}) ${choice.label}`));
|
|
16691
|
+
const chosen = choices[await promptIndex(prompt, choices.length, ctx.push)];
|
|
16692
|
+
return chosen.tool ? { tool: chosen.tool, global: chosen.global ?? false } : { dir: ctx.slug };
|
|
16693
|
+
}
|
|
16694
|
+
const mappable = mappableDeclaredTargets(detail.targets);
|
|
16695
|
+
if (mappable.length > 0) {
|
|
16696
|
+
ctx.push(
|
|
16697
|
+
"",
|
|
16698
|
+
`tip: --target ${mappable[0]} installs the ${memberCount} skills into the tool's skills folder`
|
|
16699
|
+
);
|
|
16700
|
+
}
|
|
16701
|
+
return { dir: ctx.slug };
|
|
16702
|
+
}
|
|
16703
|
+
async function chooseSingleLocation(ctx, detail, prompt) {
|
|
16704
|
+
const choices = installChoices(detail.targets, ctx.slug);
|
|
16705
|
+
if (prompt && choices.length > 1) {
|
|
16706
|
+
ctx.push("", "Install location:");
|
|
16707
|
+
choices.forEach((choice, i2) => ctx.push(` ${i2 + 1}) ${choice.label}`));
|
|
16708
|
+
return choices[await promptIndex(prompt, choices.length, ctx.push)].dir;
|
|
16709
|
+
}
|
|
16710
|
+
const mappable = mappableDeclaredTargets(detail.targets);
|
|
16711
|
+
if (mappable.length > 0) {
|
|
16712
|
+
ctx.push("", `tip: --target ${mappable[0]} installs into the tool's skills folder`);
|
|
16713
|
+
}
|
|
16714
|
+
return ctx.slug;
|
|
16715
|
+
}
|
|
16716
|
+
async function installPack(ctx, members, tool, global) {
|
|
16717
|
+
const { push, done, slug, preflight } = ctx;
|
|
16718
|
+
const area = resolveTargetArea(tool, global);
|
|
16719
|
+
if (!area.ok) {
|
|
16720
|
+
push("", `error: ${area.message}`);
|
|
16478
16721
|
return done(2);
|
|
16479
16722
|
}
|
|
16480
|
-
|
|
16481
|
-
|
|
16723
|
+
const { installs, skipped } = resolvePackMembers(members, tool);
|
|
16724
|
+
if (installs.length === 0) {
|
|
16725
|
+
push("", `error: none of this pack's skills support ${tool}`);
|
|
16482
16726
|
return done(2);
|
|
16483
16727
|
}
|
|
16484
|
-
const
|
|
16485
|
-
const
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
|
|
16489
|
-
|
|
16728
|
+
const areaAbs = resolve2(ctx.cwd, area.dir);
|
|
16729
|
+
const conflicts = installs.map((m) => join4(areaAbs, m.name)).filter(isOccupied);
|
|
16730
|
+
if (conflicts.length > 0) {
|
|
16731
|
+
push(
|
|
16732
|
+
"",
|
|
16733
|
+
"error: these destinations already exist and are not empty; nothing was installed:",
|
|
16734
|
+
...conflicts.map((c) => ` ${c}`)
|
|
16735
|
+
);
|
|
16736
|
+
return done(2);
|
|
16490
16737
|
}
|
|
16491
|
-
const
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
return done(
|
|
16738
|
+
const download = await downloadVerified(ctx.fetchImpl, ctx.apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
16739
|
+
if (!download.ok) {
|
|
16740
|
+
push("", `error: ${download.message}`);
|
|
16741
|
+
return done(2);
|
|
16495
16742
|
}
|
|
16496
|
-
const
|
|
16497
|
-
const
|
|
16498
|
-
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
if (opts.target) {
|
|
16502
|
-
const resolved = isPack ? resolveTargetArea(opts.target, opts.global) : resolveTargetDir(opts.target, slug, opts.global);
|
|
16503
|
-
if (!resolved.ok) {
|
|
16504
|
-
push("", `error: ${resolved.message}`);
|
|
16505
|
-
return done(2);
|
|
16506
|
-
}
|
|
16507
|
-
if (isPack) {
|
|
16508
|
-
packTool = opts.target;
|
|
16509
|
-
packGlobal = opts.global ?? false;
|
|
16510
|
-
} else {
|
|
16511
|
-
targetDir = resolved.dir;
|
|
16512
|
-
}
|
|
16513
|
-
if (!detail.targets.includes(opts.target)) {
|
|
16514
|
-
push("", `warning: this skill does not declare ${opts.target} as a target`);
|
|
16515
|
-
}
|
|
16516
|
-
}
|
|
16517
|
-
if (preflight.riskLevel !== "low" && !opts.yes) {
|
|
16518
|
-
if (!opts.confirmImpl) {
|
|
16519
|
-
push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
|
|
16520
|
-
return done(2);
|
|
16521
|
-
}
|
|
16522
|
-
const confirmed = await opts.confirmImpl(
|
|
16523
|
-
`Install ${slug}@${preflight.version} (${preflight.riskLevel} risk)? [y/N] `
|
|
16524
|
-
);
|
|
16525
|
-
if (!confirmed) {
|
|
16526
|
-
push("", "Install aborted.");
|
|
16527
|
-
return done(2);
|
|
16528
|
-
}
|
|
16743
|
+
const plans = planMembers(installs, download.files);
|
|
16744
|
+
const missing = plans.find((p) => p.files.length === 0);
|
|
16745
|
+
if (missing) {
|
|
16746
|
+
push("", `error: the snapshot has no files for "${missing.name}"; nothing was installed`);
|
|
16747
|
+
return done(2);
|
|
16529
16748
|
}
|
|
16530
|
-
const
|
|
16531
|
-
|
|
16532
|
-
while (true) {
|
|
16533
|
-
const answer = (await ask(`Where should it go? [1-${count}, default 1] `)).trim();
|
|
16534
|
-
if (answer === "") {
|
|
16535
|
-
return 0;
|
|
16536
|
-
}
|
|
16537
|
-
const index = /^\d+$/.test(answer) ? Number.parseInt(answer, 10) - 1 : -1;
|
|
16538
|
-
if (index >= 0 && index < count) {
|
|
16539
|
-
return index;
|
|
16540
|
-
}
|
|
16541
|
-
push(` answer 1-${count}, or press enter for the default`);
|
|
16542
|
-
}
|
|
16543
|
-
};
|
|
16544
|
-
if (isPack && packTool === void 0 && targetDir === void 0) {
|
|
16545
|
-
if (prompt) {
|
|
16546
|
-
const choices = packChoices(detail.targets, slug, members.length);
|
|
16547
|
-
push("", "Install location:");
|
|
16548
|
-
choices.forEach((choice, i2) => push(` ${i2 + 1}) ${choice.label}`));
|
|
16549
|
-
const chosen = choices[await promptIndex(prompt, choices.length)];
|
|
16550
|
-
if (chosen.tool) {
|
|
16551
|
-
packTool = chosen.tool;
|
|
16552
|
-
packGlobal = chosen.global ?? false;
|
|
16553
|
-
} else {
|
|
16554
|
-
targetDir = slug;
|
|
16555
|
-
}
|
|
16556
|
-
} else {
|
|
16557
|
-
targetDir = slug;
|
|
16558
|
-
const mappable = mappableDeclaredTargets(detail.targets);
|
|
16559
|
-
if (mappable.length > 0) {
|
|
16560
|
-
push(
|
|
16561
|
-
"",
|
|
16562
|
-
`tip: --target ${mappable[0]} installs the ${members.length} skills into the tool's skills folder`
|
|
16563
|
-
);
|
|
16564
|
-
}
|
|
16565
|
-
}
|
|
16749
|
+
for (const name of skipped) {
|
|
16750
|
+
push("", `note: ${name} does not support ${tool}; skipped`);
|
|
16566
16751
|
}
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
const { installs, skipped } = resolvePackMembers(members, packTool);
|
|
16574
|
-
if (installs.length === 0) {
|
|
16575
|
-
push("", `error: none of this pack's skills support ${packTool}`);
|
|
16576
|
-
return done(2);
|
|
16577
|
-
}
|
|
16578
|
-
const areaAbs = resolve(opts.cwd ?? process.cwd(), area.dir);
|
|
16579
|
-
const conflicts = installs.map((m) => join3(areaAbs, m.name)).filter(
|
|
16580
|
-
(dest) => existsSync(dest) && (!statSync2(dest).isDirectory() || readdirSync2(dest).length > 0)
|
|
16581
|
-
);
|
|
16582
|
-
if (conflicts.length > 0) {
|
|
16583
|
-
push(
|
|
16584
|
-
"",
|
|
16585
|
-
"error: these destinations already exist and are not empty; nothing was installed:",
|
|
16586
|
-
...conflicts.map((c) => ` ${c}`)
|
|
16587
|
-
);
|
|
16588
|
-
return done(2);
|
|
16589
|
-
}
|
|
16590
|
-
const download2 = await downloadVerified(
|
|
16591
|
-
fetchImpl,
|
|
16592
|
-
apiUrl,
|
|
16593
|
-
slug,
|
|
16594
|
-
preflight.version,
|
|
16595
|
-
preflight.sourceHash
|
|
16596
|
-
);
|
|
16597
|
-
if (!download2.ok) {
|
|
16598
|
-
push("", `error: ${download2.message}`);
|
|
16599
|
-
return done(2);
|
|
16600
|
-
}
|
|
16601
|
-
const plans = installs.map((m) => ({ ...m, files: memberFiles(download2.files, m.sourceDir) }));
|
|
16602
|
-
const missing = plans.find((p) => p.files.length === 0);
|
|
16603
|
-
if (missing) {
|
|
16604
|
-
push("", `error: the snapshot has no files for "${missing.name}"; nothing was installed`);
|
|
16605
|
-
return done(2);
|
|
16606
|
-
}
|
|
16607
|
-
for (const name of skipped) {
|
|
16608
|
-
push("", `note: ${name} does not support ${packTool}; skipped`);
|
|
16609
|
-
}
|
|
16610
|
-
const written = [];
|
|
16611
|
-
try {
|
|
16612
|
-
for (const plan of plans) {
|
|
16613
|
-
writeTree(plan.files, join3(areaAbs, plan.name));
|
|
16614
|
-
written.push(plan.name);
|
|
16615
|
-
}
|
|
16616
|
-
} catch {
|
|
16617
|
-
push(
|
|
16618
|
-
"",
|
|
16619
|
-
`error: could not write ${plans[written.length].name}; installed before the failure: ${written.join(", ") || "none"}`
|
|
16620
|
-
);
|
|
16621
|
-
return done(2);
|
|
16752
|
+
const written = [];
|
|
16753
|
+
try {
|
|
16754
|
+
for (const plan of plans) {
|
|
16755
|
+
writeTree(plan.files, join4(areaAbs, plan.name));
|
|
16756
|
+
written.push(plan.name);
|
|
16757
|
+
recordReceipt(areaAbs, plan.name, receiptFor(preflight, slug));
|
|
16622
16758
|
}
|
|
16759
|
+
} catch {
|
|
16623
16760
|
push(
|
|
16624
16761
|
"",
|
|
16625
|
-
`
|
|
16626
|
-
` ${written.join(", ")}`,
|
|
16627
|
-
"Source hash verified against the Skill Passport."
|
|
16628
|
-
);
|
|
16629
|
-
return done(0);
|
|
16630
|
-
}
|
|
16631
|
-
if (isPack && opts.dir) {
|
|
16632
|
-
push(
|
|
16633
|
-
"",
|
|
16634
|
-
`note: --dir installs the raw pack source; --target <tool> installs the ${members.length} skills individually`
|
|
16762
|
+
`error: could not write ${plans[written.length].name}; installed before the failure: ${written.join(", ") || "none"}`
|
|
16635
16763
|
);
|
|
16764
|
+
return done(2);
|
|
16636
16765
|
}
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
}
|
|
16651
|
-
const target = resolve(opts.cwd ?? process.cwd(), targetDir);
|
|
16652
|
-
if (existsSync(target)) {
|
|
16653
|
-
if (!statSync2(target).isDirectory()) {
|
|
16766
|
+
push(
|
|
16767
|
+
"",
|
|
16768
|
+
`Installed ${written.length} skills to ${areaAbs}`,
|
|
16769
|
+
` ${written.join(", ")}`,
|
|
16770
|
+
"Source hash verified against the Skill Passport."
|
|
16771
|
+
);
|
|
16772
|
+
return done(0);
|
|
16773
|
+
}
|
|
16774
|
+
async function installSingle(ctx, targetDir) {
|
|
16775
|
+
const { push, done, slug, preflight } = ctx;
|
|
16776
|
+
const target = resolve2(ctx.cwd, targetDir);
|
|
16777
|
+
if (existsSync2(target)) {
|
|
16778
|
+
if (!statSync3(target).isDirectory()) {
|
|
16654
16779
|
push("", `error: target ${target} already exists and is not a directory`);
|
|
16655
16780
|
return done(2);
|
|
16656
16781
|
}
|
|
16657
|
-
if (
|
|
16782
|
+
if (readdirSync3(target).length > 0) {
|
|
16658
16783
|
push("", `error: target directory ${target} already exists and is not empty`);
|
|
16659
16784
|
return done(2);
|
|
16660
16785
|
}
|
|
16661
16786
|
}
|
|
16662
|
-
const download = await downloadVerified(
|
|
16663
|
-
fetchImpl,
|
|
16664
|
-
apiUrl,
|
|
16665
|
-
slug,
|
|
16666
|
-
preflight.version,
|
|
16667
|
-
preflight.sourceHash
|
|
16668
|
-
);
|
|
16787
|
+
const download = await downloadVerified(ctx.fetchImpl, ctx.apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
16669
16788
|
if (!download.ok) {
|
|
16670
16789
|
push("", `error: ${download.message}`);
|
|
16671
16790
|
return done(2);
|
|
@@ -16676,6 +16795,10 @@ async function runAdd(ref, opts = {}) {
|
|
|
16676
16795
|
push("", "error: could not write the install; nothing was installed");
|
|
16677
16796
|
return done(2);
|
|
16678
16797
|
}
|
|
16798
|
+
const area = knownAreas(ctx.cwd).find((a) => a.dir === dirname(target));
|
|
16799
|
+
if (area) {
|
|
16800
|
+
recordReceipt(area.dir, slug, receiptFor(preflight));
|
|
16801
|
+
}
|
|
16679
16802
|
push(
|
|
16680
16803
|
"",
|
|
16681
16804
|
`Installed ${download.files.length} file(s) to ${target}`,
|
|
@@ -16683,30 +16806,89 @@ async function runAdd(ref, opts = {}) {
|
|
|
16683
16806
|
);
|
|
16684
16807
|
return done(0);
|
|
16685
16808
|
}
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
16809
|
+
async function runAdd(ref, opts = {}) {
|
|
16810
|
+
const { push, done } = createOutput(opts.emit);
|
|
16811
|
+
if (opts.target && opts.dir) {
|
|
16812
|
+
push(TARGET_OR_DIR);
|
|
16813
|
+
return done(2);
|
|
16814
|
+
}
|
|
16815
|
+
if (opts.global && !opts.target) {
|
|
16816
|
+
push(GLOBAL_NEEDS_TARGET);
|
|
16817
|
+
return done(2);
|
|
16818
|
+
}
|
|
16819
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
16820
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
16821
|
+
const fetched = await fetchPreflight(fetchImpl, apiUrl, ref);
|
|
16822
|
+
if (!fetched.ok) {
|
|
16823
|
+
push(...fetched.result.lines);
|
|
16824
|
+
return done(fetched.result.exitCode);
|
|
16825
|
+
}
|
|
16826
|
+
const { slug, detail, preflight } = fetched;
|
|
16827
|
+
push(...renderPreflightReport(detail, preflight, opts.style));
|
|
16828
|
+
if (preflight.blocked) {
|
|
16829
|
+
return done(1);
|
|
16830
|
+
}
|
|
16831
|
+
const members = detail.packMembers ?? [];
|
|
16832
|
+
const isPack = members.length > 0;
|
|
16833
|
+
let targetDir = opts.dir;
|
|
16834
|
+
let pack;
|
|
16835
|
+
if (opts.target) {
|
|
16836
|
+
const resolved = isPack ? resolveTargetArea(opts.target, opts.global) : resolveTargetDir(opts.target, slug, opts.global);
|
|
16837
|
+
if (!resolved.ok) {
|
|
16838
|
+
push("", `error: ${resolved.message}`);
|
|
16839
|
+
return done(2);
|
|
16697
16840
|
}
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16841
|
+
if (isPack) {
|
|
16842
|
+
pack = { tool: opts.target, global: opts.global ?? false };
|
|
16843
|
+
} else {
|
|
16844
|
+
targetDir = resolved.dir;
|
|
16845
|
+
}
|
|
16846
|
+
if (!detail.targets.includes(opts.target)) {
|
|
16847
|
+
push("", `warning: this skill does not declare ${opts.target} as a target`);
|
|
16701
16848
|
}
|
|
16702
16849
|
}
|
|
16703
|
-
|
|
16850
|
+
const confirmed = await confirmRisk(
|
|
16851
|
+
preflight,
|
|
16852
|
+
opts,
|
|
16853
|
+
`Install ${slug}@${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
|
|
16854
|
+
"Install aborted.",
|
|
16855
|
+
push
|
|
16856
|
+
);
|
|
16857
|
+
if (!confirmed) return done(2);
|
|
16858
|
+
const ctx = {
|
|
16859
|
+
push,
|
|
16860
|
+
done,
|
|
16861
|
+
fetchImpl,
|
|
16862
|
+
apiUrl,
|
|
16863
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
16864
|
+
slug,
|
|
16865
|
+
preflight
|
|
16866
|
+
};
|
|
16867
|
+
if (isPack && pack === void 0 && targetDir === void 0) {
|
|
16868
|
+
const location = await choosePackLocation(ctx, detail, members.length, opts.promptImpl);
|
|
16869
|
+
if ("tool" in location) pack = location;
|
|
16870
|
+
else targetDir = location.dir;
|
|
16871
|
+
}
|
|
16872
|
+
if (pack !== void 0) {
|
|
16873
|
+
return installPack(ctx, members, pack.tool, pack.global);
|
|
16874
|
+
}
|
|
16875
|
+
if (isPack && opts.dir) {
|
|
16876
|
+
push(
|
|
16877
|
+
"",
|
|
16878
|
+
`note: --dir installs the raw pack source; --target <tool> installs the ${members.length} skills individually`
|
|
16879
|
+
);
|
|
16880
|
+
}
|
|
16881
|
+
return installSingle(ctx, targetDir ?? await chooseSingleLocation(ctx, detail, opts.promptImpl));
|
|
16704
16882
|
}
|
|
16883
|
+
|
|
16884
|
+
// src/list.ts
|
|
16885
|
+
import { existsSync as existsSync3, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
|
|
16886
|
+
import { join as join5 } from "node:path";
|
|
16705
16887
|
function installedIn(dir) {
|
|
16706
|
-
if (!
|
|
16888
|
+
if (!existsSync3(dir)) {
|
|
16707
16889
|
return [];
|
|
16708
16890
|
}
|
|
16709
|
-
return
|
|
16891
|
+
return readdirSync4(dir).sort().filter((name) => statSync4(join5(dir, name), { throwIfNoEntry: false })?.isDirectory() ?? false);
|
|
16710
16892
|
}
|
|
16711
16893
|
function runList(opts = {}) {
|
|
16712
16894
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -16720,8 +16902,10 @@ function runList(opts = {}) {
|
|
|
16720
16902
|
lines.push("");
|
|
16721
16903
|
}
|
|
16722
16904
|
lines.push(area.label);
|
|
16905
|
+
const receipts = readReceipts(area.dir);
|
|
16723
16906
|
for (const name of names) {
|
|
16724
|
-
|
|
16907
|
+
const version2 = receipts[name]?.version;
|
|
16908
|
+
lines.push(version2 ? ` ${name} ${version2}` : ` ${name}`);
|
|
16725
16909
|
}
|
|
16726
16910
|
}
|
|
16727
16911
|
if (lines.length === 0) {
|
|
@@ -16730,36 +16914,182 @@ function runList(opts = {}) {
|
|
|
16730
16914
|
return { lines, exitCode: 0 };
|
|
16731
16915
|
}
|
|
16732
16916
|
|
|
16733
|
-
// src/
|
|
16734
|
-
import {
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16917
|
+
// src/outdated.ts
|
|
16918
|
+
import { join as join6 } from "node:path";
|
|
16919
|
+
async function identifyByHash(fetchImpl, apiUrl, slug, dir) {
|
|
16920
|
+
let hash2;
|
|
16921
|
+
try {
|
|
16922
|
+
hash2 = loadPackage(dir).sourceHash;
|
|
16923
|
+
} catch {
|
|
16924
|
+
return void 0;
|
|
16925
|
+
}
|
|
16926
|
+
const detail = await getParsed(
|
|
16927
|
+
fetchImpl,
|
|
16928
|
+
`${apiUrl}/skills/${encodeURIComponent(slug)}`,
|
|
16929
|
+
publicSkillDetailSchema
|
|
16930
|
+
);
|
|
16931
|
+
if (!detail.ok) {
|
|
16932
|
+
return void 0;
|
|
16933
|
+
}
|
|
16934
|
+
for (const version2 of detail.data.versions) {
|
|
16935
|
+
const preflight = await getParsed(
|
|
16936
|
+
fetchImpl,
|
|
16937
|
+
`${apiUrl}/skills/${encodeURIComponent(slug)}/${encodeURIComponent(version2.version)}/preflight`,
|
|
16938
|
+
publicPreflightSchema
|
|
16939
|
+
);
|
|
16940
|
+
if (preflight.ok && preflight.data.sourceHash === hash2) {
|
|
16941
|
+
return version2.version;
|
|
16942
|
+
}
|
|
16943
|
+
}
|
|
16944
|
+
return void 0;
|
|
16738
16945
|
}
|
|
16739
|
-
function
|
|
16740
|
-
const
|
|
16741
|
-
const
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
|
|
16946
|
+
async function runOutdated(opts = {}) {
|
|
16947
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
16948
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
16949
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
16950
|
+
const fetched = await getParsed(fetchImpl, `${apiUrl}/skills`, publicSkillListSchema);
|
|
16951
|
+
if (!fetched.ok) {
|
|
16952
|
+
return { lines: [`error: ${fetched.message}`], exitCode: 2 };
|
|
16953
|
+
}
|
|
16954
|
+
const latestBySlug = new Map(fetched.data.map((s) => [s.slug, s]));
|
|
16955
|
+
const lines = [];
|
|
16956
|
+
let outdatedCount = 0;
|
|
16957
|
+
let tracked = 0;
|
|
16958
|
+
for (const area of knownAreas(cwd, opts.home)) {
|
|
16959
|
+
const names = installedIn(area.dir);
|
|
16960
|
+
if (names.length === 0) {
|
|
16745
16961
|
continue;
|
|
16746
16962
|
}
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16963
|
+
const receipts = readReceipts(area.dir);
|
|
16964
|
+
const rows = [];
|
|
16965
|
+
const seenPacks = /* @__PURE__ */ new Set();
|
|
16966
|
+
let unrelated = 0;
|
|
16967
|
+
for (const name of names) {
|
|
16968
|
+
const receipt = receipts[name];
|
|
16969
|
+
if (receipt?.pack) {
|
|
16970
|
+
if (seenPacks.has(receipt.pack.slug)) {
|
|
16971
|
+
continue;
|
|
16972
|
+
}
|
|
16973
|
+
seenPacks.add(receipt.pack.slug);
|
|
16974
|
+
tracked += 1;
|
|
16975
|
+
const members = Object.values(receipts).filter(
|
|
16976
|
+
(r) => r.pack?.slug === receipt.pack?.slug
|
|
16977
|
+
).length;
|
|
16978
|
+
const latest = latestBySlug.get(receipt.pack.slug)?.version;
|
|
16979
|
+
if (latest === void 0) {
|
|
16980
|
+
rows.push(` ${receipt.pack.slug} ${receipt.pack.version} not in the directory`);
|
|
16981
|
+
} else if (latest === receipt.pack.version) {
|
|
16982
|
+
rows.push(` ${receipt.pack.slug} ${receipt.pack.version} current (pack, ${members} skills)`);
|
|
16983
|
+
} else {
|
|
16984
|
+
outdatedCount += 1;
|
|
16985
|
+
rows.push(
|
|
16986
|
+
` ${receipt.pack.slug} ${receipt.pack.version} -> ${latest} (pack, ${members} skills)`
|
|
16987
|
+
);
|
|
16988
|
+
}
|
|
16989
|
+
continue;
|
|
16990
|
+
}
|
|
16991
|
+
if (receipt) {
|
|
16992
|
+
tracked += 1;
|
|
16993
|
+
const latest = latestBySlug.get(name)?.version;
|
|
16994
|
+
if (latest === void 0) {
|
|
16995
|
+
rows.push(` ${name} ${receipt.version} not in the directory`);
|
|
16996
|
+
} else if (latest === receipt.version) {
|
|
16997
|
+
rows.push(` ${name} ${receipt.version} current`);
|
|
16998
|
+
} else {
|
|
16999
|
+
outdatedCount += 1;
|
|
17000
|
+
rows.push(` ${name} ${receipt.version} -> ${latest}`);
|
|
17001
|
+
}
|
|
17002
|
+
continue;
|
|
17003
|
+
}
|
|
17004
|
+
if (!latestBySlug.has(name)) {
|
|
17005
|
+
unrelated += 1;
|
|
17006
|
+
continue;
|
|
17007
|
+
}
|
|
17008
|
+
const identified = await identifyByHash(fetchImpl, apiUrl, name, join6(area.dir, name));
|
|
17009
|
+
if (identified !== void 0) {
|
|
17010
|
+
tracked += 1;
|
|
17011
|
+
const latest = latestBySlug.get(name)?.version;
|
|
17012
|
+
if (latest === identified) {
|
|
17013
|
+
rows.push(` ${name} ${identified} current (identified by hash)`);
|
|
17014
|
+
} else {
|
|
17015
|
+
outdatedCount += 1;
|
|
17016
|
+
rows.push(` ${name} ${identified} -> ${latest} (identified by hash)`);
|
|
17017
|
+
}
|
|
17018
|
+
continue;
|
|
17019
|
+
}
|
|
17020
|
+
rows.push(` ${name} ? no receipt - reinstall to track updates`);
|
|
16750
17021
|
}
|
|
17022
|
+
if (rows.length > 0) {
|
|
17023
|
+
if (lines.length > 0) {
|
|
17024
|
+
lines.push("");
|
|
17025
|
+
}
|
|
17026
|
+
lines.push(area.label, ...rows);
|
|
17027
|
+
if (unrelated > 0) {
|
|
17028
|
+
lines.push(` (${unrelated} folder(s) not from the directory)`);
|
|
17029
|
+
}
|
|
17030
|
+
}
|
|
17031
|
+
}
|
|
17032
|
+
if (tracked === 0 && lines.length === 0) {
|
|
17033
|
+
return { lines: ["No skills installed in the known install areas."], exitCode: 0 };
|
|
16751
17034
|
}
|
|
16752
|
-
|
|
16753
|
-
|
|
17035
|
+
lines.push(
|
|
17036
|
+
"",
|
|
17037
|
+
outdatedCount === 0 ? "Everything is current." : `${outdatedCount} update(s) available - skillpass update <slug>`
|
|
17038
|
+
);
|
|
17039
|
+
return { lines, exitCode: outdatedCount === 0 ? 0 : 1 };
|
|
17040
|
+
}
|
|
17041
|
+
|
|
17042
|
+
// src/remove.ts
|
|
17043
|
+
import { existsSync as existsSync4, rmSync as rmSync2, statSync as statSync5 } from "node:fs";
|
|
17044
|
+
import { dirname as dirname2, join as join7, resolve as resolve3 } from "node:path";
|
|
17045
|
+
function looksLikeInstalledSkill(dir) {
|
|
17046
|
+
return existsSync4(join7(dir, "SKILL.md")) || existsSync4(join7(dir, "skill.json"));
|
|
17047
|
+
}
|
|
17048
|
+
function candidateDirs(slug, cwd, home) {
|
|
17049
|
+
const inAreas = knownAreas(cwd, home).map((area) => join7(area.dir, slug));
|
|
17050
|
+
return [.../* @__PURE__ */ new Set([...inAreas, resolve3(cwd, slug)])];
|
|
16754
17051
|
}
|
|
16755
17052
|
function runRemove(slug, opts = {}) {
|
|
16756
17053
|
if (opts.target && opts.dir) {
|
|
16757
|
-
return { lines: [
|
|
17054
|
+
return { lines: [TARGET_OR_DIR], exitCode: 2 };
|
|
16758
17055
|
}
|
|
16759
17056
|
if (opts.global && !opts.target) {
|
|
16760
|
-
return { lines: [
|
|
17057
|
+
return { lines: [GLOBAL_NEEDS_TARGET], exitCode: 2 };
|
|
16761
17058
|
}
|
|
16762
17059
|
const cwd = opts.cwd ?? process.cwd();
|
|
17060
|
+
if (!opts.dir) {
|
|
17061
|
+
let areas = knownAreas(cwd, opts.home);
|
|
17062
|
+
if (opts.target) {
|
|
17063
|
+
areas = areas.filter((a) => a.tool === opts.target && a.global === (opts.global ?? false));
|
|
17064
|
+
}
|
|
17065
|
+
const packHits = areas.map((area) => ({
|
|
17066
|
+
area,
|
|
17067
|
+
members: Object.entries(readReceipts(area.dir)).filter(([, r]) => r.pack?.slug === slug).map(([name]) => name)
|
|
17068
|
+
})).filter((hit) => hit.members.length > 0);
|
|
17069
|
+
if (packHits.length > 1) {
|
|
17070
|
+
return {
|
|
17071
|
+
lines: [
|
|
17072
|
+
`error: the ${slug} pack is installed in more than one place; pick one with --target:`,
|
|
17073
|
+
...packHits.map((hit) => ` ${hit.area.dir}`)
|
|
17074
|
+
],
|
|
17075
|
+
exitCode: 2
|
|
17076
|
+
};
|
|
17077
|
+
}
|
|
17078
|
+
if (packHits.length === 1) {
|
|
17079
|
+
const { area, members } = packHits[0];
|
|
17080
|
+
for (const name of members) {
|
|
17081
|
+
rmSync2(join7(area.dir, name), { recursive: true, force: true });
|
|
17082
|
+
removeReceipt(area.dir, name);
|
|
17083
|
+
}
|
|
17084
|
+
return {
|
|
17085
|
+
lines: [
|
|
17086
|
+
`Removed pack ${slug} (${members.length} skills) from ${area.dir}`,
|
|
17087
|
+
` ${members.sort().join(", ")}`
|
|
17088
|
+
],
|
|
17089
|
+
exitCode: 0
|
|
17090
|
+
};
|
|
17091
|
+
}
|
|
17092
|
+
}
|
|
16763
17093
|
let dir;
|
|
16764
17094
|
if (opts.target) {
|
|
16765
17095
|
const resolved = resolveTargetDir(opts.target, slug, opts.global, opts.home);
|
|
@@ -16771,7 +17101,7 @@ function runRemove(slug, opts = {}) {
|
|
|
16771
17101
|
dir = resolve3(cwd, opts.dir);
|
|
16772
17102
|
} else {
|
|
16773
17103
|
const found = candidateDirs(slug, cwd, opts.home).filter(
|
|
16774
|
-
(candidate) =>
|
|
17104
|
+
(candidate) => existsSync4(candidate)
|
|
16775
17105
|
);
|
|
16776
17106
|
if (found.length === 0) {
|
|
16777
17107
|
return { lines: [`error: ${slug} is not installed in any known location`], exitCode: 2 };
|
|
@@ -16787,10 +17117,10 @@ function runRemove(slug, opts = {}) {
|
|
|
16787
17117
|
}
|
|
16788
17118
|
dir = found[0];
|
|
16789
17119
|
}
|
|
16790
|
-
if (!
|
|
17120
|
+
if (!existsSync4(dir)) {
|
|
16791
17121
|
return { lines: [`error: nothing installed at ${dir}`], exitCode: 2 };
|
|
16792
17122
|
}
|
|
16793
|
-
if (!
|
|
17123
|
+
if (!statSync5(dir).isDirectory()) {
|
|
16794
17124
|
return { lines: [`error: ${dir} is not a directory`], exitCode: 2 };
|
|
16795
17125
|
}
|
|
16796
17126
|
if (!looksLikeInstalledSkill(dir)) {
|
|
@@ -16800,6 +17130,7 @@ function runRemove(slug, opts = {}) {
|
|
|
16800
17130
|
};
|
|
16801
17131
|
}
|
|
16802
17132
|
rmSync2(dir, { recursive: true });
|
|
17133
|
+
removeReceipt(dirname2(dir), slug);
|
|
16803
17134
|
return { lines: [`Removed ${slug} from ${dir}`], exitCode: 0 };
|
|
16804
17135
|
}
|
|
16805
17136
|
|
|
@@ -16928,6 +17259,260 @@ async function runSearch(opts = {}) {
|
|
|
16928
17259
|
};
|
|
16929
17260
|
}
|
|
16930
17261
|
|
|
17262
|
+
// src/update.ts
|
|
17263
|
+
import { existsSync as existsSync5, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
|
|
17264
|
+
import { join as join8 } from "node:path";
|
|
17265
|
+
async function locate(slug, opts, fetchImpl, apiUrl) {
|
|
17266
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
17267
|
+
let areas = knownAreas(cwd, opts.home);
|
|
17268
|
+
if (opts.target) {
|
|
17269
|
+
areas = areas.filter((a) => a.tool === opts.target && a.global === (opts.global ?? false));
|
|
17270
|
+
}
|
|
17271
|
+
const hits = [];
|
|
17272
|
+
for (const area of areas) {
|
|
17273
|
+
const receipts = readReceipts(area.dir);
|
|
17274
|
+
const receipt = receipts[slug];
|
|
17275
|
+
const dir = join8(area.dir, slug);
|
|
17276
|
+
if (receipt) {
|
|
17277
|
+
hits.push({
|
|
17278
|
+
area,
|
|
17279
|
+
dir,
|
|
17280
|
+
installedVersion: receipt.version,
|
|
17281
|
+
packSlug: receipt.pack?.slug
|
|
17282
|
+
});
|
|
17283
|
+
continue;
|
|
17284
|
+
}
|
|
17285
|
+
const packMember = Object.values(receipts).find((r) => r.pack?.slug === slug);
|
|
17286
|
+
if (packMember?.pack) {
|
|
17287
|
+
hits.push({ area, dir: area.dir, installedVersion: packMember.pack.version, packSlug: slug });
|
|
17288
|
+
continue;
|
|
17289
|
+
}
|
|
17290
|
+
if (existsSync5(dir)) {
|
|
17291
|
+
const identified = await identifyByHash(fetchImpl, apiUrl, slug, dir);
|
|
17292
|
+
if (identified !== void 0) {
|
|
17293
|
+
hits.push({ area, dir, installedVersion: identified });
|
|
17294
|
+
}
|
|
17295
|
+
}
|
|
17296
|
+
}
|
|
17297
|
+
return hits;
|
|
17298
|
+
}
|
|
17299
|
+
function permissionChanges(installed, target) {
|
|
17300
|
+
const flat = (sets) => /* @__PURE__ */ new Set([...sets.declared, ...sets.detected]);
|
|
17301
|
+
const before = flat(installed);
|
|
17302
|
+
const after = flat(target);
|
|
17303
|
+
return {
|
|
17304
|
+
added: [...after].filter((key) => !before.has(key)).sort(),
|
|
17305
|
+
removed: [...before].filter((key) => !after.has(key)).sort()
|
|
17306
|
+
};
|
|
17307
|
+
}
|
|
17308
|
+
function swapTree(files, dir) {
|
|
17309
|
+
const fresh = `${dir}.new-${process.pid}`;
|
|
17310
|
+
const aside = `${dir}.old-${process.pid}`;
|
|
17311
|
+
rmSync3(fresh, { recursive: true, force: true });
|
|
17312
|
+
writeTree(files, fresh);
|
|
17313
|
+
try {
|
|
17314
|
+
if (existsSync5(dir)) renameSync2(dir, aside);
|
|
17315
|
+
renameSync2(fresh, dir);
|
|
17316
|
+
} catch (err2) {
|
|
17317
|
+
if (existsSync5(aside) && !existsSync5(dir)) renameSync2(aside, dir);
|
|
17318
|
+
rmSync3(fresh, { recursive: true, force: true });
|
|
17319
|
+
throw err2;
|
|
17320
|
+
}
|
|
17321
|
+
rmSync3(aside, { recursive: true, force: true });
|
|
17322
|
+
}
|
|
17323
|
+
async function runUpdate(ref, opts = {}) {
|
|
17324
|
+
const { push, done } = createOutput(opts.emit);
|
|
17325
|
+
const st = opts.style ?? PLAIN;
|
|
17326
|
+
if (opts.global && !opts.target) {
|
|
17327
|
+
push(GLOBAL_NEEDS_TARGET);
|
|
17328
|
+
return done(2);
|
|
17329
|
+
}
|
|
17330
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
17331
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17332
|
+
const { slug } = parseSkillRef(ref);
|
|
17333
|
+
if (!slug) {
|
|
17334
|
+
push(`error: invalid skill reference "${ref}"`);
|
|
17335
|
+
return done(2);
|
|
17336
|
+
}
|
|
17337
|
+
const hits = await locate(slug, opts, fetchImpl, apiUrl);
|
|
17338
|
+
const memberHit = hits.find((h) => h.packSlug !== void 0 && h.packSlug !== slug);
|
|
17339
|
+
if (memberHit?.packSlug !== void 0) {
|
|
17340
|
+
push(
|
|
17341
|
+
`error: ${slug} is part of the ${memberHit.packSlug} pack; run skillpass update ${memberHit.packSlug}`
|
|
17342
|
+
);
|
|
17343
|
+
return done(2);
|
|
17344
|
+
}
|
|
17345
|
+
if (hits.length === 0) {
|
|
17346
|
+
push(
|
|
17347
|
+
`error: ${slug} is not installed in a known skills area (only receipted or hash-identifiable installs can update)`
|
|
17348
|
+
);
|
|
17349
|
+
return done(2);
|
|
17350
|
+
}
|
|
17351
|
+
if (hits.length > 1) {
|
|
17352
|
+
push(
|
|
17353
|
+
`error: ${slug} is installed in more than one place; pick one with --target [--global]:`,
|
|
17354
|
+
...hits.map((h) => ` ${h.dir}`)
|
|
17355
|
+
);
|
|
17356
|
+
return done(2);
|
|
17357
|
+
}
|
|
17358
|
+
const installed = hits[0];
|
|
17359
|
+
const fetched = await fetchPreflight(fetchImpl, apiUrl, ref);
|
|
17360
|
+
if (!fetched.ok) {
|
|
17361
|
+
push(...fetched.result.lines);
|
|
17362
|
+
return done(fetched.result.exitCode);
|
|
17363
|
+
}
|
|
17364
|
+
const { detail, preflight } = fetched;
|
|
17365
|
+
if ((detail.packMembers?.length ?? 0) > 0 || installed.packSlug === slug) {
|
|
17366
|
+
return runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl, apiUrl, st, push, done);
|
|
17367
|
+
}
|
|
17368
|
+
if (installed.installedVersion === preflight.version) {
|
|
17369
|
+
push(`${slug} is already at ${preflight.version}.`);
|
|
17370
|
+
return done(0);
|
|
17371
|
+
}
|
|
17372
|
+
push(...renderPreflightReport(detail, preflight, opts.style));
|
|
17373
|
+
push("", ...await changesVsInstalled(fetchImpl, apiUrl, slug, installed, preflight, st));
|
|
17374
|
+
if (preflight.blocked) {
|
|
17375
|
+
return done(1);
|
|
17376
|
+
}
|
|
17377
|
+
const confirmed = await confirmRisk(
|
|
17378
|
+
preflight,
|
|
17379
|
+
opts,
|
|
17380
|
+
`Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
|
|
17381
|
+
"Update aborted; the installed version was left in place.",
|
|
17382
|
+
push
|
|
17383
|
+
);
|
|
17384
|
+
if (!confirmed) return done(2);
|
|
17385
|
+
const download = await downloadVerified(fetchImpl, apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
17386
|
+
if (!download.ok) {
|
|
17387
|
+
push("", `error: ${download.message}`);
|
|
17388
|
+
return done(2);
|
|
17389
|
+
}
|
|
17390
|
+
try {
|
|
17391
|
+
swapTree(download.files, installed.dir);
|
|
17392
|
+
} catch {
|
|
17393
|
+
push("", "error: could not replace the install; the installed version was left in place");
|
|
17394
|
+
return done(2);
|
|
17395
|
+
}
|
|
17396
|
+
recordReceipt(installed.area.dir, slug, receiptFor(preflight));
|
|
17397
|
+
push(
|
|
17398
|
+
"",
|
|
17399
|
+
`Updated ${slug} ${installed.installedVersion ?? "?"} -> ${preflight.version} in ${installed.dir}`,
|
|
17400
|
+
"Source hash verified against the Skill Passport."
|
|
17401
|
+
);
|
|
17402
|
+
return done(0);
|
|
17403
|
+
}
|
|
17404
|
+
async function changesVsInstalled(fetchImpl, apiUrl, slug, installed, target, st) {
|
|
17405
|
+
if (installed.installedVersion === void 0) {
|
|
17406
|
+
return ["Changes vs installed: unknown (no version identified)"];
|
|
17407
|
+
}
|
|
17408
|
+
const before = await getParsed(
|
|
17409
|
+
fetchImpl,
|
|
17410
|
+
`${apiUrl}/skills/${encodeURIComponent(slug)}/${encodeURIComponent(installed.installedVersion)}/preflight`,
|
|
17411
|
+
publicPreflightSchema
|
|
17412
|
+
);
|
|
17413
|
+
if (!before.ok) {
|
|
17414
|
+
return [
|
|
17415
|
+
`Changes vs installed v${installed.installedVersion}: unavailable (that version is no longer published)`
|
|
17416
|
+
];
|
|
17417
|
+
}
|
|
17418
|
+
const { added, removed } = permissionChanges(before.data.permissions, target.permissions);
|
|
17419
|
+
if (added.length === 0 && removed.length === 0) {
|
|
17420
|
+
return [`Changes vs installed v${installed.installedVersion}: no permission changes`];
|
|
17421
|
+
}
|
|
17422
|
+
return [
|
|
17423
|
+
`Changes vs installed v${installed.installedVersion}`,
|
|
17424
|
+
...added.map((key) => st.red(` + ${key} (new)`)),
|
|
17425
|
+
...removed.map((key) => ` - ${key} (no longer requested)`)
|
|
17426
|
+
];
|
|
17427
|
+
}
|
|
17428
|
+
async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl, apiUrl, st, push, done) {
|
|
17429
|
+
if (installed.packSlug !== slug) {
|
|
17430
|
+
push(`error: ${slug} is a pack but this install is not tracked as one; reinstall with skillpass add`);
|
|
17431
|
+
return done(2);
|
|
17432
|
+
}
|
|
17433
|
+
if (installed.installedVersion === preflight.version) {
|
|
17434
|
+
push(`${slug} is already at ${preflight.version}.`);
|
|
17435
|
+
return done(0);
|
|
17436
|
+
}
|
|
17437
|
+
push(...renderPreflightReport(detail, preflight, opts.style));
|
|
17438
|
+
push("", ...await changesVsInstalled(fetchImpl, apiUrl, slug, installed, preflight, st));
|
|
17439
|
+
if (preflight.blocked) {
|
|
17440
|
+
return done(1);
|
|
17441
|
+
}
|
|
17442
|
+
const confirmed = await confirmRisk(
|
|
17443
|
+
preflight,
|
|
17444
|
+
opts,
|
|
17445
|
+
`Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
|
|
17446
|
+
"Update aborted; the installed version was left in place.",
|
|
17447
|
+
push
|
|
17448
|
+
);
|
|
17449
|
+
if (!confirmed) return done(2);
|
|
17450
|
+
const areaDir = installed.area.dir;
|
|
17451
|
+
const { installs, skipped } = resolvePackMembers(detail.packMembers ?? [], installed.area.tool);
|
|
17452
|
+
if (installs.length === 0) {
|
|
17453
|
+
push("", `error: none of the new version's skills support ${installed.area.tool}`);
|
|
17454
|
+
return done(2);
|
|
17455
|
+
}
|
|
17456
|
+
const receipts = readReceipts(areaDir);
|
|
17457
|
+
const currentMembers = Object.entries(receipts).filter(([, r]) => r.pack?.slug === slug).map(([name]) => name);
|
|
17458
|
+
const newNames = new Set(installs.map((m) => m.name));
|
|
17459
|
+
const toRemove = currentMembers.filter((name) => !newNames.has(name));
|
|
17460
|
+
const additions = installs.filter((m) => !currentMembers.includes(m.name));
|
|
17461
|
+
const conflicts = additions.map((m) => join8(areaDir, m.name)).filter(isOccupied);
|
|
17462
|
+
if (conflicts.length > 0) {
|
|
17463
|
+
push(
|
|
17464
|
+
"",
|
|
17465
|
+
"error: these destinations already exist and are not empty; nothing was changed:",
|
|
17466
|
+
...conflicts.map((c) => ` ${c}`)
|
|
17467
|
+
);
|
|
17468
|
+
return done(2);
|
|
17469
|
+
}
|
|
17470
|
+
const download = await downloadVerified(fetchImpl, apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
17471
|
+
if (!download.ok) {
|
|
17472
|
+
push("", `error: ${download.message}`);
|
|
17473
|
+
return done(2);
|
|
17474
|
+
}
|
|
17475
|
+
const plans = planMembers(installs, download.files);
|
|
17476
|
+
const missing = plans.find((p) => p.files.length === 0);
|
|
17477
|
+
if (missing) {
|
|
17478
|
+
push("", `error: the snapshot has no files for "${missing.name}"; nothing was changed`);
|
|
17479
|
+
return done(2);
|
|
17480
|
+
}
|
|
17481
|
+
for (const name of skipped) {
|
|
17482
|
+
push("", `note: ${name} does not support ${installed.area.tool}; skipped`);
|
|
17483
|
+
}
|
|
17484
|
+
const done1 = [];
|
|
17485
|
+
try {
|
|
17486
|
+
for (const plan of plans) {
|
|
17487
|
+
const dest = join8(areaDir, plan.name);
|
|
17488
|
+
if (existsSync5(dest)) {
|
|
17489
|
+
swapTree(plan.files, dest);
|
|
17490
|
+
} else {
|
|
17491
|
+
writeTree(plan.files, dest);
|
|
17492
|
+
}
|
|
17493
|
+
done1.push(plan.name);
|
|
17494
|
+
recordReceipt(areaDir, plan.name, receiptFor(preflight, slug));
|
|
17495
|
+
}
|
|
17496
|
+
} catch {
|
|
17497
|
+
push(
|
|
17498
|
+
"",
|
|
17499
|
+
`error: could not write ${plans[done1.length].name}; updated before the failure: ${done1.join(", ") || "none"}`
|
|
17500
|
+
);
|
|
17501
|
+
return done(2);
|
|
17502
|
+
}
|
|
17503
|
+
for (const name of toRemove) {
|
|
17504
|
+
rmSync3(join8(areaDir, name), { recursive: true, force: true });
|
|
17505
|
+
removeReceipt(areaDir, name);
|
|
17506
|
+
}
|
|
17507
|
+
push(
|
|
17508
|
+
"",
|
|
17509
|
+
`Updated ${slug} ${installed.installedVersion ?? "?"} -> ${preflight.version} in ${areaDir}`,
|
|
17510
|
+
` ${done1.length} skill(s)${additions.length > 0 ? `, added: ${additions.map((m) => m.name).join(", ")}` : ""}${toRemove.length > 0 ? `, removed: ${toRemove.join(", ")}` : ""}`,
|
|
17511
|
+
"Source hash verified against the Skill Passport."
|
|
17512
|
+
);
|
|
17513
|
+
return done(0);
|
|
17514
|
+
}
|
|
17515
|
+
|
|
16931
17516
|
// src/scan.ts
|
|
16932
17517
|
async function runScan(path, opts = {}) {
|
|
16933
17518
|
let report;
|
|
@@ -16970,7 +17555,7 @@ async function runScan(path, opts = {}) {
|
|
|
16970
17555
|
}
|
|
16971
17556
|
|
|
16972
17557
|
// src/index.ts
|
|
16973
|
-
var VERSION = JSON.parse(
|
|
17558
|
+
var VERSION = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
16974
17559
|
var USAGE = [
|
|
16975
17560
|
"skillpass - validate AI agent skills locally and inspect hosted passports",
|
|
16976
17561
|
"",
|
|
@@ -16984,6 +17569,9 @@ var USAGE = [
|
|
|
16984
17569
|
" skillpass remove <slug> [--target <tool> [--global] | --dir <path>]",
|
|
16985
17570
|
" remove an installed skill",
|
|
16986
17571
|
" skillpass list show installed skills in the known areas",
|
|
17572
|
+
" skillpass outdated compare installed skills to the directory",
|
|
17573
|
+
" skillpass update <slug>[@version] [--target <tool> [--global]] [--yes]",
|
|
17574
|
+
" update an installed skill through the gate",
|
|
16987
17575
|
"",
|
|
16988
17576
|
"Flags:",
|
|
16989
17577
|
" --json print machine-readable JSON instead of the readable report",
|
|
@@ -16998,7 +17586,8 @@ var USAGE = [
|
|
|
16998
17586
|
" --help show this message",
|
|
16999
17587
|
"",
|
|
17000
17588
|
"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
|
|
17589
|
+
"Exit codes: 0 ok/warning, 1 failed or blocked, 2 usage/load/network errors",
|
|
17590
|
+
"(outdated exits 1 when updates are available)."
|
|
17002
17591
|
].join("\n");
|
|
17003
17592
|
var COMMAND_FLAGS = {
|
|
17004
17593
|
scan: ["--json"],
|
|
@@ -17006,6 +17595,8 @@ var COMMAND_FLAGS = {
|
|
|
17006
17595
|
add: ["--yes", "--target", "--dir", "--global"],
|
|
17007
17596
|
remove: ["--target", "--dir", "--global"],
|
|
17008
17597
|
list: [],
|
|
17598
|
+
outdated: [],
|
|
17599
|
+
update: ["--yes", "--target", "--global"],
|
|
17009
17600
|
search: ["--target", "--category", "--packs", "--json"]
|
|
17010
17601
|
};
|
|
17011
17602
|
function parseCliArgs(argv) {
|
|
@@ -17124,6 +17715,27 @@ async function run(argv) {
|
|
|
17124
17715
|
}
|
|
17125
17716
|
return runList();
|
|
17126
17717
|
}
|
|
17718
|
+
if (args.command === "update") {
|
|
17719
|
+
const [ref] = args.positional;
|
|
17720
|
+
if (!ref) {
|
|
17721
|
+
return { lines: ["error: update needs a skill slug", "", USAGE], exitCode: 2 };
|
|
17722
|
+
}
|
|
17723
|
+
const tty = Boolean(process.stdin.isTTY);
|
|
17724
|
+
return runUpdate(ref, {
|
|
17725
|
+
yes: args.yes,
|
|
17726
|
+
target: args.target,
|
|
17727
|
+
global: args.global,
|
|
17728
|
+
style: styler(Boolean(process.stdout.isTTY)),
|
|
17729
|
+
confirmImpl: tty ? confirmViaTty : void 0,
|
|
17730
|
+
emit: (text) => console.log(text)
|
|
17731
|
+
});
|
|
17732
|
+
}
|
|
17733
|
+
if (args.command === "outdated") {
|
|
17734
|
+
if (args.positional.length > 0) {
|
|
17735
|
+
return { lines: ["error: outdated takes no arguments", "", USAGE], exitCode: 2 };
|
|
17736
|
+
}
|
|
17737
|
+
return runOutdated();
|
|
17738
|
+
}
|
|
17127
17739
|
if (args.command === "report") {
|
|
17128
17740
|
const [ref] = args.positional;
|
|
17129
17741
|
if (!ref) {
|
|
@@ -17150,10 +17762,17 @@ async function run(argv) {
|
|
|
17150
17762
|
}
|
|
17151
17763
|
return { lines: [`error: unknown command "${args.command}"`, "", USAGE], exitCode: 2 };
|
|
17152
17764
|
}
|
|
17153
|
-
async function main(argv) {
|
|
17154
|
-
|
|
17765
|
+
async function main(argv, runImpl = run) {
|
|
17766
|
+
let result;
|
|
17767
|
+
try {
|
|
17768
|
+
result = await runImpl(argv);
|
|
17769
|
+
} catch (err2) {
|
|
17770
|
+
console.error(`error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
17771
|
+
process.exitCode = 2;
|
|
17772
|
+
return;
|
|
17773
|
+
}
|
|
17155
17774
|
if (!result.streamed) {
|
|
17156
|
-
console.log(result.lines.join("\n"));
|
|
17775
|
+
(result.exitCode === 2 ? console.error : console.log)(result.lines.join("\n"));
|
|
17157
17776
|
}
|
|
17158
17777
|
process.exitCode = result.exitCode;
|
|
17159
17778
|
}
|