claude-code-arcane 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -265,6 +265,113 @@ async function runInstallWizard(profiles, detections) {
265
265
  );
266
266
  return names;
267
267
  }
268
+ async function runAddWizard(input) {
269
+ const installedProfiles = new Set(input.installedProfiles);
270
+ const installedSkills = new Set(input.installedSkills);
271
+ const addableProfiles = input.profiles.filter(
272
+ (pr) => !installedProfiles.has(pr.name)
273
+ );
274
+ const addableSkills = input.skills.filter((s) => !installedSkills.has(s.name));
275
+ p.intro(chalk.cyan("claude-code-arcane add"));
276
+ const current = input.installedProfiles.filter((n) => n !== "core");
277
+ p.note(
278
+ [
279
+ `Profiles: ${current.length > 0 ? chalk.green(current.join(", ")) : chalk.dim("core only")}`,
280
+ `Skills: ${chalk.green(String(installedSkills.size))} installed, ${chalk.dim(`${addableSkills.length} available`)}`
281
+ ].join("\n"),
282
+ "Current installation"
283
+ );
284
+ if (addableProfiles.length === 0 && addableSkills.length === 0) {
285
+ p.outro(chalk.dim("Everything in the catalog is already installed."));
286
+ return null;
287
+ }
288
+ const kind = await pickKind(addableProfiles.length, addableSkills.length);
289
+ if (kind === null) return null;
290
+ const items = kind === "profiles" ? await pickProfiles(addableProfiles) : await pickSkills(addableSkills);
291
+ if (items === null) return null;
292
+ if (items.length === 0) {
293
+ p.outro(
294
+ chalk.dim(
295
+ "Nothing selected. Run again, or add directly: npx claude-code-arcane add +testing docker-setup"
296
+ )
297
+ );
298
+ return null;
299
+ }
300
+ const expr = items.join(" ");
301
+ const ok = await p.confirm({ message: `Add ${chalk.cyan(expr)}?` });
302
+ if (p.isCancel(ok) || !ok) {
303
+ p.cancel("Add cancelled.");
304
+ return null;
305
+ }
306
+ p.outro(
307
+ chalk.dim(`Next time, skip the menu: npx claude-code-arcane add ${expr}`)
308
+ );
309
+ return items;
310
+ }
311
+ async function pickKind(profileCount, skillCount) {
312
+ if (profileCount === 0) return "skills";
313
+ if (skillCount === 0) return "profiles";
314
+ const kind = await p.select({
315
+ message: "What do you want to add?",
316
+ options: [
317
+ {
318
+ value: "profiles",
319
+ label: "Profiles",
320
+ hint: `${profileCount} not installed \u2014 brings its skills, rules and agents`
321
+ },
322
+ {
323
+ value: "skills",
324
+ label: "Individual skills",
325
+ hint: `${skillCount} not installed \u2014 search by name`
326
+ }
327
+ ]
328
+ });
329
+ if (p.isCancel(kind)) {
330
+ p.cancel("Add cancelled.");
331
+ return null;
332
+ }
333
+ return kind;
334
+ }
335
+ async function pickProfiles(addable) {
336
+ const options = {};
337
+ for (const group of groupByCategory(addable)) {
338
+ options[group.label] = group.profiles.map((pr) => ({
339
+ value: pr.name,
340
+ label: pr.name,
341
+ hint: hint(pr.description)
342
+ }));
343
+ }
344
+ const selected = await p.groupMultiselect({
345
+ message: "Select profiles to add (space to toggle, enter to confirm)",
346
+ options,
347
+ required: false
348
+ });
349
+ if (p.isCancel(selected)) {
350
+ p.cancel("Add cancelled.");
351
+ return null;
352
+ }
353
+ return selected.map((name) => `+${name}`);
354
+ }
355
+ async function pickSkills(addable) {
356
+ const selected = await p.autocompleteMultiselect({
357
+ message: "Search skills to add (type to filter, space to toggle)",
358
+ options: addable.map((s) => ({
359
+ value: s.name,
360
+ label: s.name,
361
+ hint: hint(s.description)
362
+ })),
363
+ required: false
364
+ });
365
+ if (p.isCancel(selected)) {
366
+ p.cancel("Add cancelled.");
367
+ return null;
368
+ }
369
+ return selected;
370
+ }
371
+ function hint(description, max = 90) {
372
+ const flat = description.replace(/\s+/g, " ").trim();
373
+ return flat.length > max ? `${flat.slice(0, max - 3)}...` : flat;
374
+ }
268
375
 
269
376
  // src/installer.ts
270
377
  import fs6 from "fs";
