skilllib 1.7.0 → 1.10.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) {
@@ -550,6 +570,20 @@ function globalFolderSkills(enabled) {
550
570
  function tildify2(p) {
551
571
  return tildify(p).replace(/\\/g, "/");
552
572
  }
573
+ function grokBuiltInSkills(enabled) {
574
+ if (!enabled.includes("grok")) return [];
575
+ return skillDirsIn(join7(grokDir(), "bundled", "skills")).map((path) => ({
576
+ name: basename2(path),
577
+ kind: "built-in",
578
+ origin: "Grok",
579
+ path,
580
+ description: readSkillInfo(path).description,
581
+ movable: false,
582
+ broken: false,
583
+ harnesses: ["grok"],
584
+ links: []
585
+ }));
586
+ }
553
587
  function cursorBuiltInSkills(enabled) {
554
588
  if (!enabled.includes("cursor")) return [];
555
589
  return skillDirsIn(join7(userHome(), ".cursor", "skills-cursor")).map((path) => ({
@@ -565,30 +599,41 @@ function cursorBuiltInSkills(enabled) {
565
599
  }));
566
600
  }
567
601
  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
- }))
602
+ const byReal = /* @__PURE__ */ new Map();
603
+ for (const skills of [join7(claudeDir(), "skills"), join7(userHome(), ".claude", "skills")]) {
604
+ const synced = join7(skills, "synced");
605
+ const real = realpathOrNull(synced);
606
+ const readers = harnessesReading(skills, enabled);
607
+ if (!real || readers.length === 0) continue;
608
+ const entry = byReal.get(real) ?? { dir: synced, readers: [] };
609
+ for (const id of readers) if (!entry.readers.includes(id)) entry.readers.push(id);
610
+ byReal.set(real, entry);
611
+ }
612
+ return [...byReal.values()].flatMap(
613
+ ({ dir, readers }) => readdirSync3(dir).filter((d) => !d.startsWith(".")).flatMap(
614
+ (bucket) => skillDirsIn(join7(dir, bucket)).map((path) => ({
615
+ name: basename2(path),
616
+ kind: "claude.ai",
617
+ origin: "claude.ai account",
618
+ path,
619
+ description: readSkillInfo(path).description,
620
+ movable: false,
621
+ broken: false,
622
+ harnesses: enabled.filter((id) => readers.includes(id)),
623
+ links: []
624
+ }))
625
+ )
583
626
  );
584
627
  }
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");
628
+ function pluginDirCandidates(id, installPaths) {
629
+ const [name = "", marketplace = ""] = id.split("@");
630
+ const plugins = join7(claudeDir(), "plugins");
631
+ return [
632
+ ...installPaths,
633
+ join7(plugins, "marketplaces", marketplace, "plugins", name),
634
+ join7(plugins, "marketplaces", marketplace, "external_plugins", name),
635
+ join7(plugins, "marketplaces", marketplace)
636
+ ];
592
637
  }
593
638
  function repoSettingsFiles(root) {
594
639
  return [join7(root, ".claude", "settings.json"), join7(root, ".claude", "settings.local.json")];
@@ -606,13 +651,7 @@ function pluginSkills(harnesses, root) {
606
651
  const user = enabledPlugins();
607
652
  const enabled = root ? enabledPlugins(root) : user;
608
653
  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
- ];
654
+ const candidates = pluginDirCandidates(id, (installed[id] ?? []).flatMap((i) => i.installPath ? [i.installPath] : []));
616
655
  const dir = candidates.find((c) => existsSync6(join7(c, "skills")));
617
656
  if (!dir) return [];
618
657
  const inCursor = cursor && user[id] && (installed[id] ?? []).some((i) => i.scope === "user" && i.installPath === dir);
@@ -647,6 +686,7 @@ function machineSkills(enabled = enabledHarnesses()) {
647
686
  ...claudeAiSkills(enabled),
648
687
  ...pluginSkills(enabled),
649
688
  ...cursorBuiltInSkills(enabled),
689
+ ...grokBuiltInSkills(enabled),
650
690
  ...systemSkills(enabled)
651
691
  ].sort(byKindAndName);
652
692
  }
