skilllib 1.11.7 → 1.12.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 +19 -5
- package/dist/{chunk-PKQPUIN3.js → chunk-LORSEDNW.js} +805 -601
- package/dist/index.js +6 -4
- package/dist/json.d.cts +6 -0
- package/dist/json.d.ts +6 -0
- package/dist/{tui-B5PF3J3E.js → tui-3QD6RQGL.js} +165 -135
- package/package.json +1 -1
- package/skills/skilllib/SKILL.md +3 -3
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
unhide,
|
|
32
32
|
update,
|
|
33
33
|
usage
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-LORSEDNW.js";
|
|
35
35
|
|
|
36
36
|
// src/index.ts
|
|
37
37
|
import { readFileSync } from "fs";
|
|
@@ -53,6 +53,8 @@ Set up:
|
|
|
53
53
|
In a project (a git repo or a folder with skilllib.json; never your home folder):
|
|
54
54
|
skilllib status This project's skills, which agents load them, and issues
|
|
55
55
|
skilllib add <name>... Install library skills where your harnesses look
|
|
56
|
+
(pinned in skilllib.json when the repo has one, else in your
|
|
57
|
+
personal pins, kept out of git; --personal / --shared to choose)
|
|
56
58
|
skilllib remove <name>... Remove skills from this project
|
|
57
59
|
skilllib link [--all] Make every skill here usable by all your agents (adds links)
|
|
58
60
|
skilllib tidy [--all|--global] One real copy per skill, plus only the links your agents need
|
|
@@ -96,7 +98,7 @@ var FLAGS_OF = {
|
|
|
96
98
|
"": ["days"],
|
|
97
99
|
ui: [],
|
|
98
100
|
status: ["days"],
|
|
99
|
-
add: ["force", "allow-tracked"],
|
|
101
|
+
add: ["force", "allow-tracked", "personal", "shared"],
|
|
100
102
|
remove: ["force"],
|
|
101
103
|
sync: ["all", "force"],
|
|
102
104
|
update: ["all", "force", "allow-tracked"],
|
|
@@ -138,10 +140,10 @@ async function main() {
|
|
|
138
140
|
if (!["--version", "-v", "version", "help", "--help", "-h"].includes(command ?? "")) refreshAgentSkill();
|
|
139
141
|
switch (command) {
|
|
140
142
|
case void 0:
|
|
141
|
-
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-
|
|
143
|
+
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-3QD6RQGL.js")).tui() : overview(args);
|
|
142
144
|
case "ui":
|
|
143
145
|
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("`skilllib ui` needs an interactive terminal. Agents and scripts: use `skilllib status --json`.");
|
|
144
|
-
return (await import("./tui-
|
|
146
|
+
return (await import("./tui-3QD6RQGL.js")).tui();
|
|
145
147
|
case "status":
|
|
146
148
|
return status(args);
|
|
147
149
|
case "add":
|
package/dist/json.d.cts
CHANGED
|
@@ -33,6 +33,8 @@ type Change = {
|
|
|
33
33
|
inTheWay?: string[];
|
|
34
34
|
/** Where overwritten local edits went (~/.skilllib/edit-backup/…); `skilllib restore` puts them back. */
|
|
35
35
|
backedUp?: string;
|
|
36
|
+
/** The pin moved: to your personal pins, or to the repo's skilllib.json. */
|
|
37
|
+
moved?: "personal" | "shared";
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -67,6 +69,8 @@ type StatusSkillGroup = {
|
|
|
67
69
|
git?: GitState;
|
|
68
70
|
/** Only when some of your agents don't load it here. */
|
|
69
71
|
agents?: HarnessId[];
|
|
72
|
+
/** In your personal pins (~/.skilllib/pins): only on your machines, kept out of git. */
|
|
73
|
+
personal?: true;
|
|
70
74
|
};
|
|
71
75
|
/** Global skills that come from the same place and load in the same agents. */
|
|
72
76
|
type StatusGlobalGroup = {
|
|
@@ -145,6 +149,8 @@ type ProjectsJson = {
|
|
|
145
149
|
type ChangeJson = Change & {
|
|
146
150
|
/** Where an installed, updated or reset skill now is: the real copy first, then its links. */
|
|
147
151
|
dirs?: string[];
|
|
152
|
+
/** Pinned in your personal pins, kept out of git: nothing to commit. */
|
|
153
|
+
personal?: true;
|
|
148
154
|
};
|
|
149
155
|
/** `skilllib add|remove --json`, and `sync|update --json` without --all. */
|
|
150
156
|
type ChangesJson = ChangeJson[];
|
package/dist/json.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ type Change = {
|
|
|
33
33
|
inTheWay?: string[];
|
|
34
34
|
/** Where overwritten local edits went (~/.skilllib/edit-backup/…); `skilllib restore` puts them back. */
|
|
35
35
|
backedUp?: string;
|
|
36
|
+
/** The pin moved: to your personal pins, or to the repo's skilllib.json. */
|
|
37
|
+
moved?: "personal" | "shared";
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -67,6 +69,8 @@ type StatusSkillGroup = {
|
|
|
67
69
|
git?: GitState;
|
|
68
70
|
/** Only when some of your agents don't load it here. */
|
|
69
71
|
agents?: HarnessId[];
|
|
72
|
+
/** In your personal pins (~/.skilllib/pins): only on your machines, kept out of git. */
|
|
73
|
+
personal?: true;
|
|
70
74
|
};
|
|
71
75
|
/** Global skills that come from the same place and load in the same agents. */
|
|
72
76
|
type StatusGlobalGroup = {
|
|
@@ -145,6 +149,8 @@ type ProjectsJson = {
|
|
|
145
149
|
type ChangeJson = Change & {
|
|
146
150
|
/** Where an installed, updated or reset skill now is: the real copy first, then its links. */
|
|
147
151
|
dirs?: string[];
|
|
152
|
+
/** Pinned in your personal pins, kept out of git: nothing to commit. */
|
|
153
|
+
personal?: true;
|
|
148
154
|
};
|
|
149
155
|
/** `skilllib add|remove --json`, and `sync|update --json` without --all. */
|
|
150
156
|
type ChangesJson = ChangeJson[];
|
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
versionHistory,
|
|
85
85
|
visibleProjects,
|
|
86
86
|
withoutForceHint
|
|
87
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-LORSEDNW.js";
|
|
88
88
|
|
|
89
89
|
// src/tui/index.tsx
|
|
90
90
|
import { render } from "ink";
|
|
@@ -347,6 +347,7 @@ function shortOf(found) {
|
|
|
347
347
|
unavailable: "Pinned, not in your library",
|
|
348
348
|
pin: "Pinned version not in your library",
|
|
349
349
|
"out-of-sync": "Not the pinned version",
|
|
350
|
+
shadowed: "Pinned twice",
|
|
350
351
|
update: "Out of date",
|
|
351
352
|
uncommitted: "Commit it first",
|
|
352
353
|
local: "Not in your library",
|
|
@@ -443,6 +444,17 @@ function globalFixes(m) {
|
|
|
443
444
|
{ label: "Delete it", preview: `Remove ${m.where}/${m.name}. A backup is kept in Settings \u203A Backups.`, run: (w) => w.ops.deleteGlobal(m) }
|
|
444
445
|
];
|
|
445
446
|
}
|
|
447
|
+
function pinFix(w, repo, s) {
|
|
448
|
+
if (s.source !== "lib" || !w.library.some((l) => l.name === s.name)) return null;
|
|
449
|
+
if (s.personal)
|
|
450
|
+
return { label: "Pin it for the team", preview: `Move ${s.name}'s pin into ${repo}'s skilllib.json. Git will see skilllib.json and ${s.dir}/${s.name}: commit them to share it.`, run: (w2) => w2.ops.pin(repo, s.name, false) };
|
|
451
|
+
if (s.inGit || isShared(s.git)) return null;
|
|
452
|
+
return {
|
|
453
|
+
label: "Make it personal",
|
|
454
|
+
preview: `Move ${s.name}'s pin out of ${repo}'s skilllib.json into your personal pins, and keep its copy out of git.${project(w, repo).manifest === "committed" ? " Git will see skilllib.json change: once that's committed, teammates no longer get it." : ""}`,
|
|
455
|
+
run: (w2) => w2.ops.pin(repo, s.name, true)
|
|
456
|
+
};
|
|
457
|
+
}
|
|
446
458
|
function machineActions(m) {
|
|
447
459
|
if (m.broken) return [];
|
|
448
460
|
return m.source === "global" ? globalFixes(m) : m.source === "skilllib" ? [removeAgentSkillFix()] : [];
|
|
@@ -759,7 +771,7 @@ var bySeverity = (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]
|
|
|
759
771
|
function tag(u) {
|
|
760
772
|
const s = u.local;
|
|
761
773
|
if (s?.source === "lib") {
|
|
762
|
-
return [`lib v${s.version}`, color.accent];
|
|
774
|
+
return [s.personal ? `personal v${s.version}` : `lib v${s.version}`, color.accent];
|
|
763
775
|
}
|
|
764
776
|
if (s) return [s.source, color.blue];
|
|
765
777
|
if (u.source === "global") return [u.machine?.kept ? "global \u2713" : "global", color.yellow];
|
|
@@ -951,7 +963,7 @@ function skillActions(w, p, u, ui) {
|
|
|
951
963
|
// Its folder gone: all that's left to remove is the skilllib.json entry (the label Health offers too).
|
|
952
964
|
label: s?.missing ? "Remove it from skilllib.json" : "Remove from repo",
|
|
953
965
|
preview: (s?.missing ? `${p}'s skilllib.json stops pinning ${u.name}. There's no folder to delete.` : s?.source === "lib" ? `Delete ${s.dir}/${u.name} in ${p}.${inLibrary ? " Your library keeps it." : ""}` : `Move ${s?.dir}/${u.name} in ${p} to Settings \u203A Backups.`) + (s?.others ? ` The copy in ${s.others.join(", ")} stays.` : "") + // A managed skill's removal edits skilllib.json too.
|
|
954
|
-
(gitSees(s) || (s?.source === "lib" && project(w, p).manifest === "committed" ? " Git will see skilllib.json change." : "")),
|
|
966
|
+
(gitSees(s) || (s?.source === "lib" && !s.personal && project(w, p).manifest === "committed" ? " Git will see skilllib.json change." : "")),
|
|
955
967
|
run: (w2) => w2.ops.remove(p, u.name)
|
|
956
968
|
};
|
|
957
969
|
if (s?.source === "lib")
|
|
@@ -962,6 +974,7 @@ function skillActions(w, p, u, ui) {
|
|
|
962
974
|
{ label: "Edit in library", action: () => ui.edit(w.ops.libraryFile(u.name)) },
|
|
963
975
|
versionsOption(w, p, u.name, s.version, ui)
|
|
964
976
|
] : [],
|
|
977
|
+
...((f) => f ? [fixOption(f)] : [])(pinFix(w, p, s)),
|
|
965
978
|
fixOption(remove),
|
|
966
979
|
reviewOption(w, u.name, ui, p)
|
|
967
980
|
];
|
|
@@ -1564,34 +1577,39 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1564
1577
|
if (isNameChar(input, key)) return setFocus("list"), setQuery(query + input.toLowerCase());
|
|
1565
1578
|
if (key.backspace || key.delete) return setQuery(query.slice(0, -1));
|
|
1566
1579
|
if (input === "?") return setModal({ kind: "help" });
|
|
1567
|
-
if (focus === "detail")
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
if (
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
if (
|
|
1591
|
-
if (key
|
|
1592
|
-
|
|
1593
|
-
return;
|
|
1580
|
+
if (focus === "detail") return detailKeys(input, key);
|
|
1581
|
+
if (focus === "sidebar") return sidebarKeys(key);
|
|
1582
|
+
return listKeys(input, key);
|
|
1583
|
+
});
|
|
1584
|
+
function detailKeys(input, key) {
|
|
1585
|
+
const lines = (detail?.body ?? []).flatMap((l) => l.row ? [l.row] : l.option !== void 0 ? [[l.option]] : []);
|
|
1586
|
+
const at = lines.findIndex((r) => r.includes(detailCursor));
|
|
1587
|
+
const col = at < 0 ? 0 : lines[at].indexOf(detailCursor);
|
|
1588
|
+
const goLine = (i) => lines[i] && setDetailCursor(lines[i][Math.min(col, lines[i].length - 1)]);
|
|
1589
|
+
if (key.escape) return setFocus("list");
|
|
1590
|
+
if (key.upArrow) return goLine(Math.max(0, at - 1));
|
|
1591
|
+
if (key.downArrow) return goLine(at + 1);
|
|
1592
|
+
if (key.leftArrow) return col > 0 ? setDetailCursor(lines[at][col - 1]) : setFocus("list");
|
|
1593
|
+
if (key.rightArrow) return lines[at]?.[col + 1] !== void 0 ? setDetailCursor(lines[at][col + 1]) : void 0;
|
|
1594
|
+
if ((key.return || input === " ") && detail?.options[detailCursor]) return choose(detail.options[detailCursor]);
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
function sidebarKeys(key) {
|
|
1598
|
+
if (key.upArrow || key.downArrow) {
|
|
1599
|
+
const d = key.upArrow ? -1 : 1;
|
|
1600
|
+
let i = sideAt + d;
|
|
1601
|
+
while (sideRows[i]?.header) i += d;
|
|
1602
|
+
const next = sideRows[i];
|
|
1603
|
+
if (!next) return;
|
|
1604
|
+
if (next.key === "help") return setOnHelp(true);
|
|
1605
|
+
return next.go(), setOnHelp(false), setFocus("sidebar");
|
|
1594
1606
|
}
|
|
1607
|
+
if (key.return && onHelp) return setModal({ kind: "help" });
|
|
1608
|
+
if (key.rightArrow || key.return) return setOnHelp(false), setFocus("list");
|
|
1609
|
+
if (key.escape) return setSideQuery("");
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
function listKeys(input, key) {
|
|
1595
1613
|
if (!sidebar && (key.leftArrow || key.rightArrow)) {
|
|
1596
1614
|
const i = PLACES.findIndex(([p]) => p === place) + (key.leftArrow ? -1 : 1);
|
|
1597
1615
|
if (PLACES[i]) goPlace(PLACES[i][0]);
|
|
@@ -1625,44 +1643,13 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1625
1643
|
if (place === "projects" && !open) return key.return ? (setOpen(current.name), setQuery(""), setChip("all")) : void 0;
|
|
1626
1644
|
if (key.return && detail) return setFocus("detail"), setDetailCursor(0);
|
|
1627
1645
|
if (input === " ") return fixSelected();
|
|
1628
|
-
}
|
|
1646
|
+
}
|
|
1629
1647
|
function modalKeys(input, key) {
|
|
1630
1648
|
const m = modal;
|
|
1631
1649
|
if (m.kind === "help") return setModal(null);
|
|
1632
|
-
if (m.kind === "agents")
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
if (key.downArrow) return setModal({ ...m, cursor: Math.min(HARNESSES.length - 1, m.cursor + 1) });
|
|
1636
|
-
if (input === " ") {
|
|
1637
|
-
const id = HARNESSES[m.cursor].id;
|
|
1638
|
-
return setModal({ ...m, selected: m.selected.includes(id) ? m.selected.filter((x) => x !== id) : [...m.selected, id] });
|
|
1639
|
-
}
|
|
1640
|
-
if (key.return) {
|
|
1641
|
-
setModal(null);
|
|
1642
|
-
apply((w) => w.ops.setAgents(m.selected));
|
|
1643
|
-
m.then?.();
|
|
1644
|
-
}
|
|
1645
|
-
return;
|
|
1646
|
-
}
|
|
1647
|
-
if (m.kind === "input") {
|
|
1648
|
-
if (key.escape) return setModal(null);
|
|
1649
|
-
if (key.return) {
|
|
1650
|
-
const value = m.value.trim() || m.placeholder;
|
|
1651
|
-
setModal(null);
|
|
1652
|
-
return value ? m.submit(value) : void 0;
|
|
1653
|
-
}
|
|
1654
|
-
if (key.backspace || key.delete) return setModal({ ...m, value: m.value.slice(0, -1) });
|
|
1655
|
-
if (key.ctrl && input === "u") return setModal({ ...m, value: "" });
|
|
1656
|
-
if (input && !key.ctrl && !key.meta) return setModal({ ...m, value: m.value + input });
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1659
|
-
if (m.kind === "menu") {
|
|
1660
|
-
if (key.escape) return setModal(null);
|
|
1661
|
-
if (key.upArrow) return setModal({ ...m, cursor: Math.max(0, m.cursor - 1) });
|
|
1662
|
-
if (key.downArrow) return setModal({ ...m, cursor: Math.min(m.options.length - 1, m.cursor + 1) });
|
|
1663
|
-
if (key.return && m.options[m.cursor]) return setModal(null), choose(m.options[m.cursor]);
|
|
1664
|
-
return;
|
|
1665
|
-
}
|
|
1650
|
+
if (m.kind === "agents") return agentsKeys(m, input, key);
|
|
1651
|
+
if (m.kind === "input") return inputKeys(m, input, key);
|
|
1652
|
+
if (m.kind === "menu") return menuKeys(m, key);
|
|
1666
1653
|
if (key.escape) return setModal(null), m.kind === "confirm" && m.toast && setToast(m.toast);
|
|
1667
1654
|
if (m.kind === "confirm") {
|
|
1668
1655
|
if (key.return || input === "y") {
|
|
@@ -1672,78 +1659,115 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1672
1659
|
}
|
|
1673
1660
|
return;
|
|
1674
1661
|
}
|
|
1675
|
-
if (m.kind === "repos")
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
return setModal({ ...m, picked });
|
|
1687
|
-
}
|
|
1688
|
-
if (key.return) {
|
|
1689
|
-
if (!m.picked.size && !m.fix.allowNone) return setToast("Tick at least one repo with space, or esc to cancel");
|
|
1690
|
-
setModal(null);
|
|
1691
|
-
setFocus("list");
|
|
1692
|
-
apply((w) => m.fix.pickRepos(w, [...m.picked]));
|
|
1693
|
-
}
|
|
1694
|
-
return;
|
|
1662
|
+
if (m.kind === "repos") return reposKeys(m, input, key);
|
|
1663
|
+
if (m.kind === "fixall") return fixallKeys(m, input, key);
|
|
1664
|
+
if (m.kind === "add") return addKeys(m, input, key);
|
|
1665
|
+
}
|
|
1666
|
+
function agentsKeys(m, input, key) {
|
|
1667
|
+
if (key.escape && !m.then) return setModal(null);
|
|
1668
|
+
if (key.upArrow) return setModal({ ...m, cursor: Math.max(0, m.cursor - 1) });
|
|
1669
|
+
if (key.downArrow) return setModal({ ...m, cursor: Math.min(HARNESSES.length - 1, m.cursor + 1) });
|
|
1670
|
+
if (input === " ") {
|
|
1671
|
+
const id = HARNESSES[m.cursor].id;
|
|
1672
|
+
return setModal({ ...m, selected: m.selected.includes(id) ? m.selected.filter((x) => x !== id) : [...m.selected, id] });
|
|
1695
1673
|
}
|
|
1696
|
-
if (
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
if (key.return) {
|
|
1701
|
-
setModal(null);
|
|
1702
|
-
const chosen = m.items.filter((it) => it.on);
|
|
1703
|
-
apply((w) => runAll(w, chosen.map((it) => it.fix)));
|
|
1704
|
-
}
|
|
1705
|
-
return;
|
|
1674
|
+
if (key.return) {
|
|
1675
|
+
setModal(null);
|
|
1676
|
+
apply((w) => w.ops.setAgents(m.selected));
|
|
1677
|
+
m.then?.();
|
|
1706
1678
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
if (key.upArrow) return step(-1);
|
|
1716
|
-
if (key.downArrow) return step(1);
|
|
1717
|
-
if (key.backspace || key.delete) return setModal({ ...m, query: m.query.slice(0, -1), cursor: 0 });
|
|
1718
|
-
const members = rows[cursor2]?.members;
|
|
1719
|
-
if (input === " " && members) {
|
|
1720
|
-
const picked = new Set(m.picked);
|
|
1721
|
-
const all2 = members.every((n) => picked.has(n));
|
|
1722
|
-
for (const n of members) all2 ? picked.delete(n) : picked.add(n);
|
|
1723
|
-
return setModal({ ...m, picked });
|
|
1724
|
-
}
|
|
1725
|
-
if (key.return && m.target && m.picked.size) {
|
|
1726
|
-
const [target, names] = [m.target, [...m.picked]];
|
|
1727
|
-
setModal(null);
|
|
1728
|
-
return apply((w) => w.ops.addTo([target], names));
|
|
1729
|
-
}
|
|
1730
|
-
if (key.return && rows[cursor2]?.agent) {
|
|
1731
|
-
setModal(null);
|
|
1732
|
-
return ui.copy(writeSkillPrompt(world, m.query, m.target), "a prompt for an agent to write the skill");
|
|
1733
|
-
}
|
|
1734
|
-
if (key.return && rows[cursor2]?.run) {
|
|
1735
|
-
const row = rows[cursor2];
|
|
1736
|
-
if (row.create && !/^[a-z0-9]/.test(m.query)) return setToast("Type the new skill's name first");
|
|
1737
|
-
setModal(null);
|
|
1738
|
-
if (!row.create) return apply(row.run);
|
|
1739
|
-
const created = row.run(world);
|
|
1740
|
-
if (isFailure(created)) return setToast(`\u2717 ${said2(created)}`);
|
|
1741
|
-
return edit(world.ops.libraryFile(m.query), row.afterEdit);
|
|
1742
|
-
}
|
|
1743
|
-
if (isNameChar(input, key)) return setModal({ ...m, query: m.query + input.toLowerCase(), cursor: 0 });
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
function inputKeys(m, input, key) {
|
|
1682
|
+
if (key.escape) return setModal(null);
|
|
1683
|
+
if (key.return) {
|
|
1684
|
+
const value = m.value.trim() || m.placeholder;
|
|
1685
|
+
setModal(null);
|
|
1686
|
+
return value ? m.submit(value) : void 0;
|
|
1744
1687
|
}
|
|
1688
|
+
if (key.backspace || key.delete) return setModal({ ...m, value: m.value.slice(0, -1) });
|
|
1689
|
+
if (key.ctrl && input === "u") return setModal({ ...m, value: "" });
|
|
1690
|
+
if (input && !key.ctrl && !key.meta) return setModal({ ...m, value: m.value + input });
|
|
1691
|
+
return;
|
|
1745
1692
|
}
|
|
1746
|
-
|
|
1693
|
+
function menuKeys(m, key) {
|
|
1694
|
+
if (key.escape) return setModal(null);
|
|
1695
|
+
if (key.upArrow) return setModal({ ...m, cursor: Math.max(0, m.cursor - 1) });
|
|
1696
|
+
if (key.downArrow) return setModal({ ...m, cursor: Math.min(m.options.length - 1, m.cursor + 1) });
|
|
1697
|
+
if (key.return && m.options[m.cursor]) return setModal(null), choose(m.options[m.cursor]);
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
function reposKeys(m, input, key) {
|
|
1701
|
+
const repos = (m.only ?? world.projects.map((p) => p.name)).filter((r) => !m.query || matches(r, m.query));
|
|
1702
|
+
if (isNameChar(input, key)) return setModal({ ...m, query: m.query + input.toLowerCase(), cursor: 0 });
|
|
1703
|
+
if (key.backspace || key.delete) return setModal({ ...m, query: m.query.slice(0, -1), cursor: 0 });
|
|
1704
|
+
if (key.upArrow) return setModal({ ...m, cursor: Math.max(0, m.cursor - 1) });
|
|
1705
|
+
if (key.downArrow) return setModal({ ...m, cursor: Math.min(repos.length - 1, m.cursor + 1) });
|
|
1706
|
+
if (input === " ") {
|
|
1707
|
+
const picked = new Set(m.picked);
|
|
1708
|
+
const r = repos[m.cursor];
|
|
1709
|
+
if (!r) return;
|
|
1710
|
+
if (!picked.delete(r)) picked.add(r);
|
|
1711
|
+
return setModal({ ...m, picked });
|
|
1712
|
+
}
|
|
1713
|
+
if (key.return) {
|
|
1714
|
+
if (!m.picked.size && !m.fix.allowNone) return setToast("Tick at least one repo with space, or esc to cancel");
|
|
1715
|
+
setModal(null);
|
|
1716
|
+
setFocus("list");
|
|
1717
|
+
apply((w) => m.fix.pickRepos(w, [...m.picked]));
|
|
1718
|
+
}
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
function fixallKeys(m, input, key) {
|
|
1722
|
+
if (key.upArrow) return setModal({ ...m, cursor: Math.max(0, m.cursor - 1) });
|
|
1723
|
+
if (key.downArrow) return setModal({ ...m, cursor: Math.min(m.items.length - 1, m.cursor + 1) });
|
|
1724
|
+
if (input === " ") return setModal({ ...m, items: m.items.map((it, i) => i === m.cursor ? { ...it, on: !it.on } : it) });
|
|
1725
|
+
if (key.return) {
|
|
1726
|
+
setModal(null);
|
|
1727
|
+
const chosen = m.items.filter((it) => it.on);
|
|
1728
|
+
apply((w) => runAll(w, chosen.map((it) => it.fix)));
|
|
1729
|
+
}
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
function addKeys(m, input, key) {
|
|
1733
|
+
const rows = addRows(world, m.target, m.query);
|
|
1734
|
+
const cursor2 = addCursor(rows, m.cursor);
|
|
1735
|
+
const step = (d) => {
|
|
1736
|
+
let i = cursor2 + d;
|
|
1737
|
+
while (rows[i]?.header) i += d;
|
|
1738
|
+
return rows[i] ? setModal({ ...m, cursor: i }) : void 0;
|
|
1739
|
+
};
|
|
1740
|
+
if (key.upArrow) return step(-1);
|
|
1741
|
+
if (key.downArrow) return step(1);
|
|
1742
|
+
if (key.backspace || key.delete) return setModal({ ...m, query: m.query.slice(0, -1), cursor: 0 });
|
|
1743
|
+
const members = rows[cursor2]?.members;
|
|
1744
|
+
if (input === " " && members) {
|
|
1745
|
+
const picked = new Set(m.picked);
|
|
1746
|
+
const all2 = members.every((n) => picked.has(n));
|
|
1747
|
+
for (const n of members) all2 ? picked.delete(n) : picked.add(n);
|
|
1748
|
+
return setModal({ ...m, picked });
|
|
1749
|
+
}
|
|
1750
|
+
if (key.return && m.target && m.picked.size) {
|
|
1751
|
+
const [target, names] = [m.target, [...m.picked]];
|
|
1752
|
+
setModal(null);
|
|
1753
|
+
return apply((w) => w.ops.addTo([target], names));
|
|
1754
|
+
}
|
|
1755
|
+
if (key.return && rows[cursor2]?.agent) {
|
|
1756
|
+
setModal(null);
|
|
1757
|
+
return ui.copy(writeSkillPrompt(world, m.query, m.target), "a prompt for an agent to write the skill");
|
|
1758
|
+
}
|
|
1759
|
+
if (key.return && rows[cursor2]?.run) {
|
|
1760
|
+
const row = rows[cursor2];
|
|
1761
|
+
if (row.create && !/^[a-z0-9]/.test(m.query)) return setToast("Type the new skill's name first");
|
|
1762
|
+
setModal(null);
|
|
1763
|
+
if (!row.create) return apply(row.run);
|
|
1764
|
+
const created = row.run(world);
|
|
1765
|
+
if (isFailure(created)) return setToast(`\u2717 ${said2(created)}`);
|
|
1766
|
+
return edit(world.ops.libraryFile(m.query), row.afterEdit);
|
|
1767
|
+
}
|
|
1768
|
+
if (isNameChar(input, key)) return setModal({ ...m, query: m.query + input.toLowerCase(), cursor: 0 });
|
|
1769
|
+
}
|
|
1770
|
+
const toastMax = Math.max(1, Math.min(Math.max(3, Math.floor(termRows * 0.4)), termRows - 8 - (sidebar ? 0 : 1)));
|
|
1747
1771
|
const toastLines = toast && modal?.kind !== "confirm" ? wrap(toast, termCols - 2, toastMax).map((l) => " " + l) : [];
|
|
1748
1772
|
const bottomH = Math.max(1, toastLines.length);
|
|
1749
1773
|
const confirmLines = modal?.kind === "confirm" ? wrap(modal.fix.preview, columns - 6, 6) : [];
|
|
@@ -1979,7 +2003,7 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1979
2003
|
list,
|
|
1980
2004
|
split ? side : null
|
|
1981
2005
|
] });
|
|
1982
|
-
const hints = modal?.kind === "confirm" ? "enter apply \xB7 esc cancel" : modal?.kind === "repos" || modal?.kind === "fixall" ? "space tick \xB7 enter apply \xB7 esc cancel" : modal?.kind === "agents" ? "space tick \xB7 enter save" : modal?.kind === "menu" ? "\u2191\u2193 choose \xB7 enter do it \xB7 esc cancel" : modal?.kind === "add" ? modal.target ? "space tick \xB7 enter add \xB7 esc cancel" : "enter add \xB7 esc cancel" : modal ? "any key closes" : focus === "detail" ? "\u2191\u2193 \u2190\u2192 choose \xB7 enter apply \xB7 esc back" : focus === "sidebar" ? "\u2191\u2193 choose \xB7 enter open \xB7 type to filter repos" : place === "projects" && !open ? `type to filter \xB7 enter open \xB7 ${sidebar ? "\u2190 sidebar" : "\u2190\u2192 places"} \xB7 ? keys` : place === "settings" ? `enter toggle \xB7 ${sidebar ? "\u2190 sidebar" : "\u2190\u2192 places"} \xB7 ? keys` : `type to filter \xB7 space \u2605 fix \xB7 enter actions \xB7 tab tabs${sidebar ? " \xB7 esc back" : `${dashboard ? " \xB7 esc all projects" : ""} \xB7 ? keys`}`;
|
|
2006
|
+
const hints = modal?.kind === "confirm" ? "enter apply \xB7 esc cancel" : modal?.kind === "repos" || modal?.kind === "fixall" ? "space tick \xB7 enter apply \xB7 esc cancel" : modal?.kind === "agents" ? "space tick \xB7 enter save" : modal?.kind === "menu" ? "\u2191\u2193 choose \xB7 enter do it \xB7 esc cancel" : modal?.kind === "input" ? "enter confirm \xB7 ctrl+u clear \xB7 esc cancel" : modal?.kind === "add" ? modal.target ? "space tick \xB7 enter add \xB7 esc cancel" : "enter add \xB7 esc cancel" : modal ? "any key closes" : focus === "detail" ? "\u2191\u2193 \u2190\u2192 choose \xB7 enter apply \xB7 esc back" : focus === "sidebar" ? "\u2191\u2193 choose \xB7 enter open \xB7 type to filter repos" : place === "projects" && !open ? `type to filter \xB7 enter open \xB7 ${sidebar ? "\u2190 sidebar" : "\u2190\u2192 places"} \xB7 ? keys` : place === "settings" ? `enter toggle \xB7 ${sidebar ? "\u2190 sidebar" : "\u2190\u2192 places"} \xB7 ? keys` : `type to filter \xB7 space \u2605 fix \xB7 enter actions \xB7 tab tabs${sidebar ? " \xB7 esc back" : `${dashboard ? " \xB7 esc all projects" : ""} \xB7 ? keys`}`;
|
|
1983
2007
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: termCols, height: termRows, children: [
|
|
1984
2008
|
sidebar ? null : topBar,
|
|
1985
2009
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
@@ -2090,6 +2114,7 @@ function localSkill(root, s, git) {
|
|
|
2090
2114
|
const state = git ? git.of(relativeTo(root, s.path)) : null;
|
|
2091
2115
|
const others = otherCopies(root, s.name, s.location);
|
|
2092
2116
|
const own = other[s.state]?.source === "untracked" && isShared(state) ? { source: "repo" } : {};
|
|
2117
|
+
const inGit = s.managed && (isShared(state) || (readManifest(root).skills[s.name]?.links ?? []).some((d) => isShared(git?.of(`${d}/${s.name}`))));
|
|
2093
2118
|
return {
|
|
2094
2119
|
name: s.name,
|
|
2095
2120
|
source: "lib",
|
|
@@ -2097,7 +2122,7 @@ function localSkill(root, s, git) {
|
|
|
2097
2122
|
path: s.path,
|
|
2098
2123
|
// Agents that actually load it here: visibility lists every agent you use, with `paths: 0` for the blind ones.
|
|
2099
2124
|
agents: s.visibility.filter((v) => v.paths > 0).map((v) => v.id),
|
|
2100
|
-
...s.managed ? { version: s.version ?? void 0, ...managed[s.state] } : { ...other[s.state], ...own },
|
|
2125
|
+
...s.managed ? { version: s.version ?? void 0, ...s.personal ? { personal: true } : {}, ...inGit ? { inGit: true } : {}, ...managed[s.state] } : { ...other[s.state], ...own },
|
|
2101
2126
|
...s.source ? { origin: `npx skills: ${s.source}` } : {},
|
|
2102
2127
|
...others.length ? { others } : {},
|
|
2103
2128
|
git: state
|
|
@@ -2349,6 +2374,11 @@ function realOps(roots, backups, machine, libraryNames) {
|
|
|
2349
2374
|
const c = updateProject(rootOf(repo), [name])[0];
|
|
2350
2375
|
return c ? orForce(c, repo, "Update anyway", () => updateProject(rootOf(repo), [name], { force: true })[0]) : `${name} is up to date`;
|
|
2351
2376
|
},
|
|
2377
|
+
pin: (repo, name, personal) => {
|
|
2378
|
+
const c = addSkill(rootOf(repo), name, { personal });
|
|
2379
|
+
if (c.action === "skipped") return failed(said3(c, repo));
|
|
2380
|
+
return personal ? `${name} is now personal in ${repo}: only on your machines, kept out of git` : `${name} is now pinned in ${repo}'s skilllib.json for the team`;
|
|
2381
|
+
},
|
|
2352
2382
|
copyToLibrary: (repo, name) => ((s) => s ? toLibrary(s.path) : failed(`${name} isn't in ${repo} any more`))(find(repo, name)),
|
|
2353
2383
|
deleteGlobal: (m) => {
|
|
2354
2384
|
if (!entryExists(m.path)) return `${tildify(m.path)} is already gone`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilllib",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.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, Zed and Grok.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
package/skills/skilllib/SKILL.md
CHANGED
|
@@ -6,7 +6,7 @@ allowed-tools: Bash(skilllib:*)
|
|
|
6
6
|
|
|
7
7
|
# skilllib
|
|
8
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
|
|
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` (shared, committed), or in the user's personal pins (`~/.skilllib/pins`, kept out of git: `personal: true` in the output).
|
|
10
10
|
|
|
11
11
|
Always pass `--json` (one line of JSON on stdout; messages go to stderr). A failure exits 1 with `{"error": "..."}` as that line. `link`, `tidy`, `global` (its listing, `keep` and `unkeep`), `restore`, `new`, `folders`, `harnesses`, `scan` and `unhide` print messages only (read the exit code). Unknown flags are errors. Never run bare `skilllib` or `skilllib ui`: that's an interactive app.
|
|
12
12
|
|
|
@@ -25,7 +25,7 @@ Also: `usage --json` (Claude Code usage only, so "unused" is a hint; each skill
|
|
|
25
25
|
|
|
26
26
|
## Changing (ask first unless the user asked for this exact change)
|
|
27
27
|
|
|
28
|
-
`add <name>...` · `remove <name>...` (skills skilllib manages, `source: library`) · `update [name...]` · `sync` (installs what `skilllib.json` pins; takes no names). Pass several names in one call. Their result is `[{name, action, from, to, dirs, reason, blocked, uncommitted, inTheWay, backedUp}]`, which is the whole result: there's no need to check files afterwards. `import <dir>...` (into the library) prints `[{path, name, status}]` and exits 1 if a folder was skipped (`exists`, `no SKILL.md`). `doctor --fix` prints doctor's issues, each fix with `fixed: {ok, message}`.
|
|
28
|
+
`add <name>...` (pins in `skilllib.json` when the repo has one, else in personal pins; `--personal` / `--shared` choose, and move an existing pin without changing its version) · `remove <name>...` (skills skilllib manages, `source: library`) · `update [name...]` · `sync` (installs what `skilllib.json` pins; takes no names). Pass several names in one call. Their result is `[{name, action, from, to, dirs, reason, blocked, uncommitted, inTheWay, backedUp}]`, which is the whole result: there's no need to check files afterwards. `import <dir>...` (into the library) prints `[{path, name, status}]` and exits 1 if a folder was skipped (`exists`, `no SKILL.md`). `doctor --fix` prints doctor's issues, each fix with `fixed: {ok, message}`.
|
|
29
29
|
|
|
30
30
|
- `skipped` + `reason`: nothing changed for that skill, and the command exits 1; tell the user. `--force` overwrites local edits (`backedUp` says where they went; `skilllib restore <name>` brings them back), but only use it with their OK.
|
|
31
31
|
- `removed` + `reason`: removed from skilllib.json, but something was left as it is (its copy behind a folder that links out of the repo, a file of the user's, or a link in such a folder). Tell the user what the reason says is still there.
|
|
@@ -33,7 +33,7 @@ Also: `usage --json` (Claude Code usage only, so "unused" is a hint; each skill
|
|
|
33
33
|
- `inTheWay`: something else is where a link goes (a file, or another copy), so that agent may not run this copy there, and the command exits 1. Tell the user what's there; once they move it, `skilllib link` adds the link. (A skills folder that's a file or links out of the repo makes the skill `skipped` instead, with the reason.)
|
|
34
34
|
- `blocked`: git tracks that folder. Re-run with `--allow-tracked` (for that command only) only with their OK.
|
|
35
35
|
- `uncommitted`: git tracks that folder but not the skill's real copy, so a link there would break for teammates; `--allow-tracked` doesn't change that. Once the copy is committed, `link --allow-tracked` adds it.
|
|
36
|
-
- Changes apply from the agent's next session.
|
|
36
|
+
- Changes apply from the agent's next session. For a change without `personal`, remind the user to commit `skilllib.json` and the folders in `dirs`; a `personal` one has nothing to commit.
|
|
37
37
|
|
|
38
38
|
**Prefer per-repo skills to global ones.** A global skill loads in every repo, for every agent. When `usage --json` shows one used in only a few repos, propose moving it there:
|
|
39
39
|
|