skilllib 1.7.0 → 1.11.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.
@@ -24,6 +24,9 @@ function libraryDir() {
24
24
  function claudeDir() {
25
25
  return process.env.CLAUDE_CONFIG_DIR ?? join(userHome(), ".claude");
26
26
  }
27
+ function grokDir() {
28
+ return process.env.GROK_HOME?.trim() || join(userHome(), ".grok");
29
+ }
27
30
  function codexHome() {
28
31
  return process.env.CODEX_HOME?.trim() || join(userHome(), ".codex");
29
32
  }
@@ -32,7 +35,11 @@ function codexSystemDir() {
32
35
  }
33
36
  var PROJECT_SKILLS_DIR = ".claude/skills";
34
37
  var AGENTS_SKILLS_DIR = ".agents/skills";
38
+ var GROK_SKILLS_DIR = ".grok/skills";
35
39
  var MANIFEST_FILE = "skilllib.json";
40
+ function isRepoSkillLocation(location) {
41
+ return location === AGENTS_SKILLS_DIR || location === GROK_SKILLS_DIR;
42
+ }
36
43
  var OWN_SKILL_MARKER = ".managed-by-skilllib";
37
44
 
38
45
  // src/output.ts
@@ -40,7 +47,7 @@ var jsonMode = false;
40
47
  function setJsonMode(on) {
41
48
  jsonMode = on;
42
49
  }
43
- function json(data) {
50
+ function json(output, data) {
44
51
  process.stdout.write(JSON.stringify(data) + "\n");
45
52
  }
46
53
  var color = (code) => (s) => process.stdout.isTTY && !jsonMode ? `\x1B[${code}m${s}\x1B[0m` : s;
@@ -103,7 +110,7 @@ var HARNESSES = [
103
110
  icon: "\u2B21",
104
111
  color: "#E8E8E8",
105
112
  // The first folder is where links for Cursor go (see linkEverywhere). Cursor also reads .grok/skills;
106
- // skilllib leaves that folder to Grok, since it can't tell what Grok needs.
113
+ // skilllib leaves that folder alone (see Grok). A .grok-only skill stays invisible to Cursor here.
107
114
  projectDirs: [".agents/skills", ".cursor/skills", ".claude/skills", ".codex/skills"],
108
115
  // Cursor ignores CLAUDE_CONFIG_DIR and CODEX_HOME: it always reads these under your home folder.
109
116
  globalDirs: () => [".agents", ".cursor", ".claude", ".codex"].map((d) => join2(home(), d, "skills")),
@@ -128,6 +135,19 @@ var HARNESSES = [
128
135
  projectDirs: [".agents/skills"],
129
136
  globalDirs: () => [join2(home(), ".agents", "skills")],
130
137
  installed: () => existsSync(join2(home(), ".config", "zed")) || onPath("zed") || hasApp("Zed")
138
+ },
139
+ {
140
+ id: "grok",
141
+ name: "Grok",
142
+ short: "Gk",
143
+ icon: "\u2736",
144
+ color: "#F5C16C",
145
+ // .agents/skills is first so a skill Grok can't already see is linked into the shared folder, not .grok/skills.
146
+ projectDirs: [".agents/skills", ".grok/skills", ".claude/skills", ".cursor/skills"],
147
+ // Grok ignores CLAUDE_CONFIG_DIR: the Claude compat path is always ~/.claude/skills.
148
+ // The first folder is where global links for Grok go, so those links stay out of ~/.grok/skills.
149
+ globalDirs: () => [join2(home(), ".agents", "skills"), join2(grokDir(), "skills"), join2(home(), ".claude", "skills"), join2(home(), ".cursor", "skills")],
150
+ installed: () => existsSync(grokDir()) || onPath("grok") || hasApp("Grok")
131
151
  }
132
152
  ];
133
153
  function onPath(bin) {
@@ -322,7 +342,10 @@ function knownProjects() {
322
342
  const path = projectsFile();
323
343
  if (!existsSync4(path)) return [];
324
344
  const list2 = JSON.parse(readFileSync3(path, "utf-8"));
325
- return onePerFolder(list2.filter((p) => existsSync4(p) && isProjectCandidate(p)));
345
+ return onePerFolder(list2.filter((p) => existsSync4(p) && isProjectCandidate(p) && !isAgentWorktree(p)));
346
+ }
347
+ function isAgentWorktree(path) {
348
+ return /[\\/]\.claude[\\/]worktrees[\\/]/.test(path);
326
349
  }
327
350
  function onePerFolder(paths) {
328
351
  const byReal = /* @__PURE__ */ new Map();
@@ -337,7 +360,7 @@ function isProjectCandidate(root) {
337
360
  return realPath(root) !== realPath(userHome());
338
361
  }
339
362
  function rememberProjects(roots) {
340
- const incoming = roots.map((r) => resolve3(r)).filter(isProjectCandidate);
363
+ const incoming = roots.map((r) => resolve3(r)).filter((r) => isProjectCandidate(r) && !isAgentWorktree(r));
341
364
  const all = onePerFolder([...knownProjects(), ...incoming]).sort();
342
365
  mkdirSync2(skilllibHome(), { recursive: true });
343
366
  writeFileSync2(projectsFile(), JSON.stringify(all, null, 2) + "\n");
@@ -550,6 +573,20 @@ function globalFolderSkills(enabled) {
550
573
  function tildify2(p) {
551
574
  return tildify(p).replace(/\\/g, "/");
552
575
  }
576
+ function grokBuiltInSkills(enabled) {
577
+ if (!enabled.includes("grok")) return [];
578
+ return skillDirsIn(join7(grokDir(), "bundled", "skills")).map((path) => ({
579
+ name: basename2(path),
580
+ kind: "built-in",
581
+ origin: "Grok",
582
+ path,
583
+ description: readSkillInfo(path).description,
584
+ movable: false,
585
+ broken: false,
586
+ harnesses: ["grok"],
587
+ links: []
588
+ }));
589
+ }
553
590
  function cursorBuiltInSkills(enabled) {
554
591
  if (!enabled.includes("cursor")) return [];
555
592
  return skillDirsIn(join7(userHome(), ".cursor", "skills-cursor")).map((path) => ({
@@ -565,30 +602,41 @@ function cursorBuiltInSkills(enabled) {
565
602
  }));
566
603
  }
567
604
  function claudeAiSkills(enabled) {
568
- const synced = join7(claudeDir(), "skills", "synced");
569
- const harnesses = harnessesReading(join7(claudeDir(), "skills"), enabled);
570
- if (!existsSync6(synced) || harnesses.length === 0) return [];
571
- return readdirSync3(synced).filter((d) => !d.startsWith(".")).flatMap(
572
- (bucket) => skillDirsIn(join7(synced, bucket)).map((path) => ({
573
- name: basename2(path),
574
- kind: "claude.ai",
575
- origin: "claude.ai account",
576
- path,
577
- description: readSkillInfo(path).description,
578
- movable: false,
579
- broken: false,
580
- harnesses,
581
- links: []
582
- }))
605
+ const byReal = /* @__PURE__ */ new Map();
606
+ for (const skills of [join7(claudeDir(), "skills"), join7(userHome(), ".claude", "skills")]) {
607
+ const synced = join7(skills, "synced");
608
+ const real = realpathOrNull(synced);
609
+ const readers = harnessesReading(skills, enabled);
610
+ if (!real || readers.length === 0) continue;
611
+ const entry = byReal.get(real) ?? { dir: synced, readers: [] };
612
+ for (const id of readers) if (!entry.readers.includes(id)) entry.readers.push(id);
613
+ byReal.set(real, entry);
614
+ }
615
+ return [...byReal.values()].flatMap(
616
+ ({ dir, readers }) => readdirSync3(dir).filter((d) => !d.startsWith(".")).flatMap(
617
+ (bucket) => skillDirsIn(join7(dir, bucket)).map((path) => ({
618
+ name: basename2(path),
619
+ kind: "claude.ai",
620
+ origin: "claude.ai account",
621
+ path,
622
+ description: readSkillInfo(path).description,
623
+ movable: false,
624
+ broken: false,
625
+ harnesses: enabled.filter((id) => readers.includes(id)),
626
+ links: []
627
+ }))
628
+ )
583
629
  );
584
630
  }
585
- function setPluginEnabled(id, on) {
586
- const file = join7(claudeDir(), "settings.json");
587
- const settings = existsSync6(file) ? readJson(file) : {};
588
- if (!settings) throw new Error(`${file} isn't valid JSON, so skilllib won't rewrite it`);
589
- settings.enabledPlugins = { ...settings.enabledPlugins, [id]: on };
590
- mkdirSync4(claudeDir(), { recursive: true });
591
- writeFileSync4(file, JSON.stringify(settings, null, 2) + "\n");
631
+ function pluginDirCandidates(id, installPaths) {
632
+ const [name = "", marketplace = ""] = id.split("@");
633
+ const plugins = join7(claudeDir(), "plugins");
634
+ return [
635
+ ...installPaths,
636
+ join7(plugins, "marketplaces", marketplace, "plugins", name),
637
+ join7(plugins, "marketplaces", marketplace, "external_plugins", name),
638
+ join7(plugins, "marketplaces", marketplace)
639
+ ];
592
640
  }
593
641
  function repoSettingsFiles(root) {
594
642
  return [join7(root, ".claude", "settings.json"), join7(root, ".claude", "settings.local.json")];
@@ -606,13 +654,7 @@ function pluginSkills(harnesses, root) {
606
654
  const user = enabledPlugins();
607
655
  const enabled = root ? enabledPlugins(root) : user;
608
656
  const fromMarketplaces = [.../* @__PURE__ */ new Set([...Object.keys(enabled), ...Object.keys(user)])].filter((id) => claude2 && enabled[id] || cursor && user[id]).flatMap((id) => {
609
- const [name = "", marketplace = ""] = id.split("@");
610
- const candidates = [
611
- ...(installed[id] ?? []).flatMap((i) => i.installPath ? [i.installPath] : []),
612
- join7(plugins, "marketplaces", marketplace, "plugins", name),
613
- join7(plugins, "marketplaces", marketplace, "external_plugins", name),
614
- join7(plugins, "marketplaces", marketplace)
615
- ];
657
+ const candidates = pluginDirCandidates(id, (installed[id] ?? []).flatMap((i) => i.installPath ? [i.installPath] : []));
616
658
  const dir = candidates.find((c) => existsSync6(join7(c, "skills")));
617
659
  if (!dir) return [];
618
660
  const inCursor = cursor && user[id] && (installed[id] ?? []).some((i) => i.scope === "user" && i.installPath === dir);
@@ -647,6 +689,7 @@ function machineSkills(enabled = enabledHarnesses()) {
647
689
  ...claudeAiSkills(enabled),
648
690
  ...pluginSkills(enabled),
649
691
  ...cursorBuiltInSkills(enabled),
692
+ ...grokBuiltInSkills(enabled),
650
693
  ...systemSkills(enabled)
651
694
  ].sort(byKindAndName);
652
695
  }
@@ -670,7 +713,7 @@ function recordOrigin(name, origin) {
670
713
  function originFor(dir) {
671
714
  const match = machineSkills().find((s) => resolve5(s.path) === resolve5(dir));
672
715
  if (match) return match.kind === "global" ? "global folder" : `${match.kind}: ${match.origin}`;
673
- const project = dir.split(/[\\/]\.(?:claude|agents)[\\/]skills[\\/]/)[0];
716
+ const project = dir.split(/[\\/]\.(?:claude|agents|grok|cursor|codex)[\\/]skills[\\/]/)[0];
674
717
  if (!project || project === dir) return dir;
675
718
  const locked = projectSkillsLock(project)[basename2(dir)]?.source;
676
719
  return locked ? `skills.sh: ${locked}` : `project: ${basename2(project)}`;
@@ -716,7 +759,7 @@ function visibilityOf(root, name, enabled = enabledHarnesses()) {
716
759
  }
717
760
  function dependencyDirs(root, name, dep) {
718
761
  const wanted = installDirs(enabledHarnesses());
719
- const untracked = ALL_PROJECT_DIRS.find((d) => existsSync7(join8(root, d, name, "SKILL.md")) && !isLink(join8(root, d, name)));
762
+ const untracked = ALL_PROJECT_DIRS.find((d) => d !== GROK_SKILLS_DIR && existsSync7(join8(root, d, name, "SKILL.md")) && !isLink(join8(root, d, name)));
720
763
  const primary = dep ? dep.dir ?? PROJECT_SKILLS_DIR : untracked ?? wanted[0];
721
764
  return [primary, .../* @__PURE__ */ new Set([...dep?.links ?? [], ...wanted.filter((d) => d !== primary)])];
722
765
  }
@@ -768,7 +811,7 @@ function projectStatus(root) {
768
811
  unmanaged.push({
769
812
  name,
770
813
  managed: false,
771
- state: unmanagedState(name, real, location === AGENTS_SKILLS_DIR, source),
814
+ state: unmanagedState(name, real, isRepoSkillLocation(location), source),
772
815
  version: null,
773
816
  latest: latestVersion(name)?.version ?? null,
774
817
  location,
@@ -788,10 +831,12 @@ function unmanagedState(name, real, committed, source) {
788
831
  return latest === null ? "local only" : sameAsLibrary ? "untracked copy of library skill" : "untracked, differs from library";
789
832
  }
790
833
  var NESTED_READERS = [
791
- [PROJECT_SKILLS_DIR, ["claude-code", "cursor"]],
792
- [AGENTS_SKILLS_DIR, ["codex", "cursor"]],
793
- [".cursor/skills", ["cursor"]],
794
- [".codex/skills", ["codex", "cursor"]]
834
+ [PROJECT_SKILLS_DIR, ["claude-code", "cursor", "grok"]],
835
+ [AGENTS_SKILLS_DIR, ["codex", "cursor", "grok"]],
836
+ [".cursor/skills", ["cursor", "grok"]],
837
+ [".codex/skills", ["codex", "cursor"]],
838
+ // Cursor reads nested .grok/skills too, but skilllib doesn't count it: that folder stays Grok's.
839
+ [".grok/skills", ["grok"]]
795
840
  ];
796
841
  var NESTED_SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "vendor", "target", "coverage"]);
797
842
  function nestedSkills(root, { depth = 3, enabled = enabledHarnesses() } = {}) {
@@ -813,7 +858,7 @@ function nestedSkills(root, { depth = 3, enabled = enabledHarnesses() } = {}) {
813
858
  found.push({
814
859
  name,
815
860
  managed: false,
816
- state: unmanagedState(name, path, skillsDir === AGENTS_SKILLS_DIR, void 0),
861
+ state: unmanagedState(name, path, isRepoSkillLocation(skillsDir), void 0),
817
862
  version: null,
818
863
  latest: latestVersion(name)?.version ?? null,
819
864
  location,
@@ -866,10 +911,15 @@ function linkEverywhere(root, name, location, { allowTracked = false, dryRun = f
866
911
  }
867
912
  return result;
868
913
  }
869
- function unlinkEverywhere(root, name) {
914
+ function unlinkEverywhere(root, name, removing) {
915
+ const gone = removing ? realpathOrNull2(removing) : null;
870
916
  return ALL_PROJECT_DIRS.filter((dir) => {
871
917
  const link2 = join8(root, dir, name);
872
918
  if (!isLink(link2)) return false;
919
+ if (dir === GROK_SKILLS_DIR) {
920
+ const target = realpathOrNull2(link2);
921
+ if (target !== null && target !== gone) return false;
922
+ }
873
923
  unlinkSync(link2);
874
924
  return true;
875
925
  });
@@ -1052,7 +1102,7 @@ function stash(dir, bucket) {
1052
1102
  return target;
1053
1103
  }
1054
1104
  function removeUntracked(root, name, path) {
1055
- unlinkEverywhere(root, name);
1105
+ unlinkEverywhere(root, name, path);
1056
1106
  return stash(path, "trash");
1057
1107
  }
1058
1108
  function projectsUsing(name) {
@@ -1215,7 +1265,7 @@ function refreshAgentSkill() {
1215
1265
 
1216
1266
  // src/commands.ts
1217
1267
  import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
1218
- import { basename as basename8, join as join14, resolve as resolve7, sep as sep5 } from "path";
1268
+ import { basename as basename9, join as join14, resolve as resolve7, sep as sep5 } from "path";
1219
1269
 
1220
1270
  // src/git.ts
1221
1271
  import { execFileSync as execFileSync2 } from "child_process";
@@ -1310,21 +1360,109 @@ function claudePlugins(machine, root) {
1310
1360
  };
1311
1361
  });
1312
1362
  }
1363
+ function newer(a, b) {
1364
+ const parts = (v) => v.replace(/^v/i, "").split(/[.+-]/).map((x) => parseInt(x, 10) || 0);
1365
+ const pa = parts(a);
1366
+ const pb = parts(b);
1367
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
1368
+ return false;
1369
+ }
1370
+ function updateOf(name, marketplace, entry, catalogs) {
1371
+ const dir = join11(claudeDir(), "plugins", "marketplaces", marketplace);
1372
+ if (!catalogs.has(marketplace)) catalogs.set(marketplace, readJson2(join11(dir, ".claude-plugin", "marketplace.json"))?.plugins ?? []);
1373
+ const listed = catalogs.get(marketplace).find((p) => p.name === name);
1374
+ if (!listed) return void 0;
1375
+ const version = listed.version ?? (typeof listed.source === "string" ? readJson2(join11(dir, listed.source, ".claude-plugin", "plugin.json"))?.version : void 0);
1376
+ if (version && entry.version) return newer(version, entry.version) ? version : void 0;
1377
+ const sha = typeof listed.source === "object" ? listed.source?.sha : void 0;
1378
+ return sha && entry.gitCommitSha && sha !== entry.gitCommitSha ? "newer" : void 0;
1379
+ }
1380
+ function pluginRoot(id, entry) {
1381
+ const candidates = pluginDirCandidates(id, entry.installPath ? [entry.installPath] : []);
1382
+ return candidates.find((c) => existsSync10(join11(c, "skills"))) ?? candidates.find((c) => existsSync10(c)) ?? candidates[0];
1383
+ }
1384
+ function describe(root) {
1385
+ const manifest = readJson2(join11(root, ".claude-plugin", "plugin.json")) ?? {};
1386
+ return { description: manifest.description ?? "", version: manifest.version, skills: skillDirsIn(join11(root, "skills")), extras: extrasOf(root) };
1387
+ }
1388
+ function installedPlugins() {
1389
+ const plugins = join11(claudeDir(), "plugins");
1390
+ const installed = readJson2(join11(plugins, "installed_plugins.json"))?.plugins ?? {};
1391
+ const catalogs = /* @__PURE__ */ new Map();
1392
+ const fromMarketplaces = Object.entries(installed).flatMap(
1393
+ ([id, entries]) => entries.map((entry) => {
1394
+ const [name = id, marketplace = ""] = id.split("@");
1395
+ const scope = entry.scope ?? "user";
1396
+ const projectPath = scope === "user" ? void 0 : entry.projectPath;
1397
+ const root = pluginRoot(id, entry);
1398
+ const about = describe(root);
1399
+ const update2 = updateOf(name, marketplace, entry, catalogs);
1400
+ return {
1401
+ id,
1402
+ scope,
1403
+ ...projectPath ? { projectPath } : {},
1404
+ synced: false,
1405
+ agent: "claude-code",
1406
+ on: enabledPlugins(projectPath)[id] === true,
1407
+ root,
1408
+ ...about,
1409
+ version: entry.version ?? about.version,
1410
+ ...update2 ? { update: update2 } : {}
1411
+ };
1412
+ })
1413
+ );
1414
+ const user = enabledPlugins();
1415
+ const fromSettings = Object.keys(user).filter((id) => user[id] === true && id.includes("@") && !id.endsWith("@synced") && !installed[id]).flatMap((id) => {
1416
+ const root = pluginDirCandidates(id, []).find((c) => existsSync10(join11(c, "skills")));
1417
+ return root ? [{ id, scope: "user", synced: false, agent: "claude-code", on: true, settingsOnly: true, root, ...describe(root) }] : [];
1418
+ });
1419
+ const syncedDir = join11(plugins, "synced");
1420
+ const dirs = (p) => existsSync10(p) ? readdirSync5(p, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => join11(p, d.name)) : [];
1421
+ const fromSynced = dirs(syncedDir).flatMap(dirs).map((root) => {
1422
+ const id = `${readJson2(join11(root, ".claude-plugin", "plugin.json"))?.name ?? basename4(root)}@synced`;
1423
+ return { id, scope: null, synced: true, agent: "claude-code", on: user[id] !== false, root, ...describe(root) };
1424
+ });
1425
+ return [...fromMarketplaces, ...fromSettings, ...fromSynced];
1426
+ }
1427
+ function cursorPlugins() {
1428
+ const cache = join11(userHome(), ".cursor", "plugins", "cache");
1429
+ const dirs = (p) => existsSync10(p) ? readdirSync5(p).filter((d) => !d.startsWith(".") && statSync3(join11(p, d)).isDirectory()) : [];
1430
+ return dirs(cache).flatMap(
1431
+ (marketplace) => dirs(join11(cache, marketplace)).flatMap((plugin) => {
1432
+ const versions = dirs(join11(cache, marketplace, plugin)).map((v) => join11(cache, marketplace, plugin, v));
1433
+ const root = versions.sort((a, b) => statSync3(b).mtimeMs - statSync3(a).mtimeMs)[0];
1434
+ if (!root) return [];
1435
+ const about = describe(root);
1436
+ return [{ id: `${plugin}@${marketplace}`, scope: null, synced: false, agent: "cursor", on: null, root, ...about, version: about.version ?? basename4(root) }];
1437
+ })
1438
+ );
1439
+ }
1440
+ var binaries = /* @__PURE__ */ new Map();
1441
+ function forgetClaudeBinary() {
1442
+ binaries.clear();
1443
+ }
1313
1444
  function claudeBinary() {
1314
- const candidates = ["claude", join11(userHome(), ".local", "bin", "claude"), join11(userHome(), ".claude", "local", "claude")];
1315
- return candidates.find((bin) => {
1316
- try {
1317
- return execFileSync3(bin, ["--version"], { stdio: ["ignore", "pipe", "ignore"], timeout: 3e4, encoding: "utf-8" }).includes("Claude Code");
1318
- } catch {
1319
- return false;
1320
- }
1321
- }) ?? null;
1445
+ const key = `${process.env.PATH}|${userHome()}`;
1446
+ if (!binaries.has(key)) {
1447
+ const candidates = ["claude", join11(userHome(), ".local", "bin", "claude"), join11(userHome(), ".claude", "local", "claude")];
1448
+ const found = candidates.find((bin) => {
1449
+ try {
1450
+ return execFileSync3(bin, ["--version"], { stdio: ["ignore", "pipe", "ignore"], timeout: 1e4, encoding: "utf-8" }).includes("Claude Code");
1451
+ } catch {
1452
+ return false;
1453
+ }
1454
+ });
1455
+ binaries.set(key, found ?? null);
1456
+ }
1457
+ return binaries.get(key);
1322
1458
  }
1323
1459
  function claude(args, cwd, bin = claudeBinary()) {
1460
+ if (cwd && !existsSync10(cwd)) return { ok: false, reason: `${cwd} no longer exists; run \`claude ${args.join(" ")}\` in the project's new folder, or remove the install with /plugin` };
1324
1461
  if (!bin) return { ok: false, reason: `Claude Code's CLI didn't run (tried \`claude\` on your PATH and ~/.local/bin/claude); run \`claude ${args.join(" ")}\` yourself` };
1462
+ const slow = args[1] === "install" || args[1] === "update";
1325
1463
  try {
1326
- execFileSync3(bin, args, { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 12e4, encoding: "utf-8" });
1327
- return { ok: true };
1464
+ const out2 = execFileSync3(bin, args, { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: slow ? 12e4 : 2e4, encoding: "utf-8" });
1465
+ return { ok: true, out: out2 };
1328
1466
  } catch (err) {
1329
1467
  const e = err;
1330
1468
  return { ok: false, reason: `${(e.stderr || e.stdout || e.message || "failed").trim().split("\n").slice(-1)[0]} (ran ${bin})` };
@@ -1338,9 +1476,6 @@ function setPluginIn(file, id, on) {
1338
1476
  writeFileSync7(file, JSON.stringify({ ...settings, enabledPlugins: { ...enabled, [id]: on } }, null, 2) + "\n");
1339
1477
  return { ok: true };
1340
1478
  }
1341
- function turnOffSynced(id) {
1342
- return setPluginIn(join11(claudeDir(), "settings.json"), id, false);
1343
- }
1344
1479
  function ignoreLocalSettings(root) {
1345
1480
  const file = ".claude/settings.local.json";
1346
1481
  const git2 = (...args) => execFileSync3("git", args, { cwd: root, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
@@ -1380,6 +1515,50 @@ function turnOffIn(plugin, root) {
1380
1515
  const r = claude(["plugin", "disable", plugin.id, "--scope", "local"], root, bin);
1381
1516
  return r.ok ? { ok: true, message: done } : { ok: false, message: `${plugin.id}: ${r.reason}` };
1382
1517
  }
1518
+ function settingsFileOf(p) {
1519
+ if (p.synced || !p.scope || p.scope === "user") return join11(claudeDir(), "settings.json");
1520
+ if (!p.projectPath) return null;
1521
+ return join11(p.projectPath, ".claude", p.scope === "local" ? "settings.local.json" : "settings.json");
1522
+ }
1523
+ function whereOf(p) {
1524
+ return p.projectPath ? ` in ${basename4(p.projectPath)} (${p.scope})` : "";
1525
+ }
1526
+ function setPluginOn(p, on) {
1527
+ const done = { ok: true, message: `${p.id} is ${on ? "on" : "off"}${whereOf(p)}${p.synced ? "" : "; Claude Code picks it up in its next session"}` };
1528
+ if (p.scope && p.scope !== "user" && !p.projectPath) return { ok: false, message: `${p.id}: skilllib doesn't know which project it's installed in; run \`claude plugin ${on ? "enable" : "disable"} ${p.id} --scope ${p.scope}\` there` };
1529
+ const bin = p.synced || p.settingsOnly ? null : claudeBinary();
1530
+ if (bin) {
1531
+ const r2 = claude(["plugin", on ? "enable" : "disable", p.id, ...p.scope ? ["--scope", p.scope] : []], p.projectPath, bin);
1532
+ return r2.ok ? done : { ok: false, message: `${p.id}: ${r2.reason}` };
1533
+ }
1534
+ const r = setPluginIn(settingsFileOf(p), p.id, on);
1535
+ if (!r.ok) return { ok: false, message: `${p.id}: ${r.reason}` };
1536
+ return p.scope === "local" && p.projectPath ? { ...done, message: done.message + ignoreLocalSettings(p.projectPath) } : done;
1537
+ }
1538
+ function uninstallPlugin(p) {
1539
+ if (p.synced) return { ok: false, message: `${p.id} is synced from your claude.ai account: remove it there, or turn it off here` };
1540
+ if (p.settingsOnly) return { ok: false, message: `${p.id} isn't installed, only turned on in your settings: turn it off instead` };
1541
+ if (p.scope && p.scope !== "user" && !p.projectPath) return { ok: false, message: `${p.id}: skilllib doesn't know which project it's installed in; run \`claude plugin uninstall ${p.id} --scope ${p.scope}\` there` };
1542
+ const wasOn = enabledPlugins(p.projectPath)[p.id] === true;
1543
+ const r = claude(["plugin", "uninstall", p.id, "--keep-data", "--scope", p.scope ?? "user"], p.projectPath);
1544
+ if (!r.ok) return { ok: false, message: `${p.id}: ${r.reason}` };
1545
+ recordRemovedPlugin(p.id, p.scope ?? "user", p.projectPath, wasOn);
1546
+ return { ok: true, message: `${p.id} is uninstalled${whereOf(p)} (Settings \u203A Backups reinstalls it)` };
1547
+ }
1548
+ function updatePlugin(p) {
1549
+ if (p.synced) return { ok: false, message: `${p.id} is synced from your claude.ai account: it updates from there` };
1550
+ if (p.settingsOnly) return { ok: false, message: `${p.id} isn't installed, only turned on in your settings: install it with /plugin to update it` };
1551
+ const r = claude(["plugin", "update", p.id, ...p.scope ? ["--scope", p.scope] : [], "--json"], p.projectPath);
1552
+ if (!r.ok) return { ok: false, message: `${p.id}: ${r.reason}; to update it yourself, run \`claude plugin update ${p.id}\`${p.projectPath ? ` in ${p.projectPath}` : ""}` };
1553
+ let result = {};
1554
+ try {
1555
+ result = JSON.parse(r.out.trim().split("\n").pop() ?? "{}");
1556
+ } catch {
1557
+ }
1558
+ if (result.updateOutcome === "up_to_date") return { ok: true, message: `${p.id} is already the newest version${result.newVersion ? ` (${result.newVersion})` : ""}${whereOf(p)}` };
1559
+ const versions = result.oldVersion && result.newVersion ? ` from ${result.oldVersion} to ${result.newVersion}` : "";
1560
+ return { ok: true, message: `${p.id} is updated${versions}${whereOf(p)}; Claude Code uses the new version after a restart` };
1561
+ }
1383
1562
  function removedFile() {
1384
1563
  return join11(skilllibHome(), "plugin-backup.json");
1385
1564
  }
@@ -1392,42 +1571,45 @@ function removePlugin(plugin, how) {
1392
1571
  }
1393
1572
  const imported = plugin.skills.filter((dir) => latestVersion(basename4(dir)) === null).map((dir) => importSkill(dir).name);
1394
1573
  const scope = plugin.scope ? ["--scope", plugin.scope] : [];
1395
- const r = plugin.synced ? turnOffSynced(plugin.id) : claude(["plugin", how === "delete" ? "uninstall" : "disable", plugin.id, ...scope], plugin.projectPath);
1574
+ const r = plugin.synced ? (
1575
+ // Synced plugins aren't installed, so `claude plugin` can't touch them: their switch is enabledPlugins.
1576
+ setPluginIn(join11(claudeDir(), "settings.json"), plugin.id, false)
1577
+ ) : claude(["plugin", how === "delete" ? "uninstall" : "disable", plugin.id, ...scope], plugin.projectPath);
1396
1578
  const saved = imported.length ? `; ${imported.join(", ")} copied into Your skills` : "";
1397
1579
  if (!r.ok) return { ok: false, message: `${plugin.id}: ${r.reason}${saved}` };
1398
- if (how === "delete" && !plugin.synced) recordRemovedPlugin(plugin.id, plugin.scope ?? "user");
1580
+ if (how === "delete" && !plugin.synced) recordRemovedPlugin(plugin.id, plugin.scope ?? "user", plugin.projectPath, true);
1399
1581
  return { ok: true, message: `${plugin.id} ${how === "delete" && !plugin.synced ? "removed" : "turned off"}${saved}` };
1400
1582
  }
1401
- function recordRemovedPlugin(id, scope) {
1583
+ function recordRemovedPlugin(id, scope, projectPath, on = true) {
1402
1584
  const list2 = readJson2(removedFile()) ?? [];
1403
1585
  mkdirSync7(skilllibHome(), { recursive: true });
1404
- writeFileSync7(removedFile(), JSON.stringify([...list2, { id, scope, at: (/* @__PURE__ */ new Date()).toISOString() }], null, 2) + "\n");
1586
+ writeFileSync7(removedFile(), JSON.stringify([...list2, { id, scope, ...projectPath ? { projectPath } : {}, on, at: (/* @__PURE__ */ new Date()).toISOString() }], null, 2) + "\n");
1405
1587
  }
1406
1588
  function pluginBackups() {
1407
1589
  return (readJson2(removedFile()) ?? []).map((r) => ({
1408
1590
  name: r.id,
1409
1591
  kind: "plugin",
1410
- path: r.scope,
1592
+ // Project and local installs go back into their project.
1593
+ path: r.projectPath ? `${r.scope}:${r.projectPath}` : r.scope,
1411
1594
  movedAt: r.at.slice(0, 16).replace("T", " "),
1412
1595
  from: r.id
1413
1596
  }));
1414
1597
  }
1415
1598
  function restorePlugin(backup) {
1416
- const r = claude(["plugin", "install", backup.from, "--scope", backup.path]);
1599
+ const [scope = "user", ...rest] = backup.path.split(":");
1600
+ const projectPath = rest.join(":") || void 0;
1601
+ const r = claude(["plugin", "install", backup.from, "--scope", scope], projectPath);
1417
1602
  if (!r.ok) return r;
1418
- writeFileSync7(removedFile(), JSON.stringify((readJson2(removedFile()) ?? []).filter((x) => x.id !== backup.from), null, 2) + "\n");
1419
- return { ok: true, to: `Claude Code plugins (${backup.path})` };
1603
+ const records = readJson2(removedFile()) ?? [];
1604
+ const same = (x) => x.id === backup.from && (x.projectPath ?? void 0) === projectPath && x.scope === scope;
1605
+ const wasOff = records.filter(same).pop()?.on === false;
1606
+ const off = wasOff ? claude(["plugin", "disable", backup.from, "--scope", scope], projectPath) : null;
1607
+ writeFileSync7(removedFile(), JSON.stringify(records.filter((x) => !same(x)), null, 2) + "\n");
1608
+ const where = `Claude Code plugins (${scope}${projectPath ? ` in ${basename4(projectPath)}` : ""}${wasOff ? off?.ok ? ", off as it was" : ", but it's on: turn it off from Plugins" : ""})`;
1609
+ return { ok: true, to: where };
1420
1610
  }
1421
- function cursorPluginSkills() {
1422
- const cache = join11(userHome(), ".cursor", "plugins", "cache");
1423
- const dirs = (p) => existsSync10(p) ? readdirSync5(p).filter((d) => !d.startsWith(".") && statSync3(join11(p, d)).isDirectory()) : [];
1424
- return dirs(cache).flatMap(
1425
- (marketplace) => dirs(join11(cache, marketplace)).flatMap((plugin) => {
1426
- const versions = dirs(join11(cache, marketplace, plugin)).map((v) => join11(cache, marketplace, plugin, v));
1427
- const newest = versions.sort((a, b) => statSync3(b).mtimeMs - statSync3(a).mtimeMs)[0];
1428
- return newest ? skillDirsIn(join11(newest, "skills")).map((path) => ({ name: basename4(path), plugin, path })) : [];
1429
- })
1430
- );
1611
+ function cursorPluginSkills(plugins = cursorPlugins()) {
1612
+ return plugins.flatMap((p) => p.skills.map((path) => ({ name: basename4(path), plugin: p.id.split("@")[0], path })));
1431
1613
  }
1432
1614
 
1433
1615
  // src/tidy.ts
@@ -1455,6 +1637,11 @@ function namesIn(dirs) {
1455
1637
  }
1456
1638
  return [...names].sort();
1457
1639
  }
1640
+ var PROJECT_TIDY_DIRS = ALL_PROJECT_DIRS.filter((d) => d !== GROK_SKILLS_DIR);
1641
+ function globalTidyDirs() {
1642
+ const own = realPath(join12(grokDir(), "skills"));
1643
+ return globalSkillDirs().filter((d) => realPath(d) !== own);
1644
+ }
1458
1645
  function runsFrom(id, dirsWithCopy, readsOf) {
1459
1646
  const reads = readsOf(id).filter((d) => dirsWithCopy.includes(d));
1460
1647
  if (id === "codex") return reads;
@@ -1477,8 +1664,8 @@ function planProjectTidy(root, { keep = {}, enabled = enabledHarnesses() } = {})
1477
1664
  const readsOf = (id) => HARNESSES.find((h) => h.id === id).projectDirs.map(abs);
1478
1665
  let git2;
1479
1666
  const skillsLock = projectSkillsLock(root);
1480
- for (const name of namesIn(ALL_PROJECT_DIRS.map(abs))) {
1481
- const entries = entriesOf(ALL_PROJECT_DIRS.map(abs), name);
1667
+ for (const name of namesIn(PROJECT_TIDY_DIRS.map(abs))) {
1668
+ const entries = entriesOf(PROJECT_TIDY_DIRS.map(abs), name);
1482
1669
  const external = entries.find((e) => e.link && e.real && !(e.real + sep4).startsWith(realpathOrNull2(root) + sep4));
1483
1670
  if (external) {
1484
1671
  report.skipped.push({ name, reason: `${rel(external.path)} links outside the repo` });
@@ -1533,7 +1720,7 @@ function isSkillsDirPlugin(path) {
1533
1720
  }
1534
1721
  function planGlobalTidy({ keep = {}, enabled = enabledHarnesses() } = {}) {
1535
1722
  const report = { plans: [], conflicts: [], skipped: [] };
1536
- const dirs = globalSkillDirs();
1723
+ const dirs = globalTidyDirs();
1537
1724
  const agents = join12(userHome(), ".agents", "skills");
1538
1725
  for (const name of namesIn(dirs)) {
1539
1726
  const entries = entriesOf(dirs, name);
@@ -1607,7 +1794,7 @@ function folderLabel(root, dir) {
1607
1794
  return root ? relativeTo(root, dir) : tildify(dir);
1608
1795
  }
1609
1796
  function foldersOf(root, name) {
1610
- const entries = entriesOf(root ? ALL_PROJECT_DIRS.map((d) => join12(root, d)) : globalSkillDirs(), name);
1797
+ const entries = entriesOf(root ? PROJECT_TIDY_DIRS.map((d) => join12(root, d)) : globalTidyDirs(), name);
1611
1798
  return { all: entries.map((e) => e.dir), real: entries.filter((e) => !e.link).map((e) => e.dir) };
1612
1799
  }
1613
1800
 
@@ -1805,7 +1992,7 @@ function findIssues(projects2, machine, libraryNames, statusOf = projectStatus,
1805
1992
  id: "review:global",
1806
1993
  severity: "suggestion",
1807
1994
  title: `${plural(unreviewed.length, "global skill")} of yours ${unreviewed.length === 1 ? "loads" : "load"} in every repo`,
1808
- detail: `${unreviewed.join(", ")}. Move each to the repos that need it (skilllib \u2192 Global \u2192 Move it to the repos that need it\u2026), or mark the ones you want everywhere as global on purpose (skilllib global keep <name>).`
1995
+ detail: `${unreviewed.join(", ")}. Move each to the repos that need it (skilllib global move <name> --to <repo>...), or mark the ones you want everywhere as global on purpose (skilllib global keep <name>).`
1809
1996
  });
1810
1997
  }
1811
1998
  const enabled = enabledHarnesses();
@@ -1831,7 +2018,7 @@ function findIssues(projects2, machine, libraryNames, statusOf = projectStatus,
1831
2018
  id: `usable:global${onPurpose ? ":kept" : ""}`,
1832
2019
  severity: "suggestion",
1833
2020
  title: `${plural(skills.length, "global skill")}${onPurpose ? " you keep global" : ""} not usable by ${agentNames(missing)}`,
1834
- detail: `${skills.map((m) => `${m.name} (only ${agentNames(m.harnesses)})`).join(", ")}. ${onPurpose ? "A link in each agent's global folder makes the one real copy reach every agent you use." : `Link ${skills.length === 1 ? "it" : "them"} for every agent, or move ${skills.length === 1 ? "it" : "them"} to the repos that need ${skills.length === 1 ? "it" : "them"} (skilllib \u2192 Global), which works for every agent too.`}`,
2021
+ detail: `${skills.map((m) => `${m.name} (only ${agentNames(m.harnesses)})`).join(", ")}. ${onPurpose ? "A link in each agent's global folder makes the one real copy reach every agent you use." : `Link ${skills.length === 1 ? "it" : "them"} for every agent, or move ${skills.length === 1 ? "it" : "them"} to the repos that need ${skills.length === 1 ? "it" : "them"} (skilllib global move), which works for every agent too.`}`,
1835
2022
  ...onPurpose ? { fix: link2 } : { choices: [link2] }
1836
2023
  });
1837
2024
  }
@@ -2003,17 +2190,17 @@ function findIssues(projects2, machine, libraryNames, statusOf = projectStatus,
2003
2190
  });
2004
2191
  }
2005
2192
  if (s.state === "edited locally" || s.state === "edited locally, update available") {
2006
- const newer = s.state !== "edited locally";
2193
+ const newer2 = s.state !== "edited locally";
2007
2194
  const pinned = s.version ? `v${s.version}` : "the library version";
2008
2195
  issues.push({
2009
2196
  id: `edited:${root}:${s.name}`,
2010
2197
  severity: "suggestion",
2011
2198
  title: `${s.name} in ${where} has local edits`,
2012
- detail: `It no longer matches ${pinned} from Your skills${newer ? `, and v${s.latest} is out` : ""}. Save the edits as a new version so your other repos can get them, or discard them. Keeping them is fine too: skilllib never overwrites edits.`,
2199
+ detail: `It no longer matches ${pinned} from Your skills${newer2 ? `, and v${s.latest} is out` : ""}. Save the edits as a new version so your other repos can get them, or discard them. Keeping them is fine too: skilllib never overwrites edits.`,
2013
2200
  choices: [
2014
2201
  {
2015
2202
  label: "Save as a new version in Your skills",
2016
- hint: newer ? `becomes v${s.latest + 1}; v${s.latest}'s changes aren't merged in` : `becomes v${(s.latest ?? 0) + 1}; ${where} uses it`,
2203
+ hint: newer2 ? `becomes v${s.latest + 1}; v${s.latest}'s changes aren't merged in` : `becomes v${(s.latest ?? 0) + 1}; ${where} uses it`,
2017
2204
  run: () => {
2018
2205
  importSkill(s.path, { force: true });
2019
2206
  const c = addSkill(root, s.name);
@@ -2193,6 +2380,9 @@ function topLevel(root) {
2193
2380
  return [];
2194
2381
  }
2195
2382
  }
2383
+ function statusIssues(root, machine = machineSkills(enabledHarnesses())) {
2384
+ return findIssues(projectHere(root) === root ? [root] : [], machine, new Set(librarySkills().map((l) => l.name)));
2385
+ }
2196
2386
  function usableHere(root, uses = /* @__PURE__ */ new Map()) {
2197
2387
  const agents = enabledHarnesses();
2198
2388
  const inRepo = projectHere(root) === root;
@@ -2207,12 +2397,12 @@ function usableHere(root, uses = /* @__PURE__ */ new Map()) {
2207
2397
  const usual = s.state === "ok" || s.state === "local only" || repo && s.state !== "from npx skills";
2208
2398
  const attrs = {
2209
2399
  source: s.managed ? "library" : repo ? "repo" : "local",
2210
- ...s.location !== ".claude/skills" && { dir: s.location },
2211
- ...s.version !== null && { version: s.version },
2212
- ...!usual && { state: s.state },
2213
- ...behind && { latest: s.latest },
2214
- ...gitState && { git: gitState },
2215
- ...!sameSet(loadedBy, agents) && { agents: loadedBy }
2400
+ dir: s.location !== ".claude/skills" ? s.location : void 0,
2401
+ version: s.version ?? void 0,
2402
+ state: usual ? void 0 : s.state,
2403
+ latest: behind ? s.latest : void 0,
2404
+ git: gitState,
2405
+ agents: sameSet(loadedBy, agents) ? void 0 : loadedBy
2216
2406
  };
2217
2407
  return [JSON.stringify(attrs), s.name, () => ({ ...attrs, skills: [] })];
2218
2408
  })
@@ -2228,11 +2418,12 @@ function usableHere(root, uses = /* @__PURE__ */ new Map()) {
2228
2418
  })
2229
2419
  );
2230
2420
  const issues = /* @__PURE__ */ new Map();
2231
- for (const i of findIssues(inRepo ? [root] : [], machine, new Set(librarySkills().map((l) => l.name)))) {
2421
+ for (const i of statusIssues(root, machine)) {
2232
2422
  const choices = i.choices?.map((c) => c.label);
2233
2423
  const key = `${i.detail}\0${i.fix?.label ?? ""}\0${choices?.join() ?? ""}`;
2234
- const issue = issues.get(key) ?? { problems: [], detail: i.detail, ...i.fix && { fix: i.fix.label }, ...choices && { choices } };
2424
+ const issue = issues.get(key) ?? { problems: [], ids: [], detail: i.detail, fix: i.fix?.label, choices };
2235
2425
  issue.problems.push(i.title);
2426
+ issue.ids.push(i.id);
2236
2427
  issues.set(key, issue);
2237
2428
  }
2238
2429
  const usesHere = Object.fromEntries([...uses].filter(([, n]) => n > 0));
@@ -2241,10 +2432,10 @@ function usableHere(root, uses = /* @__PURE__ */ new Map()) {
2241
2432
  files: topLevel(root),
2242
2433
  agents,
2243
2434
  skills,
2244
- ...nested.length > 0 && { nested },
2435
+ nested: nested.length > 0 ? nested : void 0,
2245
2436
  global,
2246
2437
  issues: [...issues.values()],
2247
- ...Object.keys(usesHere).length > 0 && { usesHere }
2438
+ usesHere: Object.keys(usesHere).length > 0 ? usesHere : void 0
2248
2439
  };
2249
2440
  }
2250
2441
  function firstSentence(description) {
@@ -2256,12 +2447,12 @@ function libraryFor(currentProject) {
2256
2447
  const manifests = visibleProjects().map((p) => ({ name: basename7(p), skills: readManifest(p).skills }));
2257
2448
  const here = currentProject ? new Set(projectStatus(currentProject).map((s) => s.name)) : null;
2258
2449
  const all = librarySkills();
2259
- const describe = (s) => {
2450
+ const describe2 = (s) => {
2260
2451
  const projects2 = manifests.filter((m) => s.name in m.skills).map((m) => m.name);
2261
- return { name: s.name, description: firstSentence(s.description), ...projects2.length > 0 && { projects: projects2 } };
2452
+ return { name: s.name, description: firstSentence(s.description), projects: projects2.length > 0 ? projects2 : void 0 };
2262
2453
  };
2263
- if (!here) return { skills: all.map(describe) };
2264
- return { inThisProject: all.filter((s) => here.has(s.name)).map((s) => s.name), skills: all.filter((s) => !here.has(s.name)).map(describe) };
2454
+ if (!here) return { skills: all.map(describe2) };
2455
+ return { inThisProject: all.filter((s) => here.has(s.name)).map((s) => s.name), skills: all.filter((s) => !here.has(s.name)).map(describe2) };
2265
2456
  }
2266
2457
 
2267
2458
  // src/usage.ts
@@ -2392,6 +2583,25 @@ function usesByProject(uses, projectOf) {
2392
2583
  return byProject;
2393
2584
  }
2394
2585
 
2586
+ // src/move.ts
2587
+ import { basename as basename8 } from "path";
2588
+ function moveGlobalSkills(skills, roots, { group, allowTracked = false, label = basename8 } = {}) {
2589
+ const byName = /* @__PURE__ */ new Map();
2590
+ for (const m of skills) byName.set(m.name, [...byName.get(m.name) ?? [], m]);
2591
+ return [...byName].map(([name, copies]) => {
2592
+ if (new Set(copies.map((m) => treeHash(m.path))).size > 1)
2593
+ return { name, moved: false, reason: `your global copies of ${name} differ; nothing moved (keep one first: skilllib tidy --global)`, changes: [] };
2594
+ if (importSkill(copies[0].path).status === "exists")
2595
+ return { name, moved: false, reason: `your library has a different ${name}; nothing moved (update your library from this copy first)`, changes: [] };
2596
+ if (group) recordOrigin(name, `group: ${group}`);
2597
+ const changes = roots.map((root) => [root, addSkill(root, name, { allowTracked })]);
2598
+ const skipped = changes.filter(([, c]) => c.action === "skipped");
2599
+ if (skipped.length) return { name, moved: false, reason: `not added to ${skipped.map(([r, c]) => `${label(r)} (${c.reason})`).join(", ")}, so it still loads globally`, changes };
2600
+ const failed = copies.map((m) => unloadGlobal(m.path, m.links)).find((r) => !r.ok);
2601
+ return failed && !failed.ok ? { name, moved: false, reason: failed.reason, changes } : { name, moved: true, changes };
2602
+ });
2603
+ }
2604
+
2395
2605
  // src/commands.ts
2396
2606
  var USAGE_DAYS = 30;
2397
2607
  function parseArgs(argv) {
@@ -2399,7 +2609,7 @@ function parseArgs(argv) {
2399
2609
  const days = daysIdx >= 0 ? Number(argv[daysIdx + 1]) : USAGE_DAYS;
2400
2610
  const keepIdx = argv.indexOf("--keep");
2401
2611
  return {
2402
- positional: argv.filter((a, i) => !a.startsWith("--") && argv[i - 1] !== "--days" && argv[i - 1] !== "--keep"),
2612
+ positional: argv.filter((a, i) => !a.startsWith("--") && !["--days", "--keep", "--to"].includes(argv[i - 1])),
2403
2613
  force: argv.includes("--force"),
2404
2614
  all: argv.includes("--all"),
2405
2615
  global: argv.includes("--global"),
@@ -2409,7 +2619,8 @@ function parseArgs(argv) {
2409
2619
  days: Number.isFinite(days) && days > 0 ? days : USAGE_DAYS,
2410
2620
  dryRun: argv.includes("--dry-run"),
2411
2621
  allowGit: argv.includes("--allow-git"),
2412
- ...keepIdx >= 0 && argv[keepIdx + 1] ? { keep: argv[keepIdx + 1] } : {}
2622
+ ...keepIdx >= 0 && argv[keepIdx + 1] ? { keep: argv[keepIdx + 1] } : {},
2623
+ to: argv.flatMap((a, i) => argv[i - 1] === "--to" && !a.startsWith("--") ? a.split(",").filter(Boolean) : [])
2413
2624
  };
2414
2625
  }
2415
2626
  function colorState(state) {
@@ -2427,7 +2638,7 @@ function withDirs(root, changes) {
2427
2638
  }
2428
2639
  function printChanges(root, changes, args) {
2429
2640
  if (changes.some((c) => c.action === "skipped")) process.exitCode = 1;
2430
- if (args.json) return json(withDirs(root, changes));
2641
+ if (args.json) return json("changes", withDirs(root, changes));
2431
2642
  for (const c of changes) {
2432
2643
  if (c.blocked?.length) {
2433
2644
  warn(`${c.name}: git tracks ${c.blocked.join(", ")} here, so no link was added there. Re-run with --allow-tracked to add it.`);
@@ -2482,14 +2693,14 @@ async function status(args) {
2482
2693
  if (args.json) {
2483
2694
  const root2 = findProjectRoot();
2484
2695
  const usage3 = await usageBySkill(args.days, root2);
2485
- json(usableHere(root2, new Map([...usage3].map(([name, u]) => [name, u.uses]))));
2696
+ json("status", usableHere(root2, new Map([...usage3].map(([name, u]) => [name, u.uses]))));
2486
2697
  if (projectHere()) rememberProjects([root2]);
2487
2698
  return;
2488
2699
  }
2489
2700
  const root = requireProject();
2490
2701
  const skills = projectStatus(root);
2491
2702
  const nested = nestedSkills(root);
2492
- info(`${basename8(root)} ${dim(tildify(root))}
2703
+ info(`${basename9(root)} ${dim(tildify(root))}
2493
2704
  `);
2494
2705
  if (skills.length === 0 && nested.length === 0) info("No skills in this project yet. Add one from your library with: skilllib add <name>");
2495
2706
  const usage2 = await usageBySkill(args.days, root);
@@ -2556,7 +2767,7 @@ async function overview(args) {
2556
2767
  }
2557
2768
  async function list(args) {
2558
2769
  if (args.json) {
2559
- return json(libraryFor(projectHere()));
2770
+ return json("list", libraryFor(projectHere()));
2560
2771
  }
2561
2772
  const skills = librarySkills();
2562
2773
  if (skills.length === 0) {
@@ -2566,7 +2777,7 @@ async function list(args) {
2566
2777
  const installs = /* @__PURE__ */ new Map();
2567
2778
  for (const project of visibleProjects()) {
2568
2779
  for (const name of Object.keys(readManifest(project).skills)) {
2569
- installs.set(name, [...installs.get(name) ?? [], basename8(project)]);
2780
+ installs.set(name, [...installs.get(name) ?? [], basename9(project)]);
2570
2781
  }
2571
2782
  }
2572
2783
  const usage2 = await usageBySkill(args.days);
@@ -2600,11 +2811,16 @@ function projects(args) {
2600
2811
  const roots = visibleProjects();
2601
2812
  if (args.json) {
2602
2813
  return json(
2603
- roots.map((root) => ({
2604
- name: basename8(root),
2605
- path: root,
2606
- skills: projectStatus(root).map((s) => ({ name: s.name, managed: s.managed, state: s.state, version: s.version, latest: s.latest }))
2607
- }))
2814
+ "projects",
2815
+ roots.map(
2816
+ (root) => ({
2817
+ name: basename9(root),
2818
+ path: root,
2819
+ skills: projectStatus(root).map(
2820
+ (s) => ({ name: s.name, managed: s.managed, state: s.state, version: s.version, latest: s.latest })
2821
+ )
2822
+ })
2823
+ )
2608
2824
  );
2609
2825
  }
2610
2826
  if (roots.length === 0) {
@@ -2617,7 +2833,7 @@ function projects(args) {
2617
2833
  const managed = skills.filter((s) => s.managed);
2618
2834
  const attention = skills.filter((s) => s.state !== "ok" && s.state !== "local only").length;
2619
2835
  return {
2620
- Project: basename8(root),
2836
+ Project: basename9(root),
2621
2837
  Path: dim(tildify(root)),
2622
2838
  Skills: managed.map((s) => s.name).join(", ") || dim("none"),
2623
2839
  "Local only": String(skills.filter((s) => s.state === "local only").length),
@@ -2651,15 +2867,15 @@ function sync(args) {
2651
2867
  const changes = syncProject(root, { force: args.force });
2652
2868
  if (changes.some((c) => c.action === "skipped")) process.exitCode = 1;
2653
2869
  if (!args.json) {
2654
- if (args.all) info(`${basename8(root)} ${dim(tildify(root))}`);
2870
+ if (args.all) info(`${basename9(root)} ${dim(tildify(root))}`);
2655
2871
  if (changes.length === 0) {
2656
2872
  if (existsSync13(join14(root, MANIFEST_FILE))) success("Installed versions match skilllib.json");
2657
2873
  else info(dim("No skilllib.json: nothing to sync"));
2658
2874
  } else printChanges(root, changes, args);
2659
2875
  }
2660
- return { project: basename8(root), path: root, changes: withDirs(root, changes) };
2876
+ return { project: basename9(root), path: root, changes: withDirs(root, changes) };
2661
2877
  });
2662
- if (args.json) json(args.all ? results : results[0].changes);
2878
+ if (args.json) args.all ? json("changes --all", results) : json("changes", results[0].changes);
2663
2879
  if (!args.all) rememberProjects(roots);
2664
2880
  }
2665
2881
  function update(args) {
@@ -2674,20 +2890,20 @@ function update(args) {
2674
2890
  }
2675
2891
  if (changes.some((c) => c.action === "skipped")) process.exitCode = 1;
2676
2892
  if (!args.json) {
2677
- if (args.all) info(`${basename8(root)} ${dim(tildify(root))}`);
2893
+ if (args.all) info(`${basename9(root)} ${dim(tildify(root))}`);
2678
2894
  if (changes.length === 0) success("Everything is on the latest version");
2679
2895
  else printChanges(root, changes, args);
2680
2896
  }
2681
- return { project: basename8(root), path: root, changes: withDirs(root, changes) };
2897
+ return { project: basename9(root), path: root, changes: withDirs(root, changes) };
2682
2898
  });
2683
- if (args.json) json(args.all ? results : results[0].changes);
2899
+ if (args.json) args.all ? json("changes --all", results) : json("changes", results[0].changes);
2684
2900
  }
2685
2901
  function outdated(args) {
2686
2902
  const roots = args.all ? visibleProjects() : [requireProject()];
2687
2903
  const behind = roots.flatMap(
2688
- (root) => projectStatus(root).filter((s) => s.managed && s.latest !== null && s.version !== null && s.latest > s.version).map((s) => ({ project: basename8(root), path: root, skill: s.name, installed: s.version, latest: s.latest, state: s.state }))
2904
+ (root) => projectStatus(root).filter((s) => s.managed && s.latest !== null && s.version !== null && s.latest > s.version).map((s) => ({ project: basename9(root), path: root, skill: s.name, installed: s.version, latest: s.latest, state: s.state }))
2689
2905
  );
2690
- if (args.json) return json(behind);
2906
+ if (args.json) return json("outdated", behind);
2691
2907
  const rows = behind.map((b) => ({ Project: b.project, Skill: b.skill, Installed: `v${b.installed}`, Latest: `v${b.latest}`, Status: colorState(b.state) }));
2692
2908
  if (rows.length === 0) return success("Everything is on the latest version");
2693
2909
  table(rows);
@@ -2719,12 +2935,12 @@ function importSkills(args) {
2719
2935
  if (skillsDir === join14(root, ".claude", "skills") && isProjectCandidate(root)) {
2720
2936
  if (readManifest(root).skills[name]?.hash !== treeHash(dir)) {
2721
2937
  const change = addSkill(root, name);
2722
- if (change.action !== "skipped") info(dim(` ${basename8(root)} now depends on ${name} v${change.to}`));
2938
+ if (change.action !== "skipped") info(dim(` ${basename9(root)} now depends on ${name} v${change.to}`));
2723
2939
  }
2724
2940
  rememberProjects([root]);
2725
2941
  }
2726
2942
  }
2727
- if (args.json) json(results);
2943
+ if (args.json) json("import", results);
2728
2944
  }
2729
2945
  function scan(args) {
2730
2946
  for (const p of args.positional) addRoot(p);
@@ -2736,14 +2952,14 @@ function scan(args) {
2736
2952
  const found = discoverProjects();
2737
2953
  const library = new Set(librarySkills().map((s) => s.name));
2738
2954
  const skillNames = (root) => [
2739
- .../* @__PURE__ */ new Set([...ALL_PROJECT_DIRS.flatMap((d) => skillDirsIn(join14(root, d))).map((d) => basename8(d)), ...nestedSkills(root).map((s) => s.name)])
2955
+ .../* @__PURE__ */ new Set([...ALL_PROJECT_DIRS.flatMap((d) => skillDirsIn(join14(root, d))).map((d) => basename9(d)), ...nestedSkills(root).map((s) => s.name)])
2740
2956
  ];
2741
2957
  const withSkills = found.filter((root) => skillNames(root).length > 0);
2742
2958
  info(`Scanned ${roots.map((r) => tildify(r)).join(", ")}: ${found.length} project${found.length === 1 ? "" : "s"}, ${withSkills.length} with skills
2743
2959
  `);
2744
2960
  table(
2745
2961
  withSkills.map((root) => ({
2746
- Project: basename8(root),
2962
+ Project: basename9(root),
2747
2963
  Path: dim(tildify(root)),
2748
2964
  Skills: skillNames(root).map((n) => library.has(n) ? n : yellow(n)).join(", ")
2749
2965
  }))
@@ -2827,12 +3043,12 @@ function restore(args) {
2827
3043
  process.exitCode = 1;
2828
3044
  }
2829
3045
  }
2830
- async function doctor(args) {
3046
+ async function allIssues(args, withUsage = true) {
2831
3047
  const projects2 = visibleProjects();
2832
3048
  const machine = machineSkills();
2833
3049
  const libraryNames = new Set(librarySkills().map((s) => s.name));
2834
3050
  const issues = findIssues(projects2, machine, libraryNames);
2835
- if (enabledHarnesses().includes("claude-code")) {
3051
+ if (withUsage && enabledHarnesses().includes("claude-code")) {
2836
3052
  const projectOf = projectOfFactory(projects2);
2837
3053
  const uses = await scanUsage(args.days);
2838
3054
  const inProject = usesByProject(uses, projectOf);
@@ -2845,6 +3061,10 @@ async function doctor(args) {
2845
3061
  })
2846
3062
  );
2847
3063
  }
3064
+ return issues;
3065
+ }
3066
+ async function doctor(args) {
3067
+ const issues = await allIssues(args);
2848
3068
  const fixed = (fix) => {
2849
3069
  const r = runFix(fix);
2850
3070
  if (!r.ok) process.exitCode = 1;
@@ -2852,34 +3072,65 @@ async function doctor(args) {
2852
3072
  };
2853
3073
  if (args.json) {
2854
3074
  return json(
2855
- issues.map(({ fix, choices, ...issue }) => ({
2856
- ...issue,
2857
- ...fix && { fix: fix.label },
2858
- ...choices && { choices: choices.map((c) => c.label) },
2859
- ...args.fix && fix && { fixed: fixed(fix) }
2860
- }))
3075
+ "doctor",
3076
+ issues.map(
3077
+ ({ id, severity, title, detail, fix, choices }) => ({
3078
+ id,
3079
+ severity,
3080
+ title,
3081
+ detail,
3082
+ fix: fix?.label,
3083
+ choices: choices?.map((c) => c.label),
3084
+ fixed: args.fix && fix ? fixed(fix) : void 0
3085
+ })
3086
+ )
2861
3087
  );
2862
3088
  }
2863
3089
  if (issues.length === 0) return success("Everything looks good");
2864
3090
  for (const issue of issues) {
2865
3091
  info(`${issue.severity === "problem" ? red("\u25CF") : yellow("\u25C6")} ${issue.title}`);
2866
3092
  info(dim(` ${issue.detail}${issue.fix ? ` \u2192 ${issue.fix.label}` : ""}`));
2867
- if (issue.choices) info(dim(` choose in skilllib \u2192 Health: ${issue.choices.map((c) => c.label).join(" / ")}`));
3093
+ if (issue.choices) info(dim(` your call: ${issue.choices.map((c, i) => `${i + 1}) ${c.label}`).join(" ")} \u2192 skilllib apply ${issue.id} <n>`));
2868
3094
  if (args.fix && issue.fix) {
2869
3095
  const r = fixed(issue.fix);
2870
3096
  info(` ${r.ok ? green("\u2713") : red("\u2717")} ${r.message}`);
2871
3097
  }
2872
3098
  }
2873
- if (!args.fix && issues.some((i) => i.fix)) info(dim("\nRun `skilllib doctor --fix` to apply the fixes, or fix them one by one in `skilllib` \u2192 Health."));
3099
+ if (!args.fix && issues.some((i) => i.fix)) info(dim("\nRun `skilllib doctor --fix` to apply the fixes, or one at a time with `skilllib apply <id>`."));
3100
+ }
3101
+ async function apply(args) {
3102
+ const [id, pick] = args.positional;
3103
+ if (!id) throw new Error("Usage: skilllib apply <issue-id> [choice] (ids and choices: skilllib status --json or doctor --json)");
3104
+ const found = [...statusIssues(findProjectRoot()), ...await allIssues(args, id.startsWith("unused:"))].filter((i) => i.id === id);
3105
+ if (!found.length) throw new Error(`No issue ${id} right now (it may be fixed already; for an unused:\u2026 issue, pass doctor's --days). See skilllib status --json or doctor --json`);
3106
+ const optionsOf = (i) => i.choices ?? (i.fix ? [i.fix] : []);
3107
+ const labels = (i) => optionsOf(i).map((c) => c.label).join("\0");
3108
+ const byLabel = pick === void 0 ? void 0 : found.find((i) => optionsOf(i).some((c) => c.label.toLowerCase() === pick.toLowerCase()));
3109
+ if (!byLabel && found.some((i) => labels(i) !== labels(found[0]))) {
3110
+ const all = [...new Set(found.flatMap((i) => optionsOf(i).map((c) => c.label)))];
3111
+ throw new Error(`${found[0].title}: its choices differ between this repo and all your repos; pick one by label: ${all.map((l) => `"${l}"`).join(" ")}`);
3112
+ }
3113
+ const issue = byLabel ?? found[0];
3114
+ const options = optionsOf(issue);
3115
+ if (!options.length) throw new Error(`${issue.title}: nothing to apply. ${issue.detail}`);
3116
+ const listed = options.map((c, i) => `${i + 1}) ${c.label}`).join(" ");
3117
+ if (pick === void 0 && options.length > 1) throw new Error(`${issue.title}: pick one: ${listed}`);
3118
+ const choice = pick === void 0 ? options[0] : options.find((c) => c.label.toLowerCase() === pick.toLowerCase()) ?? options[Number(pick) - 1];
3119
+ if (!choice) throw new Error(`${issue.title}: no choice ${pick}. Pick one: ${listed}`);
3120
+ const r = runFix(choice);
3121
+ if (!r.ok) process.exitCode = 1;
3122
+ if (args.json) return json("apply", { id, applied: choice.label, ...r });
3123
+ if (r.ok) success(r.message);
3124
+ else error(r.message);
2874
3125
  }
2875
3126
  async function usage(args) {
2876
3127
  const summaries = summarize(await scanUsage(args.days), projectOfFactory(knownProjects()));
2877
3128
  if (args.json) {
2878
3129
  const library2 = new Set(librarySkills().map((s) => s.name));
2879
- return json({
3130
+ return json("usage", {
2880
3131
  usageDays: args.days,
2881
3132
  source: "Claude Code transcripts",
2882
- skills: summaries.map((s) => ({ skill: s.skill, uses: s.uses, lastUsed: s.lastUsed, projects: [...s.projects].map((p) => basename8(p)), inLibrary: library2.has(s.skill) })),
3133
+ skills: summaries.map((s) => ({ skill: s.skill, uses: s.uses, lastUsed: s.lastUsed, projects: [...s.projects].map((p) => basename9(p)), inLibrary: library2.has(s.skill) })),
2883
3134
  unusedLibrarySkills: librarySkills().filter((l) => !summaries.some((u) => u.skill === l.name)).map((l) => l.name)
2884
3135
  });
2885
3136
  }
@@ -2894,7 +3145,7 @@ async function usage(args) {
2894
3145
  summaries.map((s) => ({
2895
3146
  Skill: s.skill,
2896
3147
  Uses: String(s.uses),
2897
- Projects: [...s.projects].map((p) => basename8(p)).join(", ") || dim("-"),
3148
+ Projects: [...s.projects].map((p) => basename9(p)).join(", ") || dim("-"),
2898
3149
  "Last used": lastUsed(s),
2899
3150
  Library: library.has(s.skill) ? green("yes") : dim("no")
2900
3151
  }))
@@ -2914,13 +3165,13 @@ function show(args) {
2914
3165
  }
2915
3166
  const { description } = readSkillInfo(dir);
2916
3167
  if (args.json) {
2917
- return json({
3168
+ return json("show", {
2918
3169
  name,
2919
3170
  description,
2920
3171
  path: dir,
2921
3172
  latest: latestVersion(name)?.version ?? null,
2922
3173
  origin: libraryOrigins()[name] ?? "",
2923
- projects: knownProjects().filter((p) => name in readManifest(p).skills).map((p) => basename8(p)),
3174
+ projects: knownProjects().filter((p) => name in readManifest(p).skills).map((p) => basename9(p)),
2924
3175
  skillMd: readFileSync9(join14(dir, "SKILL.md"), "utf-8")
2925
3176
  });
2926
3177
  }
@@ -2954,7 +3205,7 @@ function link(args) {
2954
3205
  for (const root of roots) {
2955
3206
  if (args.allowTracked) allowTrackedLinks(root);
2956
3207
  const res = linkAll(root);
2957
- const label = args.all ? `${basename8(root)}: ` : "";
3208
+ const label = args.all ? `${basename9(root)}: ` : "";
2958
3209
  if (res.linked.length) success(`${label}linked ${res.linked.length} skill(s) for your other agents`);
2959
3210
  else if (!args.all && !res.blocked.length && !res.uncommitted.length) success("Every skill here is already usable by all your agents");
2960
3211
  if (res.blocked.length) {
@@ -2970,6 +3221,7 @@ function link(args) {
2970
3221
  function globalCommand(args) {
2971
3222
  const [action, ...names] = args.positional;
2972
3223
  const yours = machineSkills().filter((m) => m.movable && !m.broken);
3224
+ if (action === "move") return moveGlobal(args, names, yours);
2973
3225
  if (action === "keep" || action === "unkeep") {
2974
3226
  const unknown = names.filter((n) => !yours.some((m) => m.name === n));
2975
3227
  if (!names.length || unknown.length) {
@@ -2979,7 +3231,7 @@ function globalCommand(args) {
2979
3231
  setKeepGlobal(names, action === "keep");
2980
3232
  success(action === "keep" ? `Keeping ${names.join(", ")} global on purpose` : `${names.join(", ")} will show up in cleanup again`);
2981
3233
  } else if (action) {
2982
- error("Usage: skilllib global [keep|unkeep <name>...]");
3234
+ error("Usage: skilllib global [keep|unkeep <name>...] | skilllib global move <name>... --to <repo>...");
2983
3235
  process.exit(1);
2984
3236
  }
2985
3237
  const kept = keptGlobal();
@@ -2989,6 +3241,46 @@ function globalCommand(args) {
2989
3241
  if (unreviewed) info(dim(`
2990
3242
  ${unreviewed} not reviewed. Keep one global on purpose: skilllib global keep <name>`));
2991
3243
  }
3244
+ function repoRoot(repo) {
3245
+ const looksLikePath = /[\\/]/.test(repo) || repo.startsWith(".") || repo.startsWith("~");
3246
+ const named = looksLikePath ? [] : visibleProjects().filter((p) => basename9(p) === repo);
3247
+ if (named.length === 1) return named[0];
3248
+ if (named.length > 1) throw new Error(`Several projects are named ${repo}: ${named.map(tildify).join(", ")}. Pass its path.`);
3249
+ const path = expandHome(repo);
3250
+ if (!existsSync13(path)) throw new Error(`No project ${repo} (pass its path, or see skilllib projects)`);
3251
+ const root = projectHere(path);
3252
+ if (root !== path) throw new Error(root ? `${tildify(path)} is inside ${tildify(root)}: pass the repo's top folder` : `${tildify(path)} isn't in a git repo`);
3253
+ return root;
3254
+ }
3255
+ function moveGlobal(args, names, yours) {
3256
+ if (!names.length || !args.to.length) throw new Error("Usage: skilllib global move <name>... --to <repo>[,<repo>...]");
3257
+ const unknown = names.filter((n) => !yours.some((m) => m.name === n));
3258
+ if (unknown.length) throw new Error(`Not one of your global skills: ${unknown.join(", ")} (see skilllib global)`);
3259
+ const roots = [...new Set(args.to.map(repoRoot))];
3260
+ const skills = yours.filter((m) => names.includes(m.name));
3261
+ const moves = moveGlobalSkills(skills, roots, { allowTracked: args.allowTracked });
3262
+ if (moves.some((m) => !m.moved)) process.exitCode = 1;
3263
+ if (args.json) {
3264
+ return json(
3265
+ "global move",
3266
+ moves.map(({ name, moved, reason, changes }) => ({
3267
+ skill: name,
3268
+ moved,
3269
+ ...reason ? { reason } : {},
3270
+ projects: changes.map(([root, c]) => ({ project: basename9(root), path: root, change: withDirs(root, [c])[0] }))
3271
+ }))
3272
+ );
3273
+ }
3274
+ for (const m of moves) {
3275
+ if (m.moved) success(`${m.name} now loads only in ${m.changes.map(([r]) => basename9(r)).join(", ")} (the global copy is in skilllib restore)`);
3276
+ else error(`${m.name}: ${m.reason}`);
3277
+ for (const [root, c] of m.changes) {
3278
+ if (c.blocked?.length) warn(` ${basename9(root)}: git tracks ${c.blocked.join(", ")}, so no link there. Re-run with --allow-tracked to add it.`);
3279
+ if (c.uncommitted?.length) warn(` ${basename9(root)}: no link in ${c.uncommitted.join(", ")}: git tracks it but not the skill's copy, so the link would break for teammates. Commit the copy, then run skilllib link --allow-tracked.`);
3280
+ }
3281
+ }
3282
+ if (moves.some((m) => m.moved)) info(dim("Commit skilllib.json and the skill folders in each repo."));
3283
+ }
2992
3284
  function tidy(args) {
2993
3285
  const names = args.positional;
2994
3286
  const keepFor = (base) => args.keep ? Object.fromEntries(names.map((n) => [n, base ? resolve7(base, args.keep) : expandHome(args.keep)])) : {};
@@ -3001,13 +3293,13 @@ function tidy(args) {
3001
3293
  for (const name of names) {
3002
3294
  const where = targets.map((root) => ({ root, ...foldersOf(root, name) })).filter((w) => w.all.length);
3003
3295
  if (!where.length) {
3004
- problems.push(`${name}: no skill by that name ${args.global ? "in your global folders" : args.all ? "in your projects" : `in ${basename8(targets[0])}`}`);
3296
+ problems.push(`${name}: no skill by that name ${args.global ? "in your global folders" : args.all ? "in your projects" : `in ${basename9(targets[0])}`}`);
3005
3297
  continue;
3006
3298
  }
3007
3299
  if (!args.keep) continue;
3008
3300
  for (const { root, real } of where) {
3009
3301
  if (real.includes(keepFor(root)[name])) continue;
3010
- const label = root ? `${basename8(root)} \xB7 ` : "";
3302
+ const label = root ? `${basename9(root)} \xB7 ` : "";
3011
3303
  problems.push(
3012
3304
  `${label}${name}: no copy in ${args.keep}; ${real.length ? `pick one of: ${real.map((d) => folderLabel(root, d)).join(", ")}` : "it has no real copy to keep"}`
3013
3305
  );
@@ -3018,7 +3310,7 @@ function tidy(args) {
3018
3310
  process.exit(1);
3019
3311
  }
3020
3312
  const reports = targets.map(
3021
- (root) => root === null ? { label: "global folders", report: planGlobalTidy({ keep: keepFor(null) }) } : { label: basename8(root), report: planProjectTidy(root, { keep: keepFor(root) }) }
3313
+ (root) => root === null ? { label: "global folders", report: planGlobalTidy({ keep: keepFor(null) }) } : { label: basename9(root), report: planProjectTidy(root, { keep: keepFor(root) }) }
3022
3314
  );
3023
3315
  let held = 0;
3024
3316
  let changed = 0;
@@ -3058,7 +3350,7 @@ function agentSkill(args) {
3058
3350
  error(`Can't install the skilllib skill: ${r.reason}`);
3059
3351
  process.exit(1);
3060
3352
  }
3061
- if (args.json) return json({ state: agentSkillState(), dirs: r.dirs });
3353
+ if (args.json) return json("agent-skill", { state: agentSkillState(), dirs: r.dirs });
3062
3354
  success(`Installed in ${r.dirs.map((d) => tildify(d)).join(" and ")}`);
3063
3355
  if (!onPath("skilllib")) warn("`skilllib` isn't on your PATH, so agents can't run it. Install it: npm install -g skilllib");
3064
3356
  info(dim("New agent sessions can now answer questions like \u201Cwhich skills can you use here?\u201D"));
@@ -3066,11 +3358,11 @@ function agentSkill(args) {
3066
3358
  }
3067
3359
  if (action === "remove" || action === "rm") {
3068
3360
  const removed = removeAgentSkill();
3069
- if (args.json) return json({ state: agentSkillState(), removed });
3361
+ if (args.json) return json("agent-skill", { state: agentSkillState(), removed });
3070
3362
  return removed.length ? success(`Removed from ${removed.map((d) => tildify(d)).join(" and ")}`) : info("The skilllib skill isn't installed.");
3071
3363
  }
3072
3364
  const state = agentSkillState();
3073
- if (args.json) return json({ state, dirs: agentSkillDirs() });
3365
+ if (args.json) return json("agent-skill", { state, dirs: agentSkillDirs() });
3074
3366
  if (state === "installed") success(`Your agents can use skilllib ${dim(`(${agentSkillDirs().map((d) => tildify(d)).join(", ")})`)}`);
3075
3367
  else if (state === "outdated") warn("The skilllib skill is out of date. Run `skilllib agent-skill install` to update it.");
3076
3368
  else info("Your agents don't know about skilllib yet. Run `skilllib agent-skill install` so you can ask them about your skills.");
@@ -3102,7 +3394,6 @@ export {
3102
3394
  allowTrackedLinks,
3103
3395
  keptGlobal,
3104
3396
  setKeepGlobal,
3105
- setPluginEnabled,
3106
3397
  machineSkills,
3107
3398
  skillsLoadedIn,
3108
3399
  libraryOrigins,
@@ -3130,9 +3421,14 @@ export {
3130
3421
  gitInfo,
3131
3422
  relativeTo,
3132
3423
  claudePlugins,
3424
+ installedPlugins,
3425
+ cursorPlugins,
3426
+ forgetClaudeBinary,
3133
3427
  claudeBinary,
3134
3428
  turnOffIn,
3135
- recordRemovedPlugin,
3429
+ setPluginOn,
3430
+ uninstallPlugin,
3431
+ updatePlugin,
3136
3432
  pluginBackups,
3137
3433
  restorePlugin,
3138
3434
  cursorPluginSkills,
@@ -3148,6 +3444,7 @@ export {
3148
3444
  copyLabel,
3149
3445
  addedAt,
3150
3446
  scanUsage,
3447
+ moveGlobalSkills,
3151
3448
  parseArgs,
3152
3449
  projectOfFactory,
3153
3450
  status,
@@ -3166,6 +3463,7 @@ export {
3166
3463
  newSkill,
3167
3464
  restore,
3168
3465
  doctor,
3466
+ apply,
3169
3467
  usage,
3170
3468
  show,
3171
3469
  harnessesCommand,