@@ -1528,10 +1635,53 @@ Installing profile: ${chalk2.cyan(profileExpr)}`)
1528
1635
  }
1529
1636
 
1530
1637
  // src/commands/add.ts
1638
+ import fs10 from "fs";
1639
+ import path13 from "path";
1640
+ import chalk3 from "chalk";
1641
+
1642
+ // src/skills-catalog.ts
1531
1643
  import fs9 from "fs";
1532
1644
  import path12 from "path";
1533
- import chalk3 from "chalk";
1534
- async function addCommand(items) {
1645
+ var FRONTMATTER_BYTES = 4096;
1646
+ function listSkills(skillsDir) {
1647
+ if (!fs9.existsSync(skillsDir)) return [];
1648
+ return fs9.readdirSync(skillsDir, { withFileTypes: true }).filter(
1649
+ (d) => d.isDirectory() && !d.name.startsWith("_") && fs9.existsSync(path12.join(skillsDir, d.name, "SKILL.md"))
1650
+ ).map((d) => ({
1651
+ name: d.name,
1652
+ description: readDescription(path12.join(skillsDir, d.name, "SKILL.md"))
1653
+ })).sort((a, b) => a.name.localeCompare(b.name));
1654
+ }
1655
+ function readDescription(skillFile) {
1656
+ const head = readHead(skillFile);
1657
+ const lines = head.split(/\r?\n/);
1658
+ const idx = lines.findIndex((l) => /^description:/.test(l));
1659
+ if (idx === -1) return "";
1660
+ const inline = lines[idx].slice("description:".length).trim();
1661
+ if (inline === "|" || inline === ">" || /^[|>][-+]?$/.test(inline)) {
1662
+ const block = [];
1663
+ for (const line of lines.slice(idx + 1)) {
1664
+ if (line.trim() === "") continue;
1665
+ if (!/^\s/.test(line)) break;
1666
+ block.push(line.trim());
1667
+ }
1668
+ return block.join(" ");
1669
+ }
1670
+ return inline.replace(/^["']|["']$/g, "").trim();
1671
+ }
1672
+ function readHead(file) {
1673
+ const fd = fs9.openSync(file, "r");
1674
+ try {
1675
+ const buf = Buffer.alloc(FRONTMATTER_BYTES);
1676
+ const read = fs9.readSync(fd, buf, 0, FRONTMATTER_BYTES, 0);
1677
+ return buf.subarray(0, read).toString("utf-8");
1678
+ } finally {
1679
+ fs9.closeSync(fd);
1680
+ }
1681
+ }
1682
+
1683
+ // src/commands/add.ts
1684
+ async function addCommand(items = []) {
1535
1685
  const target = process.cwd();
1536
1686
  const source = await resolveContentSource({ quiet: true });
1537
1687
  const root = await source.getContentRoot();
@@ -1544,7 +1694,21 @@ async function addCommand(items) {
1544
1694
  );
1545
1695
  process.exit(1);
1546
1696
  }
1547
- const claudeDir = path12.join(target, ".claude");
1697
+ if (items.length === 0) {
1698
+ if (!(process.stdin.isTTY && process.stdout.isTTY)) {
1699
+ printCatalog(root, manifest.profiles, manifest.installed_skills);
1700
+ return;
1701
+ }
1702
+ const selected = await runAddWizard({
1703
+ profiles: listProfiles(path13.join(root, "profiles")),
1704
+ skills: listSkills(path13.join(root, "skills")),
1705
+ installedProfiles: manifest.profiles,
1706
+ installedSkills: manifest.installed_skills
1707
+ });
1708
+ if (!selected) return;
1709
+ items = selected;
1710
+ }
1711
+ const claudeDir = path13.join(target, ".claude");
1548
1712
  const added = [];
1549
1713
  const skipped = [];
1550
1714
  const notFound = [];
@@ -1554,8 +1718,8 @@ async function addCommand(items) {
1554
1718
  for (const item of items) {
1555
1719
  if (item.startsWith("+")) {
1556
1720
  const profileName = item.slice(1);
1557
- const profilePath = path12.join(root, "profiles", `${profileName}.yaml`);
1558
- if (!fs9.existsSync(profilePath)) {
1721
+ const profilePath = path13.join(root, "profiles", `${profileName}.yaml`);
1722
+ if (!fs10.existsSync(profilePath)) {
1559
1723
  console.error(chalk3.red(` Profile '${profileName}' not found`));
1560
1724
  continue;
1561
1725
  }
@@ -1568,10 +1732,10 @@ async function addCommand(items) {
1568
1732
  }
1569
1733
  for (const rule of profile.rules.universal) {
1570
1734
  if (!manifest.installed_rules.includes(rule)) {
1571
- const src = path12.join(root, "rules", `${rule}.md`);
1572
- if (fs9.existsSync(src)) {
1573
- ensureDir(path12.join(claudeDir, "rules"));
1574
- fs9.copyFileSync(src, path12.join(claudeDir, "rules", `${rule}.md`));
1735
+ const src = path13.join(root, "rules", `${rule}.md`);
1736
+ if (fs10.existsSync(src)) {
1737
+ ensureDir(path13.join(claudeDir, "rules"));
1738
+ fs10.copyFileSync(src, path13.join(claudeDir, "rules", `${rule}.md`));
1575
1739
  manifest.installed_rules.push(rule);
1576
1740
  addedRules.push(rule);
1577
1741
  }
@@ -1579,10 +1743,10 @@ async function addCommand(items) {
1579
1743
  }
1580
1744
  for (const rule of profile.rules.gamedev) {
1581
1745
  if (!manifest.installed_rules.includes(rule)) {
1582
- const src = path12.join(root, "rules", "gamedev", `${rule}.md`);
1583
- if (fs9.existsSync(src)) {
1584
- ensureDir(path12.join(claudeDir, "rules"));
1585
- fs9.copyFileSync(src, path12.join(claudeDir, "rules", `${rule}.md`));
1746
+ const src = path13.join(root, "rules", "gamedev", `${rule}.md`);
1747
+ if (fs10.existsSync(src)) {
1748
+ ensureDir(path13.join(claudeDir, "rules"));
1749
+ fs10.copyFileSync(src, path13.join(claudeDir, "rules", `${rule}.md`));
1586
1750
  manifest.installed_rules.push(rule);
1587
1751
  addedRules.push(rule);
1588
1752
  }
@@ -1590,9 +1754,9 @@ async function addCommand(items) {
1590
1754
  }
1591
1755
  for (const agentDir of profile.agents) {
1592
1756
  if (!manifest.installed_agents.includes(agentDir)) {
1593
- const src = path12.join(root, "agents", agentDir);
1594
- if (fs9.existsSync(src)) {
1595
- const dst = path12.join(claudeDir, "agents", agentDir);
1757
+ const src = path13.join(root, "agents", agentDir);
1758
+ if (fs10.existsSync(src)) {
1759
+ const dst = path13.join(claudeDir, "agents", agentDir);
1596
1760
  copyDirSync(src, dst);
1597
1761
  manifest.installed_agents.push(agentDir);
1598
1762
  addedAgents.push(agentDir);
@@ -1600,9 +1764,9 @@ async function addCommand(items) {
1600
1764
  }
1601
1765
  }
1602
1766
  if (profileName === "statusline") {
1603
- const statuslineSrc = path12.join(root, "hooks", "statusline.sh");
1604
- if (fs9.existsSync(statuslineSrc)) {
1605
- fs9.copyFileSync(statuslineSrc, path12.join(claudeDir, "statusline.sh"));
1767
+ const statuslineSrc = path13.join(root, "hooks", "statusline.sh");
1768
+ if (fs10.existsSync(statuslineSrc)) {
1769
+ fs10.copyFileSync(statuslineSrc, path13.join(claudeDir, "statusline.sh"));
1606
1770
  statuslineAdded = true;
1607
1771
  }
1608
1772
  }
@@ -1636,8 +1800,12 @@ async function addCommand(items) {
1636
1800
  };
1637
1801
  writeManifest(target, merged, manifest.profile_command, root);
1638
1802
  const totalAdded = added.length + addedRules.length + addedAgents.length + (statuslineAdded ? 1 : 0);
1639
- console.log(chalk3.bold(`
1640
- Added ${totalAdded} items:`));
1803
+ console.log(
1804
+ chalk3.bold(
1805
+ totalAdded === 0 ? "\nNothing added." : `
1806
+ Added ${totalAdded} items:`
1807
+ )
1808
+ );
1641
1809
  for (const s of added) console.log(chalk3.green(` [ok] skill: ${s}`));
1642
1810
  for (const r of addedRules) console.log(chalk3.green(` [ok] rule: ${r}`));
1643
1811
  for (const a of addedAgents) console.log(chalk3.green(` [ok] agents: ${a}/`));
@@ -1646,18 +1814,54 @@ Added ${totalAdded} items:`));
1646
1814
  console.log(chalk3.dim(` [skip] ${s} (already installed)`));
