skillpass 0.2.0 → 0.4.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/LICENSE +21 -0
- package/README.md +15 -5
- package/bin/skillpass.js +2 -0
- package/dist/{cli.mjs → cli.js} +723 -580
- package/package.json +5 -5
- package/bin/skillpass.mjs +0 -2
package/dist/{cli.mjs → cli.js}
RENAMED
|
@@ -492,16 +492,7 @@ function unzipSync(data, opts) {
|
|
|
492
492
|
}
|
|
493
493
|
|
|
494
494
|
// src/add.ts
|
|
495
|
-
import {
|
|
496
|
-
existsSync,
|
|
497
|
-
mkdirSync as mkdirSync2,
|
|
498
|
-
readdirSync as readdirSync2,
|
|
499
|
-
renameSync,
|
|
500
|
-
rmdirSync,
|
|
501
|
-
rmSync,
|
|
502
|
-
statSync as statSync2,
|
|
503
|
-
writeFileSync as writeFileSync2
|
|
504
|
-
} from "node:fs";
|
|
495
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync3, renameSync, rmdirSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
505
496
|
import { dirname, isAbsolute, join as join4, resolve as resolve2 } from "node:path";
|
|
506
497
|
|
|
507
498
|
// ../validator/src/load.ts
|
|
@@ -15281,10 +15272,10 @@ function parseManifest(input) {
|
|
|
15281
15272
|
}
|
|
15282
15273
|
|
|
15283
15274
|
// ../skill-schema/src/report.ts
|
|
15284
|
-
var reportFindingSchema = external_exports.
|
|
15275
|
+
var reportFindingSchema = external_exports.object({
|
|
15285
15276
|
code: external_exports.string().min(1),
|
|
15286
15277
|
message: external_exports.string().min(1),
|
|
15287
|
-
location: external_exports.
|
|
15278
|
+
location: external_exports.object({
|
|
15288
15279
|
path: external_exports.string().min(1),
|
|
15289
15280
|
line: external_exports.number().int().positive().optional(),
|
|
15290
15281
|
snippet: external_exports.string().optional()
|
|
@@ -15313,11 +15304,11 @@ var validationReportSchema = external_exports.strictObject({
|
|
|
15313
15304
|
});
|
|
15314
15305
|
|
|
15315
15306
|
// ../skill-schema/src/passport.ts
|
|
15316
|
-
var skillPassportSchema = external_exports.
|
|
15307
|
+
var skillPassportSchema = external_exports.object({
|
|
15317
15308
|
schemaVersion: external_exports.literal("0.1"),
|
|
15318
15309
|
validationStatus: validationStatusSchema,
|
|
15319
15310
|
riskLevel: riskLevelSchema,
|
|
15320
|
-
permissionsSummary: external_exports.
|
|
15311
|
+
permissionsSummary: external_exports.object({
|
|
15321
15312
|
declared: external_exports.array(permissionKeySchema),
|
|
15322
15313
|
detected: external_exports.array(permissionKeySchema)
|
|
15323
15314
|
}),
|
|
@@ -15338,7 +15329,7 @@ var skillPassportSchema = external_exports.strictObject({
|
|
|
15338
15329
|
|
|
15339
15330
|
// ../skill-schema/src/ai-review.ts
|
|
15340
15331
|
var aiReviewVerdictSchema = external_exports.enum(["clear", "caution", "concern"]);
|
|
15341
|
-
var aiReviewSchema = external_exports.
|
|
15332
|
+
var aiReviewSchema = external_exports.object({
|
|
15342
15333
|
summary: external_exports.string().min(1).max(600),
|
|
15343
15334
|
verdict: aiReviewVerdictSchema,
|
|
15344
15335
|
reasoning: external_exports.string().min(1).max(800),
|
|
@@ -15348,14 +15339,7 @@ var aiReviewSchema = external_exports.strictObject({
|
|
|
15348
15339
|
|
|
15349
15340
|
// ../skill-schema/src/submission.ts
|
|
15350
15341
|
var SUBMISSION_SOURCE_TYPES = ["github_url", "zip"];
|
|
15351
|
-
var SUBMISSION_STATUSES = [
|
|
15352
|
-
"draft",
|
|
15353
|
-
"validating",
|
|
15354
|
-
"passed",
|
|
15355
|
-
"warning",
|
|
15356
|
-
"failed",
|
|
15357
|
-
"published"
|
|
15358
|
-
];
|
|
15342
|
+
var SUBMISSION_STATUSES = ["draft", "validating", "passed", "warning", "failed", "published"];
|
|
15359
15343
|
var submissionSourceTypeSchema = external_exports.enum(SUBMISSION_SOURCE_TYPES);
|
|
15360
15344
|
var submissionStatusSchema = external_exports.enum(SUBMISSION_STATUSES);
|
|
15361
15345
|
var MAX_ZIP_BYTES = 10 * 1024 * 1024;
|
|
@@ -15390,9 +15374,14 @@ var publishResultSchema = external_exports.object({
|
|
|
15390
15374
|
slug: external_exports.string().min(1),
|
|
15391
15375
|
version: external_exports.string().min(1)
|
|
15392
15376
|
});
|
|
15377
|
+
var MAX_SLUG_LENGTH = 60;
|
|
15378
|
+
function slugForSkill(name) {
|
|
15379
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SLUG_LENGTH).replace(/-+$/, "");
|
|
15380
|
+
return slug || "skill";
|
|
15381
|
+
}
|
|
15393
15382
|
|
|
15394
15383
|
// ../skill-schema/src/public.ts
|
|
15395
|
-
var publicSkillSummarySchema = external_exports.
|
|
15384
|
+
var publicSkillSummarySchema = external_exports.object({
|
|
15396
15385
|
slug: external_exports.string().min(1),
|
|
15397
15386
|
name: external_exports.string().min(1),
|
|
15398
15387
|
summary: external_exports.string().min(1),
|
|
@@ -15422,7 +15411,7 @@ var publicSkillSummarySchema = external_exports.strictObject({
|
|
|
15422
15411
|
publishedAt: external_exports.iso.datetime()
|
|
15423
15412
|
});
|
|
15424
15413
|
var publicSkillListSchema = external_exports.array(publicSkillSummarySchema);
|
|
15425
|
-
var publicSkillVersionSchema = external_exports.
|
|
15414
|
+
var publicSkillVersionSchema = external_exports.object({
|
|
15426
15415
|
version: external_exports.string().min(1),
|
|
15427
15416
|
validationStatus: validationStatusSchema,
|
|
15428
15417
|
riskLevel: riskLevelSchema,
|
|
@@ -15431,10 +15420,10 @@ var publicSkillVersionSchema = external_exports.strictObject({
|
|
|
15431
15420
|
var publicSkillDetailSchema = publicSkillSummarySchema.extend({
|
|
15432
15421
|
// Full member entries (incl. per-target variants) for a pack; the CLI's
|
|
15433
15422
|
// per-target install resolution reads exactly this. Null for single skills.
|
|
15434
|
-
packMembers: external_exports.array(skillEntrySchema).nullable().optional(),
|
|
15423
|
+
packMembers: external_exports.array(external_exports.object(skillEntrySchema.shape)).nullable().optional(),
|
|
15435
15424
|
githubRepoUrl: external_exports.string().min(1).nullable(),
|
|
15436
15425
|
passport: skillPassportSchema,
|
|
15437
|
-
maintainerInfo: external_exports.
|
|
15426
|
+
maintainerInfo: external_exports.object({
|
|
15438
15427
|
username: external_exports.string().min(1),
|
|
15439
15428
|
displayName: external_exports.string().min(1),
|
|
15440
15429
|
avatarUrl: external_exports.string().min(1)
|
|
@@ -15443,18 +15432,18 @@ var publicSkillDetailSchema = publicSkillSummarySchema.extend({
|
|
|
15443
15432
|
// The cached AI review for this version's source hash; null until generated.
|
|
15444
15433
|
aiReview: aiReviewSchema.nullable()
|
|
15445
15434
|
});
|
|
15446
|
-
var publicSkillSourceSchema = external_exports.
|
|
15435
|
+
var publicSkillSourceSchema = external_exports.object({
|
|
15447
15436
|
version: external_exports.string().min(1),
|
|
15448
15437
|
sourceHash: external_exports.string().min(1),
|
|
15449
|
-
files: external_exports.array(external_exports.
|
|
15438
|
+
files: external_exports.array(external_exports.object({ path: external_exports.string().min(1), content: external_exports.string() }))
|
|
15450
15439
|
});
|
|
15451
15440
|
|
|
15452
15441
|
// ../skill-schema/src/preflight.ts
|
|
15453
|
-
var permissionSetDiffSchema = external_exports.
|
|
15442
|
+
var permissionSetDiffSchema = external_exports.object({
|
|
15454
15443
|
added: external_exports.array(permissionKeySchema),
|
|
15455
15444
|
removed: external_exports.array(permissionKeySchema)
|
|
15456
15445
|
});
|
|
15457
|
-
var publicPreflightSchema = external_exports.
|
|
15446
|
+
var publicPreflightSchema = external_exports.object({
|
|
15458
15447
|
version: external_exports.string().min(1),
|
|
15459
15448
|
validationStatus: validationStatusSchema,
|
|
15460
15449
|
riskLevel: riskLevelSchema,
|
|
@@ -15462,11 +15451,11 @@ var publicPreflightSchema = external_exports.strictObject({
|
|
|
15462
15451
|
sourceVerified: external_exports.boolean(),
|
|
15463
15452
|
resolvedCommitSha: external_exports.string().min(1).nullable(),
|
|
15464
15453
|
generatedAt: external_exports.iso.datetime(),
|
|
15465
|
-
permissions: external_exports.
|
|
15454
|
+
permissions: external_exports.object({
|
|
15466
15455
|
declared: external_exports.array(permissionKeySchema),
|
|
15467
15456
|
detected: external_exports.array(permissionKeySchema)
|
|
15468
15457
|
}),
|
|
15469
|
-
diff: external_exports.
|
|
15458
|
+
diff: external_exports.object({
|
|
15470
15459
|
previousVersion: external_exports.string().min(1),
|
|
15471
15460
|
declared: permissionSetDiffSchema,
|
|
15472
15461
|
detected: permissionSetDiffSchema
|
|
@@ -15546,6 +15535,39 @@ var resolveReportInputSchema = external_exports.strictObject({
|
|
|
15546
15535
|
status: external_exports.enum(["reviewed", "actioned"])
|
|
15547
15536
|
});
|
|
15548
15537
|
|
|
15538
|
+
// ../skill-schema/src/maintainer.ts
|
|
15539
|
+
var SKILL_STATUSES = ["published", "draft", "private", "flagged"];
|
|
15540
|
+
var skillStatusSchema = external_exports.enum(SKILL_STATUSES);
|
|
15541
|
+
var maintainerSkillSchema = external_exports.strictObject({
|
|
15542
|
+
slug: external_exports.string().min(1),
|
|
15543
|
+
name: external_exports.string().min(1),
|
|
15544
|
+
status: skillStatusSchema,
|
|
15545
|
+
version: external_exports.string().nullable(),
|
|
15546
|
+
validationStatus: validationStatusSchema.nullable(),
|
|
15547
|
+
riskLevel: riskLevelSchema.nullable(),
|
|
15548
|
+
updatedAt: external_exports.iso.datetime()
|
|
15549
|
+
});
|
|
15550
|
+
var maintainerReportSchema = external_exports.strictObject({
|
|
15551
|
+
id: external_exports.number().int().positive(),
|
|
15552
|
+
skill: external_exports.strictObject({ slug: external_exports.string(), name: external_exports.string() }),
|
|
15553
|
+
reason: external_exports.string(),
|
|
15554
|
+
status: abuseReportStatusSchema,
|
|
15555
|
+
createdAt: external_exports.iso.datetime()
|
|
15556
|
+
});
|
|
15557
|
+
|
|
15558
|
+
// ../skill-schema/src/user.ts
|
|
15559
|
+
var USER_ROLES = ["user", "maintainer", "admin"];
|
|
15560
|
+
var userRoleSchema = external_exports.enum(USER_ROLES);
|
|
15561
|
+
var publicUserSchema = external_exports.object({
|
|
15562
|
+
id: external_exports.number().int().positive(),
|
|
15563
|
+
username: external_exports.string().min(1),
|
|
15564
|
+
displayName: external_exports.string().min(1),
|
|
15565
|
+
avatarUrl: external_exports.string().min(1),
|
|
15566
|
+
role: userRoleSchema,
|
|
15567
|
+
reputation: external_exports.number().int(),
|
|
15568
|
+
createdAt: external_exports.iso.datetime()
|
|
15569
|
+
});
|
|
15570
|
+
|
|
15549
15571
|
// ../validator/src/rules/permissions.ts
|
|
15550
15572
|
var PERMISSION_SIGNALS = [
|
|
15551
15573
|
{
|
|
@@ -15600,24 +15622,30 @@ var PERMISSION_SIGNALS = [
|
|
|
15600
15622
|
}
|
|
15601
15623
|
];
|
|
15602
15624
|
function detectPermissions(files) {
|
|
15603
|
-
const detected = /* @__PURE__ */ new
|
|
15625
|
+
const detected = /* @__PURE__ */ new Set();
|
|
15604
15626
|
for (const file2 of files) {
|
|
15605
|
-
file2.content.split("\n")
|
|
15627
|
+
for (const line of file2.content.split("\n")) {
|
|
15606
15628
|
for (const signal of PERMISSION_SIGNALS) {
|
|
15607
15629
|
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
|
-
});
|
|
15630
|
+
detected.add(signal.permission);
|
|
15613
15631
|
}
|
|
15614
15632
|
}
|
|
15615
|
-
}
|
|
15633
|
+
}
|
|
15616
15634
|
}
|
|
15617
|
-
return [...detected
|
|
15635
|
+
return [...detected];
|
|
15618
15636
|
}
|
|
15619
15637
|
|
|
15620
15638
|
// ../validator/src/load.ts
|
|
15639
|
+
var NUL_SCAN_BYTES = 8e3;
|
|
15640
|
+
function isBinary(bytes) {
|
|
15641
|
+
if (bytes.subarray(0, NUL_SCAN_BYTES).includes(0)) return true;
|
|
15642
|
+
try {
|
|
15643
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
15644
|
+
return false;
|
|
15645
|
+
} catch {
|
|
15646
|
+
return true;
|
|
15647
|
+
}
|
|
15648
|
+
}
|
|
15621
15649
|
var PackageReadError = class extends Error {
|
|
15622
15650
|
constructor(dir, cause) {
|
|
15623
15651
|
super(`cannot read skill package at "${dir}"`, { cause });
|
|
@@ -15626,19 +15654,23 @@ var PackageReadError = class extends Error {
|
|
|
15626
15654
|
};
|
|
15627
15655
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
15628
15656
|
function walk(root, rel = "") {
|
|
15629
|
-
const
|
|
15657
|
+
const tree = { files: [], binaries: [] };
|
|
15630
15658
|
for (const name of readdirSync(join(root, rel)).sort()) {
|
|
15631
15659
|
const relPath = rel === "" ? name : `${rel}/${name}`;
|
|
15632
15660
|
const stats = statSync(join(root, relPath));
|
|
15633
15661
|
if (stats.isDirectory()) {
|
|
15634
15662
|
if (!SKIP_DIRS.has(name)) {
|
|
15635
|
-
|
|
15663
|
+
const sub = walk(root, relPath);
|
|
15664
|
+
tree.files.push(...sub.files);
|
|
15665
|
+
tree.binaries.push(...sub.binaries);
|
|
15636
15666
|
}
|
|
15637
15667
|
} else {
|
|
15638
|
-
|
|
15668
|
+
const bytes = readFileSync(join(root, relPath));
|
|
15669
|
+
if (isBinary(bytes)) tree.binaries.push(relPath);
|
|
15670
|
+
else tree.files.push({ path: relPath, content: bytes.toString("utf8") });
|
|
15639
15671
|
}
|
|
15640
15672
|
}
|
|
15641
|
-
return
|
|
15673
|
+
return tree;
|
|
15642
15674
|
}
|
|
15643
15675
|
function hashFiles(files) {
|
|
15644
15676
|
const hash2 = createHash("sha256");
|
|
@@ -15665,9 +15697,16 @@ function readManifest(files) {
|
|
|
15665
15697
|
if (!result.success) {
|
|
15666
15698
|
return { state: "invalid", error: result.error };
|
|
15667
15699
|
}
|
|
15668
|
-
return { state: "ok", data: result.data
|
|
15700
|
+
return { state: "ok", data: result.data };
|
|
15669
15701
|
}
|
|
15670
15702
|
var FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/;
|
|
15703
|
+
function readDeclaredVersion(frontmatter) {
|
|
15704
|
+
const top = /^version:\s*["']?([^"'\s]+)["']?\s*$/m.exec(frontmatter);
|
|
15705
|
+
if (top) return top[1];
|
|
15706
|
+
const block = /^metadata:\s*\n((?:[ \t]+\S.*(?:\n|$))*)/m.exec(frontmatter);
|
|
15707
|
+
const nested = block && /^[ \t]+version:\s*["']?([^"'\s]+)["']?\s*$/m.exec(block[1]);
|
|
15708
|
+
return nested ? nested[1] : void 0;
|
|
15709
|
+
}
|
|
15671
15710
|
function readSkillMeta(content) {
|
|
15672
15711
|
const out = {};
|
|
15673
15712
|
const fm = FRONTMATTER_RE.exec(content);
|
|
@@ -15690,19 +15729,45 @@ function readSkillMeta(content) {
|
|
|
15690
15729
|
const text = block[1] === ">" ? gathered.join(" ").replace(/\s+/g, " ").trim() : gathered.join("\n").trim();
|
|
15691
15730
|
if (text) out[key] = text;
|
|
15692
15731
|
} else {
|
|
15693
|
-
|
|
15732
|
+
const parts = [kv[2]];
|
|
15733
|
+
for (let j = i2 + 1; j < lines.length && /^\s+\S/.test(lines[j]); j++) parts.push(lines[j].trim());
|
|
15734
|
+
out[key] = parts.join(" ").replace(/^["']|["']$/g, "");
|
|
15694
15735
|
}
|
|
15695
15736
|
}
|
|
15737
|
+
const version2 = readDeclaredVersion(fm[1]);
|
|
15738
|
+
if (version2) out.version = version2;
|
|
15696
15739
|
}
|
|
15697
15740
|
if (!out.description) {
|
|
15698
|
-
const prose = body
|
|
15699
|
-
if (prose) out.description = prose
|
|
15741
|
+
const prose = firstParagraph(body);
|
|
15742
|
+
if (prose) out.description = prose;
|
|
15700
15743
|
}
|
|
15701
15744
|
return out;
|
|
15702
15745
|
}
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15746
|
+
var HTML_TAG_RE = /<\/?[a-zA-Z][^>]*>/g;
|
|
15747
|
+
var MD_LINK_RE = /\[([^\]]*)\]\([^)]*\)/g;
|
|
15748
|
+
var STRUCTURAL_LINE_RE = /^(#|!\[|\[!\[|---$|\*\*\*$|___$)|^<h[1-6][\s>]/i;
|
|
15749
|
+
var DESCRIPTION_MAX = 200;
|
|
15750
|
+
var cleanInline = (line) => line.replace(HTML_TAG_RE, " ").replace(MD_LINK_RE, "$1").replace(/^>\s?/, "").replace(/\s+/g, " ").trim();
|
|
15751
|
+
function clip(text, max2) {
|
|
15752
|
+
if (text.length <= max2) return text;
|
|
15753
|
+
const head = text.slice(0, max2);
|
|
15754
|
+
const sentenceEnd = Math.max(head.lastIndexOf(". "), head.lastIndexOf("! "), head.lastIndexOf("? "));
|
|
15755
|
+
if (sentenceEnd >= 60) return head.slice(0, sentenceEnd + 1);
|
|
15756
|
+
const wordEnd = head.lastIndexOf(" ");
|
|
15757
|
+
return (wordEnd > 0 ? head.slice(0, wordEnd) : head).replace(/[,;:]+$/, "").trim();
|
|
15758
|
+
}
|
|
15759
|
+
function firstParagraph(text) {
|
|
15760
|
+
const kept = [];
|
|
15761
|
+
for (const raw of text.split("\n")) {
|
|
15762
|
+
const source = raw.trim();
|
|
15763
|
+
const line = STRUCTURAL_LINE_RE.test(source) ? "" : cleanInline(source);
|
|
15764
|
+
if (!line) {
|
|
15765
|
+
if (kept.length > 0) break;
|
|
15766
|
+
continue;
|
|
15767
|
+
}
|
|
15768
|
+
kept.push(line);
|
|
15769
|
+
}
|
|
15770
|
+
return kept.length > 0 ? clip(kept.join(" "), DESCRIPTION_MAX) : void 0;
|
|
15706
15771
|
}
|
|
15707
15772
|
function inferTargets(files) {
|
|
15708
15773
|
const targets = /* @__PURE__ */ new Set();
|
|
@@ -15715,11 +15780,12 @@ function inferTargets(files) {
|
|
|
15715
15780
|
function inferManifest(files, fallbackName) {
|
|
15716
15781
|
const skillFile = files.find((f) => f.path === "SKILL.md");
|
|
15717
15782
|
const meta3 = skillFile ? readSkillMeta(skillFile.content) : {};
|
|
15718
|
-
const name =
|
|
15719
|
-
const permissions =
|
|
15783
|
+
const name = slugForSkill(meta3.name ?? fallbackName);
|
|
15784
|
+
const permissions = detectPermissions(files);
|
|
15720
15785
|
return {
|
|
15721
15786
|
schemaVersion: "0.1",
|
|
15722
15787
|
name,
|
|
15788
|
+
...meta3.version && SEMVER_RE.test(meta3.version) ? { version: meta3.version } : {},
|
|
15723
15789
|
description: meta3.description ?? name,
|
|
15724
15790
|
targets: inferTargets(files),
|
|
15725
15791
|
permissions,
|
|
@@ -15751,16 +15817,10 @@ function collectMemberFiles(files) {
|
|
|
15751
15817
|
}
|
|
15752
15818
|
function readmeProse(files) {
|
|
15753
15819
|
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);
|
|
15820
|
+
return readme ? firstParagraph(readme.content) : void 0;
|
|
15759
15821
|
}
|
|
15760
15822
|
function inferNestedManifest(files, fallbackName) {
|
|
15761
|
-
const members = collectMemberFiles(files).sort(
|
|
15762
|
-
(a, b) => a.folder < b.folder ? -1 : a.folder > b.folder ? 1 : 0
|
|
15763
|
-
);
|
|
15823
|
+
const members = collectMemberFiles(files).sort((a, b) => a.folder < b.folder ? -1 : a.folder > b.folder ? 1 : 0);
|
|
15764
15824
|
if (members.length === 0) {
|
|
15765
15825
|
return null;
|
|
15766
15826
|
}
|
|
@@ -15770,13 +15830,10 @@ function inferNestedManifest(files, fallbackName) {
|
|
|
15770
15830
|
const entryPath = member.claudePath ?? member.agentsPath ?? member.plainPath;
|
|
15771
15831
|
const file2 = files.find((f) => f.path === entryPath);
|
|
15772
15832
|
const meta3 = readSkillMeta(file2.content);
|
|
15773
|
-
let entryName =
|
|
15774
|
-
if (used.has(entryName)) entryName =
|
|
15833
|
+
let entryName = slugForSkill(meta3.name ?? member.folder);
|
|
15834
|
+
if (used.has(entryName)) entryName = slugForSkill(member.folder);
|
|
15775
15835
|
used.add(entryName);
|
|
15776
|
-
const targets = member.plainPath ? inferTargets(files) : [
|
|
15777
|
-
...member.claudePath ? ["claude-code"] : [],
|
|
15778
|
-
...member.agentsPath ? ["codex"] : []
|
|
15779
|
-
];
|
|
15836
|
+
const targets = member.plainPath ? inferTargets(files) : [...member.claudePath ? ["claude-code"] : [], ...member.agentsPath ? ["codex"] : []];
|
|
15780
15837
|
for (const target of targets) packTargets.add(target);
|
|
15781
15838
|
return {
|
|
15782
15839
|
name: entryName,
|
|
@@ -15787,10 +15844,10 @@ function inferNestedManifest(files, fallbackName) {
|
|
|
15787
15844
|
};
|
|
15788
15845
|
});
|
|
15789
15846
|
const single = entries.length === 1 ? entries[0] : null;
|
|
15790
|
-
const permissions =
|
|
15847
|
+
const permissions = detectPermissions(files);
|
|
15791
15848
|
return {
|
|
15792
15849
|
schemaVersion: "0.1",
|
|
15793
|
-
name: single ? single.name :
|
|
15850
|
+
name: single ? single.name : slugForSkill(fallbackName),
|
|
15794
15851
|
description: single ? single.description ?? single.name : readmeProse(files) ?? `A pack of ${entries.length} skills`,
|
|
15795
15852
|
targets: [...packTargets],
|
|
15796
15853
|
permissions,
|
|
@@ -15820,82 +15877,33 @@ function resolveEntries(dir, manifest, files) {
|
|
|
15820
15877
|
]);
|
|
15821
15878
|
}
|
|
15822
15879
|
var byPath = (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
|
|
15823
|
-
function loadPackageFromFiles(files, name = "package") {
|
|
15880
|
+
function loadPackageFromFiles(files, name = "package", binaries = []) {
|
|
15824
15881
|
const sorted = [...files].sort(byPath);
|
|
15825
15882
|
let manifest = readManifest(sorted);
|
|
15826
15883
|
if (manifest.state === "missing") {
|
|
15827
15884
|
const data = sorted.some((f) => f.path === "SKILL.md") ? inferManifest(sorted, name) : inferNestedManifest(sorted, name);
|
|
15828
15885
|
if (data) {
|
|
15829
|
-
manifest = { state: "ok", inferred: true, data
|
|
15886
|
+
manifest = { state: "ok", inferred: true, data };
|
|
15830
15887
|
}
|
|
15831
15888
|
}
|
|
15832
15889
|
return {
|
|
15833
|
-
dir: name,
|
|
15834
15890
|
files: sorted,
|
|
15835
15891
|
manifest,
|
|
15836
15892
|
entries: resolveEntries(name, manifest, sorted),
|
|
15837
|
-
sourceHash: hashFiles(sorted)
|
|
15893
|
+
sourceHash: hashFiles(sorted),
|
|
15894
|
+
binaries
|
|
15838
15895
|
};
|
|
15839
15896
|
}
|
|
15840
15897
|
function loadPackage(dir) {
|
|
15841
|
-
let
|
|
15898
|
+
let tree;
|
|
15842
15899
|
try {
|
|
15843
|
-
|
|
15900
|
+
tree = walk(dir);
|
|
15844
15901
|
} catch (err2) {
|
|
15845
15902
|
throw new PackageReadError(dir, err2);
|
|
15846
15903
|
}
|
|
15847
|
-
return loadPackageFromFiles(files, dir);
|
|
15904
|
+
return loadPackageFromFiles(tree.files, dir, tree.binaries);
|
|
15848
15905
|
}
|
|
15849
15906
|
|
|
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
15907
|
// ../validator/src/rules/content.ts
|
|
15900
15908
|
var SECRET_PATTERNS = [
|
|
15901
15909
|
{
|
|
@@ -16012,10 +16020,12 @@ var CREDENTIAL_PATTERNS = [
|
|
|
16012
16020
|
},
|
|
16013
16021
|
{
|
|
16014
16022
|
code: "credential-harvesting",
|
|
16015
|
-
// The
|
|
16016
|
-
//
|
|
16023
|
+
// The verb has to sit where an instruction puts it (line or sentence
|
|
16024
|
+
// start, or after then/and/to/should/must): narrative like "timing leaks
|
|
16025
|
+
// secret bits" has a noun subject and does not fire. The ::/. lookbehinds
|
|
16026
|
+
// skip code identifiers (`Box::leak(secret)`, `.leak(`).
|
|
16017
16027
|
pattern: new RegExp(
|
|
16018
|
-
String.raw`(?<!\b(?:never|not|don'?t|avoids?|prevents?|stops?)\s)(?<!::)(?<!\.)
|
|
16028
|
+
String.raw`(?:^[-*\d.)\s]*|[.:;!?]\s+|\b(?:then|and|or|to|should|must|now|please)\s+)(?<!\b(?:never|not|don'?t|avoids?|prevents?|stops?)\s)(?<!::)(?<!\.)(?:exfiltrate|steal|harvest|dump|leak)s?\b[^.\n]{0,60}?${SECRET_NOUN}`,
|
|
16019
16029
|
"i"
|
|
16020
16030
|
),
|
|
16021
16031
|
message: "matches a credential-exfiltration phrase"
|
|
@@ -16029,36 +16039,143 @@ var CREDENTIAL_PATTERNS = [
|
|
|
16029
16039
|
message: "matches a credential-file network-transmission phrase"
|
|
16030
16040
|
}
|
|
16031
16041
|
];
|
|
16032
|
-
var advisory = (rows) => rows.map((row2) => ({ ...row2, severity: "warning" }));
|
|
16042
|
+
var advisory = (rows, mentionAware = false) => rows.map((row2) => ({ ...row2, severity: "warning", mentionAware }));
|
|
16033
16043
|
var ALL_ROWS = [
|
|
16034
16044
|
...SECRET_PATTERNS,
|
|
16035
|
-
...advisory(INJECTION_PATTERNS),
|
|
16045
|
+
...advisory(INJECTION_PATTERNS, true),
|
|
16036
16046
|
...advisory(DANGEROUS_PATTERNS),
|
|
16037
16047
|
...advisory(MALWARE_PATTERNS),
|
|
16038
|
-
...advisory(CREDENTIAL_PATTERNS)
|
|
16048
|
+
...advisory(CREDENTIAL_PATTERNS, true)
|
|
16039
16049
|
];
|
|
16050
|
+
var MENTION_LEAD_RE = /\b(?:flags?|flagged|detects?|detecting|look(?:ing)?\s+for|watch\s+for|check(?:ing)?\s+for|signs?\s+of|indicators?\s+of|evidence\s+of|red\s+flags?|examples?|e\.g\.|such\s+as|attempts?\s+to|designed\s+to|(?:code|scripts?|files?|comments?|text|instructions?)\s+that)\b/i;
|
|
16051
|
+
var TRANSMIT_VERB_RE = /\b(?:post|send|curl|transmit|exfiltrate)s?\b/i;
|
|
16052
|
+
function matchAnchor(line, match) {
|
|
16053
|
+
if (match[0]) return match.index;
|
|
16054
|
+
const verb = line.search(TRANSMIT_VERB_RE);
|
|
16055
|
+
return verb < 0 ? line.length : verb;
|
|
16056
|
+
}
|
|
16057
|
+
function isMention(line, anchor, inFence) {
|
|
16058
|
+
if (inFence || line.trimStart().startsWith("|")) return true;
|
|
16059
|
+
const before = line.slice(0, anchor);
|
|
16060
|
+
const inside = (mark) => (before.split(mark).length - 1) % 2 === 1;
|
|
16061
|
+
return inside('"') || inside("`") || MENTION_LEAD_RE.test(before);
|
|
16062
|
+
}
|
|
16063
|
+
var FENCE_RE = /^\s*(?:```|~~~)/;
|
|
16064
|
+
var DOCUMENTED_PLACEHOLDERS = ["AKIAIOSFODNN7EXAMPLE", "AKIAI44QH8DHBEXAMPLE"];
|
|
16065
|
+
var withoutPlaceholders = (line) => DOCUMENTED_PLACEHOLDERS.reduce((l, p) => l.replaceAll(p, " "), line);
|
|
16066
|
+
var REDACTIONS = ALL_ROWS.filter((row2) => row2.redact).map(
|
|
16067
|
+
(row2) => new RegExp(row2.pattern.source, `${row2.pattern.flags}g`)
|
|
16068
|
+
);
|
|
16069
|
+
var redactLine = (line) => REDACTIONS.reduce((l, re) => l.replace(re, "[redacted]"), line);
|
|
16040
16070
|
var contentRule = (pkg) => {
|
|
16041
16071
|
const findings = [];
|
|
16042
16072
|
for (const file2 of pkg.files) {
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
severity: row2.severity ?? "failure",
|
|
16049
|
-
code: row2.code,
|
|
16050
|
-
message: row2.message,
|
|
16051
|
-
location: { path: file2.path, line: i2 + 1, snippet }
|
|
16052
|
-
});
|
|
16053
|
-
}
|
|
16073
|
+
let inFence = false;
|
|
16074
|
+
file2.content.split("\n").forEach((rawLine, i2) => {
|
|
16075
|
+
if (FENCE_RE.test(rawLine)) {
|
|
16076
|
+
inFence = !inFence;
|
|
16077
|
+
return;
|
|
16054
16078
|
}
|
|
16079
|
+
const line = withoutPlaceholders(rawLine);
|
|
16080
|
+
const hits = ALL_ROWS.filter((row2) => {
|
|
16081
|
+
const match = row2.pattern.exec(line);
|
|
16082
|
+
return match !== null && !(row2.mentionAware && isMention(line, matchAnchor(line, match), inFence));
|
|
16083
|
+
});
|
|
16084
|
+
if (hits.length === 0) return;
|
|
16085
|
+
const snippet = redactLine(line).trim();
|
|
16086
|
+
for (const row2 of hits) {
|
|
16087
|
+
findings.push({
|
|
16088
|
+
severity: row2.severity ?? "failure",
|
|
16089
|
+
code: row2.code,
|
|
16090
|
+
message: row2.message,
|
|
16091
|
+
location: { path: file2.path, line: i2 + 1, snippet }
|
|
16092
|
+
});
|
|
16093
|
+
}
|
|
16094
|
+
});
|
|
16095
|
+
}
|
|
16096
|
+
return findings;
|
|
16097
|
+
};
|
|
16098
|
+
|
|
16099
|
+
// ../validator/src/rules/structure.ts
|
|
16100
|
+
var structureRule = (pkg) => {
|
|
16101
|
+
if (pkg.files.length === 0) {
|
|
16102
|
+
return [
|
|
16103
|
+
{
|
|
16104
|
+
severity: "failure",
|
|
16105
|
+
code: "empty-package",
|
|
16106
|
+
message: "the package contains no files"
|
|
16107
|
+
}
|
|
16108
|
+
];
|
|
16109
|
+
}
|
|
16110
|
+
const findings = [];
|
|
16111
|
+
if (pkg.manifest.state === "missing") {
|
|
16112
|
+
findings.push({
|
|
16113
|
+
severity: "warning",
|
|
16114
|
+
code: "missing-manifest",
|
|
16115
|
+
message: "no skill.json manifest; permissions and targets are undeclared"
|
|
16116
|
+
});
|
|
16117
|
+
} else if (pkg.manifest.state === "invalid") {
|
|
16118
|
+
findings.push({
|
|
16119
|
+
severity: "failure",
|
|
16120
|
+
code: "invalid-manifest",
|
|
16121
|
+
message: `skill.json is invalid: ${pkg.manifest.error}`,
|
|
16122
|
+
location: { path: "skill.json" }
|
|
16055
16123
|
});
|
|
16056
16124
|
}
|
|
16125
|
+
for (const path of pkg.binaries) {
|
|
16126
|
+
findings.push({
|
|
16127
|
+
severity: "warning",
|
|
16128
|
+
code: "binary-dropped",
|
|
16129
|
+
message: `"${path}" is not a text file and was left out of the snapshot; installs will not include it`,
|
|
16130
|
+
location: { path }
|
|
16131
|
+
});
|
|
16132
|
+
}
|
|
16133
|
+
for (const entry of pkg.entries) {
|
|
16134
|
+
if (!entry.exists) {
|
|
16135
|
+
findings.push({
|
|
16136
|
+
severity: "failure",
|
|
16137
|
+
code: "missing-skill-file",
|
|
16138
|
+
message: `skill "${entry.skillName}" references "${entry.path}", which does not exist`,
|
|
16139
|
+
location: { path: entry.path }
|
|
16140
|
+
});
|
|
16141
|
+
} else {
|
|
16142
|
+
const file2 = pkg.files.find((f) => f.path === entry.path);
|
|
16143
|
+
if (file2 !== void 0 && file2.content.trim() === "") {
|
|
16144
|
+
findings.push({
|
|
16145
|
+
severity: "failure",
|
|
16146
|
+
code: "missing-skill-file",
|
|
16147
|
+
message: `skill "${entry.skillName}" entry "${entry.path}" is empty`,
|
|
16148
|
+
location: { path: entry.path }
|
|
16149
|
+
});
|
|
16150
|
+
}
|
|
16151
|
+
}
|
|
16152
|
+
}
|
|
16057
16153
|
return findings;
|
|
16058
16154
|
};
|
|
16059
16155
|
|
|
16156
|
+
// ../validator/package.json
|
|
16157
|
+
var package_default = {
|
|
16158
|
+
name: "validator",
|
|
16159
|
+
version: "0.3.0",
|
|
16160
|
+
private: true,
|
|
16161
|
+
type: "module",
|
|
16162
|
+
exports: {
|
|
16163
|
+
".": "./src/index.ts"
|
|
16164
|
+
},
|
|
16165
|
+
scripts: {
|
|
16166
|
+
typecheck: "tsc --noEmit"
|
|
16167
|
+
},
|
|
16168
|
+
dependencies: {
|
|
16169
|
+
"skill-schema": "workspace:*"
|
|
16170
|
+
},
|
|
16171
|
+
devDependencies: {
|
|
16172
|
+
"@types/node": "^22.0.0",
|
|
16173
|
+
typescript: "^6.0.3"
|
|
16174
|
+
}
|
|
16175
|
+
};
|
|
16176
|
+
|
|
16060
16177
|
// ../validator/src/validate.ts
|
|
16061
|
-
var ENGINE_VERSION =
|
|
16178
|
+
var ENGINE_VERSION = package_default.version;
|
|
16062
16179
|
var RULES = [
|
|
16063
16180
|
{ key: "structure", label: "Check package structure", run: structureRule },
|
|
16064
16181
|
{ key: "content", label: "Scan content for risky patterns", run: contentRule }
|
|
@@ -16070,7 +16187,7 @@ function toReportFindings(findings) {
|
|
|
16070
16187
|
}
|
|
16071
16188
|
function buildReport(pkg, findings, opts = {}) {
|
|
16072
16189
|
const declared = pkg.manifest.state === "ok" ? pkg.manifest.data.permissions : [];
|
|
16073
|
-
const detected = detectPermissions(pkg.files).
|
|
16190
|
+
const detected = [...detectPermissions(pkg.files)].sort();
|
|
16074
16191
|
const status = findings.some((f) => f.severity === "failure") ? "failed" : findings.some((f) => f.severity === "warning") ? "warning" : "passed";
|
|
16075
16192
|
const report = {
|
|
16076
16193
|
schemaVersion: "0.1",
|
|
@@ -16121,10 +16238,23 @@ async function getParsed(fetchImpl, url2, schema) {
|
|
|
16121
16238
|
try {
|
|
16122
16239
|
body = await res.json();
|
|
16123
16240
|
} catch {
|
|
16124
|
-
return {
|
|
16241
|
+
return {
|
|
16242
|
+
ok: false,
|
|
16243
|
+
notFound: false,
|
|
16244
|
+
message: `unexpected response from the API (${res.status})`,
|
|
16245
|
+
status: res.status
|
|
16246
|
+
};
|
|
16125
16247
|
}
|
|
16126
16248
|
if (typeof body !== "object" || body === null || !("success" in body) || body.success !== true) {
|
|
16127
|
-
|
|
16249
|
+
const error51 = body?.error;
|
|
16250
|
+
const apiError = typeof error51 === "string" ? { apiError: error51 } : {};
|
|
16251
|
+
return res.status === 404 ? { ok: false, notFound: true, message: "not found", status: 404, ...apiError } : {
|
|
16252
|
+
ok: false,
|
|
16253
|
+
notFound: false,
|
|
16254
|
+
message: `the API returned an error (${res.status})`,
|
|
16255
|
+
status: res.status,
|
|
16256
|
+
...apiError
|
|
16257
|
+
};
|
|
16128
16258
|
}
|
|
16129
16259
|
const parsed = schema.safeParse(body.data);
|
|
16130
16260
|
if (!parsed.success) {
|
|
@@ -16160,6 +16290,52 @@ async function fetchPreflight(fetchImpl, apiUrl, ref) {
|
|
|
16160
16290
|
return { ok: true, slug, detail: detail.data, preflight: preflight.data };
|
|
16161
16291
|
}
|
|
16162
16292
|
|
|
16293
|
+
// src/install.ts
|
|
16294
|
+
import { existsSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
16295
|
+
var GLOBAL_NEEDS_TARGET = "error: --global needs --target (e.g. --target claude-code)";
|
|
16296
|
+
var TARGET_OR_DIR = "error: pass --target or --dir, not both";
|
|
16297
|
+
function createOutput(emit) {
|
|
16298
|
+
const lines = [];
|
|
16299
|
+
const streamed = emit !== void 0;
|
|
16300
|
+
const push = (...next) => {
|
|
16301
|
+
lines.push(...next);
|
|
16302
|
+
if (next.length > 0) emit?.(next.join("\n"));
|
|
16303
|
+
};
|
|
16304
|
+
const done = (exitCode) => ({ lines, exitCode, streamed });
|
|
16305
|
+
return { push, done };
|
|
16306
|
+
}
|
|
16307
|
+
function isOccupied(path) {
|
|
16308
|
+
return existsSync(path) && (!statSync2(path).isDirectory() || readdirSync2(path).length > 0);
|
|
16309
|
+
}
|
|
16310
|
+
function receiptFor(preflight, packSlug) {
|
|
16311
|
+
return {
|
|
16312
|
+
version: preflight.version,
|
|
16313
|
+
sourceHash: preflight.sourceHash,
|
|
16314
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16315
|
+
...packSlug ? { pack: { slug: packSlug, version: preflight.version } } : {}
|
|
16316
|
+
};
|
|
16317
|
+
}
|
|
16318
|
+
function memberFiles(files, sourceDir) {
|
|
16319
|
+
if (sourceDir === ".") {
|
|
16320
|
+
return files;
|
|
16321
|
+
}
|
|
16322
|
+
const prefix = `${sourceDir}/`;
|
|
16323
|
+
return files.filter((f) => f.path.startsWith(prefix)).map((f) => ({ path: f.path.slice(prefix.length), content: f.content }));
|
|
16324
|
+
}
|
|
16325
|
+
function planMembers(installs, files) {
|
|
16326
|
+
return installs.map((m) => ({ ...m, files: memberFiles(files, m.sourceDir) }));
|
|
16327
|
+
}
|
|
16328
|
+
async function confirmRisk(preflight, opts, question, abortLine, push) {
|
|
16329
|
+
if (preflight.riskLevel === "low" || opts.yes) return true;
|
|
16330
|
+
if (!opts.confirmImpl) {
|
|
16331
|
+
push("", `error: a ${preflight.riskLevel}-risk skill needs confirmation; rerun with --yes`);
|
|
16332
|
+
return false;
|
|
16333
|
+
}
|
|
16334
|
+
if (await opts.confirmImpl(question)) return true;
|
|
16335
|
+
push("", abortLine);
|
|
16336
|
+
return false;
|
|
16337
|
+
}
|
|
16338
|
+
|
|
16163
16339
|
// src/pack.ts
|
|
16164
16340
|
import { posix } from "node:path";
|
|
16165
16341
|
function resolvePackMembers(members, target) {
|
|
@@ -16308,10 +16484,7 @@ function renderPreflightReport(detail, preflight, st = PLAIN) {
|
|
|
16308
16484
|
...renderDiff(preflight.diff)
|
|
16309
16485
|
];
|
|
16310
16486
|
if (preflight.blocked) {
|
|
16311
|
-
lines.push(
|
|
16312
|
-
"",
|
|
16313
|
-
st.red(`BLOCKED: ${preflight.blockedReason ?? "this version cannot be downloaded"}`)
|
|
16314
|
-
);
|
|
16487
|
+
lines.push("", st.red(`BLOCKED: ${preflight.blockedReason ?? "this version cannot be downloaded"}`));
|
|
16315
16488
|
}
|
|
16316
16489
|
return lines;
|
|
16317
16490
|
}
|
|
@@ -16334,37 +16507,39 @@ function renderDiff(diff) {
|
|
|
16334
16507
|
// src/targets.ts
|
|
16335
16508
|
import { homedir } from "node:os";
|
|
16336
16509
|
import { join as join3, resolve } from "node:path";
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16510
|
+
var SHARED = { project: join3(".agents", "skills"), global: join3(".agents", "skills"), layout: "codex" };
|
|
16511
|
+
var INSTALL_TOOLS = {
|
|
16512
|
+
"claude-code": { project: join3(".claude", "skills"), global: join3(".claude", "skills"), layout: "claude-code" },
|
|
16513
|
+
agents: SHARED,
|
|
16514
|
+
codex: SHARED,
|
|
16515
|
+
cursor: SHARED,
|
|
16516
|
+
windsurf: SHARED,
|
|
16517
|
+
"github-copilot": SHARED,
|
|
16518
|
+
"gemini-cli": SHARED,
|
|
16519
|
+
opencode: SHARED,
|
|
16520
|
+
cline: { project: join3(".cline", "skills"), global: join3(".cline", "skills"), layout: "codex" }
|
|
16521
|
+
};
|
|
16522
|
+
var INSTALL_TOOL_NAMES = Object.keys(INSTALL_TOOLS);
|
|
16523
|
+
function lookupTool(name) {
|
|
16524
|
+
return Object.hasOwn(INSTALL_TOOLS, name) ? INSTALL_TOOLS[name] : void 0;
|
|
16525
|
+
}
|
|
16526
|
+
var MAPPED_TARGETS = TARGETS.filter((t) => lookupTool(t) !== void 0);
|
|
16527
|
+
function layoutTarget(tool) {
|
|
16528
|
+
return lookupTool(tool)?.layout;
|
|
16529
|
+
}
|
|
16530
|
+
function declaresTool(declared, tool) {
|
|
16531
|
+
const layout = layoutTarget(tool);
|
|
16532
|
+
return declared.includes(tool) || layout !== void 0 && declared.includes(layout);
|
|
16342
16533
|
}
|
|
16343
|
-
var MAPPED_TARGETS = Object.keys(installAreas());
|
|
16344
16534
|
function resolveTargetArea(target, global = false, home) {
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
};
|
|
16350
|
-
}
|
|
16351
|
-
const area = installAreas(home)[target];
|
|
16352
|
-
if (!area) {
|
|
16353
|
-
return {
|
|
16354
|
-
ok: false,
|
|
16355
|
-
message: `${target} has no standard skills folder yet; use --dir to pick a location`
|
|
16356
|
-
};
|
|
16357
|
-
}
|
|
16358
|
-
if (global) {
|
|
16359
|
-
if (!area.global) {
|
|
16360
|
-
return {
|
|
16361
|
-
ok: false,
|
|
16362
|
-
message: `${target} has no user-level skills folder; install per project instead`
|
|
16363
|
-
};
|
|
16535
|
+
const tool = lookupTool(target);
|
|
16536
|
+
if (!tool) {
|
|
16537
|
+
if (TARGETS.includes(target)) {
|
|
16538
|
+
return { ok: false, message: `${target} has no standard skills folder yet; use --dir to pick a location` };
|
|
16364
16539
|
}
|
|
16365
|
-
return { ok:
|
|
16540
|
+
return { ok: false, message: `unknown target "${target}" (known tools: ${INSTALL_TOOL_NAMES.join(", ")})` };
|
|
16366
16541
|
}
|
|
16367
|
-
return { ok: true, dir:
|
|
16542
|
+
return { ok: true, dir: global ? join3(home ?? homedir(), tool.global) : tool.project };
|
|
16368
16543
|
}
|
|
16369
16544
|
function resolveTargetDir(target, slug, global = false, home) {
|
|
16370
16545
|
const area = resolveTargetArea(target, global, home);
|
|
@@ -16374,24 +16549,48 @@ function mappableDeclaredTargets(declared) {
|
|
|
16374
16549
|
return declared.filter((t) => MAPPED_TARGETS.includes(t));
|
|
16375
16550
|
}
|
|
16376
16551
|
function knownAreas(cwd, home) {
|
|
16377
|
-
const areas =
|
|
16378
|
-
const
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16552
|
+
const areas = /* @__PURE__ */ new Map();
|
|
16553
|
+
for (const [name, tool] of Object.entries(INSTALL_TOOLS)) {
|
|
16554
|
+
const scopes = [
|
|
16555
|
+
{ global: false, display: tool.project, dir: resolve(cwd, tool.project) },
|
|
16556
|
+
{ global: true, display: join3(home ?? homedir(), tool.global), dir: join3(home ?? homedir(), tool.global) }
|
|
16557
|
+
];
|
|
16558
|
+
for (const scope of scopes) {
|
|
16559
|
+
const existing = areas.get(scope.dir);
|
|
16560
|
+
if (existing) {
|
|
16561
|
+
if (!existing.tools.includes(name)) existing.tools.push(name);
|
|
16562
|
+
existing.project ||= !scope.global;
|
|
16563
|
+
existing.global ||= scope.global;
|
|
16564
|
+
existing.label = areaLabel(existing);
|
|
16565
|
+
continue;
|
|
16566
|
+
}
|
|
16567
|
+
const area = {
|
|
16568
|
+
tools: [name],
|
|
16569
|
+
layout: tool.layout,
|
|
16570
|
+
project: !scope.global,
|
|
16571
|
+
global: scope.global,
|
|
16572
|
+
label: "",
|
|
16573
|
+
dir: scope.dir
|
|
16574
|
+
};
|
|
16575
|
+
area.label = areaLabel(area, scope.display);
|
|
16576
|
+
areas.set(scope.dir, area);
|
|
16392
16577
|
}
|
|
16393
16578
|
}
|
|
16394
|
-
return areas;
|
|
16579
|
+
return [...areas.values()];
|
|
16580
|
+
}
|
|
16581
|
+
function areaLabel(area, display) {
|
|
16582
|
+
const shown = display ?? area.label.slice(0, area.label.indexOf(" ("));
|
|
16583
|
+
const scopes = [...area.project ? ["project"] : [], ...area.global ? ["user"] : []].join(", ");
|
|
16584
|
+
return `${shown} (${scopes}) - ${area.tools.join(", ")}`;
|
|
16585
|
+
}
|
|
16586
|
+
function resolveArea(target, global, cwd, home) {
|
|
16587
|
+
const resolved = resolveTargetArea(target, global, home);
|
|
16588
|
+
if (!resolved.ok) {
|
|
16589
|
+
return resolved;
|
|
16590
|
+
}
|
|
16591
|
+
const dir = resolve(cwd, resolved.dir);
|
|
16592
|
+
const area = knownAreas(cwd, home).find((a) => a.dir === dir);
|
|
16593
|
+
return area ? { ok: true, area } : { ok: false, message: `no known skills area for ${target}` };
|
|
16395
16594
|
}
|
|
16396
16595
|
|
|
16397
16596
|
// src/add.ts
|
|
@@ -16464,7 +16663,7 @@ function writeTree(files, target) {
|
|
|
16464
16663
|
writeFileSync2(filePath, file2.content);
|
|
16465
16664
|
}
|
|
16466
16665
|
mkdirSync2(dirname(target), { recursive: true });
|
|
16467
|
-
if (
|
|
16666
|
+
if (existsSync2(target)) {
|
|
16468
16667
|
rmdirSync(target);
|
|
16469
16668
|
}
|
|
16470
16669
|
renameSync(tempDir, target);
|
|
@@ -16473,309 +16672,247 @@ function writeTree(files, target) {
|
|
|
16473
16672
|
throw err2;
|
|
16474
16673
|
}
|
|
16475
16674
|
}
|
|
16476
|
-
function
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
|
|
16481
|
-
return
|
|
16675
|
+
function orderedAreas(declared, cwd, home) {
|
|
16676
|
+
const areas = knownAreas(cwd, home).map((area) => ({
|
|
16677
|
+
...area,
|
|
16678
|
+
declared: area.tools.some((tool) => declaresTool(declared, tool))
|
|
16679
|
+
}));
|
|
16680
|
+
return [...areas.filter((a) => a.declared), ...areas.filter((a) => !a.declared)];
|
|
16482
16681
|
}
|
|
16483
|
-
function installChoices(declared, slug) {
|
|
16484
|
-
const choices =
|
|
16485
|
-
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
];
|
|
16489
|
-
for (const tool of ordered) {
|
|
16490
|
-
const note = declared.includes(tool) ? "" : " - not declared by this skill";
|
|
16491
|
-
const resolved = resolveTargetDir(tool, slug);
|
|
16492
|
-
if (resolved.ok) {
|
|
16493
|
-
choices.push({ label: `${tool} skills folder (${resolved.dir})${note}`, dir: resolved.dir });
|
|
16494
|
-
}
|
|
16495
|
-
const global = resolveTargetDir(tool, slug, true);
|
|
16496
|
-
if (global.ok) {
|
|
16497
|
-
choices.push({
|
|
16498
|
-
label: `${tool} user-level skills folder (${global.dir})${note}`,
|
|
16499
|
-
dir: global.dir
|
|
16500
|
-
});
|
|
16501
|
-
}
|
|
16502
|
-
}
|
|
16682
|
+
function installChoices(declared, slug, cwd, home) {
|
|
16683
|
+
const choices = orderedAreas(declared, cwd, home).map((area) => ({
|
|
16684
|
+
label: `${area.label}${area.declared ? "" : " - not declared by this skill"}`,
|
|
16685
|
+
dir: join4(area.dir, slug)
|
|
16686
|
+
}));
|
|
16503
16687
|
choices.push({ label: `current directory (./${slug})`, dir: slug });
|
|
16504
16688
|
return choices;
|
|
16505
16689
|
}
|
|
16506
|
-
function packChoices(declared, slug, count) {
|
|
16507
|
-
const choices =
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
];
|
|
16512
|
-
for (const tool of ordered) {
|
|
16513
|
-
const note = declared.includes(tool) ? "" : " - not declared by this pack";
|
|
16514
|
-
const project = resolveTargetArea(tool);
|
|
16515
|
-
if (project.ok) {
|
|
16516
|
-
choices.push({
|
|
16517
|
-
label: `${tool} skills folder (${project.dir}) - ${count} skills${note}`,
|
|
16518
|
-
tool,
|
|
16519
|
-
global: false
|
|
16520
|
-
});
|
|
16521
|
-
}
|
|
16522
|
-
const global = resolveTargetArea(tool, true);
|
|
16523
|
-
if (global.ok) {
|
|
16524
|
-
choices.push({
|
|
16525
|
-
label: `${tool} user-level skills folder (${global.dir}) - ${count} skills${note}`,
|
|
16526
|
-
tool,
|
|
16527
|
-
global: true
|
|
16528
|
-
});
|
|
16529
|
-
}
|
|
16530
|
-
}
|
|
16690
|
+
function packChoices(declared, slug, count, cwd, home) {
|
|
16691
|
+
const choices = orderedAreas(declared, cwd, home).map((area) => ({
|
|
16692
|
+
label: `${area.label} - ${count} skills${area.declared ? "" : " - not declared by this pack"}`,
|
|
16693
|
+
area
|
|
16694
|
+
}));
|
|
16531
16695
|
choices.push({ label: `current directory (./${slug}, raw pack source)` });
|
|
16532
16696
|
return choices;
|
|
16533
16697
|
}
|
|
16534
|
-
async function
|
|
16535
|
-
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
if (
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
if (
|
|
16545
|
-
|
|
16698
|
+
async function promptIndex(ask, count, push) {
|
|
16699
|
+
while (true) {
|
|
16700
|
+
const answer = (await ask(`Where should it go? [1-${count}, default 1] `)).trim();
|
|
16701
|
+
if (answer === "") return 0;
|
|
16702
|
+
const index = /^\d+$/.test(answer) ? Number.parseInt(answer, 10) - 1 : -1;
|
|
16703
|
+
if (index >= 0 && index < count) return index;
|
|
16704
|
+
push(` answer 1-${count}, or press enter for the default`);
|
|
16705
|
+
}
|
|
16706
|
+
}
|
|
16707
|
+
async function choosePackLocation(ctx, detail, memberCount, prompt) {
|
|
16708
|
+
if (prompt) {
|
|
16709
|
+
const choices = packChoices(detail.targets, ctx.slug, memberCount, ctx.cwd, ctx.home);
|
|
16710
|
+
ctx.push("", "Install location:");
|
|
16711
|
+
choices.forEach((choice, i2) => ctx.push(` ${i2 + 1}) ${choice.label}`));
|
|
16712
|
+
const chosen = choices[await promptIndex(prompt, choices.length, ctx.push)];
|
|
16713
|
+
return chosen.area ? { area: chosen.area } : { dir: ctx.slug };
|
|
16714
|
+
}
|
|
16715
|
+
const mappable = mappableDeclaredTargets(detail.targets);
|
|
16716
|
+
if (mappable.length > 0) {
|
|
16717
|
+
ctx.push("", `tip: --target ${mappable[0]} installs the ${memberCount} skills into the tool's skills folder`);
|
|
16718
|
+
}
|
|
16719
|
+
return { dir: ctx.slug };
|
|
16720
|
+
}
|
|
16721
|
+
async function chooseSingleLocation(ctx, detail, prompt) {
|
|
16722
|
+
const choices = installChoices(detail.targets, ctx.slug, ctx.cwd, ctx.home);
|
|
16723
|
+
if (prompt && choices.length > 1) {
|
|
16724
|
+
ctx.push("", "Install location:");
|
|
16725
|
+
choices.forEach((choice, i2) => ctx.push(` ${i2 + 1}) ${choice.label}`));
|
|
16726
|
+
return choices[await promptIndex(prompt, choices.length, ctx.push)].dir;
|
|
16727
|
+
}
|
|
16728
|
+
const mappable = mappableDeclaredTargets(detail.targets);
|
|
16729
|
+
if (mappable.length > 0) {
|
|
16730
|
+
ctx.push("", `tip: --target ${mappable[0]} installs into the tool's skills folder`);
|
|
16731
|
+
}
|
|
16732
|
+
return ctx.slug;
|
|
16733
|
+
}
|
|
16734
|
+
async function installPack(ctx, members, targets) {
|
|
16735
|
+
const { push, done, slug, preflight } = ctx;
|
|
16736
|
+
const resolved = targets.map(({ area, tool }) => ({ area, tool, ...resolvePackMembers(members, area.layout) }));
|
|
16737
|
+
const unsupported = resolved.find((r) => r.installs.length === 0);
|
|
16738
|
+
if (unsupported) {
|
|
16739
|
+
push("", `error: none of this pack's skills support ${unsupported.tool}`);
|
|
16546
16740
|
return done(2);
|
|
16547
16741
|
}
|
|
16548
|
-
|
|
16549
|
-
|
|
16742
|
+
const conflicts = resolved.flatMap((r) => r.installs.map((m) => join4(r.area.dir, m.name))).filter(isOccupied);
|
|
16743
|
+
if (conflicts.length > 0) {
|
|
16744
|
+
push(
|
|
16745
|
+
"",
|
|
16746
|
+
"error: these destinations already exist and are not empty; nothing was installed:",
|
|
16747
|
+
...conflicts.map((c) => ` ${c}`)
|
|
16748
|
+
);
|
|
16550
16749
|
return done(2);
|
|
16551
16750
|
}
|
|
16552
|
-
const
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
push(...fetched.result.lines);
|
|
16557
|
-
return done(fetched.result.exitCode);
|
|
16558
|
-
}
|
|
16559
|
-
const { slug, detail, preflight } = fetched;
|
|
16560
|
-
push(...renderPreflightReport(detail, preflight, opts.style));
|
|
16561
|
-
if (preflight.blocked) {
|
|
16562
|
-
return done(1);
|
|
16751
|
+
const download = await downloadVerified(ctx.fetchImpl, ctx.apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
16752
|
+
if (!download.ok) {
|
|
16753
|
+
push("", `error: ${download.message}`);
|
|
16754
|
+
return done(2);
|
|
16563
16755
|
}
|
|
16564
|
-
const
|
|
16565
|
-
const
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
if (opts.target) {
|
|
16570
|
-
const resolved = isPack ? resolveTargetArea(opts.target, opts.global) : resolveTargetDir(opts.target, slug, opts.global);
|
|
16571
|
-
if (!resolved.ok) {
|
|
16572
|
-
push("", `error: ${resolved.message}`);
|
|
16573
|
-
return done(2);
|
|
16574
|
-
}
|
|
16575
|
-
if (isPack) {
|
|
16576
|
-
packTool = opts.target;
|
|
16577
|
-
packGlobal = opts.global ?? false;
|
|
16578
|
-
} else {
|
|
16579
|
-
targetDir = resolved.dir;
|
|
16580
|
-
}
|
|
16581
|
-
if (!detail.targets.includes(opts.target)) {
|
|
16582
|
-
push("", `warning: this skill does not declare ${opts.target} as a target`);
|
|
16583
|
-
}
|
|
16756
|
+
const planned = resolved.map((r) => ({ ...r, plans: planMembers(r.installs, download.files) }));
|
|
16757
|
+
const missing = planned.flatMap((r) => r.plans).find((p) => p.files.length === 0);
|
|
16758
|
+
if (missing) {
|
|
16759
|
+
push("", `error: the snapshot has no files for "${missing.name}"; nothing was installed`);
|
|
16760
|
+
return done(2);
|
|
16584
16761
|
}
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
push("", `
|
|
16588
|
-
return done(2);
|
|
16589
|
-
}
|
|
16590
|
-
const confirmed = await opts.confirmImpl(
|
|
16591
|
-
`Install ${slug}@${preflight.version} (${preflight.riskLevel} risk)? [y/N] `
|
|
16592
|
-
);
|
|
16593
|
-
if (!confirmed) {
|
|
16594
|
-
push("", "Install aborted.");
|
|
16595
|
-
return done(2);
|
|
16762
|
+
for (const r of planned) {
|
|
16763
|
+
for (const name of r.skipped) {
|
|
16764
|
+
push("", `note: ${name} does not support ${r.tool}; skipped`);
|
|
16596
16765
|
}
|
|
16597
16766
|
}
|
|
16598
|
-
const
|
|
16599
|
-
const
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
return index;
|
|
16608
|
-
}
|
|
16609
|
-
push(` answer 1-${count}, or press enter for the default`);
|
|
16610
|
-
}
|
|
16611
|
-
};
|
|
16612
|
-
if (isPack && packTool === void 0 && targetDir === void 0) {
|
|
16613
|
-
if (prompt) {
|
|
16614
|
-
const choices = packChoices(detail.targets, slug, members.length);
|
|
16615
|
-
push("", "Install location:");
|
|
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
|
-
);
|
|
16767
|
+
const landed = [];
|
|
16768
|
+
const describe3 = (dir, names) => planned.length > 1 ? `${dir}: ${names.join(", ")}` : names.join(", ");
|
|
16769
|
+
for (const r of planned) {
|
|
16770
|
+
const written = [];
|
|
16771
|
+
try {
|
|
16772
|
+
for (const plan of r.plans) {
|
|
16773
|
+
writeTree(plan.files, join4(r.area.dir, plan.name));
|
|
16774
|
+
written.push(plan.name);
|
|
16775
|
+
recordReceipt(r.area.dir, plan.name, receiptFor(preflight, slug));
|
|
16632
16776
|
}
|
|
16633
|
-
}
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
const area2 = resolveTargetArea(packTool, packGlobal);
|
|
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) {
|
|
16777
|
+
} catch {
|
|
16778
|
+
const before = [...landed, ...written.length > 0 ? [describe3(r.area.dir, written)] : []];
|
|
16779
|
+
const where = planned.length > 1 ? ` in ${r.area.dir}` : "";
|
|
16651
16780
|
push(
|
|
16652
16781
|
"",
|
|
16653
|
-
|
|
16654
|
-
...conflicts.map((c) => ` ${c}`)
|
|
16782
|
+
`error: could not write ${r.plans[written.length].name}${where}; installed before the failure: ${before.join("; ") || "none"}`
|
|
16655
16783
|
);
|
|
16656
16784
|
return done(2);
|
|
16657
16785
|
}
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16786
|
+
landed.push(describe3(r.area.dir, written));
|
|
16787
|
+
push("", `Installed ${written.length} skills to ${r.area.dir}`, ` ${written.join(", ")}`);
|
|
16788
|
+
}
|
|
16789
|
+
push("Source hash verified against the Skill Passport.");
|
|
16790
|
+
return done(0);
|
|
16791
|
+
}
|
|
16792
|
+
async function installSingle(ctx, targetDirs) {
|
|
16793
|
+
const { push, done, slug, preflight } = ctx;
|
|
16794
|
+
const targets = targetDirs.map((dir) => resolve2(ctx.cwd, dir));
|
|
16795
|
+
for (const target of targets) {
|
|
16796
|
+
if (!existsSync2(target)) continue;
|
|
16797
|
+
if (!statSync3(target).isDirectory()) {
|
|
16798
|
+
push("", `error: target ${target} already exists and is not a directory`);
|
|
16667
16799
|
return done(2);
|
|
16668
16800
|
}
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
if (missing) {
|
|
16672
|
-
push("", `error: the snapshot has no files for "${missing.name}"; nothing was installed`);
|
|
16801
|
+
if (readdirSync3(target).length > 0) {
|
|
16802
|
+
push("", `error: target directory ${target} already exists and is not empty`);
|
|
16673
16803
|
return done(2);
|
|
16674
16804
|
}
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16805
|
+
}
|
|
16806
|
+
const download = await downloadVerified(ctx.fetchImpl, ctx.apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
16807
|
+
if (!download.ok) {
|
|
16808
|
+
push("", `error: ${download.message}`);
|
|
16809
|
+
return done(2);
|
|
16810
|
+
}
|
|
16811
|
+
const areas = knownAreas(ctx.cwd, ctx.home);
|
|
16812
|
+
const landed = [];
|
|
16813
|
+
for (const target of targets) {
|
|
16679
16814
|
try {
|
|
16680
|
-
|
|
16681
|
-
writeTree(plan.files, join4(areaAbs, plan.name));
|
|
16682
|
-
written.push(plan.name);
|
|
16683
|
-
}
|
|
16815
|
+
writeTree(download.files, target);
|
|
16684
16816
|
} catch {
|
|
16685
16817
|
push(
|
|
16686
16818
|
"",
|
|
16687
|
-
`error: could not write ${
|
|
16819
|
+
landed.length === 0 ? "error: could not write the install; nothing was installed" : `error: could not write ${target}; installed before the failure: ${landed.join(", ")}`
|
|
16688
16820
|
);
|
|
16689
16821
|
return done(2);
|
|
16690
16822
|
}
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16696
|
-
pack: { slug, version: preflight.version }
|
|
16697
|
-
});
|
|
16823
|
+
landed.push(target);
|
|
16824
|
+
const area = areas.find((a) => a.dir === dirname(target));
|
|
16825
|
+
if (area) {
|
|
16826
|
+
recordReceipt(area.dir, slug, receiptFor(preflight));
|
|
16698
16827
|
}
|
|
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);
|
|
16828
|
+
push("", `Installed ${download.files.length} file(s) to ${target}`);
|
|
16706
16829
|
}
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
|
|
16830
|
+
push("Source hash verified against the Skill Passport.");
|
|
16831
|
+
return done(0);
|
|
16832
|
+
}
|
|
16833
|
+
async function runAdd(ref, opts = {}) {
|
|
16834
|
+
const { push, done } = createOutput(opts.emit);
|
|
16835
|
+
const tools = [...new Set([opts.target ?? []].flat())];
|
|
16836
|
+
if (tools.length > 0 && opts.dir) {
|
|
16837
|
+
push(TARGET_OR_DIR);
|
|
16838
|
+
return done(2);
|
|
16712
16839
|
}
|
|
16713
|
-
if (
|
|
16714
|
-
|
|
16715
|
-
|
|
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
|
-
}
|
|
16840
|
+
if (opts.global && tools.length === 0) {
|
|
16841
|
+
push(GLOBAL_NEEDS_TARGET);
|
|
16842
|
+
return done(2);
|
|
16726
16843
|
}
|
|
16727
|
-
const
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
|
|
16844
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
16845
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
16846
|
+
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
16847
|
+
const fetched = await fetchPreflight(fetchImpl, apiUrl, ref);
|
|
16848
|
+
if (!fetched.ok) {
|
|
16849
|
+
push(...fetched.result.lines);
|
|
16850
|
+
return done(fetched.result.exitCode);
|
|
16851
|
+
}
|
|
16852
|
+
const { slug, detail, preflight } = fetched;
|
|
16853
|
+
push(...renderPreflightReport(detail, preflight, opts.style));
|
|
16854
|
+
if (preflight.blocked) {
|
|
16855
|
+
return done(1);
|
|
16856
|
+
}
|
|
16857
|
+
const members = detail.packMembers ?? [];
|
|
16858
|
+
const isPack = members.length > 0;
|
|
16859
|
+
const targets = [];
|
|
16860
|
+
for (const tool of tools) {
|
|
16861
|
+
const resolved = resolveArea(tool, opts.global ?? false, cwd, opts.home);
|
|
16862
|
+
if (!resolved.ok) {
|
|
16863
|
+
push("", `error: ${resolved.message}`);
|
|
16731
16864
|
return done(2);
|
|
16732
16865
|
}
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16866
|
+
const shared = targets.find((t) => t.area.dir === resolved.area.dir);
|
|
16867
|
+
if (shared) {
|
|
16868
|
+
push("", `note: ${tool} and ${shared.tool} share ${resolved.area.dir}; installing once`);
|
|
16869
|
+
continue;
|
|
16870
|
+
}
|
|
16871
|
+
targets.push({ area: resolved.area, tool });
|
|
16872
|
+
if (!declaresTool(detail.targets, tool)) {
|
|
16873
|
+
push("", `warning: this skill does not declare ${tool} as a target`);
|
|
16736
16874
|
}
|
|
16737
16875
|
}
|
|
16738
|
-
const
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
slug
|
|
16742
|
-
|
|
16743
|
-
|
|
16876
|
+
const confirmed = await confirmRisk(
|
|
16877
|
+
preflight,
|
|
16878
|
+
opts,
|
|
16879
|
+
`Install ${slug}@${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
|
|
16880
|
+
"Install aborted.",
|
|
16881
|
+
push
|
|
16744
16882
|
);
|
|
16745
|
-
if (!
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
|
|
16752
|
-
|
|
16753
|
-
|
|
16883
|
+
if (!confirmed) return done(2);
|
|
16884
|
+
const ctx = { push, done, fetchImpl, apiUrl, cwd, home: opts.home, slug, preflight };
|
|
16885
|
+
if (isPack) {
|
|
16886
|
+
if (targets.length > 0) {
|
|
16887
|
+
return installPack(ctx, members, targets);
|
|
16888
|
+
}
|
|
16889
|
+
if (opts.dir === void 0) {
|
|
16890
|
+
const location = await choosePackLocation(ctx, detail, members.length, opts.promptImpl);
|
|
16891
|
+
return "area" in location ? installPack(ctx, members, [{ area: location.area, tool: location.area.tools[0] }]) : installSingle(ctx, [location.dir]);
|
|
16892
|
+
}
|
|
16893
|
+
push(
|
|
16894
|
+
"",
|
|
16895
|
+
`note: --dir installs the raw pack source; --target <tool> installs the ${members.length} skills individually`
|
|
16896
|
+
);
|
|
16897
|
+
return installSingle(ctx, [opts.dir]);
|
|
16754
16898
|
}
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16761
|
-
});
|
|
16899
|
+
if (targets.length > 0) {
|
|
16900
|
+
return installSingle(
|
|
16901
|
+
ctx,
|
|
16902
|
+
targets.map((t) => join4(t.area.dir, slug))
|
|
16903
|
+
);
|
|
16762
16904
|
}
|
|
16763
|
-
|
|
16764
|
-
"",
|
|
16765
|
-
`Installed ${download.files.length} file(s) to ${target}`,
|
|
16766
|
-
"Source hash verified against the Skill Passport."
|
|
16767
|
-
);
|
|
16768
|
-
return done(0);
|
|
16905
|
+
return installSingle(ctx, [opts.dir ?? await chooseSingleLocation(ctx, detail, opts.promptImpl)]);
|
|
16769
16906
|
}
|
|
16770
16907
|
|
|
16771
16908
|
// src/list.ts
|
|
16772
|
-
import { existsSync as
|
|
16909
|
+
import { existsSync as existsSync3, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
|
|
16773
16910
|
import { join as join5 } from "node:path";
|
|
16774
16911
|
function installedIn(dir) {
|
|
16775
|
-
if (!
|
|
16912
|
+
if (!existsSync3(dir)) {
|
|
16776
16913
|
return [];
|
|
16777
16914
|
}
|
|
16778
|
-
return
|
|
16915
|
+
return readdirSync4(dir).sort().filter((name) => statSync4(join5(dir, name), { throwIfNoEntry: false })?.isDirectory() ?? false);
|
|
16779
16916
|
}
|
|
16780
16917
|
function runList(opts = {}) {
|
|
16781
16918
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -16810,11 +16947,7 @@ async function identifyByHash(fetchImpl, apiUrl, slug, dir) {
|
|
|
16810
16947
|
} catch {
|
|
16811
16948
|
return void 0;
|
|
16812
16949
|
}
|
|
16813
|
-
const detail = await getParsed(
|
|
16814
|
-
fetchImpl,
|
|
16815
|
-
`${apiUrl}/skills/${encodeURIComponent(slug)}`,
|
|
16816
|
-
publicSkillDetailSchema
|
|
16817
|
-
);
|
|
16950
|
+
const detail = await getParsed(fetchImpl, `${apiUrl}/skills/${encodeURIComponent(slug)}`, publicSkillDetailSchema);
|
|
16818
16951
|
if (!detail.ok) {
|
|
16819
16952
|
return void 0;
|
|
16820
16953
|
}
|
|
@@ -16859,9 +16992,7 @@ async function runOutdated(opts = {}) {
|
|
|
16859
16992
|
}
|
|
16860
16993
|
seenPacks.add(receipt.pack.slug);
|
|
16861
16994
|
tracked += 1;
|
|
16862
|
-
const members = Object.values(receipts).filter(
|
|
16863
|
-
(r) => r.pack?.slug === receipt.pack?.slug
|
|
16864
|
-
).length;
|
|
16995
|
+
const members = Object.values(receipts).filter((r) => r.pack?.slug === receipt.pack?.slug).length;
|
|
16865
16996
|
const latest = latestBySlug.get(receipt.pack.slug)?.version;
|
|
16866
16997
|
if (latest === void 0) {
|
|
16867
16998
|
rows.push(` ${receipt.pack.slug} ${receipt.pack.version} not in the directory`);
|
|
@@ -16869,9 +17000,7 @@ async function runOutdated(opts = {}) {
|
|
|
16869
17000
|
rows.push(` ${receipt.pack.slug} ${receipt.pack.version} current (pack, ${members} skills)`);
|
|
16870
17001
|
} else {
|
|
16871
17002
|
outdatedCount += 1;
|
|
16872
|
-
rows.push(
|
|
16873
|
-
` ${receipt.pack.slug} ${receipt.pack.version} -> ${latest} (pack, ${members} skills)`
|
|
16874
|
-
);
|
|
17003
|
+
rows.push(` ${receipt.pack.slug} ${receipt.pack.version} -> ${latest} (pack, ${members} skills)`);
|
|
16875
17004
|
}
|
|
16876
17005
|
continue;
|
|
16877
17006
|
}
|
|
@@ -16927,39 +17056,27 @@ async function runOutdated(opts = {}) {
|
|
|
16927
17056
|
}
|
|
16928
17057
|
|
|
16929
17058
|
// src/remove.ts
|
|
16930
|
-
import { existsSync as
|
|
17059
|
+
import { existsSync as existsSync4, rmSync as rmSync2, statSync as statSync5 } from "node:fs";
|
|
16931
17060
|
import { dirname as dirname2, join as join7, resolve as resolve3 } from "node:path";
|
|
16932
17061
|
function looksLikeInstalledSkill(dir) {
|
|
16933
|
-
return
|
|
17062
|
+
return existsSync4(join7(dir, "SKILL.md")) || existsSync4(join7(dir, "skill.json"));
|
|
16934
17063
|
}
|
|
16935
17064
|
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)];
|
|
17065
|
+
const inAreas = knownAreas(cwd, home).map((area) => join7(area.dir, slug));
|
|
17066
|
+
return [.../* @__PURE__ */ new Set([...inAreas, resolve3(cwd, slug)])];
|
|
16950
17067
|
}
|
|
16951
17068
|
function runRemove(slug, opts = {}) {
|
|
16952
17069
|
if (opts.target && opts.dir) {
|
|
16953
|
-
return { lines: [
|
|
17070
|
+
return { lines: [TARGET_OR_DIR], exitCode: 2 };
|
|
16954
17071
|
}
|
|
16955
17072
|
if (opts.global && !opts.target) {
|
|
16956
|
-
return { lines: [
|
|
17073
|
+
return { lines: [GLOBAL_NEEDS_TARGET], exitCode: 2 };
|
|
16957
17074
|
}
|
|
16958
17075
|
const cwd = opts.cwd ?? process.cwd();
|
|
16959
17076
|
if (!opts.dir) {
|
|
16960
17077
|
let areas = knownAreas(cwd, opts.home);
|
|
16961
17078
|
if (opts.target) {
|
|
16962
|
-
areas = areas.filter((a) => a.
|
|
17079
|
+
areas = areas.filter((a) => a.tools.includes(opts.target) && (opts.global ? a.global : a.project));
|
|
16963
17080
|
}
|
|
16964
17081
|
const packHits = areas.map((area) => ({
|
|
16965
17082
|
area,
|
|
@@ -16981,10 +17098,7 @@ function runRemove(slug, opts = {}) {
|
|
|
16981
17098
|
removeReceipt(area.dir, name);
|
|
16982
17099
|
}
|
|
16983
17100
|
return {
|
|
16984
|
-
lines: [
|
|
16985
|
-
`Removed pack ${slug} (${members.length} skills) from ${area.dir}`,
|
|
16986
|
-
` ${members.sort().join(", ")}`
|
|
16987
|
-
],
|
|
17101
|
+
lines: [`Removed pack ${slug} (${members.length} skills) from ${area.dir}`, ` ${members.sort().join(", ")}`],
|
|
16988
17102
|
exitCode: 0
|
|
16989
17103
|
};
|
|
16990
17104
|
}
|
|
@@ -16999,9 +17113,7 @@ function runRemove(slug, opts = {}) {
|
|
|
16999
17113
|
} else if (opts.dir) {
|
|
17000
17114
|
dir = resolve3(cwd, opts.dir);
|
|
17001
17115
|
} else {
|
|
17002
|
-
const found = candidateDirs(slug, cwd, opts.home).filter(
|
|
17003
|
-
(candidate) => existsSync3(candidate)
|
|
17004
|
-
);
|
|
17116
|
+
const found = candidateDirs(slug, cwd, opts.home).filter((candidate) => existsSync4(candidate));
|
|
17005
17117
|
if (found.length === 0) {
|
|
17006
17118
|
return { lines: [`error: ${slug} is not installed in any known location`], exitCode: 2 };
|
|
17007
17119
|
}
|
|
@@ -17016,10 +17128,10 @@ function runRemove(slug, opts = {}) {
|
|
|
17016
17128
|
}
|
|
17017
17129
|
dir = found[0];
|
|
17018
17130
|
}
|
|
17019
|
-
if (!
|
|
17131
|
+
if (!existsSync4(dir)) {
|
|
17020
17132
|
return { lines: [`error: nothing installed at ${dir}`], exitCode: 2 };
|
|
17021
17133
|
}
|
|
17022
|
-
if (!
|
|
17134
|
+
if (!statSync5(dir).isDirectory()) {
|
|
17023
17135
|
return { lines: [`error: ${dir} is not a directory`], exitCode: 2 };
|
|
17024
17136
|
}
|
|
17025
17137
|
if (!looksLikeInstalledSkill(dir)) {
|
|
@@ -17120,25 +17232,23 @@ async function runSearch(opts = {}) {
|
|
|
17120
17232
|
}
|
|
17121
17233
|
if (opts.category && !CATEGORY_SLUGS.includes(opts.category)) {
|
|
17122
17234
|
return {
|
|
17123
|
-
lines: [
|
|
17124
|
-
`error: unknown category "${opts.category}" (known categories: ${CATEGORY_SLUGS.join(", ")})`
|
|
17125
|
-
],
|
|
17235
|
+
lines: [`error: unknown category "${opts.category}" (known categories: ${CATEGORY_SLUGS.join(", ")})`],
|
|
17126
17236
|
exitCode: 2
|
|
17127
17237
|
};
|
|
17128
17238
|
}
|
|
17129
17239
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
17130
17240
|
const apiUrl = resolveApiUrl(opts.apiUrl);
|
|
17241
|
+
if (opts.ai) {
|
|
17242
|
+
return runAiSearch(opts, fetchImpl, apiUrl);
|
|
17243
|
+
}
|
|
17131
17244
|
const fetched = await getParsed(fetchImpl, `${apiUrl}/skills`, publicSkillListSchema);
|
|
17132
17245
|
if (!fetched.ok) {
|
|
17133
17246
|
return { lines: [`error: ${fetched.message}`], exitCode: 2 };
|
|
17134
17247
|
}
|
|
17135
17248
|
const query = (opts.query ?? "").trim().toLowerCase();
|
|
17136
|
-
const matches = fetched.data.filter(
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
if (opts.packs && (skill.packSkills?.length ?? 0) === 0) return false;
|
|
17140
|
-
return query === "" || matchesQuery(skill, query);
|
|
17141
|
-
});
|
|
17249
|
+
const matches = fetched.data.filter(
|
|
17250
|
+
(skill) => matchesFilters(skill, opts) && (query === "" || matchesQuery(skill, query))
|
|
17251
|
+
);
|
|
17142
17252
|
if (opts.json) {
|
|
17143
17253
|
return { lines: [JSON.stringify(matches, null, 2)], exitCode: 0 };
|
|
17144
17254
|
}
|
|
@@ -17150,22 +17260,66 @@ async function runSearch(opts = {}) {
|
|
|
17150
17260
|
lines: [
|
|
17151
17261
|
...renderRows(matches, opts.width, st),
|
|
17152
17262
|
"",
|
|
17153
|
-
st.dim(
|
|
17154
|
-
|
|
17155
|
-
|
|
17263
|
+
st.dim(`${matches.length} of ${fetched.data.length} skills - skillpass report <slug> shows the passport`)
|
|
17264
|
+
],
|
|
17265
|
+
exitCode: 0
|
|
17266
|
+
};
|
|
17267
|
+
}
|
|
17268
|
+
function matchesFilters(skill, opts) {
|
|
17269
|
+
if (opts.target && !skill.targets.includes(opts.target)) return false;
|
|
17270
|
+
if (opts.category && (skill.category ?? null) !== opts.category) return false;
|
|
17271
|
+
if (opts.packs && (skill.packSkills?.length ?? 0) === 0) return false;
|
|
17272
|
+
return true;
|
|
17273
|
+
}
|
|
17274
|
+
var AI_ERRORS = {
|
|
17275
|
+
429: "AI search is rate limited (20 requests a minute per address); try again in a moment",
|
|
17276
|
+
503: "AI search is not configured on this API",
|
|
17277
|
+
502: "AI search is temporarily unavailable; try again shortly"
|
|
17278
|
+
};
|
|
17279
|
+
function aiErrorMessage(outcome) {
|
|
17280
|
+
if (outcome.status !== void 0 && AI_ERRORS[outcome.status]) return AI_ERRORS[outcome.status];
|
|
17281
|
+
if (outcome.status === 400 && outcome.apiError) return outcome.apiError;
|
|
17282
|
+
return outcome.message;
|
|
17283
|
+
}
|
|
17284
|
+
async function runAiSearch(opts, fetchImpl, apiUrl) {
|
|
17285
|
+
const query = (opts.query ?? "").trim();
|
|
17286
|
+
if (query === "") {
|
|
17287
|
+
return { lines: ["error: search --ai needs a query (describe what you need)"], exitCode: 2 };
|
|
17288
|
+
}
|
|
17289
|
+
const fetched = await getParsed(
|
|
17290
|
+
fetchImpl,
|
|
17291
|
+
`${apiUrl}/skills/search?q=${encodeURIComponent(query)}`,
|
|
17292
|
+
publicSkillListSchema
|
|
17293
|
+
);
|
|
17294
|
+
if (!fetched.ok) {
|
|
17295
|
+
return { lines: [`error: ${aiErrorMessage(fetched)}`], exitCode: 2 };
|
|
17296
|
+
}
|
|
17297
|
+
const matches = fetched.data.filter((skill) => matchesFilters(skill, opts));
|
|
17298
|
+
if (opts.json) {
|
|
17299
|
+
return { lines: [JSON.stringify(matches, null, 2)], exitCode: 0 };
|
|
17300
|
+
}
|
|
17301
|
+
if (matches.length === 0) {
|
|
17302
|
+
return { lines: ["No skills match by meaning - try other words"], exitCode: 0 };
|
|
17303
|
+
}
|
|
17304
|
+
const st = opts.style ?? PLAIN;
|
|
17305
|
+
return {
|
|
17306
|
+
lines: [
|
|
17307
|
+
...renderRows(matches, opts.width, st),
|
|
17308
|
+
"",
|
|
17309
|
+
st.dim(`${matches.length} skills by meaning - skillpass report <slug> shows the passport`)
|
|
17156
17310
|
],
|
|
17157
17311
|
exitCode: 0
|
|
17158
17312
|
};
|
|
17159
17313
|
}
|
|
17160
17314
|
|
|
17161
17315
|
// src/update.ts
|
|
17162
|
-
import { existsSync as
|
|
17316
|
+
import { existsSync as existsSync5, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
|
|
17163
17317
|
import { join as join8 } from "node:path";
|
|
17164
17318
|
async function locate(slug, opts, fetchImpl, apiUrl) {
|
|
17165
17319
|
const cwd = opts.cwd ?? process.cwd();
|
|
17166
17320
|
let areas = knownAreas(cwd, opts.home);
|
|
17167
17321
|
if (opts.target) {
|
|
17168
|
-
areas = areas.filter((a) => a.
|
|
17322
|
+
areas = areas.filter((a) => a.tools.includes(opts.target) && (opts.global ? a.global : a.project));
|
|
17169
17323
|
}
|
|
17170
17324
|
const hits = [];
|
|
17171
17325
|
for (const area of areas) {
|
|
@@ -17186,7 +17340,7 @@ async function locate(slug, opts, fetchImpl, apiUrl) {
|
|
|
17186
17340
|
hits.push({ area, dir: area.dir, installedVersion: packMember.pack.version, packSlug: slug });
|
|
17187
17341
|
continue;
|
|
17188
17342
|
}
|
|
17189
|
-
if (
|
|
17343
|
+
if (existsSync5(dir)) {
|
|
17190
17344
|
const identified = await identifyByHash(fetchImpl, apiUrl, slug, dir);
|
|
17191
17345
|
if (identified !== void 0) {
|
|
17192
17346
|
hits.push({ area, dir, installedVersion: identified });
|
|
@@ -17207,30 +17361,23 @@ function permissionChanges(installed, target) {
|
|
|
17207
17361
|
function swapTree(files, dir) {
|
|
17208
17362
|
const fresh = `${dir}.new-${process.pid}`;
|
|
17209
17363
|
const aside = `${dir}.old-${process.pid}`;
|
|
17364
|
+
rmSync3(fresh, { recursive: true, force: true });
|
|
17210
17365
|
writeTree(files, fresh);
|
|
17211
|
-
renameSync2(dir, aside);
|
|
17212
17366
|
try {
|
|
17367
|
+
if (existsSync5(dir)) renameSync2(dir, aside);
|
|
17213
17368
|
renameSync2(fresh, dir);
|
|
17214
17369
|
} catch (err2) {
|
|
17215
|
-
renameSync2(aside, dir);
|
|
17370
|
+
if (existsSync5(aside) && !existsSync5(dir)) renameSync2(aside, dir);
|
|
17216
17371
|
rmSync3(fresh, { recursive: true, force: true });
|
|
17217
17372
|
throw err2;
|
|
17218
17373
|
}
|
|
17219
17374
|
rmSync3(aside, { recursive: true, force: true });
|
|
17220
17375
|
}
|
|
17221
17376
|
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 });
|
|
17377
|
+
const { push, done } = createOutput(opts.emit);
|
|
17231
17378
|
const st = opts.style ?? PLAIN;
|
|
17232
17379
|
if (opts.global && !opts.target) {
|
|
17233
|
-
push(
|
|
17380
|
+
push(GLOBAL_NEEDS_TARGET);
|
|
17234
17381
|
return done(2);
|
|
17235
17382
|
}
|
|
17236
17383
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
@@ -17243,9 +17390,7 @@ async function runUpdate(ref, opts = {}) {
|
|
|
17243
17390
|
const hits = await locate(slug, opts, fetchImpl, apiUrl);
|
|
17244
17391
|
const memberHit = hits.find((h) => h.packSlug !== void 0 && h.packSlug !== slug);
|
|
17245
17392
|
if (memberHit?.packSlug !== void 0) {
|
|
17246
|
-
push(
|
|
17247
|
-
`error: ${slug} is part of the ${memberHit.packSlug} pack; run skillpass update ${memberHit.packSlug}`
|
|
17248
|
-
);
|
|
17393
|
+
push(`error: ${slug} is part of the ${memberHit.packSlug} pack; run skillpass update ${memberHit.packSlug}`);
|
|
17249
17394
|
return done(2);
|
|
17250
17395
|
}
|
|
17251
17396
|
if (hits.length === 0) {
|
|
@@ -17280,19 +17425,14 @@ async function runUpdate(ref, opts = {}) {
|
|
|
17280
17425
|
if (preflight.blocked) {
|
|
17281
17426
|
return done(1);
|
|
17282
17427
|
}
|
|
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
|
-
}
|
|
17428
|
+
const confirmed = await confirmRisk(
|
|
17429
|
+
preflight,
|
|
17430
|
+
opts,
|
|
17431
|
+
`Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
|
|
17432
|
+
"Update aborted; the installed version was left in place.",
|
|
17433
|
+
push
|
|
17434
|
+
);
|
|
17435
|
+
if (!confirmed) return done(2);
|
|
17296
17436
|
const download = await downloadVerified(fetchImpl, apiUrl, slug, preflight.version, preflight.sourceHash);
|
|
17297
17437
|
if (!download.ok) {
|
|
17298
17438
|
push("", `error: ${download.message}`);
|
|
@@ -17304,11 +17444,7 @@ async function runUpdate(ref, opts = {}) {
|
|
|
17304
17444
|
push("", "error: could not replace the install; the installed version was left in place");
|
|
17305
17445
|
return done(2);
|
|
17306
17446
|
}
|
|
17307
|
-
recordReceipt(installed.area.dir, slug,
|
|
17308
|
-
version: preflight.version,
|
|
17309
|
-
sourceHash: preflight.sourceHash,
|
|
17310
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17311
|
-
});
|
|
17447
|
+
recordReceipt(installed.area.dir, slug, receiptFor(preflight));
|
|
17312
17448
|
push(
|
|
17313
17449
|
"",
|
|
17314
17450
|
`Updated ${slug} ${installed.installedVersion ?? "?"} -> ${preflight.version} in ${installed.dir}`,
|
|
@@ -17326,9 +17462,7 @@ async function changesVsInstalled(fetchImpl, apiUrl, slug, installed, target, st
|
|
|
17326
17462
|
publicPreflightSchema
|
|
17327
17463
|
);
|
|
17328
17464
|
if (!before.ok) {
|
|
17329
|
-
return [
|
|
17330
|
-
`Changes vs installed v${installed.installedVersion}: unavailable (that version is no longer published)`
|
|
17331
|
-
];
|
|
17465
|
+
return [`Changes vs installed v${installed.installedVersion}: unavailable (that version is no longer published)`];
|
|
17332
17466
|
}
|
|
17333
17467
|
const { added, removed } = permissionChanges(before.data.permissions, target.permissions);
|
|
17334
17468
|
if (added.length === 0 && removed.length === 0) {
|
|
@@ -17354,23 +17488,19 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
|
|
|
17354
17488
|
if (preflight.blocked) {
|
|
17355
17489
|
return done(1);
|
|
17356
17490
|
}
|
|
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
|
-
}
|
|
17491
|
+
const confirmed = await confirmRisk(
|
|
17492
|
+
preflight,
|
|
17493
|
+
opts,
|
|
17494
|
+
`Update ${slug} to ${preflight.version} (${preflight.riskLevel} risk)? [y/N] `,
|
|
17495
|
+
"Update aborted; the installed version was left in place.",
|
|
17496
|
+
push
|
|
17497
|
+
);
|
|
17498
|
+
if (!confirmed) return done(2);
|
|
17370
17499
|
const areaDir = installed.area.dir;
|
|
17371
|
-
const
|
|
17500
|
+
const tool = opts.target ?? installed.area.tools[0];
|
|
17501
|
+
const { installs, skipped } = resolvePackMembers(detail.packMembers ?? [], installed.area.layout);
|
|
17372
17502
|
if (installs.length === 0) {
|
|
17373
|
-
push("", `error: none of the new version's skills support ${
|
|
17503
|
+
push("", `error: none of the new version's skills support ${tool}`);
|
|
17374
17504
|
return done(2);
|
|
17375
17505
|
}
|
|
17376
17506
|
const receipts = readReceipts(areaDir);
|
|
@@ -17378,9 +17508,7 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
|
|
|
17378
17508
|
const newNames = new Set(installs.map((m) => m.name));
|
|
17379
17509
|
const toRemove = currentMembers.filter((name) => !newNames.has(name));
|
|
17380
17510
|
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
|
-
);
|
|
17511
|
+
const conflicts = additions.map((m) => join8(areaDir, m.name)).filter(isOccupied);
|
|
17384
17512
|
if (conflicts.length > 0) {
|
|
17385
17513
|
push(
|
|
17386
17514
|
"",
|
|
@@ -17394,31 +17522,26 @@ async function runPackUpdate(slug, installed, detail, preflight, opts, fetchImpl
|
|
|
17394
17522
|
push("", `error: ${download.message}`);
|
|
17395
17523
|
return done(2);
|
|
17396
17524
|
}
|
|
17397
|
-
const plans = installs
|
|
17525
|
+
const plans = planMembers(installs, download.files);
|
|
17398
17526
|
const missing = plans.find((p) => p.files.length === 0);
|
|
17399
17527
|
if (missing) {
|
|
17400
17528
|
push("", `error: the snapshot has no files for "${missing.name}"; nothing was changed`);
|
|
17401
17529
|
return done(2);
|
|
17402
17530
|
}
|
|
17403
17531
|
for (const name of skipped) {
|
|
17404
|
-
push("", `note: ${name} does not support ${
|
|
17532
|
+
push("", `note: ${name} does not support ${tool}; skipped`);
|
|
17405
17533
|
}
|
|
17406
17534
|
const done1 = [];
|
|
17407
17535
|
try {
|
|
17408
17536
|
for (const plan of plans) {
|
|
17409
17537
|
const dest = join8(areaDir, plan.name);
|
|
17410
|
-
if (
|
|
17538
|
+
if (existsSync5(dest)) {
|
|
17411
17539
|
swapTree(plan.files, dest);
|
|
17412
17540
|
} else {
|
|
17413
17541
|
writeTree(plan.files, dest);
|
|
17414
17542
|
}
|
|
17415
17543
|
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
|
-
});
|
|
17544
|
+
recordReceipt(areaDir, plan.name, receiptFor(preflight, slug));
|
|
17422
17545
|
}
|
|
17423
17546
|
} catch {
|
|
17424
17547
|
push(
|
|
@@ -17487,11 +17610,11 @@ var USAGE = [
|
|
|
17487
17610
|
"skillpass - validate AI agent skills locally and inspect hosted passports",
|
|
17488
17611
|
"",
|
|
17489
17612
|
"Usage:",
|
|
17490
|
-
" skillpass search [query] [--target <tool>] [--category <slug>] [--packs] [--json]",
|
|
17613
|
+
" skillpass search [query] [--ai] [--target <tool>] [--category <slug>] [--packs] [--json]",
|
|
17491
17614
|
" find skills in the directory",
|
|
17492
17615
|
" skillpass scan <path> [--json] run the validator on a local skill package",
|
|
17493
17616
|
" skillpass report <slug>[@version] [--json] fetch the hosted passport pre-flight",
|
|
17494
|
-
" skillpass add <slug>[@version] [--target <tool
|
|
17617
|
+
" skillpass add <slug>[@version] [--target <tool>... [--global] | --dir <path>] [--yes]",
|
|
17495
17618
|
" install a skill through the pre-flight gate",
|
|
17496
17619
|
" skillpass remove <slug> [--target <tool> [--global] | --dir <path>]",
|
|
17497
17620
|
" remove an installed skill",
|
|
@@ -17502,11 +17625,15 @@ var USAGE = [
|
|
|
17502
17625
|
"",
|
|
17503
17626
|
"Flags:",
|
|
17504
17627
|
" --json print machine-readable JSON instead of the readable report",
|
|
17505
|
-
" --target
|
|
17506
|
-
"
|
|
17507
|
-
"
|
|
17628
|
+
" --target a tool's skills folder: claude-code, cline, or agents (the shared",
|
|
17629
|
+
" .agents/skills read by codex, cursor, windsurf, github-copilot,",
|
|
17630
|
+
" gemini-cli, and opencode; those names work too). add takes it more",
|
|
17631
|
+
" than once to install into several tools",
|
|
17632
|
+
" --global with --target, use the tool's user-level folder (~/.claude/skills,",
|
|
17633
|
+
" ~/.agents/skills, ~/.cline/skills)",
|
|
17508
17634
|
" --dir install target directory (default ./<slug>)",
|
|
17509
17635
|
" --yes skip the confirmation prompt for medium+ risk skills",
|
|
17636
|
+
" --ai search by meaning: describe what you need (needs a query)",
|
|
17510
17637
|
" --category search filter: a directory category slug",
|
|
17511
17638
|
" --packs search filter: multi-skill packs only",
|
|
17512
17639
|
" --version print the CLI version",
|
|
@@ -17524,7 +17651,7 @@ var COMMAND_FLAGS = {
|
|
|
17524
17651
|
list: [],
|
|
17525
17652
|
outdated: [],
|
|
17526
17653
|
update: ["--yes", "--target", "--global"],
|
|
17527
|
-
search: ["--target", "--category", "--packs", "--json"]
|
|
17654
|
+
search: ["--ai", "--target", "--category", "--packs", "--json"]
|
|
17528
17655
|
};
|
|
17529
17656
|
function parseCliArgs(argv) {
|
|
17530
17657
|
const parsed = {
|
|
@@ -17535,6 +17662,7 @@ function parseCliArgs(argv) {
|
|
|
17535
17662
|
yes: false,
|
|
17536
17663
|
global: false,
|
|
17537
17664
|
packs: false,
|
|
17665
|
+
ai: false,
|
|
17538
17666
|
seen: []
|
|
17539
17667
|
};
|
|
17540
17668
|
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
@@ -17551,6 +17679,9 @@ function parseCliArgs(argv) {
|
|
|
17551
17679
|
} else if (arg === "--packs") {
|
|
17552
17680
|
parsed.packs = true;
|
|
17553
17681
|
parsed.seen.push(arg);
|
|
17682
|
+
} else if (arg === "--ai") {
|
|
17683
|
+
parsed.ai = true;
|
|
17684
|
+
parsed.seen.push(arg);
|
|
17554
17685
|
} else if (arg === "--dir" || arg === "--target" || arg === "--category") {
|
|
17555
17686
|
parsed.seen.push(arg);
|
|
17556
17687
|
const value = argv[i2 + 1];
|
|
@@ -17561,7 +17692,7 @@ function parseCliArgs(argv) {
|
|
|
17561
17692
|
if (arg === "--dir") {
|
|
17562
17693
|
parsed.dir = value;
|
|
17563
17694
|
} else if (arg === "--target") {
|
|
17564
|
-
parsed.
|
|
17695
|
+
parsed.targets = [.../* @__PURE__ */ new Set([...parsed.targets ?? [], value])];
|
|
17565
17696
|
} else {
|
|
17566
17697
|
parsed.category = value;
|
|
17567
17698
|
}
|
|
@@ -17611,12 +17742,17 @@ async function run(argv) {
|
|
|
17611
17742
|
if (disallowed) {
|
|
17612
17743
|
return { lines: [`error: ${args.command} does not take ${disallowed}`, "", USAGE], exitCode: 2 };
|
|
17613
17744
|
}
|
|
17745
|
+
const target = args.targets?.[0];
|
|
17746
|
+
if (args.command !== "add" && (args.targets?.length ?? 0) > 1) {
|
|
17747
|
+
return { lines: [`error: ${args.command} takes one --target`, "", USAGE], exitCode: 2 };
|
|
17748
|
+
}
|
|
17614
17749
|
if (args.command === "search") {
|
|
17615
17750
|
return runSearch({
|
|
17616
17751
|
query: args.positional.join(" "),
|
|
17617
|
-
target
|
|
17752
|
+
target,
|
|
17618
17753
|
category: args.category,
|
|
17619
17754
|
packs: args.packs,
|
|
17755
|
+
ai: args.ai,
|
|
17620
17756
|
json: args.json,
|
|
17621
17757
|
width: process.stdout.isTTY ? process.stdout.columns : void 0,
|
|
17622
17758
|
style: styler(Boolean(process.stdout.isTTY))
|
|
@@ -17634,7 +17770,7 @@ async function run(argv) {
|
|
|
17634
17770
|
if (!slug) {
|
|
17635
17771
|
return { lines: ["error: remove needs a skill slug", "", USAGE], exitCode: 2 };
|
|
17636
17772
|
}
|
|
17637
|
-
return runRemove(slug, { target
|
|
17773
|
+
return runRemove(slug, { target, dir: args.dir, global: args.global });
|
|
17638
17774
|
}
|
|
17639
17775
|
if (args.command === "list") {
|
|
17640
17776
|
if (args.positional.length > 0) {
|
|
@@ -17650,7 +17786,7 @@ async function run(argv) {
|
|
|
17650
17786
|
const tty = Boolean(process.stdin.isTTY);
|
|
17651
17787
|
return runUpdate(ref, {
|
|
17652
17788
|
yes: args.yes,
|
|
17653
|
-
target
|
|
17789
|
+
target,
|
|
17654
17790
|
global: args.global,
|
|
17655
17791
|
style: styler(Boolean(process.stdout.isTTY)),
|
|
17656
17792
|
confirmImpl: tty ? confirmViaTty : void 0,
|
|
@@ -17679,7 +17815,7 @@ async function run(argv) {
|
|
|
17679
17815
|
return runAdd(ref, {
|
|
17680
17816
|
yes: args.yes,
|
|
17681
17817
|
dir: args.dir,
|
|
17682
|
-
target: args.
|
|
17818
|
+
target: args.targets,
|
|
17683
17819
|
global: args.global,
|
|
17684
17820
|
style: styler(Boolean(process.stdout.isTTY)),
|
|
17685
17821
|
confirmImpl: tty ? confirmViaTty : void 0,
|
|
@@ -17689,10 +17825,17 @@ async function run(argv) {
|
|
|
17689
17825
|
}
|
|
17690
17826
|
return { lines: [`error: unknown command "${args.command}"`, "", USAGE], exitCode: 2 };
|
|
17691
17827
|
}
|
|
17692
|
-
async function main(argv) {
|
|
17693
|
-
|
|
17828
|
+
async function main(argv, runImpl = run) {
|
|
17829
|
+
let result;
|
|
17830
|
+
try {
|
|
17831
|
+
result = await runImpl(argv);
|
|
17832
|
+
} catch (err2) {
|
|
17833
|
+
console.error(`error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
17834
|
+
process.exitCode = 2;
|
|
17835
|
+
return;
|
|
17836
|
+
}
|
|
17694
17837
|
if (!result.streamed) {
|
|
17695
|
-
console.log(result.lines.join("\n"));
|
|
17838
|
+
(result.exitCode === 2 ? console.error : console.log)(result.lines.join("\n"));
|
|
17696
17839
|
}
|
|
17697
17840
|
process.exitCode = result.exitCode;
|
|
17698
17841
|
}
|