@@ -670,7 +710,7 @@ function recordOrigin(name, origin) {
670
710
  function originFor(dir) {
671
711
  const match = machineSkills().find((s) => resolve5(s.path) === resolve5(dir));
672
712
  if (match) return match.kind === "global" ? "global folder" : `${match.kind}: ${match.origin}`;
673
- const project = dir.split(/[\\/]\.(?:claude|agents)[\\/]skills[\\/]/)[0];
713
+ const project = dir.split(/[\\/]\.(?:claude|agents|grok|cursor|codex)[\\/]skills[\\/]/)[0];
674
714
  if (!project || project === dir) return dir;
675
715
  const locked = projectSkillsLock(project)[basename2(dir)]?.source;
676
716
  return locked ? `skills.sh: ${locked}` : `project: ${basename2(project)}`;
@@ -716,7 +756,7 @@ function visibilityOf(root, name, enabled = enabledHarnesses()) {
716
756
  }
717
757
  function dependencyDirs(root, name, dep) {
718
758
  const wanted = installDirs(enabledHarnesses());
719
- const untracked = ALL_PROJECT_DIRS.find((d) => existsSync7(join8(root, d, name, "SKILL.md")) && !isLink(join8(root, d, name)));
759
+ const untracked = ALL_PROJECT_DIRS.find((d) => d !== GROK_SKILLS_DIR && existsSync7(join8(root, d, name, "SKILL.md")) && !isLink(join8(root, d, name)));
720
760
  const primary = dep ? dep.dir ?? PROJECT_SKILLS_DIR : untracked ?? wanted[0];
721
761
  return [primary, .../* @__PURE__ */ new Set([...dep?.links ?? [], ...wanted.filter((d) => d !== primary)])];
722
762
  }
@@ -768,7 +808,7 @@ function projectStatus(root) {
768
808
  unmanaged.push({
769
809
  name,
770
810
  managed: false,
771
- state: unmanagedState(name, real, location === AGENTS_SKILLS_DIR, source),
811
+ state: unmanagedState(name, real, isRepoSkillLocation(location), source),
772
812
  version: null,
773
813
  latest: latestVersion(name)?.version ?? null,
774
814
  location,
@@ -788,10 +828,12 @@ function unmanagedState(name, real, committed, source) {
788
828
  return latest === null ? "local only" : sameAsLibrary ? "untracked copy of library skill" : "untracked, differs from library";
789
829
  }
790
830
  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"]]
831
+ [PROJECT_SKILLS_DIR, ["claude-code", "cursor", "grok"]],
832
+ [AGENTS_SKILLS_DIR, ["codex", "cursor", "grok"]],
833
+ [".cursor/skills", ["cursor", "grok"]],
834
+ [".codex/skills", ["codex", "cursor"]],
835
+ // Cursor reads nested .grok/skills too, but skilllib doesn't count it: that folder stays Grok's.
836
+ [".grok/skills", ["grok"]]
795
837
  ];
796
838
  var NESTED_SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "vendor", "target", "coverage"]);
797
839
  function nestedSkills(root, { depth = 3, enabled = enabledHarnesses() } = {}) {
@@ -813,7 +855,7 @@ function nestedSkills(root, { depth = 3, enabled = enabledHarnesses() } = {}) {
813
855
  found.push({
814
856
  name,
815
857
  managed: false,
816
- state: unmanagedState(name, path, skillsDir === AGENTS_SKILLS_DIR, void 0),
858
+ state: unmanagedState(name, path, isRepoSkillLocation(skillsDir), void 0),
817
859
  version: null,
818
860
  latest: latestVersion(name)?.version ?? null,
819
861
  location,
@@ -866,10 +908,15 @@ function linkEverywhere(root, name, location, { allowTracked = false, dryRun = f
866
908
  }
867
909
  return result;
868
910
  }
869
- function unlinkEverywhere(root, name) {
911
+ function unlinkEverywhere(root, name, removing) {
912
+ const gone = removing ? realpathOrNull2(removing) : null;
870
913
  return ALL_PROJECT_DIRS.filter((dir) => {
871
914
  const link2 = join8(root, dir, name);
872
915
  if (!isLink(link2)) return false;
916
+ if (dir === GROK_SKILLS_DIR) {
917
+ const target = realpathOrNull2(link2);
918
+ if (target !== null && target !== gone) return false;
919
+ }
873
920
  unlinkSync(link2);
874
921
  return true;
875
922
  });
@@ -1052,7 +1099,7 @@ function stash(dir, bucket) {
1052
1099
  return target;
1053
1100
  }
1054
1101
  function removeUntracked(root, name, path) {
1055
- unlinkEverywhere(root, name);
1102
+ unlinkEverywhere(root, name, path);
1056
1103
  return stash(path, "trash");
1057
1104
  }
1058
1105
  function projectsUsing(name) {
@@ -1310,21 +1357,109 @@ function claudePlugins(machine, root) {
1310
1357
  };
1311
1358
  });
1312
1359
  }
1360
+ function newer(a, b) {
1361
+ const parts = (v) => v.replace(/^v/i, "").split(/[.+-]/).map((x) => parseInt(x, 10) || 0);
1362
+ const pa = parts(a);
1363
+ const pb = parts(b);
1364
+ 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);
1365
+ return false;
1366
+ }
1367
+ function updateOf(name, marketplace, entry, catalogs) {
1368
+ const dir = join11(claudeDir(), "plugins", "marketplaces", marketplace);
1369
+ if (!catalogs.has(marketplace)) catalogs.set(marketplace, readJson2(join11(dir, ".claude-plugin", "marketplace.json"))?.plugins ?? []);
1370
+ const listed = catalogs.get(marketplace).find((p) => p.name === name);
1371
+ if (!listed) return void 0;
1372
+ const version = listed.version ?? (typeof listed.source === "string" ? readJson2(join11(dir, listed.source, ".claude-plugin", "plugin.json"))?.version : void 0);
1373
+ if (version && entry.version) return newer(version, entry.version) ? version : void 0;
1374
+ const sha = typeof listed.source === "object" ? listed.source?.sha : void 0;
1375
+ return sha && entry.gitCommitSha && sha !== entry.gitCommitSha ? "newer" : void 0;
1376
+ }
1377
+ function pluginRoot(id, entry) {
1378
+ const candidates = pluginDirCandidates(id, entry.installPath ? [entry.installPath] : []);
1379
+ return candidates.find((c) => existsSync10(join11(c, "skills"))) ?? candidates.find((c) => existsSync10(c)) ?? candidates[0];
1380
+ }
1381
+ function describe(root) {
1382
+ const manifest = readJson2(join11(root, ".claude-plugin", "plugin.json")) ?? {};
1383
+ return { description: manifest.description ?? "", version: manifest.version, skills: skillDirsIn(join11(root, "skills")), extras: extrasOf(root) };
1384
+ }
1385
+ function installedPlugins() {
1386
+ const plugins = join11(claudeDir(), "plugins");
1387
+ const installed = readJson2(join11(plugins, "installed_plugins.json"))?.plugins ?? {};
1388
+ const catalogs = /* @__PURE__ */ new Map();
1389
+ const fromMarketplaces = Object.entries(installed).flatMap(
1390
+ ([id, entries]) => entries.map((entry) => {
1391
+ const [name = id, marketplace = ""] = id.split("@");
1392
+ const scope = entry.scope ?? "user";
1393
+ const projectPath = scope === "user" ? void 0 : entry.projectPath;
1394
+ const root = pluginRoot(id, entry);
1395
+ const about = describe(root);
1396
+ const update2 = updateOf(name, marketplace, entry, catalogs);
1397
+ return {
1398
+ id,
1399
+ scope,
1400
+ ...projectPath ? { projectPath } : {},
1401
+ synced: false,
1402
+ agent: "claude-code",
1403
+ on: enabledPlugins(projectPath)[id] === true,
1404
+ root,
1405
+ ...about,
1406
+ version: entry.version ?? about.version,
1407
+ ...update2 ? { update: update2 } : {}
1408
+ };
1409
+ })
1410
+ );
1411
+ const user = enabledPlugins();
1412
+ const fromSettings = Object.keys(user).filter((id) => user[id] === true && id.includes("@") && !id.endsWith("@synced") && !installed[id]).flatMap((id) => {
1413
+ const root = pluginDirCandidates(id, []).find((c) => existsSync10(join11(c, "skills")));
1414
+ return root ? [{ id, scope: "user", synced: false, agent: "claude-code", on: true, settingsOnly: true, root, ...describe(root) }] : [];
1415
+ });
1416
+ const syncedDir = join11(plugins, "synced");
1417
+ const dirs = (p) => existsSync10(p) ? readdirSync5(p, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => join11(p, d.name)) : [];
1418
+ const fromSynced = dirs(syncedDir).flatMap(dirs).map((root) => {
1419
+ const id = `${readJson2(join11(root, ".claude-plugin", "plugin.json"))?.name ?? basename4(root)}@synced`;
1420
+ return { id, scope: null, synced: true, agent: "claude-code", on: user[id] !== false, root, ...describe(root) };
1421
+ });
1422
+ return [...fromMarketplaces, ...fromSettings, ...fromSynced];
1423
+ }
1424
+ function cursorPlugins() {
1425
+ const cache = join11(userHome(), ".cursor", "plugins", "cache");
1426
+ const dirs = (p) => existsSync10(p) ? readdirSync5(p).filter((d) => !d.startsWith(".") && statSync3(join11(p, d)).isDirectory()) : [];
1427
+ return dirs(cache).flatMap(
1428
+ (marketplace) => dirs(join11(cache, marketplace)).flatMap((plugin) => {
1429
+ const versions = dirs(join11(cache, marketplace, plugin)).map((v) => join11(cache, marketplace, plugin, v));
1430
+ const root = versions.sort((a, b) => statSync3(b).mtimeMs - statSync3(a).mtimeMs)[0];
1431
+ if (!root) return [];
1432
+ const about = describe(root);
1433
+ return [{ id: `${plugin}@${marketplace}`, scope: null, synced: false, agent: "cursor", on: null, root, ...about, version: about.version ?? basename4(root) }];
1434
+ })
1435
+ );
1436
+ }
1437
+ var binaries = /* @__PURE__ */ new Map();
1438
+ function forgetClaudeBinary() {
1439
+ binaries.clear();
1440
+ }
1313
1441
  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;
1442
+ const key = `${process.env.PATH}|${userHome()}`;
1443
+ if (!binaries.has(key)) {
1444
+ const candidates = ["claude", join11(userHome(), ".local", "bin", "claude"), join11(userHome(), ".claude", "local", "claude")];
1445
+ const found = candidates.find((bin) => {
1446
+ try {
1447
+ return execFileSync3(bin, ["--version"], { stdio: ["ignore", "pipe", "ignore"], timeout: 1e4, encoding: "utf-8" }).includes("Claude Code");
1448
+ } catch {
1449
+ return false;
1450
+ }
1451
+ });
1452
+ binaries.set(key, found ?? null);
1453
+ }
1454
+ return binaries.get(key);
1322
1455
  }
1323
1456
  function claude(args, cwd, bin = claudeBinary()) {
1457
+ 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
1458
  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` };
1459
+ const slow = args[1] === "install" || args[1] === "update";
1325
1460
  try {
1326
- execFileSync3(bin, args, { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 12e4, encoding: "utf-8" });
1327
- return { ok: true };
1461
+ const out2 = execFileSync3(bin, args, { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: slow ? 12e4 : 2e4, encoding: "utf-8" });
1462
+ return { ok: true, out: out2 };
1328
1463
  } catch (err) {
1329
1464
  const e = err;
1330
1465
  return { ok: false, reason: `${(e.stderr || e.stdout || e.message || "failed").trim().split("\n").slice(-1)[0]} (ran ${bin})` };
@@ -1338,9 +1473,6 @@ function setPluginIn(file, id, on) {
1338
1473
  writeFileSync7(file, JSON.stringify({ ...settings, enabledPlugins: { ...enabled, [id]: on } }, null, 2) + "\n");
1339
1474
  return { ok: true };
1340
1475
  }
1341
- function turnOffSynced(id) {
1342
- return setPluginIn(join11(claudeDir(), "settings.json"), id, false);
1343
- }
1344
1476
  function ignoreLocalSettings(root) {
1345
1477
  const file = ".claude/settings.local.json";
1346
1478
  const git2 = (...args) => execFileSync3("git", args, { cwd: root, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
@@ -1380,6 +1512,50 @@ function turnOffIn(plugin, root) {
1380
1512
  const r = claude(["plugin", "disable", plugin.id, "--scope", "local"], root, bin);
1381
1513
  return r.ok ? { ok: true, message: done } : { ok: false, message: `${plugin.id}: ${r.reason}` };
1382
1514
  }
1515
+ function settingsFileOf(p) {
1516
+ if (p.synced || !p.scope || p.scope === "user") return join11(claudeDir(), "settings.json");
1517
+ if (!p.projectPath) return null;
1518
+ return join11(p.projectPath, ".claude", p.scope === "local" ? "settings.local.json" : "settings.json");
1519
+ }
1520
+ function whereOf(p) {
1521
+ return p.projectPath ? ` in ${basename4(p.projectPath)} (${p.scope})` : "";
1522
+ }
1523
+ function setPluginOn(p, on) {
1524
+ const done = { ok: true, message: `${p.id} is ${on ? "on" : "off"}${whereOf(p)}${p.synced ? "" : "; Claude Code picks it up in its next session"}` };
1525
+ 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` };
1526
+ const bin = p.synced || p.settingsOnly ? null : claudeBinary();
1527
+ if (bin) {
1528
+ const r2 = claude(["plugin", on ? "enable" : "disable", p.id, ...p.scope ? ["--scope", p.scope] : []], p.projectPath, bin);
1529
+ return r2.ok ? done : { ok: false, message: `${p.id}: ${r2.reason}` };
1530
+ }
1531
+ const r = setPluginIn(settingsFileOf(p), p.id, on);
1532
+ if (!r.ok) return { ok: false, message: `${p.id}: ${r.reason}` };
1533
+ return p.scope === "local" && p.projectPath ? { ...done, message: done.message + ignoreLocalSettings(p.projectPath) } : done;
1534
+ }
1535
+ function uninstallPlugin(p) {
1536
+ if (p.synced) return { ok: false, message: `${p.id} is synced from your claude.ai account: remove it there, or turn it off here` };
1537
+ if (p.settingsOnly) return { ok: false, message: `${p.id} isn't installed, only turned on in your settings: turn it off instead` };
1538
+ 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` };
1539
+ const wasOn = enabledPlugins(p.projectPath)[p.id] === true;
1540
+ const r = claude(["plugin", "uninstall", p.id, "--keep-data", "--scope", p.scope ?? "user"], p.projectPath);
1541
+ if (!r.ok) return { ok: false, message: `${p.id}: ${r.reason}` };
1542
+ recordRemovedPlugin(p.id, p.scope ?? "user", p.projectPath, wasOn);
1543
+ return { ok: true, message: `${p.id} is uninstalled${whereOf(p)} (Settings \u203A Backups reinstalls it)` };
1544
+ }
1545
+ function updatePlugin(p) {
1546
+ if (p.synced) return { ok: false, message: `${p.id} is synced from your claude.ai account: it updates from there` };
1547
+ 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` };
1548
+ const r = claude(["plugin", "update", p.id, ...p.scope ? ["--scope", p.scope] : [], "--json"], p.projectPath);
1549
+ 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}` : ""}` };
1550
+ let result = {};
1551
+ try {
1552
+ result = JSON.parse(r.out.trim().split("\n").pop() ?? "{}");
1553
+ } catch {
1554
+ }
1555
+ if (result.updateOutcome === "up_to_date") return { ok: true, message: `${p.id} is already the newest version${result.newVersion ? ` (${result.newVersion})` : ""}${whereOf(p)}` };
1556
+ const versions = result.oldVersion && result.newVersion ? ` from ${result.oldVersion} to ${result.newVersion}` : "";
1557
+ return { ok: true, message: `${p.id} is updated${versions}${whereOf(p)}; Claude Code uses the new version after a restart` };
1558
+ }
1383
1559
  function removedFile() {
1384
1560
  return join11(skilllibHome(), "plugin-backup.json");
1385
1561
  }
