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.
- package/README.md +74 -24
- package/dist/{chunk-AYAN7Q2X.js → chunk-Z2F5W3DY.js} +316 -117
- package/dist/index.js +5 -5
- package/dist/json.d.cts +200 -0
- package/dist/json.d.ts +200 -0
- package/dist/{tui-RU255WYX.js → tui-7CMGG6HX.js} +281 -78
- package/package.json +16 -3
|
@@ -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
|
|
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
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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 ?
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1419
|
-
|
|
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
|
-
|
|
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(
|
|
1481
|
-
const entries = entriesOf(
|
|
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 =
|
|
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 ?
|
|
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
|
|
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${
|
|
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:
|
|
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
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
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,
|
|
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
|
-
|
|
2428
|
+
nested: nested.length > 0 ? nested : void 0,
|
|
2245
2429
|
global,
|
|
2246
2430
|
issues: [...issues.values()],
|
|
2247
|
-
|
|
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
|
|
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),
|
|
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(
|
|
2264
|
-
return { inThisProject: all.filter((s) => here.has(s.name)).map((s) => s.name), skills: all.filter((s) => !here.has(s.name)).map(
|
|
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
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
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
|
-
|
|
3332
|
+
setPluginOn,
|
|
3333
|
+
uninstallPlugin,
|
|
3334
|
+
updatePlugin,
|
|
3136
3335
|
pluginBackups,
|
|
3137
3336
|
restorePlugin,
|
|
3138
3337
|
cursorPluginSkills,
|