skilllib 1.12.2 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import {
2
+ FEATURED_HARNESSES,
2
3
  HARNESSES,
3
4
  PROJECT_SKILLS_DIR,
4
5
  addRefusal,
@@ -87,7 +88,7 @@ import {
87
88
  versionHistory,
88
89
  visibleProjects,
89
90
  withoutForceHint
90
- } from "./chunk-YQFLFWAY.js";
91
+ } from "./chunk-XMXC2XM2.js";
91
92
 
92
93
  // src/tui/index.tsx
93
94
  import { render } from "ink";
@@ -302,17 +303,6 @@ function copyText(text) {
302
303
  return { copied: false, savedTo };
303
304
  }
304
305
 
305
- // src/search.ts
306
- function matchScore(text, query) {
307
- if (!query) return 0;
308
- const t = text.toLowerCase();
309
- const q = query.toLowerCase();
310
- if (t.startsWith(q)) return 0;
311
- if (t.includes(q)) return 1;
312
- const initials = t.split(/[-_\s./]+/).filter(Boolean).map((w) => w[0]).join("");
313
- return q.length >= 2 && initials.startsWith(q) ? 2 : null;
314
- }
315
-
316
306
  // src/tui/world.ts
317
307
  var failed = (message) => ({ message, failed: true });
318
308
  var isFailure = (r) => typeof r !== "string" && "failed" in r;
@@ -623,7 +613,12 @@ function groupLabel(g, names) {
623
613
  return `${top} + ${names.length - 1} more \xB7 ${g.label}`;
624
614
  }
625
615
  function matches(name, query) {
626
- return matchScore(name, query) !== null;
616
+ if (!query) return true;
617
+ const text = name.toLowerCase();
618
+ const q = query.toLowerCase();
619
+ if (text.includes(q)) return true;
620
+ const initials = text.split(/[-_\s./]+/).filter(Boolean).map((w) => w[0]).join("");
621
+ return q.length >= 2 && initials.startsWith(q);
627
622
  }
628
623
  function addRows(w, target, q) {
629
624
  const taken = new Set(target ? project(w, target).skills.map((s) => s.name) : []);
@@ -1509,8 +1504,11 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
1509
1504
  }
1510
1505
  useEffect(() => {
1511
1506
  const folder = () => !initial.roots.length && askForFolder("Where are your projects? skilllib finds the git repos in there");
1512
- if (!initial.agentsChosen) setModal({ kind: "agents", selected: initial.agents, cursor: 0, then: folder });
1513
- else folder();
1507
+ if (!initial.agentsChosen) {
1508
+ const installed = HARNESSES.filter((h) => h.installed()).map((h) => h.id);
1509
+ const ids = [...FEATURED_HARNESSES, ...installed.filter((id) => !FEATURED_HARNESSES.includes(id))];
1510
+ setModal({ kind: "agents", selected: initial.agents, ids, cursor: 0, then: folder });
1511
+ } else folder();
1514
1512
  }, []);
1515
1513
  function openInGlobal(name) {
1516
1514
  goPlace("global");
@@ -1530,14 +1528,24 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
1530
1528
  cells: [{ text, width: 34, color: c }, { text: sub, grow: true, color: color.faint }],
1531
1529
  onEnter
1532
1530
  });