@@ -1392,42 +1568,45 @@ function removePlugin(plugin, how) {
1392
1568
  }
1393
1569
  const imported = plugin.skills.filter((dir) => latestVersion(basename4(dir)) === null).map((dir) => importSkill(dir).name);
1394
1570
  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);
1571
+ const r = plugin.synced ? (
1572
+ // Synced plugins aren't installed, so `claude plugin` can't touch them: their switch is enabledPlugins.
1573
+ setPluginIn(join11(claudeDir(), "settings.json"), plugin.id, false)
1574
+ ) : claude(["plugin", how === "delete" ? "uninstall" : "disable", plugin.id, ...scope], plugin.projectPath);
1396
1575
  const saved = imported.length ? `; ${imported.join(", ")} copied into Your skills` : "";
1397
1576
  if (!r.ok) return { ok: false, message: `${plugin.id}: ${r.reason}${saved}` };
1398
- if (how === "delete" && !plugin.synced) recordRemovedPlugin(plugin.id, plugin.scope ?? "user");
1577
+ if (how === "delete" && !plugin.synced) recordRemovedPlugin(plugin.id, plugin.scope ?? "user", plugin.projectPath, true);
1399
1578
  return { ok: true, message: `${plugin.id} ${how === "delete" && !plugin.synced ? "removed" : "turned off"}${saved}` };
