skilllib 1.11.0 → 1.11.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 +44 -32
- package/dist/{chunk-LO4FMXDE.js → chunk-OFWHWIER.js} +82 -63
- package/dist/index.js +10 -6
- package/dist/json.d.cts +5 -1
- package/dist/json.d.ts +5 -1
- package/dist/{tui-3LDZ5GOZ.js → tui-NGTOOVAQ.js} +7 -6
- package/package.json +1 -1
- package/skills/skilllib/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# ◆ skilllib
|
|
4
4
|
|
|
5
|
-
**Manage your agent skills like dependencies.**<br>
|
|
5
|
+
**Manage your agent skills like dependencies, or let your agents do it.**<br>
|
|
6
6
|
One library for all your skills. Each repo gets only the ones it needs,<br>
|
|
7
7
|
and every agent you use can see them.
|
|
8
8
|
|
|
@@ -12,9 +12,11 @@ and every agent you use can see them.
|
|
|
12
12
|

|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
|
|
15
|
+
npm install -g skilllib && skilllib agent-skill install
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
Then ask your agent: *"Which skills can you use here, and which of my global skills should only load in this repo?"*
|
|
19
|
+
|
|
18
20
|
<img src="docs/design/repo.svg" alt="skilllib: Places on the left, a repo's skills with their issues and uses, and the selected skill's fixes and actions below" width="100%">
|
|
19
21
|
|
|
20
22
|
</div>
|
|
@@ -43,18 +45,18 @@ Most agents can't turn off a global skill for just one repo. Claude Code can, bu
|
|
|
43
45
|
|
|
44
46
|
## How skilllib fixes it
|
|
45
47
|
|
|
48
|
+
- **🤖 Your agents run it.** One small skill teaches Claude Code, Cursor, Codex, Zed and Grok to use the skilllib CLI. Ask them what's loaded here, what to add, what's out of date, or to move a global skill into the repos that use it. Every command has `--json` output shaped for agents, and they ask before changing anything.
|
|
46
49
|
- **📚 Your skills: one versioned library.** Every skill has a single master copy in `~/.skilllib/library`, and each change becomes a new version.
|
|
47
50
|
- **📦 Per-repo installs, pinned like dependencies.** Each repo lists its skills and versions in `skilllib.json`. `sync` reproduces them exactly, and `update` moves to the newest.
|
|
48
51
|
- **🤝 One copy, every agent.** skilllib puts one real copy in the repo and links it into whichever folders your other agents read.
|
|
49
|
-
- **🧹 Health: one list of what to fix.** Global skills you haven't decided about, skills loaded twice, duplicate copies, updates, broken links, each with its fix. For a global skill: move it to the repos that need it, keep it global, or delete it. Usage numbers and descriptions help you decide.
|
|
52
|
+
- **🧹 Health: one list of what to fix.** Global skills you haven't decided about, skills loaded twice, duplicate copies, updates, broken links, each with its fix. For a global skill: move it to the repos that need it, keep it global, or delete it. Usage numbers and descriptions help you, or your agent, decide.
|
|
50
53
|
- **✓ Global on purpose.** Some skills belong everywhere. Mark them once and skilllib stops warning about them.
|
|
51
54
|
- **🔎 See everything in a repo:**
|
|
52
55
|
- where each skill comes from (the repo, your library, global, a plugin, claude.ai…)
|
|
53
56
|
- which agents load it
|
|
54
57
|
- whether it's committed to git or gitignored
|
|
55
58
|
- how often it was used
|
|
56
|
-
-
|
|
57
|
-
- **✦ Ask an agent.** Copy a ready-made prompt with all of that data, and let Claude Code, Cursor, Codex or Grok tell you what to keep, move or delete.
|
|
59
|
+
- **🖥 An app when you want to look yourself.** `skilllib` opens a terminal app with the same data and fixes, and can copy a review prompt for any agent.
|
|
58
60
|
- **🛟 Nothing is lost.** Every removal goes to a backup you can restore with one key.
|
|
59
61
|
|
|
60
62
|
Everything runs locally: no server, no account, no network calls.
|
|
@@ -63,17 +65,27 @@ Everything runs locally: no server, no account, no network calls.
|
|
|
63
65
|
|
|
64
66
|
## Quick start
|
|
65
67
|
|
|
68
|
+
It needs **Node.js 22+** and works on macOS and Linux. Windows support is newer: it uses directory junctions, so no admin rights are needed.
|
|
69
|
+
|
|
70
|
+
### With your agents
|
|
71
|
+
|
|
66
72
|
```bash
|
|
67
|
-
npm install -g skilllib
|
|
68
|
-
skilllib
|
|
73
|
+
npm install -g skilllib
|
|
74
|
+
skilllib agent-skill install
|
|
69
75
|
```
|
|
70
76
|
|
|
71
|
-
|
|
77
|
+
This installs the `skilllib` skill where your agents look, in every repo (see [Use it from your agents](#use-it-from-your-agents)). Start a new agent session in any repo and ask away.
|
|
78
|
+
|
|
79
|
+
### In the app
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
skilllib # or: npx skilllib
|
|
83
|
+
```
|
|
72
84
|
|
|
73
85
|
On first run, skilllib asks two things:
|
|
74
86
|
|
|
75
87
|
1. **Which agents you use:** Claude Code, Cursor, Codex, Zed, Grok. The ones installed on your machine are pre-selected.
|
|
76
|
-
2. **Where your projects live**, e.g. `~/Projects`. It finds every git repo in there, and rescans each time it opens.
|
|
88
|
+
2. **Where your projects live**, e.g. `~/Projects`. It finds every git repo in there, and rescans each time it opens. Agent worktrees (`<repo>/.claude/worktrees/*`) aren't counted as projects.
|
|
77
89
|
|
|
78
90
|
Then a good first session looks like this:
|
|
79
91
|
|
|
@@ -83,7 +95,26 @@ Then a good first session looks like this:
|
|
|
83
95
|
|
|
84
96
|
---
|
|
85
97
|
|
|
86
|
-
##
|
|
98
|
+
## Use it from your agents
|
|
99
|
+
|
|
100
|
+
`skilllib agent-skill install` puts a small `skilllib` skill in `~/.claude/skills` (Claude Code, Cursor and Grok read it there), with a link in `~/.agents/skills` (Codex and Zed). This is the one skill that should be global, because it's about every repo. Upgrading skilllib keeps it up to date, and `skilllib agent-skill remove` takes it out. Agents use it from their next session.
|
|
101
|
+
|
|
102
|
+
Then ask in plain words. A few examples, with what the agent runs:
|
|
103
|
+
|
|
104
|
+
| Ask | The agent runs |
|
|
105
|
+
|---|---|
|
|
106
|
+
| *Which skills can you use here, and where does each come from?* | `status --json`, which Claude Code gets as the skill loads, so it answers in one step |
|
|
107
|
+
| *Which of my skills should this repo use?* | `list --json`, then reads the README and manifests, and gives a reason for each pick |
|
|
108
|
+
| *Which skills do I use most? Which ones never?* | `usage --json` (Claude Code usage only, so "never" is a hint) |
|
|
109
|
+
| *Move my global skills into the repos that actually use them.* | `usage --json` to see where each is used, then `global move <name> --to <repo>`, or `global keep <name>` for the ones you want everywhere |
|
|
110
|
+
| *Is anything broken or out of date? Fix it.* | `status --json` or `doctor --json`, then `doctor --fix` for the safe repairs and `apply <id> <choice>` for each decision you approve |
|
|
111
|
+
| *Add the skill from github.com/acme/skills to my library.* | Clones the repo into a temporary folder, then `import <dir>` and `add <name>`. skilllib itself downloads nothing |
|
|
112
|
+
|
|
113
|
+
The agent asks before it changes anything, unless you asked for that exact change. Anything skilllib removes can be restored: see [Backups](#backups).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## The app
|
|
87
118
|
|
|
88
119
|
### The screen
|
|
89
120
|
|
|
@@ -224,25 +255,6 @@ Paste it into Claude Code, Cursor, Codex or Grok. You get back **keep global / m
|
|
|
224
255
|
|
|
225
256
|
It's in every skill's actions, in a repo's **⋯** menu (all its skills), and at the top of Global (all your global skills).
|
|
226
257
|
|
|
227
|
-
### Ask your agents about skills
|
|
228
|
-
|
|
229
|
-
Run this once:
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
skilllib agent-skill install
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
This installs a small `skilllib` skill that tells your agents how to use skilllib. It goes where your agents look, in every repo: `~/.claude/skills` for Claude Code and Cursor, with a link in `~/.agents/skills` for Codex and Zed. This is the one skill that should be global, because it's about every repo. Health and Settings offer the same install, and upgrading skilllib keeps the skill up to date.
|
|
236
|
-
|
|
237
|
-
Then, in any repo, you can ask:
|
|
238
|
-
|
|
239
|
-
- *Which skills can you use in this repo?*
|
|
240
|
-
- *Where does each one come from?*
|
|
241
|
-
- *Which skills from my library would you add to this project?*
|
|
242
|
-
- *Is anything out of date? Update it.*
|
|
243
|
-
|
|
244
|
-
The agent runs `skilllib status --json`, `skilllib list --json` and similar commands, reads the repo, and answers. It asks before it adds or removes anything. `skilllib agent-skill remove` uninstalls it.
|
|
245
|
-
|
|
246
258
|
### Duplicates
|
|
247
259
|
|
|
248
260
|
The same skill often reaches your agents more than once: a copy in `.claude/skills` *and* in `.agents/skills`, a global copy *and* a repo copy, a plugin *and* your skill. skilllib keeps **one real copy per skill, plus only the links your agents need**. **Health** lists each case on the skill it's about, with its fix. From the command line, `skilllib doctor` lists them and `skilllib tidy` fixes the folder ones.
|
|
@@ -351,7 +363,7 @@ A skill is a folder with a `SKILL.md`. These are all the places your agents load
|
|
|
351
363
|
|
|
352
364
|
## Command line
|
|
353
365
|
|
|
354
|
-
Everything the app does also works as a command, for scripts and CI:
|
|
366
|
+
Everything the app does also works as a command, for your agents, scripts and CI:
|
|
355
367
|
|
|
356
368
|
```
|
|
357
369
|
skilllib open the app (prints `status` when piped)
|
|
@@ -385,7 +397,7 @@ The repo commands (`status`, `add`, `remove`, `sync`, `outdated`, `update`, `lin
|
|
|
385
397
|
|
|
386
398
|
Flags: `--force` (overwrite local edits, backed up to `~/.skilllib/edit-backup`), `--allow-tracked` (link into git-tracked folders; skills git doesn't share are still left out), `--days N`, `--json`. For `tidy`: `--dry-run` (preview), `--allow-git` (also change committed files), `<name> --keep <folder>` (the copy that wins when copies differ).
|
|
387
399
|
|
|
388
|
-
A command exits 1 when it couldn't do all you asked: a skill skipped (not in the library, or local edits in the way), links git-tracked folders blocked, an unknown `tidy` name or `--keep` folder. What it did do is still done, and listed.
|
|
400
|
+
A command exits 1 when it couldn't do all you asked: a skill skipped (not in the library, or local edits in the way), links git-tracked folders blocked, an unknown `tidy` name or `--keep` folder. What it did do is still done, and listed. An unknown or misspelled flag is an error too, so a typo never runs silently. With `--json`, a failure prints `{"error": "…"}` on stdout as well as the message on stderr.
|
|
389
401
|
|
|
390
402
|
**`--json`** prints one line of JSON on stdout, for scripts and agents. Messages and warnings go to stderr. It works with `status`, `list`, `show`, `projects`, `outdated`, `usage`, `doctor`, `apply`, `add`, `remove`, `sync`, `update`, `import`, `global move` and `agent-skill`. The output is shaped to keep agents fast and cheap:
|
|
391
403
|
|
|
@@ -442,7 +454,7 @@ Environment variables: `SKILLLIB_HOME` moves `~/.skilllib`, `$VISUAL` / `$EDITOR
|
|
|
442
454
|
- Turning off Cursor marketplace plugins: skilllib lists them and reports ones that duplicate your skills, but Cursor doesn't record which plugins are on in a file skilllib can read.
|
|
443
455
|
- Installing plugins from a marketplace: use `/plugin` in Claude Code. Plugins a repo's own `.claude/settings.json` turns on without installing them aren't listed in Plugins yet.
|
|
444
456
|
- Unloading a `npx skills` skill doesn't update that tool's lock file.
|
|
445
|
-
-
|
|
457
|
+
- Downloading skills: skilllib makes no network calls. To get a skill from GitHub, clone the repo and run `skilllib import <folder>`, or ask your agent to.
|
|
446
458
|
|
|
447
459
|
---
|
|
448
460
|
|
|
@@ -47,6 +47,7 @@ var jsonMode = false;
|
|
|
47
47
|
function setJsonMode(on) {
|
|
48
48
|
jsonMode = on;
|
|
49
49
|
}
|
|
50
|
+
var isJsonMode = () => jsonMode;
|
|
50
51
|
function json(output, data) {
|
|
51
52
|
process.stdout.write(JSON.stringify(data) + "\n");
|
|
52
53
|
}
|
|
@@ -219,7 +220,8 @@ function isSkillDir(dir) {
|
|
|
219
220
|
function listFiles(dir) {
|
|
220
221
|
return readdirSync(dir).filter((entry) => !IGNORED.has(entry)).flatMap((entry) => {
|
|
221
222
|
const full = join3(dir, entry);
|
|
222
|
-
|
|
223
|
+
const st = statSync(full, { throwIfNoEntry: false });
|
|
224
|
+
return !st ? [] : st.isDirectory() ? listFiles(full) : [full];
|
|
223
225
|
});
|
|
224
226
|
}
|
|
225
227
|
function treeHash(dir) {
|
|
@@ -232,8 +234,9 @@ function treeHash(dir) {
|
|
|
232
234
|
return hash.digest("hex").slice(0, 16);
|
|
233
235
|
}
|
|
234
236
|
function copySkill(from, to) {
|
|
237
|
+
const source = realpathSync2(from);
|
|
235
238
|
rmSync(to, { recursive: true, force: true });
|
|
236
|
-
cpSync(
|
|
239
|
+
cpSync(source, to, { recursive: true, dereference: true, filter: (src) => !IGNORED.has(basename(src)) && existsSync2(src) });
|
|
237
240
|
}
|
|
238
241
|
function skillDirsIn(dir) {
|
|
239
242
|
if (!existsSync2(dir)) return [];
|
|
@@ -461,7 +464,7 @@ function setKeepGlobal(names, keep) {
|
|
|
461
464
|
|
|
462
465
|
// src/library.ts
|
|
463
466
|
import { execFileSync } from "child_process";
|
|
464
|
-
import { existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync6, realpathSync as realpathSync4, renameSync, rmSync as rmSync2, symlinkSync, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
|
|
467
|
+
import { cpSync as cpSync2, existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync6, realpathSync as realpathSync4, renameSync, rmSync as rmSync2, symlinkSync, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
|
|
465
468
|
import { basename as basename3, dirname as dirname2, join as join8, relative as relative2, sep as sep3 } from "path";
|
|
466
469
|
|
|
467
470
|
// src/sources.ts
|
|
@@ -1032,25 +1035,28 @@ function removeSkill(root, name, { force = false } = {}) {
|
|
|
1032
1035
|
return { name, action: "removed", ...backedUp ? { backedUp } : {} };
|
|
1033
1036
|
}
|
|
1034
1037
|
function syncProject(root, { force = false } = {}) {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
});
|
|
1038
|
+
return Object.keys(readManifest(root).skills).flatMap((name) => syncSkill(root, name, { force }));
|
|
1039
|
+
}
|
|
1040
|
+
function syncSkill(root, name, { force = false } = {}) {
|
|
1041
|
+
const dep = readManifest(root).skills[name];
|
|
1042
|
+
if (!dep) return [];
|
|
1043
|
+
const local = treeHash(join8(root, dep.dir ?? PROJECT_SKILLS_DIR, name));
|
|
1044
|
+
const linksOk = (dep.links ?? []).every((d) => existsSync7(join8(root, d, name, "SKILL.md")));
|
|
1045
|
+
if (local === dep.hash && linksOk) return [];
|
|
1046
|
+
if (local !== null && local !== dep.hash && !force) return [{ name, action: "skipped", reason: "has local edits (use --force to reset them)" }];
|
|
1047
|
+
const change = addSkill(root, name, { force: true, version: pinnedVersion(name, dep) });
|
|
1048
|
+
if (change.action === "skipped") return [{ ...change, reason: `not in your library (${tildify(libraryDir())}); import it, or set SKILLLIB_HOME to the library that has it` }];
|
|
1049
|
+
return [{ ...change, action: local === null ? "installed" : change.action }];
|
|
1050
|
+
}
|
|
1051
|
+
function pinnedVersion(name, dep) {
|
|
1052
|
+
return versionForHash(name, dep.hash)?.version ?? (dep.version > 0 && getVersion(name, dep.version) ? dep.version : void 0);
|
|
1046
1053
|
}
|
|
1047
1054
|
function discardEdits(root, name) {
|
|
1048
1055
|
const dep = readManifest(root).skills[name];
|
|
1049
1056
|
if (!dep) return { name, action: "skipped", reason: "not managed by skilllib in this project" };
|
|
1050
1057
|
const dir = join8(root, dep.dir ?? PROJECT_SKILLS_DIR, name);
|
|
1051
|
-
const pinned = dep.version > 0 && getVersion(name, dep.version) ? dep.version : void 0;
|
|
1052
1058
|
if (existsSync7(dir) && !isLink(dir)) stash(dir, "edit-backup");
|
|
1053
|
-
return addSkill(root, name, { force: true, version:
|
|
1059
|
+
return addSkill(root, name, { force: true, version: pinnedVersion(name, dep) });
|
|
1054
1060
|
}
|
|
1055
1061
|
function updateProject(root, names, { force = false } = {}) {
|
|
1056
1062
|
const { skills } = readManifest(root);
|
|
@@ -1090,6 +1096,15 @@ function readBackupIndex(bucket) {
|
|
|
1090
1096
|
return {};
|
|
1091
1097
|
}
|
|
1092
1098
|
}
|
|
1099
|
+
function move(from, to) {
|
|
1100
|
+
try {
|
|
1101
|
+
renameSync(from, to);
|
|
1102
|
+
} catch (err) {
|
|
1103
|
+
if (err.code !== "EXDEV") throw err;
|
|
1104
|
+
cpSync2(from, to, { recursive: true, verbatimSymlinks: true });
|
|
1105
|
+
rmSync2(from, { recursive: true, force: true });
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1093
1108
|
function stash(dir, bucket) {
|
|
1094
1109
|
let at = Date.now();
|
|
1095
1110
|
const entryAt = (ms) => `${basename3(dir)}-${new Date(ms).toISOString().replace(/[:.]/g, "-")}`;
|
|
@@ -1097,7 +1112,7 @@ function stash(dir, bucket) {
|
|
|
1097
1112
|
const entry = entryAt(at);
|
|
1098
1113
|
const target = join8(skilllibHome(), bucket, entry);
|
|
1099
1114
|
mkdirSync5(join8(skilllibHome(), bucket), { recursive: true });
|
|
1100
|
-
|
|
1115
|
+
move(dir, target);
|
|
1101
1116
|
writeFileSync5(backupIndexFile(bucket), JSON.stringify({ ...readBackupIndex(bucket), [entry]: dir }, null, 2) + "\n");
|
|
1102
1117
|
return target;
|
|
1103
1118
|
}
|
|
@@ -1195,7 +1210,7 @@ function restoreBackup(backup) {
|
|
|
1195
1210
|
}
|
|
1196
1211
|
if (taken) return { ok: false, reason: `${backup.name} already exists there` };
|
|
1197
1212
|
mkdirSync5(join8(to, ".."), { recursive: true });
|
|
1198
|
-
|
|
1213
|
+
move(backup.path, to);
|
|
1199
1214
|
forgetLatest(backup.name);
|
|
1200
1215
|
return { ok: true, to };
|
|
1201
1216
|
}
|
|
@@ -1265,6 +1280,7 @@ function refreshAgentSkill() {
|
|
|
1265
1280
|
|
|
1266
1281
|
// src/commands.ts
|
|
1267
1282
|
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
|
|
1283
|
+
import { parseArgs as parseFlags } from "util";
|
|
1268
1284
|
import { basename as basename9, join as join14, resolve as resolve7, sep as sep5 } from "path";
|
|
1269
1285
|
|
|
1270
1286
|
// src/git.ts
|
|
@@ -2604,23 +2620,35 @@ function moveGlobalSkills(skills, roots, { group, allowTracked = false, label =
|
|
|
2604
2620
|
|
|
2605
2621
|
// src/commands.ts
|
|
2606
2622
|
var USAGE_DAYS = 30;
|
|
2623
|
+
var FLAGS = {
|
|
2624
|
+
force: { type: "boolean" },
|
|
2625
|
+
all: { type: "boolean" },
|
|
2626
|
+
global: { type: "boolean" },
|
|
2627
|
+
fix: { type: "boolean" },
|
|
2628
|
+
"allow-tracked": { type: "boolean" },
|
|
2629
|
+
json: { type: "boolean" },
|
|
2630
|
+
"dry-run": { type: "boolean" },
|
|
2631
|
+
"allow-git": { type: "boolean" },
|
|
2632
|
+
days: { type: "string" },
|
|
2633
|
+
keep: { type: "string" },
|
|
2634
|
+
to: { type: "string", multiple: true }
|
|
2635
|
+
};
|
|
2607
2636
|
function parseArgs(argv) {
|
|
2608
|
-
const
|
|
2609
|
-
const days =
|
|
2610
|
-
const keepIdx = argv.indexOf("--keep");
|
|
2637
|
+
const { values: v, positionals } = parseFlags({ args: argv, options: FLAGS, allowPositionals: true, strict: true });
|
|
2638
|
+
const days = Number(v.days ?? USAGE_DAYS);
|
|
2611
2639
|
return {
|
|
2612
|
-
positional:
|
|
2613
|
-
force:
|
|
2614
|
-
all:
|
|
2615
|
-
global:
|
|
2616
|
-
fix:
|
|
2617
|
-
allowTracked:
|
|
2618
|
-
json:
|
|
2640
|
+
positional: positionals,
|
|
2641
|
+
force: !!v.force,
|
|
2642
|
+
all: !!v.all,
|
|
2643
|
+
global: !!v.global,
|
|
2644
|
+
fix: !!v.fix,
|
|
2645
|
+
allowTracked: !!v["allow-tracked"],
|
|
2646
|
+
json: !!v.json,
|
|
2619
2647
|
days: Number.isFinite(days) && days > 0 ? days : USAGE_DAYS,
|
|
2620
|
-
dryRun:
|
|
2621
|
-
allowGit:
|
|
2622
|
-
...
|
|
2623
|
-
to:
|
|
2648
|
+
dryRun: !!v["dry-run"],
|
|
2649
|
+
allowGit: !!v["allow-git"],
|
|
2650
|
+
...v.keep ? { keep: v.keep } : {},
|
|
2651
|
+
to: (v.to ?? []).flatMap((t) => t.split(",").filter(Boolean))
|
|
2624
2652
|
};
|
|
2625
2653
|
}
|
|
2626
2654
|
function colorState(state) {
|
|
@@ -2844,8 +2872,7 @@ function projects(args) {
|
|
|
2844
2872
|
}
|
|
2845
2873
|
function add(args) {
|
|
2846
2874
|
if (args.positional.length === 0) {
|
|
2847
|
-
|
|
2848
|
-
process.exit(1);
|
|
2875
|
+
throw new Error("Usage: skilllib add <skill-name>... [--force]");
|
|
2849
2876
|
}
|
|
2850
2877
|
const root = requireProject();
|
|
2851
2878
|
if (args.allowTracked) allowTrackedLinks(root);
|
|
@@ -2854,13 +2881,13 @@ function add(args) {
|
|
|
2854
2881
|
}
|
|
2855
2882
|
function remove(args) {
|
|
2856
2883
|
if (args.positional.length === 0) {
|
|
2857
|
-
|
|
2858
|
-
process.exit(1);
|
|
2884
|
+
throw new Error("Usage: skilllib remove <skill-name>... [--force]");
|
|
2859
2885
|
}
|
|
2860
2886
|
const root = requireProject();
|
|
2861
2887
|
printChanges(root, args.positional.map((name) => removeSkill(root, name, { force: args.force })), args);
|
|
2862
2888
|
}
|
|
2863
2889
|
function sync(args) {
|
|
2890
|
+
if (args.positional.length) throw new Error(`sync takes no skill names: it installs exactly what skilllib.json pins. To add a skill: skilllib add ${args.positional.join(" ")}`);
|
|
2864
2891
|
const roots = args.all ? visibleProjects() : [requireProject()];
|
|
2865
2892
|
const results = roots.map((root) => {
|
|
2866
2893
|
if (args.allowTracked) allowTrackedLinks(root);
|
|
@@ -2913,20 +2940,21 @@ function outdated(args) {
|
|
|
2913
2940
|
function importSkills(args) {
|
|
2914
2941
|
const dirs = args.global ? machineSkills().filter((m) => m.movable && !m.broken).map((m) => m.path) : args.positional.map((p) => resolve7(p));
|
|
2915
2942
|
if (dirs.length === 0) {
|
|
2916
|
-
|
|
2917
|
-
process.exit(1);
|
|
2943
|
+
throw new Error("Usage: skilllib import <skill-dir>... [--force] | skilllib import --global");
|
|
2918
2944
|
}
|
|
2919
2945
|
const results = [];
|
|
2920
2946
|
for (const dir of dirs) {
|
|
2921
2947
|
if (!isSkillDir(dir)) {
|
|
2922
2948
|
warn(`${tildify(dir)}: no SKILL.md, skipping`);
|
|
2923
2949
|
results.push({ path: dir, status: "no SKILL.md" });
|
|
2950
|
+
process.exitCode = 1;
|
|
2924
2951
|
continue;
|
|
2925
2952
|
}
|
|
2926
2953
|
const { name, status: result } = importSkill(dir, { force: args.force });
|
|
2927
2954
|
results.push({ path: dir, name, status: result });
|
|
2928
2955
|
if (result === "exists") {
|
|
2929
2956
|
warn(`${name}: the library already has a different version (use --force to replace it)`);
|
|
2957
|
+
process.exitCode = 1;
|
|
2930
2958
|
continue;
|
|
2931
2959
|
}
|
|
2932
2960
|
success(`${name} ${dim(result === "unchanged" ? "already in library" : result === "added" ? "added to library" : "library updated")}`);
|
|
@@ -2990,12 +3018,11 @@ function folders(args) {
|
|
|
2990
3018
|
info(roots.length ? `Project folders:
|
|
2991
3019
|
${roots.map((r) => ` ${tildify(r)}`).join("\n")}` : "No project folders. Add one: skilllib folders add ~/Projects");
|
|
2992
3020
|
if (hidden.length) info(dim(`Hidden projects: ${hidden.map((h) => tildify(h)).join(", ")} (skilllib unhide <path>)`));
|
|
2993
|
-
if (failed) process.
|
|
3021
|
+
if (failed) process.exitCode = 1;
|
|
2994
3022
|
}
|
|
2995
3023
|
function unhide(args) {
|
|
2996
3024
|
if (args.positional.length === 0) {
|
|
2997
|
-
|
|
2998
|
-
process.exit(1);
|
|
3025
|
+
throw new Error("Usage: skilllib unhide <path>...");
|
|
2999
3026
|
}
|
|
3000
3027
|
let failed = false;
|
|
3001
3028
|
for (const p of args.positional) {
|
|
@@ -3007,18 +3034,16 @@ function unhide(args) {
|
|
|
3007
3034
|
failed = true;
|
|
3008
3035
|
}
|
|
3009
3036
|
}
|
|
3010
|
-
if (failed) process.
|
|
3037
|
+
if (failed) process.exitCode = 1;
|
|
3011
3038
|
}
|
|
3012
3039
|
function newSkill(args) {
|
|
3013
3040
|
const [name, ...rest] = args.positional;
|
|
3014
3041
|
if (!name) {
|
|
3015
|
-
|
|
3016
|
-
process.exit(1);
|
|
3042
|
+
throw new Error("Usage: skilllib new <name> [description]");
|
|
3017
3043
|
}
|
|
3018
3044
|
const r = createSkill(name, rest.join(" "));
|
|
3019
3045
|
if (!r.ok) {
|
|
3020
|
-
|
|
3021
|
-
process.exit(1);
|
|
3046
|
+
throw new Error(`Can't create ${name}: ${r.reason}`);
|
|
3022
3047
|
}
|
|
3023
3048
|
success(`Created ${tildify(join14(r.dir, "SKILL.md"))}`);
|
|
3024
3049
|
}
|
|
@@ -3033,8 +3058,7 @@ function restore(args) {
|
|
|
3033
3058
|
}
|
|
3034
3059
|
const backup = backups.find((b) => b.name === name);
|
|
3035
3060
|
if (!backup) {
|
|
3036
|
-
|
|
3037
|
-
process.exit(1);
|
|
3061
|
+
throw new Error(`No backup named ${name}`);
|
|
3038
3062
|
}
|
|
3039
3063
|
const r = backup.kind === "plugin" ? restorePlugin(backup) : restoreBackup(backup);
|
|
3040
3064
|
if (r.ok) success(`Restored ${name} to ${tildify(r.to)}`);
|
|
@@ -3160,8 +3184,7 @@ function show(args) {
|
|
|
3160
3184
|
const name = args.positional[0];
|
|
3161
3185
|
const dir = name ? join14(libraryDir(), name) : "";
|
|
3162
3186
|
if (!name || !isSkillDir(dir)) {
|
|
3163
|
-
|
|
3164
|
-
process.exit(1);
|
|
3187
|
+
throw new Error(name ? `${name} is not in the library` : "Usage: skilllib show <skill-name>");
|
|
3165
3188
|
}
|
|
3166
3189
|
const { description } = readSkillInfo(dir);
|
|
3167
3190
|
if (args.json) {
|
|
@@ -3187,8 +3210,7 @@ function harnessesCommand(args) {
|
|
|
3187
3210
|
if (args.positional.length) {
|
|
3188
3211
|
const unknown = args.positional.filter((p) => !ids.includes(p));
|
|
3189
3212
|
if (unknown.length) {
|
|
3190
|
-
|
|
3191
|
-
process.exit(1);
|
|
3213
|
+
throw new Error(`Unknown harness: ${unknown.join(", ")}. Choose from: ${ids.join(", ")}`);
|
|
3192
3214
|
}
|
|
3193
3215
|
setHarnesses(args.positional);
|
|
3194
3216
|
success(`Using ${args.positional.join(", ")}`);
|
|
@@ -3225,14 +3247,12 @@ function globalCommand(args) {
|
|
|
3225
3247
|
if (action === "keep" || action === "unkeep") {
|
|
3226
3248
|
const unknown = names.filter((n) => !yours.some((m) => m.name === n));
|
|
3227
3249
|
if (!names.length || unknown.length) {
|
|
3228
|
-
|
|
3229
|
-
process.exit(1);
|
|
3250
|
+
throw new Error(names.length ? `Not one of your global skills: ${unknown.join(", ")}` : `Usage: skilllib global ${action} <name>...`);
|
|
3230
3251
|
}
|
|
3231
3252
|
setKeepGlobal(names, action === "keep");
|
|
3232
3253
|
success(action === "keep" ? `Keeping ${names.join(", ")} global on purpose` : `${names.join(", ")} will show up in cleanup again`);
|
|
3233
3254
|
} else if (action) {
|
|
3234
|
-
|
|
3235
|
-
process.exit(1);
|
|
3255
|
+
throw new Error("Usage: skilllib global [keep|unkeep <name>...] | skilllib global move <name>... --to <repo>...");
|
|
3236
3256
|
}
|
|
3237
3257
|
const kept = keptGlobal();
|
|
3238
3258
|
if (!yours.length) return info("None of your skills load globally.");
|
|
@@ -3285,8 +3305,7 @@ function tidy(args) {
|
|
|
3285
3305
|
const names = args.positional;
|
|
3286
3306
|
const keepFor = (base) => args.keep ? Object.fromEntries(names.map((n) => [n, base ? resolve7(base, args.keep) : expandHome(args.keep)])) : {};
|
|
3287
3307
|
if (args.keep && !names.length) {
|
|
3288
|
-
|
|
3289
|
-
process.exit(1);
|
|
3308
|
+
throw new Error("Usage: skilllib tidy <name>... --keep <folder>");
|
|
3290
3309
|
}
|
|
3291
3310
|
const targets = args.global ? [null] : args.all ? visibleProjects() : [requireProject()];
|
|
3292
3311
|
const problems = [];
|
|
@@ -3306,8 +3325,7 @@ function tidy(args) {
|
|
|
3306
3325
|
}
|
|
3307
3326
|
}
|
|
3308
3327
|
if (problems.length) {
|
|
3309
|
-
|
|
3310
|
-
process.exit(1);
|
|
3328
|
+
throw new Error(problems.join("\n"));
|
|
3311
3329
|
}
|
|
3312
3330
|
const reports = targets.map(
|
|
3313
3331
|
(root) => root === null ? { label: "global folders", report: planGlobalTidy({ keep: keepFor(null) }) } : { label: basename9(root), report: planProjectTidy(root, { keep: keepFor(root) }) }
|
|
@@ -3347,8 +3365,7 @@ function agentSkill(args) {
|
|
|
3347
3365
|
if (action === "install") {
|
|
3348
3366
|
const r = installAgentSkill();
|
|
3349
3367
|
if (!r.ok) {
|
|
3350
|
-
|
|
3351
|
-
process.exit(1);
|
|
3368
|
+
throw new Error(`Can't install the skilllib skill: ${r.reason}`);
|
|
3352
3369
|
}
|
|
3353
3370
|
if (args.json) return json("agent-skill", { state: agentSkillState(), dirs: r.dirs });
|
|
3354
3371
|
success(`Installed in ${r.dirs.map((d) => tildify(d)).join(" and ")}`);
|
|
@@ -3380,6 +3397,8 @@ export {
|
|
|
3380
3397
|
projectHere,
|
|
3381
3398
|
readManifest,
|
|
3382
3399
|
setJsonMode,
|
|
3400
|
+
isJsonMode,
|
|
3401
|
+
json,
|
|
3383
3402
|
error,
|
|
3384
3403
|
tildify,
|
|
3385
3404
|
readConfig,
|
|
@@ -3406,7 +3425,7 @@ export {
|
|
|
3406
3425
|
relinkDependency,
|
|
3407
3426
|
addSkill,
|
|
3408
3427
|
removeSkill,
|
|
3409
|
-
|
|
3428
|
+
syncSkill,
|
|
3410
3429
|
updateProject,
|
|
3411
3430
|
importSkill,
|
|
3412
3431
|
removeUntracked,
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
globalCommand,
|
|
10
10
|
harnessesCommand,
|
|
11
11
|
importSkills,
|
|
12
|
+
isJsonMode,
|
|
13
|
+
json,
|
|
12
14
|
link,
|
|
13
15
|
list,
|
|
14
16
|
newSkill,
|
|
@@ -28,7 +30,7 @@ import {
|
|
|
28
30
|
unhide,
|
|
29
31
|
update,
|
|
30
32
|
usage
|
|
31
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-OFWHWIER.js";
|
|
32
34
|
|
|
33
35
|
// src/index.ts
|
|
34
36
|
import { readFileSync } from "fs";
|
|
@@ -91,15 +93,15 @@ function packageVersion() {
|
|
|
91
93
|
}
|
|
92
94
|
async function main() {
|
|
93
95
|
const [command, ...rest] = process.argv.slice(2);
|
|
96
|
+
setJsonMode(rest.includes("--json"));
|
|
94
97
|
const args = parseArgs(rest);
|
|
95
|
-
setJsonMode(args.json);
|
|
96
98
|
if (!["--version", "-v", "version", "help", "--help", "-h"].includes(command ?? "")) refreshAgentSkill();
|
|
97
99
|
switch (command) {
|
|
98
100
|
case void 0:
|
|
99
|
-
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-
|
|
101
|
+
return process.stdin.isTTY && process.stdout.isTTY ? (await import("./tui-NGTOOVAQ.js")).tui() : overview(args);
|
|
100
102
|
case "ui":
|
|
101
103
|
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("`skilllib ui` needs an interactive terminal. Agents and scripts: use `skilllib status --json`.");
|
|
102
|
-
return (await import("./tui-
|
|
104
|
+
return (await import("./tui-NGTOOVAQ.js")).tui();
|
|
103
105
|
case "status":
|
|
104
106
|
return status(args);
|
|
105
107
|
case "add":
|
|
@@ -108,7 +110,6 @@ async function main() {
|
|
|
108
110
|
case "rm":
|
|
109
111
|
return remove(args);
|
|
110
112
|
case "sync":
|
|
111
|
-
case "install":
|
|
112
113
|
return sync(args);
|
|
113
114
|
case "update":
|
|
114
115
|
case "upgrade":
|
|
@@ -165,6 +166,7 @@ async function main() {
|
|
|
165
166
|
console.log(HELP);
|
|
166
167
|
return;
|
|
167
168
|
default:
|
|
169
|
+
if (isJsonMode()) throw new Error(`Unknown command: ${command} (skilllib help lists them)`);
|
|
168
170
|
console.error(`Unknown command: ${command}
|
|
169
171
|
`);
|
|
170
172
|
console.log(HELP);
|
|
@@ -172,7 +174,9 @@ async function main() {
|
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
main().catch((err) => {
|
|
175
|
-
|
|
177
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
178
|
+
if (isJsonMode()) json("error", { error: message });
|
|
179
|
+
error(message);
|
|
176
180
|
if (process.env.DEBUG && err instanceof Error) console.error(err.stack);
|
|
177
181
|
process.exit(1);
|
|
178
182
|
});
|
package/dist/json.d.cts
CHANGED
|
@@ -178,6 +178,10 @@ type DoctorJson = {
|
|
|
178
178
|
message: string;
|
|
179
179
|
};
|
|
180
180
|
}[];
|
|
181
|
+
/** Any command with --json that fails: it exits 1, and this is the one line on stdout. */
|
|
182
|
+
type ErrorJson = {
|
|
183
|
+
error: string;
|
|
184
|
+
};
|
|
181
185
|
/** `skilllib apply <issue-id> [choice] --json` */
|
|
182
186
|
type ApplyJson = {
|
|
183
187
|
id: string;
|
|
@@ -219,4 +223,4 @@ type AgentSkillJson = {
|
|
|
219
223
|
removed?: string[];
|
|
220
224
|
};
|
|
221
225
|
|
|
222
|
-
export type { AgentSkillJson, AgentSkillState, AllChangesJson, ApplyJson, ChangeJson, ChangesJson, DoctorJson, GitState, GlobalMoveJson, HarnessId, ImportJson, ListJson, OutdatedJson, ProjectSource, ProjectsJson, ShowJson, SkillState, SourceKind, StatusGlobalGroup, StatusIssue, StatusJson, StatusSkillGroup, UsageJson };
|
|
226
|
+
export type { AgentSkillJson, AgentSkillState, AllChangesJson, ApplyJson, ChangeJson, ChangesJson, DoctorJson, ErrorJson, GitState, GlobalMoveJson, HarnessId, ImportJson, ListJson, OutdatedJson, ProjectSource, ProjectsJson, ShowJson, SkillState, SourceKind, StatusGlobalGroup, StatusIssue, StatusJson, StatusSkillGroup, UsageJson };
|
package/dist/json.d.ts
CHANGED
|
@@ -178,6 +178,10 @@ type DoctorJson = {
|
|
|
178
178
|
message: string;
|
|
179
179
|
};
|
|
180
180
|
}[];
|
|
181
|
+
/** Any command with --json that fails: it exits 1, and this is the one line on stdout. */
|
|
182
|
+
type ErrorJson = {
|
|
183
|
+
error: string;
|
|
184
|
+
};
|
|
181
185
|
/** `skilllib apply <issue-id> [choice] --json` */
|
|
182
186
|
type ApplyJson = {
|
|
183
187
|
id: string;
|
|
@@ -219,4 +223,4 @@ type AgentSkillJson = {
|
|
|
219
223
|
removed?: string[];
|
|
220
224
|
};
|
|
221
225
|
|
|
222
|
-
export type { AgentSkillJson, AgentSkillState, AllChangesJson, ApplyJson, ChangeJson, ChangesJson, DoctorJson, GitState, GlobalMoveJson, HarnessId, ImportJson, ListJson, OutdatedJson, ProjectSource, ProjectsJson, ShowJson, SkillState, SourceKind, StatusGlobalGroup, StatusIssue, StatusJson, StatusSkillGroup, UsageJson };
|
|
226
|
+
export type { AgentSkillJson, AgentSkillState, AllChangesJson, ApplyJson, ChangeJson, ChangesJson, DoctorJson, ErrorJson, GitState, GlobalMoveJson, HarnessId, ImportJson, ListJson, OutdatedJson, ProjectSource, ProjectsJson, ShowJson, SkillState, SourceKind, StatusGlobalGroup, StatusIssue, StatusJson, StatusSkillGroup, UsageJson };
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
setPluginOn,
|
|
64
64
|
skilllibHome,
|
|
65
65
|
skillsLoadedIn,
|
|
66
|
-
|
|
66
|
+
syncSkill,
|
|
67
67
|
tildify,
|
|
68
68
|
turnOffIn,
|
|
69
69
|
uninstallPlugin,
|
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
versionHistory,
|
|
74
74
|
visibilityOf,
|
|
75
75
|
visibleProjects
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-OFWHWIER.js";
|
|
77
77
|
|
|
78
78
|
// src/tui/index.tsx
|
|
79
79
|
import { render } from "ink";
|
|
@@ -1671,7 +1671,7 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1671
1671
|
pickRepos: (w, repos) => w.ops.moveGlobal(mine, repos, label)
|
|
1672
1672
|
}),
|
|
1673
1673
|
...mine.some((m) => !m.kept) ? [fixOption({ label: `Keep all ${mine.length} global on purpose`, preview: `skilllib stops warning about these ${n(mine.length)}.`, run: (w) => (mine.forEach((m) => w.ops.keepGlobal(m.name, true)), `${n(mine.length)} marked as global on purpose`) })] : [],
|
|
1674
|
-
fixOption({ label: `Delete all ${mine.length}`, preview: `Move these ${n(mine.length)} to Settings \u203A Backups.`, run: (w) => (mine.
|
|
1674
|
+
fixOption({ label: `Delete all ${mine.length}`, preview: `Move these ${n(mine.length)} to Settings \u203A Backups.`, run: (w) => joined(mine.map((m) => w.ops.deleteGlobal(m))) })
|
|
1675
1675
|
] : [],
|
|
1676
1676
|
...libs.length ? [
|
|
1677
1677
|
fixOption({
|
|
@@ -1683,7 +1683,7 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
1683
1683
|
})
|
|
1684
1684
|
] : [],
|
|
1685
1685
|
...libs.length ? [fixOption(deleteLibraryFix(world, libs.map((l) => l.name)))] : [],
|
|
1686
|
-
...locals.length ? [fixOption({ label: `Remove all ${locals.length} from this repo`, preview: `Remove these ${n(locals.length)} from ${open}; your library keeps the tracked ones.`, run: (w) => (locals.
|
|
1686
|
+
...locals.length ? [fixOption({ label: `Remove all ${locals.length} from this repo`, preview: `Remove these ${n(locals.length)} from ${open}; your library keeps the tracked ones.`, run: (w) => joined(locals.map((s) => w.ops.remove(open, s.name))) })] : [],
|
|
1687
1687
|
...[...new Set(machines.filter((m) => m.source === "plugin").map((m) => m.where))].map((id) => ({ label: `Open ${id.split("@")[0]} in Plugins`, action: () => openInPlugins(id) })),
|
|
1688
1688
|
{ label: "Review prompt", action: () => ui.copy(reviewPrompt(world, names, `Review these related skills (${label}).`), `a review prompt for ${label}`) }
|
|
1689
1689
|
];
|
|
@@ -2013,7 +2013,8 @@ function App({ initial, reload, loadUsage: loadUsage2 }) {
|
|
|
2013
2013
|
if (row.create && !/^[a-z0-9]/.test(m.query)) return setToast("Type the new skill's name first");
|
|
2014
2014
|
setModal(null);
|
|
2015
2015
|
if (!row.create) return apply(row.run);
|
|
2016
|
-
row.run(world);
|
|
2016
|
+
const created = row.run(world);
|
|
2017
|
+
if (isFailure(created)) return setToast(`\u2717 ${said2(created)}`);
|
|
2017
2018
|
return edit(world.ops.libraryFile(m.query), row.afterEdit);
|
|
2018
2019
|
}
|
|
2019
2020
|
if (isNameChar(input, key)) return setModal({ ...m, query: m.query + input.toLowerCase(), cursor: 0 });
|
|
@@ -2636,7 +2637,7 @@ function realOps(roots, backups) {
|
|
|
2636
2637
|
return orForce(removeSkill(rootOf(repo), name), repo, "Remove it anyway", () => removeSkill(rootOf(repo), name, { force: true }));
|
|
2637
2638
|
},
|
|
2638
2639
|
restore: (repo, name) => {
|
|
2639
|
-
const c =
|
|
2640
|
+
const [c] = syncSkill(rootOf(repo), name);
|
|
2640
2641
|
return c ? result(c, repo) : `${name} is already in place`;
|
|
2641
2642
|
},
|
|
2642
2643
|
update: (repo, name) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilllib",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.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
|
@@ -8,7 +8,7 @@ allowed-tools: Bash(skilllib:*)
|
|
|
8
8
|
|
|
9
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`.
|
|
10
10
|
|
|
11
|
-
Always pass `--json` (one line of JSON on stdout; messages go to stderr). 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` and `global keep` print messages only. Unknown flags are errors. Never run bare `skilllib` or `skilllib ui`: that's an interactive app.
|
|
12
12
|
|
|
13
13
|
## Reading
|
|
14
14
|
|
|
@@ -25,12 +25,12 @@ 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>...` · `update [name...]` · `sync` (
|
|
28
|
+
`add <name>...` · `remove <name>...` · `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, 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
|
- `blocked`: git tracks that folder. Re-run with `--allow-tracked` only with their OK.
|
|
32
32
|
- `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.
|
|
33
|
-
- Changes apply from the agent's next session. Remind the user to commit `skilllib.json` and `.
|
|
33
|
+
- Changes apply from the agent's next session. Remind the user to commit `skilllib.json` and the folders in `dirs`.
|
|
34
34
|
|
|
35
35
|
**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:
|
|
36
36
|
|