1531
+ const shown = [
1532
+ ...FEATURED_HARNESSES.map((id) => harness(id)),
1533
+ ...world.agents.filter((id) => !FEATURED_HARNESSES.includes(id)).map((id) => harness(id))
1534
+ ];
1533
1535
  return [
1534
1536
  header("#agents", "Agents"),
1535
- ...HARNESSES.map(
1537
+ ...shown.map(
1536
1538
  (h) => row(`agent:${h.id}`, `${world.agents.includes(h.id) ? "[x]" : "[ ]"} ${h.icon} ${h.name}`, h.projectDirs.join(", "), () => {
1537
1539
  const wasOn = world.agents.includes(h.id);
1538
1540
  apply((w) => (w.ops.setAgents(wasOn ? w.agents.filter((a) => a !== h.id) : HARNESSES.map((x) => x.id).filter((id) => id === h.id || w.agents.includes(id))), `${h.name} ${wasOn ? "off" : "on"}`));
1539
1541
  })
1540
1542
  ),
1543
+ row(
1544
+ "agents:more",
1545
+ "More agents\u2026",
1546
+ "Copilot, Gemini, OpenCode, Windsurf, and the rest",
1547
+ () => setModal({ kind: "agents", selected: world.agents, ids: HARNESSES.map((h) => h.id), cursor: 0 })
1548
+ ),
1541
1549
  // The skill that lets your agents use skilllib: installing adds a global skill, so it asks first.
1542
1550
  row(
1543
1551
  "agent-skill",
@@ -1667,11 +1675,13 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
1667
1675
  if (m.kind === "add") return addKeys(m, input, key);
1668
1676
  }
1669
1677
  function agentsKeys(m, input, key) {
1678
+ const list2 = m.ids.map((id) => harness(id));
1670
1679
  if (key.escape && !m.then) return setModal(null);
1671
1680
  if (key.upArrow) return setModal({ ...m, cursor: Math.max(0, m.cursor - 1) });
1672
- if (key.downArrow) return setModal({ ...m, cursor: Math.min(HARNESSES.length - 1, m.cursor + 1) });
1681
+ if (key.downArrow) return setModal({ ...m, cursor: Math.min(list2.length - 1, m.cursor + 1) });
1673
1682
  if (input === " ") {
1674
- const id = HARNESSES[m.cursor].id;
1683
+ const id = list2[m.cursor]?.id;
1684
+ if (!id) return;
1675
1685
  return setModal({ ...m, selected: m.selected.includes(id) ? m.selected.filter((x) => x !== id) : [...m.selected, id] });
1676
1686
  }
1677
1687
  if (key.return) {
@@ -1905,11 +1915,11 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
1905
1915
  empty: "",
1906
1916
  header: /* @__PURE__ */ jsx2(Text2, { color: color.muted, children: "skilllib makes your skills visible to each one. Detected ones are ticked." }),
1907
1917
  headerHeight: 1,
1908
- rows: HARNESSES.map((h) => ({
1918
+ rows: modal.ids.map((id) => harness(id)).map((h) => ({
1909
1919
  key: h.id,
1910
1920
  cells: [
1911
1921
  { text: modal.selected.includes(h.id) ? "[x] " : "[ ] ", width: 4, color: modal.selected.includes(h.id) ? color.green : color.faint },
1912
- { text: `${h.icon} ${h.name}`, width: 18, color: h.color },
1922
+ { text: `${h.icon} ${h.name}`, width: 22, color: h.color },
1913
1923
  { text: `reads ${h.projectDirs.join(", ")}`, grow: true, color: color.faint }
1914
1924
  ]
1915
1925
  }))
package/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "skilllib",
3
- "version": "1.12.2",
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.",
3
+ "version": "1.14.0",
4
+ "description": "Manage your agent skills like dependencies. One library, install from skills.sh, and every agent you use can see them.",
5
5
  "keywords": [
6
6
  "agent-skills",
7
7
  "skills",
8
+ "skills.sh",
8
9
  "skill-md",
9
10
  "claude-code",
10
11
  "claude",
11
12
  "cursor",
12
13
  "codex",
14
+ "gemini",
15
+ "github-copilot",
16
+ "opencode",
13
17
  "zed",
14
18
  "grok",
15
19
  "ai-agents",
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: skilllib
3
- description: Agent skills (SKILL.md) loaded in this repo and the user's skill library, via the skilllib CLI. Use for which skills you can use here and where each comes from, which library skills to add, what's out of date, broken or duplicated, and adding, removing or updating skills.
3
+ description: Agent skills (SKILL.md) loaded in this repo and the user's skill library, via the skilllib CLI. Use for which skills you can use here and where each comes from, which library skills to add, what's out of date, broken or duplicated, adding, removing or updating skills, and installing skills from skills.sh or GitHub.
4
4
  allowed-tools: Bash(skilllib:*)
5
5
  ---
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` (shared, committed), or in the user's personal pins (`~/.skilllib/pins`, kept out of git: `personal: true` in the output).
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. Adding a skill pins it in the user's personal pins (`~/.skilllib/pins`, kept out of git: `personal: true`). `skilllib.json` is only for sharing a skill with the team, and only when they ask: `--shared` writes it, and they commit it.
10
10
 
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.
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`, `search` (without `--json`) 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
 
13
13
  ## Reading
14
14
 
@@ -23,9 +23,11 @@ The **Now** section at the end has this repo's `status` and `list` output. Answe
23
23
 
24
24
  Also: `usage --json` (Claude Code usage only, so "unused" is a hint; each skill lists the `projects` that used it), `projects --json` (every repo), `outdated --all --json`, `doctor --json` (every issue in every repo and globally, each with an `id`).
25
25
 
26
+ `check --json` reads the global skills and, in a repo, that repo's. It stores nothing. `brief` is the report: how many tokens the names and descriptions cost on every prompt, whether that is heavy, and the cuts that can actually be made. A Claude Code plugin it names turns off with `plugin off <id> --json`, and `plugin on <id>` puts it back. Run that only after the user agrees: the switch is global, and a skill in the plugin that was used goes too. Cursor plugins turn off in Cursor. claude.ai skills turn off on claude.ai. Built-ins ship with Cursor, Codex, and Grok, and skilllib can't unload them. Your own unused globals can move into the repos that need them. `uses` is Claude Code only, so 0 means it wasn't seen. Read `brief` to the user. The `skills` list is there if they ask about one.
27
+
26
28
  ## Changing (ask first unless the user asked for this exact change)
27
29
 
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}`.
30
+ `plugin off|on <id>` (a Claude Code plugin, `name@marketplace` from `check`; a name alone works when only one plugin has it; it stays installed. The copy that loads in every repo. A per-repo install is left on, and the message says where) · `add <name>...` (personal pins; `--shared` writes `skilllib.json` for the team, and only when they asked to share it; `--personal` moves a shared pin back. Either flag moves an existing pin without changing its version. A pin stays where it already is) · `remove <name>...` (skills skilllib manages, `source: library`) · `update [name...]` · `sync` (installs what's pinned, personal pins and `skilllib.json`; 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
31
 
30
32
  - `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
33
  - `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,16 +35,16 @@ Also: `usage --json` (Claude Code usage only, so "unused" is a hint; each skill
33
35
  - `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
36
  - `blocked`: git tracks that folder. Re-run with `--allow-tracked` (for that command only) only with their OK.
35
37
  - `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. For a change without `personal`, remind the user to commit `skilllib.json` and the folders in `dirs`; a `personal` one has nothing to commit.
38
+ - Changes apply from the agent's next session. A `personal` change has nothing to commit. Without `personal` (`--shared`, or a pin that was already in `skilllib.json`), remind the user to commit `skilllib.json` and the folders in `dirs`.
37
39
 
38
40
  **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
41
 
40
- - `global move <name>... --to <repo>[,<repo>...]` (a project name from `projects`, or the path of a repo's top folder): imports it into the library, adds it to each repo, and only then stops loading it globally (restorable with `skilllib restore`). Prints `[{skill, moved, reason, left, projects: [{project, path, change}]}]` (`left`: links in a repo or its packages that now point at nothing, though a copy in the repo still serves them; tell the user to remove them); `moved: false` means it still loads globally, and `reason` says why (for example, git tracks the folder a link goes in, so an agent couldn't see it there: re-run with `--allow-tracked` only with the user's OK).
42
+ - `global move <name>... --to <repo>[,<repo>...]` (a project name from `projects`, or the path of a repo's top folder): imports it into the library, adds it to each repo, and only then stops loading it globally (restorable with `skilllib restore`). Prints `[{skill, moved, reason, left, projects: [{project, path, change}]}]` (`left`: links in a repo or its packages that now point at nothing, though a copy in the repo still serves them; tell the user to remove them); `moved: false` means it still loads globally, and `reason` says why.
41
43
  - `global keep <name>...`: the user wants it everywhere; skilllib stops flagging it.
42
44
 
43
45
  **Fixes and decisions.** `apply <id> [choice]` runs one problem's `fix`, or the choice you name: its label (safest), or its number from 1. With several choices it needs one. Prints `{id, applied, ok, message}`. Ids come from `issues` in `status --json` (Now, below) or from `doctor --json` (every repo; pass `apply` the same `--days` you gave `doctor`). A few issues span repos and offer different choices in each; then `apply` takes only the label.
44
46
 
45
- **Importing.** `import <dir>...` copies skill folders (each with a `SKILL.md`) into the library; skilllib doesn't download anything. For a skill on GitHub, clone the repo into a temporary folder, `import` the skill's folder, then `add` it here.
47
+ **Importing and installing.** `import <dir>...` copies skill folders (each with a `SKILL.md`) into the library; it doesn't download anything. `install <source>...` does: `owner/repo`, `owner/repo@skill`, a `https://skills.sh/...` or GitHub URL, or a local folder. It puts each skill in the library. In a repo it also adds them here; `--global` loads them in every repo instead (and marks them kept). A repo with several skills is listed, not installed, until you pass `@name` or `--all`. `search <query>` lists matches from skills.sh. `install` and `search` accept `--json`.
46
48
 
47
49
  ## Now
48
50