1400
1579
  }
1401
- function recordRemovedPlugin(id, scope) {
1580
+ function recordRemovedPlugin(id, scope, projectPath, on = true) {
1402
1581
  const list2 = readJson2(removedFile()) ?? [];
1403
1582
  mkdirSync7(skilllibHome(), { recursive: true });
1404
- writeFileSync7(removedFile(), JSON.stringify([...list2, { id, scope, at: (/* @__PURE__ */ new Date()).toISOString() }], null, 2) + "\n");
1583
+ writeFileSync7(removedFile(), JSON.stringify([...list2, { id, scope, ...projectPath ? { projectPath } : {}, on, at: (/* @__PURE__ */ new Date()).toISOString() }], null, 2) + "\n");
1405
1584
  }
1406
1585
  function pluginBackups() {
1407
1586
  return (readJson2(removedFile()) ?? []).map((r) => ({
1408
1587
  name: r.id,
1409
1588
  kind: "plugin",
1410
- path: r.scope,
1589
+ // Project and local installs go back into their project.
1590
+ path: r.projectPath ? `${r.scope}:${r.projectPath}` : r.scope,
1411
1591
  movedAt: r.at.slice(0, 16).replace("T", " "),
1412
1592
  from: r.id
1413
1593
  }));
1414
1594
  }
1415
1595
  function restorePlugin(backup) {
1416
- const r = claude(["plugin", "install", backup.from, "--scope", backup.path]);
1596
+ const [scope = "user", ...rest] = backup.path.split(":");
1597
+ const projectPath = rest.join(":") || void 0;
1598
+ const r = claude(["plugin", "install", backup.from, "--scope", scope], projectPath);
1417
1599
  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})` };
1600
+ const records = readJson2(removedFile()) ?? [];
1601
+ const same = (x) => x.id === backup.from && (x.projectPath ?? void 0) === projectPath && x.scope === scope;
1602
+ const wasOff = records.filter(same).pop()?.on === false;
1603
+ const off = wasOff ? claude(["plugin", "disable", backup.from, "--scope", scope], projectPath) : null;
1604
+ writeFileSync7(removedFile(), JSON.stringify(records.filter((x) => !same(x)), null, 2) + "\n");
1605
+ 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" : ""})`;
1606
+ return { ok: true, to: where };
1420
1607
  }
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
- );
1608
+ function cursorPluginSkills(plugins = cursorPlugins()) {
1609
+ return plugins.flatMap((p) => p.skills.map((path) => ({ name: basename4(path), plugin: p.id.split("@")[0], path })));
1431
1610
  }
