@ichintansoni/skills-master 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/bin.js +569 -182
- package/package.json +7 -4
- package/dist/bin.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -4,15 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// package.json
|
|
7
|
-
var version = "0.1.
|
|
8
|
-
|
|
9
|
-
// src/types.ts
|
|
10
|
-
var ALL_TARGETS = ["claude", "cursor", "copilot", "agents"];
|
|
11
|
-
var RESOURCE_FILES = {
|
|
12
|
-
reference: "reference.md",
|
|
13
|
-
examples: "examples.md",
|
|
14
|
-
checklist: "checklist.md"
|
|
15
|
-
};
|
|
7
|
+
var version = "0.1.7";
|
|
16
8
|
|
|
17
9
|
// src/schema/projectConfig.ts
|
|
18
10
|
import { z } from "zod";
|
|
@@ -38,7 +30,8 @@ var ProjectConfigSchema = z.object({
|
|
|
38
30
|
copilot: z.string(),
|
|
39
31
|
agents: z.string()
|
|
40
32
|
}).partial().default({}),
|
|
41
|
-
|
|
33
|
+
/** @deprecated accepted for configs written by older versions; never written. */
|
|
34
|
+
scope: z.enum(["project"]).optional(),
|
|
42
35
|
/** true = commit generated files; false = add them to .gitignore. */
|
|
43
36
|
commit: z.boolean().default(true)
|
|
44
37
|
}).strict();
|
|
@@ -48,6 +41,32 @@ function resolvePaths(cfg) {
|
|
|
48
41
|
return { ...DEFAULT_PATHS, ...cfg.paths };
|
|
49
42
|
}
|
|
50
43
|
|
|
44
|
+
// src/types.ts
|
|
45
|
+
var ALL_TARGETS = ["claude", "cursor", "copilot", "agents"];
|
|
46
|
+
var RESOURCE_FILES = {
|
|
47
|
+
reference: "reference.md",
|
|
48
|
+
examples: "examples.md",
|
|
49
|
+
checklist: "checklist.md"
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/core/stability-note.ts
|
|
53
|
+
function stabilityNote(stability, snapshotDate) {
|
|
54
|
+
switch (stability) {
|
|
55
|
+
case "stable":
|
|
56
|
+
return null;
|
|
57
|
+
case "emerging":
|
|
58
|
+
return `> **Emerging** \u2014 this covers an API that was pre-1.0, newly shipped, or still moving as of ${snapshotDate}. Treat the specifics as provisional and confirm against current documentation before relying on them.`;
|
|
59
|
+
case "contested":
|
|
60
|
+
return "> **Contested** \u2014 practitioners disagree here and the vendor does not prescribe an answer. Weigh the tradeoffs for the project at hand rather than adopting one option as the default.";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function withStabilityNote(body, note) {
|
|
64
|
+
if (!note) return body;
|
|
65
|
+
return `${note}
|
|
66
|
+
|
|
67
|
+
${body.replace(/^\n+/, "")}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
// src/core/yaml.ts
|
|
52
71
|
import YAML from "yaml";
|
|
53
72
|
function toYaml(obj) {
|
|
@@ -71,10 +90,11 @@ import { join } from "path";
|
|
|
71
90
|
function existsRel(root, rel) {
|
|
72
91
|
return existsSync(join(root, rel));
|
|
73
92
|
}
|
|
74
|
-
var
|
|
93
|
+
var CASED_TOKENS = {
|
|
75
94
|
hig: "HIG",
|
|
76
95
|
ui: "UI",
|
|
77
96
|
ml: "ML",
|
|
97
|
+
ai: "AI",
|
|
78
98
|
ar: "AR",
|
|
79
99
|
av: "AV",
|
|
80
100
|
os: "OS",
|
|
@@ -84,15 +104,48 @@ var ACRONYMS = {
|
|
|
84
104
|
api: "API",
|
|
85
105
|
sf: "SF",
|
|
86
106
|
spm: "SPM",
|
|
107
|
+
m3: "M3",
|
|
108
|
+
nfc: "NFC",
|
|
109
|
+
http: "HTTP",
|
|
110
|
+
sqlite: "SQLite",
|
|
87
111
|
ios: "iOS",
|
|
88
112
|
ipados: "iPadOS",
|
|
89
113
|
macos: "macOS",
|
|
90
114
|
tvos: "tvOS",
|
|
91
115
|
visionos: "visionOS",
|
|
92
|
-
watchos: "watchOS"
|
|
116
|
+
watchos: "watchOS",
|
|
117
|
+
chromeos: "ChromeOS",
|
|
118
|
+
swiftui: "SwiftUI",
|
|
119
|
+
swiftdata: "SwiftData",
|
|
120
|
+
uikit: "UIKit",
|
|
121
|
+
appkit: "AppKit",
|
|
122
|
+
xcode: "Xcode",
|
|
123
|
+
xctest: "XCTest",
|
|
124
|
+
viewmodel: "ViewModel",
|
|
125
|
+
workmanager: "WorkManager",
|
|
126
|
+
activitykit: "ActivityKit",
|
|
127
|
+
arkit: "ARKit",
|
|
128
|
+
cloudkit: "CloudKit",
|
|
129
|
+
cryptokit: "CryptoKit",
|
|
130
|
+
eventkit: "EventKit",
|
|
131
|
+
gamekit: "GameKit",
|
|
132
|
+
healthkit: "HealthKit",
|
|
133
|
+
mapkit: "MapKit",
|
|
134
|
+
musickit: "MusicKit",
|
|
135
|
+
passkit: "PassKit",
|
|
136
|
+
pencilkit: "PencilKit",
|
|
137
|
+
photokit: "PhotoKit",
|
|
138
|
+
realitykit: "RealityKit",
|
|
139
|
+
scenekit: "SceneKit",
|
|
140
|
+
screencapturekit: "ScreenCaptureKit",
|
|
141
|
+
spritekit: "SpriteKit",
|
|
142
|
+
storekit: "StoreKit",
|
|
143
|
+
tipkit: "TipKit",
|
|
144
|
+
weatherkit: "WeatherKit",
|
|
145
|
+
widgetkit: "WidgetKit"
|
|
93
146
|
};
|
|
94
147
|
function titleFromName(name) {
|
|
95
|
-
return name.split("-").filter(Boolean).map((w) =>
|
|
148
|
+
return name.split("-").filter(Boolean).map((w) => CASED_TOKENS[w] ?? w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
96
149
|
}
|
|
97
150
|
function globsToString(fm) {
|
|
98
151
|
const g = fm.globs;
|
|
@@ -110,6 +163,8 @@ var claudeEmitter = {
|
|
|
110
163
|
detect: (root) => existsRel(root, ".claude"),
|
|
111
164
|
emit(skill, ctx) {
|
|
112
165
|
const dir = `${ctx.paths.claude}/${skill.name}`;
|
|
166
|
+
const xm = skill.frontmatter["x-skills-master"];
|
|
167
|
+
const note = stabilityNote(xm.stability, xm.snapshot_date);
|
|
113
168
|
const fm = {
|
|
114
169
|
name: skill.frontmatter.name,
|
|
115
170
|
description: skill.frontmatter.description
|
|
@@ -117,7 +172,7 @@ var claudeEmitter = {
|
|
|
117
172
|
const files = [
|
|
118
173
|
{
|
|
119
174
|
path: `${dir}/SKILL.md`,
|
|
120
|
-
contents: withFrontmatter(fm, skill.body),
|
|
175
|
+
contents: withFrontmatter(fm, withStabilityNote(skill.body, note)),
|
|
121
176
|
mode: "whole"
|
|
122
177
|
}
|
|
123
178
|
];
|
|
@@ -152,12 +207,12 @@ function condenseBody(body, opts = {}) {
|
|
|
152
207
|
if (opts.hadResources || strippedLink) {
|
|
153
208
|
out += `
|
|
154
209
|
|
|
155
|
-
> ${
|
|
210
|
+
> ${DEFAULT_NOTE}`;
|
|
156
211
|
}
|
|
157
212
|
return out + "\n";
|
|
158
213
|
}
|
|
159
214
|
function summarizeOpenQuestion(body) {
|
|
160
|
-
const re = /^## Open question[ \t]*\n([\s\S]*?)(?=\n##
|
|
215
|
+
const re = /^## Open question[ \t]*\n([\s\S]*?)(?=\n## |(?![\s\S]))/m;
|
|
161
216
|
return body.replace(re, (_m, section) => {
|
|
162
217
|
const firstPara = section.trim().split(/\n\s*\n/)[0]?.replace(/\s+/g, " ").trim() ?? "";
|
|
163
218
|
return `## Open question
|
|
@@ -166,6 +221,43 @@ Tradeoff: ${firstPara}
|
|
|
166
221
|
`;
|
|
167
222
|
});
|
|
168
223
|
}
|
|
224
|
+
function sectionHighlights(body, section, max) {
|
|
225
|
+
const re = new RegExp(`^## ${section}\\n([\\s\\S]*?)(?=\\n## |(?![\\s\\S]))`, "m");
|
|
226
|
+
const m = re.exec(body);
|
|
227
|
+
if (!m) return [];
|
|
228
|
+
const text = m[1];
|
|
229
|
+
const bullets = [];
|
|
230
|
+
let inFence = false;
|
|
231
|
+
for (const line of text.split("\n")) {
|
|
232
|
+
if (/^(```|~~~)/.test(line.trimStart())) {
|
|
233
|
+
inFence = !inFence;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (!inFence && /^- /.test(line.trim())) bullets.push(line.trim());
|
|
237
|
+
if (bullets.length === max) break;
|
|
238
|
+
}
|
|
239
|
+
if (bullets.length > 0) return bullets;
|
|
240
|
+
const firstPara = text.trim().split(/\n\s*\n/)[0]?.replace(/\s+/g, " ").trim();
|
|
241
|
+
return firstPara ? [firstPara] : [];
|
|
242
|
+
}
|
|
243
|
+
function digestBody(body, opts) {
|
|
244
|
+
const guidance = sectionHighlights(body, "Core guidance", 6);
|
|
245
|
+
const pitfalls = sectionHighlights(body, "Pitfalls", 3);
|
|
246
|
+
const flatten = (lines) => lines.map((l) => l.replace(L3_LINK_RE, (_m, text) => text));
|
|
247
|
+
const parts = [];
|
|
248
|
+
if (opts.stabilityNote) parts.push(opts.stabilityNote);
|
|
249
|
+
parts.push(opts.description.trim());
|
|
250
|
+
if (guidance.length > 0) parts.push(`#### Core guidance
|
|
251
|
+
|
|
252
|
+
${flatten(guidance).join("\n")}`);
|
|
253
|
+
if (pitfalls.length > 0) parts.push(`#### Pitfalls
|
|
254
|
+
|
|
255
|
+
${flatten(pitfalls).join("\n")}`);
|
|
256
|
+
parts.push(
|
|
257
|
+
`> Digest only \u2014 the complete skill (full guidance, examples, references) ships with the Claude Code, Cursor, and Copilot projections, or via \`skills-master view ${opts.name}\`.`
|
|
258
|
+
);
|
|
259
|
+
return parts.join("\n\n");
|
|
260
|
+
}
|
|
169
261
|
|
|
170
262
|
// src/emitters/cursor.ts
|
|
171
263
|
var cursorEmitter = {
|
|
@@ -179,10 +271,14 @@ var cursorEmitter = {
|
|
|
179
271
|
};
|
|
180
272
|
if (globs) fm.globs = globs;
|
|
181
273
|
fm.alwaysApply = false;
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
274
|
+
const xm = skill.frontmatter["x-skills-master"];
|
|
275
|
+
const body = withStabilityNote(
|
|
276
|
+
condenseBody(skill.body, {
|
|
277
|
+
openQuestion: "keep",
|
|
278
|
+
hadResources: hasResources(skill.resources)
|
|
279
|
+
}),
|
|
280
|
+
stabilityNote(xm.stability, xm.snapshot_date)
|
|
281
|
+
);
|
|
186
282
|
return [
|
|
187
283
|
{
|
|
188
284
|
path: `${ctx.paths.cursor}/${skill.name}.mdc`,
|
|
@@ -197,19 +293,25 @@ var cursorEmitter = {
|
|
|
197
293
|
var copilotEmitter = {
|
|
198
294
|
id: "copilot",
|
|
199
295
|
label: "GitHub Copilot",
|
|
200
|
-
|
|
296
|
+
// A bare .github/ (workflows, templates) says nothing about Copilot use —
|
|
297
|
+
// detect only on Copilot's own customization files.
|
|
298
|
+
detect: (root) => existsRel(root, ".github/copilot-instructions.md") || existsRel(root, ".github/instructions"),
|
|
201
299
|
emit(skill, ctx) {
|
|
202
300
|
const base = ctx.paths.copilot;
|
|
203
301
|
const instructionsPath = `${base}/instructions/${skill.name}.instructions.md`;
|
|
204
|
-
const applyTo = globsToString(skill.frontmatter)
|
|
302
|
+
const applyTo = globsToString(skill.frontmatter);
|
|
205
303
|
const fm = {
|
|
206
|
-
applyTo,
|
|
304
|
+
...applyTo ? { applyTo } : {},
|
|
207
305
|
description: skill.frontmatter.description
|
|
208
306
|
};
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
307
|
+
const xm = skill.frontmatter["x-skills-master"];
|
|
308
|
+
const body = withStabilityNote(
|
|
309
|
+
condenseBody(skill.body, {
|
|
310
|
+
openQuestion: "keep",
|
|
311
|
+
hadResources: hasResources(skill.resources)
|
|
312
|
+
}),
|
|
313
|
+
stabilityNote(xm.stability, xm.snapshot_date)
|
|
314
|
+
);
|
|
213
315
|
const pointer = `For ${titleFromName(skill.name)} guidance, see \`${instructionsPath}\`.`;
|
|
214
316
|
return [
|
|
215
317
|
{
|
|
@@ -234,9 +336,11 @@ var agentsEmitter = {
|
|
|
234
336
|
label: "AGENTS.md",
|
|
235
337
|
detect: (root) => existsRel(root, "AGENTS.md"),
|
|
236
338
|
emit(skill, ctx) {
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
339
|
+
const xm = skill.frontmatter["x-skills-master"];
|
|
340
|
+
const body = digestBody(skill.body, {
|
|
341
|
+
name: skill.name,
|
|
342
|
+
description: skill.frontmatter.description,
|
|
343
|
+
stabilityNote: stabilityNote(xm.stability, xm.snapshot_date)
|
|
240
344
|
});
|
|
241
345
|
const section = `### ${titleFromName(skill.name)}
|
|
242
346
|
|
|
@@ -254,12 +358,7 @@ ${body.trim()}`;
|
|
|
254
358
|
};
|
|
255
359
|
|
|
256
360
|
// src/emitters/index.ts
|
|
257
|
-
var EMITTERS = [
|
|
258
|
-
claudeEmitter,
|
|
259
|
-
cursorEmitter,
|
|
260
|
-
copilotEmitter,
|
|
261
|
-
agentsEmitter
|
|
262
|
-
];
|
|
361
|
+
var EMITTERS = [claudeEmitter, cursorEmitter, copilotEmitter, agentsEmitter];
|
|
263
362
|
var BY_ID = new Map(EMITTERS.map((e) => [e.id, e]));
|
|
264
363
|
function getEmitter(id) {
|
|
265
364
|
return BY_ID.get(id);
|
|
@@ -271,6 +370,7 @@ function detectTargets(projectRoot) {
|
|
|
271
370
|
// src/core/project.ts
|
|
272
371
|
import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
|
|
273
372
|
import { join as join2 } from "path";
|
|
373
|
+
import { z as z3 } from "zod";
|
|
274
374
|
|
|
275
375
|
// src/schema/lockfile.ts
|
|
276
376
|
import { z as z2 } from "zod";
|
|
@@ -300,6 +400,25 @@ function emptyLockfile(contentRef = "main") {
|
|
|
300
400
|
}
|
|
301
401
|
|
|
302
402
|
// src/core/project.ts
|
|
403
|
+
function parseJsonFile(p, schema) {
|
|
404
|
+
let data;
|
|
405
|
+
try {
|
|
406
|
+
data = JSON.parse(readFileSync(p, "utf8"));
|
|
407
|
+
} catch (err) {
|
|
408
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
409
|
+
throw new Error(`${p} is not valid JSON: ${msg}`);
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
return schema.parse(data);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
if (err instanceof z3.ZodError) {
|
|
415
|
+
const first = err.issues[0];
|
|
416
|
+
const at = first?.path.length ? ` at "${first.path.join(".")}"` : "";
|
|
417
|
+
throw new Error(`${p} is invalid${at}: ${first?.message ?? "schema mismatch"}`);
|
|
418
|
+
}
|
|
419
|
+
throw err;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
303
422
|
function configPath(root) {
|
|
304
423
|
return join2(root, CONFIG_FILENAME);
|
|
305
424
|
}
|
|
@@ -309,7 +428,7 @@ function lockfilePath(root) {
|
|
|
309
428
|
function loadConfig(root) {
|
|
310
429
|
const p = configPath(root);
|
|
311
430
|
if (!existsSync2(p)) return null;
|
|
312
|
-
return
|
|
431
|
+
return parseJsonFile(p, ProjectConfigSchema);
|
|
313
432
|
}
|
|
314
433
|
function loadConfigOrDefault(root) {
|
|
315
434
|
return loadConfig(root) ?? ProjectConfigSchema.parse({});
|
|
@@ -321,14 +440,13 @@ function saveConfig(root, cfg) {
|
|
|
321
440
|
function loadLockfile(root) {
|
|
322
441
|
const p = lockfilePath(root);
|
|
323
442
|
if (!existsSync2(p)) return emptyLockfile();
|
|
324
|
-
return
|
|
443
|
+
return parseJsonFile(p, LockfileSchema);
|
|
325
444
|
}
|
|
326
445
|
function saveLockfile(root, lock) {
|
|
327
446
|
writeFileSync(lockfilePath(root), JSON.stringify(lock, null, 2) + "\n", "utf8");
|
|
328
447
|
}
|
|
329
448
|
|
|
330
449
|
// src/util/log.ts
|
|
331
|
-
var quiet = false;
|
|
332
450
|
var sym = {
|
|
333
451
|
info: "\u2022",
|
|
334
452
|
ok: "\u2713",
|
|
@@ -337,16 +455,16 @@ var sym = {
|
|
|
337
455
|
};
|
|
338
456
|
var log = {
|
|
339
457
|
plain(msg) {
|
|
340
|
-
|
|
458
|
+
console.log(msg);
|
|
341
459
|
},
|
|
342
460
|
info(msg) {
|
|
343
|
-
|
|
461
|
+
console.log(`${sym.info} ${msg}`);
|
|
344
462
|
},
|
|
345
463
|
success(msg) {
|
|
346
|
-
|
|
464
|
+
console.log(`${sym.ok} ${msg}`);
|
|
347
465
|
},
|
|
348
466
|
warn(msg) {
|
|
349
|
-
|
|
467
|
+
console.warn(`${sym.warn} ${msg}`);
|
|
350
468
|
},
|
|
351
469
|
error(msg) {
|
|
352
470
|
console.error(`${sym.err} ${msg}`);
|
|
@@ -357,7 +475,9 @@ var log = {
|
|
|
357
475
|
function initCommand(opts) {
|
|
358
476
|
const existing = loadConfig(opts.cwd);
|
|
359
477
|
if (existing && !opts.force) {
|
|
360
|
-
log.warn(
|
|
478
|
+
log.warn(
|
|
479
|
+
`skills-master.json already exists \u2014 leaving it untouched (use --force to overwrite).`
|
|
480
|
+
);
|
|
361
481
|
return existing;
|
|
362
482
|
}
|
|
363
483
|
let targets = opts.targets;
|
|
@@ -427,45 +547,40 @@ import { basename, join as join4 } from "path";
|
|
|
427
547
|
import matter from "gray-matter";
|
|
428
548
|
|
|
429
549
|
// src/schema/frontmatter.ts
|
|
430
|
-
import { z as
|
|
550
|
+
import { z as z4 } from "zod";
|
|
431
551
|
import semver from "semver";
|
|
432
552
|
var NAME_RE = /^[a-z0-9-]{1,64}$/;
|
|
433
553
|
var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
434
|
-
var StabilitySchema =
|
|
435
|
-
var SkillClassSchema =
|
|
436
|
-
"code",
|
|
437
|
-
"design",
|
|
438
|
-
"lang-tooling",
|
|
439
|
-
"overview"
|
|
440
|
-
]);
|
|
554
|
+
var StabilitySchema = z4.enum(["stable", "emerging", "contested"]);
|
|
555
|
+
var SkillClassSchema = z4.enum(["code", "design", "lang-tooling", "overview"]);
|
|
441
556
|
var CLASS_DIR = {
|
|
442
557
|
code: "code",
|
|
443
558
|
design: "design",
|
|
444
559
|
"lang-tooling": "lang-tooling",
|
|
445
560
|
overview: "overviews"
|
|
446
561
|
};
|
|
447
|
-
var XSkillsMasterSchema =
|
|
448
|
-
domain:
|
|
562
|
+
var XSkillsMasterSchema = z4.object({
|
|
563
|
+
domain: z4.string().min(1),
|
|
449
564
|
class: SkillClassSchema,
|
|
450
|
-
category:
|
|
451
|
-
platforms:
|
|
565
|
+
category: z4.string().min(1),
|
|
566
|
+
platforms: z4.array(z4.string().min(1)).min(1),
|
|
452
567
|
/** domain-defined version requirements, e.g. { ios: "17", swift: "6.0" }. */
|
|
453
|
-
requires:
|
|
454
|
-
pairs_with:
|
|
568
|
+
requires: z4.record(z4.string(), z4.string()).optional(),
|
|
569
|
+
pairs_with: z4.array(z4.string().regex(NAME_RE)).default([]),
|
|
455
570
|
/** citation URLs to canonical docs — never verbatim content. */
|
|
456
|
-
sources:
|
|
457
|
-
snapshot_date:
|
|
571
|
+
sources: z4.array(z4.url()).default([]),
|
|
572
|
+
snapshot_date: z4.string().regex(ISO_DATE_RE, "must be an ISO date (YYYY-MM-DD)"),
|
|
458
573
|
stability: StabilitySchema,
|
|
459
|
-
version:
|
|
574
|
+
version: z4.string().refine((v) => semver.valid(v) != null, "must be a valid semver version")
|
|
460
575
|
}).strict();
|
|
461
|
-
var GlobsSchema =
|
|
462
|
-
var FrontmatterSchema =
|
|
463
|
-
name:
|
|
464
|
-
description:
|
|
576
|
+
var GlobsSchema = z4.union([z4.string(), z4.array(z4.string())]).transform((g) => Array.isArray(g) ? g : [g]).optional();
|
|
577
|
+
var FrontmatterSchema = z4.looseObject({
|
|
578
|
+
name: z4.string().regex(NAME_RE, "must be kebab-case ([a-z0-9-], <=64 chars)"),
|
|
579
|
+
description: z4.string().min(1, "description is required").max(1024, "description must be <= 1024 characters"),
|
|
465
580
|
globs: GlobsSchema,
|
|
466
|
-
tags:
|
|
581
|
+
tags: z4.array(z4.string()).default([]),
|
|
467
582
|
"x-skills-master": XSkillsMasterSchema
|
|
468
|
-
})
|
|
583
|
+
});
|
|
469
584
|
|
|
470
585
|
// src/core/parse.ts
|
|
471
586
|
function loadRawSkill(dir, skillsRoot) {
|
|
@@ -555,16 +670,57 @@ function buildRegistry(skillsRoot, version2 = "0.1.0") {
|
|
|
555
670
|
};
|
|
556
671
|
}
|
|
557
672
|
|
|
673
|
+
// src/schema/registry.ts
|
|
674
|
+
import { z as z5 } from "zod";
|
|
675
|
+
var RegistryEntrySchema = z5.object({
|
|
676
|
+
name: z5.string(),
|
|
677
|
+
domain: z5.string(),
|
|
678
|
+
class: SkillClassSchema,
|
|
679
|
+
category: z5.string(),
|
|
680
|
+
description: z5.string(),
|
|
681
|
+
platforms: z5.array(z5.string()),
|
|
682
|
+
stability: StabilitySchema,
|
|
683
|
+
version: z5.string(),
|
|
684
|
+
tags: z5.array(z5.string()).default([]),
|
|
685
|
+
pairs_with: z5.array(z5.string()).default([]),
|
|
686
|
+
/** path relative to the skills root. */
|
|
687
|
+
path: z5.string(),
|
|
688
|
+
/** which on-demand resource files exist. */
|
|
689
|
+
resources: z5.object({
|
|
690
|
+
reference: z5.boolean(),
|
|
691
|
+
examples: z5.boolean(),
|
|
692
|
+
checklist: z5.boolean()
|
|
693
|
+
})
|
|
694
|
+
});
|
|
695
|
+
var RegistrySchema = z5.object({
|
|
696
|
+
$schema: z5.string().optional(),
|
|
697
|
+
/** aggregate library version (bumped on release). */
|
|
698
|
+
version: z5.string().default("0.1.0"),
|
|
699
|
+
skills: z5.array(RegistryEntrySchema).default([])
|
|
700
|
+
});
|
|
701
|
+
var REGISTRY_FILENAME = "registry.json";
|
|
702
|
+
|
|
558
703
|
// src/content/source.ts
|
|
559
704
|
var DEFAULT_REPO = "github:iChintanSoni/skills-master";
|
|
560
705
|
var CONTENT_REPO_PACKAGE = "skills-master-monorepo";
|
|
706
|
+
var SkillNotFoundError = class extends Error {
|
|
707
|
+
constructor(skillName, root) {
|
|
708
|
+
super(`Skill "${skillName}" not found in content at ${root}`);
|
|
709
|
+
this.skillName = skillName;
|
|
710
|
+
this.name = "SkillNotFoundError";
|
|
711
|
+
}
|
|
712
|
+
skillName;
|
|
713
|
+
};
|
|
561
714
|
var ContentSource = class {
|
|
562
715
|
constructor(root) {
|
|
563
716
|
this.root = root;
|
|
564
717
|
}
|
|
565
718
|
root;
|
|
719
|
+
#dirs;
|
|
720
|
+
#registry;
|
|
566
721
|
skillDirs() {
|
|
567
|
-
|
|
722
|
+
this.#dirs ??= findSkillDirs(this.root);
|
|
723
|
+
return this.#dirs;
|
|
568
724
|
}
|
|
569
725
|
findDir(name) {
|
|
570
726
|
const dirs = this.skillDirs();
|
|
@@ -572,11 +728,27 @@ var ContentSource = class {
|
|
|
572
728
|
}
|
|
573
729
|
loadSkill(name) {
|
|
574
730
|
const dir = this.findDir(name);
|
|
575
|
-
if (!dir) throw new
|
|
731
|
+
if (!dir) throw new SkillNotFoundError(name, this.root);
|
|
576
732
|
return loadSkill(dir, this.root);
|
|
577
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* The catalog used by list/search/add. Reads the committed registry.json
|
|
736
|
+
* when the content ships one (it is generated and CI-gated for drift);
|
|
737
|
+
* otherwise — or if the committed file is unreadable — falls back to
|
|
738
|
+
* scanning and parsing every skill, which is always ground truth.
|
|
739
|
+
*/
|
|
578
740
|
registry() {
|
|
579
|
-
|
|
741
|
+
this.#registry ??= this.readCommittedRegistry() ?? buildRegistry(this.root);
|
|
742
|
+
return this.#registry;
|
|
743
|
+
}
|
|
744
|
+
readCommittedRegistry() {
|
|
745
|
+
const p = join5(this.root, "registry.json");
|
|
746
|
+
if (!existsSync5(p)) return null;
|
|
747
|
+
try {
|
|
748
|
+
return RegistrySchema.parse(JSON.parse(readFileSync3(p, "utf8")));
|
|
749
|
+
} catch {
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
580
752
|
}
|
|
581
753
|
};
|
|
582
754
|
function safeName(dir, root) {
|
|
@@ -589,10 +761,15 @@ function safeName(dir, root) {
|
|
|
589
761
|
async function resolveContent(opts = {}) {
|
|
590
762
|
if (opts.content) {
|
|
591
763
|
const root = isAbsolute(opts.content) ? opts.content : resolve(process.cwd(), opts.content);
|
|
764
|
+
if (!existsSync5(root)) throw new Error(`--content directory not found: ${root}`);
|
|
592
765
|
return new ContentSource(root);
|
|
593
766
|
}
|
|
594
767
|
const env = process.env.SKILLS_MASTER_CONTENT;
|
|
595
|
-
if (env)
|
|
768
|
+
if (env) {
|
|
769
|
+
const root = resolve(env);
|
|
770
|
+
if (!existsSync5(root)) throw new Error(`SKILLS_MASTER_CONTENT directory not found: ${root}`);
|
|
771
|
+
return new ContentSource(root);
|
|
772
|
+
}
|
|
596
773
|
const local = findLocalSkillsDir(opts.cwd ?? process.cwd());
|
|
597
774
|
if (local) return new ContentSource(local);
|
|
598
775
|
return new ContentSource(await fetchRemote(opts.ref ?? "main"));
|
|
@@ -618,14 +795,24 @@ function isContentRepoRoot(dir) {
|
|
|
618
795
|
}
|
|
619
796
|
async function fetchRemote(ref) {
|
|
620
797
|
const repo = process.env.SKILLS_MASTER_REPO ?? DEFAULT_REPO;
|
|
621
|
-
const
|
|
798
|
+
const safeRef = ref.trim().replace(/[^\w.-]/g, "_");
|
|
799
|
+
if (!safeRef) throw new Error(`Invalid content ref "${ref}".`);
|
|
800
|
+
const cacheDir = join5(homedir(), ".skills-master-cache", safeRef);
|
|
622
801
|
const { downloadTemplate } = await import("giget");
|
|
623
|
-
|
|
624
|
-
dir
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
802
|
+
try {
|
|
803
|
+
const { dir } = await downloadTemplate(`${repo}/skills#${ref.trim()}`, {
|
|
804
|
+
dir: cacheDir,
|
|
805
|
+
force: true,
|
|
806
|
+
forceClean: true
|
|
807
|
+
});
|
|
808
|
+
return dir;
|
|
809
|
+
} catch (err) {
|
|
810
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
811
|
+
throw new Error(
|
|
812
|
+
`Failed to fetch skills content from ${repo}#${ref.trim()}: ${msg}
|
|
813
|
+
Check the ref, or point at a local checkout with --content <dir> or SKILLS_MASTER_CONTENT.`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
629
816
|
}
|
|
630
817
|
|
|
631
818
|
// src/core/install.ts
|
|
@@ -644,7 +831,15 @@ function compileSkill(skill, targets, ctx) {
|
|
|
644
831
|
}
|
|
645
832
|
|
|
646
833
|
// src/core/writer.ts
|
|
647
|
-
import {
|
|
834
|
+
import {
|
|
835
|
+
existsSync as existsSync6,
|
|
836
|
+
mkdirSync,
|
|
837
|
+
readdirSync as readdirSync2,
|
|
838
|
+
readFileSync as readFileSync4,
|
|
839
|
+
rmdirSync,
|
|
840
|
+
rmSync,
|
|
841
|
+
writeFileSync as writeFileSync2
|
|
842
|
+
} from "fs";
|
|
648
843
|
import { dirname, join as join6 } from "path";
|
|
649
844
|
|
|
650
845
|
// src/core/markers.ts
|
|
@@ -704,21 +899,17 @@ function applyWhole(projectRoot, file, opts) {
|
|
|
704
899
|
ensureDir(abs);
|
|
705
900
|
writeFileSync2(abs, next, "utf8");
|
|
706
901
|
}
|
|
707
|
-
return { path: file.path, mode: "whole", action: "created"
|
|
902
|
+
return { path: file.path, mode: "whole", action: "created" };
|
|
708
903
|
}
|
|
709
904
|
const current = readFileSync4(abs, "utf8");
|
|
710
905
|
if (current === next) {
|
|
711
906
|
return { path: file.path, mode: "whole", action: "unchanged" };
|
|
712
907
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
choice = opts.onConflict ? opts.onConflict(file.path) : opts.dryRun ? "overwrite" : "skip";
|
|
716
|
-
}
|
|
717
|
-
if (choice === "skip") {
|
|
718
|
-
return { path: file.path, mode: "whole", action: "skipped", before: current, after: next };
|
|
908
|
+
if (!opts.overwrite && !opts.dryRun) {
|
|
909
|
+
return { path: file.path, mode: "whole", action: "skipped" };
|
|
719
910
|
}
|
|
720
911
|
if (!opts.dryRun) writeFileSync2(abs, next, "utf8");
|
|
721
|
-
return { path: file.path, mode: "whole", action: "updated"
|
|
912
|
+
return { path: file.path, mode: "whole", action: "updated" };
|
|
722
913
|
}
|
|
723
914
|
function applyBlock(projectRoot, file, opts) {
|
|
724
915
|
const abs = join6(projectRoot, file.path);
|
|
@@ -871,7 +1062,7 @@ async function addCommand(opts) {
|
|
|
871
1062
|
const byClass = registry2.skills.filter((s) => s.class === token);
|
|
872
1063
|
const group = byCategory.length ? byCategory : byClass;
|
|
873
1064
|
if (group.length) {
|
|
874
|
-
|
|
1065
|
+
for (const s of group) selected.add(s.name);
|
|
875
1066
|
} else {
|
|
876
1067
|
log.warn(`No skill, category, or class matches "${token}".`);
|
|
877
1068
|
skipped.push(token);
|
|
@@ -893,16 +1084,22 @@ async function addCommand(opts) {
|
|
|
893
1084
|
const lock = loadLockfile(opts.cwd);
|
|
894
1085
|
lock.contentRef = opts.ref ?? cfg.contentRef;
|
|
895
1086
|
const installed = [];
|
|
1087
|
+
const ownedFiles = /* @__PURE__ */ new Set();
|
|
1088
|
+
const sharedBlockFiles = /* @__PURE__ */ new Set();
|
|
896
1089
|
const prefix = opts.dryRun ? "[dry-run] " : "";
|
|
897
1090
|
for (const name of [...selected].sort()) {
|
|
898
1091
|
const skill = content.loadSkill(name);
|
|
899
1092
|
const result = installSkill(opts.cwd, skill, targets, paths, {
|
|
900
1093
|
dryRun: opts.dryRun,
|
|
901
|
-
overwrite: opts.overwrite
|
|
902
|
-
onConflict: opts.onConflict
|
|
1094
|
+
overwrite: opts.overwrite
|
|
903
1095
|
});
|
|
904
1096
|
if (!opts.dryRun) lock.skills[name] = result.locked;
|
|
905
1097
|
installed.push({ name, version: result.version });
|
|
1098
|
+
for (const e of Object.values(result.locked.emitted)) {
|
|
1099
|
+
if (!e) continue;
|
|
1100
|
+
for (const f of e.files) ownedFiles.add(f);
|
|
1101
|
+
if (e.block) sharedBlockFiles.add(e.block);
|
|
1102
|
+
}
|
|
906
1103
|
for (const r of result.results) {
|
|
907
1104
|
const tag = r.mode === "block" ? `${r.path} [${r.blockId}]` : r.path;
|
|
908
1105
|
log.info(`${prefix}${r.action.padEnd(9)} ${tag}`);
|
|
@@ -915,8 +1112,15 @@ async function addCommand(opts) {
|
|
|
915
1112
|
log.info("Wrote skills-master.json.");
|
|
916
1113
|
}
|
|
917
1114
|
if (!cfg.commit) {
|
|
918
|
-
|
|
919
|
-
|
|
1115
|
+
ensureGitignored(
|
|
1116
|
+
opts.cwd,
|
|
1117
|
+
[...ownedFiles].sort((a, b) => a.localeCompare(b)).map((f) => `/${f}`)
|
|
1118
|
+
);
|
|
1119
|
+
if (sharedBlockFiles.size > 0) {
|
|
1120
|
+
log.warn(
|
|
1121
|
+
`Not gitignoring shared file(s) with managed blocks: ${[...sharedBlockFiles].sort().join(", ")}.`
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
920
1124
|
}
|
|
921
1125
|
}
|
|
922
1126
|
log.success(
|
|
@@ -951,8 +1155,12 @@ async function updateCommand(opts) {
|
|
|
951
1155
|
let skill;
|
|
952
1156
|
try {
|
|
953
1157
|
skill = content.loadSkill(name);
|
|
954
|
-
} catch {
|
|
955
|
-
|
|
1158
|
+
} catch (err) {
|
|
1159
|
+
if (err instanceof SkillNotFoundError) {
|
|
1160
|
+
log.warn(`"${name}" no longer exists in the content library.`);
|
|
1161
|
+
} else {
|
|
1162
|
+
log.error(`Failed to load "${name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1163
|
+
}
|
|
956
1164
|
skipped.push(name);
|
|
957
1165
|
continue;
|
|
958
1166
|
}
|
|
@@ -967,15 +1175,14 @@ async function updateCommand(opts) {
|
|
|
967
1175
|
const e = locked.emitted[t];
|
|
968
1176
|
return e && diskHash(opts.cwd, e.files) !== e.hash;
|
|
969
1177
|
});
|
|
970
|
-
if (userEdited && !opts.overwrite
|
|
1178
|
+
if (userEdited && !opts.overwrite) {
|
|
971
1179
|
log.warn(`${prefix}"${name}" has local edits \u2014 skipping (use --overwrite to replace).`);
|
|
972
1180
|
skipped.push(name);
|
|
973
1181
|
continue;
|
|
974
1182
|
}
|
|
975
1183
|
const result = installSkill(opts.cwd, skill, targets, paths, {
|
|
976
1184
|
dryRun: opts.dryRun,
|
|
977
|
-
overwrite: opts.overwrite || !userEdited
|
|
978
|
-
onConflict: opts.onConflict
|
|
1185
|
+
overwrite: opts.overwrite || !userEdited
|
|
979
1186
|
});
|
|
980
1187
|
if (!opts.dryRun) lock.skills[name] = result.locked;
|
|
981
1188
|
updated.push(name);
|
|
@@ -1009,6 +1216,11 @@ function removeCommand(opts) {
|
|
|
1009
1216
|
continue;
|
|
1010
1217
|
}
|
|
1011
1218
|
const targets = (opts.targets?.length ? opts.targets : Object.keys(locked.emitted)).filter((t) => locked.emitted[t]);
|
|
1219
|
+
if (targets.length === 0) {
|
|
1220
|
+
log.warn(`"${name}" is not installed to ${opts.targets?.join(", ")} \u2014 nothing to remove.`);
|
|
1221
|
+
missing.push(name);
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1012
1224
|
const wholeRemoved = [];
|
|
1013
1225
|
for (const t of targets) {
|
|
1014
1226
|
const e = locked.emitted[t];
|
|
@@ -1034,9 +1246,44 @@ function removeCommand(opts) {
|
|
|
1034
1246
|
return { removed, missing };
|
|
1035
1247
|
}
|
|
1036
1248
|
|
|
1037
|
-
// src/
|
|
1249
|
+
// src/core/installed-state.ts
|
|
1038
1250
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
1039
1251
|
import { join as join9 } from "path";
|
|
1252
|
+
var STATES = ["ok", "edited", "missing"];
|
|
1253
|
+
function worst(states) {
|
|
1254
|
+
return states.reduce(
|
|
1255
|
+
(acc, s) => STATES.indexOf(s) > STATES.indexOf(acc) ? s : acc,
|
|
1256
|
+
"ok"
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
function diagnoseInstalled(cwd, lock) {
|
|
1260
|
+
const out = [];
|
|
1261
|
+
for (const name of Object.keys(lock.skills).sort()) {
|
|
1262
|
+
const locked = lock.skills[name];
|
|
1263
|
+
const targets = [];
|
|
1264
|
+
for (const [target, e] of Object.entries(locked.emitted)) {
|
|
1265
|
+
const missingFiles = e.files.filter((f) => !existsSync9(join9(cwd, f)));
|
|
1266
|
+
const edited = missingFiles.length === 0 && diskHash(cwd, e.files) !== e.hash;
|
|
1267
|
+
let missingBlock;
|
|
1268
|
+
if (e.block) {
|
|
1269
|
+
const abs = join9(cwd, e.block);
|
|
1270
|
+
if (!existsSync9(abs) || !hasBlock(readFileSync7(abs, "utf8"), name)) missingBlock = e.block;
|
|
1271
|
+
}
|
|
1272
|
+
const state = missingFiles.length > 0 || missingBlock ? "missing" : edited ? "edited" : "ok";
|
|
1273
|
+
targets.push({ target, missingFiles, edited, missingBlock, state });
|
|
1274
|
+
}
|
|
1275
|
+
targets.sort((a, b) => a.target.localeCompare(b.target));
|
|
1276
|
+
out.push({
|
|
1277
|
+
name,
|
|
1278
|
+
version: locked.version,
|
|
1279
|
+
targets,
|
|
1280
|
+
state: worst(targets.map((t) => t.state))
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
return out;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// src/commands/doctor.ts
|
|
1040
1287
|
function doctorCommand(opts) {
|
|
1041
1288
|
const problems = [];
|
|
1042
1289
|
const note = (msg) => problems.push(msg);
|
|
@@ -1047,32 +1294,28 @@ function doctorCommand(opts) {
|
|
|
1047
1294
|
log.info(`Config targets: ${cfg.targets.length ? cfg.targets.join(", ") : "(auto-detect)"}`);
|
|
1048
1295
|
}
|
|
1049
1296
|
const lock = loadLockfile(opts.cwd);
|
|
1050
|
-
const
|
|
1051
|
-
if (
|
|
1297
|
+
const diagnoses = diagnoseInstalled(opts.cwd, lock);
|
|
1298
|
+
if (diagnoses.length === 0) {
|
|
1052
1299
|
log.info("No skills installed.");
|
|
1053
1300
|
return { problems, ok: true };
|
|
1054
1301
|
}
|
|
1055
|
-
for (const
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
if (!existsSync9(join9(opts.cwd, file))) note(`${name}: missing ${target} file ${file}`);
|
|
1302
|
+
for (const skill of diagnoses) {
|
|
1303
|
+
for (const t of skill.targets) {
|
|
1304
|
+
for (const file of t.missingFiles) {
|
|
1305
|
+
note(`${skill.name}: missing ${t.target} file ${file}`);
|
|
1060
1306
|
}
|
|
1061
|
-
if (
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1307
|
+
if (t.edited) {
|
|
1308
|
+
note(
|
|
1309
|
+
`${skill.name}: local edits to ${t.target} output(s) (run \`update --overwrite\` to reset)`
|
|
1310
|
+
);
|
|
1065
1311
|
}
|
|
1066
|
-
if (
|
|
1067
|
-
|
|
1068
|
-
if (!existsSync9(abs) || !hasBlock(readFileSync7(abs, "utf8"), name)) {
|
|
1069
|
-
note(`${name}: missing managed block in ${e.block}`);
|
|
1070
|
-
}
|
|
1312
|
+
if (t.missingBlock) {
|
|
1313
|
+
note(`${skill.name}: missing managed block in ${t.missingBlock}`);
|
|
1071
1314
|
}
|
|
1072
1315
|
}
|
|
1073
1316
|
}
|
|
1074
1317
|
if (problems.length === 0) {
|
|
1075
|
-
log.success(`All ${
|
|
1318
|
+
log.success(`All ${diagnoses.length} installed skill(s) look healthy.`);
|
|
1076
1319
|
} else {
|
|
1077
1320
|
for (const p of problems) log.warn(p);
|
|
1078
1321
|
log.plain(`
|
|
@@ -1081,6 +1324,70 @@ ${problems.length} problem(s) found.`);
|
|
|
1081
1324
|
return { problems, ok: problems.length === 0 };
|
|
1082
1325
|
}
|
|
1083
1326
|
|
|
1327
|
+
// src/commands/status.ts
|
|
1328
|
+
var MARK = { ok: "ok", edited: "edited", missing: "missing" };
|
|
1329
|
+
function statusCommand(opts) {
|
|
1330
|
+
const cfg = loadConfig(opts.cwd);
|
|
1331
|
+
const lock = loadLockfile(opts.cwd);
|
|
1332
|
+
let skills = diagnoseInstalled(opts.cwd, lock);
|
|
1333
|
+
if (opts.names?.length) {
|
|
1334
|
+
const want = new Set(opts.names);
|
|
1335
|
+
skills = skills.filter((s) => want.has(s.name));
|
|
1336
|
+
}
|
|
1337
|
+
if (opts.problemsOnly) skills = skills.filter((s) => s.state !== "ok");
|
|
1338
|
+
const counts = { ok: 0, edited: 0, missing: 0 };
|
|
1339
|
+
for (const s of skills) counts[s.state]++;
|
|
1340
|
+
const report = {
|
|
1341
|
+
contentRef: lock.contentRef,
|
|
1342
|
+
configuredTargets: cfg?.targets ?? [],
|
|
1343
|
+
skills,
|
|
1344
|
+
counts
|
|
1345
|
+
};
|
|
1346
|
+
if (opts.json) {
|
|
1347
|
+
log.plain(JSON.stringify(report, null, 2));
|
|
1348
|
+
return report;
|
|
1349
|
+
}
|
|
1350
|
+
if (Object.keys(lock.skills).length === 0) {
|
|
1351
|
+
log.info("No skills installed \u2014 run `skills-master add <name>`.");
|
|
1352
|
+
return report;
|
|
1353
|
+
}
|
|
1354
|
+
if (skills.length === 0) {
|
|
1355
|
+
log.info(opts.problemsOnly ? "Nothing needs attention." : "No installed skills match.");
|
|
1356
|
+
return report;
|
|
1357
|
+
}
|
|
1358
|
+
log.info(
|
|
1359
|
+
`${skills.length} skill(s) from ref ${report.contentRef}` + (report.configuredTargets.length ? ` \xB7 config targets: ${report.configuredTargets.join(", ")}` : "")
|
|
1360
|
+
);
|
|
1361
|
+
log.plain("");
|
|
1362
|
+
const nameWidth = Math.max(...skills.map((s) => s.name.length));
|
|
1363
|
+
const verWidth = Math.max(...skills.map((s) => s.version.length + 1));
|
|
1364
|
+
for (const s of skills) {
|
|
1365
|
+
const targets = s.targets.map((t) => t.state === "ok" ? t.target : `${t.target} (${MARK[t.state]})`).join(", ");
|
|
1366
|
+
log.plain(
|
|
1367
|
+
` ${s.name.padEnd(nameWidth)} ${`v${s.version}`.padEnd(verWidth)} ${MARK[s.state].padEnd(7)} ${targets}`
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
log.plain("");
|
|
1371
|
+
const parts = [`${counts.ok} ok`];
|
|
1372
|
+
if (counts.edited) parts.push(`${counts.edited} edited`);
|
|
1373
|
+
if (counts.missing) parts.push(`${counts.missing} missing`);
|
|
1374
|
+
log.plain(parts.join(", ") + ".");
|
|
1375
|
+
if (counts.edited || counts.missing) {
|
|
1376
|
+
log.plain("Run `skills-master doctor` for detail, or `update --overwrite` to reset.");
|
|
1377
|
+
}
|
|
1378
|
+
return report;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/core/search-text.ts
|
|
1382
|
+
function searchNormalize(value) {
|
|
1383
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
1384
|
+
}
|
|
1385
|
+
function searchableText(parts) {
|
|
1386
|
+
return searchNormalize(
|
|
1387
|
+
[parts.name, parts.description, parts.domain, parts.category, parts.class].join(" ")
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1084
1391
|
// src/commands/catalog.ts
|
|
1085
1392
|
async function registryOf(q) {
|
|
1086
1393
|
const content = await resolveContent({ content: q.content, ref: q.ref, cwd: q.cwd });
|
|
@@ -1124,9 +1431,11 @@ ${skills.length} skill(s).`);
|
|
|
1124
1431
|
}
|
|
1125
1432
|
async function searchCommand(opts) {
|
|
1126
1433
|
const reg = await registryOf(opts);
|
|
1127
|
-
const q = opts.query
|
|
1434
|
+
const q = searchNormalize(opts.query);
|
|
1128
1435
|
const hits = reg.skills.filter(
|
|
1129
|
-
(s) =>
|
|
1436
|
+
(s) => searchNormalize(
|
|
1437
|
+
[s.name, s.description, s.domain, s.category, s.class, ...s.tags].join(" ")
|
|
1438
|
+
).includes(q)
|
|
1130
1439
|
);
|
|
1131
1440
|
if (hits.length === 0) {
|
|
1132
1441
|
log.info(`No matches for "${opts.query}".`);
|
|
@@ -1148,10 +1457,14 @@ async function viewCommand(opts) {
|
|
|
1148
1457
|
log.plain(skill.body);
|
|
1149
1458
|
return;
|
|
1150
1459
|
}
|
|
1151
|
-
log.plain(
|
|
1460
|
+
log.plain(
|
|
1461
|
+
`${skill.name} v${xm.version} [${xm.domain}/${xm.class}/${xm.category}] ${xm.stability}`
|
|
1462
|
+
);
|
|
1152
1463
|
log.plain(`platforms: ${xm.platforms.join(", ")}`);
|
|
1153
1464
|
if (xm.requires) {
|
|
1154
|
-
log.plain(
|
|
1465
|
+
log.plain(
|
|
1466
|
+
`requires: ${Object.entries(xm.requires).map(([k, v]) => `${k} ${v}`).join(", ")}`
|
|
1467
|
+
);
|
|
1155
1468
|
}
|
|
1156
1469
|
if (xm.pairs_with.length) log.plain(`pairs with: ${xm.pairs_with.join(", ")}`);
|
|
1157
1470
|
log.plain(`
|
|
@@ -1212,10 +1525,21 @@ function lintSkills(skillsRoot) {
|
|
|
1212
1525
|
for (const issue of v.issues) {
|
|
1213
1526
|
diagnostics.push({ relPath: raw.relPath, level: "error", message: issue });
|
|
1214
1527
|
}
|
|
1215
|
-
loaded.push({
|
|
1528
|
+
loaded.push({
|
|
1529
|
+
relPath: raw.relPath,
|
|
1530
|
+
folderName: raw.folderName,
|
|
1531
|
+
body: raw.body,
|
|
1532
|
+
resources: raw.resources
|
|
1533
|
+
});
|
|
1216
1534
|
continue;
|
|
1217
1535
|
}
|
|
1218
|
-
loaded.push({
|
|
1536
|
+
loaded.push({
|
|
1537
|
+
relPath: raw.relPath,
|
|
1538
|
+
folderName: raw.folderName,
|
|
1539
|
+
fm: v.value,
|
|
1540
|
+
body: raw.body,
|
|
1541
|
+
resources: raw.resources
|
|
1542
|
+
});
|
|
1219
1543
|
byName.set(v.value.name, v.value);
|
|
1220
1544
|
}
|
|
1221
1545
|
const nameDirs = /* @__PURE__ */ new Map();
|
|
@@ -1226,7 +1550,11 @@ function lintSkills(skillsRoot) {
|
|
|
1226
1550
|
for (const [name, paths] of nameDirs) {
|
|
1227
1551
|
if (paths.length > 1) {
|
|
1228
1552
|
for (const p of paths) {
|
|
1229
|
-
diagnostics.push({
|
|
1553
|
+
diagnostics.push({
|
|
1554
|
+
relPath: p,
|
|
1555
|
+
level: "error",
|
|
1556
|
+
message: `duplicate skill name "${name}" (also at ${paths.filter((x) => x !== p).join(", ")})`
|
|
1557
|
+
});
|
|
1230
1558
|
}
|
|
1231
1559
|
}
|
|
1232
1560
|
}
|
|
@@ -1245,17 +1573,58 @@ function lintSkills(skillsRoot) {
|
|
|
1245
1573
|
if (xm.snapshot_date > todayStr) {
|
|
1246
1574
|
push("error", `snapshot_date ${xm.snapshot_date} is in the future`);
|
|
1247
1575
|
}
|
|
1248
|
-
|
|
1576
|
+
const hasOpenQuestion = /^## Open question\b/m.test(s.body);
|
|
1577
|
+
if (xm.stability === "contested" && !hasOpenQuestion) {
|
|
1249
1578
|
push("error", `stability is "contested" but no "## Open question" section is present`);
|
|
1579
|
+
} else if (xm.stability !== "contested" && hasOpenQuestion) {
|
|
1580
|
+
push(
|
|
1581
|
+
"error",
|
|
1582
|
+
`"## Open question" is reserved for stability: contested skills \u2014 retitle the section or mark the skill contested`
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
if (xm.sources.length > 3) {
|
|
1586
|
+
push("warn", `${xm.sources.length} sources \u2014 keep at most 3 canonical citation URLs`);
|
|
1587
|
+
}
|
|
1588
|
+
const findable = searchableText({
|
|
1589
|
+
name: fm.name,
|
|
1590
|
+
description: fm.description,
|
|
1591
|
+
domain: xm.domain,
|
|
1592
|
+
category: xm.category,
|
|
1593
|
+
class: xm.class
|
|
1594
|
+
});
|
|
1595
|
+
for (const tag of fm.tags ?? []) {
|
|
1596
|
+
if (findable.includes(searchNormalize(tag))) {
|
|
1597
|
+
push("warn", `tag "${tag}" is already in the name or description \u2014 it adds no search term`);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const l3 = [
|
|
1601
|
+
["reference", "reference.md"],
|
|
1602
|
+
["examples", "examples.md"],
|
|
1603
|
+
["checklist", "checklist.md"]
|
|
1604
|
+
];
|
|
1605
|
+
for (const [key, file] of l3) {
|
|
1606
|
+
if (s.resources?.[key] != null && !s.body.includes(`(${file}`) && !s.body.includes(`(./${file}`)) {
|
|
1607
|
+
push("warn", `${file} exists but is never linked from the SKILL.md body`);
|
|
1608
|
+
}
|
|
1250
1609
|
}
|
|
1251
1610
|
const lineCount = s.body.split("\n").length;
|
|
1252
1611
|
if (lineCount > MAX_BODY_LINES) {
|
|
1253
|
-
push(
|
|
1612
|
+
push(
|
|
1613
|
+
"error",
|
|
1614
|
+
`SKILL.md body is ${lineCount} lines (max ${MAX_BODY_LINES}); move depth into reference.md/examples.md`
|
|
1615
|
+
);
|
|
1254
1616
|
} else if (lineCount > WARN_BODY_LINES) {
|
|
1255
|
-
push(
|
|
1617
|
+
push(
|
|
1618
|
+
"warn",
|
|
1619
|
+
`SKILL.md body is ${lineCount} lines (approaching the ${MAX_BODY_LINES}-line cap)`
|
|
1620
|
+
);
|
|
1256
1621
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1622
|
+
const expectedPath = `${xm.domain}/${CLASS_DIR[xm.class]}/${xm.category}/${fm.name}`;
|
|
1623
|
+
if (s.relPath !== expectedPath) {
|
|
1624
|
+
push(
|
|
1625
|
+
"error",
|
|
1626
|
+
`on-disk path "${s.relPath}" must be "${expectedPath}" (domain/class dir/category/name from frontmatter)`
|
|
1627
|
+
);
|
|
1259
1628
|
}
|
|
1260
1629
|
if (xm.sources.length === 0) {
|
|
1261
1630
|
push("warn", `no sources \u2014 add at least one canonical documentation URL`);
|
|
@@ -1270,6 +1639,12 @@ function lintSkills(skillsRoot) {
|
|
|
1270
1639
|
push("warn", `missing recommended section "${heading}"`);
|
|
1271
1640
|
}
|
|
1272
1641
|
}
|
|
1642
|
+
if (xm.pairs_with.length > 4) {
|
|
1643
|
+
push(
|
|
1644
|
+
"warn",
|
|
1645
|
+
`${xm.pairs_with.length} pairs_with entries \u2014 keep at most 4; put wider cross-references in "## See also"`
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1273
1648
|
for (const partner of xm.pairs_with) {
|
|
1274
1649
|
const partnerFm = byName.get(partner);
|
|
1275
1650
|
if (!partnerFm) {
|
|
@@ -1277,7 +1652,10 @@ function lintSkills(skillsRoot) {
|
|
|
1277
1652
|
continue;
|
|
1278
1653
|
}
|
|
1279
1654
|
if (!partnerFm["x-skills-master"].pairs_with.includes(fm.name)) {
|
|
1280
|
-
push(
|
|
1655
|
+
push(
|
|
1656
|
+
"error",
|
|
1657
|
+
`pairs_with "${partner}" is not reciprocated (add "${fm.name}" to its pairs_with)`
|
|
1658
|
+
);
|
|
1281
1659
|
}
|
|
1282
1660
|
}
|
|
1283
1661
|
}
|
|
@@ -1305,39 +1683,6 @@ Linted ${result.skillCount} skill(s): ${result.errorCount} error(s), ${result.wa
|
|
|
1305
1683
|
// src/commands/registry.ts
|
|
1306
1684
|
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
1307
1685
|
import { join as join10 } from "path";
|
|
1308
|
-
|
|
1309
|
-
// src/schema/registry.ts
|
|
1310
|
-
import { z as z4 } from "zod";
|
|
1311
|
-
var RegistryEntrySchema = z4.object({
|
|
1312
|
-
name: z4.string(),
|
|
1313
|
-
domain: z4.string(),
|
|
1314
|
-
class: SkillClassSchema,
|
|
1315
|
-
category: z4.string(),
|
|
1316
|
-
description: z4.string(),
|
|
1317
|
-
platforms: z4.array(z4.string()),
|
|
1318
|
-
stability: StabilitySchema,
|
|
1319
|
-
version: z4.string(),
|
|
1320
|
-
tags: z4.array(z4.string()).default([]),
|
|
1321
|
-
pairs_with: z4.array(z4.string()).default([]),
|
|
1322
|
-
/** path relative to the skills root. */
|
|
1323
|
-
path: z4.string(),
|
|
1324
|
-
/** which on-demand resource files exist. */
|
|
1325
|
-
resources: z4.object({
|
|
1326
|
-
reference: z4.boolean(),
|
|
1327
|
-
examples: z4.boolean(),
|
|
1328
|
-
checklist: z4.boolean()
|
|
1329
|
-
})
|
|
1330
|
-
});
|
|
1331
|
-
var RegistrySchema = z4.object({
|
|
1332
|
-
$schema: z4.string().optional(),
|
|
1333
|
-
/** aggregate library version (bumped on release). */
|
|
1334
|
-
version: z4.string().default("0.1.0"),
|
|
1335
|
-
generatedAt: z4.string().optional(),
|
|
1336
|
-
skills: z4.array(RegistryEntrySchema).default([])
|
|
1337
|
-
});
|
|
1338
|
-
var REGISTRY_FILENAME = "registry.json";
|
|
1339
|
-
|
|
1340
|
-
// src/commands/registry.ts
|
|
1341
1686
|
async function registryBuildCommand(opts) {
|
|
1342
1687
|
const content = await resolveContent({ content: opts.content, cwd: opts.cwd });
|
|
1343
1688
|
const registry2 = buildRegistry(content.root, opts.version ?? "0.1.0");
|
|
@@ -1433,7 +1778,13 @@ function buildOutputs(content, out, version2) {
|
|
|
1433
1778
|
`${PLUGINS_DIR}/${name}/.claude-plugin/plugin.json`,
|
|
1434
1779
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
1435
1780
|
);
|
|
1436
|
-
plugins.push({
|
|
1781
|
+
plugins.push({
|
|
1782
|
+
name,
|
|
1783
|
+
source: `./${PLUGINS_DIR}/${name}`,
|
|
1784
|
+
description,
|
|
1785
|
+
version: version2,
|
|
1786
|
+
category: CLASS_CATEGORY[cls]
|
|
1787
|
+
});
|
|
1437
1788
|
counts.push({ name, count });
|
|
1438
1789
|
}
|
|
1439
1790
|
plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -1480,7 +1831,11 @@ async function marketplaceBuildCommand(opts) {
|
|
|
1480
1831
|
const stale = [...onDisk].filter((rel) => !files.has(rel)).sort();
|
|
1481
1832
|
for (const rel of stale) rmSync2(join11(out, rel), { force: true });
|
|
1482
1833
|
pruneEmptyDirsUnder(join11(out, PLUGINS_DIR));
|
|
1483
|
-
const emitted = [...files].map(([path, contents]) => ({
|
|
1834
|
+
const emitted = [...files].map(([path, contents]) => ({
|
|
1835
|
+
path,
|
|
1836
|
+
contents,
|
|
1837
|
+
mode: "whole"
|
|
1838
|
+
}));
|
|
1484
1839
|
applyFiles(out, emitted, { overwrite: true });
|
|
1485
1840
|
for (const { name, count } of counts) log.info(`Built ${name} (${count} skills).`);
|
|
1486
1841
|
if (stale.length) log.info(`Removed ${stale.length} stale file(s).`);
|
|
@@ -1567,7 +1922,7 @@ async function newSkillCommand(opts) {
|
|
|
1567
1922
|
return skillMd;
|
|
1568
1923
|
}
|
|
1569
1924
|
|
|
1570
|
-
// src/
|
|
1925
|
+
// src/util/targets.ts
|
|
1571
1926
|
var VALID = new Set(ALL_TARGETS);
|
|
1572
1927
|
function parseTargets(value) {
|
|
1573
1928
|
if (!value) return void 0;
|
|
@@ -1580,6 +1935,8 @@ function parseTargets(value) {
|
|
|
1580
1935
|
}
|
|
1581
1936
|
return ids;
|
|
1582
1937
|
}
|
|
1938
|
+
|
|
1939
|
+
// src/bin.ts
|
|
1583
1940
|
async function run(fn, exitOnFalse = false) {
|
|
1584
1941
|
try {
|
|
1585
1942
|
const result = await fn();
|
|
@@ -1602,7 +1959,7 @@ program.command("init").description("Detect tools and write skills-master.json")
|
|
|
1602
1959
|
})
|
|
1603
1960
|
)
|
|
1604
1961
|
);
|
|
1605
|
-
program.command("list").description("List available skills").option("--domain <domain>", "e.g. apple, android").option("--class <class>").option("--category <category>").option("--platform <platform>").option("--json").option("--content <dir>", "local skills directory").option("--ref <ref>").action(
|
|
1962
|
+
program.command("list").description("List available skills").option("--domain <domain>", "e.g. apple, android").option("--class <class>", "e.g. code, design, lang-tooling, overview").option("--category <category>", "e.g. app-frameworks, compose-ui").option("--platform <platform>", "e.g. ios, watchos, android").option("--json", "machine-readable JSON output").option("--content <dir>", "local skills directory").option("--ref <ref>", "content git ref (tag/branch/sha)").action(
|
|
1606
1963
|
(opts) => run(
|
|
1607
1964
|
() => listCommand({
|
|
1608
1965
|
cwd: process.cwd(),
|
|
@@ -1616,15 +1973,21 @@ program.command("list").description("List available skills").option("--domain <d
|
|
|
1616
1973
|
})
|
|
1617
1974
|
)
|
|
1618
1975
|
);
|
|
1619
|
-
program.command("search <query>").description("Search skills by name, description, tags").option("--content <dir>").option("--ref <ref>").action(
|
|
1976
|
+
program.command("search <query>").description("Search skills by name, description, tags").option("--content <dir>", "local skills directory").option("--ref <ref>", "content git ref (tag/branch/sha)").action(
|
|
1620
1977
|
(query, opts) => run(() => searchCommand({ cwd: process.cwd(), query, content: opts.content, ref: opts.ref }))
|
|
1621
1978
|
);
|
|
1622
|
-
program.command("view <name>").description("Show a skill's metadata and body").option("--raw", "print the raw SKILL.md body").option("--content <dir>").option("--ref <ref>").action(
|
|
1979
|
+
program.command("view <name>").description("Show a skill's metadata and body").option("--raw", "print the raw SKILL.md body").option("--content <dir>", "local skills directory").option("--ref <ref>", "content git ref (tag/branch/sha)").action(
|
|
1623
1980
|
(name, opts) => run(
|
|
1624
|
-
() => viewCommand({
|
|
1981
|
+
() => viewCommand({
|
|
1982
|
+
cwd: process.cwd(),
|
|
1983
|
+
name,
|
|
1984
|
+
raw: opts.raw,
|
|
1985
|
+
content: opts.content,
|
|
1986
|
+
ref: opts.ref
|
|
1987
|
+
})
|
|
1625
1988
|
)
|
|
1626
1989
|
);
|
|
1627
|
-
program.command("add <names...>").description("Install skills (by name, category, or class) into your tools").option("--target <list>", "comma list or 'all'").option("--with-pairs", "also install paired (code<->design) skills").option("--dry-run", "preview without writing").option("--overwrite", "overwrite changed files without asking").option("--content <dir>").option("--ref <ref>").action(
|
|
1990
|
+
program.command("add <names...>").description("Install skills (by name, category, or class) into your tools").option("--target <list>", "comma list or 'all'").option("--with-pairs", "also install paired (code<->design) skills").option("--dry-run", "preview without writing").option("--overwrite", "overwrite changed files without asking").option("--content <dir>", "local skills directory").option("--ref <ref>", "content git ref (tag/branch/sha)").action(
|
|
1628
1991
|
(names, opts) => run(
|
|
1629
1992
|
() => addCommand({
|
|
1630
1993
|
cwd: process.cwd(),
|
|
@@ -1638,7 +2001,7 @@ program.command("add <names...>").description("Install skills (by name, category
|
|
|
1638
2001
|
})
|
|
1639
2002
|
)
|
|
1640
2003
|
);
|
|
1641
|
-
program.command("update [names...]").description("Re-install skills whose content changed").option("--dry-run").option("--overwrite", "force re-install, replacing local edits").option("--content <dir>").option("--ref <ref>").action(
|
|
2004
|
+
program.command("update [names...]").description("Re-install skills whose content changed").option("--dry-run", "preview without writing").option("--overwrite", "force re-install, replacing local edits").option("--content <dir>", "local skills directory").option("--ref <ref>", "content git ref (tag/branch/sha)").action(
|
|
1642
2005
|
(names, opts) => run(
|
|
1643
2006
|
() => updateCommand({
|
|
1644
2007
|
cwd: process.cwd(),
|
|
@@ -1650,35 +2013,59 @@ program.command("update [names...]").description("Re-install skills whose conten
|
|
|
1650
2013
|
})
|
|
1651
2014
|
)
|
|
1652
2015
|
);
|
|
1653
|
-
program.command("remove <names...>").description("Remove installed skills").option("--target <list>", "comma list or 'all'").option("--dry-run").action(
|
|
2016
|
+
program.command("remove <names...>").description("Remove installed skills").option("--target <list>", "comma list or 'all'").option("--dry-run", "preview without writing").action(
|
|
2017
|
+
(names, opts) => run(
|
|
2018
|
+
() => removeCommand({
|
|
2019
|
+
cwd: process.cwd(),
|
|
2020
|
+
names,
|
|
2021
|
+
targets: parseTargets(opts.target),
|
|
2022
|
+
dryRun: opts.dryRun
|
|
2023
|
+
})
|
|
2024
|
+
)
|
|
2025
|
+
);
|
|
2026
|
+
program.command("status").description("Show installed skills: versions, targets, and local-edit state").argument("[names...]", "limit the report to these skills").option("--problems", "show only skills that are edited or missing files").option("--json", "machine-readable output").action(
|
|
1654
2027
|
(names, opts) => run(
|
|
1655
|
-
() =>
|
|
2028
|
+
() => statusCommand({
|
|
2029
|
+
cwd: process.cwd(),
|
|
2030
|
+
names,
|
|
2031
|
+
problemsOnly: opts.problems,
|
|
2032
|
+
json: opts.json
|
|
2033
|
+
})
|
|
1656
2034
|
)
|
|
1657
2035
|
);
|
|
1658
|
-
program.command("doctor").description("Check installed skills for drift and missing files").action(() => run(() => doctorCommand({ cwd: process.cwd() })));
|
|
1659
|
-
program.command("lint").description("Validate the skill library (maintainer command)").option("--content <dir>").action((opts) => run(() => lintCommand({ cwd: process.cwd(), content: opts.content }), true));
|
|
1660
|
-
program.command("new <spec>").description("Scaffold a new skill: class/category/name (maintainer command)").option("--content <dir>").option("--force").action(
|
|
1661
|
-
(spec, opts) => run(
|
|
2036
|
+
program.command("doctor").description("Check installed skills for drift and missing files").action(() => run(() => doctorCommand({ cwd: process.cwd() }).ok, true));
|
|
2037
|
+
program.command("lint").description("Validate the skill library (maintainer command)").option("--content <dir>", "local skills directory").action((opts) => run(() => lintCommand({ cwd: process.cwd(), content: opts.content }), true));
|
|
2038
|
+
program.command("new <spec>").description("Scaffold a new skill: domain/class/category/name (maintainer command)").option("--content <dir>", "local skills directory").option("--force", "overwrite an existing skill directory").action(
|
|
2039
|
+
(spec, opts) => run(
|
|
2040
|
+
() => newSkillCommand({ cwd: process.cwd(), spec, content: opts.content, force: opts.force })
|
|
2041
|
+
)
|
|
1662
2042
|
);
|
|
1663
2043
|
var registry = program.command("registry").description("Registry maintenance");
|
|
1664
|
-
registry.command("build").description("Generate registry.json from the skill tree").option("--content <dir>").option("--check", "verify the committed registry.json is current (CI)").option("--version <v>").action(
|
|
2044
|
+
registry.command("build").description("Generate registry.json from the skill tree").option("--content <dir>", "local skills directory").option("--check", "verify the committed registry.json is current (CI)").option("--set-version <v>", "schema version to stamp into registry.json").action(
|
|
1665
2045
|
(opts) => run(
|
|
1666
|
-
() => registryBuildCommand({
|
|
2046
|
+
() => registryBuildCommand({
|
|
2047
|
+
cwd: process.cwd(),
|
|
2048
|
+
content: opts.content,
|
|
2049
|
+
check: opts.check,
|
|
2050
|
+
version: opts.setVersion
|
|
2051
|
+
}),
|
|
1667
2052
|
true
|
|
1668
2053
|
)
|
|
1669
2054
|
);
|
|
1670
2055
|
var marketplace = program.command("marketplace").description("Claude marketplace maintenance");
|
|
1671
|
-
marketplace.command("build").description("Generate .claude-plugin/marketplace.json and per-class plugins").option("--content <dir>").option("--out <dir>", "output root").option("--check", "verify the committed marketplace output is current (CI)").option("--version <v>").action(
|
|
2056
|
+
marketplace.command("build").description("Generate .claude-plugin/marketplace.json and per-class plugins").option("--content <dir>", "local skills directory").option("--out <dir>", "output root").option("--check", "verify the committed marketplace output is current (CI)").option("--set-version <v>", "version to stamp into plugin manifests").action(
|
|
1672
2057
|
(opts) => run(
|
|
1673
2058
|
() => marketplaceBuildCommand({
|
|
1674
2059
|
cwd: process.cwd(),
|
|
1675
2060
|
content: opts.content,
|
|
1676
2061
|
out: opts.out,
|
|
1677
2062
|
check: opts.check,
|
|
1678
|
-
version: opts.
|
|
2063
|
+
version: opts.setVersion
|
|
1679
2064
|
}),
|
|
1680
2065
|
true
|
|
1681
2066
|
)
|
|
1682
2067
|
);
|
|
1683
|
-
program.parseAsync(process.argv)
|
|
1684
|
-
|
|
2068
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
2069
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
2070
|
+
process.exitCode = 1;
|
|
2071
|
+
});
|