skilllib 1.12.0 → 1.12.2
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
|
@@ -195,7 +195,7 @@ A skill's pin goes in one of two places:
|
|
|
195
195
|
- **`skilllib.json` in the repo (shared).** Commit it with the skill folders, and teammates get the same skills. This is where pins go when the repo already has a `skilllib.json`.
|
|
196
196
|
- **Your personal pins (`~/.skilllib/pins/`).** One file per repo, matched by its `origin` remote, so every clone and worktree of the repo shares it. skilllib keeps the copies and links out of git through the repo's `.git/info/exclude` (only your clone; the repo's `.gitignore` doesn't change). Nothing shows up in `git status`. This is where pins go when the repo has no `skilllib.json`.
|
|
197
197
|
|
|
198
|
-
`skilllib add <name> --personal` or `--shared` picks the place, and moves an existing pin without changing its version. In the app, a library skill's actions offer *Make it personal* or *Pin it for the team*, and its tag says `
|
|
198
|
+
`skilllib add <name> --personal` or `--shared` picks the place, and moves an existing pin without changing its version. In the app, a library skill's actions offer *Make it personal* or *Pin it for the team*, and its tag says `personal v2`. A copy git already has can't become personal. A skill pinned in both places uses the repo's pin; doctor offers to drop yours (a choice: yours would keep the skill if the team unpinned it).
|
|
199
199
|
|
|
200
200
|
**On another machine:** keep `~/.skilllib/pins/` and `~/.skilllib/library/` in git (a dotfiles repo, say). Clone a repo there and run `skilllib sync`: your personal skills come back, out of git again. A repo without a remote keeps its pins by folder, so they stay on that machine until it gets one: adding a remote (or renaming it) keeps them. Only a repo's top folder can have personal pins, not a monorepo package's.
|
|
201
201
|
|
|
@@ -235,7 +235,7 @@ function installDirs(enabled, keep) {
|
|
|
235
235
|
import { execFileSync } from "child_process";
|
|
236
236
|
import { createHash as createHash2 } from "crypto";
|
|
237
237
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
238
|
-
import { basename as basename2, dirname as dirname2, join as join5, resolve as resolve3 } from "path";
|
|
238
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join5, relative as relative2, resolve as resolve3, sep as sep3 } from "path";
|
|
239
239
|
|
|
240
240
|
// src/versions.ts
|
|
241
241
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -425,7 +425,7 @@ function writeManifest(root, manifest) {
|
|
|
425
425
|
for (const [name, dep] of Object.entries(readPins(file).skills)) if (onDisk[name] && manifest.skills[name] && !manifest.skills[name].personal) mine[name] = dep;
|
|
426
426
|
if (Object.keys(mine).length) writeChanged(file, { ...pinsHeader(repoOf(root)), skills: sorted(mine) });
|
|
427
427
|
else rmSync3(file, { force: true });
|
|
428
|
-
excludePersonal(root, mine);
|
|
428
|
+
excludePersonal(root, Object.fromEntries(Object.entries(mine).filter(([name]) => !shared[name])));
|
|
429
429
|
}
|
|
430
430
|
function writeChanged(path, data) {
|
|
431
431
|
try {
|
|
@@ -478,7 +478,7 @@ function repoOf(root) {
|
|
|
478
478
|
const seen = changedAt(config);
|
|
479
479
|
let repo = null;
|
|
480
480
|
if (out2[0] === "") {
|
|
481
|
-
const urls = (gitIn(root, ["config", "--get-regexp", "^remote\\..*\\.url$"]) ?? "").split("\n").filter(Boolean).map((l) => l.
|
|
481
|
+
const urls = (gitIn(root, ["config", "--get-regexp", "^remote\\..*\\.url$"]) ?? "").split("\n").filter(Boolean).map((l) => [l.slice(0, l.indexOf(" ")), l.slice(l.indexOf(" ") + 1)]);
|
|
482
482
|
const url = (urls.find(([k]) => k === "remote.origin.url") ?? urls[0])?.[1];
|
|
483
483
|
const remote = url ? normalizeRemote(url) : void 0;
|
|
484
484
|
repo = { file: pinsFileFor(root, remote, folder), folder, common, ...remote ? { remote } : {}, root };
|
|
@@ -530,7 +530,15 @@ function excludePersonal(root, skills = Object.fromEntries(Object.entries(readMa
|
|
|
530
530
|
const end = start === -1 ? -1 : lines.indexOf(EXCLUDE_END, start);
|
|
531
531
|
const rest = start === -1 ? lines : [...lines.slice(0, start), ...lines.slice(end === -1 ? start + 1 : end + 1)];
|
|
532
532
|
while (rest.length && rest[rest.length - 1] === "") rest.pop();
|
|
533
|
-
const
|
|
533
|
+
const top = realNative(root) ?? root;
|
|
534
|
+
const places = (dir) => {
|
|
535
|
+
const real = realNative(join5(root, dir));
|
|
536
|
+
const rel = real ? relative2(top, real).split(sep3).join("/") : "";
|
|
537
|
+
return rel && rel !== ".." && !rel.startsWith("../") && !isAbsolute(rel) && rel !== dir ? [dir, rel] : [dir];
|
|
538
|
+
};
|
|
539
|
+
const paths = Object.entries(skills).flatMap(
|
|
540
|
+
([name, d]) => [d.dir ?? PROJECT_SKILLS_DIR, ...d.links ?? []].flatMap(places).map((dir) => `/${dir}/${name.replace(/[[\]*?\\]/g, "\\$&")}`)
|
|
541
|
+
);
|
|
534
542
|
const block = paths.length ? [EXCLUDE_START, ...[...new Set(paths)].sort(), EXCLUDE_END] : [];
|
|
535
543
|
const next = [...rest, ...rest.length && block.length ? [""] : [], ...block].join("\n") + (rest.length || block.length ? "\n" : "");
|
|
536
544
|
if (next === text) return;
|
|
@@ -688,11 +696,11 @@ import { join as join9 } from "path";
|
|
|
688
696
|
// src/library.ts
|
|
689
697
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
690
698
|
import { cpSync as cpSync2, existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync4, readlinkSync as readlinkSync2, renameSync as renameSync3, rmSync as rmSync4, symlinkSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
691
|
-
import { basename as basename4, dirname as dirname3, isAbsolute, join as join8, posix, relative as
|
|
699
|
+
import { basename as basename4, dirname as dirname3, isAbsolute as isAbsolute2, join as join8, posix, relative as relative3, resolve as resolve6, sep as sep5 } from "path";
|
|
692
700
|
|
|
693
701
|
// src/sources.ts
|
|
694
702
|
import { existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readlinkSync } from "fs";
|
|
695
|
-
import { basename as basename3, join as join7, resolve as resolve5, sep as
|
|
703
|
+
import { basename as basename3, join as join7, resolve as resolve5, sep as sep4 } from "path";
|
|
696
704
|
import { tmpdir } from "os";
|
|
697
705
|
function skillsShLock() {
|
|
698
706
|
return readJson(join7(userHome(), ".agents", ".skill-lock.json"))?.skills ?? {};
|
|
@@ -884,7 +892,7 @@ function recordOrigin(name, origin) {
|
|
|
884
892
|
writeState(originsFile(), { ...origins, [name]: origin });
|
|
885
893
|
}
|
|
886
894
|
function originFor(dir) {
|
|
887
|
-
const under = (path, folder) => path.startsWith((realpathOrNull(folder) ?? folder) +
|
|
895
|
+
const under = (path, folder) => path.startsWith((realpathOrNull(folder) ?? folder) + sep4);
|
|
888
896
|
const real = realpathOrNull(dir) ?? dir;
|
|
889
897
|
const temps = [tmpdir(), "/tmp", "/var/tmp"];
|
|
890
898
|
const homeInTemp = temps.some((t) => under(realpathOrNull(userHome()) ?? userHome(), t));
|
|
@@ -922,7 +930,7 @@ function notWritable(root, dir) {
|
|
|
922
930
|
p = join8(p, part);
|
|
923
931
|
if (!entryExists(p)) return null;
|
|
924
932
|
const real = realpathOrNull(p);
|
|
925
|
-
if (!isDir(p) || real === null || real !== top && !real.startsWith(top +
|
|
933
|
+
if (!isDir(p) || real === null || real !== top && !real.startsWith(top + sep5)) return relative3(root, p).split(sep5).join("/");
|
|
926
934
|
}
|
|
927
935
|
return null;
|
|
928
936
|
}
|
|
@@ -973,7 +981,7 @@ function syncBlocker(root, name, dep = readManifest(root).skills[name]) {
|
|
|
973
981
|
}
|
|
974
982
|
function linkDir(target, link2) {
|
|
975
983
|
if (process.platform === "win32") symlinkSync(target, link2, "junction");
|
|
976
|
-
else symlinkSync(
|
|
984
|
+
else symlinkSync(relative3(dirname3(link2), target), link2);
|
|
977
985
|
}
|
|
978
986
|
function entryExists(path) {
|
|
979
987
|
return isLink(path) || existsSync7(path);
|
|
@@ -1005,7 +1013,7 @@ function lostAt(path, gone) {
|
|
|
1005
1013
|
let last = null;
|
|
1006
1014
|
for (let hop = 0; hop < 40; hop++) {
|
|
1007
1015
|
const here = canon(at);
|
|
1008
|
-
if (moved.some((m) => key(here) === m || key(here).startsWith(m +
|
|
1016
|
+
if (moved.some((m) => key(here) === m || key(here).startsWith(m + sep5))) return last ?? here;
|
|
1009
1017
|
if (!isLink(here)) return existsSync7(join8(here, "SKILL.md")) ? null : here;
|
|
1010
1018
|
last = here;
|
|
1011
1019
|
at = resolve6(dirname3(here), readlinkSync2(here).replace(/^\\\\\?\\(UNC\\)?/, (_m, unc) => unc ? "\\\\" : ""));
|
|
@@ -1019,7 +1027,7 @@ function blindWithoutGlobal(name, copies, roots, nestedOf = (root) => nestedEntr
|
|
|
1019
1027
|
const holds = (root) => ALL_PROJECT_DIRS.some((d) => reposChoice(root, d) && entryExists(join8(root, d, name))) || pinsSkill(root, name);
|
|
1020
1028
|
const keepsTop = (root, id) => harness(id).projectDirs.some((d) => existsSync7(join8(root, d, name, "SKILL.md")) && survives(join8(root, d, name), gone));
|
|
1021
1029
|
const shown = (root, p) => {
|
|
1022
|
-
const under = (dir) => ((d) => p.startsWith(d +
|
|
1030
|
+
const under = (dir) => ((d) => p.startsWith(d + sep5) ? relative3(d, p).split(sep5).join("/") : null)(realNative(dir) ?? dir);
|
|
1023
1031
|
return under(root) ?? ((h) => h === null ? p : `~/${h}`)(under(userHome()));
|
|
1024
1032
|
};
|
|
1025
1033
|
const blindIn = (root) => enabled.flatMap((id) => {
|
|
@@ -1047,16 +1055,16 @@ function blindWithoutGlobal(name, copies, roots, nestedOf = (root) => nestedEntr
|
|
|
1047
1055
|
const had = byWho.get(k);
|
|
1048
1056
|
if (!had || !(had.link || had.folder || had.shared) && (b.link || b.folder || b.shared)) byWho.set(k, b);
|
|
1049
1057
|
}
|
|
1050
|
-
const within = (d) => unique.some((r) => d === r || d.startsWith(r +
|
|
1058
|
+
const within = (d) => unique.some((r) => d === r || d.startsWith(r + sep5));
|
|
1051
1059
|
const home2 = realNative(userHome()) ?? userHome();
|
|
1052
|
-
const belowHome = (d) => (realNative(d) ?? d) !== home2 && !home2.startsWith((realNative(d) ?? d) +
|
|
1060
|
+
const belowHome = (d) => (realNative(d) ?? d) !== home2 && !home2.startsWith((realNative(d) ?? d) + sep5);
|
|
1053
1061
|
const gitTop = (from) => {
|
|
1054
1062
|
for (let d = from; d !== dirname3(d) && belowHome(d); d = dirname3(d)) if (existsSync7(join8(d, ".git"))) return d;
|
|
1055
1063
|
return null;
|
|
1056
1064
|
};
|
|
1057
1065
|
const servedAbove = (b) => {
|
|
1058
1066
|
const top = gitTop(b.root);
|
|
1059
|
-
const inside = (d) => belowHome(d) && (top ? d === top || d.startsWith(top +
|
|
1067
|
+
const inside = (d) => belowHome(d) && (top ? d === top || d.startsWith(top + sep5) : within(d));
|
|
1060
1068
|
for (let d = dirname3(b.root); inside(d) && d !== dirname3(d); d = dirname3(d)) if (keepsTop(d, b.id)) return true;
|
|
1061
1069
|
return false;
|
|
1062
1070
|
};
|
|
@@ -1073,7 +1081,7 @@ function strandedLinks(name, copies, roots, label = basename4) {
|
|
|
1073
1081
|
).map(
|
|
1074
1082
|
(d) => `${d}/${name} in ${label(root)}`
|
|
1075
1083
|
),
|
|
1076
|
-
...nestedEntries(root).filter((n) => n.name === name && isLink(n.path) && !survives(n.path, gone) && once(n.path)).map((n) => `${
|
|
1084
|
+
...nestedEntries(root).filter((n) => n.name === name && isLink(n.path) && !survives(n.path, gone) && once(n.path)).map((n) => `${relative3(root, n.path).split(sep5).join("/")} in ${label(root)}`)
|
|
1077
1085
|
]);
|
|
1078
1086
|
}
|
|
1079
1087
|
function copiesDiffer(paths) {
|
|
@@ -1082,7 +1090,7 @@ function copiesDiffer(paths) {
|
|
|
1082
1090
|
function blindSaid(name, blind, label = basename4) {
|
|
1083
1091
|
const who = blind.map((b) => `${harness(b.id).name} in ${label(b.root)}`).join(", ");
|
|
1084
1092
|
const folders2 = [...new Set(blind.flatMap((b) => b.folder ? [`${b.folder} in ${label(b.root)}`] : []))];
|
|
1085
|
-
const links = [...new Set(blind.flatMap((b) => b.link ? [b.link.startsWith("~") ||
|
|
1093
|
+
const links = [...new Set(blind.flatMap((b) => b.link ? [b.link.startsWith("~") || isAbsolute2(b.link) ? b.link : `${b.link} in ${label(b.root)}`] : []))];
|
|
1086
1094
|
const sharedBy = /* @__PURE__ */ new Map();
|
|
1087
1095
|
for (const b of blind.filter((x) => x.shared)) {
|
|
1088
1096
|
if (!sharedBy.has(b.shared.to)) sharedBy.set(b.shared.to, { to: b.shared.to, where: /* @__PURE__ */ new Set(), dirs: /* @__PURE__ */ new Set() });
|
|
@@ -1178,7 +1186,7 @@ function projectStatus(root) {
|
|
|
1178
1186
|
}
|
|
1179
1187
|
for (const [name, paths] of byName) {
|
|
1180
1188
|
const real = paths.find((p) => !isLink(p)) ?? paths[0];
|
|
1181
|
-
const location =
|
|
1189
|
+
const location = relative3(root, join8(real, "..")).split(sep5).join("/");
|
|
1182
1190
|
const source = lock[name]?.source;
|
|
1183
1191
|
unmanaged.push({
|
|
1184
1192
|
name,
|
|
@@ -1226,7 +1234,7 @@ function nestedEntries(root, { depth = PACKAGE_DEPTH, enabled = enabledHarnesses
|
|
|
1226
1234
|
const top = realpathOrNull(root) ?? root;
|
|
1227
1235
|
const found = packageSkillFolders(root, subs ?? packageDirs(root, depth)).flatMap(
|
|
1228
1236
|
({ sub, skillsDir, location }) => skillDirsIn(join8(sub, skillsDir)).flatMap((path) => {
|
|
1229
|
-
if (isLink(path) && (realpathOrNull(path) ?? "").startsWith(top +
|
|
1237
|
+
if (isLink(path) && (realpathOrNull(path) ?? "").startsWith(top + sep5)) return [];
|
|
1230
1238
|
const name = basename4(path);
|
|
1231
1239
|
const readers = NESTED_READERS.filter(([d]) => realpathOrNull(join8(sub, d, name)) === realpathOrNull(path)).flatMap(([, r]) => r);
|
|
1232
1240
|
return [{ name, path, location, skillsDir, readers: enabled.filter((id) => readers.includes(id)) }];
|
|
@@ -1257,7 +1265,7 @@ function packageSkillFolders(root, subs = packageDirs(root)) {
|
|
|
1257
1265
|
return subs.flatMap(
|
|
1258
1266
|
(sub) => NESTED_READERS.flatMap(([skillsDir]) => {
|
|
1259
1267
|
if (!existsSync7(join8(sub, skillsDir))) return [];
|
|
1260
|
-
const location =
|
|
1268
|
+
const location = relative3(root, join8(sub, skillsDir)).split(sep5).join("/");
|
|
1261
1269
|
return reposChoice(root, location) ? [{ sub, skillsDir, location }] : [];
|
|
1262
1270
|
})
|
|
1263
1271
|
);
|
|
@@ -1387,8 +1395,8 @@ function addSkill(root, name, { force = false, version, allowTracked = false, pe
|
|
|
1387
1395
|
if (moving && !keep) return { name, action: "skipped", reason: `your library has no copy of the version it pins, so its pin can't move as it is: skilllib update ${name} first` };
|
|
1388
1396
|
const target = keep ?? (version === void 0 ? latestVersion(name) : getVersion(name, version));
|
|
1389
1397
|
if (!target) return { name, action: "skipped", reason: version === void 0 ? "not in the library" : `version ${version} doesn't exist` };
|
|
1390
|
-
const plan = addPlan(root, name, target.hash, force);
|
|
1391
|
-
if ("reason" in plan) return { name, action: "skipped", reason: plan.reason };
|
|
1398
|
+
const plan = addPlan(root, name, target.hash, moving ? false : force);
|
|
1399
|
+
if ("reason" in plan) return { name, action: "skipped", reason: moving ? `${withoutForceHint(plan.reason)}, so its pin can't move: save the edits to your library first` : plan.reason };
|
|
1392
1400
|
const { manifest, recorded, primary, wantedLinks, to, local } = plan;
|
|
1393
1401
|
const mine = pinsWhere(root, recorded, personal);
|
|
1394
1402
|
if (typeof mine === "string") return { name, action: "skipped", reason: mine };
|
|
@@ -1636,7 +1644,7 @@ function stash(dir, bucket) {
|
|
|
1636
1644
|
return target;
|
|
1637
1645
|
}
|
|
1638
1646
|
function removeUntracked(root, name, path) {
|
|
1639
|
-
const stuck = notWritable(root,
|
|
1647
|
+
const stuck = notWritable(root, relative3(root, dirname3(path)));
|
|
1640
1648
|
if (stuck) throw new Error(`${stuck} isn't a folder in the repo: skilllib leaves what's in it alone`);
|
|
1641
1649
|
const to = stash(path, "trash");
|
|
1642
1650
|
unlinkEverywhere(root, name);
|
|
@@ -1801,12 +1809,12 @@ function refreshAgentSkill() {
|
|
|
1801
1809
|
// src/commands.ts
|
|
1802
1810
|
import { existsSync as existsSync14, readFileSync as readFileSync7 } from "fs";
|
|
1803
1811
|
import { parseArgs as parseFlags } from "util";
|
|
1804
|
-
import { basename as basename10, join as join15, resolve as resolve8, sep as
|
|
1812
|
+
import { basename as basename10, join as join15, resolve as resolve8, sep as sep9 } from "path";
|
|
1805
1813
|
|
|
1806
1814
|
// src/git.ts
|
|
1807
1815
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1808
1816
|
import { existsSync as existsSync9, realpathSync as realpathSync3 } from "fs";
|
|
1809
|
-
import { isAbsolute as
|
|
1817
|
+
import { isAbsolute as isAbsolute3, join as join10, relative as relative4, sep as sep6 } from "path";
|
|
1810
1818
|
function isShared(state) {
|
|
1811
1819
|
return state === "committed" || state === "changed";
|
|
1812
1820
|
}
|
|
@@ -1835,7 +1843,7 @@ function gitInfo(root, extraDirs = []) {
|
|
|
1835
1843
|
};
|
|
1836
1844
|
const realRoot = real(root);
|
|
1837
1845
|
const places = new Map(
|
|
1838
|
-
[.../* @__PURE__ */ new Set([...ALL_PROJECT_DIRS, MANIFEST_FILE, ...extraDirs])].filter((d) => existsSync9(join10(root, d))).map((d) => [d,
|
|
1846
|
+
[.../* @__PURE__ */ new Set([...ALL_PROJECT_DIRS, MANIFEST_FILE, ...extraDirs])].filter((d) => existsSync9(join10(root, d))).map((d) => [d, relative4(realRoot, real(join10(root, d))).split(sep6).join("/")]).filter(([, place]) => place !== "" && place !== ".." && !place.startsWith("../") && !isAbsolute3(place))
|
|
1839
1847
|
);
|
|
1840
1848
|
const dirs = [...new Set(places.values())];
|
|
1841
1849
|
if (dirs.length === 0) return { of: () => "new" };
|
|
@@ -1863,7 +1871,7 @@ function gitInfo(root, extraDirs = []) {
|
|
|
1863
1871
|
};
|
|
1864
1872
|
}
|
|
1865
1873
|
function relativeTo(root, path) {
|
|
1866
|
-
return
|
|
1874
|
+
return relative4(root, path).split("\\").join("/");
|
|
1867
1875
|
}
|
|
1868
1876
|
|
|
1869
1877
|
// src/here.ts
|
|
@@ -1872,7 +1880,7 @@ import { basename as basename8 } from "path";
|
|
|
1872
1880
|
|
|
1873
1881
|
// src/health.ts
|
|
1874
1882
|
import { existsSync as existsSync12, lstatSync as lstatSync4, readdirSync as readdirSync8 } from "fs";
|
|
1875
|
-
import { basename as basename7, dirname as dirname6, isAbsolute as
|
|
1883
|
+
import { basename as basename7, dirname as dirname6, isAbsolute as isAbsolute4, join as join13, relative as relative5, sep as sep8 } from "path";
|
|
1876
1884
|
|
|
1877
1885
|
// src/plugins.ts
|
|
1878
1886
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
@@ -2175,7 +2183,7 @@ function cursorPluginSkills(plugins = cursorPlugins()) {
|
|
|
2175
2183
|
|
|
2176
2184
|
// src/tidy.ts
|
|
2177
2185
|
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readdirSync as readdirSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
2178
|
-
import { basename as basename6, dirname as dirname5, join as join12, sep as
|
|
2186
|
+
import { basename as basename6, dirname as dirname5, join as join12, sep as sep7 } from "path";
|
|
2179
2187
|
function entriesOf(dirs, name) {
|
|
2180
2188
|
return dirs.flatMap((dir) => {
|
|
2181
2189
|
const path = join12(dir, name);
|
|
@@ -2233,7 +2241,7 @@ function planProjectTidy(root, { keep = {}, enabled = enabledHarnesses() } = {})
|
|
|
2233
2241
|
const realRoot = realpathOrNull(root) ?? root;
|
|
2234
2242
|
for (const name of namesIn(dirs)) {
|
|
2235
2243
|
const entries = entriesOf(dirs, name);
|
|
2236
|
-
const external = entries.find((e) => e.link && e.real && !(e.real +
|
|
2244
|
+
const external = entries.find((e) => e.link && e.real && !(e.real + sep7).startsWith(realRoot + sep7));
|
|
2237
2245
|
if (external) {
|
|
2238
2246
|
const differs = entries.some((e) => e !== external && e.real && existsSync11(join12(e.real, "SKILL.md")) && treeHash(e.real) !== treeHash(external.real));
|
|
2239
2247
|
report.skipped.push({ name, reason: `${rel(external.path)} links outside the repo`, ...differs ? { differs } : {} });
|
|
@@ -2392,6 +2400,8 @@ function intoLibrary(path, force = false) {
|
|
|
2392
2400
|
if (r.status === "exists") throw new Error(`${r.name}: your library has a different ${r.name}; update or delete it first`);
|
|
2393
2401
|
return r.name;
|
|
2394
2402
|
}
|
|
2403
|
+
var pinsOf = (where, personal) => personal ? "your personal pins" : `${where}'s skilllib.json`;
|
|
2404
|
+
var newPinsOf = (root, where) => pinsOf(where, !existsSync12(join13(root, MANIFEST_FILE)) && personalPinsFile(root) !== null);
|
|
2395
2405
|
function track(root, name) {
|
|
2396
2406
|
const c = addSkill(root, name);
|
|
2397
2407
|
if (c.action === "skipped") throw new Error(`${name}: ${c.reason}`);
|
|
@@ -2465,7 +2475,7 @@ function brokenLinksIn(root, opened) {
|
|
|
2465
2475
|
const realRoot = realNative(root) ?? root;
|
|
2466
2476
|
const ownerOf = (real) => {
|
|
2467
2477
|
let nearest = null;
|
|
2468
|
-
for (let d = dirname6(dirname6(real)); d.startsWith(realRoot +
|
|
2478
|
+
for (let d = dirname6(dirname6(real)); d.startsWith(realRoot + sep8); d = dirname6(d)) {
|
|
2469
2479
|
if (!existsSync12(join13(d, MANIFEST_FILE))) continue;
|
|
2470
2480
|
if (opened.has(d)) return { opened: true, own: d };
|
|
2471
2481
|
nearest ??= d;
|
|
@@ -2497,13 +2507,13 @@ function brokenLinksIn(root, opened) {
|
|
|
2497
2507
|
return `Removed the broken link ${dir}/${name} in ${where} (restorable with skilllib restore)`;
|
|
2498
2508
|
}
|
|
2499
2509
|
};
|
|
2500
|
-
const placeOf = (p) =>
|
|
2510
|
+
const placeOf = (p) => relative5(realRoot, p).split(sep8).join("/");
|
|
2501
2511
|
git2 ??= gitInfo(
|
|
2502
2512
|
root,
|
|
2503
|
-
folders2.map((f) => placeOf(f.real)).filter((p) => p !== "" && p !== ".." && !p.startsWith("../") && !
|
|
2513
|
+
folders2.map((f) => placeOf(f.real)).filter((p) => p !== "" && p !== ".." && !p.startsWith("../") && !isAbsolute4(p))
|
|
2504
2514
|
);
|
|
2505
2515
|
const tracked = isShared(git2?.of(placeOf(join13(real, name))));
|
|
2506
|
-
const recorded = !!own && mends(own,
|
|
2516
|
+
const recorded = !!own && mends(own, relative5(own, real).split(sep8).join("/"), name);
|
|
2507
2517
|
const pinnedThere = own ? ` ${placeOf(own)} has its own skilllib.json${recorded ? `, which records this link: skilllib sync in ${placeOf(own)} brings back what it points to` : ""}.` : "";
|
|
2508
2518
|
return [
|
|
2509
2519
|
{
|
|
@@ -2911,7 +2921,7 @@ function repoIssues(root, ctx) {
|
|
|
2911
2921
|
ctx.issues.push({
|
|
2912
2922
|
id: `stuck:${root}:${name}`,
|
|
2913
2923
|
severity: "problem",
|
|
2914
|
-
title: `${name} in ${where}: sync can't put it back as skilllib.json pins it`,
|
|
2924
|
+
title: `${name} in ${where}: sync can't put it back as ${pins[name]?.personal ? "your personal pins have" : "skilllib.json pins"} it`,
|
|
2915
2925
|
// What to do depends on what's there: a folder that links out of the repo is a setup of yours, and a pin
|
|
2916
2926
|
// your library has no version of can't come back by sync anyway.
|
|
2917
2927
|
detail: `${blocker.said}, so sync can't put it back there.${blocker.outside ? " skilllib only installs inside the repo: point that folder back into the repo, or remove the skill here (skilllib remove)." : versionForHash(name, pins[name]?.hash ?? "") !== null ? " Move it out of the way, then run skilllib sync." : " Move it out of the way; your library has no copy of the pinned version either."}`,
|
|
@@ -2925,11 +2935,11 @@ function repoIssues(root, ctx) {
|
|
|
2925
2935
|
ctx.issues.push({
|
|
2926
2936
|
id: `pin:${root}:${s.name}`,
|
|
2927
2937
|
severity: "problem",
|
|
2928
|
-
title: `${s.name}: the version ${where}'s skilllib.json
|
|
2929
|
-
detail: `${s.state === "folder missing" ? "The folder is missing, and no" : "The copy is another version, and no"} version in Your skills has the content it pins (pinned from another library?). ${untrackable(s) ? `${untrackable(s)}, so skilllib can't install it here.` : `Install your newest v${s.latest}; ${where}'s skilllib.json then
|
|
2938
|
+
title: `${s.name}: the version ${s.personal ? "your personal pins" : `${where}'s skilllib.json`} pin${s.personal ? "" : "s"} isn't in your library`,
|
|
2939
|
+
detail: `${s.state === "folder missing" ? "The folder is missing, and no" : "The copy is another version, and no"} version in Your skills has the content it pins (pinned from another library?). ${untrackable(s) ? `${untrackable(s)}, so skilllib can't install it here.` : `Install your newest v${s.latest}; ${s.personal ? "your personal pins" : `${where}'s skilllib.json`} then pin${s.personal ? "" : "s"} it.`}`,
|
|
2930
2940
|
at: rows(root, [s.name]),
|
|
2931
2941
|
// It changes what the shared skilllib.json pins: a choice, never run by `doctor --fix`.
|
|
2932
|
-
...untrackable(s) ? {} : { choices: [{ label: `Install your newest (v${s.latest})`, hint: `${where}'s skilllib.json pins it`, run: () => `${s.name}: installed v${track(root, s.name).to}` }] }
|
|
2942
|
+
...untrackable(s) ? {} : { choices: [{ label: `Install your newest (v${s.latest})`, hint: s.personal ? "your personal pins pin it" : `${where}'s skilllib.json pins it`, run: () => `${s.name}: installed v${track(root, s.name).to}` }] }
|
|
2933
2943
|
});
|
|
2934
2944
|
if (restorable.length > 0) {
|
|
2935
2945
|
ctx.issues.push({
|
|
@@ -2967,18 +2977,18 @@ function repoIssues(root, ctx) {
|
|
|
2967
2977
|
ctx.issues.push({
|
|
2968
2978
|
id: `unavailable:${root}:${s.name}`,
|
|
2969
2979
|
severity: "problem",
|
|
2970
|
-
title: `${s.name}: pinned in ${where}'s skilllib.json, but not in your library`,
|
|
2980
|
+
title: `${s.name}: pinned in ${s.personal ? "your personal pins" : `${where}'s skilllib.json`}, but not in your library`,
|
|
2971
2981
|
detail: `Its folder is missing, and your library (${tildify(libraryDir())}) has no copy to restore: import it, or set SKILLLIB_HOME to the skilllib folder whose library has it.`,
|
|
2972
2982
|
at: rows(root, [s.name]),
|
|
2973
2983
|
// Editing skilllib.json is the team's call: a choice, never run by `doctor --fix`.
|
|
2974
2984
|
choices: [
|
|
2975
2985
|
{
|
|
2976
|
-
label: "Remove it from skilllib.json",
|
|
2986
|
+
label: s.personal ? "Remove it from your personal pins" : "Remove it from skilllib.json",
|
|
2977
2987
|
hint: `${where} stops pinning it`,
|
|
2978
2988
|
run: () => {
|
|
2979
2989
|
const r = removeSkill(root, s.name);
|
|
2980
2990
|
if (r.action !== "removed") throw new Error(`${s.name}: ${r.reason}`);
|
|
2981
|
-
return `${s.name}: removed from ${where
|
|
2991
|
+
return `${s.name}: removed from ${pinsOf(where, s.personal)}`;
|
|
2982
2992
|
}
|
|
2983
2993
|
}
|
|
2984
2994
|
]
|
|
@@ -2995,7 +3005,7 @@ function repoIssues(root, ctx) {
|
|
|
2995
3005
|
choices: [
|
|
2996
3006
|
{
|
|
2997
3007
|
label: "Update to the newest versions",
|
|
2998
|
-
hint: isShared(gitOf(root)?.of(MANIFEST_FILE)) ? "git will see the new copies and skilllib.json" : "replaces each copy with your library's newest",
|
|
3008
|
+
hint: behind.some((s) => !s.personal) && isShared(gitOf(root)?.of(MANIFEST_FILE)) ? "git will see the new copies and skilllib.json" : "replaces each copy with your library's newest",
|
|
2999
3009
|
run: () => summarize(where, "updated", updateProject(root, behind.map((s) => s.name)))
|
|
3000
3010
|
}
|
|
3001
3011
|
]
|
|
@@ -3078,7 +3088,7 @@ function repoIssues(root, ctx) {
|
|
|
3078
3088
|
if (partial.length) {
|
|
3079
3089
|
const missing2 = [...new Set(partial.flatMap((s) => s.visibility.filter((v) => v.paths === 0).map((v) => v.id)))];
|
|
3080
3090
|
const tracked = [...new Set(blindHere.flatMap(({ would }) => would.blocked))];
|
|
3081
|
-
const manifestShared = partial.some((s) => s.managed) && isShared(gitOf(root)?.of(MANIFEST_FILE));
|
|
3091
|
+
const manifestShared = partial.some((s) => s.managed && !s.personal) && isShared(gitOf(root)?.of(MANIFEST_FILE));
|
|
3082
3092
|
const run = (allowTracked) => () => {
|
|
3083
3093
|
if (allowTracked) allowTrackedLinks(root);
|
|
3084
3094
|
const r = linkAll(root, { allowTracked, names: partial.map((s) => s.name) });
|
|
@@ -3134,7 +3144,7 @@ function repoIssues(root, ctx) {
|
|
|
3134
3144
|
id: `local:${root}:${s.name}`,
|
|
3135
3145
|
severity: "suggestion",
|
|
3136
3146
|
title: `${s.name} in ${where} isn't in your library`,
|
|
3137
|
-
detail: "Import it into the library to reuse it in other projects and keep it backed up. The repo then tracks it in skilllib.json.",
|
|
3147
|
+
detail: "Import it into the library to reuse it in other projects and keep it backed up. The repo then tracks it (in skilllib.json, or your personal pins when it has none).",
|
|
3138
3148
|
at: rows(root, [s.name]),
|
|
3139
3149
|
choices: [
|
|
3140
3150
|
{
|
|
@@ -3154,9 +3164,9 @@ function repoIssues(root, ctx) {
|
|
|
3154
3164
|
id: `adopt:${root}:${s.name}`,
|
|
3155
3165
|
severity: "suggestion",
|
|
3156
3166
|
title: `${s.name} in ${where} isn't tracked`,
|
|
3157
|
-
detail:
|
|
3167
|
+
detail: `It's identical to the library copy; track it (in ${newPinsOf(root, where)}) so future library changes reach it.`,
|
|
3158
3168
|
at: rows(root, [s.name]),
|
|
3159
|
-
choices: [{ label: "Track it", hint: `adds it to ${where}
|
|
3169
|
+
choices: [{ label: "Track it", hint: `adds it to ${newPinsOf(root, where)}`, run: () => `${track(root, s.name).name} tracked in ${where}` }]
|
|
3160
3170
|
});
|
|
3161
3171
|
} else if (s.state === "untracked, differs from library" || s.state === "repo skill, differs from library") {
|
|
3162
3172
|
const teams = s.state.startsWith("repo skill") || committed(s) || !!untrackable(s);
|
|
@@ -3197,7 +3207,7 @@ function repoIssues(root, ctx) {
|
|
|
3197
3207
|
id: `npx:${root}:${s.name}`,
|
|
3198
3208
|
severity: "suggestion",
|
|
3199
3209
|
title: `${s.name} in ${where} came from npx skills, not your library`,
|
|
3200
|
-
detail: `Installed with \`npx skills add\`${s.source ? ` (${s.source})` : ""}. Import it into your library to reuse it in other projects and keep it backed up${teams ? `; ${where}'s copy stays as it is (${committed(s) ? "committed" : `${untrackable(s)}, so skilllib can't track it here`})` : `; ${where} then tracks it in skilllib.json`}.`,
|
|
3210
|
+
detail: `Installed with \`npx skills add\`${s.source ? ` (${s.source})` : ""}. Import it into your library to reuse it in other projects and keep it backed up${teams ? `; ${where}'s copy stays as it is (${committed(s) ? "committed" : `${untrackable(s)}, so skilllib can't track it here`})` : `; ${where} then tracks it (in skilllib.json, or your personal pins when it has none)`}.`,
|
|
3201
3211
|
at: rows(root, [s.name]),
|
|
3202
3212
|
choices: [
|
|
3203
3213
|
{
|
|
@@ -3218,7 +3228,7 @@ function repoIssues(root, ctx) {
|
|
|
3218
3228
|
id: `not-in-lib:${root}:${s.name}`,
|
|
3219
3229
|
severity: "suggestion",
|
|
3220
3230
|
title: `${s.name} in ${where} is tracked, but no longer in your library`,
|
|
3221
|
-
detail: `${where}'s skilllib.json pins it, but your library has no copy: if the folder goes, sync can't restore it, and your other repos can't add it. Copy ${where}'s copy back into the library.`,
|
|
3231
|
+
detail: `${s.personal ? "Your personal pins have" : `${where}'s skilllib.json pins`} it, but your library has no copy: if the folder goes, sync can't restore it, and your other repos can't add it. Copy ${where}'s copy back into the library.`,
|
|
3222
3232
|
at: rows(root, [s.name]),
|
|
3223
3233
|
choices: [
|
|
3224
3234
|
{
|
|
@@ -3739,9 +3749,9 @@ function projectOfFactory(projects2) {
|
|
|
3739
3749
|
const sorted2 = [...projects2].sort((a, b) => b.length - a.length);
|
|
3740
3750
|
return (cwd) => {
|
|
3741
3751
|
if (!cwd) return null;
|
|
3742
|
-
const known = sorted2.find((p) => cwd === p || cwd.startsWith(p +
|
|
3752
|
+
const known = sorted2.find((p) => cwd === p || cwd.startsWith(p + sep9));
|
|
3743
3753
|
if (known) return known;
|
|
3744
|
-
const repo = cwd.split(`${
|
|
3754
|
+
const repo = cwd.split(`${sep9}.claude${sep9}worktrees${sep9}`)[0];
|
|
3745
3755
|
return isProjectCandidate(repo) ? repo : null;
|
|
3746
3756
|
};
|
|
3747
3757
|
}
|
|
@@ -3950,7 +3960,7 @@ function add(args) {
|
|
|
3950
3960
|
printChanges(root, changes, args);
|
|
3951
3961
|
const pins = readManifest(root).skills;
|
|
3952
3962
|
const done = changes.filter((c) => c.action !== "skipped" && pins[c.name]);
|
|
3953
|
-
if (!args.json && done.some((c) => pins[c.name].personal))
|
|
3963
|
+
if (!args.json && args.personal === void 0 && done.some((c) => c.action === "installed" && !c.moved && pins[c.name].personal))
|
|
3954
3964
|
info(dim(`Pinned in your personal pins (${tildify(personalPinsDir())}) and kept out of git. --shared pins in skilllib.json for the team.`));
|
|
3955
3965
|
rememberProjects([root]);
|
|
3956
3966
|
}
|
|
@@ -4382,7 +4392,7 @@ function moveGlobal(args, names, yours) {
|
|
|
4382
4392
|
for (const [root, c] of m.changes) warnHeldLinks(root, c, `in ${basename10(root)}`);
|
|
4383
4393
|
if (m.left) warn(`${m.name}: ${m.left.join(", ")} now point${m.left.length === 1 ? "s" : ""} at nothing (the repo's own copy serves that package): remove ${m.left.length === 1 ? "it" : "them"}`);
|
|
4384
4394
|
}
|
|
4385
|
-
const toCommit = [...new Set(moves.filter((m) => m.moved).flatMap((m) => m.changes.
|
|
4395
|
+
const toCommit = [...new Set(moves.filter((m) => m.moved).flatMap((m) => m.changes.filter(([r]) => pinsIn(r)[m.name] && !pinsIn(r)[m.name].personal).map(([r]) => r)))];
|
|
4386
4396
|
if (toCommit.length) info(dim(`Commit skilllib.json and the skill folders in ${toCommit.map((r) => basename10(r)).join(", ")}.`));
|
|
4387
4397
|
}
|
|
4388
4398
|
function tidy(args) {
|
|
@@ -4489,11 +4499,14 @@ export {
|
|
|
4489
4499
|
versionHistory,
|
|
4490
4500
|
forgetLatest,
|
|
4491
4501
|
latestVersion,
|
|
4502
|
+
versionForHash,
|
|
4492
4503
|
projectHere,
|
|
4493
4504
|
readManifest,
|
|
4505
|
+
personalPinsFile,
|
|
4494
4506
|
knownProjects,
|
|
4495
4507
|
rememberProjects,
|
|
4496
4508
|
readableRoots,
|
|
4509
|
+
pinsIn,
|
|
4497
4510
|
setJsonMode,
|
|
4498
4511
|
isJsonMode,
|
|
4499
4512
|
json,
|
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-YQFLFWAY.js";
|
|
35
35
|
|
|
36
36
|
// src/index.ts
|
|
37
37
|
import { readFileSync } from "fs";
|
|
@@ -140,10 +140,10 @@ async function main() {
|
|
|
140
140
|
if (!["--version", "-v", "version", "help", "--help", "-h"].includes(command ?? "")) refreshAgentSkill();
|
|
141
141
|
switch (command) {
|
|
142
142
|
case void 0:
|
|
143
|
-
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-
|
|
143
|
+
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-P3HKRNHI.js")).tui() : overview(args);
|
|
144
144
|
case "ui":
|
|
145
145
|
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("`skilllib ui` needs an interactive terminal. Agents and scripts: use `skilllib status --json`.");
|
|
146
|
-
return (await import("./tui-
|
|
146
|
+
return (await import("./tui-P3HKRNHI.js")).tui();
|
|
147
147
|
case "status":
|
|
148
148
|
return status(args);
|
|
149
149
|
case "add":
|
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
moveGlobalSkills,
|
|
44
44
|
movedTogether,
|
|
45
45
|
notWritable,
|
|
46
|
+
personalPinsFile,
|
|
47
|
+
pinsIn,
|
|
46
48
|
pluginBackups,
|
|
47
49
|
projectHere,
|
|
48
50
|
projectOfFactory,
|
|
@@ -81,10 +83,11 @@ import {
|
|
|
81
83
|
updatePlugin,
|
|
82
84
|
updateProject,
|
|
83
85
|
usageIssues,
|
|
86
|
+
versionForHash,
|
|
84
87
|
versionHistory,
|
|
85
88
|
visibleProjects,
|
|
86
89
|
withoutForceHint
|
|
87
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-YQFLFWAY.js";
|
|
88
91
|
|
|
89
92
|
// src/tui/index.tsx
|
|
90
93
|
import { render } from "ink";
|
|
@@ -445,7 +448,7 @@ function globalFixes(m) {
|
|
|
445
448
|
];
|
|
446
449
|
}
|
|
447
450
|
function pinFix(w, repo, s) {
|
|
448
|
-
if (s.source !== "lib" || !w.library.some((l) => l.name === s.name)) return null;
|
|
451
|
+
if (s.source !== "lib" || !s.pinMoves || !w.library.some((l) => l.name === s.name)) return null;
|
|
449
452
|
if (s.personal)
|
|
450
453
|
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
454
|
if (s.inGit || isShared(s.git)) return null;
|
|
@@ -2095,7 +2098,7 @@ function uniqueNames(roots) {
|
|
|
2095
2098
|
})
|
|
2096
2099
|
);
|
|
2097
2100
|
}
|
|
2098
|
-
function localSkill(root, s, git) {
|
|
2101
|
+
function localSkill(root, s, git, pins, canBePersonal) {
|
|
2099
2102
|
const managed = {
|
|
2100
2103
|
"edited locally": { edited: true },
|
|
2101
2104
|
"edited locally, update available": { edited: true },
|
|
@@ -2114,7 +2117,9 @@ function localSkill(root, s, git) {
|
|
|
2114
2117
|
const state = git ? git.of(relativeTo(root, s.path)) : null;
|
|
2115
2118
|
const others = otherCopies(root, s.name, s.location);
|
|
2116
2119
|
const own = other[s.state]?.source === "untracked" && isShared(state) ? { source: "repo" } : {};
|
|
2117
|
-
const
|
|
2120
|
+
const dep = pins[s.name];
|
|
2121
|
+
const inGit = s.managed && (isShared(state) || (dep?.links ?? []).some((d) => isShared(git?.of(`${d}/${s.name}`))));
|
|
2122
|
+
const pinMoves = s.managed && !!dep && versionForHash(s.name, dep.hash) !== null && !s.state.startsWith("edited") && (s.personal || canBePersonal);
|
|
2118
2123
|
return {
|
|
2119
2124
|
name: s.name,
|
|
2120
2125
|
source: "lib",
|
|
@@ -2122,7 +2127,7 @@ function localSkill(root, s, git) {
|
|
|
2122
2127
|
path: s.path,
|
|
2123
2128
|
// Agents that actually load it here: visibility lists every agent you use, with `paths: 0` for the blind ones.
|
|
2124
2129
|
agents: s.visibility.filter((v) => v.paths > 0).map((v) => v.id),
|
|
2125
|
-
...s.managed ? { version: s.version ?? void 0, ...s.personal ? { personal: true } : {}, ...inGit ? { inGit: true } : {}, ...managed[s.state] } : { ...other[s.state], ...own },
|
|
2130
|
+
...s.managed ? { version: s.version ?? void 0, ...s.personal ? { personal: true } : {}, ...inGit ? { inGit: true } : {}, ...pinMoves ? { pinMoves: true } : {}, ...managed[s.state] } : { ...other[s.state], ...own },
|
|
2126
2131
|
...s.source ? { origin: `npx skills: ${s.source}` } : {},
|
|
2127
2132
|
...others.length ? { others } : {},
|
|
2128
2133
|
git: state
|
|
@@ -2219,11 +2224,11 @@ function loadWorld() {
|
|
|
2219
2224
|
name: names.get(root),
|
|
2220
2225
|
path: root,
|
|
2221
2226
|
manifest: hasManifest ? git ? git.of("skilllib.json") : "no git" : "none",
|
|
2222
|
-
skills: status.map((s) => {
|
|
2223
|
-
const local = localSkill(root, s, git);
|
|
2227
|
+
skills: ((pins, canBePersonal) => status.map((s) => {
|
|
2228
|
+
const local = localSkill(root, s, git, pins, canBePersonal);
|
|
2224
2229
|
const own = s.state.includes("missing") ? "" : readSkillInfo(s.path).description;
|
|
2225
2230
|
return own && own !== descriptions[s.name] ? { ...local, description: own } : local;
|
|
2226
|
-
}),
|
|
2231
|
+
}))(pinsIn(root), personalPinsFile(root) !== null),
|
|
2227
2232
|
...loadedHere(root)
|
|
2228
2233
|
};
|
|
2229
2234
|
});
|
|
@@ -2376,8 +2381,14 @@ function realOps(roots, backups, machine, libraryNames) {
|
|
|
2376
2381
|
},
|
|
2377
2382
|
pin: (repo, name, personal) => {
|
|
2378
2383
|
const c = addSkill(rootOf(repo), name, { personal });
|
|
2379
|
-
if (c.action === "skipped") return failed(
|
|
2380
|
-
|
|
2384
|
+
if (c.action === "skipped") return failed(`${name}: ${withoutForceHint(c.reason ?? "skipped")}`);
|
|
2385
|
+
const moved = 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`;
|
|
2386
|
+
if (c.uncommitted?.length) {
|
|
2387
|
+
const copy = `${copyDir(repo, name)}/${name}`;
|
|
2388
|
+
return failed(`${moved}, but not linked in ${c.uncommitted.join(", ")}: git tracks ${c.uncommitted.length === 1 ? "it" : "them"} and ${copy} isn't committed, so agents reading ${c.uncommitted.length === 1 ? "it" : "them"} can't use ${name}. Commit ${copy}, then run skilllib link --allow-tracked`);
|
|
2389
|
+
}
|
|
2390
|
+
if (c.blocked?.length || c.inTheWay?.length) return withLinks(c, repo, name);
|
|
2391
|
+
return moved;
|
|
2381
2392
|
},
|
|
2382
2393
|
copyToLibrary: (repo, name) => ((s) => s ? toLibrary(s.path) : failed(`${name} isn't in ${repo} any more`))(find(repo, name)),
|
|
2383
2394
|
deleteGlobal: (m) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilllib",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.2",
|
|
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
|
@@ -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>...` (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}]
|
|
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's pinned, in `skilllib.json` and your personal pins; takes no names). Pass several names in one call. Their result is `[{name, action, from, to, dirs, personal, moved, reason, blocked, uncommitted, inTheWay, backedUp}]` (`personal: true` when the pin is in your personal pins; `moved` when `--personal`/`--shared` moved it), 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.
|