1432
1611
 
1433
1612
  // src/tidy.ts
@@ -1455,6 +1634,11 @@ function namesIn(dirs) {
1455
1634
  }
1456
1635
  return [...names].sort();
1457
1636
  }
1637
+ var PROJECT_TIDY_DIRS = ALL_PROJECT_DIRS.filter((d) => d !== GROK_SKILLS_DIR);
1638
+ function globalTidyDirs() {
1639
+ const own = realPath(join12(grokDir(), "skills"));
1640
+ return globalSkillDirs().filter((d) => realPath(d) !== own);
1641
+ }
1458
1642
  function runsFrom(id, dirsWithCopy, readsOf) {
1459
1643
  const reads = readsOf(id).filter((d) => dirsWithCopy.includes(d));
1460
1644
  if (id === "codex") return reads;
@@ -1477,8 +1661,8 @@ function planProjectTidy(root, { keep = {}, enabled = enabledHarnesses() } = {})
1477
1661
  const readsOf = (id) => HARNESSES.find((h) => h.id === id).projectDirs.map(abs);
1478
1662
  let git2;
1479
1663
  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);
1664
+ for (const name of namesIn(PROJECT_TIDY_DIRS.map(abs))) {
1665
+ const entries = entriesOf(PROJECT_TIDY_DIRS.map(abs), name);
1482
1666
  const external = entries.find((e) => e.link && e.real && !(e.real + sep4).startsWith(realpathOrNull2(root) + sep4));
1483
1667
  if (external) {
1484
1668
  report.skipped.push({ name, reason: `${rel(external.path)} links outside the repo` });
@@ -1533,7 +1717,7 @@ function isSkillsDirPlugin(path) {
1533
1717
  }
1534
1718
  function planGlobalTidy({ keep = {}, enabled = enabledHarnesses() } = {}) {
1535
1719
  const report = { plans: [], conflicts: [], skipped: [] };
1536
- const dirs = globalSkillDirs();
1720
+ const dirs = globalTidyDirs();
1537
1721
  const agents = join12(userHome(), ".agents", "skills");
1538
1722
  for (const name of namesIn(dirs)) {
1539
1723
  const entries = entriesOf(dirs, name);
@@ -1607,7 +1791,7 @@ function folderLabel(root, dir) {
1607
1791
  return root ? relativeTo(root, dir) : tildify(dir);
1608
1792
  }
1609
1793
  function foldersOf(root, name) {
1610
- const entries = entriesOf(root ? ALL_PROJECT_DIRS.map((d) => join12(root, d)) : globalSkillDirs(), name);
1794
+ const entries = entriesOf(root ? PROJECT_TIDY_DIRS.map((d) => join12(root, d)) : globalTidyDirs(), name);
1611
1795
  return { all: entries.map((e) => e.dir), real: entries.filter((e) => !e.link).map((e) => e.dir) };
1612
1796
  }
1613
1797
 
@@ -2003,17 +2187,17 @@ function findIssues(projects2, machine, libraryNames, statusOf = projectStatus,
2003
2187
  });
2004
2188
  }
2005
2189
  if (s.state === "edited locally" || s.state === "edited locally, update available") {
2006
- const newer = s.state !== "edited locally";
2190
+ const newer2 = s.state !== "edited locally";
2007
2191
  const pinned = s.version ? `v${s.version}` : "the library version";
2008
2192
  issues.push({
2009
2193
  id: `edited:${root}:${s.name}`,
2010
2194
  severity: "suggestion",
2011
2195
  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.`,
2196
+ 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
2197
  choices: [
2014
2198
  {
2015
2199
  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`,
2200
+ 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
2201
  run: () => {
2018
2202
  importSkill(s.path, { force: true });
2019
2203
  const c = addSkill(root, s.name);
@@ -2207,12 +2391,12 @@ function usableHere(root, uses = /* @__PURE__ */ new Map()) {
2207
2391
  const usual = s.state === "ok" || s.state === "local only" || repo && s.state !== "from npx skills";
2208
2392
  const attrs = {
2209
2393
  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 }
2394
+ dir: s.location !== ".claude/skills" ? s.location : void 0,
2395
+ version: s.version ?? void 0,
2396
+ state: usual ? void 0 : s.state,
2397
+ latest: behind ? s.latest : void 0,
2398
+ git: gitState,
2399
+ agents: sameSet(loadedBy, agents) ? void 0 : loadedBy
2216
2400
  };
2217
2401
  return [JSON.stringify(attrs), s.name, () => ({ ...attrs, skills: [] })];
2218
2402
  })
@@ -2231,7 +2415,7 @@ function usableHere(root, uses = /* @__PURE__ */ new Map()) {
2231
2415
  for (const i of findIssues(inRepo ? [root] : [], machine, new Set(librarySkills().map((l) => l.name)))) {
2232
2416
  const choices = i.choices?.map((c) => c.label);
2233
2417
  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 } };
2418
+ const issue = issues.get(key) ?? { problems: [], detail: i.detail, fix: i.fix?.label, choices };
2235
2419
  issue.problems.push(i.title);
2236
2420
  issues.set(key, issue);
2237
2421
  }
@@ -2241,10 +2425,10 @@ function usableHere(root, uses = /* @__PURE__ */ new Map()) {
2241
2425
  files: topLevel(root),
2242
2426
  agents,
2243
2427
  skills,
2244
- ...nested.length > 0 && { nested },
2428
+ nested: nested.length > 0 ? nested : void 0,
2245
2429
  global,
2246
2430
  issues: [...issues.values()],
2247
- ...Object.keys(usesHere).length > 0 && { usesHere }
2431
+ usesHere: Object.keys(usesHere).length > 0 ? usesHere : void 0
2248
2432
  };
2249
2433
  }
2250
2434
  function firstSentence(description) {
@@ -2256,12 +2440,12 @@ function libraryFor(currentProject) {
2256
2440
  const manifests = visibleProjects().map((p) => ({ name: basename7(p), skills: readManifest(p).skills }));
2257
2441
  const here = currentProject ? new Set(projectStatus(currentProject).map((s) => s.name)) : null;
2258
2442
  const all = librarySkills();
2259
- const describe = (s) => {
2443
+ const describe2 = (s) => {
2260
2444
  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 } };
2445
+ return { name: s.name, description: firstSentence(s.description), projects: projects2.length > 0 ? projects2 : void 0 };
2262
2446
  };
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) };
2447
+ if (!here) return { skills: all.map(describe2) };
2448
+ return { inThisProject: all.filter((s) => here.has(s.name)).map((s) => s.name), skills: all.filter((s) => !here.has(s.name)).map(describe2) };
2265
2449
  }
