skillpass 0.2.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/bin/skillpass.js +2 -0
- package/dist/{cli.mjs → cli.js} +479 -399
- package/package.json +5 -5
- package/bin/skillpass.mjs +0 -2
package/bin/skillpass.js
ADDED
package/dist/{cli.mjs → cli.js}
RENAMED
|
@@ -493,13 +493,13 @@ function unzipSync(data, opts) {
|
|
|
493
493
|
|
|
494
494
|
// src/add.ts
|
|
495
495
|
import {
|
|
496
|
-
existsSync,
|
|
496
|
+
existsSync as existsSync2,
|
|
497
497
|
mkdirSync as mkdirSync2,
|
|
498
|
-
readdirSync as
|
|
498
|
+
readdirSync as readdirSync3,
|
|
499
499
|
renameSync,
|
|
500
500
|
rmdirSync,
|
|
501
501
|
rmSync,
|
|
502
|
-
statSync as
|
|
502
|
+
statSync as statSync3,
|
|
503
503
|
writeFileSync as writeFileSync2
|
|
504
504
|
} from "node:fs";
|
|
505
505
|
import { dirname, isAbsolute, join as join4, resolve as resolve2 } from "node:path";
|
|
@@ -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
|
+
});
|
|
16054
16080
|
}
|
|
16055
16081
|
});
|
|
16056
16082
|
}
|
|
16057
16083
|
return findings;
|
|
16058
16084
|
};
|
|
16059
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"
|
|
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 }
|
|
16118
|
+
});
|
|
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
|
+
}
|
|
16140
|
+
return findings;
|
|
16141
|
+
};
|
|
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",
|
|
@@ -16160,6 +16264,52 @@ async function fetchPreflight(fetchImpl, apiUrl, ref) {
|
|
|
16160
16264
|
return { ok: true, slug, detail: detail.data, preflight: preflight.data };
|
|
16161
16265
|
}
|
|
16162
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
|
+
|
|
16163
16313
|
// src/pack.ts
|
|
16164
16314
|
import { posix } from "node:path";
|
|
16165
16315
|
function resolvePackMembers(members, target) {
|
|
@@ -16464,7 +16614,7 @@ function writeTree(files, target) {
|
|
|
16464
16614
|
writeFileSync2(filePath, file2.content);
|
|
16465
16615
|
}
|
|
16466
16616
|
mkdirSync2(dirname(target), { recursive: true });
|
|
16467
|
-
if (
|
|
16617
|
+
if (existsSync2(target)) {
|
|
16468
16618
|
rmdirSync(target);
|
|
16469
16619
|
}
|
|
16470
16620
|
renameSync(tempDir, target);
|
|
@@ -16473,13 +16623,6 @@ function writeTree(files, target) {
|
|
|
16473
16623
|
throw err2;
|
|
16474
16624
|
}
|
|
16475
16625
|
}
|
|
16476
|
-
function memberFiles(files, sourceDir) {
|
|
16477
|
-
if (sourceDir === ".") {
|
|
16478
|
-
return files;
|
|
16479
|
-
}
|
|
16480
|
-
const prefix = `${sourceDir}/`;
|
|
16481
|
-
return files.filter((f) => f.path.startsWith(prefix)).map((f) => ({ path: f.path.slice(prefix.length), content: f.content }));
|
|
16482
|
-
}
|
|
16483
16626
|
function installChoices(declared, slug) {
|
|
16484
16627
|
const choices = [];
|
|
16485
16628
|
const ordered = [
|
|
@@ -16531,22 +16674,146 @@ function packChoices(declared, slug, count) {
|
|
|
16531
16674
|
choices.push({ label: `current directory (./${slug}, raw pack source)` });
|
|
16532
16675
|
return choices;
|
|
16533
16676
|
}
|
|
16534
|
-
async function
|
|
16535
|
-
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
if (
|
|
16540
|
-
|
|
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}`);
|
|
16721
|
+
return done(2);
|
|
16722
|
+
}
|
|
16723
|
+
const { installs, skipped } = resolvePackMembers(members, tool);
|
|
16724
|
+
if (installs.length === 0) {
|
|
16725
|
+
push("", `error: none of this pack's skills support ${tool}`);
|
|
16726
|
+
return done(2);
|
|
16727
|
+
}
|
|
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);
|
|
16737
|
+
}
|
|
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);
|
|
16742
|
+
}
|
|
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);
|
|
16748
|
+
}
|
|
16749
|
+
for (const name of skipped) {
|
|
16750
|
+
push("", `note: ${name} does not support ${tool}; skipped`);
|
|
16751
|
+
}
|
|
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));
|
|
16541
16758
|
}
|
|
16542
|
-
}
|
|
16543
|
-
|
|
16759
|
+
} catch {
|
|
16760
|
+
push(
|
|
16761
|
+
"",
|
|
16762
|
+
`error: could not write ${plans[written.length].name}; installed before the failure: ${written.join(", ") || "none"}`
|
|
16763
|
+
);
|
|
16764
|
+
return done(2);
|
|
16765
|
+
}
|
|
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()) {
|
|
16779
|
+
push("", `error: target ${target} already exists and is not a directory`);
|
|
16780
|
+
return done(2);
|
|
16781
|
+
}
|
|
16782
|
+
if (readdirSync3(target).length > 0) {
|
|
16783
|
+
push("", `error: target directory ${target} already exists and is not empty`);
|
|
16784
|
+
return done(2);
|
|
16785
|
+
}
|
|
16786
|
+
}
|
|
16787
|
+
const download = await downloadVerified(ctx.fetchImpl, ctx.apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
16788
|
+
if (!download.ok) {
|
|
16789
|
+
push("", `error: ${download.message}`);
|
|
16790
|
+
return done(2);
|
|
16791
|
+
}
|
|
16792
|
+
try {
|
|
16793
|
+
writeTree(download.files, target);
|
|
16794
|
+
} catch {
|
|
16795
|
+
push("", "error: could not write the install; nothing was installed");
|
|
16796
|
+
return done(2);
|
|
16797
|
+
}
|
|
16798
|
+
const area = knownAreas(ctx.cwd).find((a) => a.dir === dirname(target));
|
|
16799
|
+
if (area) {
|
|
16800
|
+
recordReceipt(area.dir, slug, receiptFor(preflight));
|
|
16801
|
+
}
|
|
16802
|
+
push(
|
|
16803
|
+
"",
|
|
16804
|
+
`Installed ${download.files.length} file(s) to ${target}`,
|
|
16805
|
+
"Source hash verified against the Skill Passport."
|
|
16806
|
+
);
|
|
16807
|
+
return done(0);
|
|
16808
|
+
}
|
|
16809
|
+
async function runAdd(ref, opts = {}) {
|
|
16810
|
+
const { push, done } = createOutput(opts.emit);
|
|
16544
16811
|
if (opts.target && opts.dir) {
|
|
16545
|
-
push(
|
|
16812
|
+
push(TARGET_OR_DIR);
|
|
16546
16813
|
return done(2);
|
|
16547
16814
|
}
|
|
16548
16815
|
if (opts.global && !opts.target) {
|
|
16549
|
-
push(
|
|
16816
|
+
push(GLOBAL_NEEDS_TARGET);
|
|
16550
16817
|
return done(2);
|
|
16551
16818
|
}
|
|
16552
16819
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
@@ -16564,8 +16831,7 @@ async function runAdd(ref, opts = {}) {
|
|
|
16564
16831
|
const members = detail.packMembers ?? [];
|
|
16565
16832
|
const isPack = members.length > 0;
|
|
16566
16833
|
let targetDir = opts.dir;
|
|
16567
|
-
let
|
|
16568
|
-
let packGlobal = false;
|
|
16834
|
+
let pack;
|
|
16569
16835
|
if (opts.target) {
|
|
16570
16836
|
const resolved = isPack ? resolveTargetArea(opts.target, opts.global) : resolveTargetDir(opts.target, slug, opts.global);
|
|
16571
16837
|
if (!resolved.ok) {
|
|
@@ -16573,8 +16839,7 @@ async function runAdd(ref, opts = {}) {
|
|
|
16573
16839
|
return done(2);
|
|
16574
16840
|
}
|
|
16575
16841
|
if (isPack) {
|
|
16576
|
-
|
|
16577
|
-
packGlobal = opts.global ?? false;
|
|
16842
|
+
pack = { tool: opts.target, global: opts.global ?? false };
|
|
16578
16843
|
} else {
|
|
16579
16844
|
targetDir = resolved.dir;
|
|
16580
16845
|
}
|
|
@@ -16582,127 +16847,30 @@ async function runAdd(ref, opts = {}) {
|
|
|
16582
16847
|
push("", `warning: this skill does not declare ${opts.target} as a target`);
|
|
16583
16848
|
}
|
|
16584
16849
|
}
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
const answer = (await ask(`Where should it go? [1-${count}, default 1] `)).trim();
|
|
16602
|
-
if (answer === "") {
|
|
16603
|
-
return 0;
|
|
16604
|
-
}
|
|
16605
|
-
const index = /^\d+$/.test(answer) ? Number.parseInt(answer, 10) - 1 : -1;
|
|
16606
|
-
if (index >= 0 && index < count) {
|
|
16607
|
-
return index;
|
|
16608
|
-
}
|
|
16609
|
-
push(` answer 1-${count}, or press enter for the default`);
|
|
16610
|
-
}
|
|
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
|
|
16611
16866
|
};
|
|
16612
|
-
if (isPack &&
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
choices.forEach((choice, i2) => push(` ${i2 + 1}) ${choice.label}`));
|
|
16617
|
-
const chosen = choices[await promptIndex(prompt, choices.length)];
|
|
16618
|
-
if (chosen.tool) {
|
|
16619
|
-
packTool = chosen.tool;
|
|
16620
|
-
packGlobal = chosen.global ?? false;
|
|
16621
|
-
} else {
|
|
16622
|
-
targetDir = slug;
|
|
16623
|
-
}
|
|
16624
|
-
} else {
|
|
16625
|
-
targetDir = slug;
|
|
16626
|
-
const mappable = mappableDeclaredTargets(detail.targets);
|
|
16627
|
-
if (mappable.length > 0) {
|
|
16628
|
-
push(
|
|
16629
|
-
"",
|
|
16630
|
-
`tip: --target ${mappable[0]} installs the ${members.length} skills into the tool's skills folder`
|
|
16631
|
-
);
|
|
16632
|
-
}
|
|
16633
|
-
}
|
|
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;
|
|
16634
16871
|
}
|
|
16635
|
-
if (
|
|
16636
|
-
|
|
16637
|
-
if (!area2.ok) {
|
|
16638
|
-
push("", `error: ${area2.message}`);
|
|
16639
|
-
return done(2);
|
|
16640
|
-
}
|
|
16641
|
-
const { installs, skipped } = resolvePackMembers(members, packTool);
|
|
16642
|
-
if (installs.length === 0) {
|
|
16643
|
-
push("", `error: none of this pack's skills support ${packTool}`);
|
|
16644
|
-
return done(2);
|
|
16645
|
-
}
|
|
16646
|
-
const areaAbs = resolve2(opts.cwd ?? process.cwd(), area2.dir);
|
|
16647
|
-
const conflicts = installs.map((m) => join4(areaAbs, m.name)).filter(
|
|
16648
|
-
(dest) => existsSync(dest) && (!statSync2(dest).isDirectory() || readdirSync2(dest).length > 0)
|
|
16649
|
-
);
|
|
16650
|
-
if (conflicts.length > 0) {
|
|
16651
|
-
push(
|
|
16652
|
-
"",
|
|
16653
|
-
"error: these destinations already exist and are not empty; nothing was installed:",
|
|
16654
|
-
...conflicts.map((c) => ` ${c}`)
|
|
16655
|
-
);
|
|
16656
|
-
return done(2);
|
|
16657
|
-
}
|
|
16658
|
-
const download2 = await downloadVerified(
|
|
16659
|
-
fetchImpl,
|
|
16660
|
-
apiUrl,
|
|
16661
|
-
slug,
|
|
16662
|
-
preflight.version,
|
|
16663
|
-
preflight.sourceHash
|
|
16664
|
-
);
|
|
16665
|
-
if (!download2.ok) {
|
|
16666
|
-
push("", `error: ${download2.message}`);
|
|
16667
|
-
return done(2);
|
|
16668
|
-
}
|
|
16669
|
-
const plans = installs.map((m) => ({ ...m, files: memberFiles(download2.files, m.sourceDir) }));
|
|
16670
|
-
const missing = plans.find((p) => p.files.length === 0);
|
|
16671
|
-
if (missing) {
|
|
16672
|
-
push("", `error: the snapshot has no files for "${missing.name}"; nothing was installed`);
|
|
16673
|
-
return done(2);
|
|
16674
|
-
}
|
|
16675
|
-
for (const name of skipped) {
|
|
16676
|
-
push("", `note: ${name} does not support ${packTool}; skipped`);
|
|
16677
|
-
}
|
|
16678
|
-
const written = [];
|
|
16679
|
-
try {
|
|
16680
|
-
for (const plan of plans) {
|
|
16681
|
-
writeTree(plan.files, join4(areaAbs, plan.name));
|
|
16682
|
-
written.push(plan.name);
|
|
16683
|
-
}
|
|
16684
|
-
} catch {
|
|
16685
|
-
push(
|
|
16686
|
-
"",
|
|
16687
|
-
`error: could not write ${plans[written.length].name}; installed before the failure: ${written.join(", ") || "none"}`
|
|
16688
|
-
);
|
|
16689
|
-
return done(2);
|
|
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
|
-
}
|
|
16699
|
-
push(
|
|
16700
|
-
"",
|
|
16701
|
-
`Installed ${written.length} skills to ${areaAbs}`,
|
|
16702
|
-
` ${written.join(", ")}`,
|
|
16703
|
-
"Source hash verified against the Skill Passport."
|
|
16704
|
-
);
|
|
16705
|
-
return done(0);
|
|
16872
|
+
if (pack !== void 0) {
|
|
16873
|
+
return installPack(ctx, members, pack.tool, pack.global);
|
|
16706
16874
|
}
|
|
16707
16875
|
if (isPack && opts.dir) {
|
|
16708
16876
|
push(
|
|
@@ -16710,72 +16878,17 @@ async function runAdd(ref, opts = {}) {
|
|
|
16710
16878
|
`note: --dir installs the raw pack source; --target <tool> installs the ${members.length} skills individually`
|
|
16711
16879
|
);
|
|
16712
16880
|
}
|
|
16713
|
-
|
|
16714
|
-
const choices = installChoices(detail.targets, slug);
|
|
16715
|
-
if (prompt && choices.length > 1) {
|
|
16716
|
-
push("", "Install location:");
|
|
16717
|
-
choices.forEach((choice, i2) => push(` ${i2 + 1}) ${choice.label}`));
|
|
16718
|
-
targetDir = choices[await promptIndex(prompt, choices.length)].dir;
|
|
16719
|
-
} else {
|
|
16720
|
-
targetDir = slug;
|
|
16721
|
-
const mappable = mappableDeclaredTargets(detail.targets);
|
|
16722
|
-
if (mappable.length > 0) {
|
|
16723
|
-
push("", `tip: --target ${mappable[0]} installs into the tool's skills folder`);
|
|
16724
|
-
}
|
|
16725
|
-
}
|
|
16726
|
-
}
|
|
16727
|
-
const target = resolve2(opts.cwd ?? process.cwd(), targetDir);
|
|
16728
|
-
if (existsSync(target)) {
|
|
16729
|
-
if (!statSync2(target).isDirectory()) {
|
|
16730
|
-
push("", `error: target ${target} already exists and is not a directory`);
|
|
16731
|
-
return done(2);
|
|
16732
|
-
}
|
|
16733
|
-
if (readdirSync2(target).length > 0) {
|
|
16734
|
-
push("", `error: target directory ${target} already exists and is not empty`);
|
|
16735
|
-
return done(2);
|
|
16736
|
-
}
|
|
16737
|
-
}
|
|
16738
|
-
const download = await downloadVerified(
|
|
16739
|
-
fetchImpl,
|
|
16740
|
-
apiUrl,
|
|
16741
|
-
slug,
|
|
16742
|
-
preflight.version,
|
|
16743
|
-
preflight.sourceHash
|
|
16744
|
-
);
|
|
16745
|
-
if (!download.ok) {
|
|
16746
|
-
push("", `error: ${download.message}`);
|
|
16747
|
-
return done(2);
|
|
16748
|
-
}
|
|
16749
|
-
try {
|
|
16750
|
-
writeTree(download.files, target);
|
|
16751
|
-
} catch {
|
|
16752
|
-
push("", "error: could not write the install; nothing was installed");
|
|
16753
|
-
return done(2);
|
|
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
|
-
}
|
|
16763
|
-
push(
|
|
16764
|
-
"",
|
|
16765
|
-
`Installed ${download.files.length} file(s) to ${target}`,
|
|
16766
|
-
"Source hash verified against the Skill Passport."
|
|
16767
|
-
);
|
|
16768
|
-
return done(0);
|
|
16881
|
+
return installSingle(ctx, targetDir ?? await chooseSingleLocation(ctx, detail, opts.promptImpl));
|
|
16769
16882
|
}
|
|
16770
16883
|
|
|
16771
16884
|
// src/list.ts
|
|
16772
|
-
import { existsSync as
|
|
16885
|
+
import { existsSync as existsSync3, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
|
|
16773
16886
|
import { join as join5 } from "node:path";
|
|
16774
16887
|
function installedIn(dir) {
|
|
16775
|
-
if (!
|
|
16888
|
+
if (!existsSync3(dir)) {
|
|
16776
16889
|
return [];
|
|
16777
16890
|
}
|
|
16778
|
-
return
|
|
16891
|
+
return readdirSync4(dir).sort().filter((name) => statSync4(join5(dir, name), { throwIfNoEntry: false })?.isDirectory() ?? false);
|
|
16779
16892
|
}
|
|
16780
16893
|
function runList(opts = {}) {
|
|
16781
16894
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -16927,33 +17040,21 @@ async function runOutdated(opts = {}) {
|
|
|
16927
17040
|
}
|
|
16928
17041
|
|
|
16929
17042
|
// src/remove.ts
|
|
16930
|
-
import { existsSync as
|
|
17043
|
+
import { existsSync as existsSync4, rmSync as rmSync2, statSync as statSync5 } from "node:fs";
|
|
16931
17044
|
import { dirname as dirname2, join as join7, resolve as resolve3 } from "node:path";
|
|
16932
17045
|
function looksLikeInstalledSkill(dir) {
|
|
16933
|
-
return
|
|
17046
|
+
return existsSync4(join7(dir, "SKILL.md")) || existsSync4(join7(dir, "skill.json"));
|
|
16934
17047
|
}
|
|
16935
17048
|
function candidateDirs(slug, cwd, home) {
|
|
16936
|
-
const
|
|
16937
|
-
|
|
16938
|
-
for (const tool of MAPPED_TARGETS) {
|
|
16939
|
-
const area = byTarget[tool];
|
|
16940
|
-
if (!area) {
|
|
16941
|
-
continue;
|
|
16942
|
-
}
|
|
16943
|
-
dirs.push(resolve3(cwd, join7(area.project, slug)));
|
|
16944
|
-
if (area.global) {
|
|
16945
|
-
dirs.push(join7(area.global, slug));
|
|
16946
|
-
}
|
|
16947
|
-
}
|
|
16948
|
-
dirs.push(resolve3(cwd, slug));
|
|
16949
|
-
return [...new Set(dirs)];
|
|
17049
|
+
const inAreas = knownAreas(cwd, home).map((area) => join7(area.dir, slug));
|
|
17050
|
+
return [.../* @__PURE__ */ new Set([...inAreas, resolve3(cwd, slug)])];
|
|
16950
17051
|
}
|
|
16951
17052
|
function runRemove(slug, opts = {}) {
|
|
16952
17053
|
if (opts.target && opts.dir) {
|
|
16953
|
-
return { lines: [
|
|
17054
|
+
return { lines: [TARGET_OR_DIR], exitCode: 2 };
|
|
16954
17055
|
}
|
|
16955
17056
|
if (opts.global && !opts.target) {
|
|
16956
|
-
return { lines: [
|
|
17057
|
+
return { lines: [GLOBAL_NEEDS_TARGET], exitCode: 2 };
|
|
16957
17058
|
}
|
|
16958
17059
|
const cwd = opts.cwd ?? process.cwd();
|
|
16959
17060
|
if (!opts.dir) {
|
|
@@ -17000,7 +17101,7 @@ function runRemove(slug, opts = {}) {
|
|
|
17000
17101
|
dir = resolve3(cwd, opts.dir);
|
|
17001
17102
|
} else {
|
|
17002
17103
|
const found = candidateDirs(slug, cwd, opts.home).filter(
|
|
17003
|
-
(candidate) =>
|
|
17104
|
+
(candidate) => existsSync4(candidate)
|
|
17004
17105
|
);
|
|
17005
17106
|
if (found.length === 0) {
|
|
17006
17107
|
return { lines: [`error: ${slug} is not installed in any known location`], exitCode: 2 };
|
|
@@ -17016,10 +17117,10 @@ function runRemove(slug, opts = {}) {
|
|
|
17016
17117
|
}
|
|
17017
17118
|
dir = found[0];
|
|
17018
17119
|
}
|
|
17019
|
-
if (!
|
|
17120
|
+
if (!existsSync4(dir)) {
|
|
17020
17121
|
return { lines: [`error: nothing installed at ${dir}`], exitCode: 2 };
|
|
17021
17122
|
}
|
|
17022
|
-
if (!
|
|
17123
|
+
if (!statSync5(dir).isDirectory()) {
|
|
17023
17124
|
return { lines: [`error: ${dir} is not a directory`], exitCode: 2 };
|
|
17024
17125
|
}
|
|
17025
17126
|
if (!looksLikeInstalledSkill(dir)) {
|
|
@@ -17159,7 +17260,7 @@ async function runSearch(opts = {}) {
|
|
|
17159
17260
|
}
|
|
17160
17261
|
|
|
17161
17262
|
// src/update.ts
|
|
17162
|
-
import { existsSync as
|
|
17263
|
+
import { existsSync as existsSync5, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
|
|
17163
17264
|
import { join as join8 } from "node:path";
|
|
17164
17265
|
async function locate(slug, opts, fetchImpl, apiUrl) {
|
|
17165
17266
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -17186,7 +17287,7 @@ async function locate(slug, opts, fetchImpl, apiUrl) {
|
|
|
17186
17287
|
hits.push({ area, dir: area.dir, installedVersion: packMember.pack.version, packSlug: slug });
|
|
17187
17288
|
continue;
|
|
17188
17289
|
}
|
|
17189
|
-
if (
|
|
17290
|
+
if (existsSync5(dir)) {
|
|
17190
17291
|
const identified = await identifyByHash(fetchImpl, apiUrl, slug, dir);
|
|
17191
17292
|
if (identified !== void 0) {
|
|
17192
17293
|
hits.push({ area, dir, installedVersion: identified });
|
|
@@ -17207,30 +17308,23 @@ function permissionChanges(installed, target) {
|
|
|
17207
17308
|
function swapTree(files, dir) {
|
|
17208
17309
|
const fresh = `${dir}.new-${process.pid}`;
|
|
17209
17310
|
const aside = `${dir}.old-${process.pid}`;
|
|
17311
|
+
rmSync3(fresh, { recursive: true, force: true });
|
|
17210
17312
|
writeTree(files, fresh);
|
|
17211
|
-
renameSync2(dir, aside);
|
|
17212
17313
|
try {
|
|
17314
|
+
if (existsSync5(dir)) renameSync2(dir, aside);
|
|
17213
17315
|
renameSync2(fresh, dir);
|
|
17214
17316
|
} catch (err2) {
|
|
17215
|
-
renameSync2(aside, dir);
|
|
17317
|
+
if (existsSync5(aside) && !existsSync5(dir)) renameSync2(aside, dir);
|
|
17216
17318
|
rmSync3(fresh, { recursive: true, force: true });
|
|
17217
17319
|
throw err2;
|
|
17218
17320
|
}
|
|
17219
17321
|
rmSync3(aside, { recursive: true, force: true });
|
|
17220
17322
|
}
|
|
17221
17323
|
async function runUpdate(ref, opts = {}) {
|
|
17222
|
-
const
|
|
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 });
|
|
17324
|
+
const { push, done } = createOutput(opts.emit);
|
|
17231
17325
|
const st = opts.style ?? PLAIN;
|
|
17232
17326
|
if (opts.global && !opts.target) {
|
|
17233
|
-
push(
|
|
17327
|
+
push(GLOBAL_NEEDS_TARGET);
|
|
17234
17328
|
return done(2);
|
|
17235
17329
|
}
|
|
17236
17330
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
@@ -17280,19 +17374,14 @@ async function runUpdate(ref, opts = {}) {
|
|
|
17280
17374
|
if (preflight.blocked) {
|
|
17281
17375
|
return done(1);
|
|
17282
17376
|
}
|
|
17283
|
-
|
|
17284
|
-
|
|
17285
|
-
|
|
17286
|
-
|
|
17287
|
-
|
|
17288
|
-
|
|
17289
|
-
|
|
17290
|
-
|
|
17291
|
-
if (!confirmed) {
|
|
17292
|
-
push("", "Update aborted; the installed version was left in place.");
|
|
17293
|
-
return done(2);
|
|
17294
|
-
}
|
|
17295
|
-
}
|
|
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);
|
|
17296
17385
|
const download = await downloadVerified(fetchImpl, apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
17297
17386
|
if (!download.ok) {
|
|
17298
17387
|
push("", `error: ${download.message}`);
|
|
@@ -17304,11 +17393,7 @@ async function runUpdate(ref, opts = {}) {
|
|
|
17304
17393
|
push("", "error: could not replace the install; the installed version was left in place");
|
|
17305
17394
|
return done(2);
|
|
17306
17395
|
}
|
|
17307
|
-
recordReceipt(installed.area.dir, slug,
|
|
17308
|
-
version: preflight.version,
|
|
17309
|
-
sourceHash: preflight.sourceHash,
|
|
17310
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17311
|
-
});
|
|
17396
|
+
recordReceipt(installed.area.dir, slug, receiptFor(preflight));
|
|
17312
17397
|
push(
|
|
17313
17398
|
"",
|
|
17314
17399
|
`Updated ${slug} ${installed.installedVersion ?? "?"} -> ${preflight.version} in ${installed.dir}`,
|
|
@@ -17354,19 +17439,14 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
|
|
|
17354
17439
|
if (preflight.blocked) {
|
|
17355
17440
|
return done(1);
|
|
17356
17441
|
}
|
|
17357
|
-
|
|
17358
|
-
|
|
17359
|
-
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
if (!confirmed) {
|
|
17366
|
-
push("", "Update aborted; the installed version was left in place.");
|
|
17367
|
-
return done(2);
|
|
17368
|
-
}
|
|
17369
|
-
}
|
|
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);
|
|
17370
17450
|
const areaDir = installed.area.dir;
|
|
17371
17451
|
const { installs, skipped } = resolvePackMembers(detail.packMembers ?? [], installed.area.tool);
|
|
17372
17452
|
if (installs.length === 0) {
|
|
@@ -17378,9 +17458,7 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
|
|
|
17378
17458
|
const newNames = new Set(installs.map((m) => m.name));
|
|
17379
17459
|
const toRemove = currentMembers.filter((name) => !newNames.has(name));
|
|
17380
17460
|
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
|
-
);
|
|
17461
|
+
const conflicts = additions.map((m) => join8(areaDir, m.name)).filter(isOccupied);
|
|
17384
17462
|
if (conflicts.length > 0) {
|
|
17385
17463
|
push(
|
|
17386
17464
|
"",
|
|
@@ -17394,7 +17472,7 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
|
|
|
17394
17472
|
push("", `error: ${download.message}`);
|
|
17395
17473
|
return done(2);
|
|
17396
17474
|
}
|
|
17397
|
-
const plans = installs
|
|
17475
|
+
const plans = planMembers(installs, download.files);
|
|
17398
17476
|
const missing = plans.find((p) => p.files.length === 0);
|
|
17399
17477
|
if (missing) {
|
|
17400
17478
|
push("", `error: the snapshot has no files for "${missing.name}"; nothing was changed`);
|
|
@@ -17407,18 +17485,13 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
|
|
|
17407
17485
|
try {
|
|
17408
17486
|
for (const plan of plans) {
|
|
17409
17487
|
const dest = join8(areaDir, plan.name);
|
|
17410
|
-
if (
|
|
17488
|
+
if (existsSync5(dest)) {
|
|
17411
17489
|
swapTree(plan.files, dest);
|
|
17412
17490
|
} else {
|
|
17413
17491
|
writeTree(plan.files, dest);
|
|
17414
17492
|
}
|
|
17415
17493
|
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
|
-
});
|
|
17494
|
+
recordReceipt(areaDir, plan.name, receiptFor(preflight, slug));
|
|
17422
17495
|
}
|
|
17423
17496
|
} catch {
|
|
17424
17497
|
push(
|
|
@@ -17689,10 +17762,17 @@ async function run(argv) {
|
|
|
17689
17762
|
}
|
|
17690
17763
|
return { lines: [`error: unknown command "${args.command}"`, "", USAGE], exitCode: 2 };
|
|
17691
17764
|
}
|
|
17692
|
-
async function main(argv) {
|
|
17693
|
-
|
|
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
|
+
}
|
|
17694
17774
|
if (!result.streamed) {
|
|
17695
|
-
console.log(result.lines.join("\n"));
|
|
17775
|
+
(result.exitCode === 2 ? console.error : console.log)(result.lines.join("\n"));
|
|
17696
17776
|
}
|
|
17697
17777
|
process.exitCode = result.exitCode;
|
|
17698
17778
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillpass",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"author": "Brad Traversy",
|
|
22
22
|
"type": "module",
|
|
23
23
|
"bin": {
|
|
24
|
-
"skillpass": "./bin/skillpass.
|
|
24
|
+
"skillpass": "./bin/skillpass.js"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"bin",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"esbuild": "^0.28.1",
|
|
37
37
|
"fflate": "^0.8.3",
|
|
38
38
|
"typescript": "^6.0.3",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
39
|
+
"skill-schema": "0.1.0",
|
|
40
|
+
"validator": "0.3.0"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
|
-
"build": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.
|
|
43
|
+
"build": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --log-level=warning",
|
|
44
44
|
"typecheck": "tsc --noEmit"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/bin/skillpass.mjs
DELETED