@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.
Files changed (3) hide show
  1. package/README.md +42 -2
  2. package/dist/bin.js +261 -35
  3. 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 Apple's copyrighted text or sample code.
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";
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
- let targets = opts.targets?.length ? opts.targets : cfg.targets;
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 (problems.length === 0) {
1318
- log.success(`All ${diagnoses.length} installed skill(s) look healthy.`);
1319
- } else {
1320
- for (const p of problems) log.warn(p);
1321
- log.plain(`
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(() => searchCommand({ cwd: process.cwd(), query, content: opts.content, ref: opts.ref }))
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ichintansoni/skills-master",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Install tool-agnostic Apple & Android development skills into any AI coding tool (Claude Code, Cursor, Copilot, AGENTS.md).",
5
5
  "keywords": [
6
6
  "skills",