1647
1815
  for (const s of notFound)
1648
1816
  console.log(chalk3.red(` [miss] ${s} (not found in source)`));
1817
+ if (totalAdded > 0) {
1818
+ console.log(
1819
+ chalk3.dim(
1820
+ `
1821
+ Now installed: ${manifest.installed_skills.length} skills, ${manifest.installed_rules.length} rules, ${manifest.installed_agents.length} agents.`
1822
+ )
1823
+ );
1824
+ }
1825
+ }
1826
+ function printCatalog(root, installedProfiles, installedSkills) {
1827
+ const profiles = listProfiles(path13.join(root, "profiles")).filter(
1828
+ (pr) => !installedProfiles.includes(pr.name)
1829
+ );
1830
+ const skills = listSkills(path13.join(root, "skills")).filter(
1831
+ (sk) => !installedSkills.includes(sk.name)
1832
+ );
1833
+ if (profiles.length === 0 && skills.length === 0) {
1834
+ console.log(chalk3.dim("\nEverything in the catalog is already installed.\n"));
1835
+ return;
1836
+ }
1837
+ console.log(chalk3.bold("\nProfiles you can add:\n"));
1838
+ for (const group of groupByCategory(profiles)) {
1839
+ console.log(chalk3.cyan(` ${group.label}:`));
1840
+ for (const pr of group.profiles) {
1841
+ console.log(
1842
+ ` ${chalk3.green(`+${pr.name}`.padEnd(20))} ${pr.description}`
1843
+ );
1844
+ }
1845
+ console.log();
1846
+ }
1847
+ console.log(
1848
+ chalk3.dim(` Plus ${skills.length} individual skills \u2014 see \`arcane list\`.`)
1849
+ );
1850
+ console.log(
1851
+ chalk3.dim(" Usage: npx claude-code-arcane add +testing docker-setup\n")
1852
+ );
1649
1853
  }
1650
1854
  function addSkill(root, target, skill, installed) {
1651
1855
  if (installed.includes(skill)) return "skipped";
1652
- const src = path12.join(root, "skills", skill);
1653
- if (!fs9.existsSync(src)) return "not-found";
1654
- const dst = path12.join(target, ".claude", "skills", skill);
1856
+ const src = path13.join(root, "skills", skill);
1857
+ if (!fs10.existsSync(src)) return "not-found";
1858
+ const dst = path13.join(target, ".claude", "skills", skill);
1655
1859
  copyDirSync(src, dst);
1656
1860
  return "added";
1657
1861
  }
