@ichintansoni/skills-master 0.1.7 → 0.1.9
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 +42 -2
- package/dist/bin.js +261 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,10 +14,13 @@ npx @ichintansoni/skills-master list --class code # browse the catalo
|
|
|
14
14
|
npx @ichintansoni/skills-master search navigation
|
|
15
15
|
npx @ichintansoni/skills-master update # pull newer skill versions
|
|
16
16
|
npx @ichintansoni/skills-master remove swiftui-sheets
|
|
17
|
+
npx @ichintansoni/skills-master sync # re-emit to match the current config
|
|
17
18
|
npx @ichintansoni/skills-master status # what's installed, and has it drifted?
|
|
18
19
|
npx @ichintansoni/skills-master doctor # same detection, but fails on drift
|
|
19
20
|
```
|
|
20
21
|
|
|
22
|
+
`update` pulls newer skill content, but only ever re-emits to the targets a skill was *already* installed to. So adding a target to `skills-master.json` after the fact, or changing a `paths` override, leaves config and disk disagreeing with nothing to reconcile them. `sync` is that reconciliation — it treats the configured target set as the source of truth and makes disk match. Local edits are preserved unless you pass `--overwrite`. A target you *dropped* from config is reported but only deleted with `--prune`; a `paths` change is a move, so the old copy is cleaned up straight away rather than left for agents to load twice.
|
|
23
|
+
|
|
21
24
|
`status` and `doctor` share their drift detection but answer different questions. `status` is an inventory — it lists every installed skill with its version, targets, and whether the output has been edited or deleted, works entirely offline, and always exits 0, so it is safe to pipe (`status --json`, `status --problems`, `status <name>…`). `doctor` is the gate: same findings, but it exits non-zero, which is what you want in CI.
|
|
22
25
|
|
|
23
26
|
`add` writes, per detected tool:
|
|
@@ -29,7 +32,7 @@ npx @ichintansoni/skills-master doctor # same detection,
|
|
|
29
32
|
| GitHub Copilot | `.github/instructions/<name>.instructions.md` |
|
|
30
33
|
| AGENTS.md | a sentinel-marked block (your hand-written content is preserved) |
|
|
31
34
|
|
|
32
|
-
Flags: `--target claude,cursor,copilot,agents|all` · `--with-pairs` (also install the paired code↔design skill) · `--dry-run` · `--overwrite` · `--content <dir>` (use a local skills checkout) · `--ref <git-ref>`.
|
|
35
|
+
Flags: `--target claude,cursor,copilot,agents|all` · `--with-pairs` (also install the paired code↔design skill) · `--dry-run` · `--overwrite` · `--content <dir>` (use a local skills checkout) · `--ref <git-ref>`. An explicit `--target` or `--ref` is remembered in `skills-master.json`, and `--target` widens the configured set rather than replacing it.
|
|
33
36
|
|
|
34
37
|
Claude Code users can alternatively install via the plugin marketplace:
|
|
35
38
|
|
|
@@ -38,8 +41,45 @@ Claude Code users can alternatively install via the plugin marketplace:
|
|
|
38
41
|
/plugin install skills-master-apple-code@skills-master
|
|
39
42
|
```
|
|
40
43
|
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
Run `skills-master <command> --help` for the full flag list on any of these.
|
|
47
|
+
|
|
48
|
+
### Browsing the catalog
|
|
49
|
+
|
|
50
|
+
| Command | What it does |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `list` | List available skills. `--domain` · `--class` · `--category` · `--platform` · `--json` |
|
|
53
|
+
| `search <query>` | Match a query against names, descriptions, facets, and tags. Spacing, hyphens and case are ignored, so `wear os`, `wear-os` and `WearOS` all find the same skills. `--json` |
|
|
54
|
+
| `view <name>` | Show a skill's metadata and body. `--raw` prints the body alone; `--json` returns metadata *and* body in one document |
|
|
55
|
+
|
|
56
|
+
### Working in a project
|
|
57
|
+
|
|
58
|
+
| Command | What it does |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `init` | Detect which tools the project uses and write `skills-master.json`. `--target` · `--commit` · `--ref` · `--force` |
|
|
61
|
+
| `add <names…>` | Install skills by name, category, or class |
|
|
62
|
+
| `update [names…]` | Re-install skills whose **content** changed upstream. Only touches targets a skill is already installed to |
|
|
63
|
+
| `sync [names…]` | Re-emit to match the **current config** — new targets, moved `paths`, deleted files. `--overwrite` · `--prune` · `--dry-run` |
|
|
64
|
+
| `remove <names…>` | Remove installed skills. `--target` removes from one tool only |
|
|
65
|
+
| `status [names…]` | Inventory: versions, targets, and local-edit state. Offline, always exits 0. `--problems` · `--json` |
|
|
66
|
+
| `doctor` | The same detection as `status`, but **exits non-zero** on drift — the CI gate. `--json` |
|
|
67
|
+
|
|
68
|
+
`update` vs `sync` is the distinction worth knowing: `update` follows *content*, `sync` follows *config*.
|
|
69
|
+
|
|
70
|
+
### Maintaining the library
|
|
71
|
+
|
|
72
|
+
These operate on a skills checkout rather than a consuming project.
|
|
73
|
+
|
|
74
|
+
| Command | What it does |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `lint` | Validate the library: naming, reciprocal `pairs_with`, body caps, tag and source rules |
|
|
77
|
+
| `new <domain/class/category/name>` | Scaffold a skill from the canonical template. `--force` |
|
|
78
|
+
| `registry build` | Regenerate `registry.json`. `--check` fails on drift instead of rewriting |
|
|
79
|
+
| `marketplace build` | Regenerate the Claude plugin marketplace. `--check` fails on drift |
|
|
80
|
+
|
|
41
81
|
## How it works
|
|
42
82
|
|
|
43
83
|
Content lives in the [skills-master repo](https://github.com/iChintanSoni/skills-master) and is fetched on demand; the CLI compiles each skill into your tools' formats and records what it installed in `skills-master.json` + a lockfile so `update`/`remove` stay surgical. Generated files are committed to your repo by default, so teammates' IDEs pick them up without running anything.
|
|
44
84
|
|
|
45
|
-
MIT licensed. Skill content is original prose that summarizes Apple's publicly documented best practices and links to the canonical docs — it does not reproduce
|
|
85
|
+
MIT licensed. Skill content is original prose that summarizes Apple's and Google's publicly documented best practices and links to the canonical docs — it does not reproduce either vendor's copyrighted text or sample code.
|
package/dist/bin.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// package.json
|
|
7
|
-
var version = "0.1.
|
|
7
|
+
var version = "0.1.9";
|
|
8
8
|
|
|
9
9
|
// src/schema/projectConfig.ts
|
|
10
10
|
import { z } from "zod";
|
|
@@ -1037,13 +1037,31 @@ function ensureGitignored(root, entries) {
|
|
|
1037
1037
|
writeFileSync3(p, next, "utf8");
|
|
1038
1038
|
}
|
|
1039
1039
|
|
|
1040
|
+
// src/util/targets.ts
|
|
1041
|
+
var VALID = new Set(ALL_TARGETS);
|
|
1042
|
+
function resolveTargets(cwd, configured, explicit) {
|
|
1043
|
+
if (explicit?.length) return explicit;
|
|
1044
|
+
if (configured.length) return configured;
|
|
1045
|
+
const detected = detectTargets(cwd);
|
|
1046
|
+
return detected.length ? detected : ALL_TARGETS;
|
|
1047
|
+
}
|
|
1048
|
+
function parseTargets(value) {
|
|
1049
|
+
if (!value) return void 0;
|
|
1050
|
+
if (value === "all") return ALL_TARGETS;
|
|
1051
|
+
const ids = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1052
|
+
for (const id of ids) {
|
|
1053
|
+
if (!VALID.has(id)) {
|
|
1054
|
+
throw new Error(`Unknown target "${id}". Valid: ${[...VALID, "all"].join(", ")}.`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return ids;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1040
1060
|
// src/commands/add.ts
|
|
1041
1061
|
async function addCommand(opts) {
|
|
1042
1062
|
const cfg = loadConfigOrDefault(opts.cwd);
|
|
1043
1063
|
const hadConfig = loadConfig(opts.cwd) != null;
|
|
1044
|
-
|
|
1045
|
-
if (!targets.length) targets = detectTargets(opts.cwd);
|
|
1046
|
-
if (!targets.length) targets = ALL_TARGETS;
|
|
1064
|
+
const targets = resolveTargets(opts.cwd, cfg.targets, opts.targets);
|
|
1047
1065
|
const content = await resolveContent({
|
|
1048
1066
|
content: opts.content,
|
|
1049
1067
|
ref: opts.ref ?? cfg.contentRef,
|
|
@@ -1110,6 +1128,23 @@ async function addCommand(opts) {
|
|
|
1110
1128
|
if (!hadConfig) {
|
|
1111
1129
|
saveConfig(opts.cwd, { ...cfg, targets });
|
|
1112
1130
|
log.info("Wrote skills-master.json.");
|
|
1131
|
+
} else {
|
|
1132
|
+
const merged = [.../* @__PURE__ */ new Set([...cfg.targets, ...opts.targets ?? []])].sort();
|
|
1133
|
+
const targetsChanged = opts.targets?.length != null && merged.join() !== [...cfg.targets].sort().join();
|
|
1134
|
+
const refChanged = opts.ref != null && opts.ref !== cfg.contentRef;
|
|
1135
|
+
if (targetsChanged || refChanged) {
|
|
1136
|
+
const next = {
|
|
1137
|
+
...cfg,
|
|
1138
|
+
targets: targetsChanged ? merged : cfg.targets,
|
|
1139
|
+
contentRef: refChanged ? opts.ref : cfg.contentRef
|
|
1140
|
+
};
|
|
1141
|
+
saveConfig(opts.cwd, next);
|
|
1142
|
+
const what = [
|
|
1143
|
+
targetsChanged ? `targets: ${next.targets.join(", ")}` : null,
|
|
1144
|
+
refChanged ? `ref: ${next.contentRef}` : null
|
|
1145
|
+
].filter(Boolean);
|
|
1146
|
+
log.info(`Updated skills-master.json (${what.join(", ")}).`);
|
|
1147
|
+
}
|
|
1113
1148
|
}
|
|
1114
1149
|
if (!cfg.commit) {
|
|
1115
1150
|
ensureGitignored(
|
|
@@ -1288,16 +1323,34 @@ function doctorCommand(opts) {
|
|
|
1288
1323
|
const problems = [];
|
|
1289
1324
|
const note = (msg) => problems.push(msg);
|
|
1290
1325
|
const cfg = loadConfig(opts.cwd);
|
|
1291
|
-
if (!cfg) {
|
|
1292
|
-
log.warn("No skills-master.json found \u2014 run `skills-master init`.");
|
|
1293
|
-
} else {
|
|
1294
|
-
log.info(`Config targets: ${cfg.targets.length ? cfg.targets.join(", ") : "(auto-detect)"}`);
|
|
1295
|
-
}
|
|
1296
1326
|
const lock = loadLockfile(opts.cwd);
|
|
1297
1327
|
const diagnoses = diagnoseInstalled(opts.cwd, lock);
|
|
1328
|
+
const emit = (report) => {
|
|
1329
|
+
if (!opts.json) return report;
|
|
1330
|
+
log.plain(
|
|
1331
|
+
JSON.stringify(
|
|
1332
|
+
{
|
|
1333
|
+
ok: report.ok,
|
|
1334
|
+
problems: report.problems,
|
|
1335
|
+
skills: diagnoses,
|
|
1336
|
+
configuredTargets: cfg?.targets ?? []
|
|
1337
|
+
},
|
|
1338
|
+
null,
|
|
1339
|
+
2
|
|
1340
|
+
)
|
|
1341
|
+
);
|
|
1342
|
+
return report;
|
|
1343
|
+
};
|
|
1344
|
+
if (!opts.json) {
|
|
1345
|
+
if (!cfg) {
|
|
1346
|
+
log.warn("No skills-master.json found \u2014 run `skills-master init`.");
|
|
1347
|
+
} else {
|
|
1348
|
+
log.info(`Config targets: ${cfg.targets.length ? cfg.targets.join(", ") : "(auto-detect)"}`);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1298
1351
|
if (diagnoses.length === 0) {
|
|
1299
|
-
log.info("No skills installed.");
|
|
1300
|
-
return { problems, ok: true };
|
|
1352
|
+
if (!opts.json) log.info("No skills installed.");
|
|
1353
|
+
return emit({ problems, ok: true });
|
|
1301
1354
|
}
|
|
1302
1355
|
for (const skill of diagnoses) {
|
|
1303
1356
|
for (const t of skill.targets) {
|
|
@@ -1314,14 +1367,16 @@ function doctorCommand(opts) {
|
|
|
1314
1367
|
}
|
|
1315
1368
|
}
|
|
1316
1369
|
}
|
|
1317
|
-
if (
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1370
|
+
if (!opts.json) {
|
|
1371
|
+
if (problems.length === 0) {
|
|
1372
|
+
log.success(`All ${diagnoses.length} installed skill(s) look healthy.`);
|
|
1373
|
+
} else {
|
|
1374
|
+
for (const p of problems) log.warn(p);
|
|
1375
|
+
log.plain(`
|
|
1322
1376
|
${problems.length} problem(s) found.`);
|
|
1377
|
+
}
|
|
1323
1378
|
}
|
|
1324
|
-
return { problems, ok: problems.length === 0 };
|
|
1379
|
+
return emit({ problems, ok: problems.length === 0 });
|
|
1325
1380
|
}
|
|
1326
1381
|
|
|
1327
1382
|
// src/commands/status.ts
|
|
@@ -1378,6 +1433,147 @@ function statusCommand(opts) {
|
|
|
1378
1433
|
return report;
|
|
1379
1434
|
}
|
|
1380
1435
|
|
|
1436
|
+
// src/commands/sync.ts
|
|
1437
|
+
async function syncCommand(opts) {
|
|
1438
|
+
const cfg = loadConfigOrDefault(opts.cwd);
|
|
1439
|
+
const lock = loadLockfile(opts.cwd);
|
|
1440
|
+
const targets = resolveTargets(opts.cwd, cfg.targets);
|
|
1441
|
+
const paths = resolvePaths(cfg);
|
|
1442
|
+
const prefix = opts.dryRun ? "[dry-run] " : "";
|
|
1443
|
+
const all = Object.keys(lock.skills).sort();
|
|
1444
|
+
const names = opts.names?.length ? all.filter((n) => opts.names.includes(n)) : all;
|
|
1445
|
+
const result = {
|
|
1446
|
+
synced: [],
|
|
1447
|
+
skipped: [],
|
|
1448
|
+
addedTargets: [],
|
|
1449
|
+
orphaned: [],
|
|
1450
|
+
stale: [],
|
|
1451
|
+
pruned: false
|
|
1452
|
+
};
|
|
1453
|
+
if (all.length === 0) {
|
|
1454
|
+
log.info("No skills installed \u2014 run `skills-master add <name>`.");
|
|
1455
|
+
return result;
|
|
1456
|
+
}
|
|
1457
|
+
if (names.length === 0) {
|
|
1458
|
+
log.warn("No installed skills match.");
|
|
1459
|
+
return result;
|
|
1460
|
+
}
|
|
1461
|
+
const edited = new Map(
|
|
1462
|
+
diagnoseInstalled(opts.cwd, lock).map((d) => [d.name, d.targets.some((t) => t.edited)])
|
|
1463
|
+
);
|
|
1464
|
+
const content = await resolveContent({
|
|
1465
|
+
content: opts.content,
|
|
1466
|
+
ref: opts.ref ?? cfg.contentRef,
|
|
1467
|
+
cwd: opts.cwd
|
|
1468
|
+
});
|
|
1469
|
+
log.info(`Syncing ${names.length} skill(s) to targets: ${targets.join(", ")}`);
|
|
1470
|
+
const addedTargets = /* @__PURE__ */ new Set();
|
|
1471
|
+
const pruneNames = [];
|
|
1472
|
+
const pruneTargets = /* @__PURE__ */ new Set();
|
|
1473
|
+
for (const name of names) {
|
|
1474
|
+
const locked = lock.skills[name];
|
|
1475
|
+
const installedTo = Object.keys(locked.emitted);
|
|
1476
|
+
const orphans = installedTo.filter((t) => !targets.includes(t));
|
|
1477
|
+
if (orphans.length) {
|
|
1478
|
+
result.orphaned.push({ name, targets: orphans });
|
|
1479
|
+
pruneNames.push(name);
|
|
1480
|
+
for (const t of orphans) pruneTargets.add(t);
|
|
1481
|
+
}
|
|
1482
|
+
if (edited.get(name) && !opts.overwrite) {
|
|
1483
|
+
log.warn(`${prefix}"${name}" has local edits \u2014 skipping (use --overwrite to replace).`);
|
|
1484
|
+
result.skipped.push(name);
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
let skill;
|
|
1488
|
+
try {
|
|
1489
|
+
skill = content.loadSkill(name);
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
if (err instanceof SkillNotFoundError) {
|
|
1492
|
+
log.warn(`"${name}" no longer exists in the content library \u2014 leaving it in place.`);
|
|
1493
|
+
} else {
|
|
1494
|
+
log.error(`Failed to load "${name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1495
|
+
}
|
|
1496
|
+
result.skipped.push(name);
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
for (const t of targets) if (!locked.emitted[t]) addedTargets.add(t);
|
|
1500
|
+
const prevFiles = /* @__PURE__ */ new Set();
|
|
1501
|
+
const prevBlocks = /* @__PURE__ */ new Map();
|
|
1502
|
+
for (const t of targets) {
|
|
1503
|
+
const e = locked.emitted[t];
|
|
1504
|
+
if (!e) continue;
|
|
1505
|
+
for (const f of e.files) prevFiles.add(f);
|
|
1506
|
+
if (e.block) prevBlocks.set(t, e.block);
|
|
1507
|
+
}
|
|
1508
|
+
const emitted = installSkill(opts.cwd, skill, targets, paths, {
|
|
1509
|
+
dryRun: opts.dryRun,
|
|
1510
|
+
overwrite: true
|
|
1511
|
+
// edits were already checked above
|
|
1512
|
+
});
|
|
1513
|
+
const nowFiles = /* @__PURE__ */ new Set();
|
|
1514
|
+
for (const e of Object.values(emitted.locked.emitted)) {
|
|
1515
|
+
for (const f of e.files) nowFiles.add(f);
|
|
1516
|
+
}
|
|
1517
|
+
const staleFiles = [...prevFiles].filter((f) => !nowFiles.has(f));
|
|
1518
|
+
const staleBlocks = [...prevBlocks].filter(([t, b]) => emitted.locked.emitted[t]?.block !== b).map(([, b]) => b);
|
|
1519
|
+
if (staleFiles.length || staleBlocks.length) {
|
|
1520
|
+
result.stale.push({ name, files: staleFiles, blocks: staleBlocks });
|
|
1521
|
+
}
|
|
1522
|
+
if (!opts.dryRun) {
|
|
1523
|
+
lock.skills[name] = {
|
|
1524
|
+
...emitted.locked,
|
|
1525
|
+
emitted: { ...locked.emitted, ...emitted.locked.emitted }
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
result.synced.push(name);
|
|
1529
|
+
for (const r of emitted.results) {
|
|
1530
|
+
if (r.action === "unchanged") continue;
|
|
1531
|
+
const tag = r.mode === "block" ? `${r.path} [${r.blockId}]` : r.path;
|
|
1532
|
+
log.info(`${prefix}${r.action.padEnd(9)} ${tag}`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
result.addedTargets = [...addedTargets].sort();
|
|
1536
|
+
if (!opts.dryRun) saveLockfile(opts.cwd, lock);
|
|
1537
|
+
if (result.orphaned.length) {
|
|
1538
|
+
const list = [...pruneTargets].sort().join(", ");
|
|
1539
|
+
if (opts.prune) {
|
|
1540
|
+
removeCommand({
|
|
1541
|
+
cwd: opts.cwd,
|
|
1542
|
+
names: pruneNames,
|
|
1543
|
+
targets: [...pruneTargets],
|
|
1544
|
+
dryRun: opts.dryRun
|
|
1545
|
+
});
|
|
1546
|
+
result.pruned = true;
|
|
1547
|
+
} else {
|
|
1548
|
+
log.warn(
|
|
1549
|
+
`${result.orphaned.length} skill(s) still have output for target(s) the config no longer lists: ${list}. Re-run with --prune to remove.`
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
if (result.stale.length) {
|
|
1554
|
+
const gone = [];
|
|
1555
|
+
for (const f of result.stale.flatMap((s) => s.files)) {
|
|
1556
|
+
if (removeWholeFile(opts.cwd, f, opts.dryRun)) {
|
|
1557
|
+
gone.push(f);
|
|
1558
|
+
log.info(`${prefix}moved-from ${f}`);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
pruneEmptyDirs(opts.cwd, gone, opts.dryRun);
|
|
1562
|
+
for (const s of result.stale) {
|
|
1563
|
+
for (const b of s.blocks) {
|
|
1564
|
+
if (removeBlockFromFile(opts.cwd, b, s.name, opts.dryRun)) {
|
|
1565
|
+
log.info(`${prefix}unblocked ${b} [${s.name}]`);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
const bits = [`${result.synced.length} synced`];
|
|
1571
|
+
if (result.addedTargets.length) bits.push(`new target(s): ${result.addedTargets.join(", ")}`);
|
|
1572
|
+
if (result.skipped.length) bits.push(`${result.skipped.length} skipped`);
|
|
1573
|
+
log.success(`${prefix}${bits.join(", ")}.`);
|
|
1574
|
+
return result;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1381
1577
|
// src/core/search-text.ts
|
|
1382
1578
|
function searchNormalize(value) {
|
|
1383
1579
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
@@ -1437,6 +1633,10 @@ async function searchCommand(opts) {
|
|
|
1437
1633
|
[s.name, s.description, s.domain, s.category, s.class, ...s.tags].join(" ")
|
|
1438
1634
|
).includes(q)
|
|
1439
1635
|
);
|
|
1636
|
+
if (opts.json) {
|
|
1637
|
+
log.plain(JSON.stringify(hits, null, 2));
|
|
1638
|
+
return hits;
|
|
1639
|
+
}
|
|
1440
1640
|
if (hits.length === 0) {
|
|
1441
1641
|
log.info(`No matches for "${opts.query}".`);
|
|
1442
1642
|
return hits;
|
|
@@ -1453,6 +1653,24 @@ async function viewCommand(opts) {
|
|
|
1453
1653
|
const content = await resolveContent({ content: opts.content, ref: opts.ref, cwd: opts.cwd });
|
|
1454
1654
|
const skill = content.loadSkill(opts.name);
|
|
1455
1655
|
const xm = skill.frontmatter["x-skills-master"];
|
|
1656
|
+
if (opts.json) {
|
|
1657
|
+
log.plain(
|
|
1658
|
+
JSON.stringify(
|
|
1659
|
+
{
|
|
1660
|
+
name: skill.name,
|
|
1661
|
+
description: skill.frontmatter.description,
|
|
1662
|
+
globs: skill.frontmatter.globs ?? [],
|
|
1663
|
+
tags: skill.frontmatter.tags ?? [],
|
|
1664
|
+
...xm,
|
|
1665
|
+
resources: Object.entries(skill.resources).filter(([, v]) => v).map(([k]) => k),
|
|
1666
|
+
body: skill.body
|
|
1667
|
+
},
|
|
1668
|
+
null,
|
|
1669
|
+
2
|
|
1670
|
+
)
|
|
1671
|
+
);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1456
1674
|
if (opts.raw) {
|
|
1457
1675
|
log.plain(skill.body);
|
|
1458
1676
|
return;
|
|
@@ -1922,20 +2140,6 @@ async function newSkillCommand(opts) {
|
|
|
1922
2140
|
return skillMd;
|
|
1923
2141
|
}
|
|
1924
2142
|
|
|
1925
|
-
// src/util/targets.ts
|
|
1926
|
-
var VALID = new Set(ALL_TARGETS);
|
|
1927
|
-
function parseTargets(value) {
|
|
1928
|
-
if (!value) return void 0;
|
|
1929
|
-
if (value === "all") return ALL_TARGETS;
|
|
1930
|
-
const ids = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1931
|
-
for (const id of ids) {
|
|
1932
|
-
if (!VALID.has(id)) {
|
|
1933
|
-
throw new Error(`Unknown target "${id}". Valid: ${[...VALID, "all"].join(", ")}.`);
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
return ids;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
2143
|
// src/bin.ts
|
|
1940
2144
|
async function run(fn, exitOnFalse = false) {
|
|
1941
2145
|
try {
|
|
@@ -1973,15 +2177,24 @@ program.command("list").description("List available skills").option("--domain <d
|
|
|
1973
2177
|
})
|
|
1974
2178
|
)
|
|
1975
2179
|
);
|
|
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(
|
|
1977
|
-
(query, opts) => run(
|
|
2180
|
+
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)").option("--json", "machine-readable output").action(
|
|
2181
|
+
(query, opts) => run(
|
|
2182
|
+
() => searchCommand({
|
|
2183
|
+
cwd: process.cwd(),
|
|
2184
|
+
query,
|
|
2185
|
+
content: opts.content,
|
|
2186
|
+
ref: opts.ref,
|
|
2187
|
+
json: opts.json
|
|
2188
|
+
})
|
|
2189
|
+
)
|
|
1978
2190
|
);
|
|
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(
|
|
2191
|
+
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)").option("--json", "machine-readable output (metadata plus body)").action(
|
|
1980
2192
|
(name, opts) => run(
|
|
1981
2193
|
() => viewCommand({
|
|
1982
2194
|
cwd: process.cwd(),
|
|
1983
2195
|
name,
|
|
1984
2196
|
raw: opts.raw,
|
|
2197
|
+
json: opts.json,
|
|
1985
2198
|
content: opts.content,
|
|
1986
2199
|
ref: opts.ref
|
|
1987
2200
|
})
|
|
@@ -2023,6 +2236,19 @@ program.command("remove <names...>").description("Remove installed skills").opti
|
|
|
2023
2236
|
})
|
|
2024
2237
|
)
|
|
2025
2238
|
);
|
|
2239
|
+
program.command("sync").description("Re-emit installed skills to match the current config (new targets, moved paths)").argument("[names...]", "limit the sync to these installed skills").option("--content <dir>", "local skills directory").option("--ref <git-ref>", "content ref to read from").option("--overwrite", "replace locally edited outputs").option("--prune", "delete outputs for targets the config no longer lists").option("--dry-run", "show what would change without writing").action(
|
|
2240
|
+
(names, opts) => run(
|
|
2241
|
+
() => syncCommand({
|
|
2242
|
+
cwd: process.cwd(),
|
|
2243
|
+
names,
|
|
2244
|
+
content: opts.content,
|
|
2245
|
+
ref: opts.ref,
|
|
2246
|
+
overwrite: opts.overwrite,
|
|
2247
|
+
prune: opts.prune,
|
|
2248
|
+
dryRun: opts.dryRun
|
|
2249
|
+
})
|
|
2250
|
+
)
|
|
2251
|
+
);
|
|
2026
2252
|
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(
|
|
2027
2253
|
(names, opts) => run(
|
|
2028
2254
|
() => statusCommand({
|
|
@@ -2033,7 +2259,7 @@ program.command("status").description("Show installed skills: versions, targets,
|
|
|
2033
2259
|
})
|
|
2034
2260
|
)
|
|
2035
2261
|
);
|
|
2036
|
-
program.command("doctor").description("Check installed skills for drift and missing files").action(() => run(() => doctorCommand({ cwd: process.cwd() }).ok, true));
|
|
2262
|
+
program.command("doctor").description("Check installed skills for drift and missing files").option("--json", "machine-readable output").action((opts) => run(() => doctorCommand({ cwd: process.cwd(), json: opts.json }).ok, true));
|
|
2037
2263
|
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
2264
|
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
2265
|
(spec, opts) => run(
|
package/package.json
CHANGED