2266
2450
 
2267
2451
  // src/usage.ts
@@ -2427,7 +2611,7 @@ function withDirs(root, changes) {
2427
2611
  }
2428
2612
  function printChanges(root, changes, args) {
2429
2613
  if (changes.some((c) => c.action === "skipped")) process.exitCode = 1;
2430
- if (args.json) return json(withDirs(root, changes));
2614
+ if (args.json) return json("changes", withDirs(root, changes));
2431
2615
  for (const c of changes) {
2432
2616
  if (c.blocked?.length) {
2433
2617
  warn(`${c.name}: git tracks ${c.blocked.join(", ")} here, so no link was added there. Re-run with --allow-tracked to add it.`);
@@ -2482,7 +2666,7 @@ async function status(args) {
2482
2666
  if (args.json) {
2483
2667
  const root2 = findProjectRoot();
2484
2668
  const usage3 = await usageBySkill(args.days, root2);
2485
- json(usableHere(root2, new Map([...usage3].map(([name, u]) => [name, u.uses]))));
2669
+ json("status", usableHere(root2, new Map([...usage3].map(([name, u]) => [name, u.uses]))));
2486
2670
  if (projectHere()) rememberProjects([root2]);
2487
2671
  return;
2488
2672
  }
@@ -2556,7 +2740,7 @@ async function overview(args) {
2556
2740
  }
2557
2741
  async function list(args) {
2558
2742
  if (args.json) {
2559
- return json(libraryFor(projectHere()));
2743
+ return json("list", libraryFor(projectHere()));
2560
2744
  }
2561
2745
  const skills = librarySkills();
2562
2746
  if (skills.length === 0) {
@@ -2600,11 +2784,16 @@ function projects(args) {
2600
2784
  const roots = visibleProjects();
2601
2785
  if (args.json) {
2602
2786
  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
- }))
2787
+ "projects",
2788
+ roots.map(
2789
+ (root) => ({
2790
+ name: basename8(root),
2791
+ path: root,
2792
+ skills: projectStatus(root).map(
2793
+ (s) => ({ name: s.name, managed: s.managed, state: s.state, version: s.version, latest: s.latest })
2794
+ )
2795
+ })
2796
+ )
2608
2797
  );
2609
2798
  }
2610
2799
  if (roots.length === 0) {
@@ -2659,7 +2848,7 @@ function sync(args) {
2659
2848
  }
2660
2849
  return { project: basename8(root), path: root, changes: withDirs(root, changes) };
2661
2850
  });
2662
- if (args.json) json(args.all ? results : results[0].changes);
2851
+ if (args.json) args.all ? json("changes --all", results) : json("changes", results[0].changes);
2663
2852
  if (!args.all) rememberProjects(roots);
2664
2853
  }
2665
2854
  function update(args) {
@@ -2680,14 +2869,14 @@ function update(args) {
2680
2869
  }
2681
2870
  return { project: basename8(root), path: root, changes: withDirs(root, changes) };
2682
2871
  });