1658
1862
  function mergePermissions(claudeDir, newPerms) {
1659
- const settingsPath = path12.join(claudeDir, "settings.json");
1660
- if (!fs9.existsSync(settingsPath)) return;
1863
+ const settingsPath = path13.join(claudeDir, "settings.json");
1864
+ if (!fs10.existsSync(settingsPath)) return;
1661
1865
  const settings = readJsonSync(settingsPath);
1662
1866
  const perms = settings.permissions ?? { allow: [], deny: [] };
1663
1867
  const allowSet = new Set(perms.allow);
@@ -1670,8 +1874,8 @@ function mergePermissions(claudeDir, newPerms) {
1670
1874
  writeJsonSync(settingsPath, settings);
1671
1875
  }
1672
1876
  function addStatuslineToSettings(claudeDir) {
1673
- const settingsPath = path12.join(claudeDir, "settings.json");
1674
- if (!fs9.existsSync(settingsPath)) return;
1877
+ const settingsPath = path13.join(claudeDir, "settings.json");
1878
+ if (!fs10.existsSync(settingsPath)) return;
1675
1879
  const settings = readJsonSync(settingsPath);
1676
1880
  if (settings.statusLine) return;
1677
1881
  settings.statusLine = {
@@ -1682,8 +1886,8 @@ function addStatuslineToSettings(claudeDir) {
1682
1886
  }
1683
1887
 
1684
1888
  // src/commands/remove.ts
1685
- import fs10 from "fs";
1686
- import path13 from "path";
1889
+ import fs11 from "fs";
1890
+ import path14 from "path";
1687
1891
  import chalk4 from "chalk";
1688
1892
  var CORE_SKILLS = [
1689
1893
  "commit",
@@ -1748,7 +1952,7 @@ async function removeCommand(items, opts = {}) {
1748
1952
  }
1749
1953
  manifest.total_skills = manifest.installed_skills.length;
1750
1954
  manifest.total_rules = manifest.installed_rules.length;
1751
- const mp = path13.join(target, ".claude", "arcane-manifest.json");
1955
+ const mp = path14.join(target, ".claude", "arcane-manifest.json");
1752
1956
  writeJsonSync(mp, manifest);
1753
1957
  const totalRemoved = removedSkills.length + removedAgents.length + removedProfiles.length;
1754
1958
  console.log(chalk4.bold(`
@@ -1769,9 +1973,9 @@ function removeSkill(target, skill, manifest) {
1769
1973
  );
1770
1974
  return "skipped";
1771
1975
  }
1772
- const skillDir = path13.join(target, ".claude", "skills", skill);
1773
- if (!fs10.existsSync(skillDir)) return "skipped";
1774
- fs10.rmSync(skillDir, { recursive: true, force: true });
1976
+ const skillDir = path14.join(target, ".claude", "skills", skill);
1977
+ if (!fs11.existsSync(skillDir)) return "skipped";
1978
+ fs11.rmSync(skillDir, { recursive: true, force: true });
1775
1979
  manifest.installed_skills = manifest.installed_skills.filter(
1776
1980
  (s) => s !== skill
1777
1981
  );
@@ -1790,8 +1994,8 @@ function removeProfile(root, target, profileName, manifest) {
1790
1994
  );
1791
1995
  return { removed: false, skills: [], agents: [] };
1792
1996
  }
1793
- const profilePath = path13.join(root, "profiles", `${profileName}.yaml`);
1794
- if (!fs10.existsSync(profilePath)) {
1997
+ const profilePath = path14.join(root, "profiles", `${profileName}.yaml`);
1998
+ if (!fs11.existsSync(profilePath)) {
1795
1999
  console.warn(
1796
2000
  chalk4.yellow(` WARNING: Profile '${profileName}.yaml' not found.`)
1797
2001
  );
@@ -1805,8 +2009,8 @@ function removeProfile(root, target, profileName, manifest) {
1805
2009
  const sharedAgents = /* @__PURE__ */ new Set();
1806
2010
  const sharedRules = /* @__PURE__ */ new Set();
1807
2011
  for (const rp of remainingProfiles) {
1808
- const rpPath = path13.join(root, "profiles", `${rp}.yaml`);
1809
- if (!fs10.existsSync(rpPath)) continue;
2012
+ const rpPath = path14.join(root, "profiles", `${rp}.yaml`);
2013
+ if (!fs11.existsSync(rpPath)) continue;
1810
2014
  const rpDef = parseProfile(rpPath);
1811
2015
  rpDef.skills.forEach((s) => sharedSkills.add(s));
1812
2016
  rpDef.agents.forEach((a) => sharedAgents.add(a));
@@ -1817,9 +2021,9 @@ function removeProfile(root, target, profileName, manifest) {
1817
2021
  for (const skill of profile.skills) {
1818
2022
  if (sharedSkills.has(skill)) continue;
1819
2023
  if (CORE_SKILLS.includes(skill)) continue;
1820
- const skillDir = path13.join(target, ".claude", "skills", skill);
1821
- if (fs10.existsSync(skillDir)) {
1822
- fs10.rmSync(skillDir, { recursive: true, force: true });
2024
+ const skillDir = path14.join(target, ".claude", "skills", skill);
2025
+ if (fs11.existsSync(skillDir)) {
2026
+ fs11.rmSync(skillDir, { recursive: true, force: true });
1823
2027
  removedSkills.push(skill);
1824
2028
  }
1825
2029
  manifest.installed_skills = manifest.installed_skills.filter(
@@ -1829,9 +2033,9 @@ function removeProfile(root, target, profileName, manifest) {
1829
2033
  const removedAgents = [];
1830
2034
  for (const agent of profile.agents) {
1831
2035
  if (sharedAgents.has(agent)) continue;
1832
- const agentDir = path13.join(target, ".claude", "agents", agent);
1833
- if (fs10.existsSync(agentDir)) {
1834
- fs10.rmSync(agentDir, { recursive: true, force: true });
2036
+ const agentDir = path14.join(target, ".claude", "agents", agent);
2037
+ if (fs11.existsSync(agentDir)) {
2038
+ fs11.rmSync(agentDir, { recursive: true, force: true });
1835
2039
  removedAgents.push(agent);
1836
2040
  }
1837
2041
  manifest.installed_agents = manifest.installed_agents.filter(
@@ -1843,44 +2047,44 @@ function removeProfile(root, target, profileName, manifest) {
1843
2047
  ...profile.rules.gamedev
1844
2048
  ]) {
1845
2049
  if (sharedRules.has(rule)) continue;
1846
- const ruleFile = path13.join(target, ".claude", "rules", `${rule}.md`);
1847
- if (fs10.existsSync(ruleFile)) {
1848
- fs10.rmSync(ruleFile);
2050
+ const ruleFile = path14.join(target, ".claude", "rules", `${rule}.md`);
2051
+ if (fs11.existsSync(ruleFile)) {
2052
+ fs11.rmSync(ruleFile);
1849
2053
  }
1850
2054
  manifest.installed_rules = manifest.installed_rules.filter(
1851
2055
  (r) => r !== rule
1852
2056
  );
1853
2057
  }
1854
2058
  if (profileName === "statusline") {
1855
- const statuslineFile = path13.join(target, ".claude", "statusline.sh");
1856
- if (fs10.existsSync(statuslineFile)) {
1857
- fs10.rmSync(statuslineFile);
2059
+ const statuslineFile = path14.join(target, ".claude", "statusline.sh");
2060
+ if (fs11.existsSync(statuslineFile)) {
2061
+ fs11.rmSync(statuslineFile);
1858
2062
  }
1859
- removeStatuslineFromSettings(path13.join(target, ".claude"));
2063
+ removeStatuslineFromSettings(path14.join(target, ".claude"));
1860
2064
  }
1861
2065
  manifest.profiles = remainingProfiles;
1862
2066
  manifest.profile_command = remainingProfiles.filter((p2) => p2 !== "core").join("+");
1863
2067
  return { removed: true, skills: removedSkills, agents: removedAgents };
1864
2068
  }
1865
2069
  function removeStatuslineFromSettings(claudeDir) {
1866
- const settingsPath = path13.join(claudeDir, "settings.json");
1867
- if (!fs10.existsSync(settingsPath)) return;
1868
- const settings = JSON.parse(fs10.readFileSync(settingsPath, "utf-8"));
2070
+ const settingsPath = path14.join(claudeDir, "settings.json");
2071
+ if (!fs11.existsSync(settingsPath)) return;
2072
+ const settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
1869
2073
  if (settings.statusLine) {
1870
2074
  delete settings.statusLine;
1871
- fs10.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2075
+ fs11.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
1872
2076
  }
1873
2077
  }
1874
2078
 
1875
2079
  // src/commands/list.ts
1876
- import fs11 from "fs";
1877
- import path14 from "path";
2080
+ import fs12 from "fs";
2081
+ import path15 from "path";
1878
2082
  import chalk5 from "chalk";
1879
2083
  async function listCommand() {
1880
2084
  const source = await resolveContentSource({ quiet: true });
1881
2085
  const root = await source.getContentRoot();
1882
- const profilesDir = path14.join(root, "profiles");
1883
- const skillsDir = path14.join(root, "skills");
2086
+ const profilesDir = path15.join(root, "profiles");
2087
+ const skillsDir = path15.join(root, "skills");
1884
2088
  const target = process.cwd();
1885
2089
  const manifest = readManifest(target);
1886
2090
  const profiles = listProfiles(profilesDir);
@@ -1897,10 +2101,8 @@ async function listCommand() {
1897
2101
  }
1898
2102
  console.log();
1899
2103
  }
1900
- if (fs11.existsSync(skillsDir)) {
1901
- const allSkills = fs11.readdirSync(skillsDir, { withFileTypes: true }).filter(
1902
- (d) => d.isDirectory() && !d.name.startsWith("_") && fs11.existsSync(path14.join(skillsDir, d.name, "SKILL.md"))
1903
- ).map((d) => d.name).sort();
2104
+ if (fs12.existsSync(skillsDir)) {
2105
+ const allSkills = listSkills(skillsDir).map((s) => s.name);
1904
2106
  console.log(chalk5.bold(`
1905
2107
  === Skills (${allSkills.length}) ===
1906
2108
  `));
@@ -1966,8 +2168,8 @@ async function statusCommand() {
1966
2168
  }
1967
2169
 
1968
2170
  // src/commands/update.ts
1969
- import fs12 from "fs";
1970
- import path15 from "path";
2171
+ import fs13 from "fs";
2172
+ import path16 from "path";
1971
2173
  import os4 from "os";
1972
2174
  import chalk7 from "chalk";
1973
2175
 
@@ -2039,12 +2241,12 @@ async function updateCommand(opts) {
2039
2241
  }
2040
2242
  }
2041
2243
  const cwd = process.cwd();
2042
- if (fs12.existsSync(manifestPath(cwd))) {
2244
+ if (fs13.existsSync(manifestPath(cwd))) {
2043
2245
  registerInstallation(cwd);
2044
2246
  }
2045
2247
  const targets = pruneRegistry().map((e) => e.path);
2046
2248
  const globalTarget = os4.homedir();
2047
- if (!process.env.VITEST && fs12.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
2249
+ if (!process.env.VITEST && fs13.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
2048
2250
  targets.push(globalTarget);
2049
2251
  }
2050
2252
  if (targets.length === 0) {
@@ -2120,11 +2322,11 @@ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersi
2120
2322
  );
2121
2323
  }
2122
2324
  const profileNames = manifest.profile_command.split("+").filter(Boolean);
2123
- const profilesDir = path15.join(root, "profiles");
2325
+ const profilesDir = path16.join(root, "profiles");
2124
2326
  const merged = mergeProfiles(profilesDir, profileNames);
2125
2327
  const allRules = [...merged.rules.universal, ...merged.rules.gamedev];
2126
2328
  const sourceHashes = computeSourceHashes(root, merged.skills, allRules, merged.agents);
2127
- const claudeDir = path15.join(target, ".claude");
2329
+ const claudeDir = path16.join(target, ".claude");
2128
2330
  const installedHashes = computeContentHashes(claudeDir);
2129
2331
  const items = computeUpdatePlan(
2130
2332
  manifest.content_hashes ?? null,
@@ -2317,48 +2519,48 @@ function applyUpdates(items, root, claudeDir, merged, force) {
2317
2519
  function applyItem(item, root, claudeDir) {
2318
2520
  const src = getSourcePath(item, root);
2319
2521
  const dst = getInstalledPath(item, claudeDir);
2320
- if (!src || !fs12.existsSync(src)) return;
2522
+ if (!src || !fs13.existsSync(src)) return;
2321
2523
  if (item.type === "skill" || item.type === "agent") {
2322
- if (fs12.existsSync(dst)) {
2323
- fs12.rmSync(dst, { recursive: true, force: true });
2524
+ if (fs13.existsSync(dst)) {
2525
+ fs13.rmSync(dst, { recursive: true, force: true });
2324
2526
  }
2325
2527
  copyDirSync(src, dst);
2326
2528
  } else {
2327
- const dir = path15.dirname(dst);
2328
- if (!fs12.existsSync(dir)) {
2329
- fs12.mkdirSync(dir, { recursive: true });
2529
+ const dir = path16.dirname(dst);
2530
+ if (!fs13.existsSync(dir)) {
2531
+ fs13.mkdirSync(dir, { recursive: true });
2330
2532
  }
2331
- fs12.copyFileSync(src, dst);
2533
+ fs13.copyFileSync(src, dst);
2332
2534
  }
2333
2535
  }
2334
2536
  function backupItem(item, claudeDir) {
2335
2537
  const installed = getInstalledPath(item, claudeDir);
2336
- if (!fs12.existsSync(installed)) return;
2538
+ if (!fs13.existsSync(installed)) return;
2337
2539
  const backupPath = installed + ".bak";
2338
- if (fs12.existsSync(backupPath)) {
2339
- fs12.rmSync(backupPath, { recursive: true, force: true });
2540
+ if (fs13.existsSync(backupPath)) {
2541
+ fs13.rmSync(backupPath, { recursive: true, force: true });
2340
2542
  }
2341
- fs12.renameSync(installed, backupPath);
2543
+ fs13.renameSync(installed, backupPath);
2342
2544
  }
2343
2545
  function removeItem(item, claudeDir) {
2344
2546
  const installed = getInstalledPath(item, claudeDir);
2345
- if (fs12.existsSync(installed)) {
2346
- fs12.rmSync(installed, { recursive: true, force: true });
2547
+ if (fs13.existsSync(installed)) {
2548
+ fs13.rmSync(installed, { recursive: true, force: true });
2347
2549
  }
2348
2550
  }
2349
2551
  function getSourcePath(item, root) {
2350
2552
  switch (item.type) {
2351
2553
  case "skill":
2352
- return path15.join(root, "skills", item.name);
2554
+ return path16.join(root, "skills", item.name);
2353
2555
  case "rule": {
2354
- const direct = path15.join(root, "rules", item.name);
2355
- if (fs12.existsSync(direct)) return direct;
2356
- return path15.join(root, "rules", "gamedev", item.name);
2556
+ const direct = path16.join(root, "rules", item.name);
2557
+ if (fs13.existsSync(direct)) return direct;
2558
+ return path16.join(root, "rules", "gamedev", item.name);
2357
2559
  }
2358
2560
  case "agent":
2359
- return path15.join(root, "agents", item.name);
2561
+ return path16.join(root, "agents", item.name);
2360
2562
  case "hook":
2361
- return path15.join(root, "hooks", item.name);
2563
+ return path16.join(root, "hooks", item.name);
2362
2564
  default:
2363
2565
  return null;
2364
2566
  }
@@ -2366,13 +2568,13 @@ function getSourcePath(item, root) {
2366
2568
  function getInstalledPath(item, claudeDir) {
2367
2569
  switch (item.type) {
2368
2570
  case "skill":
2369
- return path15.join(claudeDir, "skills", item.name);
2571
+ return path16.join(claudeDir, "skills", item.name);
2370
2572
  case "rule":
2371
- return path15.join(claudeDir, "rules", item.name);
2573
+ return path16.join(claudeDir, "rules", item.name);
2372
2574
  case "agent":
2373
- return path15.join(claudeDir, "agents", item.name);
2575
+ return path16.join(claudeDir, "agents", item.name);
2374
2576
  case "hook":
2375
- return path15.join(claudeDir, "hooks", item.name);
2577
+ return path16.join(claudeDir, "hooks", item.name);
2376
2578
  }
2377
2579
  }
2378
2580
 
@@ -2407,8 +2609,8 @@ async function cleanCommand(opts) {
2407
2609
  }
2408
2610
 
2409
2611
  // src/commands/worktree.ts
2410
- import fs13 from "fs";
2411
- import path16 from "path";
2612
+ import fs14 from "fs";
2613
+ import path17 from "path";
2412
2614
  import { execSync as execSync2 } from "child_process";
2413
2615
  import chalk9 from "chalk";
2414
2616
  async function worktreeCommand(branch, opts) {
@@ -2419,8 +2621,8 @@ async function worktreeCommand(branch, opts) {
2419
2621
  process.exit(1);
2420
2622
  }
2421
2623
  const repoRoot = wtInfo.isWorktree ? wtInfo.mainWorktreePath : cwd;
2422
- const worktreePath = opts.path ? path16.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
2423
- if (fs13.existsSync(worktreePath)) {
2624
+ const worktreePath = opts.path ? path17.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
2625
+ if (fs14.existsSync(worktreePath)) {
2424
2626
  console.error(
2425
2627
  chalk9.red(`Path already exists: ${worktreePath}`)
2426
2628
  );
@@ -2467,7 +2669,7 @@ async function worktreeCommand(branch, opts) {
2467
2669
  }
2468
2670
  console.log(" Installing Arcane...");
2469
2671
  const root = getPackageRoot();
2470
- const profilesDir = path16.join(root, "profiles");
2672
+ const profilesDir = path17.join(root, "profiles");
2471
2673
  const profileNames = profileExpr.split("+").filter(Boolean);
2472
2674
  const merged = mergeProfiles(profilesDir, profileNames);
2473
2675
  const shareFrom = opts.share !== false ? cwd : void 0;
@@ -2503,13 +2705,13 @@ async function worktreeCommand(branch, opts) {
2503
2705
  }
2504
2706
  }
2505
2707
  if (opts.isolate) {
2506
- const isolateScript = path16.join(
2708
+ const isolateScript = path17.join(
2507
2709
  worktreePath,
2508
2710
  ".claude",
2509
2711
  "skills",
2510
2712
  "worktree-isolation"
2511
2713
  );
2512
- if (fs13.existsSync(isolateScript)) {
2714
+ if (fs14.existsSync(isolateScript)) {
2513
2715
  console.log(" Applying worktree isolation...");
2514
2716
  try {
2515
2717
  execSync2(
@@ -2544,13 +2746,13 @@ async function worktreeCommand(branch, opts) {
2544
2746
  }
2545
2747
 
2546
2748
  // src/commands/global.ts
2547
- import fs14 from "fs";
2548
- import path17 from "path";
2749
+ import fs15 from "fs";
2750
+ import path18 from "path";
2549
2751
  import os5 from "os";
2550
2752
  import chalk10 from "chalk";
2551
- var CLAUDE_HOME = path17.join(os5.homedir(), ".claude");
2552
- var SCRIPTS_DIR = path17.join(CLAUDE_HOME, "scripts", "worktree-isolation");
2553
- var SETTINGS_PATH = path17.join(CLAUDE_HOME, "settings.json");
2753
+ var CLAUDE_HOME = path18.join(os5.homedir(), ".claude");
2754
+ var SCRIPTS_DIR = path18.join(CLAUDE_HOME, "scripts", "worktree-isolation");
2755
+ var SETTINGS_PATH = path18.join(CLAUDE_HOME, "settings.json");
2554
2756
  async function globalCommand(opts) {
2555
2757
  if (opts.status) {
2556
2758
  showStatus();
@@ -2564,8 +2766,8 @@ async function globalCommand(opts) {
2564
2766
  }
2565
2767
  function showStatus() {
2566
2768
  console.log(chalk10.bold("\n=== Arcane Global Status ===\n"));
2567
- const hasScripts = fs14.existsSync(
2568
- path17.join(SCRIPTS_DIR, "apply.py")
2769
+ const hasScripts = fs15.existsSync(
2770
+ path18.join(SCRIPTS_DIR, "apply.py")
2569
2771
  );
2570
2772
  console.log(
2571
2773
  ` Worktree scripts: ${hasScripts ? chalk10.green("installed") : chalk10.dim("not installed")}`
@@ -2583,32 +2785,32 @@ function showStatus() {
2583
2785
  function installGlobal() {
2584
2786
  console.log(chalk10.bold("\nInstalling Arcane global hooks...\n"));
2585
2787
  const root = getPackageRoot();
2586
- const scriptsSrc = path17.join(
2788
+ const scriptsSrc = path18.join(
2587
2789
  root,
2588
2790
  "skills",
2589
2791
  "worktree-isolation",
2590
2792
  "scripts"
2591
2793
  );
2592
- if (!fs14.existsSync(scriptsSrc)) {
2794
+ if (!fs15.existsSync(scriptsSrc)) {
2593
2795
  console.error(
2594
2796
  chalk10.red(" worktree-isolation scripts not found in package.")
2595
2797
  );
2596
2798
  process.exit(1);
2597
2799
  }
2598
- fs14.mkdirSync(SCRIPTS_DIR, { recursive: true });
2800
+ fs15.mkdirSync(SCRIPTS_DIR, { recursive: true });
2599
2801
  for (const file of ["apply.py", "lib.py"]) {
2600
- const src = path17.join(scriptsSrc, file);
2601
- const dst = path17.join(SCRIPTS_DIR, file);
2602
- if (!fs14.existsSync(src)) {
2802
+ const src = path18.join(scriptsSrc, file);
2803
+ const dst = path18.join(SCRIPTS_DIR, file);
2804
+ if (!fs15.existsSync(src)) {
2603
2805
  console.warn(chalk10.yellow(` WARN: ${file} not found, skipping`));
2604
2806
  continue;
2605
2807
  }
2606
- if (fs14.existsSync(dst) && fs14.readFileSync(src).equals(fs14.readFileSync(dst))) {
2808
+ if (fs15.existsSync(dst) && fs15.readFileSync(src).equals(fs15.readFileSync(dst))) {
2607
2809
  console.log(chalk10.dim(` [skip] ${file} (already up-to-date)`));
2608
2810
  } else {
2609
- fs14.copyFileSync(src, dst);
2811
+ fs15.copyFileSync(src, dst);
2610
2812
  if (process.platform !== "win32") {
2611
- fs14.chmodSync(dst, 493);
2813
+ fs15.chmodSync(dst, 493);
2612
2814
  }
2613
2815
  console.log(chalk10.green(` [ok] ${file} -> ${SCRIPTS_DIR}`));
2614
2816
  }
@@ -2632,8 +2834,8 @@ function installGlobal() {
2632
2834
  }
2633
2835
  function removeGlobal() {
2634
2836
  console.log(chalk10.bold("\nRemoving Arcane global hooks...\n"));
2635
- if (fs14.existsSync(SCRIPTS_DIR)) {
2636
- fs14.rmSync(SCRIPTS_DIR, { recursive: true, force: true });
2837
+ if (fs15.existsSync(SCRIPTS_DIR)) {
2838
+ fs15.rmSync(SCRIPTS_DIR, { recursive: true, force: true });
2637
2839
  console.log(chalk10.green(" [ok] Removed worktree-isolation scripts"));
2638
2840
  } else {
2639
2841
  console.log(chalk10.dim(" [skip] Scripts not found"));
@@ -2647,16 +2849,16 @@ function removeGlobal() {
2647
2849
  console.log(chalk10.bold.green("\n Global hooks removed.\n"));
2648
2850
  }
2649
2851
  function readSettings() {
2650
- if (!fs14.existsSync(SETTINGS_PATH)) return {};
2852
+ if (!fs15.existsSync(SETTINGS_PATH)) return {};
2651
2853
  try {
2652
- return JSON.parse(fs14.readFileSync(SETTINGS_PATH, "utf-8"));
2854
+ return JSON.parse(fs15.readFileSync(SETTINGS_PATH, "utf-8"));
2653
2855
  } catch {
2654
2856
  return {};
2655
2857
  }
2656
2858
  }
2657
2859
  function writeSettings(settings) {
2658
- fs14.mkdirSync(path17.dirname(SETTINGS_PATH), { recursive: true });
2659
- fs14.writeFileSync(
2860
+ fs15.mkdirSync(path18.dirname(SETTINGS_PATH), { recursive: true });
2861
+ fs15.writeFileSync(
2660
2862
  SETTINGS_PATH,
2661
2863
  JSON.stringify(settings, null, 2) + "\n",
2662
2864
  "utf-8"
@@ -2713,15 +2915,34 @@ function removeHookFromSettings() {
2713
2915
  }
2714
2916
 
2715
2917
  // src/update-check.ts
2716
- import fs15 from "fs";
2717
- import path18 from "path";
2918
+ import fs16 from "fs";
2919
+ import path19 from "path";
2718
2920
  import os6 from "os";
2719
2921
  import chalk11 from "chalk";
2720
- var CHECK_FILE = path18.join(os6.homedir(), ".arcane", "last-check.json");
2922
+ var CHECK_FILE = path19.join(os6.homedir(), ".arcane", "last-check.json");
2721
2923
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
2722
2924
  var GITHUB_OWNER2 = "SebastianLuser";
2723
2925
  var GITHUB_REPO2 = "Claude-Code-Arcane";
2724
2926
  var GITHUB_BRANCH2 = "main";
2927
+ var PACKAGE_NAME2 = "claude-code-arcane";
2928
+ var SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
2929
+ function compareSemver(a, b) {
2930
+ const release = (v) => v.trim().replace(/^v/, "").split(/[-+]/)[0];
2931
+ const parts = (v) => release(v).split(".").map((n) => Number.parseInt(n, 10) || 0);
2932
+ const [aMajor = 0, aMinor = 0, aPatch = 0] = parts(a);
2933
+ const [bMajor = 0, bMinor = 0, bPatch = 0] = parts(b);
2934
+ if (aMajor !== bMajor) return aMajor - bMajor;
2935
+ if (aMinor !== bMinor) return aMinor - bMinor;
2936
+ if (aPatch !== bPatch) return aPatch - bPatch;
2937
+ const aPre = /-/.test(a.trim());
2938
+ const bPre = /-/.test(b.trim());
2939
+ if (aPre !== bPre) return aPre ? -1 : 1;
2940
+ return 0;
2941
+ }
2942
+ function isContentUpdateAvailable(localVersion, remoteSha) {
2943
+ if (!SHA_PATTERN.test(localVersion)) return false;
2944
+ return !remoteSha.startsWith(localVersion) && !localVersion.startsWith(remoteSha);
2945
+ }
2725
2946
  async function checkForUpdates(opts = {}) {
2726
2947
  try {
2727
2948
  const cached = readCachedCheck();
@@ -2729,40 +2950,62 @@ async function checkForUpdates(opts = {}) {
2729
2950
  const age = Date.now() - new Date(cached.checked_at).getTime();
2730
2951
  if (age < CHECK_INTERVAL_MS) {
2731
2952
  if (cached.update_available && !opts.quiet) {
2732
- printUpdateNotice(cached.local_version, cached.remote_sha);
2953
+ printUpdateNotice(cached);
2733
2954
  }
2734
2955
  return cached.update_available;
2735
2956
  }
2736
2957
  }
2737
2958
  const manifest = readManifest(process.cwd());
2738
2959
  const localVersion = manifest?.source_version ?? manifest?.arcane_version ?? "unknown";
2739
- const remoteSha = await getLatestCommitSha();
2740
- if (!remoteSha) return false;
2741
- const shortSha = remoteSha.substring(0, 12);
2742
- const updateAvailable = localVersion !== shortSha && !remoteSha.startsWith(localVersion);
2960
+ const cliVersion = getPackageVersion();
2961
+ const [remoteSha, latestCli] = await Promise.all([
2962
+ getLatestCommitSha(),
2963
+ getLatestNpmVersion()
2964
+ ]);
2965
+ const shortSha = remoteSha ? remoteSha.substring(0, 12) : cached?.remote_sha ?? "";
2966
+ const contentUpdate = shortSha !== "" && isContentUpdateAvailable(localVersion, shortSha);
2967
+ const cliUpdate = latestCli !== null && compareSemver(latestCli, cliVersion) > 0;
2968
+ if (!remoteSha && latestCli === null) return false;
2743
2969
  const result = {
2744
2970
  checked_at: (/* @__PURE__ */ new Date()).toISOString(),
2745
2971
  local_version: localVersion,
2746
2972
  remote_sha: shortSha,
2747
- update_available: updateAvailable
2973
+ update_available: contentUpdate || cliUpdate,
2974
+ content_update_available: contentUpdate,
2975
+ cli_version: cliVersion,
2976
+ latest_cli_version: latestCli ?? void 0,
2977
+ cli_update_available: cliUpdate
2748
2978
  };
2749
2979
  writeCachedCheck(result);
2750
- if (updateAvailable && !opts.quiet) {
2751
- printUpdateNotice(localVersion, shortSha);
2980
+ if (result.update_available && !opts.quiet) {
2981
+ printUpdateNotice(result);
2752
2982
  }
2753
- return updateAvailable;
2983
+ return result.update_available;
2754
2984
  } catch {
2755
2985
  return false;
2756
2986
  }
2757
2987
  }
2758
- function printUpdateNotice(localVersion, remoteSha) {
2759
- console.log(
2760
- chalk11.yellow(`
2761
- Arcane update available: ${localVersion} \u2192 ${remoteSha}`)
2762
- );
2763
- console.log(
2764
- chalk11.dim(" Run: arcane update (--dry-run to preview)\n")
2765
- );
2988
+ function printUpdateNotice(result) {
2989
+ if (result.cli_update_available) {
2990
+ console.log(
2991
+ chalk11.yellow(
2992
+ `
2993
+ Arcane CLI update: ${result.cli_version} \u2192 ${result.latest_cli_version}`
2994
+ )
2995
+ );
2996
+ const how = isGloballyInstalled() ? ` Run: npm install -g ${PACKAGE_NAME2}@latest` : ` You are on an older CLI. Latest: ${PACKAGE_NAME2}@${result.latest_cli_version}`;
2997
+ console.log(chalk11.dim(how));
2998
+ }
2999
+ if (result.content_update_available ?? true) {
3000
+ console.log(
3001
+ chalk11.yellow(
3002
+ `
3003
+ Arcane content update: ${result.local_version} \u2192 ${result.remote_sha}`
3004
+ )
3005
+ );
3006
+ console.log(chalk11.dim(" Run: arcane update (--dry-run to preview)"));
3007
+ }
3008
+ console.log("");
2766
3009
  }
2767
3010
  async function getLatestCommitSha() {
2768
3011
  try {
@@ -2783,21 +3026,37 @@ async function getLatestCommitSha() {
2783
3026
  return null;
2784
3027
  }
2785
3028
  }
3029
+ async function getLatestNpmVersion() {
3030
+ try {
3031
+ const response = await fetch(
3032
+ `https://registry.npmjs.org/${PACKAGE_NAME2}/latest`,
3033
+ {
3034
+ headers: { "User-Agent": "arcane-cli", Accept: "application/json" },
3035
+ signal: AbortSignal.timeout(5e3)
3036
+ }
3037
+ );
3038
+ if (!response.ok) return null;
3039
+ const data = await response.json();
3040
+ return typeof data.version === "string" ? data.version : null;
3041
+ } catch {
3042
+ return null;
3043
+ }
3044
+ }
2786
3045
  function readCachedCheck() {
2787
3046
  try {
2788
- if (!fs15.existsSync(CHECK_FILE)) return null;
2789
- return JSON.parse(fs15.readFileSync(CHECK_FILE, "utf-8"));
3047
+ if (!fs16.existsSync(CHECK_FILE)) return null;
3048
+ return JSON.parse(fs16.readFileSync(CHECK_FILE, "utf-8"));
2790
3049
  } catch {
2791
3050
  return null;
2792
3051
  }
2793
3052
  }
2794
3053
  function writeCachedCheck(result) {
2795
3054
  try {
2796
- const dir = path18.dirname(CHECK_FILE);
2797
- if (!fs15.existsSync(dir)) {
2798
- fs15.mkdirSync(dir, { recursive: true });
3055
+ const dir = path19.dirname(CHECK_FILE);
3056
+ if (!fs16.existsSync(dir)) {
3057
+ fs16.mkdirSync(dir, { recursive: true });
2799
3058
  }
2800
- fs15.writeFileSync(CHECK_FILE, JSON.stringify(result, null, 2) + "\n");
3059
+ fs16.writeFileSync(CHECK_FILE, JSON.stringify(result, null, 2) + "\n");
2801
3060
  } catch {
2802
3061
  }
2803
3062
  }
@@ -2815,9 +3074,9 @@ program.command("install [profile]").description(
2815
3074
  ).option("--source <source>", "Content source: auto, github, or bundled", "auto").action(async (profile, opts) => {
2816
3075
  await installCommand(profile, opts);
2817
3076
  });
2818
- program.command("add <items...>").description(
2819
- "Add skills or profiles. Use +profileName for profiles, skillName for individual skills."
2820
- ).action(async (items) => {
3077
+ program.command("add [items...]").description(
3078
+ "Add skills or profiles. Use +profileName for profiles, skillName for individual skills. Without args, opens an interactive picker."
3079
+ ).action(async (items = []) => {
2821
3080
  await addCommand(items);
2822
3081
  });
2823
3082
  program.command("remove <items...>").description(