skilllib 1.12.1 → 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.
@@ -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.split(" "));
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 };
@@ -1395,8 +1395,8 @@ function addSkill(root, name, { force = false, version, allowTracked = false, pe
1395
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` };
1396
1396
  const target = keep ?? (version === void 0 ? latestVersion(name) : getVersion(name, version));
1397
1397
  if (!target) return { name, action: "skipped", reason: version === void 0 ? "not in the library" : `version ${version} doesn't exist` };
1398
- const plan = addPlan(root, name, target.hash, force);
1399
- 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 };
1400
1400
  const { manifest, recorded, primary, wantedLinks, to, local } = plan;
1401
1401
  const mine = pinsWhere(root, recorded, personal);
1402
1402
  if (typeof mine === "string") return { name, action: "skipped", reason: mine };
@@ -2400,6 +2400,8 @@ function intoLibrary(path, force = false) {
2400
2400
  if (r.status === "exists") throw new Error(`${r.name}: your library has a different ${r.name}; update or delete it first`);
2401
2401
  return r.name;
2402
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);
2403
2405
  function track(root, name) {
2404
2406
  const c = addSkill(root, name);
2405
2407
  if (c.action === "skipped") throw new Error(`${name}: ${c.reason}`);
@@ -2937,7 +2939,7 @@ function repoIssues(root, ctx) {
2937
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.`}`,
2938
2940
  at: rows(root, [s.name]),
2939
2941
  // It changes what the shared skilllib.json pins: a choice, never run by `doctor --fix`.
2940
- ...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}` }] }
2941
2943
  });
2942
2944
  if (restorable.length > 0) {
2943
2945
  ctx.issues.push({
@@ -2981,12 +2983,12 @@ function repoIssues(root, ctx) {
2981
2983
  // Editing skilllib.json is the team's call: a choice, never run by `doctor --fix`.
2982
2984
  choices: [
2983
2985
  {
2984
- label: "Remove it from skilllib.json",
2986
+ label: s.personal ? "Remove it from your personal pins" : "Remove it from skilllib.json",
2985
2987
  hint: `${where} stops pinning it`,
2986
2988
  run: () => {
2987
2989
  const r = removeSkill(root, s.name);
2988
2990
  if (r.action !== "removed") throw new Error(`${s.name}: ${r.reason}`);
2989
- return `${s.name}: removed from ${where}'s skilllib.json`;
2991
+ return `${s.name}: removed from ${pinsOf(where, s.personal)}`;
2990
2992
  }
2991
2993
  }
2992
2994
  ]
@@ -3003,7 +3005,7 @@ function repoIssues(root, ctx) {
3003
3005
  choices: [
3004
3006
  {
3005
3007
  label: "Update to the newest versions",
3006
- 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",
3007
3009
  run: () => summarize(where, "updated", updateProject(root, behind.map((s) => s.name)))
3008
3010
  }
3009
3011
  ]
@@ -3086,7 +3088,7 @@ function repoIssues(root, ctx) {
3086
3088
  if (partial.length) {
3087
3089
  const missing2 = [...new Set(partial.flatMap((s) => s.visibility.filter((v) => v.paths === 0).map((v) => v.id)))];
3088
3090
  const tracked = [...new Set(blindHere.flatMap(({ would }) => would.blocked))];
3089
- 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));
3090
3092
  const run = (allowTracked) => () => {
3091
3093
  if (allowTracked) allowTrackedLinks(root);
3092
3094
  const r = linkAll(root, { allowTracked, names: partial.map((s) => s.name) });
@@ -3162,9 +3164,9 @@ function repoIssues(root, ctx) {
3162
3164
  id: `adopt:${root}:${s.name}`,
3163
3165
  severity: "suggestion",
3164
3166
  title: `${s.name} in ${where} isn't tracked`,
3165
- detail: "It's identical to the library copy; track it (in skilllib.json) so future library changes reach it.",
3167
+ detail: `It's identical to the library copy; track it (in ${newPinsOf(root, where)}) so future library changes reach it.`,
3166
3168
  at: rows(root, [s.name]),
3167
- choices: [{ label: "Track it", hint: `adds it to ${where}'s skilllib.json`, run: () => `${track(root, s.name).name} tracked in ${where}` }]
3169
+ choices: [{ label: "Track it", hint: `adds it to ${newPinsOf(root, where)}`, run: () => `${track(root, s.name).name} tracked in ${where}` }]
3168
3170
  });
3169
3171
  } else if (s.state === "untracked, differs from library" || s.state === "repo skill, differs from library") {
3170
3172
  const teams = s.state.startsWith("repo skill") || committed(s) || !!untrackable(s);
@@ -3226,7 +3228,7 @@ function repoIssues(root, ctx) {
3226
3228
  id: `not-in-lib:${root}:${s.name}`,
3227
3229
  severity: "suggestion",
3228
3230
  title: `${s.name} in ${where} is tracked, but no longer in your library`,
3229
- 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.`,
3230
3232
  at: rows(root, [s.name]),
3231
3233
  choices: [
3232
3234
  {
@@ -3958,7 +3960,7 @@ function add(args) {
3958
3960
  printChanges(root, changes, args);
3959
3961
  const pins = readManifest(root).skills;
3960
3962
  const done = changes.filter((c) => c.action !== "skipped" && pins[c.name]);
3961
- 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))
3962
3964
  info(dim(`Pinned in your personal pins (${tildify(personalPinsDir())}) and kept out of git. --shared pins in skilllib.json for the team.`));
3963
3965
  rememberProjects([root]);
3964
3966
  }
@@ -4390,7 +4392,7 @@ function moveGlobal(args, names, yours) {
4390
4392
  for (const [root, c] of m.changes) warnHeldLinks(root, c, `in ${basename10(root)}`);
4391
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"}`);
4392
4394
  }
4393
- const toCommit = [...new Set(moves.filter((m) => m.moved).flatMap((m) => m.changes.map(([r]) => r)))].filter((r) => existsSync14(join15(r, MANIFEST_FILE)));
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)))];
4394
4396
  if (toCommit.length) info(dim(`Commit skilllib.json and the skill folders in ${toCommit.map((r) => basename10(r)).join(", ")}.`));
4395
4397
  }
4396
4398
  function tidy(args) {
@@ -4497,11 +4499,14 @@ export {
4497
4499
  versionHistory,
4498
4500
  forgetLatest,
4499
4501
  latestVersion,
4502
+ versionForHash,
4500
4503
  projectHere,
4501
4504
  readManifest,
4505
+ personalPinsFile,
4502
4506
  knownProjects,
4503
4507
  rememberProjects,
4504
4508
  readableRoots,
4509
+ pinsIn,
4505
4510
  setJsonMode,
4506
4511
  isJsonMode,
4507
4512
  json,
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  unhide,
32
32
  update,
33
33
  usage
34
- } from "./chunk-LORSEDNW.js";
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-3QD6RQGL.js")).tui() : overview(args);
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-3QD6RQGL.js")).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-LORSEDNW.js";
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 inGit = s.managed && (isShared(state) || (readManifest(root).skills[s.name]?.links ?? []).some((d) => isShared(git?.of(`${d}/${s.name}`))));
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(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`;
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.1",
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",
@@ -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}]`, 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'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.