2683
- if (args.json) json(args.all ? results : results[0].changes);
2872
+ if (args.json) args.all ? json("changes --all", results) : json("changes", results[0].changes);
2684
2873
  }
2685
2874
  function outdated(args) {
2686
2875
  const roots = args.all ? visibleProjects() : [requireProject()];
2687
2876
  const behind = roots.flatMap(
2688
2877
  (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 }))
2689
2878
  );
2690
- if (args.json) return json(behind);
2879
+ if (args.json) return json("outdated", behind);
2691
2880
  const rows = behind.map((b) => ({ Project: b.project, Skill: b.skill, Installed: `v${b.installed}`, Latest: `v${b.latest}`, Status: colorState(b.state) }));
2692
2881
  if (rows.length === 0) return success("Everything is on the latest version");
2693
2882
  table(rows);
@@ -2724,7 +2913,7 @@ function importSkills(args) {
2724
2913
  rememberProjects([root]);
2725
2914
  }
2726
2915
  }
2727
- if (args.json) json(results);
2916
+ if (args.json) json("import", results);
2728
2917
  }
2729
2918
  function scan(args) {
2730
2919
  for (const p of args.positional) addRoot(p);
@@ -2852,12 +3041,18 @@ async function doctor(args) {
2852
3041
  };
2853
3042
  if (args.json) {
2854
3043
  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
- }))
3044
+ "doctor",
3045
+ issues.map(
3046
+ ({ id, severity, title, detail, fix, choices }) => ({
3047
+ id,
3048
+ severity,
3049
+ title,
3050
+ detail,
3051
+ fix: fix?.label,
3052
+ choices: choices?.map((c) => c.label),
3053
+ fixed: args.fix && fix ? fixed(fix) : void 0
3054
+ })
3055
+ )
2861
3056
  );
