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
|
@@ -12,11 +12,13 @@ import {
|
|
|
12
12
|
copyLabel,
|
|
13
13
|
createSkill,
|
|
14
14
|
cursorPluginSkills,
|
|
15
|
+
cursorPlugins,
|
|
15
16
|
deleteGlobal,
|
|
16
17
|
deleteLibrarySkill,
|
|
17
18
|
describeStep,
|
|
18
19
|
discoverProjects,
|
|
19
20
|
enabledHarnesses,
|
|
21
|
+
forgetClaudeBinary,
|
|
20
22
|
forgetLatest,
|
|
21
23
|
gitInfo,
|
|
22
24
|
gitVisibleSteps,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
harnessesChosen,
|
|
25
27
|
importSkill,
|
|
26
28
|
installAgentSkill,
|
|
29
|
+
installedPlugins,
|
|
27
30
|
keptGlobal,
|
|
28
31
|
latestVersion,
|
|
29
32
|
libraryOrigins,
|
|
@@ -44,7 +47,6 @@ import {
|
|
|
44
47
|
readManifest,
|
|
45
48
|
readSkillInfo,
|
|
46
49
|
recordOrigin,
|
|
47
|
-
recordRemovedPlugin,
|
|
48
50
|
relativeTo,
|
|
49
51
|
relinkDependency,
|
|
50
52
|
removeAgentSkill,
|
|
@@ -57,18 +59,20 @@ import {
|
|
|
57
59
|
setHarnesses,
|
|
58
60
|
setHidden,
|
|
59
61
|
setKeepGlobal,
|
|
60
|
-
|
|
62
|
+
setPluginOn,
|
|
61
63
|
skilllibHome,
|
|
62
64
|
skillsLoadedIn,
|
|
63
65
|
syncProject,
|
|
64
66
|
tildify,
|
|
65
67
|
turnOffIn,
|
|
68
|
+
uninstallPlugin,
|
|
66
69
|
unloadGlobal,
|
|
70
|
+
updatePlugin,
|
|
67
71
|
updateProject,
|
|
68
72
|
versionHistory,
|
|
69
73
|
visibilityOf,
|
|
70
74
|
visibleProjects
|
|
71
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-Z2F5W3DY.js";
|
|
72
76
|
|
|
73
77
|
// src/tui/index.tsx
|
|
74
78
|
import { render } from "ink";
|
|
@@ -240,7 +244,7 @@ function buildReviewPrompt(skills, ctx) {
|
|
|
240
244
|
- A skill is a folder with a SKILL.md. Every loaded skill's name and description sit in the agent's context in every session, so each one costs context and can trigger when it shouldn't.
|
|
241
245
|
- "Global" skills load in EVERY repo for the listed agents. Project skills only load inside that repo.
|
|
242
246
|
- I use these agents: ${ctx.harnesses.join(", ") || "(none configured)"}.
|
|
243
|
-
- I manage skills with skilllib, which can: keep a skill global; move it into specific projects (and out of global); delete it (a backup is kept); or leave vendor skills alone. Vendor skills (plugins, claude.ai, Cursor built-ins) can only be turned off at their source.
|
|
247
|
+
- I manage skills with skilllib, which can: keep a skill global; move it into specific projects (and out of global); delete it (a backup is kept); or leave vendor skills alone. Vendor skills (plugins, claude.ai, Cursor built-ins, Grok bundled skills) can only be turned off at their source.
|
|
244
248
|
- Usage counts only cover Claude Code sessions over the last ${ctx.usageDays} days; other agents' usage isn't tracked, so treat "unused" as a strong hint, not proof.
|
|
245
249
|
|
|
246
250
|
## My projects
|
|
@@ -543,7 +547,7 @@ function issuesOf(w, projectName, u) {
|
|
|
543
547
|
title: `Same name as a skill in plugin ${vendor.where}: Claude Code has it off in ${projectName}, but Cursor ignores repo settings and lists both`,
|
|
544
548
|
short: "Cursor also lists a plugin's copy",
|
|
545
549
|
decision: true,
|
|
546
|
-
fixes: [
|
|
550
|
+
fixes: [pluginFixFor(w, vendor.where)]
|
|
547
551
|
});
|
|
548
552
|
else if (vendor)
|
|
549
553
|
issues.push({
|
|
@@ -745,25 +749,117 @@ function deleteLibraryFix(w, names) {
|
|
|
745
749
|
run: (w2) => joined(names.map((n) => w2.ops.deleteLibrary(n)))
|
|
746
750
|
};
|
|
747
751
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
752
|
+
var plural = (n, what) => `${n} ${what}${n === 1 ? "" : "s"}`;
|
|
753
|
+
function pluginName(p) {
|
|
754
|
+
return p.id.split("@")[0];
|
|
755
|
+
}
|
|
756
|
+
function pluginSkillKey(p, skill) {
|
|
757
|
+
return `${pluginName(p)}:${skill}`;
|
|
758
|
+
}
|
|
759
|
+
function pluginUses(w, p, repo) {
|
|
760
|
+
return p.skills.reduce((n, s) => n + (repo ? w.usage[repo]?.[pluginSkillKey(p, s.name)] ?? 0 : totalUses(w, pluginSkillKey(p, s.name))), 0);
|
|
761
|
+
}
|
|
762
|
+
function pluginScope(p) {
|
|
763
|
+
return p.repo ? `${p.scope} ${p.repo}` : p.scope;
|
|
764
|
+
}
|
|
765
|
+
function replacePluginFix(p) {
|
|
766
|
+
const names = p.skills.map((s) => s.name);
|
|
767
|
+
const synced = p.scope === "claude.ai";
|
|
768
|
+
const offOnly = synced || !!p.settingsOnly;
|
|
753
769
|
return {
|
|
754
|
-
label: `Replace ${
|
|
770
|
+
label: `Replace ${pluginName(p)} with library skills\u2026`,
|
|
755
771
|
preview: [
|
|
756
|
-
`Copies its ${
|
|
757
|
-
synced ? `then turns ${id} off in Claude Code. It's synced from your claude.ai account: to delete it for good, remove it there.` : `then uninstalls ${id} from
|
|
758
|
-
parts.length ? `It also brings ${parts.join(", ")}: those ${
|
|
759
|
-
|
|
772
|
+
`Copies its ${plural(names.length, "skill")} into your library and adds them to the repos you tick (or none),`,
|
|
773
|
+
synced ? `then turns ${p.id} off in Claude Code. It's synced from your claude.ai account: to delete it for good, remove it there.` : p.settingsOnly ? `then turns ${p.id} off in ~/.claude/settings.json. It isn't installed, only turned on there, so there's nothing to uninstall.` : `then uninstalls ${p.id}${p.repo ? ` from ${p.repo}` : ""} (its saved data is kept).`,
|
|
774
|
+
p.parts.length ? `It also brings ${p.parts.join(", ")}: those ${offOnly ? "stop" : "go"} too.` : "",
|
|
775
|
+
offOnly ? "Turn it back on from Plugins." : "Settings \u203A Backups reinstalls it."
|
|
760
776
|
].filter(Boolean).join(" "),
|
|
761
777
|
run: () => "",
|
|
762
778
|
allowNone: true,
|
|
763
|
-
preticked: (
|
|
764
|
-
pickRepos: (
|
|
779
|
+
preticked: (w) => w.projects.filter((x) => pluginUses(w, p, x.name) > 0).map((x) => x.name),
|
|
780
|
+
pickRepos: (w, repos) => w.ops.replacePlugin(p, repos)
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function pluginFixFor(w, id) {
|
|
784
|
+
const ofId = w.plugins.filter((p2) => p2.agent === "claude-code" && p2.id === id);
|
|
785
|
+
const p = ofId.find((x) => !x.repo) ?? ofId[0];
|
|
786
|
+
if (p) return replacePluginFix(p);
|
|
787
|
+
return { label: "Turn the plugin off with /plugin in Claude Code", preview: `skilllib can't tell where ${id} is installed.`, run: () => `Turn ${id} off with /plugin in Claude Code; skilllib picks it up next time` };
|
|
788
|
+
}
|
|
789
|
+
function pluginSwitchFix(p, on) {
|
|
790
|
+
const where = p.repo ? ` in ${p.repo} (${p.scope} install)` : p.scope === "claude.ai" ? " on this machine" : " in every repo";
|
|
791
|
+
return {
|
|
792
|
+
label: on ? "Turn it on" : "Turn it off",
|
|
793
|
+
preview: `Turns ${p.id} ${on ? "on" : "off"}${where}${p.parts.length ? `, with its ${p.parts.join(", ")}` : ""}. ${p.scope === "claude.ai" ? `Sets "${p.id}": ${on} in ~/.claude/settings.json.` : `Runs claude plugin ${on ? "enable" : "disable"} ${p.id} --scope ${p.scope}${p.repo ? ` in ${p.repo}` : ""}.`} Claude Code picks it up in its next session.`,
|
|
794
|
+
run: (w) => w.ops.setPlugin(p, on)
|
|
765
795
|
};
|
|
766
796
|
}
|
|
797
|
+
function uninstallPluginFix(p) {
|
|
798
|
+
return {
|
|
799
|
+
label: "Uninstall",
|
|
800
|
+
preview: `Runs claude plugin uninstall ${p.id} --keep-data --scope ${p.scope}${p.repo ? ` in ${p.repo}` : ""}: its ${[plural(p.skills.length, "skill"), ...p.parts].join(", ")} go, its saved data stays. Settings \u203A Backups reinstalls it.`,
|
|
801
|
+
run: (w) => w.ops.uninstallPlugin(p)
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function updatePluginFix(p) {
|
|
805
|
+
return {
|
|
806
|
+
label: p.update === "newer" ? "Update it" : `Update to ${p.update}`,
|
|
807
|
+
preview: `Runs claude plugin update ${p.id}${p.repo ? ` in ${p.repo}` : ""}. Updates can bring new commands, hooks or MCP servers. If the marketplace asks to run a command, skilllib stops and you update it yourself. Claude Code uses the new version after a restart.`,
|
|
808
|
+
run: (w) => w.ops.updatePlugin(p)
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function pluginIssues(w, p) {
|
|
812
|
+
const library = new Set(w.library.map((l) => l.name));
|
|
813
|
+
const global = new Set(w.machine.filter((m) => m.source === "global" && !m.broken).map((m) => m.name));
|
|
814
|
+
if (p.agent === "cursor") {
|
|
815
|
+
const dupes = p.skills.filter((s) => library.has(s.name) || global.has(s.name));
|
|
816
|
+
return dupes.length ? [
|
|
817
|
+
{
|
|
818
|
+
id: `plugin-dupes:${p.key}`,
|
|
819
|
+
severity: "warning",
|
|
820
|
+
title: `Repeats your ${dupes.map((s) => s.name).join(", ")}: Cursor lists both while it's on`,
|
|
821
|
+
short: `Repeats ${dupes.length} of yours`,
|
|
822
|
+
decision: true,
|
|
823
|
+
fixes: [{ label: "Turn it off in Cursor (Settings \u203A Plugins)", preview: "skilllib can't read or change Cursor's plugins.", run: () => `Turn ${p.id} off in Cursor; skilllib can't tell whether it's on` }]
|
|
824
|
+
}
|
|
825
|
+
] : [];
|
|
826
|
+
}
|
|
827
|
+
const issues = [];
|
|
828
|
+
if (p.on) {
|
|
829
|
+
const repo = p.repo ? w.projects.find((x) => x.name === p.repo) : void 0;
|
|
830
|
+
const here = repo ? new Set(repo.skills.map((s) => s.name)) : void 0;
|
|
831
|
+
const yours = (n) => global.has(n) || (p.repo ? !!here?.has(n) : library.has(n));
|
|
832
|
+
const dupes = p.skills.filter((s) => yours(s.name));
|
|
833
|
+
if (dupes.length)
|
|
834
|
+
issues.push({
|
|
835
|
+
id: `plugin-dupes:${p.key}`,
|
|
836
|
+
severity: "warning",
|
|
837
|
+
title: `Repeats your ${dupes.map((s) => s.name).join(", ")}`,
|
|
838
|
+
short: `Repeats ${dupes.length} of yours`,
|
|
839
|
+
decision: true,
|
|
840
|
+
fixes: [replacePluginFix(p), pluginSwitchFix(p, false)]
|
|
841
|
+
});
|
|
842
|
+
if (w.days && p.skills.length && pluginUses(w, p) === 0)
|
|
843
|
+
issues.push({
|
|
844
|
+
id: `plugin-unused:${p.key}`,
|
|
845
|
+
severity: "hint",
|
|
846
|
+
title: `Its skills weren't used in 30 days (Claude Code)${p.parts.length ? `; it also brings ${p.parts.join(", ")}` : ""}`,
|
|
847
|
+
short: "Unused 30 days",
|
|
848
|
+
decision: true,
|
|
849
|
+
fixes: [pluginSwitchFix(p, false), ...p.scope === "claude.ai" || p.settingsOnly ? [] : [uninstallPluginFix(p)]]
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
if (p.update)
|
|
853
|
+
issues.push({
|
|
854
|
+
id: `plugin-update:${p.key}`,
|
|
855
|
+
severity: "hint",
|
|
856
|
+
title: p.update === "newer" ? "Its marketplace has a newer commit" : `Update available: ${p.version ?? "?"} \u2192 ${p.update}`,
|
|
857
|
+
short: p.update === "newer" ? "Update available" : `Update to ${p.update}`,
|
|
858
|
+
decision: true,
|
|
859
|
+
fixes: [updatePluginFix(p)]
|
|
860
|
+
});
|
|
861
|
+
return issues;
|
|
862
|
+
}
|
|
767
863
|
function originGroup(origin) {
|
|
768
864
|
if (!origin) return null;
|
|
769
865
|
const plugin = origin.match(/^plugin: (.+)$/);
|
|
@@ -788,6 +884,7 @@ function groupCandidates(w, u) {
|
|
|
788
884
|
if (m?.source === "plugin") return [{ key: `plugin:${m.where}`, label: `\u29C9 ${m.where.split("@")[0]} plugin` }];
|
|
789
885
|
if (m?.source === "claude.ai") return [{ key: "claude.ai", label: "claude.ai skills" }];
|
|
790
886
|
if (m?.source === "cursor") return [{ key: "cursor", label: "Cursor built-ins" }];
|
|
887
|
+
if (m?.source === "grok") return [{ key: "grok", label: "Grok bundled" }];
|
|
791
888
|
if (m?.source === "system") return [{ key: `system:${m.where}`, label: m.where }];
|
|
792
889
|
if (m?.source === "skilllib") return [];
|
|
793
890
|
if (m) {
|
|
@@ -918,6 +1015,7 @@ var PLACES = [
|
|
|
918
1015
|
["projects", "Projects"],
|
|
919
1016
|
["library", "Your skills"],
|
|
920
1017
|
["global", "Global"],
|
|
1018
|
+
["plugins", "Plugins"],
|
|
921
1019
|
["health", "Health"],
|
|
922
1020
|
["settings", "Settings"]
|
|
923
1021
|
];
|
|
@@ -996,7 +1094,7 @@ function TitleRow({ cells, width }) {
|
|
|
996
1094
|
}
|
|
997
1095
|
var fixOption = (fix) => ({ label: fix.label, fix });
|
|
998
1096
|
var GIT_LABEL = { committed: "\u2713 committed", changed: "\xB1 uncommitted changes", new: "+ not committed", ignored: "\u2205 gitignored" };
|
|
999
|
-
var COPY_RANK = { lib: 0, repo: 0, untracked: 0, global: 1, skilllib: 1, plugin: 2, "claude.ai": 3, cursor: 3, system: 3 };
|
|
1097
|
+
var COPY_RANK = { lib: 0, repo: 0, untracked: 0, global: 1, skilllib: 1, plugin: 2, "claude.ai": 3, cursor: 3, grok: 3, system: 3 };
|
|
1000
1098
|
function byName(copies) {
|
|
1001
1099
|
const groups = /* @__PURE__ */ new Map();
|
|
1002
1100
|
for (const c of copies) groups.set(c.name, [...groups.get(c.name) ?? [], c]);
|
|
@@ -1139,11 +1237,13 @@ function overview(w, p) {
|
|
|
1139
1237
|
const project2 = project(w, p);
|
|
1140
1238
|
const info = w.ops.repoInfo(p);
|
|
1141
1239
|
const manifest = project2.manifest === "none" ? ["no skilllib.json", color.faint] : project2.manifest === "no git" ? ["skilllib.json (not a git repo)", color.muted] : [`skilllib.json ${GIT_LABEL[project2.manifest]}`, project2.manifest === "committed" ? color.text : color.yellow];
|
|
1240
|
+
const plugins = [...new Set(machineIn(w, project2).filter((m) => m.source === "plugin").map((m) => m.where.split("@")[0]))];
|
|
1142
1241
|
return [
|
|
1143
1242
|
["Folder", [[tildify3(project2.path), color.text]]],
|
|
1144
1243
|
["Remote", [[info.remote ?? "none", info.remote ? color.blue : color.faint]]],
|
|
1145
1244
|
["Branch", [[info.branch ?? "\u2013", color.text], [info.dirty ? ` ${info.dirty} uncommitted` : "", color.yellow]]],
|
|
1146
|
-
["Manifest", [manifest]]
|
|
1245
|
+
["Manifest", [manifest]],
|
|
1246
|
+
["Plugins", [[plugins.length ? plugins.join(", ") : "none", plugins.length ? color.magenta : color.faint]]]
|
|
1147
1247
|
];
|
|
1148
1248
|
}
|
|
1149
1249
|
function InfoGrid({ rows, columns, width }) {
|
|
@@ -1185,13 +1285,21 @@ function globalItems(w, nameW, ui) {
|
|
|
1185
1285
|
const copies = w.machine.map((m) => ({ name: m.name, source: m.source, where: m.where, agents: m.agents, uses: totalUses(w, m.name), machine: m }));
|
|
1186
1286
|
return byName(copies).map((g) => skillItem(w, g, mergeIssues(g.map((c) => machineIssues(w, c.machine))), nameW, globalActions(w, g[0], ui), null));
|
|
1187
1287
|
}
|
|
1288
|
+
var AT_SOURCE = {
|
|
1289
|
+
global: null,
|
|
1290
|
+
plugin: null,
|
|
1291
|
+
skilllib: null,
|
|
1292
|
+
"claude.ai": "Turn it off in claude.ai \u203A Settings",
|
|
1293
|
+
cursor: "Cursor manages it",
|
|
1294
|
+
grok: "Ships with Grok; a same-named skill of yours overrides it"
|
|
1295
|
+
};
|
|
1188
1296
|
function globalActions(w, u, ui) {
|
|
1189
1297
|
const m = u.machine;
|
|
1190
|
-
const atSource = m.source === "
|
|
1298
|
+
const atSource = m.source === "system" ? `An admin manages ${m.where}` : AT_SOURCE[m.source];
|
|
1191
1299
|
return [
|
|
1192
1300
|
...machineActions(m).map(fixOption),
|
|
1193
|
-
...m.source === "plugin" ? [
|
|
1194
|
-
...
|
|
1301
|
+
...m.source === "plugin" ? [{ label: `Open ${m.where.split("@")[0]} in Plugins`, action: () => ui.openInPlugins(m.where) }] : [],
|
|
1302
|
+
...atSource ? [{ label: atSource, action: () => ui.notify(`${atSource}; skilllib picks up the change next time`) }] : [],
|
|
1195
1303
|
...m.source === "global" && !m.broken ? [{ label: "Edit SKILL.md", action: () => ui.edit(`${m.path}/SKILL.md`) }] : [],
|
|
1196
1304
|
reviewOption(w, m.name, ui)
|
|
1197
1305
|
];
|
|
@@ -1283,6 +1391,66 @@ function agentSkillItems(w, nameW) {
|
|
|
1283
1391
|
}
|
|
1284
1392
|
];
|
|
1285
1393
|
}
|
|
1394
|
+
var pluginW = (nameW) => Math.min(nameW, 17);
|
|
1395
|
+
var STATE = { on: ["on", color.green], off: ["off", color.faint], unknown: ["?", color.faint] };
|
|
1396
|
+
function pluginItems(w, skillW, ui, issuesOf2) {
|
|
1397
|
+
const nameW = pluginW(skillW);
|
|
1398
|
+
return w.plugins.map((p) => {
|
|
1399
|
+
const issues = issuesOf2.get(p.key) ?? [];
|
|
1400
|
+
const top = worst(issues);
|
|
1401
|
+
const cursor = p.agent === "cursor";
|
|
1402
|
+
const uses = cursor ? 0 : pluginUses(w, p);
|
|
1403
|
+
const [state, stateColor] = STATE[p.on === null ? "unknown" : p.on ? "on" : "off"];
|
|
1404
|
+
const n = p.skills.length;
|
|
1405
|
+
const name = pluginName(p);
|
|
1406
|
+
const actions = cursor ? [{ label: "Turn it off in Cursor (Settings \u203A Plugins)", action: () => ui.notify(`Turn ${p.id} off in Cursor; skilllib can't read or change Cursor's plugins`) }] : [
|
|
1407
|
+
fixOption(pluginSwitchFix(p, !p.on)),
|
|
1408
|
+
...p.update ? [fixOption(updatePluginFix(p))] : [],
|
|
1409
|
+
...n ? [fixOption(replacePluginFix(p))] : [],
|
|
1410
|
+
...p.scope === "claude.ai" || p.settingsOnly ? [] : [fixOption(uninstallPluginFix(p))]
|
|
1411
|
+
];
|
|
1412
|
+
return {
|
|
1413
|
+
key: p.key,
|
|
1414
|
+
name: p.id,
|
|
1415
|
+
plugin: p,
|
|
1416
|
+
issues,
|
|
1417
|
+
uses,
|
|
1418
|
+
cells: [
|
|
1419
|
+
{ text: top ? SEV[top.severity].icon : " ", width: 2, color: top ? SEV[top.severity].color : void 0 },
|
|
1420
|
+
{ text: clip(name, nameW), width: nameW, color: p.on === false ? color.muted : void 0 },
|
|
1421
|
+
{ text: harness(p.agent).icon, width: 3, color: harness(p.agent).color },
|
|
1422
|
+
{ text: clip(cursor ? "\u2013" : pluginScope(p), 19), width: 19, color: p.repo ? color.blue : color.muted },
|
|
1423
|
+
{ text: clip(`${n} skill${n === 1 ? "" : "s"}${p.parts.length ? ` +${p.parts.length}` : ""}`, 13), width: 13, color: color.muted },
|
|
1424
|
+
{ text: state.padEnd(4), width: 4, color: stateColor },
|
|
1425
|
+
{ text: cursor ? " \u2013 " : usesText(uses), width: 6, color: color.muted },
|
|
1426
|
+
{ text: " " + (top?.short ?? ""), grow: true, color: top ? SEV[top.severity].color : color.faint }
|
|
1427
|
+
],
|
|
1428
|
+
detail: (width) => {
|
|
1429
|
+
const b = builder(p.id);
|
|
1430
|
+
for (const l of wrap(p.description || "(no description)", width - 4, 2)) b.line([l, color.muted]);
|
|
1431
|
+
b.line();
|
|
1432
|
+
b.issues(issues);
|
|
1433
|
+
b.actions([...actions, { label: "Open its folder", action: () => ui.notify(said2(w.ops.openFolder(p.path))) }, {
|
|
1434
|
+
label: "Review prompt",
|
|
1435
|
+
action: () => ui.copy(
|
|
1436
|
+
reviewPrompt(w, p.skills.map((s) => s.name), `Review the ${harness(p.agent).name} plugin ${p.id}${p.description ? ` (${p.description})` : ""}: these are its skills${p.parts.length ? `, and it also brings ${p.parts.join(", ")}` : ""}. Should I keep it ${p.on === false ? "off" : "on"}, turn it ${p.on === false ? "on" : "off"}, uninstall it, or replace it with my own copies of the skills I use?`),
|
|
1437
|
+
`a review prompt for ${name}`
|
|
1438
|
+
)
|
|
1439
|
+
}], width);
|
|
1440
|
+
b.line();
|
|
1441
|
+
const label = (t) => [t.padEnd(12), color.muted];
|
|
1442
|
+
const uses2 = (n2) => n2 ? `${n2} use${n2 === 1 ? "" : "s"}` : "unused";
|
|
1443
|
+
const skills = p.skills.map((s) => cursor || !w.days ? s.name : `${s.name} (${uses2(totalUses(w, pluginSkillKey(p, s.name)))})`);
|
|
1444
|
+
wrap(skills.join(" \xB7 ") || "none", width - 16, 4).forEach((l, i) => b.line(label(i ? "" : "Skills"), [l, color.text]));
|
|
1445
|
+
b.line(label("Also brings"), [p.parts.join(", ") || "nothing else", p.parts.length ? color.text : color.faint]);
|
|
1446
|
+
const installed = cursor ? "in Cursor (whether it's on is only in Cursor)" : p.scope === "claude.ai" ? "synced from your claude.ai account" : p.repo ? `${p.scope} scope, in ${p.repo} only` : `user scope, in every repo`;
|
|
1447
|
+
b.line(label("Installed"), [installed, color.text], [p.version ? ` v${p.version.replace(/^v/, "")}` : "", color.muted], [p.id.includes("@") && p.scope !== "claude.ai" ? ` from ${p.id.split("@")[1]}` : "", color.faint]);
|
|
1448
|
+
b.line(label("Folder"), [tildify3(p.path), color.faint]);
|
|
1449
|
+
return b.d;
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1286
1454
|
function addCursor(rows, cursor) {
|
|
1287
1455
|
const i = Math.min(cursor, rows.length - 1);
|
|
1288
1456
|
return rows[i] && !rows[i].header ? i : Math.max(0, rows.findIndex((r) => !r.header));
|
|
@@ -1324,10 +1492,12 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1324
1492
|
const ui = {
|
|
1325
1493
|
notify: setToast,
|
|
1326
1494
|
openInGlobal,
|
|
1495
|
+
openInPlugins,
|
|
1327
1496
|
edit,
|
|
1328
1497
|
copy: (text, what) => setToast(said2(world.ops.copy(text, what))),
|
|
1329
1498
|
menu: (title, options) => setModal({ kind: "menu", title, options, cursor: 0 })
|
|
1330
1499
|
};
|
|
1500
|
+
const pluginIssues2 = new Map(world.plugins.map((p) => [p.key, pluginIssues(world, p)]));
|
|
1331
1501
|
const flagged = [
|
|
1332
1502
|
...world.projects.flatMap((p) => projectIssues(world, p.name).map((x) => ({ key: `${p.name}:${x.skill.name}`, issue: x.issue }))),
|
|
1333
1503
|
...world.machine.flatMap((m) => machineIssues(world, m).map((issue) => ({ key: `Global:${m.name}`, issue }))),
|
|
@@ -1336,7 +1506,7 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1336
1506
|
];
|
|
1337
1507
|
const healthCount = new Set(flagged.map((f) => f.key)).size;
|
|
1338
1508
|
const healthWorst = worst(flagged.map((f) => f.issue));
|
|
1339
|
-
const all = place === "projects" ? open ? projectSkillItems(world, open, nameW, ui) : projectItems(world) : place === "global" ? globalItems(world, nameW, ui) : place === "library" ? libraryItems(world, nameW, ui) : place === "health" ? healthItems(world, nameW, ui) : settingsItems();
|
|
1509
|
+
const all = place === "projects" ? open ? projectSkillItems(world, open, nameW, ui) : projectItems(world) : place === "global" ? globalItems(world, nameW, ui) : place === "library" ? libraryItems(world, nameW, ui) : place === "plugins" ? pluginItems(world, nameW, ui, pluginIssues2) : place === "health" ? healthItems(world, nameW, ui) : settingsItems();
|
|
1340
1510
|
const inChip = (i, c) => c === "all" || (c === "issues" ? i.issues.length > 0 : i.group === c);
|
|
1341
1511
|
let items = all.filter((i) => i.header || (!query || matches(i.name, query)) && inChip(i, chip));
|
|
1342
1512
|
const viewKey = [place, open, chip, query, used ? "usage" : ""].join("|");
|
|
@@ -1485,8 +1655,7 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1485
1655
|
] : [],
|
|
1486
1656
|
...libs.length ? [fixOption(deleteLibraryFix(world, libs.map((l) => l.name)))] : [],
|
|
1487
1657
|
...locals.length ? [fixOption({ label: `Remove all ${locals.length} from this repo`, preview: `Remove these ${n(locals.length)} from ${open}; your library keeps the tracked ones.`, run: (w) => (locals.forEach((s) => w.ops.remove(open, s.name)), `Removed ${n(locals.length)} from ${open}`) })] : [],
|
|
1488
|
-
...[...new Set(machines.filter((m) => m.source === "plugin").map((m) => m.where))].map((id) =>
|
|
1489
|
-
...machines.some((m) => m.source === "plugin") ? [{ label: "Turn the plugin off with /plugin in Claude Code", action: () => setToast("Turn it off with /plugin in Claude Code; skilllib picks it up next time") }] : [],
|
|
1658
|
+
...[...new Set(machines.filter((m) => m.source === "plugin").map((m) => m.where))].map((id) => ({ label: `Open ${id.split("@")[0]} in Plugins`, action: () => openInPlugins(id) })),
|
|
1490
1659
|
{ label: "Review prompt", action: () => ui.copy(reviewPrompt(world, names, `Review these related skills (${label}).`), `a review prompt for ${label}`) }
|
|
1491
1660
|
];
|
|
1492
1661
|
}
|
|
@@ -1579,6 +1748,10 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1579
1748
|
goPlace("global");
|
|
1580
1749
|
setQuery(name);
|
|
1581
1750
|
}
|
|
1751
|
+
function openInPlugins(id) {
|
|
1752
|
+
goPlace("plugins");
|
|
1753
|
+
setQuery(id);
|
|
1754
|
+
}
|
|
1582
1755
|
function settingsItems() {
|
|
1583
1756
|
const header = (key, text) => ({ key, name: "", header: true, issues: [], uses: 0, cells: [{ text }] });
|
|
1584
1757
|
const row = (key, text, sub, onEnter, c) => ({
|
|
@@ -1812,6 +1985,7 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1812
1985
|
const globalFlagged = world.machine.map((m) => [m.name, machineIssues(world, m)]).filter(([, issues]) => issues.length);
|
|
1813
1986
|
const globalIssues = new Set(globalFlagged.map(([name]) => name)).size;
|
|
1814
1987
|
const globalProblem = globalFlagged.some(([, issues]) => issues.some((x) => x.severity === "problem"));
|
|
1988
|
+
const pluginsWorst = worst([...pluginIssues2.values()].flat().filter((i) => i.severity !== "hint"));
|
|
1815
1989
|
const badge = (p) => {
|
|
1816
1990
|
const found = projectIssues(world, p);
|
|
1817
1991
|
const top = worst(found.map((x) => x.issue));
|
|
@@ -1833,6 +2007,9 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1833
2007
|
sideRow("global", "\u25C8 Global", place === "global", () => goPlace("global"), [
|
|
1834
2008
|
{ text: String(new Set(world.machine.map((m) => m.name)).size), width: 4, color: globalProblem ? color.red : globalIssues ? color.yellow : color.muted }
|
|
1835
2009
|
]),
|
|
2010
|
+
sideRow("plugins", "\u29C9 Plugins", place === "plugins", () => goPlace("plugins"), [
|
|
2011
|
+
{ text: String(world.plugins.length), width: 4, color: pluginsWorst ? SEV[pluginsWorst.severity].color : color.muted }
|
|
2012
|
+
]),
|
|
1836
2013
|
sideRow("health", "\u2713 Health", place === "health", () => goPlace("health"), [
|
|
1837
2014
|
{ text: String(healthCount), width: 4, color: healthWorst ? SEV[healthWorst.severity].color : color.green }
|
|
1838
2015
|
]),
|
|
@@ -1870,17 +2047,17 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1870
2047
|
}),
|
|
1871
2048
|
/* @__PURE__ */ jsx2(Text2, { color: color.accent, children: query ? ` \u2315 ${query}\u258F` : "" })
|
|
1872
2049
|
] }) });
|
|
1873
|
-
const columnTitles = dashboard || place === "global" ? titles([["", 2], ["Skill", nameW], ["Source", 14], [" Uses", 6], [" Issue", 0]]) : place === "projects" ? titles([["", 2], ["Project", 18], ["Skills", 10], ["Issues", 0]]) : place === "library" ? titles([["", 2], ["Skill", nameW], ["Ver", 5], ["Used in", 18], [" Uses", 6], [" Issue / description", 0]]) : place === "health" ? titles([["", 2], ["Skill", nameW], ["Where", 16], [" Issue", 0]]) : null;
|
|
2050
|
+
const columnTitles = dashboard || place === "global" ? titles([["", 2], ["Skill", nameW], ["Source", 14], [" Uses", 6], [" Issue", 0]]) : place === "projects" ? titles([["", 2], ["Project", 18], ["Skills", 10], ["Issues", 0]]) : place === "library" ? titles([["", 2], ["Skill", nameW], ["Ver", 5], ["Used in", 18], [" Uses", 6], [" Issue / description", 0]]) : place === "plugins" ? titles([["", 2], ["Plugin", pluginW(nameW)], ["", 3], ["Scope", 19], ["Brings", 13], ["On", 4], [" Uses", 6], [" Issue", 0]]) : place === "health" ? titles([["", 2], ["Skill", nameW], ["Where", 16], [" Issue", 0]]) : null;
|
|
1874
2051
|
const showChips = chips.length > 0;
|
|
1875
2052
|
const listHeader = /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1876
2053
|
showChips ? chipRow : null,
|
|
1877
2054
|
columnTitles ? /* @__PURE__ */ jsx2(TitleRow, { cells: columnTitles, width: columns }) : null
|
|
1878
2055
|
] });
|
|
1879
|
-
const overviewH = dashboard && !sidebar ?
|
|
2056
|
+
const overviewH = dashboard && !sidebar ? Math.ceil(overview(world, open).length / 2) + 2 : 0;
|
|
1880
2057
|
const listH = split ? Math.floor((bodyH - overviewH) / 2) : bodyH - overviewH;
|
|
1881
2058
|
const detailH = split ? bodyH - overviewH - listH : bodyH;
|
|
1882
2059
|
const listRows = items.map((i) => ({ key: i.key, cells: i.cells, header: i.header }));
|
|
1883
|
-
const listTitle = dashboard ? "Skills" : place === "projects" ? "Projects" : place === "library" ? "Your skills" : place === "global" ? "Loads everywhere" : place === "health" ? "To fix" : "Settings";
|
|
2060
|
+
const listTitle = dashboard ? "Skills" : place === "projects" ? "Projects" : place === "library" ? "Your skills" : place === "global" ? "Loads everywhere" : place === "plugins" ? "Plugins" : place === "health" ? "To fix" : "Settings";
|
|
1884
2061
|
const list = /* @__PURE__ */ jsx2(
|
|
1885
2062
|
ListPanel,
|
|
1886
2063
|
{
|
|
@@ -1944,6 +2121,7 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1944
2121
|
else if (modal?.kind === "repos") {
|
|
1945
2122
|
const repos = (modal.only ?? world.projects.map((p) => p.name)).filter((r) => !modal.query || matches(r, modal.query));
|
|
1946
2123
|
const name = current?.name ?? "";
|
|
2124
|
+
const usedHere = (r) => current?.plugin ? pluginUses(world, current.plugin, r) : world.usage[r]?.[name] ?? 0;
|
|
1947
2125
|
const pickerLines = wrap(modal.fix.preview, columns - 6, 4);
|
|
1948
2126
|
body = /* @__PURE__ */ jsx2(
|
|
1949
2127
|
ListPanel,
|
|
@@ -1964,7 +2142,7 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1964
2142
|
cells: [
|
|
1965
2143
|
{ text: modal.picked.has(r) ? "[x] " : "[ ] ", width: 4, color: modal.picked.has(r) ? color.green : color.faint },
|
|
1966
2144
|
{ text: r, width: 20 },
|
|
1967
|
-
{ text:
|
|
2145
|
+
{ text: usedHere(r) ? `used ${usedHere(r)}\xD7 here` : "", grow: true, color: color.faint }
|
|
1968
2146
|
]
|
|
1969
2147
|
}))
|
|
1970
2148
|
}
|
|
@@ -2100,7 +2278,7 @@ function Help({ width, height }) {
|
|
|
2100
2278
|
}
|
|
2101
2279
|
|
|
2102
2280
|
// src/tui/load.ts
|
|
2103
|
-
import { execFileSync, spawn
|
|
2281
|
+
import { execFileSync, spawn } from "child_process";
|
|
2104
2282
|
import { existsSync as existsSync3, lstatSync, realpathSync } from "fs";
|
|
2105
2283
|
import { basename, dirname, join as join2 } from "path";
|
|
2106
2284
|
function tildify4(p) {
|
|
@@ -2155,20 +2333,6 @@ function localSkill(root, s, git2) {
|
|
|
2155
2333
|
git: state
|
|
2156
2334
|
};
|
|
2157
2335
|
}
|
|
2158
|
-
var partsOf = /* @__PURE__ */ new Map();
|
|
2159
|
-
function pluginParts(root) {
|
|
2160
|
-
if (!partsOf.has(root))
|
|
2161
|
-
partsOf.set(
|
|
2162
|
-
root,
|
|
2163
|
-
[
|
|
2164
|
-
["commands", "commands"],
|
|
2165
|
-
["agents", "agents"],
|
|
2166
|
-
["hooks", "hooks"],
|
|
2167
|
-
[".mcp.json", "MCP servers"]
|
|
2168
|
-
].flatMap(([file, what]) => existsSync3(join2(root, file)) ? [what] : [])
|
|
2169
|
-
);
|
|
2170
|
-
return partsOf.get(root);
|
|
2171
|
-
}
|
|
2172
2336
|
function installedAt(path) {
|
|
2173
2337
|
try {
|
|
2174
2338
|
const st = lstatSync(path);
|
|
@@ -2182,7 +2346,7 @@ function machineSkill(s, kept) {
|
|
|
2182
2346
|
const global = s.kind === "global" || s.kind === "skills.sh";
|
|
2183
2347
|
return {
|
|
2184
2348
|
name: s.name,
|
|
2185
|
-
source: global ? "global" : s.kind === "plugin" ? "plugin" : s.kind === "claude.ai" ? "claude.ai" : s.kind === "system" ? "system" : s.kind === "skilllib" ? "skilllib" : "cursor",
|
|
2349
|
+
source: global ? "global" : s.kind === "plugin" ? "plugin" : s.kind === "claude.ai" ? "claude.ai" : s.kind === "system" ? "system" : s.kind === "skilllib" ? "skilllib" : s.kind === "built-in" && s.harnesses[0] === "grok" ? "grok" : "cursor",
|
|
2186
2350
|
where: global || s.kind === "skilllib" ? tildify4(dirname(s.path)) : s.kind === "claude.ai" ? "claude.ai account" : s.kind === "built-in" ? `${s.origin} built-in` : s.origin,
|
|
2187
2351
|
...s.kind === "skills.sh" ? { origin: `npx skills: ${s.origin}` } : {},
|
|
2188
2352
|
path: s.path,
|
|
@@ -2190,10 +2354,31 @@ function machineSkill(s, kept) {
|
|
|
2190
2354
|
agents: s.harnesses,
|
|
2191
2355
|
...global && kept.has(s.name) ? { kept: true } : {},
|
|
2192
2356
|
...s.broken ? { broken: true } : {},
|
|
2193
|
-
...global && s.kind !== "skills.sh" ? { installed: installedAt(s.path) } : {}
|
|
2194
|
-
|
|
2357
|
+
...global && s.kind !== "skills.sh" ? { installed: installedAt(s.path) } : {}
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
function pluginOf(p, repoOf) {
|
|
2361
|
+
const scope = p.agent === "cursor" ? "cursor" : p.synced ? "claude.ai" : p.scope ?? "user";
|
|
2362
|
+
const repo = p.projectPath ? repoOf(p.projectPath) ?? basename(p.projectPath) : void 0;
|
|
2363
|
+
return {
|
|
2364
|
+
key: [p.agent, p.id, scope, p.projectPath ?? ""].join("|"),
|
|
2365
|
+
id: p.id,
|
|
2366
|
+
agent: p.agent,
|
|
2367
|
+
scope,
|
|
2368
|
+
...repo ? { repo, projectPath: p.projectPath } : {},
|
|
2369
|
+
on: p.on,
|
|
2370
|
+
...p.version ? { version: p.version } : {},
|
|
2371
|
+
...p.update ? { update: p.update } : {},
|
|
2372
|
+
...p.settingsOnly ? { settingsOnly: true } : {},
|
|
2373
|
+
description: p.description,
|
|
2374
|
+
path: p.root,
|
|
2375
|
+
skills: p.skills.map((path) => ({ name: basename(path), path })),
|
|
2376
|
+
parts: p.extras
|
|
2195
2377
|
};
|
|
2196
2378
|
}
|
|
2379
|
+
function refOf(p) {
|
|
2380
|
+
return { id: p.id, scope: p.scope === "claude.ai" ? null : p.scope, ...p.projectPath ? { projectPath: p.projectPath } : {}, synced: p.scope === "claude.ai", ...p.settingsOnly ? { settingsOnly: true } : {} };
|
|
2381
|
+
}
|
|
2197
2382
|
function dupesOf(r, root, git2 = null) {
|
|
2198
2383
|
const out = /* @__PURE__ */ new Map();
|
|
2199
2384
|
for (const p of r.plans) out.set(p.name, { steps: p.steps.map((s) => describeStep(s, root)), git: gitVisibleSteps(p, git2).map((s) => describeStep(s, root)) });
|
|
@@ -2203,6 +2388,7 @@ function dupesOf(r, root, git2 = null) {
|
|
|
2203
2388
|
}
|
|
2204
2389
|
function loadWorld() {
|
|
2205
2390
|
forgetLatest();
|
|
2391
|
+
forgetClaudeBinary();
|
|
2206
2392
|
const agents = enabledHarnesses();
|
|
2207
2393
|
const here = projectHere();
|
|
2208
2394
|
const real = (p) => {
|
|
@@ -2223,7 +2409,8 @@ function loadWorld() {
|
|
|
2223
2409
|
return { name: s.name, latest: latestVersion(s.name)?.version ?? 1, origin: origins[s.name] };
|
|
2224
2410
|
});
|
|
2225
2411
|
const kept = keptGlobal();
|
|
2226
|
-
const
|
|
2412
|
+
const cursorList = agents.includes("cursor") ? cursorPlugins() : [];
|
|
2413
|
+
const cursorPlugin = new Map(cursorPluginSkills(cursorList).map((s) => [s.name, s.plugin]));
|
|
2227
2414
|
const globalDupes = dupesOf(planGlobalTidy({ enabled: agents }), null);
|
|
2228
2415
|
const sourced = machineSkills(agents);
|
|
2229
2416
|
const machine = sourced.map((s) => {
|
|
@@ -2234,7 +2421,7 @@ function loadWorld() {
|
|
|
2234
2421
|
const plugin = cursorPlugin.get(m.name);
|
|
2235
2422
|
return { ...m, ...dupes ? { dupes } : {}, ...plugin ? { cursorPlugin: plugin } : {} };
|
|
2236
2423
|
});
|
|
2237
|
-
const
|
|
2424
|
+
const loadedHere = (root) => {
|
|
2238
2425
|
const loaded = skillsLoadedIn(root, sourced, agents);
|
|
2239
2426
|
if (loaded === sourced) return {};
|
|
2240
2427
|
for (const s of loaded) descriptions[s.name] ??= s.description;
|
|
@@ -2255,15 +2442,18 @@ function loadWorld() {
|
|
|
2255
2442
|
const plugin = cursorPlugin.get(s.name);
|
|
2256
2443
|
return { ...localSkill(root, s, git2), ...d ? { dupes: d } : {}, ...plugin ? { cursorPlugin: plugin } : {} };
|
|
2257
2444
|
}),
|
|
2258
|
-
...
|
|
2445
|
+
...loadedHere(root)
|
|
2259
2446
|
};
|
|
2260
2447
|
});
|
|
2261
2448
|
const backups = [...listBackups(), ...pluginBackups()].sort((a, b) => b.movedAt.localeCompare(a.movedAt));
|
|
2449
|
+
const repoOf = (path) => projects.find((p) => real(p.path) === real(path))?.name;
|
|
2450
|
+
const plugins = [...installedPlugins(), ...cursorList].map((p) => pluginOf(p, repoOf));
|
|
2262
2451
|
return {
|
|
2263
2452
|
agents,
|
|
2264
2453
|
cwd: cwd ? names.get(cwd) : null,
|
|
2265
2454
|
projects,
|
|
2266
2455
|
machine,
|
|
2456
|
+
plugins,
|
|
2267
2457
|
library,
|
|
2268
2458
|
usage: {},
|
|
2269
2459
|
descriptions,
|
|
@@ -2325,6 +2515,9 @@ function realOps(roots, backups) {
|
|
|
2325
2515
|
};
|
|
2326
2516
|
const addTo = (repos, names) => {
|
|
2327
2517
|
if (repos.length === 1 && names.length === 1) return withLinks(addSkill(rootOf(repos[0]), names[0]), repos[0], names[0]);
|
|
2518
|
+
return addMany(repos, names).result;
|
|
2519
|
+
};
|
|
2520
|
+
const addMany = (repos, names) => {
|
|
2328
2521
|
const changes = repos.flatMap((repo) => names.map((name) => ({ repo, name, c: addSkill(rootOf(repo), name) })));
|
|
2329
2522
|
const done = changes.filter((x) => x.c.action !== "skipped");
|
|
2330
2523
|
const added = [...new Set(done.map((x) => x.name))];
|
|
@@ -2343,9 +2536,8 @@ function realOps(roots, backups) {
|
|
|
2343
2536
|
const skipped = changes.filter((x) => x.c.action === "skipped").map((x) => `${x.name} not added to ${x.repo}: ${x.c.reason}`);
|
|
2344
2537
|
const text = [summary, ...notes, ...skipped].filter(Boolean).join("; ");
|
|
2345
2538
|
const held = done.flatMap((x) => x.c.blocked?.length ? [{ repo: x.repo, name: x.name, blocked: x.c.blocked }] : []);
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
return skipped.length ? failed(text) : text;
|
|
2539
|
+
const result2 = !done.length ? failed(text) : held.length ? { message: text, then: allowTracked(held) } : skipped.length ? failed(text) : text;
|
|
2540
|
+
return { result: result2, skipped, notes };
|
|
2349
2541
|
};
|
|
2350
2542
|
const allowTracked = (held) => {
|
|
2351
2543
|
const repos = [...new Set(held.map((h) => h.repo))];
|
|
@@ -2519,33 +2711,44 @@ function realOps(roots, backups) {
|
|
|
2519
2711
|
const r = turnOffIn(plugin, root);
|
|
2520
2712
|
return r.ok ? r.message : failed(r.message);
|
|
2521
2713
|
},
|
|
2522
|
-
replacePlugin: (
|
|
2523
|
-
const
|
|
2524
|
-
const
|
|
2714
|
+
replacePlugin: (p, repos) => {
|
|
2715
|
+
const id = p.id;
|
|
2716
|
+
const imported = p.skills.map((s) => ({ name: s.name, status: importSkill(s.path).status }));
|
|
2717
|
+
const clashes = imported.filter((r) => r.status === "exists").map((r) => r.name);
|
|
2525
2718
|
if (clashes.length) return failed(`${id} kept: your library has different ${clashes.join(", ")}; update or delete ${clashes.length === 1 ? "it" : "them"} first`);
|
|
2526
|
-
const
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
const
|
|
2719
|
+
for (const r of imported) if (r.status !== "unchanged") recordOrigin(r.name, `plugin: ${id}`);
|
|
2720
|
+
const added = repos.length ? addMany(repos, p.skills.map((s) => s.name)) : { result: "", skipped: [], notes: [] };
|
|
2721
|
+
if (added.skipped.length) return failed(`${id} kept: ${added.skipped.join("; ")}`);
|
|
2722
|
+
const n = p.skills.length;
|
|
2723
|
+
const copied = `${n} skill${n === 1 ? " from " + id + " is" : "s from " + id + " are"} in your library${repos.length ? ` and in ${repos.join(", ")}` : ""}`;
|
|
2724
|
+
const notes = added.notes.length ? `; ${added.notes.join("; ")}` : "";
|
|
2725
|
+
const then = followUp(added.result);
|
|
2726
|
+
const done = (message) => then ? { message: message + notes, then } : message + notes;
|
|
2727
|
+
const ref = refOf(p);
|
|
2532
2728
|
const bin = claudeBinary();
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
return `${copied}; ${id} is uninstalled (restore it from Settings \u203A Backups, or reinstall with /plugin)`;
|
|
2539
|
-
}
|
|
2540
|
-
if (off?.status === 0)
|
|
2541
|
-
return synced ? `${copied}; ${id} is off (it's synced from claude.ai: remove it there to delete it for good)` : `${copied}; ${id} is off: finish with /plugin uninstall ${id}`;
|
|
2542
|
-
const why = !off ? "the claude command isn't on your PATH" : off.error ? `claude didn't answer: ${off.error.message}` : (off.stderr || off.stdout).trim().split("\n")[0] || `exit ${off.status}`;
|
|
2543
|
-
try {
|
|
2544
|
-
setPluginEnabled(id, false);
|
|
2545
|
-
} catch (e) {
|
|
2546
|
-
return failed(`${copied}; couldn't turn ${id} off (${why}; ${e.message}): turn it off with /plugin`);
|
|
2729
|
+
let uninstallFailed = "";
|
|
2730
|
+
if (bin && !ref.synced && !ref.settingsOnly) {
|
|
2731
|
+
const r = uninstallPlugin(ref);
|
|
2732
|
+
if (r.ok) return done(`${copied}; ${r.message}`);
|
|
2733
|
+
uninstallFailed = ` (uninstalling failed: ${r.message.replace(`${id}: `, "")})`;
|
|
2547
2734
|
}
|
|
2548
|
-
|
|
2735
|
+
const off = setPluginOn(ref, false);
|
|
2736
|
+
if (!off.ok) return failed(`${copied}; couldn't turn ${id} off (${off.message}): turn it off with /plugin${notes}`);
|
|
2737
|
+
if (ref.synced) return done(`${copied}; ${id} is off (it's synced from claude.ai: remove it there to delete it for good)`);
|
|
2738
|
+
if (ref.settingsOnly) return done(`${copied}; ${id} is off (it was only turned on in your settings, so there's nothing to uninstall)`);
|
|
2739
|
+
return done(`${copied}; ${id} is off${bin ? uninstallFailed : " (the claude command isn't on your PATH)"}: finish with /plugin uninstall ${id}`);
|
|
2740
|
+
},
|
|
2741
|
+
setPlugin: (p, on) => {
|
|
2742
|
+
const r = setPluginOn(refOf(p), on);
|
|
2743
|
+
return r.ok ? r.message : failed(r.message);
|
|
2744
|
+
},
|
|
2745
|
+
uninstallPlugin: (p) => {
|
|
2746
|
+
const r = uninstallPlugin(refOf(p));
|
|
2747
|
+
return r.ok ? r.message : failed(r.message);
|
|
2748
|
+
},
|
|
2749
|
+
updatePlugin: (p) => {
|
|
2750
|
+
const r = updatePlugin(refOf(p));
|
|
2751
|
+
return r.ok ? r.message : failed(r.message);
|
|
2549
2752
|
},
|
|
2550
2753
|
deleteLibrary: (name) => {
|
|
2551
2754
|
const from = [];
|
|
@@ -2584,7 +2787,7 @@ function realOps(roots, backups) {
|
|
|
2584
2787
|
},
|
|
2585
2788
|
copy: (text, what) => {
|
|
2586
2789
|
const r = copyText(text);
|
|
2587
|
-
return r.copied ? `Copied ${what}: paste it into Claude Code, Cursor or
|
|
2790
|
+
return r.copied ? `Copied ${what}: paste it into Claude Code, Cursor, Codex or Grok` : `Couldn't reach the clipboard; ${what} is in ${tildify4(r.savedTo)}`;
|
|
2588
2791
|
},
|
|
2589
2792
|
installAgentSkill: () => {
|
|
2590
2793
|
const r = installAgentSkill();
|