skilllib 1.3.1 → 1.6.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 +80 -26
- package/dist/{chunk-TWQQHXVG.js → chunk-PHRUPZGX.js} +916 -260
- package/dist/index.js +19 -4
- package/dist/{tui-H5IQAGAL.js → tui-WYVSDGHD.js} +185 -26
- package/package.json +2 -1
- package/skills/skilllib/SKILL.md +42 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
add,
|
|
4
|
+
agentSkill,
|
|
4
5
|
doctor,
|
|
5
6
|
error,
|
|
6
7
|
folders,
|
|
@@ -13,9 +14,11 @@ import {
|
|
|
13
14
|
outdated,
|
|
14
15
|
parseArgs,
|
|
15
16
|
projects,
|
|
17
|
+
refreshAgentSkill,
|
|
16
18
|
remove,
|
|
17
19
|
restore,
|
|
18
20
|
scan,
|
|
21
|
+
setJsonMode,
|
|
19
22
|
show,
|
|
20
23
|
status,
|
|
21
24
|
sync,
|
|
@@ -23,7 +26,7 @@ import {
|
|
|
23
26
|
unhide,
|
|
24
27
|
update,
|
|
25
28
|
usage
|
|
26
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-PHRUPZGX.js";
|
|
27
30
|
|
|
28
31
|
// src/index.ts
|
|
29
32
|
import { readFileSync } from "fs";
|
|
@@ -64,6 +67,13 @@ Health:
|
|
|
64
67
|
skilllib restore [name] Restore something skilllib moved out of the way
|
|
65
68
|
skilllib unhide <path> Show a hidden project again
|
|
66
69
|
|
|
70
|
+
Agents:
|
|
71
|
+
skilllib agent-skill [install|remove]
|
|
72
|
+
Teach your agents to use skilllib, so you can ask them
|
|
73
|
+
"which skills can you use here?" in any repo
|
|
74
|
+
--json Machine-readable output (status list show projects outdated
|
|
75
|
+
usage doctor add remove sync update import agent-skill)
|
|
76
|
+
|
|
67
77
|
Options: --force (overwrite local edits) --allow-tracked (link into git-tracked skill folders) --days N
|
|
68
78
|
`.trim();
|
|
69
79
|
function packageVersion() {
|
|
@@ -77,11 +87,14 @@ function packageVersion() {
|
|
|
77
87
|
async function main() {
|
|
78
88
|
const [command, ...rest] = process.argv.slice(2);
|
|
79
89
|
const args = parseArgs(rest);
|
|
90
|
+
setJsonMode(args.json);
|
|
91
|
+
if (!["--version", "-v", "version", "help", "--help", "-h"].includes(command ?? "")) refreshAgentSkill();
|
|
80
92
|
switch (command) {
|
|
81
93
|
case void 0:
|
|
82
|
-
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-
|
|
94
|
+
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-WYVSDGHD.js")).tui() : status(args);
|
|
83
95
|
case "ui":
|
|
84
|
-
|
|
96
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("`skilllib ui` needs an interactive terminal. Agents and scripts: use `skilllib status --json`.");
|
|
97
|
+
return (await import("./tui-WYVSDGHD.js")).tui();
|
|
85
98
|
case "status":
|
|
86
99
|
return status(args);
|
|
87
100
|
case "add":
|
|
@@ -107,7 +120,7 @@ async function main() {
|
|
|
107
120
|
case "scan":
|
|
108
121
|
return scan(args);
|
|
109
122
|
case "projects":
|
|
110
|
-
return projects();
|
|
123
|
+
return projects(args);
|
|
111
124
|
case "link":
|
|
112
125
|
return link(args);
|
|
113
126
|
case "tidy":
|
|
@@ -132,6 +145,8 @@ async function main() {
|
|
|
132
145
|
return doctor(args);
|
|
133
146
|
case "usage":
|
|
134
147
|
return usage(args);
|
|
148
|
+
case "agent-skill":
|
|
149
|
+
return agentSkill(args);
|
|
135
150
|
case "--version":
|
|
136
151
|
case "-v":
|
|
137
152
|
case "version":
|
|
@@ -3,15 +3,21 @@ import {
|
|
|
3
3
|
addRoot,
|
|
4
4
|
addSkill,
|
|
5
5
|
allowTrackedLinks,
|
|
6
|
+
applyTidy,
|
|
6
7
|
claudeBinary,
|
|
8
|
+
claudePlugins,
|
|
9
|
+
copyLabel,
|
|
7
10
|
createSkill,
|
|
11
|
+
cursorPluginSkills,
|
|
8
12
|
deleteGlobal,
|
|
9
13
|
deleteLibrarySkill,
|
|
14
|
+
describeStep,
|
|
10
15
|
discoverProjects,
|
|
11
16
|
enabledHarnesses,
|
|
12
17
|
findProjectRoot,
|
|
13
18
|
forgetLatest,
|
|
14
19
|
gitInfo,
|
|
20
|
+
gitVisibleSteps,
|
|
15
21
|
harness,
|
|
16
22
|
harnessesChosen,
|
|
17
23
|
importSkill,
|
|
@@ -22,8 +28,11 @@ import {
|
|
|
22
28
|
librarySkillDir,
|
|
23
29
|
librarySkills,
|
|
24
30
|
linkEverywhere,
|
|
31
|
+
linkGlobal,
|
|
25
32
|
listBackups,
|
|
26
33
|
machineSkills,
|
|
34
|
+
planGlobalTidy,
|
|
35
|
+
planProjectTidy,
|
|
27
36
|
pluginBackups,
|
|
28
37
|
projectOfFactory,
|
|
29
38
|
projectStatus,
|
|
@@ -45,13 +54,15 @@ import {
|
|
|
45
54
|
setKeepGlobal,
|
|
46
55
|
setPluginEnabled,
|
|
47
56
|
skilllibHome,
|
|
57
|
+
skillsLoadedIn,
|
|
48
58
|
syncProject,
|
|
59
|
+
turnOffIn,
|
|
49
60
|
unloadGlobal,
|
|
50
61
|
updateProject,
|
|
51
62
|
userHome,
|
|
52
63
|
versionHistory,
|
|
53
64
|
visibleProjects
|
|
54
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-PHRUPZGX.js";
|
|
55
66
|
|
|
56
67
|
// src/tui/index.tsx
|
|
57
68
|
import { render } from "ink";
|
|
@@ -299,17 +310,71 @@ function dailyUses(w, name, onlyRepo) {
|
|
|
299
310
|
}
|
|
300
311
|
return days;
|
|
301
312
|
}
|
|
313
|
+
function machineIn(w, p) {
|
|
314
|
+
return p.machine ?? w.machine;
|
|
315
|
+
}
|
|
302
316
|
function usable(w, projectName) {
|
|
303
317
|
const p = project(w, projectName);
|
|
304
318
|
const uses = (n) => w.usage[projectName]?.[n] ?? 0;
|
|
305
319
|
return [
|
|
306
320
|
...p.skills.map((s) => ({ name: s.name, source: s.source, where: s.dir, agents: s.agents, uses: uses(s.name), local: s })),
|
|
307
|
-
...w.
|
|
321
|
+
...machineIn(w, p).map((m) => ({ name: m.name, source: m.source, where: m.where, agents: m.agents, uses: uses(m.name), machine: m }))
|
|
308
322
|
];
|
|
309
323
|
}
|
|
310
324
|
function lib(w, name) {
|
|
311
325
|
return w.library.find((l) => l.name === name);
|
|
312
326
|
}
|
|
327
|
+
var agentNames = (ids) => ids.map((a) => harness(a).name).join(", ");
|
|
328
|
+
function dupeIssues(repo, name, d) {
|
|
329
|
+
const g = repo === null ? "-g" : "";
|
|
330
|
+
if (d.differ)
|
|
331
|
+
return [
|
|
332
|
+
{
|
|
333
|
+
id: `conflict${g}:${name}`,
|
|
334
|
+
severity: "problem",
|
|
335
|
+
title: `Copies differ: ${d.differ.map((c) => c.label + (c.runs.length ? ` (${agentNames(c.runs)})` : "")).join(" vs ")}`,
|
|
336
|
+
short: "Copies differ",
|
|
337
|
+
decision: true,
|
|
338
|
+
fixes: d.differ.filter((c) => c.canWin).map((c) => ({
|
|
339
|
+
label: `Keep the ${c.label} copy`,
|
|
340
|
+
preview: `The other ${d.differ.length === 2 ? "copy becomes a link" : "copies become links"} to it, so every agent runs this one. Replaced copies go to Settings \u203A Backups.${repo ? " If git tracks a copy that changes, skilllib asks first." : ""}`,
|
|
341
|
+
run: (w) => w.ops.tidy(repo, name, { keep: c.dir })
|
|
342
|
+
}))
|
|
343
|
+
}
|
|
344
|
+
];
|
|
345
|
+
return [
|
|
346
|
+
{
|
|
347
|
+
id: `copies${g}:${name}`,
|
|
348
|
+
severity: "warning",
|
|
349
|
+
title: repo ? "More copies or links in this repo than your agents need" : "Identical copies in several global folders",
|
|
350
|
+
short: "Extra copies",
|
|
351
|
+
decision: false,
|
|
352
|
+
fixes: [
|
|
353
|
+
{
|
|
354
|
+
label: "Keep one copy, plus the links your agents need",
|
|
355
|
+
preview: `${d.steps.join("; ")}. Replaced copies go to Settings \u203A Backups.${d.git.length ? ` Git would see ${d.git.join(", ")}: skilllib asks before ${d.git.length === 1 ? "that one" : "those"}.` : ""}`,
|
|
356
|
+
run: (w) => w.ops.tidy(repo, name)
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
function cursorPluginIssue(name, plugin) {
|
|
363
|
+
return {
|
|
364
|
+
id: `cursor-plugin:${name}`,
|
|
365
|
+
severity: "warning",
|
|
366
|
+
title: `Also in the Cursor plugin ${plugin}: Cursor lists both while it's on`,
|
|
367
|
+
short: "Also in a Cursor plugin",
|
|
368
|
+
decision: true,
|
|
369
|
+
fixes: [
|
|
370
|
+
{
|
|
371
|
+
label: "Turn the plugin off in Cursor (Settings \u203A Plugins)",
|
|
372
|
+
preview: "skilllib can't read or change Cursor's plugins. Your skill wins once the plugin is off.",
|
|
373
|
+
run: () => `Turn ${plugin} off in Cursor; skilllib can't tell whether it's on`
|
|
374
|
+
}
|
|
375
|
+
]
|
|
376
|
+
};
|
|
377
|
+
}
|
|
313
378
|
function globalFixes(m) {
|
|
314
379
|
return [
|
|
315
380
|
{
|
|
@@ -355,26 +420,38 @@ function machineIssues(w, m) {
|
|
|
355
420
|
}
|
|
356
421
|
]
|
|
357
422
|
});
|
|
358
|
-
|
|
359
|
-
if (m.source === "global" &&
|
|
423
|
+
if (m.source === "global" && m.dupes) issues.push(...dupeIssues(null, m.name, m.dupes));
|
|
424
|
+
if (m.source === "global" && m.cursorPlugin && m.agents.includes("cursor")) issues.push(cursorPluginIssue(m.name, m.cursorPlugin));
|
|
425
|
+
const seen = new Set(w.machine.filter((x) => x.name === m.name && !x.broken).flatMap((x) => x.agents));
|
|
426
|
+
const blind = m.source === "global" && m.agents.length && !m.dupes ? w.agents.filter((a) => !seen.has(a)) : [];
|
|
427
|
+
const names = blind.map((a) => harness(a).name).join(", ");
|
|
428
|
+
const link = {
|
|
429
|
+
label: `Link it for ${names}`,
|
|
430
|
+
preview: `Add a link in the global skills folder ${names} read${blind.length === 1 ? "s" : ""} (nothing is copied or moved), so ${names} load${blind.length === 1 ? "s" : ""} it in every repo too.`,
|
|
431
|
+
run: (w2) => w2.ops.linkGlobal(m, blind)
|
|
432
|
+
};
|
|
433
|
+
if (m.kept && blind.length)
|
|
434
|
+
issues.push({ id: `blind-global:${m.name}`, severity: "warning", title: `${names} can't see it: only ${m.agents.map((a) => harness(a).name).join(", ")} load${m.agents.length === 1 ? "s" : ""} it`, short: `${names} can't see it`, decision: false, fixes: [link] });
|
|
435
|
+
if (m.source === "global" && !m.kept) {
|
|
436
|
+
const [move, ...rest] = globalFixes(m);
|
|
360
437
|
issues.push({
|
|
361
|
-
id: `
|
|
362
|
-
severity: "
|
|
363
|
-
title: `
|
|
364
|
-
short: "
|
|
438
|
+
id: `global:${m.name}`,
|
|
439
|
+
severity: "warning",
|
|
440
|
+
title: blind.length ? `Global, not reviewed: loads in every repo, but ${names} can't see it` : "Global, not reviewed: loads in every repo",
|
|
441
|
+
short: blind.length ? `Not reviewed \xB7 ${names} can't see it` : "Not reviewed",
|
|
365
442
|
decision: true,
|
|
366
|
-
fixes:
|
|
443
|
+
fixes: blind.length ? [move, link, ...rest] : [move, ...rest]
|
|
367
444
|
});
|
|
368
|
-
|
|
369
|
-
issues.push({ id: `global:${m.name}`, severity: "warning", title: "Global, not reviewed: loads in every repo", short: "Not reviewed", decision: true, fixes: globalFixes(m) });
|
|
445
|
+
}
|
|
370
446
|
return issues;
|
|
371
447
|
}
|
|
372
448
|
function issuesOf(w, projectName, u) {
|
|
373
449
|
const s = u.local;
|
|
374
450
|
const issues = [];
|
|
375
451
|
const shares = (m) => m.name === s.name && !m.broken && m.agents.some((a) => s.agents.includes(a));
|
|
376
|
-
const
|
|
377
|
-
const
|
|
452
|
+
const loaded = machineIn(w, project(w, projectName));
|
|
453
|
+
const g = loaded.find((m) => m.source === "global" && shares(m));
|
|
454
|
+
const vendor = loaded.find((m) => m.source !== "global" && shares(m));
|
|
378
455
|
const l = lib(w, s.name);
|
|
379
456
|
const remove = {
|
|
380
457
|
label: "Remove this repo's copy",
|
|
@@ -399,7 +476,23 @@ function issuesOf(w, projectName, u) {
|
|
|
399
476
|
decision: false,
|
|
400
477
|
fixes: [{ label: "Copy it back into your library", preview: `Copy this repo's ${s.name} into your library, so it can be restored and shared again.`, run: (w2) => w2.ops.copyToLibrary(projectName, s.name) }]
|
|
401
478
|
});
|
|
402
|
-
if (g)
|
|
479
|
+
if (g?.kept)
|
|
480
|
+
issues.push({
|
|
481
|
+
id: `kept-g:${s.name}`,
|
|
482
|
+
severity: "warning",
|
|
483
|
+
title: `Also global in ${g.where}, and you keep it global: this repo's copy is extra`,
|
|
484
|
+
short: "Extra: you keep it global",
|
|
485
|
+
decision: true,
|
|
486
|
+
fixes: [
|
|
487
|
+
...s.source !== "repo" ? [{ ...remove, label: "Remove this repo's copy, keep it global" }] : [],
|
|
488
|
+
{
|
|
489
|
+
label: "Stop loading it globally after all",
|
|
490
|
+
preview: `Copy ${s.name} into your library if needed, then move ${g.path} to the backups. Other repos stop seeing it.`,
|
|
491
|
+
run: (w2) => w2.ops.unloadGlobal(g)
|
|
492
|
+
}
|
|
493
|
+
]
|
|
494
|
+
});
|
|
495
|
+
else if (g)
|
|
403
496
|
issues.push({
|
|
404
497
|
id: `twice-g:${s.name}`,
|
|
405
498
|
severity: "problem",
|
|
@@ -424,13 +517,19 @@ function issuesOf(w, projectName, u) {
|
|
|
424
517
|
decision: true,
|
|
425
518
|
fixes: [
|
|
426
519
|
...s.source !== "repo" ? [{ ...remove, label: `Remove this repo's copy, use the ${vendor.source}'s` }] : [],
|
|
427
|
-
{
|
|
428
|
-
label:
|
|
429
|
-
preview: "
|
|
520
|
+
vendor.source === "plugin" ? {
|
|
521
|
+
label: `Turn the plugin off in ${projectName} only`,
|
|
522
|
+
preview: `Sets "${vendor.where}": false in ${projectName}'s .claude/settings.local.json (claude plugin disable --scope local). Other repos keep it.`,
|
|
523
|
+
run: (w2) => w2.ops.pluginOffHere(projectName, vendor.where)
|
|
524
|
+
} : {
|
|
525
|
+
label: `Turn it off in ${vendor.where}`,
|
|
526
|
+
preview: "skilllib can't turn it off for you.",
|
|
430
527
|
run: () => `Turn ${vendor.where} off at its source; skilllib picks it up next time`
|
|
431
528
|
}
|
|
432
529
|
]
|
|
433
530
|
});
|
|
531
|
+
if (s.dupes) issues.push(...dupeIssues(projectName, s.name, s.dupes));
|
|
532
|
+
if (s.cursorPlugin && s.agents.includes("cursor")) issues.push(cursorPluginIssue(s.name, s.cursorPlugin));
|
|
434
533
|
if (s.source === "lib" && s.edited)
|
|
435
534
|
issues.push({
|
|
436
535
|
id: `edited:${s.name}`,
|
|
@@ -468,7 +567,8 @@ function issuesOf(w, projectName, u) {
|
|
|
468
567
|
}
|
|
469
568
|
]
|
|
470
569
|
});
|
|
471
|
-
|
|
570
|
+
const differ = !!s.dupes?.differ;
|
|
571
|
+
if (s.source !== "lib" && s.library === "differs" && !differ)
|
|
472
572
|
issues.push({
|
|
473
573
|
id: `differs:${s.name}`,
|
|
474
574
|
severity: "hint",
|
|
@@ -480,7 +580,7 @@ function issuesOf(w, projectName, u) {
|
|
|
480
580
|
...s.source === "untracked" ? [{ label: "Replace it with the library version", preview: `Replace ${s.dir}/${s.name} with your library's newest version and track it (this copy backed up).`, run: (w2) => w2.ops.discardEdits(projectName, s.name) }] : []
|
|
481
581
|
]
|
|
482
582
|
});
|
|
483
|
-
if (s.source === "untracked" && s.library === "same")
|
|
583
|
+
if (s.source === "untracked" && s.library === "same" && !differ)
|
|
484
584
|
issues.push({
|
|
485
585
|
id: `adopt:${s.name}`,
|
|
486
586
|
severity: "hint",
|
|
@@ -489,7 +589,7 @@ function issuesOf(w, projectName, u) {
|
|
|
489
589
|
decision: false,
|
|
490
590
|
fixes: [{ label: "Track it", preview: `Record ${s.name} in skilllib.json so library updates reach it.`, run: (w2) => w2.ops.track(projectName, s.name) }]
|
|
491
591
|
});
|
|
492
|
-
if (s.source === "untracked" && !l && !vendor)
|
|
592
|
+
if (s.source === "untracked" && !l && !vendor && !differ)
|
|
493
593
|
issues.push({
|
|
494
594
|
id: `local:${s.name}`,
|
|
495
595
|
severity: "hint",
|
|
@@ -589,6 +689,7 @@ function groupCandidates(w, u) {
|
|
|
589
689
|
if (m?.source === "plugin") return [{ key: `plugin:${m.where}`, label: `\u29C9 ${m.where.split("@")[0]} plugin` }];
|
|
590
690
|
if (m?.source === "claude.ai") return [{ key: "claude.ai", label: "claude.ai skills" }];
|
|
591
691
|
if (m?.source === "cursor") return [{ key: "cursor", label: "Cursor built-ins" }];
|
|
692
|
+
if (m?.source === "system") return [{ key: `system:${m.where}`, label: m.where }];
|
|
592
693
|
if (m) {
|
|
593
694
|
const origin = originGroup(m.origin);
|
|
594
695
|
return [...origin ? [origin] : [], ...byTime("global", m.installed), ...byWord("global", m.name)];
|
|
@@ -775,7 +876,7 @@ function TitleRow({ cells, width }) {
|
|
|
775
876
|
}
|
|
776
877
|
var fixOption = (fix) => ({ label: fix.label, fix });
|
|
777
878
|
var GIT_LABEL = { committed: "\u2713 committed", changed: "\xB1 uncommitted changes", new: "+ not committed", ignored: "\u2205 gitignored" };
|
|
778
|
-
var COPY_RANK = { lib: 0, repo: 0, untracked: 0, global: 1, plugin: 2, "claude.ai": 3, cursor: 3 };
|
|
879
|
+
var COPY_RANK = { lib: 0, repo: 0, untracked: 0, global: 1, plugin: 2, "claude.ai": 3, cursor: 3, system: 3 };
|
|
779
880
|
function byName(copies) {
|
|
780
881
|
const groups = /* @__PURE__ */ new Map();
|
|
781
882
|
for (const c of copies) groups.set(c.name, [...groups.get(c.name) ?? [], c]);
|
|
@@ -955,7 +1056,7 @@ function globalItems(w, nameW, ui) {
|
|
|
955
1056
|
}
|
|
956
1057
|
function globalActions(w, u, ui) {
|
|
957
1058
|
const m = u.machine;
|
|
958
|
-
const atSource = m.source === "plugin" ? "Turn it off with /plugin in Claude Code" : m.source === "claude.ai" ? "Turn it off in claude.ai \u203A Settings" : "Cursor manages it";
|
|
1059
|
+
const atSource = m.source === "plugin" ? "Turn it off with /plugin in Claude Code" : m.source === "claude.ai" ? "Turn it off in claude.ai \u203A Settings" : m.source === "system" ? `An admin manages ${m.where}` : "Cursor manages it";
|
|
959
1060
|
return [
|
|
960
1061
|
...machineActions(m).map(fixOption),
|
|
961
1062
|
...m.source === "plugin" ? [fixOption(replacePluginFix(w, m.where))] : [],
|
|
@@ -1903,7 +2004,7 @@ function machineSkill(s, kept) {
|
|
|
1903
2004
|
const global = s.kind === "global" || s.kind === "skills.sh";
|
|
1904
2005
|
return {
|
|
1905
2006
|
name: s.name,
|
|
1906
|
-
source: global ? "global" : s.kind === "plugin" ? "plugin" : s.kind === "claude.ai" ? "claude.ai" : "cursor",
|
|
2007
|
+
source: global ? "global" : s.kind === "plugin" ? "plugin" : s.kind === "claude.ai" ? "claude.ai" : s.kind === "system" ? "system" : "cursor",
|
|
1907
2008
|
where: global ? tildify3(dirname(s.path)) : s.kind === "claude.ai" ? "claude.ai account" : s.kind === "built-in" ? `${s.origin} built-in` : s.origin,
|
|
1908
2009
|
...s.kind === "skills.sh" ? { origin: `npx skills: ${s.origin}` } : {},
|
|
1909
2010
|
path: s.path,
|
|
@@ -1915,6 +2016,13 @@ function machineSkill(s, kept) {
|
|
|
1915
2016
|
...s.kind === "plugin" ? { pluginParts: pluginParts(dirname(dirname(s.path))) } : {}
|
|
1916
2017
|
};
|
|
1917
2018
|
}
|
|
2019
|
+
function dupesOf(r, root, git2 = null) {
|
|
2020
|
+
const out = /* @__PURE__ */ new Map();
|
|
2021
|
+
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)) });
|
|
2022
|
+
for (const c of r.conflicts)
|
|
2023
|
+
out.set(c.name, { steps: [], git: [], differ: c.copies.map((x) => ({ dir: x.dir, label: root ? copyLabel(c, x.dir) : tildify3(x.dir), runs: x.runs, canWin: !c.managed || x.dir === c.managed })) });
|
|
2024
|
+
return out;
|
|
2025
|
+
}
|
|
1918
2026
|
function loadWorld() {
|
|
1919
2027
|
forgetLatest();
|
|
1920
2028
|
const agents = enabledHarnesses();
|
|
@@ -1938,20 +2046,39 @@ function loadWorld() {
|
|
|
1938
2046
|
return { name: s.name, latest: latestVersion(s.name)?.version ?? 1, origin: origins[s.name] };
|
|
1939
2047
|
});
|
|
1940
2048
|
const kept = keptGlobal();
|
|
1941
|
-
const
|
|
2049
|
+
const cursorPlugin = new Map(agents.includes("cursor") ? cursorPluginSkills().map((s) => [s.name, s.plugin]) : []);
|
|
2050
|
+
const globalDupes = dupesOf(planGlobalTidy({ enabled: agents }), null);
|
|
2051
|
+
const sourced = machineSkills(agents);
|
|
2052
|
+
const machine = sourced.map((s) => {
|
|
1942
2053
|
descriptions[s.name] ??= s.description;
|
|
1943
|
-
|
|
2054
|
+
const m = machineSkill(s, kept);
|
|
2055
|
+
if (m.source !== "global" || m.broken) return m;
|
|
2056
|
+
const dupes = globalDupes.get(m.name);
|
|
2057
|
+
const plugin = cursorPlugin.get(m.name);
|
|
2058
|
+
return { ...m, ...dupes ? { dupes } : {}, ...plugin ? { cursorPlugin: plugin } : {} };
|
|
1944
2059
|
});
|
|
2060
|
+
const plugins = (root) => {
|
|
2061
|
+
const loaded = skillsLoadedIn(root, sourced, agents);
|
|
2062
|
+
if (loaded === sourced) return {};
|
|
2063
|
+
for (const s of loaded) descriptions[s.name] ??= s.description;
|
|
2064
|
+
return { machine: loaded.map((s) => machine[sourced.indexOf(s)] ?? machineSkill(s, kept)) };
|
|
2065
|
+
};
|
|
1945
2066
|
const projects = roots.map((root) => {
|
|
1946
2067
|
const status = projectStatus(root);
|
|
1947
2068
|
const hasManifest = existsSync3(join2(root, "skilllib.json"));
|
|
1948
2069
|
const git2 = status.length || hasManifest ? gitInfo(root) : null;
|
|
1949
2070
|
for (const s of status) if (!descriptions[s.name] && !s.state.includes("missing")) descriptions[s.name] = readSkillInfo(s.path).description;
|
|
2071
|
+
const dupes = status.length ? dupesOf(planProjectTidy(root, { enabled: agents }), root, git2) : /* @__PURE__ */ new Map();
|
|
1950
2072
|
return {
|
|
1951
2073
|
name: names.get(root),
|
|
1952
2074
|
path: root,
|
|
1953
2075
|
manifest: hasManifest ? git2 ? git2.of("skilllib.json") : "no git" : "none",
|
|
1954
|
-
skills: status.map((s) =>
|
|
2076
|
+
skills: status.map((s) => {
|
|
2077
|
+
const d = dupes.get(s.name);
|
|
2078
|
+
const plugin = cursorPlugin.get(s.name);
|
|
2079
|
+
return { ...localSkill(root, s, git2), ...d ? { dupes: d } : {}, ...plugin ? { cursorPlugin: plugin } : {} };
|
|
2080
|
+
}),
|
|
2081
|
+
...plugins(root)
|
|
1955
2082
|
};
|
|
1956
2083
|
});
|
|
1957
2084
|
const backups = [...listBackups(), ...pluginBackups()].sort((a, b) => b.movedAt.localeCompare(a.movedAt));
|
|
@@ -2065,6 +2192,24 @@ function realOps(roots, backups) {
|
|
|
2065
2192
|
return withLinks(addSkill(rootOf(repo), name), repo, name);
|
|
2066
2193
|
},
|
|
2067
2194
|
copyToLibrary: (repo, name) => toLibrary(find(repo, name).path),
|
|
2195
|
+
tidy: (repo, name, { keep, allowGit = false } = {}) => {
|
|
2196
|
+
const root = repo === null ? null : rootOf(repo);
|
|
2197
|
+
const opts = keep ? { keep: { [name]: keep } } : {};
|
|
2198
|
+
const plan = (root ? planProjectTidy(root, opts) : planGlobalTidy(opts)).plans.find((p) => p.name === name);
|
|
2199
|
+
if (!plan) return `${name}: nothing to tidy`;
|
|
2200
|
+
const r = applyTidy(plan, { git: allowGit ? "go" : "keep" });
|
|
2201
|
+
const steps = (list) => list.map((s) => describeStep(s, root)).join(", ");
|
|
2202
|
+
const message = r.applied.length ? `${name}: ${steps(r.applied)}` : `${name}: nothing changed yet`;
|
|
2203
|
+
if (!r.held.length) return message;
|
|
2204
|
+
return {
|
|
2205
|
+
message,
|
|
2206
|
+
then: {
|
|
2207
|
+
label: "Also change what git tracks",
|
|
2208
|
+
preview: `${steps(r.held)}: these changes will show in git status in ${repo}. Replaced copies go to Settings \u203A Backups.`,
|
|
2209
|
+
run: (w) => w.ops.tidy(repo, name, { keep, allowGit: true })
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
},
|
|
2068
2213
|
unloadGlobal: (m) => {
|
|
2069
2214
|
if (!m.broken && !latestVersion(m.name)) importSkill(m.path);
|
|
2070
2215
|
const r = unloadGlobal(m.path, m.links);
|
|
@@ -2075,6 +2220,12 @@ function realOps(roots, backups) {
|
|
|
2075
2220
|
return r.ok ? m.broken ? `Removed the broken link ${m.name}` : `Deleted ${m.name} (in Settings \u203A Backups)` : `${m.name}: ${r.reason}`;
|
|
2076
2221
|
},
|
|
2077
2222
|
keepGlobal: (name, keep) => (setKeepGlobal([name], keep), keep ? `${name} marked as global on purpose` : `${name} is no longer marked as global on purpose`),
|
|
2223
|
+
linkGlobal: (m, agents) => {
|
|
2224
|
+
const r = linkGlobal(m.path, agents);
|
|
2225
|
+
const linked = r.linked.map(tildify3).join(", ");
|
|
2226
|
+
const skipped = r.skipped.length ? `; ${r.skipped.map(tildify3).join(", ")} already holds a different skill` : "";
|
|
2227
|
+
return `${m.name}: ${linked ? `linked ${linked}` : "nothing linked"}${skipped}`;
|
|
2228
|
+
},
|
|
2078
2229
|
moveGlobal: (m, repos, group) => {
|
|
2079
2230
|
if (importSkill(m.path).status === "exists") return `${m.name}: your library has a different ${m.name}; nothing moved (update your library from this copy first)`;
|
|
2080
2231
|
if (group) recordOrigin(m.name, `group: ${group}`);
|
|
@@ -2091,6 +2242,14 @@ function realOps(roots, backups) {
|
|
|
2091
2242
|
const r = createSkill(name, "");
|
|
2092
2243
|
return r.ok ? `Created ${name} in your library: edit its SKILL.md from Your skills` : `${name}: ${r.reason}`;
|
|
2093
2244
|
},
|
|
2245
|
+
pluginOffHere: (repo, id) => {
|
|
2246
|
+
const root = rootOf(repo);
|
|
2247
|
+
const plugin = claudePlugins(skillsLoadedIn(root, machineSkills()), root).find((p) => p.id === id);
|
|
2248
|
+
if (!plugin) return `${id} isn't on in ${repo}`;
|
|
2249
|
+
const r = turnOffIn(plugin, root);
|
|
2250
|
+
if (!r.ok) throw new Error(r.message);
|
|
2251
|
+
return r.message;
|
|
2252
|
+
},
|
|
2094
2253
|
replacePlugin: (id, repos) => {
|
|
2095
2254
|
const skills = machineSkills().filter((s) => s.kind === "plugin" && s.origin === id && !s.broken);
|
|
2096
2255
|
const clashes = skills.filter((s) => importSkill(s.path).status === "exists").map((s) => s.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilllib",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Manage your agent skills like dependencies. One library for all your skills; each repo gets only the ones it needs, and every agent can see them. Works with Claude Code, Cursor, Codex and Zed.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
|
+
"skills",
|
|
35
36
|
"README.md",
|
|
36
37
|
"LICENSE"
|
|
37
38
|
],
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skilllib
|
|
3
|
+
description: Agent skills (SKILL.md) loaded in this repo and the user's skill library, via the skilllib CLI. Use for which skills you can use here and where each comes from, which library skills to add, what's out of date, broken or duplicated, and adding, removing or updating skills.
|
|
4
|
+
allowed-tools: Bash(skilllib:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# skilllib
|
|
8
|
+
|
|
9
|
+
The user manages agent skills with the `skilllib` CLI. Their **library** (`~/.skilllib/library`, "my skills", "the registry") holds versioned skills that load nowhere until added to a repo. A repo pins its skills in `skilllib.json`.
|
|
10
|
+
|
|
11
|
+
Always pass `--json` (one line of JSON on stdout; messages go to stderr). Never run bare `skilllib` or `skilllib ui`: that's an interactive app.
|
|
12
|
+
|
|
13
|
+
## Reading
|
|
14
|
+
|
|
15
|
+
The **Now** section at the end has this repo's `status` and `list` output. Answer from it and don't run them again. If it shows the commands instead of their output, run both in parallel.
|
|
16
|
+
|
|
17
|
+
`status --json`: `skills` are this repo's own; `global` groups load in every repo; `issues` are everything skilllib finds wrong (out-of-date or missing skills, broken links, skills loaded twice), with either a `fix` (a safe repair; `skilllib doctor --fix` applies every fix, across all the user's projects) or `choices` (decisions the user makes in `skilllib` → Health). When asked what's out of date or broken, `issues` and each skill's `state` are the answer. Don't audit skill contents unless asked.
|
|
18
|
+
|
|
19
|
+
- `source`: `library` (added with skilllib) · `repo` (committed to `.agents/skills` by the team) · `local` (unmanaged folder) · `global` (`~/.claude/skills`, `~/.agents/skills` or `~/.codex/skills`) · `skills.sh` (`npx skills add`; `from` is the source repo) · `plugin` (Claude Code plugin; turn off with `/plugin`) · `claude.ai` (synced from the account) · `built-in` (bundled with an agent).
|
|
20
|
+
- Missing fields mean the usual: loaded by all of `agents`, state ok, in `.claude/skills`. `usesHere` counts Claude Code uses in this repo.
|
|
21
|
+
|
|
22
|
+
`list --json`: `inThisProject` (names) and `skills`, the library skills the repo doesn't have, each with a short `description` and the `projects` using it. To recommend, read the repo's README and manifests (see `files` in `status`) in one parallel step, then give a reason for each pick. Don't add a skill that already loads globally (it would load twice): name it, and say `skilllib` → Global → Clean up moves it into just the repos that need it. `show <name> --json` has the full SKILL.md.
|
|
23
|
+
|
|
24
|
+
Also: `usage --json` (Claude Code usage only, so "unused" is a hint), `projects --json` (every repo), `outdated --all --json`.
|
|
25
|
+
|
|
26
|
+
## Changing (ask first unless the user asked for this exact change)
|
|
27
|
+
|
|
28
|
+
`add <name>...` · `remove <name>...` · `update [name...]` · `sync` (pinned versions) · `import <dir>` (into the library) · `doctor --fix`. Pass several names in one call. Each prints `[{name, action, from, to, dirs, reason, blocked}]`, which is the whole result: there's no need to check files afterwards.
|
|
29
|
+
|
|
30
|
+
- `skipped` + `reason`: nothing changed; tell the user. `--force` overwrites local edits, but only use it with their OK.
|
|
31
|
+
- `blocked`: git tracks that folder. Re-run with `--allow-tracked` only with their OK.
|
|
32
|
+
- Changes apply from the agent's next session. Remind the user to commit `skilllib.json` and `.claude/skills`.
|
|
33
|
+
|
|
34
|
+
To move global skills into repos, tell the user to run `skilllib` and open Global → Clean up.
|
|
35
|
+
|
|
36
|
+
## Now
|
|
37
|
+
|
|
38
|
+
This repo (`skilllib status --json`):
|
|
39
|
+
!`skilllib status --json`
|
|
40
|
+
|
|
41
|
+
Library skills it could add (`skilllib list --json`):
|
|
42
|
+
!`skilllib list --json`
|