2862
3057
  }
2863
3058
  if (issues.length === 0) return success("Everything looks good");
@@ -2876,7 +3071,7 @@ async function usage(args) {
2876
3071
  const summaries = summarize(await scanUsage(args.days), projectOfFactory(knownProjects()));
2877
3072
  if (args.json) {
2878
3073
  const library2 = new Set(librarySkills().map((s) => s.name));
2879
- return json({
3074
+ return json("usage", {
2880
3075
  usageDays: args.days,
2881
3076
  source: "Claude Code transcripts",
2882
3077
  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) })),
@@ -2914,7 +3109,7 @@ function show(args) {
2914
3109
  }
2915
3110
  const { description } = readSkillInfo(dir);
2916
3111
  if (args.json) {
2917
- return json({
3112
+ return json("show", {
2918
3113
  name,
2919
3114
  description,
2920
3115
  path: dir,
@@ -3058,7 +3253,7 @@ function agentSkill(args) {
3058
3253
  error(`Can't install the skilllib skill: ${r.reason}`);
3059
3254
  process.exit(1);
3060
3255
  }
3061
- if (args.json) return json({ state: agentSkillState(), dirs: r.dirs });
3256
+ if (args.json) return json("agent-skill", { state: agentSkillState(), dirs: r.dirs });
3062
3257
  success(`Installed in ${r.dirs.map((d) => tildify(d)).join(" and ")}`);
3063
3258
  if (!onPath("skilllib")) warn("`skilllib` isn't on your PATH, so agents can't run it. Install it: npm install -g skilllib");
3064
3259
  info(dim("New agent sessions can now answer questions like \u201Cwhich skills can you use here?\u201D"));
@@ -3066,11 +3261,11 @@ function agentSkill(args) {
3066
3261
  }
3067
3262
  if (action === "remove" || action === "rm") {
3068
3263
  const removed = removeAgentSkill();
3069
- if (args.json) return json({ state: agentSkillState(), removed });
3264
+ if (args.json) return json("agent-skill", { state: agentSkillState(), removed });
3070
3265
  return removed.length ? success(`Removed from ${removed.map((d) => tildify(d)).join(" and ")}`) : info("The skilllib skill isn't installed.");
3071
3266
  }
3072
3267
  const state = agentSkillState();
3073
- if (args.json) return json({ state, dirs: agentSkillDirs() });
3268
+ if (args.json) return json("agent-skill", { state, dirs: agentSkillDirs() });
3074
3269
  if (state === "installed") success(`Your agents can use skilllib ${dim(`(${agentSkillDirs().map((d) => tildify(d)).join(", ")})`)}`);
3075
3270
  else if (state === "outdated") warn("The skilllib skill is out of date. Run `skilllib agent-skill install` to update it.");
3076
3271
  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 +3297,6 @@ export {
3102
3297
  allowTrackedLinks,
3103
3298
  keptGlobal,
3104
3299
  setKeepGlobal,
3105
- setPluginEnabled,
3106
3300
  machineSkills,
3107
3301
  skillsLoadedIn,
3108
3302
  libraryOrigins,
@@ -3130,9 +3324,14 @@ export {
3130
3324
  gitInfo,
3131
3325
  relativeTo,
3132
3326
  claudePlugins,
3327
+ installedPlugins,
3328
+ cursorPlugins,
3329
+ forgetClaudeBinary,
3133
3330
  claudeBinary,
3134
3331
  turnOffIn,
3135
- recordRemovedPlugin,
3332
+ setPluginOn,
3333
+ uninstallPlugin,
3334
+ updatePlugin,
3136
3335
  pluginBackups,
3137
3336
  restorePlugin,
3138
3337
  cursorPluginSkills,