skilllib 1.1.0 → 1.2.1
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 +35 -10
- package/dist/{chunk-HLCJQVHB.js → chunk-O5TY4NWZ.js} +655 -87
- package/dist/index.js +10 -3
- package/dist/{tui-YF7BQ7CD.js → tui-6BOQIKAU.js} +78 -99
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,10 +19,11 @@ import {
|
|
|
19
19
|
show,
|
|
20
20
|
status,
|
|
21
21
|
sync,
|
|
22
|
+
tidy,
|
|
22
23
|
unhide,
|
|
23
24
|
update,
|
|
24
25
|
usage
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-O5TY4NWZ.js";
|
|
26
27
|
|
|
27
28
|
// src/index.ts
|
|
28
29
|
import { readFileSync } from "fs";
|
|
@@ -44,6 +45,9 @@ In a project:
|
|
|
44
45
|
skilllib add <name>... Install library skills where your harnesses look
|
|
45
46
|
skilllib remove <name>... Remove skills from this project
|
|
46
47
|
skilllib link [--all] Make every skill here usable by all your agents (adds links)
|
|
48
|
+
skilllib tidy [--all|--global] One real copy per skill, plus only the links your agents need
|
|
49
|
+
(--dry-run to preview, --allow-git to change what git sees,
|
|
50
|
+
<name> --keep <folder> to pick the copy that wins)
|
|
47
51
|
skilllib sync [--all] Install exactly the versions in skilllib.json
|
|
48
52
|
skilllib outdated [--all] Skills with a newer version in the library
|
|
49
53
|
skilllib update [name...] Move to the newest versions (--all: every project)
|
|
@@ -75,9 +79,9 @@ async function main() {
|
|
|
75
79
|
const args = parseArgs(rest);
|
|
76
80
|
switch (command) {
|
|
77
81
|
case void 0:
|
|
78
|
-
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-
|
|
82
|
+
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-6BOQIKAU.js")).tui() : status(args);
|
|
79
83
|
case "ui":
|
|
80
|
-
return (await import("./tui-
|
|
84
|
+
return (await import("./tui-6BOQIKAU.js")).tui();
|
|
81
85
|
case "status":
|
|
82
86
|
return status(args);
|
|
83
87
|
case "add":
|
|
@@ -106,6 +110,9 @@ async function main() {
|
|
|
106
110
|
return projects();
|
|
107
111
|
case "link":
|
|
108
112
|
return link(args);
|
|
113
|
+
case "tidy":
|
|
114
|
+
case "dedup":
|
|
115
|
+
return tidy(args);
|
|
109
116
|
case "harnesses":
|
|
110
117
|
case "agents":
|
|
111
118
|
return harnessesCommand(args);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ALL_PROJECT_DIRS,
|
|
3
3
|
HARNESSES,
|
|
4
|
-
MANIFEST_FILE,
|
|
5
4
|
addRoot,
|
|
6
5
|
addSkill,
|
|
7
6
|
allowTrackedLinks,
|
|
@@ -13,6 +12,7 @@ import {
|
|
|
13
12
|
findIssues,
|
|
14
13
|
findProjectRoot,
|
|
15
14
|
forgetLatest,
|
|
15
|
+
gitInfo,
|
|
16
16
|
harnessesChosen,
|
|
17
17
|
importSkill,
|
|
18
18
|
installDirs,
|
|
@@ -26,14 +26,18 @@ import {
|
|
|
26
26
|
linkEverywhere,
|
|
27
27
|
listBackups,
|
|
28
28
|
machineSkills,
|
|
29
|
+
pluginBackups,
|
|
29
30
|
projectOfFactory,
|
|
30
31
|
projectStatus,
|
|
31
32
|
readConfig,
|
|
32
33
|
readSkillInfo,
|
|
34
|
+
relativeTo,
|
|
33
35
|
relinkDependency,
|
|
34
36
|
removeRoot,
|
|
35
37
|
removeSkill,
|
|
36
38
|
restoreBackup,
|
|
39
|
+
restorePlugin,
|
|
40
|
+
runFix,
|
|
37
41
|
scanUsage,
|
|
38
42
|
setHarnesses,
|
|
39
43
|
setHidden,
|
|
@@ -48,7 +52,7 @@ import {
|
|
|
48
52
|
versionHistory,
|
|
49
53
|
visibilityOf,
|
|
50
54
|
visibleProjects
|
|
51
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-O5TY4NWZ.js";
|
|
52
56
|
|
|
53
57
|
// src/tui/index.tsx
|
|
54
58
|
import { render } from "ink";
|
|
@@ -56,7 +60,7 @@ import { render } from "ink";
|
|
|
56
60
|
// src/tui/App.tsx
|
|
57
61
|
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
58
62
|
import { homedir } from "os";
|
|
59
|
-
import { basename as basename3, dirname, join as
|
|
63
|
+
import { basename as basename3, dirname, join as join3, sep } from "path";
|
|
60
64
|
import { useEffect, useRef, useState as useState2 } from "react";
|
|
61
65
|
import { Box as Box3, Text as Text3, useApp, useInput, useWindowSize } from "ink";
|
|
62
66
|
import { lstatSync } from "fs";
|
|
@@ -104,6 +108,8 @@ function stateBadge(state) {
|
|
|
104
108
|
return { icon: "\u2713", label: "in your skills too", color: color.green };
|
|
105
109
|
case "repo skill, differs from library":
|
|
106
110
|
return { icon: "\u2260", label: "differs from yours", color: color.yellow };
|
|
111
|
+
case "from npx skills":
|
|
112
|
+
return { icon: "\u2193", label: "npx skills", color: color.magenta };
|
|
107
113
|
case "available":
|
|
108
114
|
return { icon: " ", label: "", color: color.faint };
|
|
109
115
|
}
|
|
@@ -262,7 +268,7 @@ function loadSnapshot() {
|
|
|
262
268
|
library,
|
|
263
269
|
machine,
|
|
264
270
|
issues: findIssues(projects, machine, new Set(library.map((l) => l.name)), statusOf),
|
|
265
|
-
backups: listBackups(),
|
|
271
|
+
backups: [...listBackups(), ...pluginBackups()].sort((a, b) => b.movedAt.localeCompare(a.movedAt)),
|
|
266
272
|
roots: readConfig().roots,
|
|
267
273
|
harnesses: enabledHarnesses(),
|
|
268
274
|
harnessesChosen: harnessesChosen(),
|
|
@@ -284,6 +290,7 @@ function projectRows(root, library, status = projectStatus(root)) {
|
|
|
284
290
|
location: s.location,
|
|
285
291
|
path: s.path,
|
|
286
292
|
visibility: s.visibility,
|
|
293
|
+
...s.source ? { source: s.source } : {},
|
|
287
294
|
description: library.find((l) => l.name === s.name)?.description ?? readSkillInfo(s.path).description
|
|
288
295
|
})
|
|
289
296
|
);
|
|
@@ -633,11 +640,10 @@ var HELP_TOPICS = [
|
|
|
633
640
|
heading: "Badges",
|
|
634
641
|
lines: [
|
|
635
642
|
"\u273B \u2B21 \u25CE \u2124 Claude Code, Cursor, Codex, Zed \u2014 lit when that agent loads the skill here",
|
|
636
|
-
"\xB2 Cursor reaches it through two folders (it reads several)",
|
|
637
643
|
"\u26A0 loaded globally loads in every repo on this machine, and you haven't decided about it yet",
|
|
638
644
|
"\u2713 global loads in every repo, and you keep it global on purpose (Enter \u2192 Keep global on purpose)",
|
|
639
|
-
"\u29C9 N copies the same skill reaches agents from N places \u2014
|
|
640
|
-
"\u25CF in sync \xB7 \u2191 update \xB7 \u270E edited \xB7 \u25CB only here",
|
|
645
|
+
"\u29C9 N copies the same skill reaches agents from N places \u2014 Health has the fix",
|
|
646
|
+
"\u25CF in sync \xB7 \u2191 update \xB7 \u270E edited \xB7 \u25CB only here \xB7 \u2193 owner/repo installed here by npx skills",
|
|
641
647
|
"\u2713 committed in git, so teammates get it \xB1 changed committed, with local edits",
|
|
642
648
|
"+ not added not committed yet \u2205 ignored gitignored \u2014 only on your machine"
|
|
643
649
|
]
|
|
@@ -654,8 +660,9 @@ var HELP_TOPICS = [
|
|
|
654
660
|
{
|
|
655
661
|
heading: "In a repo (only agents working in that repo see them)",
|
|
656
662
|
lines: [
|
|
657
|
-
".claude/skills Claude Code (Cursor reads it too). Where skilllib installs your skills.",
|
|
658
663
|
".agents/skills the shared folder: Codex, Cursor, Zed and most other agents. Repos often commit their team skills here.",
|
|
664
|
+
" skilllib (like `npx skills`) keeps the real copy here whenever one of your agents needs it.",
|
|
665
|
+
".claude/skills Claude Code (Cursor reads it too). A link to .agents/skills, or the copy itself if only Claude Code needs it.",
|
|
659
666
|
".cursor/skills Cursor only. .codex/skills Cursor only (compatibility)."
|
|
660
667
|
]
|
|
661
668
|
},
|
|
@@ -735,7 +742,7 @@ var HELP_TOPICS = [
|
|
|
735
742
|
"Compat .claude/skills, .codex/skills, ~/.claude/skills, ~/.codex/skills",
|
|
736
743
|
"Plus plugins from its marketplace, and Cursor's built-in skills",
|
|
737
744
|
"Cloud Agents only see ~/.cursor/skills.",
|
|
738
|
-
"
|
|
745
|
+
"A skill in two folders is listed once (tested). A plugin's copy and yours are listed separately."
|
|
739
746
|
]
|
|
740
747
|
},
|
|
741
748
|
{
|
|
@@ -761,7 +768,7 @@ var HELP_TOPICS = [
|
|
|
761
768
|
"User ~/.agents/skills",
|
|
762
769
|
"Admin /etc/codex/skills",
|
|
763
770
|
"System skills bundled by OpenAI",
|
|
764
|
-
"It does NOT read .claude/skills \u2014 skilllib
|
|
771
|
+
"It does NOT read .claude/skills \u2014 skilllib keeps your skills' real copies in .agents/skills."
|
|
765
772
|
]
|
|
766
773
|
},
|
|
767
774
|
{
|
|
@@ -802,57 +809,18 @@ var HELP_TOPICS = [
|
|
|
802
809
|
}
|
|
803
810
|
];
|
|
804
811
|
|
|
805
|
-
// src/git.ts
|
|
806
|
-
import { execFileSync } from "child_process";
|
|
807
|
-
import { existsSync as existsSync2 } from "fs";
|
|
808
|
-
import { join as join2, relative } from "path";
|
|
809
|
-
function git(root, args, input) {
|
|
810
|
-
try {
|
|
811
|
-
return execFileSync("git", args, { cwd: root, encoding: "utf-8", input, stdio: ["pipe", "pipe", "ignore"], maxBuffer: 16 * 1024 * 1024 });
|
|
812
|
-
} catch (err) {
|
|
813
|
-
const out = err.stdout;
|
|
814
|
-
return typeof out === "string" ? out : null;
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
function gitInfo(root) {
|
|
818
|
-
if (!existsSync2(join2(root, ".git"))) return null;
|
|
819
|
-
const dirs = [...ALL_PROJECT_DIRS, MANIFEST_FILE].filter((d) => existsSync2(join2(root, d)));
|
|
820
|
-
if (dirs.length === 0) return { of: () => "new" };
|
|
821
|
-
const tracked = (git(root, ["ls-files", "-z", "--", ...dirs]) ?? "").split("\0").filter(Boolean);
|
|
822
|
-
const dirty = (git(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...dirs]) ?? "").split("\0").filter(Boolean).map((entry) => entry.slice(3));
|
|
823
|
-
const ignoredCache = /* @__PURE__ */ new Map();
|
|
824
|
-
const isIgnored = (rel) => {
|
|
825
|
-
if (!ignoredCache.has(rel)) {
|
|
826
|
-
const out = git(root, ["check-ignore", "--no-index", "--", rel]) ?? "";
|
|
827
|
-
ignoredCache.set(rel, out.trim().length > 0);
|
|
828
|
-
}
|
|
829
|
-
return ignoredCache.get(rel);
|
|
830
|
-
};
|
|
831
|
-
const under = (list, rel) => list.some((p) => p === rel || p.startsWith(rel + "/"));
|
|
832
|
-
return {
|
|
833
|
-
of: (relPath) => {
|
|
834
|
-
const rel = relPath.replace(/\/+$/, "");
|
|
835
|
-
if (under(tracked, rel)) return under(dirty, rel) ? "changed" : "committed";
|
|
836
|
-
return isIgnored(rel) ? "ignored" : "new";
|
|
837
|
-
}
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
function relativeTo(root, path) {
|
|
841
|
-
return relative(root, path).split("\\").join("/");
|
|
842
|
-
}
|
|
843
|
-
|
|
844
812
|
// src/review.ts
|
|
845
813
|
import { spawnSync } from "child_process";
|
|
846
|
-
import { existsSync as
|
|
847
|
-
import { join as
|
|
814
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
815
|
+
import { join as join2 } from "path";
|
|
848
816
|
var EXCERPT_CHARS = 1200;
|
|
849
817
|
function tildify(p) {
|
|
850
818
|
const home = userHome();
|
|
851
819
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
852
820
|
}
|
|
853
821
|
function skillExcerpt(dir, chars) {
|
|
854
|
-
const file =
|
|
855
|
-
if (!
|
|
822
|
+
const file = join2(dir, "SKILL.md");
|
|
823
|
+
if (!existsSync2(file)) return { text: "(SKILL.md not found)", files: 0, size: 0 };
|
|
856
824
|
const content = readFileSync2(file, "utf-8").replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
|
857
825
|
let files = 0;
|
|
858
826
|
try {
|
|
@@ -923,7 +891,7 @@ Reply with a short table (skill \xB7 decision \xB7 projects \xB7 one-line reason
|
|
|
923
891
|
`;
|
|
924
892
|
}
|
|
925
893
|
function copyText(text) {
|
|
926
|
-
const savedTo =
|
|
894
|
+
const savedTo = join2(skilllibHome(), "review-prompt.md");
|
|
927
895
|
mkdirSync(skilllibHome(), { recursive: true });
|
|
928
896
|
writeFileSync(savedTo, text);
|
|
929
897
|
const tools = process.platform === "darwin" ? [["pbcopy", []]] : process.platform === "win32" ? [["clip", []]] : [
|
|
@@ -1063,15 +1031,18 @@ function versionText(r) {
|
|
|
1063
1031
|
}
|
|
1064
1032
|
var harnessName = (id) => HARNESSES.find((h) => h.id === id)?.name ?? id;
|
|
1065
1033
|
function harnessChips(visibility, installed) {
|
|
1066
|
-
return visibility.
|
|
1034
|
+
return visibility.map((v) => {
|
|
1067
1035
|
const h = HARNESSES.find((x) => x.id === v.id);
|
|
1068
1036
|
const on = installed && v.paths > 0;
|
|
1069
|
-
return
|
|
1070
|
-
{ text: ` ${h?.icon ?? "?"}`, color: on ? h?.color : color.faint, dim: !on },
|
|
1071
|
-
{ text: v.paths > 1 && installed ? "\xB2" : " ", color: color.yellow }
|
|
1072
|
-
];
|
|
1037
|
+
return { text: ` ${h?.icon ?? "?"} `, color: on ? h?.color : color.faint, dim: !on };
|
|
1073
1038
|
});
|
|
1074
1039
|
}
|
|
1040
|
+
var BACKUP_LABEL = {
|
|
1041
|
+
trash: "deleted from library",
|
|
1042
|
+
"global-backup": "unloaded from global",
|
|
1043
|
+
"tidy-backup": "duplicate replaced by a link",
|
|
1044
|
+
plugin: "plugin removed"
|
|
1045
|
+
};
|
|
1075
1046
|
var GIT_LABEL = {
|
|
1076
1047
|
committed: { text: "\u2713 committed", color: color.green },
|
|
1077
1048
|
changed: { text: "\xB1 changed", color: color.yellow },
|
|
@@ -1117,7 +1088,7 @@ function wrapAligned(line, width) {
|
|
|
1117
1088
|
function hasLinks(root, name) {
|
|
1118
1089
|
return ALL_PROJECT_DIRS.some((d) => {
|
|
1119
1090
|
try {
|
|
1120
|
-
return lstatSync(
|
|
1091
|
+
return lstatSync(join3(root, d, name)).isSymbolicLink();
|
|
1121
1092
|
} catch {
|
|
1122
1093
|
return false;
|
|
1123
1094
|
}
|
|
@@ -1282,8 +1253,8 @@ function App() {
|
|
|
1282
1253
|
const pKey = placeKey(place);
|
|
1283
1254
|
function projectSections(root) {
|
|
1284
1255
|
const where = basename3(root);
|
|
1285
|
-
const
|
|
1286
|
-
const gitOf = (path) =>
|
|
1256
|
+
const git = gitFor(root);
|
|
1257
|
+
const gitOf = (path) => git ? git.of(relativeTo(root, path)) : null;
|
|
1287
1258
|
const rows = snapshot.rows.get(root) ?? [];
|
|
1288
1259
|
const projectActions = [
|
|
1289
1260
|
{
|
|
@@ -1336,7 +1307,8 @@ function App() {
|
|
|
1336
1307
|
const dir = r.path;
|
|
1337
1308
|
const badge = stateBadge(r.state);
|
|
1338
1309
|
const edited = r.state.startsWith("edited");
|
|
1339
|
-
const
|
|
1310
|
+
const npx = r.state === "from npx skills";
|
|
1311
|
+
const inRepo = r.location === ".agents/skills" && !r.managed && !npx;
|
|
1340
1312
|
const missing = r.visibility.filter((v) => v.paths === 0);
|
|
1341
1313
|
const seenBy = r.visibility.filter((v) => v.paths > 0);
|
|
1342
1314
|
const notInLibrary = r.state === "local only" || r.state === "untracked, differs from library" || r.state === "repo skill" || r.state === "repo skill, differs from library";
|
|
@@ -1377,7 +1349,10 @@ function App() {
|
|
|
1377
1349
|
...r.installed && missing.length ? [{ label: `Make visible to ${missing.map((v) => harnessName(v.id)).join(", ")}`, hint: "adds links; one real copy", run: () => makeVisible() }] : [],
|
|
1378
1350
|
...r.installed && !r.managed && r.visibility.some((v) => v.paths > 0) && hasLinks(root, r.name) ? [{ label: "Remove links", hint: "keeps the real folder", run: removeLinks }] : []
|
|
1379
1351
|
];
|
|
1380
|
-
if (
|
|
1352
|
+
if (npx) {
|
|
1353
|
+
primary = { label: "copy to your skills", run: copyRepoSkillToLibrary };
|
|
1354
|
+
actions.push({ label: "Copy to Your skills", hint: "so other repos can use it; npx skills keeps this copy", run: copyRepoSkillToLibrary });
|
|
1355
|
+
} else if (inRepo) {
|
|
1381
1356
|
const libraryAction = r.state === "repo skill" ? { label: "Copy to Your skills", hint: "so other repos can use it; this repo keeps its copy", run: copyRepoSkillToLibrary } : r.state === "repo skill, differs from library" ? { label: "Update Your skills from this repo's copy", hint: "becomes a new version", run: copyRepoSkillToLibrary } : { label: "Already in Your skills", hint: `v${r.latest}`, run: () => say(`${r.name} matches library v${r.latest}`) };
|
|
1382
1357
|
primary = r.state === "repo skill, in library" ? missing.length ? { label: "make visible to all", run: () => makeVisible() } : void 0 : { label: "copy to your skills", run: copyRepoSkillToLibrary };
|
|
1383
1358
|
actions.push(libraryAction, ...visibilityActions);
|
|
@@ -1437,7 +1412,7 @@ function App() {
|
|
|
1437
1412
|
}
|
|
1438
1413
|
if (!inRepo) actions.push(...visibilityActions);
|
|
1439
1414
|
actions.push(
|
|
1440
|
-
{ label: "Edit SKILL.md", run: () => void edit(
|
|
1415
|
+
{ label: "Edit SKILL.md", run: () => void edit(join3(dir, "SKILL.md")) },
|
|
1441
1416
|
{ label: "Open folder", run: () => openFolder(dir) },
|
|
1442
1417
|
...projectActions
|
|
1443
1418
|
);
|
|
@@ -1446,11 +1421,11 @@ function App() {
|
|
|
1446
1421
|
return {
|
|
1447
1422
|
key,
|
|
1448
1423
|
search: r.name,
|
|
1449
|
-
group: prefixGroup(r.name),
|
|
1424
|
+
group: npx ? { key: `repo:${r.source}`, label: r.source, about: `Installed with \`npx skills add ${r.source}\` into this repo (recorded in skills-lock.json).` } : prefixGroup(r.name),
|
|
1450
1425
|
skill: { name: r.name, path: dir },
|
|
1451
1426
|
tags: [
|
|
1452
1427
|
r.installed ? "in this project" : "installable",
|
|
1453
|
-
...r.installed && r.state !== "ok" && r.state !== "repo skill, in library" ? ["needs attention"] : []
|
|
1428
|
+
...r.installed && r.state !== "ok" && r.state !== "repo skill, in library" && !npx ? ["needs attention"] : []
|
|
1454
1429
|
],
|
|
1455
1430
|
row: {
|
|
1456
1431
|
key,
|
|
@@ -1461,11 +1436,11 @@ function App() {
|
|
|
1461
1436
|
},
|
|
1462
1437
|
{ text: r.name, grow: true, color: r.installed ? void 0 : color.muted },
|
|
1463
1438
|
{ text: whereLabel.padStart(8), width: 8, color: color.faint },
|
|
1464
|
-
...gitCell(r.installed ? gitOf(r.path) : null, !!
|
|
1439
|
+
...gitCell(r.installed ? gitOf(r.path) : null, !!git),
|
|
1465
1440
|
...harnessChips(r.visibility.length ? r.visibility : enabledIds.map((id) => ({ id, paths: 0 })), r.installed),
|
|
1466
1441
|
{ text: versionText(r).padStart(8), width: 8, color: behind ? color.yellow : color.muted },
|
|
1467
1442
|
{
|
|
1468
|
-
text: r.installed && dupes(r.name) > 1 ? ` \u29C9 ${dupes(r.name)} copies` : !r.installed && globalCopy ? " \u2197 already global" : ` ${badge.icon} ${badge.label}`,
|
|
1443
|
+
text: r.installed && dupes(r.name) > 1 ? ` \u29C9 ${dupes(r.name)} copies` : !r.installed && globalCopy ? " \u2197 already global" : ` ${badge.icon} ${npx ? r.source : badge.label}`,
|
|
1469
1444
|
width: 19,
|
|
1470
1445
|
color: r.installed && dupes(r.name) > 1 ? color.red : !r.installed && globalCopy ? color.yellow : badge.color
|
|
1471
1446
|
},
|
|
@@ -1479,12 +1454,12 @@ function App() {
|
|
|
1479
1454
|
dir,
|
|
1480
1455
|
meta: [
|
|
1481
1456
|
r.installed ? `${seenBy.length ? `loaded by ${seenBy.map((v) => `${harnessName(v.id)}${v.paths > 1 ? ` (via ${v.paths} folders)` : ""}`).join(", ")}` : "not loaded by your harnesses"}${missing.length ? ` \xB7 not ${missing.map((v) => harnessName(v.id)).join(", ")}` : ""}` : "not installed",
|
|
1482
|
-
inRepo ? `committed in ${r.location}` : r.installed ? r.managed ? `v${r.version} installed in .
|
|
1483
|
-
...r.installed &&
|
|
1457
|
+
npx ? `installed by npx skills from ${r.source} into ${r.location}` : inRepo ? `committed in ${r.location}` : r.installed ? r.managed ? `v${r.version} installed in ${r.location}` : notInLibrary ? "only in this project" : "untracked copy" : "not installed",
|
|
1458
|
+
...r.installed && git ? [GIT_ABOUT[gitOf(r.path)]] : [],
|
|
1484
1459
|
r.inLibrary ? `in Your skills (v${r.latest}${origin ? `, from ${origin}` : ""})` : "not in Your skills",
|
|
1485
1460
|
`${usesText(r.name, root)} Claude Code uses here (${USAGE_DAYS}d)`
|
|
1486
1461
|
],
|
|
1487
|
-
...r.installed && dupes(r.name) > 1 ? { description: `
|
|
1462
|
+
...r.installed && dupes(r.name) > 1 ? { description: `Reaches your agents from ${dupes(r.name)} places: ${copiesOf(r.name).join(" \xB7 ")}. Health has the fix (your skill wins over plugins).` } : {}
|
|
1488
1463
|
}
|
|
1489
1464
|
};
|
|
1490
1465
|
};
|
|
@@ -1535,7 +1510,7 @@ function App() {
|
|
|
1535
1510
|
{ text: "\u25C8 ", color: color.accentDim },
|
|
1536
1511
|
{ text: m.name, grow: true },
|
|
1537
1512
|
{ text: (m.kind === "global" || m.kind === "skills.sh" ? "global" : m.kind).padStart(8), width: 8, color: color.faint },
|
|
1538
|
-
...gitCell(null, !!
|
|
1513
|
+
...gitCell(null, !!git),
|
|
1539
1514
|
...harnessChips(enabledIds.map((id) => ({ id, paths: m.harnesses.includes(id) ? 1 : 0 })), true),
|
|
1540
1515
|
{ text: "", width: 8 },
|
|
1541
1516
|
{
|
|
@@ -1556,7 +1531,7 @@ function App() {
|
|
|
1556
1531
|
`${m.kind}: ${m.origin}`,
|
|
1557
1532
|
kept ? "yours: kept global on purpose" : m.movable ? "yours: the cleanup wizard can scope it to projects" : "managed by the vendor"
|
|
1558
1533
|
],
|
|
1559
|
-
description: dupes(m.name) > 1 ? `
|
|
1534
|
+
description: dupes(m.name) > 1 ? `Reaches your agents from ${dupes(m.name)} places: ${copiesOf(m.name).join(" \xB7 ")}. Health has the fix (your skill wins over plugins).` : sourceGroup(m)?.about
|
|
1560
1535
|
}
|
|
1561
1536
|
};
|
|
1562
1537
|
};
|
|
@@ -1601,12 +1576,23 @@ function App() {
|
|
|
1601
1576
|
preview: {
|
|
1602
1577
|
title: "Make every skill usable by all your agents",
|
|
1603
1578
|
meta: [`${plural2(partial.length, "skill")} missing for ${missingAgents.map(harnessName).join(", ")}`],
|
|
1604
|
-
description: `Adds relative symlinks so each agent's folder reaches the one real copy (e.g. .claude/skills/<name> \u2192 .agents/skills/<name>). The repo's files aren't changed; folders git tracks need your OK first. Cursor
|
|
1579
|
+
description: `Adds relative symlinks so each agent's folder reaches the one real copy (e.g. .claude/skills/<name> \u2192 .agents/skills/<name>). The repo's files aren't changed; folders git tracks need your OK first. Cursor lists a skill it reaches through several folders once.`
|
|
1605
1580
|
}
|
|
1606
1581
|
}
|
|
1607
1582
|
] : [];
|
|
1608
1583
|
const fromLibrary = local.filter((r) => r.managed || r.state === "untracked copy of library skill");
|
|
1609
|
-
const
|
|
1584
|
+
const fromNpx = local.filter((r) => r.state === "from npx skills");
|
|
1585
|
+
const repoOwn = local.filter((r) => !fromLibrary.includes(r) && !fromNpx.includes(r));
|
|
1586
|
+
const copyAllToLibrary = (members) => [
|
|
1587
|
+
{
|
|
1588
|
+
label: `Copy all ${members.length} into your library`,
|
|
1589
|
+
hint: "the repo keeps its copies",
|
|
1590
|
+
run: () => act(() => {
|
|
1591
|
+
const added = members.map((m) => importSkill(m.skill.path, { force: true })).filter((r) => r.status !== "unchanged").length;
|
|
1592
|
+
return ok(`${plural2(added, "skill")} copied into your library`);
|
|
1593
|
+
})
|
|
1594
|
+
}
|
|
1595
|
+
];
|
|
1610
1596
|
const yours = globals.filter((m) => m.movable);
|
|
1611
1597
|
const keptYours = yours.filter((m) => snapshot.keptGlobal.has(m.name));
|
|
1612
1598
|
const unreviewed = yours.filter((m) => !snapshot.keptGlobal.has(m.name));
|
|
@@ -1649,20 +1635,8 @@ function App() {
|
|
|
1649
1635
|
}
|
|
1650
1636
|
]
|
|
1651
1637
|
},
|
|
1652
|
-
{
|
|
1653
|
-
|
|
1654
|
-
items: repoOwn.map(item),
|
|
1655
|
-
groupActions: (members) => [
|
|
1656
|
-
{
|
|
1657
|
-
label: `Copy all ${members.length} into your library`,
|
|
1658
|
-
hint: "the repo keeps its copies",
|
|
1659
|
-
run: () => act(() => {
|
|
1660
|
-
const added = members.map((m) => importSkill(m.skill.path, { force: true })).filter((r) => r.status !== "unchanged").length;
|
|
1661
|
-
return ok(`${plural2(added, "skill")} copied into your library`);
|
|
1662
|
-
})
|
|
1663
|
-
}
|
|
1664
|
-
]
|
|
1665
|
-
},
|
|
1638
|
+
{ title: `From npx skills (${fromNpx.length})`, items: fromNpx.map(item), groupActions: copyAllToLibrary },
|
|
1639
|
+
{ title: `The repo's own (${repoOwn.length})`, items: repoOwn.map(item), groupActions: copyAllToLibrary },
|
|
1666
1640
|
{
|
|
1667
1641
|
title: `\u26A0 Global \xB7 yours, not reviewed, loaded in every repo (${unreviewed.length})`,
|
|
1668
1642
|
items: [...cleanupItem, ...unreviewed.map(globalItem)],
|
|
@@ -1754,7 +1728,7 @@ function App() {
|
|
|
1754
1728
|
primary: { label: "choose projects", run: toggleProjects.run },
|
|
1755
1729
|
actions: [
|
|
1756
1730
|
toggleProjects,
|
|
1757
|
-
{ label: "Edit SKILL.md", hint: "saving creates a new version", run: () => void edit(
|
|
1731
|
+
{ label: "Edit SKILL.md", hint: "saving creates a new version", run: () => void edit(join3(librarySkillDir(l.name), "SKILL.md")) },
|
|
1758
1732
|
{ label: "Open folder", run: () => openFolder(librarySkillDir(l.name)) },
|
|
1759
1733
|
{
|
|
1760
1734
|
label: "Copy review prompt",
|
|
@@ -2182,7 +2156,12 @@ function App() {
|
|
|
2182
2156
|
title: "Issues",
|
|
2183
2157
|
items: snapshot.issues.map((issue) => {
|
|
2184
2158
|
const fix = issue.fix;
|
|
2185
|
-
const
|
|
2159
|
+
const result = (c) => {
|
|
2160
|
+
const r = runFix(c);
|
|
2161
|
+
return r.ok ? ok(r.message) : warn(r.message);
|
|
2162
|
+
};
|
|
2163
|
+
const choices = (issue.choices ?? []).map((c) => ({ label: c.label, hint: c.hint, run: () => act(() => result(c)) }));
|
|
2164
|
+
const run = fix ? () => confirm(`${fix.label}?`, () => result(fix)) : choices.length ? () => setOverlay({ type: "menu", title: issue.title, index: 0, build: () => choices }) : void 0;
|
|
2186
2165
|
return {
|
|
2187
2166
|
key: issue.id,
|
|
2188
2167
|
search: issue.title,
|
|
@@ -2196,10 +2175,10 @@ function App() {
|
|
|
2196
2175
|
]
|
|
2197
2176
|
},
|
|
2198
2177
|
primary: run ? { label: "fix", run } : void 0,
|
|
2199
|
-
actions:
|
|
2178
|
+
actions: fix && run ? [{ label: fix.label, run }] : choices,
|
|
2200
2179
|
preview: {
|
|
2201
2180
|
title: issue.title,
|
|
2202
|
-
meta: [issue.severity, fix ? `fix: ${fix.label.toLowerCase()}` : "no automatic fix"],
|
|
2181
|
+
meta: [issue.severity, fix ? `fix: ${fix.label.toLowerCase()}` : choices.length ? `${choices.length} ways to fix it: pick one` : "no automatic fix"],
|
|
2203
2182
|
description: issue.detail
|
|
2204
2183
|
}
|
|
2205
2184
|
};
|
|
@@ -2209,7 +2188,7 @@ function App() {
|
|
|
2209
2188
|
title: "Moved out \xB7 restorable",
|
|
2210
2189
|
items: snapshot.backups.map((b) => {
|
|
2211
2190
|
const restore = () => act(() => {
|
|
2212
|
-
const r = restoreBackup(b);
|
|
2191
|
+
const r = b.kind === "plugin" ? restorePlugin(b) : restoreBackup(b);
|
|
2213
2192
|
return r.ok ? ok(`${b.name} restored to ${tildify2(r.to)}`) : warn(`Can't restore ${b.name}: ${r.reason}`);
|
|
2214
2193
|
});
|
|
2215
2194
|
return {
|
|
@@ -2220,7 +2199,7 @@ function App() {
|
|
|
2220
2199
|
key: b.path,
|
|
2221
2200
|
cells: [
|
|
2222
2201
|
{ text: b.name, grow: true },
|
|
2223
|
-
{ text: b.kind
|
|
2202
|
+
{ text: ` ${BACKUP_LABEL[b.kind]}`, color: color.muted },
|
|
2224
2203
|
{ text: ` ${b.movedAt}`, color: color.faint }
|
|
2225
2204
|
]
|
|
2226
2205
|
},
|
|
@@ -2229,7 +2208,7 @@ function App() {
|
|
|
2229
2208
|
preview: {
|
|
2230
2209
|
title: b.name,
|
|
2231
2210
|
dir: b.path,
|
|
2232
|
-
meta: [b.kind === "
|
|
2211
|
+
meta: [b.kind === "plugin" ? "Claude Code plugin; restoring reinstalls it" : `from ${tildify2(b.from)}`, `moved ${b.movedAt}`]
|
|
2233
2212
|
}
|
|
2234
2213
|
};
|
|
2235
2214
|
})
|
|
@@ -2280,7 +2259,7 @@ function App() {
|
|
|
2280
2259
|
if (!r.ok) return say(`Can't create ${value}: ${r.reason}`, false);
|
|
2281
2260
|
setSelected((s) => ({ ...s, "right:library": value.trim() }));
|
|
2282
2261
|
reload();
|
|
2283
|
-
void edit(
|
|
2262
|
+
void edit(join3(r.dir, "SKILL.md"));
|
|
2284
2263
|
}
|
|
2285
2264
|
});
|
|
2286
2265
|
}
|
|
@@ -2472,7 +2451,7 @@ function App() {
|
|
|
2472
2451
|
"Skills your harnesses load in EVERY project (~/.claude/skills, ~/.agents/skills, plugins\u2026). Move yours into the library and add them only where needed."
|
|
2473
2452
|
],
|
|
2474
2453
|
help: ["Help", "Where skills come from, and how Claude Code, Cursor, Codex and Zed find and use them. Press enter (or ? anywhere)."],
|
|
2475
|
-
health: ["Health", "Broken links, skills
|
|
2454
|
+
health: ["Health", "Broken links, duplicates, copies that differ, plugins that repeat your skills, skills some agents can't reach, out-of-date projects \u2014 and everything skilllib moved out, ready to restore."],
|
|
2476
2455
|
settings: ["Settings", `Harnesses: ${snapshot.harnesses.map(harnessName).join(", ") || "none"} \xB7 Folders: ${snapshot.roots.map(tildify2).join(", ") || "none yet"}`]
|
|
2477
2456
|
};
|
|
2478
2457
|
const [title, description] = about[place.kind] ?? ["", ""];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilllib",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
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",
|