skilllib 1.0.2 → 1.1.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
CHANGED
|
@@ -47,6 +47,7 @@ Most agents can't turn off a global skill for just one repo. Claude Code can, bu
|
|
|
47
47
|
- **📦 Per-repo installs, pinned like dependencies.** Each repo lists its skills and versions in `skilllib.json`. `sync` reproduces them exactly, and `update` moves to the newest.
|
|
48
48
|
- **🤝 One copy, every agent.** skilllib puts one real copy in the repo and links it into whichever folders your other agents read.
|
|
49
49
|
- **🧹 A cleanup wizard for global skills.** For each global skill, pick the repos that should keep it, keep it global, or delete it. Usage numbers and descriptions help you decide.
|
|
50
|
+
- **✓ Global on purpose.** Some skills belong everywhere. Mark them once and skilllib stops warning about them.
|
|
50
51
|
- **🔎 See everything in a repo:**
|
|
51
52
|
- where each skill comes from (the repo, your library, global, a plugin, claude.ai…)
|
|
52
53
|
- which agents load it
|
|
@@ -119,7 +120,8 @@ A repo has two lists:
|
|
|
119
120
|
|---|---|---|
|
|
120
121
|
| *From your skills* | Versioned skills you added from your library | Remove it from this repo |
|
|
121
122
|
| *The repo's own* | Skills committed to the repo (e.g. your team's `.agents/skills`) | Copy it into your skills, so other repos can use it |
|
|
122
|
-
| *⚠ Global · yours* | Your global skills: they load in every repo | Open the cleanup wizard |
|
|
123
|
+
| *⚠ Global · yours, not reviewed* | Your global skills you haven't decided about: they load in every repo | Open the cleanup wizard |
|
|
124
|
+
| *✓ Global · yours, on purpose* | Your global skills you chose to keep global | Open the cleanup wizard |
|
|
123
125
|
| *Global · from vendors* | Plugins, claude.ai skills, Cursor built-ins | – (manage them at the source) |
|
|
124
126
|
|
|
125
127
|
**Add skills** shows your skills that this repo doesn't have yet. `space` adds one.
|
|
@@ -171,13 +173,23 @@ Edit a skill once in **Your skills** (Enter → *Edit SKILL.md*). Every repo tha
|
|
|
171
173
|
Open **Global → ⚠ Clean up…**, or press `space` on any of your global skills. For each skill, `space` cycles through:
|
|
172
174
|
|
|
173
175
|
- **◉ move:** installed into the repos you tick on the right. Repos that already have it, or where Claude Code used it, are pre-ticked.
|
|
174
|
-
- **○ keep global
|
|
176
|
+
- **○ keep global**: skilllib remembers this choice, and the next cleanup skips the skill
|
|
175
177
|
- **✕ delete** (`d`): removed without keeping a copy in your library
|
|
176
178
|
|
|
177
179
|
Each skill shows how often Claude Code used it, and the panel below shows its description and where it was used. Review, apply, and your globals are gone: moved ones are in your library and in the right repos. Originals go to a backup you can restore from **Health**.
|
|
178
180
|
|
|
179
181
|
You can also delete one skill (Enter → **Delete**) or a whole group (Enter → **Delete all N**) straight from Global.
|
|
180
182
|
|
|
183
|
+
### Keeping a skill global on purpose
|
|
184
|
+
|
|
185
|
+
Some skills are useful in every repo, for example a commit-message skill. Mark them so skilllib stops asking about them:
|
|
186
|
+
|
|
187
|
+
- Enter on the skill (in Global or in a repo) → **✓ Keep global on purpose**. Enter on a group → **Keep all N global on purpose**.
|
|
188
|
+
- In the cleanup wizard, every skill you leave on **○ keep global** is marked when you apply.
|
|
189
|
+
- From the command line: `skilllib global keep <name>`.
|
|
190
|
+
|
|
191
|
+
A marked skill shows **✓ global** instead of **⚠ loaded globally**, and has its own group in a repo. The cleanup wizard hides it (`k` shows it again), and review prompts tell the agent that you chose to keep it global. The mark only records your decision: no files move, and agents load the skill as before. Duplicate warnings (⧉) still show. To undo, use Enter → **Unmark** or `skilllib global unkeep <name>`.
|
|
192
|
+
|
|
181
193
|
### Ask an agent to review your skills
|
|
182
194
|
|
|
183
195
|
Not sure what to keep? **Copy review prompt** puts a ready-to-paste prompt on your clipboard (also saved to `~/.skilllib/review-prompt.md`). For each skill, it includes:
|
|
@@ -277,6 +289,7 @@ skilllib link [--all] make every skill here usable by all your agents
|
|
|
277
289
|
|
|
278
290
|
skilllib list | show <name> your library
|
|
279
291
|
skilllib import <dir>... add skill folders to your library (--global: all your global skills)
|
|
292
|
+
skilllib global [keep|unkeep] your global skills; mark the ones you keep global on purpose
|
|
280
293
|
skilllib new <name> [desc] create a skill
|
|
281
294
|
|
|
282
295
|
skilllib usage [--days N] which skills Claude Code used
|
|
@@ -294,7 +307,7 @@ Flags: `--force` (overwrite local edits), `--allow-tracked` (link into git-track
|
|
|
294
307
|
| `<repo>/skilllib.json` | The repo's skills and pinned versions. **Commit it.** |
|
|
295
308
|
| `~/.skilllib/library/` | Your skills (master copies). Worth putting under git. |
|
|
296
309
|
| `~/.skilllib/store/` | Every version of every skill, immutable |
|
|
297
|
-
| `~/.skilllib/config.json` | Your agents, project folders, hidden projects |
|
|
310
|
+
| `~/.skilllib/config.json` | Your agents, project folders, hidden projects, skills you keep global |
|
|
298
311
|
| `~/.skilllib/trash/`, `global-backup/` | Everything skilllib removed, restorable from Health |
|
|
299
312
|
| `~/.skilllib/review-prompt.md` | The last review prompt you copied |
|
|
300
313
|
|
|
@@ -328,7 +341,7 @@ npm link # use your checkout as `skilllib`
|
|
|
328
341
|
1. Bump `version` in `package.json` and merge to `main`.
|
|
329
342
|
2. Tag and push:
|
|
330
343
|
```bash
|
|
331
|
-
git tag v1.0
|
|
344
|
+
git tag v1.1.0 && git push --tags
|
|
332
345
|
```
|
|
333
346
|
|
|
334
347
|
CI then tests on Linux, macOS and Windows, publishes to npm through [trusted publishing](https://docs.npmjs.com/trusted-publishers) (no token; provenance is automatic), and creates a GitHub release.
|
|
@@ -373,6 +373,19 @@ function allowTrackedLinks(root) {
|
|
|
373
373
|
const config = readConfig();
|
|
374
374
|
writeConfig({ ...config, agentsDirOk: [.../* @__PURE__ */ new Set([...config.agentsDirOk ?? [], root])].sort() });
|
|
375
375
|
}
|
|
376
|
+
function keptGlobal() {
|
|
377
|
+
return new Set(readConfig().keepGlobal ?? []);
|
|
378
|
+
}
|
|
379
|
+
function setKeepGlobal(names, keep) {
|
|
380
|
+
const kept = keptGlobal();
|
|
381
|
+
const before = kept.size;
|
|
382
|
+
for (const n of names) {
|
|
383
|
+
if (keep) kept.add(n);
|
|
384
|
+
else kept.delete(n);
|
|
385
|
+
}
|
|
386
|
+
if (kept.size === before) return;
|
|
387
|
+
writeConfig({ ...readConfig(), keepGlobal: [...kept].sort() });
|
|
388
|
+
}
|
|
376
389
|
|
|
377
390
|
// src/sources.ts
|
|
378
391
|
import { existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -849,7 +862,9 @@ function unloadGlobal(path, links = [], { requireLibrary = true } = {}) {
|
|
|
849
862
|
const broken = !existsSync7(join8(path, "SKILL.md"));
|
|
850
863
|
if (requireLibrary && !broken && latestVersion(name) === null) return { ok: false, reason: "import it into the library first" };
|
|
851
864
|
for (const link2 of links) if (isLink(link2)) stash(link2, "global-backup");
|
|
852
|
-
|
|
865
|
+
const movedTo = stash(path, "global-backup");
|
|
866
|
+
if (!globalSkillDirs().some((d) => entryExists(join8(d, name)))) setKeepGlobal([name], false);
|
|
867
|
+
return { ok: true, movedTo };
|
|
853
868
|
}
|
|
854
869
|
function createSkill(name, description) {
|
|
855
870
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) return { ok: false, reason: "use lowercase letters, digits and dashes" };
|
|
@@ -1498,6 +1513,28 @@ function link(args) {
|
|
|
1498
1513
|
if (res.blocked.length) warn(`${label}git tracks ${res.blocked.join(", ")}; re-run with --allow-tracked to link there too`);
|
|
1499
1514
|
}
|
|
1500
1515
|
}
|
|
1516
|
+
function globalCommand(args) {
|
|
1517
|
+
const [action, ...names] = args.positional;
|
|
1518
|
+
const yours = machineSkills().filter((m) => m.movable && !m.broken);
|
|
1519
|
+
if (action === "keep" || action === "unkeep") {
|
|
1520
|
+
const unknown = names.filter((n) => !yours.some((m) => m.name === n));
|
|
1521
|
+
if (!names.length || unknown.length) {
|
|
1522
|
+
error(names.length ? `Not one of your global skills: ${unknown.join(", ")}` : `Usage: skilllib global ${action} <name>...`);
|
|
1523
|
+
process.exit(1);
|
|
1524
|
+
}
|
|
1525
|
+
setKeepGlobal(names, action === "keep");
|
|
1526
|
+
success(action === "keep" ? `Keeping ${names.join(", ")} global on purpose` : `${names.join(", ")} will show up in cleanup again`);
|
|
1527
|
+
} else if (action) {
|
|
1528
|
+
error("Usage: skilllib global [keep|unkeep <name>...]");
|
|
1529
|
+
process.exit(1);
|
|
1530
|
+
}
|
|
1531
|
+
const kept = keptGlobal();
|
|
1532
|
+
if (!yours.length) return info("None of your skills load globally.");
|
|
1533
|
+
for (const m of yours) info(`${kept.has(m.name) ? green("\u2713") : yellow("\u26A0")} ${m.name.padEnd(32)} ${dim(tildify(m.path))}`);
|
|
1534
|
+
const unreviewed = yours.filter((m) => !kept.has(m.name)).length;
|
|
1535
|
+
if (unreviewed) info(dim(`
|
|
1536
|
+
${unreviewed} not reviewed. Keep one global on purpose: skilllib global keep <name>`));
|
|
1537
|
+
}
|
|
1501
1538
|
|
|
1502
1539
|
export {
|
|
1503
1540
|
userHome,
|
|
@@ -1522,6 +1559,8 @@ export {
|
|
|
1522
1559
|
harnessesChosen,
|
|
1523
1560
|
setHarnesses,
|
|
1524
1561
|
allowTrackedLinks,
|
|
1562
|
+
keptGlobal,
|
|
1563
|
+
setKeepGlobal,
|
|
1525
1564
|
machineSkills,
|
|
1526
1565
|
libraryOrigins,
|
|
1527
1566
|
librarySkills,
|
|
@@ -1567,5 +1606,6 @@ export {
|
|
|
1567
1606
|
usage,
|
|
1568
1607
|
show,
|
|
1569
1608
|
harnessesCommand,
|
|
1570
|
-
link
|
|
1609
|
+
link,
|
|
1610
|
+
globalCommand
|
|
1571
1611
|
};
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
doctor,
|
|
5
5
|
error,
|
|
6
6
|
folders,
|
|
7
|
+
globalCommand,
|
|
7
8
|
harnessesCommand,
|
|
8
9
|
importSkills,
|
|
9
10
|
link,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
unhide,
|
|
22
23
|
update,
|
|
23
24
|
usage
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-HLCJQVHB.js";
|
|
25
26
|
|
|
26
27
|
// src/index.ts
|
|
27
28
|
import { readFileSync } from "fs";
|
|
@@ -36,6 +37,7 @@ Set up:
|
|
|
36
37
|
skilllib harnesses [id...] Show or set the agents you use (claude-code cursor codex zed)
|
|
37
38
|
skilllib import <dir>... Add skill folders to the library (--force replaces)
|
|
38
39
|
skilllib import --global Copy ~/.claude/skills into the library
|
|
40
|
+
skilllib global [keep|unkeep <name>...] Your global skills; mark ones you keep global on purpose
|
|
39
41
|
|
|
40
42
|
In a project:
|
|
41
43
|
skilllib status This project's skills and their status
|
|
@@ -73,9 +75,9 @@ async function main() {
|
|
|
73
75
|
const args = parseArgs(rest);
|
|
74
76
|
switch (command) {
|
|
75
77
|
case void 0:
|
|
76
|
-
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-
|
|
78
|
+
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-YF7BQ7CD.js")).tui() : status(args);
|
|
77
79
|
case "ui":
|
|
78
|
-
return (await import("./tui-
|
|
80
|
+
return (await import("./tui-YF7BQ7CD.js")).tui();
|
|
79
81
|
case "status":
|
|
80
82
|
return status(args);
|
|
81
83
|
case "add":
|
|
@@ -115,6 +117,8 @@ async function main() {
|
|
|
115
117
|
case "new":
|
|
116
118
|
case "create":
|
|
117
119
|
return newSkill(args);
|
|
120
|
+
case "global":
|
|
121
|
+
return globalCommand(args);
|
|
118
122
|
case "restore":
|
|
119
123
|
return restore(args);
|
|
120
124
|
case "doctor":
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
importSkill,
|
|
18
18
|
installDirs,
|
|
19
19
|
isProjectCandidate,
|
|
20
|
+
keptGlobal,
|
|
20
21
|
latestVersion,
|
|
21
22
|
libraryOrigins,
|
|
22
23
|
librarySkillDir,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
scanUsage,
|
|
37
38
|
setHarnesses,
|
|
38
39
|
setHidden,
|
|
40
|
+
setKeepGlobal,
|
|
39
41
|
skilllibHome,
|
|
40
42
|
summarize,
|
|
41
43
|
syncProject,
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
versionHistory,
|
|
47
49
|
visibilityOf,
|
|
48
50
|
visibleProjects
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-HLCJQVHB.js";
|
|
50
52
|
|
|
51
53
|
// src/tui/index.tsx
|
|
52
54
|
import { render } from "ink";
|
|
@@ -263,7 +265,8 @@ function loadSnapshot() {
|
|
|
263
265
|
backups: listBackups(),
|
|
264
266
|
roots: readConfig().roots,
|
|
265
267
|
harnesses: enabledHarnesses(),
|
|
266
|
-
harnessesChosen: harnessesChosen()
|
|
268
|
+
harnessesChosen: harnessesChosen(),
|
|
269
|
+
keptGlobal: keptGlobal()
|
|
267
270
|
};
|
|
268
271
|
}
|
|
269
272
|
function projectRows(root, library, status = projectStatus(root)) {
|
|
@@ -351,6 +354,7 @@ var NEXT_MODE = { move: "keep", keep: "delete", delete: "move" };
|
|
|
351
354
|
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
352
355
|
function CleanupWizard({
|
|
353
356
|
skills,
|
|
357
|
+
kept,
|
|
354
358
|
projects,
|
|
355
359
|
usesIn,
|
|
356
360
|
usesTotal,
|
|
@@ -367,11 +371,19 @@ function CleanupWizard({
|
|
|
367
371
|
const [step, setStep] = useState("choose");
|
|
368
372
|
const [focus, setFocus] = useState("skills");
|
|
369
373
|
const [modes, setModes] = useState(
|
|
370
|
-
() => new Map(skills.map((s) => [s.path,
|
|
374
|
+
() => new Map(skills.map((s) => [s.path, s.name === initialSkill || !initialSkill && !kept.has(s.name) ? "move" : "keep"]))
|
|
371
375
|
);
|
|
372
376
|
const modeOf = (s) => modes.get(s.path) ?? "keep";
|
|
373
|
-
const
|
|
374
|
-
const
|
|
377
|
+
const [touched, setTouched] = useState(() => new Set(initialSkill ? [] : skills.map((s) => s.path)));
|
|
378
|
+
const setMode = (s, mode) => {
|
|
379
|
+
setModes((prev) => new Map(prev).set(s.path, mode));
|
|
380
|
+
setTouched((prev) => new Set(prev).add(s.path));
|
|
381
|
+
};
|
|
382
|
+
const [showKept, setShowKept] = useState(false);
|
|
383
|
+
const shown = (on) => skills.filter((s) => on || !kept.has(s.name) || s.name === initialSkill);
|
|
384
|
+
const list = shown(showKept);
|
|
385
|
+
const hiddenKept = skills.length - shown(false).length;
|
|
386
|
+
const [skillIndex, setSkillIndex] = useState(() => Math.max(0, list.findIndex((s) => s.name === initialSkill)));
|
|
375
387
|
const [projectIndex, setProjectIndex] = useState(0);
|
|
376
388
|
const [status, setStatus] = useState("");
|
|
377
389
|
const [targets, setTargets] = useState(
|
|
@@ -379,9 +391,10 @@ function CleanupWizard({
|
|
|
379
391
|
skills.map((s) => [s.path, new Set(projects.filter((p) => installedIn(s.name).includes(p) || (usesIn(s.name, p) ?? 0) > 0))])
|
|
380
392
|
)
|
|
381
393
|
);
|
|
382
|
-
const skill =
|
|
394
|
+
const skill = list[skillIndex];
|
|
383
395
|
const chosen = skills.filter((s) => modeOf(s) === "move");
|
|
384
396
|
const toDelete = skills.filter((s) => modeOf(s) === "delete");
|
|
397
|
+
const toKeep = skills.filter((s) => modeOf(s) === "keep" && touched.has(s.path) && !kept.has(s.name));
|
|
385
398
|
const totalInstalls = chosen.reduce((n, s) => n + (targets.get(s.path)?.size ?? 0), 0);
|
|
386
399
|
const orphans = chosen.filter((s) => (targets.get(s.path)?.size ?? 0) === 0);
|
|
387
400
|
const toggleTarget = (s, project) => setTargets((prev) => {
|
|
@@ -417,7 +430,9 @@ function CleanupWizard({
|
|
|
417
430
|
if (r.ok) unloaded++;
|
|
418
431
|
else problems.push(`${s.name}: ${r.reason}`);
|
|
419
432
|
}
|
|
420
|
-
|
|
433
|
+
setKeepGlobal(toKeep.map((s) => s.name), true);
|
|
434
|
+
setKeepGlobal([...chosen, ...toDelete].map((s) => s.name).filter((n) => kept.has(n)), false);
|
|
435
|
+
onDone({ unloaded, installs, deleted, kept: toKeep.length, problems });
|
|
421
436
|
};
|
|
422
437
|
inputRef.current = (input, key) => {
|
|
423
438
|
if (key.ctrl && input === "c") return onCancel();
|
|
@@ -427,14 +442,19 @@ function CleanupWizard({
|
|
|
427
442
|
return;
|
|
428
443
|
}
|
|
429
444
|
if (key.escape) return onCancel();
|
|
430
|
-
if (input === "p") return setStatus(onReviewPrompt(
|
|
431
|
-
if (
|
|
445
|
+
if (input === "p") return setStatus(onReviewPrompt(list));
|
|
446
|
+
if (input === "k" && hiddenKept) {
|
|
447
|
+
const next = shown(!showKept);
|
|
448
|
+
setShowKept(!showKept);
|
|
449
|
+
return setSkillIndex(Math.max(0, next.findIndex((s) => s.path === skill?.path)));
|
|
450
|
+
}
|
|
451
|
+
if (key.return) return chosen.length || toDelete.length || toKeep.length ? setStep("review") : void 0;
|
|
432
452
|
if (key.tab || key.rightArrow || key.leftArrow) {
|
|
433
453
|
return setFocus(key.leftArrow ? "skills" : key.rightArrow ? "projects" : focus === "skills" ? "projects" : "skills");
|
|
434
454
|
}
|
|
435
455
|
if (focus === "skills") {
|
|
436
456
|
if (key.upArrow) return setSkillIndex((i) => Math.max(0, i - 1));
|
|
437
|
-
if (key.downArrow) return setSkillIndex((i) => Math.min(
|
|
457
|
+
if (key.downArrow) return setSkillIndex((i) => Math.min(list.length - 1, i + 1));
|
|
438
458
|
if (input === " " && skill) return setMode(skill, NEXT_MODE[modeOf(skill)]);
|
|
439
459
|
if (input === "d" && skill) return setMode(skill, modeOf(skill) === "delete" ? "keep" : "delete");
|
|
440
460
|
} else {
|
|
@@ -460,8 +480,9 @@ function CleanupWizard({
|
|
|
460
480
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: color.accent, paddingX: 2, paddingY: 1, height: bodyHeight + 2, children: [
|
|
461
481
|
/* @__PURE__ */ jsx2(Text2, { color: color.text, children: [
|
|
462
482
|
chosen.length ? `${plural(chosen.length, "skill")} ${chosen.length === 1 ? "moves" : "move"} into your projects (${plural(totalInstalls, "install")})` : "",
|
|
463
|
-
toDelete.length ? `${plural(toDelete.length, "skill")} ${toDelete.length === 1 ? "is" : "are"} deleted` : ""
|
|
464
|
-
|
|
483
|
+
toDelete.length ? `${plural(toDelete.length, "skill")} ${toDelete.length === 1 ? "is" : "are"} deleted` : "",
|
|
484
|
+
toKeep.length ? `${plural(toKeep.length, "skill")} ${toKeep.length === 1 ? "stays" : "stay"} global on purpose` : ""
|
|
485
|
+
].filter(Boolean).join(" \xB7 ") + "." }),
|
|
465
486
|
/* @__PURE__ */ jsx2(Text2, { color: color.faint, children: "Moved skills are copied into Your skills first. Every original goes to a backup you can restore from Health." }),
|
|
466
487
|
/* @__PURE__ */ jsx2(Text2, { children: " " }),
|
|
467
488
|
lines.slice(0, Math.max(1, bodyHeight - 7)).map(({ s, where }) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
|
|
@@ -471,6 +492,7 @@ function CleanupWizard({
|
|
|
471
492
|
] }, s.path)),
|
|
472
493
|
lines.length > bodyHeight - 7 ? /* @__PURE__ */ jsx2(Text2, { color: color.faint, children: `\u2026 and ${lines.length - (bodyHeight - 7)} more` }) : null,
|
|
473
494
|
toDelete.length ? /* @__PURE__ */ jsx2(Text2, { color: color.red, wrap: "truncate-end", children: `\u2715 delete (not kept in Your skills): ${toDelete.map((s) => s.name).join(", ")}` }) : null,
|
|
495
|
+
toKeep.length ? /* @__PURE__ */ jsx2(Text2, { color: color.muted, wrap: "truncate-end", children: `\u2713 keep global, and skip next cleanup: ${toKeep.map((s) => s.name).join(", ")}` }) : null,
|
|
474
496
|
/* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
|
|
475
497
|
orphans.length ? /* @__PURE__ */ jsx2(Text2, { color: color.yellow, children: `${plural(orphans.length, "skill")} won't be in any project \u2014 add them later from "Add skills".` }) : null
|
|
476
498
|
] }),
|
|
@@ -490,7 +512,7 @@ function CleanupWizard({
|
|
|
490
512
|
const detailHeight = height >= 26 ? 7 : height >= 20 ? 5 : 0;
|
|
491
513
|
const listHeight = bodyHeight - detailHeight;
|
|
492
514
|
const usesText = (n) => n === null ? "\u2026" : n === 0 ? "unused" : `${n} use${n === 1 ? "" : "s"}`;
|
|
493
|
-
const skillRows =
|
|
515
|
+
const skillRows = list.map((s) => {
|
|
494
516
|
const mode = modeOf(s);
|
|
495
517
|
const n = targets.get(s.path)?.size ?? 0;
|
|
496
518
|
return {
|
|
@@ -500,7 +522,7 @@ function CleanupWizard({
|
|
|
500
522
|
{ text: s.name, grow: true, color: mode === "keep" ? color.muted : mode === "delete" ? color.red : void 0 },
|
|
501
523
|
{ text: usesText(usesTotal(s.name)).padStart(9), width: 9, color: usesTotal(s.name) ? color.text : color.faint },
|
|
502
524
|
{
|
|
503
|
-
text: mode === "keep" ? " keep global" : mode === "delete" ? " delete" : n ? ` \u2192 ${plural(n, "project")}` : " \u2192 no project",
|
|
525
|
+
text: mode === "keep" ? kept.has(s.name) ? " \u2713 kept global" : " keep global" : mode === "delete" ? " delete" : n ? ` \u2192 ${plural(n, "project")}` : " \u2192 no project",
|
|
504
526
|
width: 15,
|
|
505
527
|
color: mode === "keep" ? color.faint : mode === "delete" ? color.red : n ? color.green : color.yellow
|
|
506
528
|
}
|
|
@@ -526,19 +548,19 @@ function CleanupWizard({
|
|
|
526
548
|
] }),
|
|
527
549
|
/* @__PURE__ */ jsxs2(Box2, { height: 2, paddingX: 1, flexDirection: "column", children: [
|
|
528
550
|
/* @__PURE__ */ jsx2(Text2, { color: color.text, wrap: "truncate-end", children: "These load in every repo, so every agent reads them all the time. Pick the projects that should keep each one." }),
|
|
529
|
-
/* @__PURE__ */ jsx2(Text2, { color: color.faint, wrap: "truncate-end", children: status ? `\u2713 ${status}` : "\u25C9 move into the ticked projects \xB7 \u25CB keep global \xB7 \u2715 delete. Pre-ticked: projects that have it or used it. p: ask an agent." })
|
|
551
|
+
/* @__PURE__ */ jsx2(Text2, { color: color.faint, wrap: "truncate-end", children: status ? `\u2713 ${status}` : "\u25C9 move into the ticked projects \xB7 \u25CB keep global (remembered) \xB7 \u2715 delete. Pre-ticked: projects that have it or used it. p: ask an agent." })
|
|
530
552
|
] }),
|
|
531
553
|
/* @__PURE__ */ jsxs2(Box2, { height: listHeight, children: [
|
|
532
554
|
/* @__PURE__ */ jsx2(
|
|
533
555
|
ListPanel,
|
|
534
556
|
{
|
|
535
|
-
title: `Global skills \xB7 ${chosen.length} move \xB7 ${toDelete.length} delete \xB7 uses ${usageDays}d`,
|
|
557
|
+
title: `Global skills \xB7 ${chosen.length} move \xB7 ${toDelete.length} delete${hiddenKept && !showKept ? ` \xB7 ${hiddenKept} kept hidden` : ""} \xB7 uses ${usageDays}d`,
|
|
536
558
|
focused: focus === "skills",
|
|
537
559
|
width: leftWidth,
|
|
538
560
|
height: listHeight,
|
|
539
561
|
rows: skillRows,
|
|
540
562
|
selected: skillIndex,
|
|
541
|
-
empty: "None of your skills load globally. \u{1F389}"
|
|
563
|
+
empty: hiddenKept ? `All reviewed: you keep ${plural(hiddenKept, "skill")} global on purpose. k shows them.` : "None of your skills load globally. \u{1F389}"
|
|
542
564
|
}
|
|
543
565
|
),
|
|
544
566
|
/* @__PURE__ */ jsx2(
|
|
@@ -571,6 +593,7 @@ function CleanupWizard({
|
|
|
571
593
|
["space", focus === "skills" ? "move / keep / delete" : "tick project"],
|
|
572
594
|
...focus === "skills" ? [["d", "delete"]] : [],
|
|
573
595
|
["tab", "switch side"],
|
|
596
|
+
...hiddenKept ? [["k", showKept ? "hide kept" : `show kept (${hiddenKept})`]] : [],
|
|
574
597
|
["enter", `review (${plural(totalInstalls, "install")})`],
|
|
575
598
|
["p", "ask an agent"],
|
|
576
599
|
["esc", "cancel"]
|
|
@@ -602,7 +625,8 @@ var HELP_TOPICS = [
|
|
|
602
625
|
"tab switch lists inside a project",
|
|
603
626
|
"esc clear the search, then go back to the left pane",
|
|
604
627
|
"ctrl+r reload ctrl+c quit",
|
|
605
|
-
"p in the cleanup wizard: copy a prompt asking an agent to review the skills"
|
|
628
|
+
"p in the cleanup wizard: copy a prompt asking an agent to review the skills",
|
|
629
|
+
"k in the cleanup wizard: show / hide the skills you keep global on purpose"
|
|
606
630
|
]
|
|
607
631
|
},
|
|
608
632
|
{
|
|
@@ -610,7 +634,8 @@ var HELP_TOPICS = [
|
|
|
610
634
|
lines: [
|
|
611
635
|
"\u273B \u2B21 \u25CE \u2124 Claude Code, Cursor, Codex, Zed \u2014 lit when that agent loads the skill here",
|
|
612
636
|
"\xB2 Cursor reaches it through two folders (it reads several)",
|
|
613
|
-
"\u26A0 loaded globally loads in every repo on this machine",
|
|
637
|
+
"\u26A0 loaded globally loads in every repo on this machine, and you haven't decided about it yet",
|
|
638
|
+
"\u2713 global loads in every repo, and you keep it global on purpose (Enter \u2192 Keep global on purpose)",
|
|
614
639
|
"\u29C9 N copies the same skill reaches agents from N places \u2014 keep one",
|
|
615
640
|
"\u25CF in sync \xB7 \u2191 update \xB7 \u270E edited \xB7 \u25CB only here",
|
|
616
641
|
"\u2713 committed in git, so teammates get it \xB1 changed committed, with local edits",
|
|
@@ -851,6 +876,7 @@ function buildReviewPrompt(skills, ctx) {
|
|
|
851
876
|
`- Location: ${tildify(s.path)}${s.links.length ? ` (also linked from ${s.links.map(tildify).join(", ")})` : ""}`,
|
|
852
877
|
`- Loaded by: ${s.loadedBy}`,
|
|
853
878
|
...s.vendor ? [`- Vendor-managed: ${s.vendor}`] : [],
|
|
879
|
+
...s.keptGlobal ? ["- I marked this as global on purpose: recommend changing that only for a strong reason (unused, duplicated, low quality)"] : [],
|
|
854
880
|
`- Usage (Claude Code transcripts only): ${usage}`,
|
|
855
881
|
`- Installed in projects: ${s.installedIn.length ? s.installedIn.join(", ") : "none"}`,
|
|
856
882
|
`- Other copies agents can see: ${s.otherCopies.length ? s.otherCopies.join("; ") : "none"}`,
|
|
@@ -1470,6 +1496,7 @@ function App() {
|
|
|
1470
1496
|
const dupes = (name) => copiesOf(name).length;
|
|
1471
1497
|
const globalItem = (m) => {
|
|
1472
1498
|
const inLib = libraryNames.has(m.name);
|
|
1499
|
+
const kept = m.movable && snapshot.keptGlobal.has(m.name);
|
|
1473
1500
|
const alsoLocal = rows.some((r) => r.installed && r.name === m.name);
|
|
1474
1501
|
const moveHere = () => confirm(
|
|
1475
1502
|
`Use ${m.name} only where you add it? It's ${inLib ? "" : "imported into the library, "}added to ${where}, and stops loading globally \u2014 other projects lose it until you add it there. Restorable from Health.`,
|
|
@@ -1483,6 +1510,7 @@ function App() {
|
|
|
1483
1510
|
);
|
|
1484
1511
|
const actions = m.movable ? [
|
|
1485
1512
|
{ label: "Choose which repos keep it\u2026", hint: "cleanup wizard", run: () => setWizard({ initialSkill: m.name }) },
|
|
1513
|
+
keepAction(m),
|
|
1486
1514
|
{ label: `Use only in ${where}`, hint: "add here, stop loading everywhere", run: moveHere },
|
|
1487
1515
|
{ label: "Delete", hint: "not kept in Your skills \xB7 restorable from Health", run: () => confirmDeleteGlobal([m]) },
|
|
1488
1516
|
...inLib ? [] : [
|
|
@@ -1511,9 +1539,9 @@ function App() {
|
|
|
1511
1539
|
...harnessChips(enabledIds.map((id) => ({ id, paths: m.harnesses.includes(id) ? 1 : 0 })), true),
|
|
1512
1540
|
{ text: "", width: 8 },
|
|
1513
1541
|
{
|
|
1514
|
-
text: ` ${dupes(m.name) > 1 ? `\u29C9 ${dupes(m.name)} copies` : m.movable ? "\u26A0 loaded globally" : "vendor"}`,
|
|
1542
|
+
text: ` ${dupes(m.name) > 1 ? `\u29C9 ${dupes(m.name)} copies` : kept ? "\u2713 global" : m.movable ? "\u26A0 loaded globally" : "vendor"}`,
|
|
1515
1543
|
width: 19,
|
|
1516
|
-
color: dupes(m.name) > 1 ? color.red : m.movable ? color.yellow : color.faint
|
|
1544
|
+
color: dupes(m.name) > 1 ? color.red : kept ? color.green : m.movable ? color.yellow : color.faint
|
|
1517
1545
|
},
|
|
1518
1546
|
{ text: usesText(m.name, root).padStart(4), color: color.muted }
|
|
1519
1547
|
]
|
|
@@ -1526,7 +1554,7 @@ function App() {
|
|
|
1526
1554
|
meta: [
|
|
1527
1555
|
`loaded in every project by ${m.harnesses.map(harnessName).join(", ")}`,
|
|
1528
1556
|
`${m.kind}: ${m.origin}`,
|
|
1529
|
-
m.movable ? "yours: the cleanup wizard can scope it to projects" : "managed by the vendor"
|
|
1557
|
+
kept ? "yours: kept global on purpose" : m.movable ? "yours: the cleanup wizard can scope it to projects" : "managed by the vendor"
|
|
1530
1558
|
],
|
|
1531
1559
|
description: dupes(m.name) > 1 ? `Loaded ${dupes(m.name)} times here: ${copiesOf(m.name).join(" \xB7 ")}. Agents may see it twice \u2014 keep one.` : sourceGroup(m)?.about
|
|
1532
1560
|
}
|
|
@@ -1580,8 +1608,10 @@ function App() {
|
|
|
1580
1608
|
const fromLibrary = local.filter((r) => r.managed || r.state === "untracked copy of library skill");
|
|
1581
1609
|
const repoOwn = local.filter((r) => !fromLibrary.includes(r));
|
|
1582
1610
|
const yours = globals.filter((m) => m.movable);
|
|
1611
|
+
const keptYours = yours.filter((m) => snapshot.keptGlobal.has(m.name));
|
|
1612
|
+
const unreviewed = yours.filter((m) => !snapshot.keptGlobal.has(m.name));
|
|
1583
1613
|
const vendor = globals.filter((m) => !m.movable);
|
|
1584
|
-
const cleanupItem =
|
|
1614
|
+
const cleanupItem = unreviewed.length ? [
|
|
1585
1615
|
{
|
|
1586
1616
|
key: "__cleanup",
|
|
1587
1617
|
search: "clean up global",
|
|
@@ -1590,7 +1620,7 @@ function App() {
|
|
|
1590
1620
|
key: "__cleanup",
|
|
1591
1621
|
cells: [
|
|
1592
1622
|
{ text: "\u26A0 ", color: color.yellow },
|
|
1593
|
-
{ text: `${plural2(
|
|
1623
|
+
{ text: `${plural2(unreviewed.length, "skill")} of yours load in every repo \u2014 choose where each should live\u2026`, grow: true, color: color.yellow }
|
|
1594
1624
|
]
|
|
1595
1625
|
},
|
|
1596
1626
|
primary: { label: "clean up", run: () => setWizard({}) },
|
|
@@ -1634,28 +1664,14 @@ function App() {
|
|
|
1634
1664
|
]
|
|
1635
1665
|
},
|
|
1636
1666
|
{
|
|
1637
|
-
title: `\u26A0 Global \xB7 yours, loaded in every repo (${
|
|
1638
|
-
items: [...cleanupItem, ...
|
|
1639
|
-
groupActions: (members) => [
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
{
|
|
1646
|
-
label: `Copy all ${members.length} to Your skills`,
|
|
1647
|
-
hint: "they keep loading globally",
|
|
1648
|
-
run: () => act(() => {
|
|
1649
|
-
const added = members.map((m) => importSkill(m.skill.path)).filter((r) => r.status === "added").length;
|
|
1650
|
-
return ok(`${plural2(added, "skill")} imported into your library`);
|
|
1651
|
-
})
|
|
1652
|
-
},
|
|
1653
|
-
{
|
|
1654
|
-
label: `Delete all ${members.length}`,
|
|
1655
|
-
hint: "not kept in Your skills \xB7 restorable from Health",
|
|
1656
|
-
run: () => confirmDeleteGlobal(snapshot.machine.filter((m) => members.some((x) => x.skill?.path === m.path)))
|
|
1657
|
-
}
|
|
1658
|
-
]
|
|
1667
|
+
title: `\u26A0 Global \xB7 yours, not reviewed, loaded in every repo (${unreviewed.length})`,
|
|
1668
|
+
items: [...cleanupItem, ...unreviewed.map(globalItem)],
|
|
1669
|
+
groupActions: (members) => [...keepGroupActions(members), ...yourGlobalActions(members)]
|
|
1670
|
+
},
|
|
1671
|
+
{
|
|
1672
|
+
title: `\u2713 Global \xB7 yours, on purpose (${keptYours.length})`,
|
|
1673
|
+
items: keptYours.map(globalItem),
|
|
1674
|
+
groupActions: (members) => [...keepGroupActions(members), ...yourGlobalActions(members)]
|
|
1659
1675
|
},
|
|
1660
1676
|
{
|
|
1661
1677
|
title: `Global \xB7 from vendors: plugins, claude.ai, built-in (${vendor.length})`,
|
|
@@ -1795,6 +1811,8 @@ function App() {
|
|
|
1795
1811
|
}
|
|
1796
1812
|
function globalSections() {
|
|
1797
1813
|
const yours = snapshot.machine.filter((m) => m.movable);
|
|
1814
|
+
const unreviewed = yours.filter((m) => !m.broken && !snapshot.keptGlobal.has(m.name));
|
|
1815
|
+
const keptCount = yours.filter((m) => !m.broken && snapshot.keptGlobal.has(m.name)).length;
|
|
1798
1816
|
const importAll = {
|
|
1799
1817
|
label: "Copy all of yours to Your skills",
|
|
1800
1818
|
run: () => {
|
|
@@ -1833,12 +1851,16 @@ function App() {
|
|
|
1833
1851
|
key: "__cleanup",
|
|
1834
1852
|
cells: [
|
|
1835
1853
|
{ text: "\u26A0 ", color: color.yellow },
|
|
1836
|
-
{ text: `Clean up: choose which repos keep each of your ${
|
|
1854
|
+
{ text: `Clean up: choose which repos keep each of your ${unreviewed.length} unreviewed global skills\u2026`, grow: true, color: color.yellow }
|
|
1837
1855
|
]
|
|
1838
1856
|
},
|
|
1839
1857
|
primary: { label: "clean up", run: () => setWizard({}) },
|
|
1840
1858
|
actions: [{ label: "Open the cleanup wizard", run: () => setWizard({}) }],
|
|
1841
|
-
preview: {
|
|
1859
|
+
preview: {
|
|
1860
|
+
title: "Clean up global skills",
|
|
1861
|
+
meta: ["every agent reads global skills in every repo", ...keptCount ? [`skips the ${keptCount} you keep global on purpose (k shows them)`] : []],
|
|
1862
|
+
description: "Install each skill only where it's needed, keep it global on purpose, or delete it. Originals are kept in backup."
|
|
1863
|
+
}
|
|
1842
1864
|
};
|
|
1843
1865
|
const grouped = groups.map(([title, test]) => ({
|
|
1844
1866
|
title,
|
|
@@ -1849,6 +1871,7 @@ function App() {
|
|
|
1849
1871
|
run: () => copyReviewPrompt(snapshot.machine.filter((m) => members.some((x) => x.skill?.path === m.path)).map(reviewOfGlobal), `These are global skills (group ${members[0]?.group?.label ?? ""}).`)
|
|
1850
1872
|
},
|
|
1851
1873
|
...title.startsWith("Yours") ? [
|
|
1874
|
+
...keepGroupActions(members),
|
|
1852
1875
|
{
|
|
1853
1876
|
label: `Delete all ${members.length}`,
|
|
1854
1877
|
hint: "not kept in Your skills \xB7 restorable from Health",
|
|
@@ -1879,7 +1902,7 @@ function App() {
|
|
|
1879
1902
|
actions.push({ label: "Remove the broken link", run: unload });
|
|
1880
1903
|
} else {
|
|
1881
1904
|
if (!inLib) actions.push({ label: "Copy to Your skills", hint: "keeps loading globally", run: importIt });
|
|
1882
|
-
if (m.movable) actions.push({ label: "Stop loading it globally", hint: "use it per project instead", run: unload });
|
|
1905
|
+
if (m.movable) actions.push(keepAction(m), { label: "Stop loading it globally", hint: "use it per project instead", run: unload });
|
|
1883
1906
|
primary = m.movable ? { label: "choose repos\u2026", run: () => setWizard({ initialSkill: m.name }) } : !inLib ? { label: "copy to your skills", run: importIt } : void 0;
|
|
1884
1907
|
if (m.movable) {
|
|
1885
1908
|
actions.unshift({ label: "Choose which repos keep it\u2026", hint: "cleanup wizard", run: () => setWizard({ initialSkill: m.name }) });
|
|
@@ -1901,6 +1924,7 @@ function App() {
|
|
|
1901
1924
|
{ text: m.name.padEnd(30).slice(0, 30), color: m.broken ? color.red : void 0 },
|
|
1902
1925
|
{ text: m.broken ? "broken link" : m.origin, grow: true, color: m.broken ? color.red : kindColor[m.kind] },
|
|
1903
1926
|
...harnessChips(enabledIds.map((id) => ({ id, paths: m.harnesses.includes(id) ? 1 : 0 })), !m.broken),
|
|
1927
|
+
{ text: m.movable && snapshot.keptGlobal.has(m.name) ? " \u2713 kept" : " ", color: color.green },
|
|
1904
1928
|
{ text: inLib ? " \u2713 library" : " ", color: color.green },
|
|
1905
1929
|
{ text: usesText(m.name).padStart(5), color: color.muted }
|
|
1906
1930
|
]
|
|
@@ -1913,7 +1937,7 @@ function App() {
|
|
|
1913
1937
|
meta: [
|
|
1914
1938
|
m.harnesses.length ? `loaded everywhere by ${m.harnesses.map(harnessName).join(", ")}` : "not loaded by your harnesses",
|
|
1915
1939
|
`${m.kind}: ${m.origin}`,
|
|
1916
|
-
m.movable ? "yours to manage" : "managed by the vendor",
|
|
1940
|
+
m.movable ? snapshot.keptGlobal.has(m.name) ? "yours \xB7 kept global on purpose" : "yours to manage" : "managed by the vendor",
|
|
1917
1941
|
tildify2(m.path) + (m.links.length ? ` (+ ${m.links.length} link${m.links.length > 1 ? "s" : ""})` : "")
|
|
1918
1942
|
],
|
|
1919
1943
|
description: m.broken ? "Broken link: it points at a folder that no longer exists, so it loads nothing." : void 0
|
|
@@ -1952,7 +1976,48 @@ function App() {
|
|
|
1952
1976
|
description: "The prompt includes each skill's description, a SKILL.md excerpt, how it was installed (vendor, skills.sh, plugin\u2026), which agents load it, usage per project, duplicates and your project list. Paste it into Claude Code, Cursor or Codex; it replies with keep / move / delete per skill."
|
|
1953
1977
|
}
|
|
1954
1978
|
};
|
|
1955
|
-
return [{ title: "", items: [...
|
|
1979
|
+
return [{ title: "", items: [...unreviewed.length ? [launcher] : [], reviewAll] }, ...grouped];
|
|
1980
|
+
}
|
|
1981
|
+
function markKept(names, keep) {
|
|
1982
|
+
act(() => {
|
|
1983
|
+
setKeepGlobal(names, keep);
|
|
1984
|
+
const label = names.length === 1 ? names[0] : plural2(names.length, "skill");
|
|
1985
|
+
return ok(keep ? `${label} kept global on purpose \u2014 cleanup skips ${names.length === 1 ? "it" : "them"}` : `${label} back in cleanup`);
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
function keepAction(m) {
|
|
1989
|
+
return snapshot.keptGlobal.has(m.name) ? { label: "Unmark: not global on purpose", hint: "cleanup asks about it again", run: () => markKept([m.name], false) } : { label: "\u2713 Keep global on purpose", hint: "stops the \u26A0 and skips it in cleanup", run: () => markKept([m.name], true) };
|
|
1990
|
+
}
|
|
1991
|
+
function keepGroupActions(members) {
|
|
1992
|
+
const names = [...new Set(members.flatMap((m) => m.skill ? [m.skill.name] : []))];
|
|
1993
|
+
const unkept = names.filter((n) => !snapshot.keptGlobal.has(n));
|
|
1994
|
+
const kept = names.filter((n) => snapshot.keptGlobal.has(n));
|
|
1995
|
+
return [
|
|
1996
|
+
...unkept.length ? [{ label: `\u2713 Keep all ${unkept.length} global on purpose`, hint: "cleanup skips them", run: () => markKept(unkept, true) }] : [],
|
|
1997
|
+
...kept.length ? [{ label: `Unmark all ${kept.length}`, hint: "cleanup asks about them again", run: () => markKept(kept, false) }] : []
|
|
1998
|
+
];
|
|
1999
|
+
}
|
|
2000
|
+
function yourGlobalActions(members) {
|
|
2001
|
+
return [
|
|
2002
|
+
{
|
|
2003
|
+
label: `Copy review prompt for all ${members.length}`,
|
|
2004
|
+
hint: "for an agent to decide keep / move / delete",
|
|
2005
|
+
run: () => copyReviewPrompt(snapshot.machine.filter((m) => members.some((x) => x.skill?.path === m.path)).map(reviewOfGlobal), `These are global skills (group ${members[0]?.group?.label ?? ""}).`)
|
|
2006
|
+
},
|
|
2007
|
+
{
|
|
2008
|
+
label: `Copy all ${members.length} to Your skills`,
|
|
2009
|
+
hint: "they keep loading globally",
|
|
2010
|
+
run: () => act(() => {
|
|
2011
|
+
const added = members.map((m) => importSkill(m.skill.path)).filter((r) => r.status === "added").length;
|
|
2012
|
+
return ok(`${plural2(added, "skill")} imported into your library`);
|
|
2013
|
+
})
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
label: `Delete all ${members.length}`,
|
|
2017
|
+
hint: "not kept in Your skills \xB7 restorable from Health",
|
|
2018
|
+
run: () => confirmDeleteGlobal(snapshot.machine.filter((m) => members.some((x) => x.skill?.path === m.path)))
|
|
2019
|
+
}
|
|
2020
|
+
];
|
|
1956
2021
|
}
|
|
1957
2022
|
function confirmDeleteGlobal(skills) {
|
|
1958
2023
|
if (skills.length === 0) return;
|
|
@@ -2001,7 +2066,8 @@ function App() {
|
|
|
2001
2066
|
usesByProject: u.byProject,
|
|
2002
2067
|
installedIn: projectsWith(m.name),
|
|
2003
2068
|
otherCopies: snapshot.machine.filter((x) => x.name === m.name && x.path !== m.path).map((x) => `${x.kind} ${x.origin} (${tildify2(x.path)})`),
|
|
2004
|
-
inYourSkills: lib ? `yes, v${lib.version}` : null
|
|
2069
|
+
inYourSkills: lib ? `yes, v${lib.version}` : null,
|
|
2070
|
+
keptGlobal: m.movable && snapshot.keptGlobal.has(m.name)
|
|
2005
2071
|
};
|
|
2006
2072
|
}
|
|
2007
2073
|
function reviewOfLibrary(l) {
|
|
@@ -2496,6 +2562,7 @@ function App() {
|
|
|
2496
2562
|
CleanupWizard,
|
|
2497
2563
|
{
|
|
2498
2564
|
skills: yours,
|
|
2565
|
+
kept: snapshot.keptGlobal,
|
|
2499
2566
|
projects: snapshot.projects,
|
|
2500
2567
|
usesIn: (skill, project) => usage ? usage.byProject.get(project)?.get(skill) ?? 0 : null,
|
|
2501
2568
|
usesTotal: (skill) => usage ? usage.total.get(skill) ?? 0 : null,
|
|
@@ -2524,7 +2591,12 @@ function App() {
|
|
|
2524
2591
|
reload();
|
|
2525
2592
|
setToast({
|
|
2526
2593
|
ok: r.problems.length === 0,
|
|
2527
|
-
text:
|
|
2594
|
+
text: [
|
|
2595
|
+
r.unloaded || r.installs ? `${plural2(r.unloaded, "skill")} moved into projects (${plural2(r.installs, "install")})` : "",
|
|
2596
|
+
r.deleted ? `${r.deleted} deleted` : "",
|
|
2597
|
+
r.kept ? `${plural2(r.kept, "skill")} kept global on purpose` : "",
|
|
2598
|
+
r.problems.length ? `${r.problems.length} left global: ${r.problems[0]}` : ""
|
|
2599
|
+
].filter(Boolean).join(" \xB7 ")
|
|
2528
2600
|
});
|
|
2529
2601
|
}
|
|
2530
2602
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilllib",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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",
|