skilllib 1.12.1 → 1.13.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.
- package/README.md +12 -7
- package/dist/{chunk-LORSEDNW.js → chunk-C6TSR7NT.js} +531 -88
- package/dist/index.js +18 -6
- package/dist/json.d.cts +665 -2
- package/dist/json.d.ts +665 -2
- package/dist/{tui-3QD6RQGL.js → tui-3MBVYSVM.js} +44 -17
- package/package.json +6 -2
- package/skills/skilllib/SKILL.md +4 -4
|
@@ -139,6 +139,77 @@ import { join as join6, resolve as resolve4 } from "path";
|
|
|
139
139
|
// src/harnesses.ts
|
|
140
140
|
import { existsSync } from "fs";
|
|
141
141
|
import { delimiter, join as join2 } from "path";
|
|
142
|
+
var EXTRAS = [
|
|
143
|
+
{ id: "github-copilot", name: "GitHub Copilot", short: "Cp", icon: "\u25D0", color: "#E8E8E8", project: [".agents/skills", ".claude/skills", ".github/skills"], global: [".agents/skills", ".claude/skills", ".copilot/skills"], detect: [".copilot"], bin: "copilot" },
|
|
144
|
+
{ id: "gemini-cli", name: "Gemini CLI", short: "Ge", icon: "\u2726", color: "#8AB4F8", project: [".agents/skills", ".gemini/skills"], global: [".agents/skills", ".gemini/skills"], detect: [], bin: "gemini" },
|
|
145
|
+
{ id: "opencode", name: "OpenCode", short: "Oc", icon: "\u2318", color: "#A78BFA", project: [".agents/skills", ".claude/skills", ".opencode/skills"], global: [".agents/skills", ".claude/skills", ".config/opencode/skills"], detect: [".config/opencode", ".opencode"], bin: "opencode" },
|
|
146
|
+
{ id: "antigravity", name: "Antigravity", short: "Ag", icon: "\u25B2", color: "#34A853", project: [".agents/skills", ".agent/skills"], global: [".gemini/config/skills", ".gemini/antigravity/skills"], detect: [".gemini/antigravity"], bin: "agy", app: "Antigravity" },
|
|
147
|
+
{ id: "antigravity-ide", name: "Antigravity IDE", short: "Ai", icon: "\u25B2", color: "#34A853", project: [".agents/skills", ".agent/skills"], global: [".gemini/antigravity-ide/skills"], detect: [".gemini/antigravity-ide"], app: "Antigravity IDE" },
|
|
148
|
+
{ id: "antigravity-cli", name: "Antigravity CLI", short: "Ac", icon: "\u25B2", color: "#34A853", project: [".agents/skills", ".agent/skills"], global: [".gemini/antigravity-cli/skills"], detect: [".gemini/antigravity-cli"], bin: "agy" },
|
|
149
|
+
{ id: "windsurf", name: "Windsurf", short: "Ws", icon: "\u224B", color: "#0EA5E9", project: [".windsurf/skills"], global: [".codeium/windsurf/skills"], detect: [".codeium/windsurf"], bin: "windsurf", app: "Windsurf" },
|
|
150
|
+
{ id: "devin", name: "Devin", short: "Dv", icon: "\u0394", color: "#7C6AF7", project: [".agents/skills", ".devin/skills", ".windsurf/skills"], global: [".agents/skills", ".config/devin/skills"], detect: [".config/devin"], bin: "devin" },
|
|
151
|
+
{ id: "cline", name: "Cline", short: "Cl", icon: "\u25A3", color: "#22C55E", project: [".agents/skills"], global: [".agents/skills"], detect: [".cline"], bin: "cline" },
|
|
152
|
+
{ id: "amp", name: "Amp", short: "Am", icon: "\u26A1", color: "#F5C16C", project: [".agents/skills"], global: [".config/agents/skills"], detect: [".config/amp"], bin: "amp" },
|
|
153
|
+
{ id: "droid", name: "Factory Droid", short: "Dr", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".agents/skills", ".factory/skills"], detect: [".factory"], bin: "droid" },
|
|
154
|
+
{ id: "openclaw", name: "OpenClaw", short: "Ow", icon: "\xB7", color: "#8A93A6", project: ["skills"], global: [".openclaw/skills"], detect: [".openclaw", ".clawdbot", ".moltbot"], bin: "openclaw" },
|
|
155
|
+
{ id: "continue", name: "Continue", short: "Cn", icon: "\xB7", color: "#8A93A6", project: [".continue/skills"], global: [".continue/skills"], detect: [".continue"] },
|
|
156
|
+
{ id: "augment", name: "Augment", short: "Au", icon: "\xB7", color: "#8A93A6", project: [".augment/skills"], global: [".augment/skills"], detect: [".augment"] },
|
|
157
|
+
{ id: "junie", name: "Junie", short: "Ju", icon: "\xB7", color: "#8A93A6", project: [".junie/skills"], global: [".junie/skills"], detect: [".junie"], bin: "junie" },
|
|
158
|
+
{ id: "roo", name: "Roo Code", short: "Ro", icon: "\xB7", color: "#8A93A6", project: [".roo/skills"], global: [".roo/skills"], detect: [".roo"] },
|
|
159
|
+
{ id: "goose", name: "Goose", short: "Gs", icon: "\xB7", color: "#8A93A6", project: [".goose/skills"], global: [".config/goose/skills"], detect: [".config/goose"], bin: "goose" },
|
|
160
|
+
{ id: "openhands", name: "OpenHands", short: "Oh", icon: "\xB7", color: "#8A93A6", project: [".openhands/skills"], global: [".openhands/skills"], detect: [".openhands"] },
|
|
161
|
+
{ id: "crush", name: "Crush", short: "Cr", icon: "\xB7", color: "#8A93A6", project: [".crush/skills"], global: [".config/crush/skills"], detect: [".config/crush"], bin: "crush" },
|
|
162
|
+
{ id: "pi", name: "Pi", short: "Pi", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".agents/skills", ".pi/agent/skills"], detect: [".pi/agent"], bin: "pi" },
|
|
163
|
+
{ id: "kimi-code-cli", name: "Kimi Code CLI", short: "Ki", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".agents/skills"], detect: [".kimi"], bin: "kimi" },
|
|
164
|
+
{ id: "kilo", name: "Kilo Code", short: "Kl", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".kilo/skills", ".kilocode/skills"], detect: [".kilo", ".kilocode"] },
|
|
165
|
+
{ id: "kiro-cli", name: "Kiro CLI", short: "Kr", icon: "\xB7", color: "#8A93A6", project: [".kiro/skills"], global: [".kiro/skills"], detect: [".kiro"], bin: "kiro" },
|
|
166
|
+
{ id: "qwen-code", name: "Qwen Code", short: "Qw", icon: "\xB7", color: "#8A93A6", project: [".qwen/skills"], global: [".qwen/skills"], detect: [".qwen"], bin: "qwen" },
|
|
167
|
+
{ id: "warp", name: "Warp", short: "Wp", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".agents/skills"], detect: [".warp"], app: "Warp" },
|
|
168
|
+
{ id: "replit", name: "Replit", short: "Rp", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".config/agents/skills"], detect: [] },
|
|
169
|
+
{ id: "trae", name: "Trae", short: "Tr", icon: "\xB7", color: "#8A93A6", project: [".trae/skills"], global: [".trae/skills"], detect: [".trae"], app: "Trae" },
|
|
170
|
+
{ id: "mux", name: "Mux", short: "Mx", icon: "\xB7", color: "#8A93A6", project: [".mux/skills"], global: [".mux/skills"], detect: [".mux"] },
|
|
171
|
+
{ id: "mistral-vibe", name: "Mistral Vibe", short: "Mv", icon: "\xB7", color: "#8A93A6", project: [".vibe/skills"], global: [".vibe/skills"], detect: [".vibe"], bin: "vibe" },
|
|
172
|
+
{ id: "qoder", name: "Qoder", short: "Qd", icon: "\xB7", color: "#8A93A6", project: [".qoder/skills"], global: [".qoder/skills"], detect: [".qoder"] },
|
|
173
|
+
{ id: "zencoder", name: "Zencoder", short: "Ze", icon: "\xB7", color: "#8A93A6", project: [".zencoder/skills"], global: [".zencoder/skills"], detect: [".zencoder"] },
|
|
174
|
+
{ id: "neovate", name: "Neovate", short: "Nv", icon: "\xB7", color: "#8A93A6", project: [".neovate/skills"], global: [".neovate/skills"], detect: [".neovate"] },
|
|
175
|
+
{ id: "adal", name: "AdaL", short: "Ad", icon: "\xB7", color: "#8A93A6", project: [".adal/skills"], global: [".adal/skills"], detect: [".adal"] },
|
|
176
|
+
{ id: "bob", name: "IBM Bob", short: "Bb", icon: "\xB7", color: "#8A93A6", project: [".bob/skills"], global: [".bob/skills"], detect: [".bob"] },
|
|
177
|
+
{ id: "aider-desk", name: "AiderDesk", short: "Adk", icon: "\xB7", color: "#8A93A6", project: [".aider-desk/skills"], global: [".aider-desk/skills"], detect: [".aider-desk"] },
|
|
178
|
+
{ id: "codebuddy", name: "CodeBuddy", short: "Cb", icon: "\xB7", color: "#8A93A6", project: [".codebuddy/skills"], global: [".codebuddy/skills"], detect: [".codebuddy"] },
|
|
179
|
+
{ id: "command-code", name: "Command Code", short: "Cm", icon: "\xB7", color: "#8A93A6", project: [".commandcode/skills"], global: [".commandcode/skills"], detect: [".commandcode"] },
|
|
180
|
+
{ id: "cortex", name: "Cortex Code", short: "Ct", icon: "\xB7", color: "#8A93A6", project: [".cortex/skills"], global: [".snowflake/cortex/skills"], detect: [".snowflake/cortex"] },
|
|
181
|
+
{ id: "hermes-agent", name: "Hermes Agent", short: "He", icon: "\xB7", color: "#8A93A6", project: [".hermes/skills"], global: [".hermes/skills"], detect: [".hermes"] },
|
|
182
|
+
{ id: "iflow-cli", name: "iFlow CLI", short: "If", icon: "\xB7", color: "#8A93A6", project: [".iflow/skills"], global: [".iflow/skills"], detect: [".iflow"] },
|
|
183
|
+
{ id: "kode", name: "Kode", short: "Kd", icon: "\xB7", color: "#8A93A6", project: [".kode/skills"], global: [".kode/skills"], detect: [".kode"] },
|
|
184
|
+
{ id: "mcpjam", name: "MCPJam", short: "Mj", icon: "\xB7", color: "#8A93A6", project: [".mcpjam/skills"], global: [".mcpjam/skills"], detect: [".mcpjam"] },
|
|
185
|
+
{ id: "minimax-code", name: "MiniMax Code", short: "Mm", icon: "\xB7", color: "#8A93A6", project: [".minimax/skills"], global: [".minimax/skills"], detect: [".minimax"] },
|
|
186
|
+
{ id: "pochi", name: "Pochi", short: "Po", icon: "\xB7", color: "#8A93A6", project: [".pochi/skills"], global: [".pochi/skills"], detect: [".pochi"] },
|
|
187
|
+
{ id: "rovodev", name: "Rovo Dev", short: "Rv", icon: "\xB7", color: "#8A93A6", project: [".rovodev/skills"], global: [".rovodev/skills"], detect: [".rovodev"] },
|
|
188
|
+
{ id: "tabnine-cli", name: "Tabnine CLI", short: "Tn", icon: "\xB7", color: "#8A93A6", project: [".tabnine/agent/skills"], global: [".tabnine/agent/skills"], detect: [".tabnine"] },
|
|
189
|
+
{ id: "zcode", name: "ZCode", short: "Zc", icon: "\xB7", color: "#8A93A6", project: [".zcode/skills"], global: [".zcode/skills"], detect: [".zcode"], app: "ZCode" },
|
|
190
|
+
{ id: "autohand-code", name: "Autohand Code", short: "Ah", icon: "\xB7", color: "#8A93A6", project: [".autohand/skills"], global: [".autohand/skills"], detect: [".autohand"] },
|
|
191
|
+
{ id: "codearts-agent", name: "CodeArts Agent", short: "Ca", icon: "\xB7", color: "#8A93A6", project: [".codeartsdoer/skills"], global: [".codeartsdoer/skills"], detect: [".codeartsdoer"] },
|
|
192
|
+
{ id: "codemaker", name: "CodeMaker", short: "Mk", icon: "\xB7", color: "#8A93A6", project: [".codemaker/skills"], global: [".codemaker/skills"], detect: [".codemaker"] },
|
|
193
|
+
{ id: "codestudio", name: "CodeStudio", short: "Cs", icon: "\xB7", color: "#8A93A6", project: [".codestudio/skills"], global: [".codestudio/skills"], detect: [".codestudio"] },
|
|
194
|
+
{ id: "deepagents", name: "Deep Agents", short: "Da", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".deepagents/agent/skills"], detect: [".deepagents"] },
|
|
195
|
+
{ id: "dexto", name: "Dexto", short: "Dx", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".agents/skills"], detect: [".dexto"] },
|
|
196
|
+
{ id: "firebender", name: "Firebender", short: "Fb", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".firebender/skills"], detect: [".firebender"] },
|
|
197
|
+
{ id: "forgecode", name: "ForgeCode", short: "Fc", icon: "\xB7", color: "#8A93A6", project: [".forge/skills"], global: [".forge/skills"], detect: [".forge"] },
|
|
198
|
+
{ id: "fx", name: "fx", short: "Fx", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".agents/skills"], detect: [".fx"] },
|
|
199
|
+
{ id: "inference-sh", name: "inference.sh", short: "In", icon: "\xB7", color: "#8A93A6", project: [".inferencesh/skills"], global: [".inferencesh/skills"], detect: [".inferencesh"] },
|
|
200
|
+
{ id: "jazz", name: "Jazz", short: "Jz", icon: "\xB7", color: "#8A93A6", project: [".jazz/skills"], global: [".jazz/skills"], detect: [".jazz"] },
|
|
201
|
+
{ id: "kimchi", name: "Kimchi", short: "Kc", icon: "\xB7", color: "#8A93A6", project: [".kimchi/skills"], global: [".config/kimchi/harness/skills"], detect: [".config/kimchi"] },
|
|
202
|
+
{ id: "lingma", name: "Lingma", short: "Lg", icon: "\xB7", color: "#8A93A6", project: [".lingma/skills"], global: [".lingma/skills"], detect: [".lingma"] },
|
|
203
|
+
{ id: "loaf", name: "Loaf", short: "Lf", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".agents/skills"], detect: [".loaf"] },
|
|
204
|
+
{ id: "moxby", name: "Moxby", short: "Mb", icon: "\xB7", color: "#8A93A6", project: [".moxby/skills"], global: [".moxby/skills"], detect: [".moxby"] },
|
|
205
|
+
{ id: "ona", name: "Ona", short: "On", icon: "\xB7", color: "#8A93A6", project: [".ona/skills"], global: [".ona/skills"], detect: [".ona"] },
|
|
206
|
+
{ id: "posit-assistant", name: "Posit Assistant", short: "Pa", icon: "\xB7", color: "#8A93A6", project: [".posit/assistant/skills"], global: [".posit/assistant/skills"], detect: [".posit"] },
|
|
207
|
+
{ id: "reasonix", name: "Reasonix", short: "Rx", icon: "\xB7", color: "#8A93A6", project: [".reasonix/skills"], global: [".reasonix/skills"], detect: [".reasonix"] },
|
|
208
|
+
{ id: "sarvam-code", name: "Sarvam Code", short: "Sv", icon: "\xB7", color: "#8A93A6", project: [".agents/skills"], global: [".agents/skills"], detect: [".sarvam"] },
|
|
209
|
+
{ id: "terramind", name: "Terramind", short: "Tm", icon: "\xB7", color: "#8A93A6", project: [".terramind/skills"], global: [".terramind/skills"], detect: [".terramind"] },
|
|
210
|
+
{ id: "tinycloud", name: "TinyCloud", short: "Ty", icon: "\xB7", color: "#8A93A6", project: [".tinycloud/skills"], global: [".tinycloud/skills"], detect: [".tinycloud"] }
|
|
211
|
+
];
|
|
212
|
+
var FEATURED_HARNESSES = ["claude-code", "cursor", "codex", "zed", "grok"];
|
|
142
213
|
var home = userHome;
|
|
143
214
|
var HARNESSES = [
|
|
144
215
|
{
|
|
@@ -196,7 +267,19 @@ var HARNESSES = [
|
|
|
196
267
|
// The first folder is where global links for Grok go, so those links stay out of ~/.grok/skills.
|
|
197
268
|
globalDirs: () => [join2(home(), ".agents", "skills"), join2(grokDir(), "skills"), join2(home(), ".claude", "skills"), join2(home(), ".cursor", "skills")],
|
|
198
269
|
installed: () => existsSync(grokDir()) || onPath("grok") || hasApp("Grok")
|
|
199
|
-
}
|
|
270
|
+
},
|
|
271
|
+
...EXTRAS.map(
|
|
272
|
+
(e) => ({
|
|
273
|
+
id: e.id,
|
|
274
|
+
name: e.name,
|
|
275
|
+
short: e.short,
|
|
276
|
+
icon: e.icon,
|
|
277
|
+
color: e.color,
|
|
278
|
+
projectDirs: [...e.project],
|
|
279
|
+
globalDirs: () => e.global.map((g) => join2(home(), g)),
|
|
280
|
+
installed: () => e.detect.some((d) => existsSync(join2(home(), d))) || ("bin" in e && e.bin ? onPath(e.bin) : false) || ("app" in e && e.app ? hasApp(e.app) : false)
|
|
281
|
+
})
|
|
282
|
+
)
|
|
200
283
|
];
|
|
201
284
|
function onPath(bin) {
|
|
202
285
|
const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
|
|
@@ -215,20 +298,59 @@ function preference(dir) {
|
|
|
215
298
|
const i = PREFERRED_DIRS.indexOf(dir);
|
|
216
299
|
return i === -1 ? PREFERRED_DIRS.length : i;
|
|
217
300
|
}
|
|
218
|
-
function
|
|
219
|
-
return
|
|
301
|
+
function projectDirsInUse(enabled) {
|
|
302
|
+
return ALL_PROJECT_DIRS.filter((d) => d !== "skills" || enabled.includes("openclaw"));
|
|
303
|
+
}
|
|
304
|
+
function cover(enabled, universe, reads, pref) {
|
|
305
|
+
const left = new Set(enabled);
|
|
306
|
+
const chosen = [];
|
|
307
|
+
while (left.size) {
|
|
308
|
+
let best = null;
|
|
309
|
+
let bestCount = 0;
|
|
310
|
+
let bestPref = Infinity;
|
|
311
|
+
for (const dir of universe) {
|
|
312
|
+
if (chosen.includes(dir)) continue;
|
|
313
|
+
const count = [...left].filter((id) => reads(id).includes(dir)).length;
|
|
314
|
+
if (count === 0) continue;
|
|
315
|
+
const p = pref(dir);
|
|
316
|
+
if (count > bestCount || count === bestCount && (p < bestPref || p === bestPref && (best === null || dir < best))) {
|
|
317
|
+
best = dir;
|
|
318
|
+
bestCount = count;
|
|
319
|
+
bestPref = p;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!best) break;
|
|
323
|
+
chosen.push(best);
|
|
324
|
+
for (const id of [...left]) if (reads(id).includes(best)) left.delete(id);
|
|
325
|
+
}
|
|
326
|
+
return chosen;
|
|
220
327
|
}
|
|
221
328
|
function installDirs(enabled, keep) {
|
|
222
329
|
if (enabled.length === 0) return [keep ?? PROJECT_SKILLS_DIR];
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
330
|
+
const reads = (id) => harness(id).projectDirs;
|
|
331
|
+
const rest = keep ? enabled.filter((id) => !reads(id).includes(keep)) : enabled;
|
|
332
|
+
const chosen = [.../* @__PURE__ */ new Set([...keep ? [keep] : [], ...cover(rest, ALL_PROJECT_DIRS, reads, preference)])];
|
|
333
|
+
const primary = keep ?? [...chosen].sort((a, b) => preference(a) - preference(b) || a.localeCompare(b))[0];
|
|
334
|
+
return [primary, ...chosen.filter((d) => d !== primary).sort((a, b) => preference(a) - preference(b) || a.localeCompare(b))];
|
|
335
|
+
}
|
|
336
|
+
function globalPreference(dir) {
|
|
337
|
+
const agents = realPath(join2(userHome(), ".agents", "skills"));
|
|
338
|
+
const claude2 = realPath(join2(claudeDir(), "skills"));
|
|
339
|
+
const real = realPath(dir);
|
|
340
|
+
if (real === agents) return 0;
|
|
341
|
+
if (real === claude2) return 1;
|
|
342
|
+
return 2;
|
|
343
|
+
}
|
|
344
|
+
function globalInstallDirs(enabled) {
|
|
345
|
+
if (enabled.length === 0) return [];
|
|
346
|
+
const reads = (id) => harness(id).globalDirs();
|
|
347
|
+
const chosen = cover(enabled, [...new Set(enabled.flatMap(reads))], reads, globalPreference);
|
|
348
|
+
const claude2 = join2(claudeDir(), "skills");
|
|
349
|
+
const agents = join2(userHome(), ".agents", "skills");
|
|
350
|
+
const primary = chosen.find((d) => realPath(d) === realPath(claude2)) ?? chosen.find((d) => realPath(d) === realPath(agents)) ?? chosen[0];
|
|
351
|
+
if (!primary) return [];
|
|
352
|
+
const dirs = [primary, ...chosen.filter((d) => realPath(d) !== realPath(primary))];
|
|
353
|
+
return dirs.filter((d, i) => dirs.findIndex((o) => realPath(o) === realPath(d)) === i);
|
|
232
354
|
}
|
|
233
355
|
|
|
234
356
|
// src/project.ts
|
|
@@ -478,7 +600,7 @@ function repoOf(root) {
|
|
|
478
600
|
const seen = changedAt(config);
|
|
479
601
|
let repo = null;
|
|
480
602
|
if (out2[0] === "") {
|
|
481
|
-
const urls = (gitIn(root, ["config", "--get-regexp", "^remote\\..*\\.url$"]) ?? "").split("\n").filter(Boolean).map((l) => l.
|
|
603
|
+
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
604
|
const url = (urls.find(([k]) => k === "remote.origin.url") ?? urls[0])?.[1];
|
|
483
605
|
const remote = url ? normalizeRemote(url) : void 0;
|
|
484
606
|
repo = { file: pinsFileFor(root, remote, folder), folder, common, ...remote ? { remote } : {}, root };
|
|
@@ -639,7 +761,7 @@ function findProjects(dir, depth = 4) {
|
|
|
639
761
|
if (depth < 0 || !existsSync5(dir)) return [];
|
|
640
762
|
if (isProjectCandidate(dir)) {
|
|
641
763
|
const isRepo = existsSync5(join6(dir, ".git"));
|
|
642
|
-
const hasSkills =
|
|
764
|
+
const hasSkills = projectDirsInUse(enabledHarnesses()).some((d) => skillDirsIn(join6(dir, d)).length > 0) || existsSync5(join6(dir, MANIFEST_FILE));
|
|
643
765
|
if (isRepo || hasSkills) return [dir];
|
|
644
766
|
}
|
|
645
767
|
let entries = [];
|
|
@@ -1112,7 +1234,7 @@ function blindSaid(name, blind, label = basename4) {
|
|
|
1112
1234
|
return `${who} only ${blind.length === 1 ? "sees" : "see"} the global copy: ${fixes.join("; ")}, or keep ${name} global (skilllib global keep)`;
|
|
1113
1235
|
}
|
|
1114
1236
|
function dependencyDirs(root, name, dep) {
|
|
1115
|
-
const untracked =
|
|
1237
|
+
const untracked = projectDirsInUse(enabledHarnesses()).find((d) => d !== GROK_SKILLS_DIR && existsSync7(join8(root, d, name, "SKILL.md")) && !isLink(join8(root, d, name)));
|
|
1116
1238
|
const primary = dep ? dep.dir ?? PROJECT_SKILLS_DIR : untracked ?? installDirs(enabledHarnesses())[0];
|
|
1117
1239
|
const wanted = installDirs(enabledHarnesses(), primary);
|
|
1118
1240
|
const real = (d) => realpathOrNull(join8(root, d));
|
|
@@ -1177,7 +1299,7 @@ function projectStatus(root) {
|
|
|
1177
1299
|
const lock = projectSkillsLock(root);
|
|
1178
1300
|
const unmanaged = [];
|
|
1179
1301
|
const byName = /* @__PURE__ */ new Map();
|
|
1180
|
-
for (const dir of
|
|
1302
|
+
for (const dir of projectDirsInUse(enabled).filter((d) => writableDir(root, d))) {
|
|
1181
1303
|
for (const path of skillDirsIn(join8(root, dir))) {
|
|
1182
1304
|
const name = basename4(path);
|
|
1183
1305
|
if (seen.has(name)) continue;
|
|
@@ -1395,8 +1517,8 @@ function addSkill(root, name, { force = false, version, allowTracked = false, pe
|
|
|
1395
1517
|
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
1518
|
const target = keep ?? (version === void 0 ? latestVersion(name) : getVersion(name, version));
|
|
1397
1519
|
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 };
|
|
1520
|
+
const plan = addPlan(root, name, target.hash, moving ? false : force);
|
|
1521
|
+
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
1522
|
const { manifest, recorded, primary, wantedLinks, to, local } = plan;
|
|
1401
1523
|
const mine = pinsWhere(root, recorded, personal);
|
|
1402
1524
|
if (typeof mine === "string") return { name, action: "skipped", reason: mine };
|
|
@@ -1754,12 +1876,7 @@ function shippedSkill() {
|
|
|
1754
1876
|
return readFileSync5(new URL("../skills/skilllib/SKILL.md", import.meta.url), "utf-8");
|
|
1755
1877
|
}
|
|
1756
1878
|
function agentSkillDirs(enabled = enabledHarnesses()) {
|
|
1757
|
-
|
|
1758
|
-
const agentsSkills = join9(userHome(), ".agents", "skills");
|
|
1759
|
-
const needsClaude = enabled.some((id) => harness(id).globalDirs().includes(claudeSkills));
|
|
1760
|
-
const needsAgents = enabled.some((id) => !harness(id).globalDirs().includes(claudeSkills));
|
|
1761
|
-
const dirs = [...needsClaude ? [claudeSkills] : [], ...needsAgents ? [agentsSkills] : []];
|
|
1762
|
-
return dirs.filter((d, i) => dirs.findIndex((o) => realPath(o) === realPath(d)) === i).map((d) => join9(d, AGENT_SKILL_NAME));
|
|
1879
|
+
return globalInstallDirs(enabled).map((d) => join9(d, AGENT_SKILL_NAME));
|
|
1763
1880
|
}
|
|
1764
1881
|
function isOurs(dir) {
|
|
1765
1882
|
return existsSync8(join9(dir, OWN_SKILL_MARKER));
|
|
@@ -1790,7 +1907,7 @@ function installAgentSkill(enabled = enabledHarnesses()) {
|
|
|
1790
1907
|
return { ok: true, dirs: [primary, ...links] };
|
|
1791
1908
|
}
|
|
1792
1909
|
function removeAgentSkill() {
|
|
1793
|
-
const all =
|
|
1910
|
+
const all = [...new Set(HARNESSES.flatMap((h) => h.globalDirs().map((d) => join9(d, AGENT_SKILL_NAME))))];
|
|
1794
1911
|
const removed = all.filter((d) => entryExists(d) && (isOurs(d) || isDangling(d)));
|
|
1795
1912
|
for (const d of removed) {
|
|
1796
1913
|
if (!entryExists(d)) continue;
|
|
@@ -1807,9 +1924,9 @@ function refreshAgentSkill() {
|
|
|
1807
1924
|
}
|
|
1808
1925
|
|
|
1809
1926
|
// src/commands.ts
|
|
1810
|
-
import { existsSync as
|
|
1927
|
+
import { existsSync as existsSync15, readFileSync as readFileSync7, rmSync as rmSync7 } from "fs";
|
|
1811
1928
|
import { parseArgs as parseFlags } from "util";
|
|
1812
|
-
import { basename as
|
|
1929
|
+
import { basename as basename11, join as join16, resolve as resolve9, sep as sep10 } from "path";
|
|
1813
1930
|
|
|
1814
1931
|
// src/git.ts
|
|
1815
1932
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
@@ -2209,11 +2326,28 @@ function namesIn(dirs) {
|
|
|
2209
2326
|
}
|
|
2210
2327
|
return [...names].sort();
|
|
2211
2328
|
}
|
|
2212
|
-
var
|
|
2213
|
-
var
|
|
2329
|
+
var CLASSIC_TIDY = [PROJECT_SKILLS_DIR, AGENTS_SKILLS_DIR, ".cursor/skills", ".codex/skills"];
|
|
2330
|
+
var tidyDirs = () => {
|
|
2331
|
+
const enabled = enabledHarnesses();
|
|
2332
|
+
const fromEnabled = enabled.flatMap((id) => harness(id).projectDirs);
|
|
2333
|
+
return [.../* @__PURE__ */ new Set([...CLASSIC_TIDY, ...fromEnabled])].filter((d) => d !== GROK_SKILLS_DIR && (d !== "skills" || enabled.includes("openclaw")));
|
|
2334
|
+
};
|
|
2335
|
+
var repoTidyDirs = (root) => tidyDirs().filter((d) => notWritable(root, d) === null);
|
|
2214
2336
|
function globalTidyDirs() {
|
|
2215
2337
|
const own = realPath(join12(grokDir(), "skills"));
|
|
2216
|
-
|
|
2338
|
+
const allowed = new Set(
|
|
2339
|
+
[
|
|
2340
|
+
join12(userHome(), ".agents", "skills"),
|
|
2341
|
+
join12(claudeDir(), "skills"),
|
|
2342
|
+
join12(userHome(), ".cursor", "skills"),
|
|
2343
|
+
join12(userHome(), ".codex", "skills"),
|
|
2344
|
+
...enabledHarnesses().flatMap((id) => harness(id).globalDirs())
|
|
2345
|
+
].map((d) => realPath(d))
|
|
2346
|
+
);
|
|
2347
|
+
return globalSkillDirs().filter((d) => {
|
|
2348
|
+
const real = realPath(d);
|
|
2349
|
+
return real !== own && allowed.has(real);
|
|
2350
|
+
});
|
|
2217
2351
|
}
|
|
2218
2352
|
function runsFrom(id, dirsWithCopy, readsOf) {
|
|
2219
2353
|
const reads = readsOf(id).filter((d) => dirsWithCopy.includes(d));
|
|
@@ -2400,6 +2534,8 @@ function intoLibrary(path, force = false) {
|
|
|
2400
2534
|
if (r.status === "exists") throw new Error(`${r.name}: your library has a different ${r.name}; update or delete it first`);
|
|
2401
2535
|
return r.name;
|
|
2402
2536
|
}
|
|
2537
|
+
var pinsOf = (where, personal) => personal ? "your personal pins" : `${where}'s skilllib.json`;
|
|
2538
|
+
var newPinsOf = (root, where) => pinsOf(where, !existsSync12(join13(root, MANIFEST_FILE)) && personalPinsFile(root) !== null);
|
|
2403
2539
|
function track(root, name) {
|
|
2404
2540
|
const c = addSkill(root, name);
|
|
2405
2541
|
if (c.action === "skipped") throw new Error(`${name}: ${c.reason}`);
|
|
@@ -2462,8 +2598,8 @@ function applyAll(plans, git2) {
|
|
|
2462
2598
|
}
|
|
2463
2599
|
function brokenLinksIn(root, opened) {
|
|
2464
2600
|
const where = basename7(root);
|
|
2465
|
-
const
|
|
2466
|
-
const hasCopy = (name) =>
|
|
2601
|
+
const tidyDirs2 = repoTidyDirs(root);
|
|
2602
|
+
const hasCopy = (name) => tidyDirs2.some((d) => !isLink(join13(root, d, name)) && existsSync12(join13(root, d, name, "SKILL.md")));
|
|
2467
2603
|
const mends = (project, dir, name) => {
|
|
2468
2604
|
const dep = pinsIn(project)[name];
|
|
2469
2605
|
return !!dep && (dep.links ?? []).includes(dir) && !syncBlocker(project, name, dep) && versionForHash(name, dep.hash) !== null;
|
|
@@ -2483,7 +2619,7 @@ function brokenLinksIn(root, opened) {
|
|
|
2483
2619
|
const seen = /* @__PURE__ */ new Set();
|
|
2484
2620
|
const aliased = (dir) => (realNative(join13(root, dir)) ?? "") !== join13(realRoot, dir);
|
|
2485
2621
|
const packageFolders = packageSkillFolders(root).map((f) => ({ dir: f.location, top: false })).sort((a, b) => Number(aliased(a.dir)) - Number(aliased(b.dir)));
|
|
2486
|
-
const folders2 = [...
|
|
2622
|
+
const folders2 = [...projectDirsInUse(enabledHarnesses()).map((dir) => ({ dir, top: true })), ...packageFolders].flatMap(({ dir, top }) => {
|
|
2487
2623
|
const real = realNative(join13(root, dir)) ?? join13(root, dir);
|
|
2488
2624
|
if (seen.has(real)) return [];
|
|
2489
2625
|
seen.add(real);
|
|
@@ -2865,7 +3001,7 @@ function findIssues(roots, machine, libraryNames, statusOf = projectStatus, load
|
|
|
2865
3001
|
}
|
|
2866
3002
|
const agentSkill2 = agentSkillState();
|
|
2867
3003
|
if (agentSkill2 !== "installed") {
|
|
2868
|
-
const
|
|
3004
|
+
const install2 = {
|
|
2869
3005
|
label: agentSkill2 === "missing" ? "Install the skilllib skill for your agents" : "Update the skilllib skill",
|
|
2870
3006
|
hint: "one global skill, in ~/.claude/skills (and ~/.agents/skills)",
|
|
2871
3007
|
run: () => {
|
|
@@ -2880,7 +3016,7 @@ function findIssues(roots, machine, libraryNames, statusOf = projectStatus, load
|
|
|
2880
3016
|
title: agentSkill2 === "missing" ? "Your agents don't know about skilllib" : "The skilllib skill for your agents is out of date",
|
|
2881
3017
|
detail: "The skilllib skill lets you ask your agents which skills they can use here, where each comes from, and which of your skills a repo should add.",
|
|
2882
3018
|
at: rows(null, ["skilllib"]),
|
|
2883
|
-
...agentSkill2 === "missing" ? { choices: [
|
|
3019
|
+
...agentSkill2 === "missing" ? { choices: [install2] } : { fix: install2 }
|
|
2884
3020
|
});
|
|
2885
3021
|
}
|
|
2886
3022
|
return issues;
|
|
@@ -2937,7 +3073,7 @@ function repoIssues(root, ctx) {
|
|
|
2937
3073
|
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
3074
|
at: rows(root, [s.name]),
|
|
2939
3075
|
// 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}` }] }
|
|
3076
|
+
...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
3077
|
});
|
|
2942
3078
|
if (restorable.length > 0) {
|
|
2943
3079
|
ctx.issues.push({
|
|
@@ -2957,7 +3093,7 @@ function repoIssues(root, ctx) {
|
|
|
2957
3093
|
if (drifted.length) {
|
|
2958
3094
|
const seen = drifted.some((s) => isShared(gitOf(root)?.of(relativeTo(root, s.path))));
|
|
2959
3095
|
const yours = drifted.some((s) => untrackable(s));
|
|
2960
|
-
const
|
|
3096
|
+
const install2 = {
|
|
2961
3097
|
label: "Install the pinned versions",
|
|
2962
3098
|
hint: seen ? "git will see the copies change" : "your library keeps every version",
|
|
2963
3099
|
run: () => summarize(where, "synced", syncProject(root, { names: drifted.map((s) => s.name) }))
|
|
@@ -2968,7 +3104,7 @@ function repoIssues(root, ctx) {
|
|
|
2968
3104
|
title: `${where}: ${plural(drifted.length, "skill")} not at the pinned version`,
|
|
2969
3105
|
detail: `${drifted.map((s) => s.name).join(", ")}: each copy is another version from your library. sync installs the pinned one.${yours ? ` ${[...new Set(drifted.flatMap((s) => untrackable(s) ? [untrackable(s)] : []))].map((why) => `${drifted.filter((s) => untrackable(s) === why).map((s) => s.name).join(", ")}: ${why}`).join("; ")}, so skilllib can't pin the version that's there; ${drifted.filter((s) => untrackable(s)).length === 1 ? "it may be edits you copied into Your skills on purpose, and stays" : "they may be edits you copied into Your skills on purpose, and stay"} unless you choose this.` : ""}`,
|
|
2970
3106
|
at: rows(root, drifted.map((s) => s.name)),
|
|
2971
|
-
...seen || yours ? { choices: [
|
|
3107
|
+
...seen || yours ? { choices: [install2] } : { fix: install2 }
|
|
2972
3108
|
});
|
|
2973
3109
|
}
|
|
2974
3110
|
for (const s of unstuck.filter((s2) => s2.state === "folder missing" && s2.latest === null))
|
|
@@ -2981,12 +3117,12 @@ function repoIssues(root, ctx) {
|
|
|
2981
3117
|
// Editing skilllib.json is the team's call: a choice, never run by `doctor --fix`.
|
|
2982
3118
|
choices: [
|
|
2983
3119
|
{
|
|
2984
|
-
label: "Remove it from skilllib.json",
|
|
3120
|
+
label: s.personal ? "Remove it from your personal pins" : "Remove it from skilllib.json",
|
|
2985
3121
|
hint: `${where} stops pinning it`,
|
|
2986
3122
|
run: () => {
|
|
2987
3123
|
const r = removeSkill(root, s.name);
|
|
2988
3124
|
if (r.action !== "removed") throw new Error(`${s.name}: ${r.reason}`);
|
|
2989
|
-
return `${s.name}: removed from ${where
|
|
3125
|
+
return `${s.name}: removed from ${pinsOf(where, s.personal)}`;
|
|
2990
3126
|
}
|
|
2991
3127
|
}
|
|
2992
3128
|
]
|
|
@@ -3003,7 +3139,7 @@ function repoIssues(root, ctx) {
|
|
|
3003
3139
|
choices: [
|
|
3004
3140
|
{
|
|
3005
3141
|
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",
|
|
3142
|
+
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
3143
|
run: () => summarize(where, "updated", updateProject(root, behind.map((s) => s.name)))
|
|
3008
3144
|
}
|
|
3009
3145
|
]
|
|
@@ -3086,7 +3222,7 @@ function repoIssues(root, ctx) {
|
|
|
3086
3222
|
if (partial.length) {
|
|
3087
3223
|
const missing2 = [...new Set(partial.flatMap((s) => s.visibility.filter((v) => v.paths === 0).map((v) => v.id)))];
|
|
3088
3224
|
const tracked = [...new Set(blindHere.flatMap(({ would }) => would.blocked))];
|
|
3089
|
-
const manifestShared = partial.some((s) => s.managed) && isShared(gitOf(root)?.of(MANIFEST_FILE));
|
|
3225
|
+
const manifestShared = partial.some((s) => s.managed && !s.personal) && isShared(gitOf(root)?.of(MANIFEST_FILE));
|
|
3090
3226
|
const run = (allowTracked) => () => {
|
|
3091
3227
|
if (allowTracked) allowTrackedLinks(root);
|
|
3092
3228
|
const r = linkAll(root, { allowTracked, names: partial.map((s) => s.name) });
|
|
@@ -3162,9 +3298,9 @@ function repoIssues(root, ctx) {
|
|
|
3162
3298
|
id: `adopt:${root}:${s.name}`,
|
|
3163
3299
|
severity: "suggestion",
|
|
3164
3300
|
title: `${s.name} in ${where} isn't tracked`,
|
|
3165
|
-
detail:
|
|
3301
|
+
detail: `It's identical to the library copy; track it (in ${newPinsOf(root, where)}) so future library changes reach it.`,
|
|
3166
3302
|
at: rows(root, [s.name]),
|
|
3167
|
-
choices: [{ label: "Track it", hint: `adds it to ${where}
|
|
3303
|
+
choices: [{ label: "Track it", hint: `adds it to ${newPinsOf(root, where)}`, run: () => `${track(root, s.name).name} tracked in ${where}` }]
|
|
3168
3304
|
});
|
|
3169
3305
|
} else if (s.state === "untracked, differs from library" || s.state === "repo skill, differs from library") {
|
|
3170
3306
|
const teams = s.state.startsWith("repo skill") || committed(s) || !!untrackable(s);
|
|
@@ -3226,7 +3362,7 @@ function repoIssues(root, ctx) {
|
|
|
3226
3362
|
id: `not-in-lib:${root}:${s.name}`,
|
|
3227
3363
|
severity: "suggestion",
|
|
3228
3364
|
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.`,
|
|
3365
|
+
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
3366
|
at: rows(root, [s.name]),
|
|
3231
3367
|
choices: [
|
|
3232
3368
|
{
|
|
@@ -3623,12 +3759,217 @@ function usesByProject(uses, projectOf) {
|
|
|
3623
3759
|
return byProject;
|
|
3624
3760
|
}
|
|
3625
3761
|
|
|
3762
|
+
// src/install.ts
|
|
3763
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
3764
|
+
import { createWriteStream, existsSync as existsSync14, lstatSync as lstatSync5, mkdirSync as mkdirSync9, mkdtempSync, readdirSync as readdirSync11, rmSync as rmSync6, statSync as statSync7 } from "fs";
|
|
3765
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3766
|
+
import { basename as basename9, join as join15, resolve as resolve8, sep as sep9 } from "path";
|
|
3767
|
+
import { Readable } from "stream";
|
|
3768
|
+
import { pipeline } from "stream/promises";
|
|
3769
|
+
var SEARCH_API = process.env.SKILLLIB_SKILLS_API || "https://skills.sh";
|
|
3770
|
+
var MAX_BYTES = 32 * 1024 * 1024;
|
|
3771
|
+
var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage"]);
|
|
3772
|
+
async function searchSkills(query) {
|
|
3773
|
+
const url = `${SEARCH_API}/api/search?${new URLSearchParams({ q: query, limit: "20" })}`;
|
|
3774
|
+
const res = await fetch(url);
|
|
3775
|
+
if (!res.ok) throw new Error(`skills.sh search failed (${res.status})`);
|
|
3776
|
+
const data = await res.json();
|
|
3777
|
+
return (data.skills ?? []).map((s) => ({
|
|
3778
|
+
name: s.name || s.skillId || s.id || "",
|
|
3779
|
+
source: s.source || "",
|
|
3780
|
+
skillId: s.skillId || s.name || "",
|
|
3781
|
+
installs: s.installs ?? 0
|
|
3782
|
+
}));
|
|
3783
|
+
}
|
|
3784
|
+
var NAME = /^[A-Za-z0-9_.-]+$/;
|
|
3785
|
+
function parseInstallSpec(input) {
|
|
3786
|
+
const trimmed = input.trim().replace(/\/+$/, "");
|
|
3787
|
+
if (!trimmed) return null;
|
|
3788
|
+
if (trimmed.startsWith(".") || trimmed.startsWith("/") || trimmed.startsWith("~") || /^[A-Za-z]:[\\/]/.test(trimmed)) {
|
|
3789
|
+
return { local: trimmed.replace(/^~(?=$|[\\/])/, userHome()) };
|
|
3790
|
+
}
|
|
3791
|
+
let text = trimmed;
|
|
3792
|
+
try {
|
|
3793
|
+
const url = new URL(trimmed);
|
|
3794
|
+
const host = url.hostname.replace(/^www\./, "");
|
|
3795
|
+
const parts2 = url.pathname.split("/").filter(Boolean);
|
|
3796
|
+
if (host === "skills.sh" && parts2.length >= 2) text = parts2.length >= 3 ? `${parts2[0]}/${parts2[1]}@${parts2.slice(2).join("/")}` : `${parts2[0]}/${parts2[1]}`;
|
|
3797
|
+
else if (host === "github.com" && parts2.length >= 2) {
|
|
3798
|
+
const repo = parts2[1].replace(/\.git$/, "");
|
|
3799
|
+
if (parts2[2] === "tree" && parts2.length >= 5) return namePair(parts2[0], repo, parts2.slice(4).join("/"));
|
|
3800
|
+
return namePair(parts2[0], repo);
|
|
3801
|
+
} else return null;
|
|
3802
|
+
} catch {
|
|
3803
|
+
}
|
|
3804
|
+
const at = text.split("@");
|
|
3805
|
+
if (at.length === 2 && at[0]?.includes("/") && at[1] && at[0].split("/").length === 2) {
|
|
3806
|
+
const [owner, repo] = at[0].split("/");
|
|
3807
|
+
return namePair(owner, repo, at[1]);
|
|
3808
|
+
}
|
|
3809
|
+
const parts = text.split("/");
|
|
3810
|
+
if (parts.length === 2) return namePair(parts[0], parts[1]);
|
|
3811
|
+
if (parts.length >= 3) return namePair(parts[0], parts[1], parts.slice(2).join("/"));
|
|
3812
|
+
return null;
|
|
3813
|
+
}
|
|
3814
|
+
function namePair(owner, repo, skill) {
|
|
3815
|
+
if (!NAME.test(owner) || !NAME.test(repo) || owner === "." || repo === "." || owner === ".." || repo === "..") return null;
|
|
3816
|
+
return skill ? { owner, repo, skill } : { owner, repo };
|
|
3817
|
+
}
|
|
3818
|
+
function escapes(root, path) {
|
|
3819
|
+
const base = realpathOrNull(root);
|
|
3820
|
+
const real = realpathOrNull(path);
|
|
3821
|
+
if (!base || !real) return true;
|
|
3822
|
+
return real !== base && !real.startsWith(base + sep9);
|
|
3823
|
+
}
|
|
3824
|
+
function findSkillDirs(root, depth = 0) {
|
|
3825
|
+
if (depth > 6 || !isDir(root)) return [];
|
|
3826
|
+
if (isSkillDir(root)) return escapes(root, root) ? [] : [root];
|
|
3827
|
+
let names = [];
|
|
3828
|
+
try {
|
|
3829
|
+
names = readdirSync11(root);
|
|
3830
|
+
} catch {
|
|
3831
|
+
return [];
|
|
3832
|
+
}
|
|
3833
|
+
return names.filter((n) => !n.startsWith(".") && !SKIP.has(n)).flatMap((n) => {
|
|
3834
|
+
const path = join15(root, n);
|
|
3835
|
+
const st = lstatSync5(path, { throwIfNoEntry: false });
|
|
3836
|
+
if (!st?.isDirectory()) return [];
|
|
3837
|
+
if (st.isSymbolicLink() && escapes(root, path)) return [];
|
|
3838
|
+
return findSkillDirs(path, depth + 1);
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
function skillNamed(dirs, skill) {
|
|
3842
|
+
if (!skill) return dirs;
|
|
3843
|
+
const want = skill.split("/").filter(Boolean).pop();
|
|
3844
|
+
return dirs.filter((d) => basename9(d) === want || readSkillInfo(d).name === want);
|
|
3845
|
+
}
|
|
3846
|
+
async function downloadGitHub(owner, repo) {
|
|
3847
|
+
const dest = mkdtempSync(join15(tmpdir2(), "skilllib-"));
|
|
3848
|
+
const archive = join15(dest, "repo.tar.gz");
|
|
3849
|
+
const url = `https://codeload.github.com/${owner}/${repo}/tar.gz/HEAD`;
|
|
3850
|
+
let res;
|
|
3851
|
+
try {
|
|
3852
|
+
res = await fetch(url, { redirect: "follow" });
|
|
3853
|
+
} catch (err) {
|
|
3854
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
3855
|
+
throw new Error(`couldn't download ${owner}/${repo} (${err instanceof Error ? err.message : err})`);
|
|
3856
|
+
}
|
|
3857
|
+
if (!res.ok || !res.body) {
|
|
3858
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
3859
|
+
throw new Error(res.status === 404 ? `${owner}/${repo} not found on GitHub` : `couldn't download ${owner}/${repo} (${res.status})`);
|
|
3860
|
+
}
|
|
3861
|
+
const host = new URL(res.url).hostname;
|
|
3862
|
+
if (host !== "codeload.github.com" && host !== "github.com") {
|
|
3863
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
3864
|
+
throw new Error(`refusing to download ${owner}/${repo} from ${host}`);
|
|
3865
|
+
}
|
|
3866
|
+
let bytes = 0;
|
|
3867
|
+
try {
|
|
3868
|
+
await pipeline(
|
|
3869
|
+
Readable.from(
|
|
3870
|
+
(async function* () {
|
|
3871
|
+
const reader = res.body.getReader();
|
|
3872
|
+
for (; ; ) {
|
|
3873
|
+
const { done, value } = await reader.read();
|
|
3874
|
+
if (done) return;
|
|
3875
|
+
bytes += value.byteLength;
|
|
3876
|
+
if (bytes > MAX_BYTES) throw new Error(`${owner}/${repo} is larger than ${MAX_BYTES / 1024 / 1024}MB`);
|
|
3877
|
+
yield value;
|
|
3878
|
+
}
|
|
3879
|
+
})()
|
|
3880
|
+
),
|
|
3881
|
+
createWriteStream(archive)
|
|
3882
|
+
);
|
|
3883
|
+
execFileSync5("tar", ["-xzf", archive, "-C", dest], { stdio: "ignore" });
|
|
3884
|
+
} catch (err) {
|
|
3885
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
3886
|
+
if (err instanceof Error && /larger than|couldn't download/.test(err.message)) throw err;
|
|
3887
|
+
throw new Error(`couldn't extract ${owner}/${repo}`);
|
|
3888
|
+
}
|
|
3889
|
+
rmSync6(archive, { force: true });
|
|
3890
|
+
const top = readdirSync11(dest);
|
|
3891
|
+
const root = top.length === 1 && statSync7(join15(dest, top[0])).isDirectory() ? join15(dest, top[0]) : dest;
|
|
3892
|
+
return { tmp: dest, root };
|
|
3893
|
+
}
|
|
3894
|
+
function installGlobal(name, { force = false } = {}) {
|
|
3895
|
+
const latest = latestVersion(name);
|
|
3896
|
+
if (!latest) return { ok: false, reason: "not in the library" };
|
|
3897
|
+
const parents = globalInstallDirs(enabledHarnesses());
|
|
3898
|
+
if (!parents.length) return { ok: false, reason: "no agents chosen yet (skilllib harnesses)" };
|
|
3899
|
+
const [primary, ...links] = parents;
|
|
3900
|
+
const dest = join15(primary, name);
|
|
3901
|
+
if (entryExists(dest) && !isLink(dest) && !isDir(dest)) return { ok: false, reason: `${dest} is a file` };
|
|
3902
|
+
const there = treeHash(dest);
|
|
3903
|
+
if (there && there !== latest.hash && versionForHash(name, there) === null && !force) {
|
|
3904
|
+
return { ok: false, reason: `${name} is already global, with edits that aren't in your library (pass --force to replace them)` };
|
|
3905
|
+
}
|
|
3906
|
+
if (there !== latest.hash) {
|
|
3907
|
+
if (isLink(dest)) rmSync6(dest, { force: true });
|
|
3908
|
+
mkdirSync9(primary, { recursive: true });
|
|
3909
|
+
copySkill(versionDir(name, latest.version), dest);
|
|
3910
|
+
}
|
|
3911
|
+
const wrote = [dest];
|
|
3912
|
+
for (const dir of links) {
|
|
3913
|
+
const link2 = join15(dir, name);
|
|
3914
|
+
if (isLink(link2) && realpathOrNull(link2) === null) rmSync6(link2, { force: true });
|
|
3915
|
+
else if (entryExists(link2)) {
|
|
3916
|
+
if (realpathOrNull(link2) === realpathOrNull(dest)) wrote.push(link2);
|
|
3917
|
+
continue;
|
|
3918
|
+
}
|
|
3919
|
+
mkdirSync9(dir, { recursive: true });
|
|
3920
|
+
linkDir(realpathOrNull(dest) ?? dest, join15(realpathOrNull(dir) ?? dir, name));
|
|
3921
|
+
wrote.push(link2);
|
|
3922
|
+
}
|
|
3923
|
+
setKeepGlobal([name], true);
|
|
3924
|
+
return { ok: true, dirs: wrote };
|
|
3925
|
+
}
|
|
3926
|
+
function installFound(dirs, source, { force = false, global = false, allowTracked = false } = {}) {
|
|
3927
|
+
const root = global ? null : projectHere();
|
|
3928
|
+
const label = source.startsWith("/") || source.startsWith(".") ? source : `skills.sh: ${source}`;
|
|
3929
|
+
return dirs.map((dir) => {
|
|
3930
|
+
const imported = importSkill(dir, { force });
|
|
3931
|
+
if (imported.status !== "exists") recordOrigin(imported.name, label);
|
|
3932
|
+
const row = { name: imported.name, source, status: imported.status };
|
|
3933
|
+
if (imported.status === "exists") {
|
|
3934
|
+
row.reason = "the library already has a different version (pass --force)";
|
|
3935
|
+
return row;
|
|
3936
|
+
}
|
|
3937
|
+
if (global) {
|
|
3938
|
+
const placed = installGlobal(imported.name, { force });
|
|
3939
|
+
if (!placed.ok) {
|
|
3940
|
+
row.status = "skipped";
|
|
3941
|
+
row.reason = placed.reason;
|
|
3942
|
+
} else row.global = placed.dirs;
|
|
3943
|
+
return row;
|
|
3944
|
+
}
|
|
3945
|
+
if (!root) return row;
|
|
3946
|
+
row.project = addSkill(root, imported.name, { force, allowTracked });
|
|
3947
|
+
if (row.project.action === "skipped") {
|
|
3948
|
+
row.status = "skipped";
|
|
3949
|
+
row.reason = row.project.reason;
|
|
3950
|
+
}
|
|
3951
|
+
return row;
|
|
3952
|
+
});
|
|
3953
|
+
}
|
|
3954
|
+
async function resolveInstallDir(spec) {
|
|
3955
|
+
if ("local" in spec) {
|
|
3956
|
+
const path = resolve8(spec.local);
|
|
3957
|
+
if (!existsSync14(path)) throw new Error(`${path} doesn't exist`);
|
|
3958
|
+
const dirs2 = isSkillDir(path) ? [path] : findSkillDirs(path);
|
|
3959
|
+
return { dirs: dirs2, source: path, many: dirs2.length > 1 };
|
|
3960
|
+
}
|
|
3961
|
+
const got = await downloadGitHub(spec.owner, spec.repo);
|
|
3962
|
+
const source = spec.skill ? `${spec.owner}/${spec.repo}@${spec.skill}` : `${spec.owner}/${spec.repo}`;
|
|
3963
|
+
const dirs = skillNamed(findSkillDirs(got.root), spec.skill);
|
|
3964
|
+
return { tmp: got.tmp, dirs, source, many: !spec.skill && dirs.length > 1 };
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3626
3967
|
// src/move.ts
|
|
3627
|
-
import { basename as
|
|
3968
|
+
import { basename as basename10 } from "path";
|
|
3628
3969
|
function moveGlobalSkills(skills, roots, {
|
|
3629
3970
|
group,
|
|
3630
3971
|
allowTracked = false,
|
|
3631
|
-
label =
|
|
3972
|
+
label = basename10,
|
|
3632
3973
|
allowTrackedHint = "re-run with --allow-tracked"
|
|
3633
3974
|
} = {}) {
|
|
3634
3975
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -3747,9 +4088,9 @@ function projectOfFactory(projects2) {
|
|
|
3747
4088
|
const sorted2 = [...projects2].sort((a, b) => b.length - a.length);
|
|
3748
4089
|
return (cwd) => {
|
|
3749
4090
|
if (!cwd) return null;
|
|
3750
|
-
const known = sorted2.find((p) => cwd === p || cwd.startsWith(p +
|
|
4091
|
+
const known = sorted2.find((p) => cwd === p || cwd.startsWith(p + sep10));
|
|
3751
4092
|
if (known) return known;
|
|
3752
|
-
const repo = cwd.split(`${
|
|
4093
|
+
const repo = cwd.split(`${sep10}.claude${sep10}worktrees${sep10}`)[0];
|
|
3753
4094
|
return isProjectCandidate(repo) ? repo : null;
|
|
3754
4095
|
};
|
|
3755
4096
|
}
|
|
@@ -3763,7 +4104,7 @@ var projectsFor = (args) => {
|
|
|
3763
4104
|
if (!args.all) return [requireProject()];
|
|
3764
4105
|
const { readable, unreadable } = readableRoots(visibleProjects());
|
|
3765
4106
|
for (const u of unreadable) {
|
|
3766
|
-
error(`${
|
|
4107
|
+
error(`${basename11(u.root)}: ${u.reason}`);
|
|
3767
4108
|
process.exitCode = 1;
|
|
3768
4109
|
}
|
|
3769
4110
|
return readable;
|
|
@@ -3775,7 +4116,7 @@ function requireProject() {
|
|
|
3775
4116
|
if (!isProjectCandidate(findProjectRoot())) {
|
|
3776
4117
|
const top = findProjectRoot();
|
|
3777
4118
|
if (realPath(top) !== realPath(userHome())) throw new Error(`${tildify(top)} is skilllib's or an agent's own folder, not a project. Run this inside a project.`);
|
|
3778
|
-
const stray =
|
|
4119
|
+
const stray = existsSync15(join16(userHome(), MANIFEST_FILE)) ? `, though ${tildify(join16(userHome(), MANIFEST_FILE))} makes it look like one` : "";
|
|
3779
4120
|
throw new Error(`Your home folder isn't a project${stray}: skills added here would load in every repo. Run this inside a project.`);
|
|
3780
4121
|
}
|
|
3781
4122
|
throw new Error("Not in a git repo. Run this inside a project (or `git init` first).");
|
|
@@ -3797,7 +4138,7 @@ async function status(args) {
|
|
|
3797
4138
|
const root = requireProject();
|
|
3798
4139
|
const skills = projectStatus(root);
|
|
3799
4140
|
const nested = nestedSkills(root);
|
|
3800
|
-
info(`${
|
|
4141
|
+
info(`${basename11(root)} ${dim(tildify(root))}
|
|
3801
4142
|
`);
|
|
3802
4143
|
if (skills.length === 0 && nested.length === 0) info("No skills in this project yet. Add one from your library with: skilllib add <name>");
|
|
3803
4144
|
const usage2 = await usageBySkill(args.days, root);
|
|
@@ -3870,7 +4211,7 @@ async function list(args) {
|
|
|
3870
4211
|
const installs = /* @__PURE__ */ new Map();
|
|
3871
4212
|
for (const project of visibleProjects()) {
|
|
3872
4213
|
for (const name of Object.keys(pinsIn(project))) {
|
|
3873
|
-
installs.set(name, [...installs.get(name) ?? [],
|
|
4214
|
+
installs.set(name, [...installs.get(name) ?? [], basename11(project)]);
|
|
3874
4215
|
}
|
|
3875
4216
|
}
|
|
3876
4217
|
const usage2 = await usageBySkill(args.days);
|
|
@@ -3913,7 +4254,7 @@ function teamStateIn(root, extraDirs = []) {
|
|
|
3913
4254
|
function projects(args) {
|
|
3914
4255
|
const { readable: roots, unreadable } = readableRoots(visibleProjects());
|
|
3915
4256
|
for (const u of unreadable) {
|
|
3916
|
-
error(`${
|
|
4257
|
+
error(`${basename11(u.root)}: ${u.reason}`);
|
|
3917
4258
|
process.exitCode = 1;
|
|
3918
4259
|
}
|
|
3919
4260
|
if (args.json) {
|
|
@@ -3922,7 +4263,7 @@ function projects(args) {
|
|
|
3922
4263
|
roots.map((root) => {
|
|
3923
4264
|
const teamState = teamStateIn(root);
|
|
3924
4265
|
return {
|
|
3925
|
-
name:
|
|
4266
|
+
name: basename11(root),
|
|
3926
4267
|
path: root,
|
|
3927
4268
|
skills: projectStatus(root).map((s) => ({ name: s.name, managed: s.managed, state: teamState(s), version: s.version, latest: s.latest }))
|
|
3928
4269
|
};
|
|
@@ -3940,7 +4281,7 @@ function projects(args) {
|
|
|
3940
4281
|
const usual = /* @__PURE__ */ new Set(["ok", "local only", "repo skill", "repo skill, in library", "from npx skills"]);
|
|
3941
4282
|
const attention = skills.filter((s) => !usual.has(teamState(s))).length;
|
|
3942
4283
|
return {
|
|
3943
|
-
Project:
|
|
4284
|
+
Project: basename11(root),
|
|
3944
4285
|
Path: dim(tildify(root)),
|
|
3945
4286
|
Skills: skills.map((s) => s.name).join(", ") || dim("none"),
|
|
3946
4287
|
"Local only": String(skills.filter((s) => teamState(s) === "local only").length),
|
|
@@ -3958,7 +4299,7 @@ function add(args) {
|
|
|
3958
4299
|
printChanges(root, changes, args);
|
|
3959
4300
|
const pins = readManifest(root).skills;
|
|
3960
4301
|
const done = changes.filter((c) => c.action !== "skipped" && pins[c.name]);
|
|
3961
|
-
if (!args.json && done.some((c) => pins[c.name].personal))
|
|
4302
|
+
if (!args.json && args.personal === void 0 && done.some((c) => c.action === "installed" && !c.moved && pins[c.name].personal))
|
|
3962
4303
|
info(dim(`Pinned in your personal pins (${tildify(personalPinsDir())}) and kept out of git. --shared pins in skilllib.json for the team.`));
|
|
3963
4304
|
rememberProjects([root]);
|
|
3964
4305
|
}
|
|
@@ -3975,11 +4316,11 @@ function changeEach(args, change, nothing) {
|
|
|
3975
4316
|
const changes = change(root);
|
|
3976
4317
|
if (changes.some(incomplete)) process.exitCode = 1;
|
|
3977
4318
|
if (!args.json) {
|
|
3978
|
-
if (args.all) info(`${
|
|
4319
|
+
if (args.all) info(`${basename11(root)} ${dim(tildify(root))}`);
|
|
3979
4320
|
if (changes.length === 0) nothing(root);
|
|
3980
4321
|
else printChanges(root, changes, args);
|
|
3981
4322
|
}
|
|
3982
|
-
return { project:
|
|
4323
|
+
return { project: basename11(root), path: root, changes: withDirs(root, changes) };
|
|
3983
4324
|
});
|
|
3984
4325
|
if (args.json) args.all ? json("changes --all", results) : json("changes", results[0].changes);
|
|
3985
4326
|
return roots;
|
|
@@ -4010,7 +4351,7 @@ function update(args) {
|
|
|
4010
4351
|
function outdated(args) {
|
|
4011
4352
|
const roots = projectsFor(args);
|
|
4012
4353
|
const behind = roots.flatMap(
|
|
4013
|
-
(root) => projectStatus(root).filter((s) => s.state.endsWith("update available") && s.latest !== null && s.version !== null).map((s) => ({ project:
|
|
4354
|
+
(root) => projectStatus(root).filter((s) => s.state.endsWith("update available") && s.latest !== null && s.version !== null).map((s) => ({ project: basename11(root), path: root, skill: s.name, installed: s.version, latest: s.latest, state: s.state }))
|
|
4014
4355
|
);
|
|
4015
4356
|
if (args.json) return json("outdated", behind);
|
|
4016
4357
|
const rows2 = behind.map((b) => ({ Project: b.project, Skill: b.skill, Installed: `v${b.installed}`, Latest: `v${b.latest}`, Status: colorState(b.state) }));
|
|
@@ -4021,7 +4362,7 @@ function outdated(args) {
|
|
|
4021
4362
|
}
|
|
4022
4363
|
function importSkills(args) {
|
|
4023
4364
|
if (args.global && args.positional.length) throw new Error("skilllib import --global takes no folders: it imports all your global skills");
|
|
4024
|
-
const dirs = args.global ? yourGlobals().map((m) => m.path) : args.positional.map((p) =>
|
|
4365
|
+
const dirs = args.global ? yourGlobals().map((m) => m.path) : args.positional.map((p) => resolve9(p));
|
|
4025
4366
|
if (dirs.length === 0) {
|
|
4026
4367
|
throw new Error("Usage: skilllib import <skill-dir>... [--force] | skilllib import --global");
|
|
4027
4368
|
}
|
|
@@ -4041,11 +4382,11 @@ function importSkills(args) {
|
|
|
4041
4382
|
continue;
|
|
4042
4383
|
}
|
|
4043
4384
|
success(`${name} ${dim(result === "unchanged" ? "already in library" : result === "added" ? "added to library" : "library updated")}`);
|
|
4044
|
-
const root =
|
|
4385
|
+
const root = resolve9(dir, "..", "..", "..");
|
|
4045
4386
|
const dep = projectHere(root) === root ? readManifest(root).skills[name] : void 0;
|
|
4046
|
-
if (dep && realpathOrNull(
|
|
4387
|
+
if (dep && realpathOrNull(join16(root, dep.dir ?? PROJECT_SKILLS_DIR, name)) === realpathOrNull(dir) && dep.hash !== treeHash(dir)) {
|
|
4047
4388
|
const change = addSkill(root, name);
|
|
4048
|
-
if (change.action !== "skipped") info(dim(` ${
|
|
4389
|
+
if (change.action !== "skipped") info(dim(` ${basename11(root)} now depends on ${name} v${change.to}`));
|
|
4049
4390
|
}
|
|
4050
4391
|
}
|
|
4051
4392
|
if (args.json) json("import", results);
|
|
@@ -4061,14 +4402,14 @@ function scan(args) {
|
|
|
4061
4402
|
const library = new Set(librarySkills().map((s) => s.name));
|
|
4062
4403
|
const skillNames = (root) => [
|
|
4063
4404
|
// Not a folder that links out of the repo: what's in it isn't the repo's (as in status).
|
|
4064
|
-
.../* @__PURE__ */ new Set([...
|
|
4405
|
+
.../* @__PURE__ */ new Set([...projectDirsInUse(enabledHarnesses()).filter((d) => !notWritable(root, d)).flatMap((d) => skillDirsIn(join16(root, d))).map((d) => basename11(d)), ...nestedSkills(root).map((s) => s.name)])
|
|
4065
4406
|
];
|
|
4066
4407
|
const withSkills = found.filter((root) => skillNames(root).length > 0);
|
|
4067
4408
|
info(`Scanned ${roots.map((r) => tildify(r)).join(", ")}: ${found.length} project${found.length === 1 ? "" : "s"}, ${withSkills.length} with skills
|
|
4068
4409
|
`);
|
|
4069
4410
|
table(
|
|
4070
4411
|
withSkills.map((root) => ({
|
|
4071
|
-
Project:
|
|
4412
|
+
Project: basename11(root),
|
|
4072
4413
|
Path: dim(tildify(root)),
|
|
4073
4414
|
Skills: skillNames(root).map((n) => library.has(n) ? n : yellow(n)).join(", ")
|
|
4074
4415
|
}))
|
|
@@ -4125,7 +4466,7 @@ function newSkill(args) {
|
|
|
4125
4466
|
if (!r.ok) {
|
|
4126
4467
|
throw new Error(`Can't create ${name}: ${r.reason}`);
|
|
4127
4468
|
}
|
|
4128
|
-
success(`Created ${tildify(
|
|
4469
|
+
success(`Created ${tildify(join16(r.dir, "SKILL.md"))}`);
|
|
4129
4470
|
}
|
|
4130
4471
|
function restore(args) {
|
|
4131
4472
|
const backups = [...listBackups(), ...pluginBackups()];
|
|
@@ -4238,7 +4579,7 @@ async function usage(args) {
|
|
|
4238
4579
|
return json("usage", {
|
|
4239
4580
|
usageDays: args.days,
|
|
4240
4581
|
source: "Claude Code transcripts",
|
|
4241
|
-
skills: summaries.map((s) => ({ skill: s.skill, uses: s.uses, lastUsed: s.lastUsed, projects: [...s.projects].map((p) =>
|
|
4582
|
+
skills: summaries.map((s) => ({ skill: s.skill, uses: s.uses, lastUsed: s.lastUsed, projects: [...s.projects].map((p) => basename11(p)), inLibrary: library.has(s.skill) })),
|
|
4242
4583
|
unusedLibrarySkills: unused
|
|
4243
4584
|
});
|
|
4244
4585
|
}
|
|
@@ -4246,13 +4587,13 @@ async function usage(args) {
|
|
|
4246
4587
|
info(`No skill usage in Claude Code transcripts from the last ${args.days} days.`);
|
|
4247
4588
|
return;
|
|
4248
4589
|
}
|
|
4249
|
-
info(`Skill usage, last ${args.days} days ${dim(`(from ${tildify(
|
|
4590
|
+
info(`Skill usage, last ${args.days} days ${dim(`(from ${tildify(join16(claudeDir(), "projects"))})`)}
|
|
4250
4591
|
`);
|
|
4251
4592
|
table(
|
|
4252
4593
|
summaries.map((s) => ({
|
|
4253
4594
|
Skill: s.skill,
|
|
4254
4595
|
Uses: String(s.uses),
|
|
4255
|
-
Projects: [...s.projects].map((p) =>
|
|
4596
|
+
Projects: [...s.projects].map((p) => basename11(p)).join(", ") || dim("-"),
|
|
4256
4597
|
"Last used": lastUsed(s),
|
|
4257
4598
|
Library: library.has(s.skill) ? green("yes") : dim("no")
|
|
4258
4599
|
}))
|
|
@@ -4264,7 +4605,7 @@ async function usage(args) {
|
|
|
4264
4605
|
}
|
|
4265
4606
|
function show(args) {
|
|
4266
4607
|
const name = args.positional[0];
|
|
4267
|
-
const dir = name ?
|
|
4608
|
+
const dir = name ? join16(libraryDir(), name) : "";
|
|
4268
4609
|
if (!name || !isSkillDir(dir)) {
|
|
4269
4610
|
throw new Error(name ? `${name} is not in the library` : "Usage: skilllib show <skill-name>");
|
|
4270
4611
|
}
|
|
@@ -4276,8 +4617,8 @@ function show(args) {
|
|
|
4276
4617
|
path: dir,
|
|
4277
4618
|
latest: latestVersion(name)?.version ?? null,
|
|
4278
4619
|
origin: libraryOrigins()[name] ?? "",
|
|
4279
|
-
projects: projectsUsing(name).map((p) =>
|
|
4280
|
-
skillMd: readFileSync7(
|
|
4620
|
+
projects: projectsUsing(name).map((p) => basename11(p)),
|
|
4621
|
+
skillMd: readFileSync7(join16(dir, "SKILL.md"), "utf-8")
|
|
4281
4622
|
});
|
|
4282
4623
|
}
|
|
4283
4624
|
info(`${name} ${dim(tildify(dir))}`);
|
|
@@ -4299,7 +4640,7 @@ function harnessesCommand(args) {
|
|
|
4299
4640
|
}
|
|
4300
4641
|
const enabled = enabledHarnesses();
|
|
4301
4642
|
for (const h of HARNESSES) {
|
|
4302
|
-
info(`${enabled.includes(h.id) ? green("\u25C9") : dim("\u25CB")} ${h.name.padEnd(
|
|
4643
|
+
info(`${enabled.includes(h.id) ? green("\u25C9") : dim("\u25CB")} ${h.name.padEnd(18)} ${dim(`reads ${h.projectDirs.join(", ")}${h.installed() ? " \xB7 detected" : ""}`)}`);
|
|
4303
4644
|
}
|
|
4304
4645
|
info(dim(`
|
|
4305
4646
|
New installs go to ${installDirs(enabled).join(" (real copy) + ")}${installDirs(enabled).length > 1 ? " (link)" : ""}.`));
|
|
@@ -4308,7 +4649,7 @@ function link(args) {
|
|
|
4308
4649
|
const roots = projectsFor(args);
|
|
4309
4650
|
for (const root of roots) {
|
|
4310
4651
|
const res = linkAll(root, { allowTracked: args.allowTracked });
|
|
4311
|
-
const label = args.all ? `${
|
|
4652
|
+
const label = args.all ? `${basename11(root)}: ` : "";
|
|
4312
4653
|
if (res.linked.length) success(`${label}linked ${res.linked.length} skill(s) for your other agents`);
|
|
4313
4654
|
else if (!args.all && !res.blocked.length && !res.uncommitted.length && !res.inTheWay.length) {
|
|
4314
4655
|
const missing = projectStatus(root).filter((s) => s.state === "folder missing").map((s) => s.name);
|
|
@@ -4354,11 +4695,11 @@ ${unreviewed} not reviewed. Keep one global on purpose: skilllib global keep <na
|
|
|
4354
4695
|
}
|
|
4355
4696
|
function repoRoot(repo) {
|
|
4356
4697
|
const looksLikePath = /[\\/]/.test(repo) || repo.startsWith(".") || repo.startsWith("~");
|
|
4357
|
-
const named = looksLikePath ? [] : visibleProjects().filter((p) =>
|
|
4698
|
+
const named = looksLikePath ? [] : visibleProjects().filter((p) => basename11(p) === repo);
|
|
4358
4699
|
if (named.length === 1) return named[0];
|
|
4359
4700
|
if (named.length > 1) throw new Error(`Several projects are named ${repo}: ${named.map(tildify).join(", ")}. Pass its path.`);
|
|
4360
4701
|
const path = expandHome(repo);
|
|
4361
|
-
if (!
|
|
4702
|
+
if (!existsSync15(path)) throw new Error(`No project ${repo} (pass its path, or see skilllib projects)`);
|
|
4362
4703
|
const root = projectHere(path);
|
|
4363
4704
|
if (root !== path) throw new Error(root ? `${tildify(path)} is inside ${tildify(root)}: pass the repo's top folder` : `${tildify(path)} isn't in a git repo`);
|
|
4364
4705
|
return root;
|
|
@@ -4380,22 +4721,22 @@ function moveGlobal(args, names, yours) {
|
|
|
4380
4721
|
moved,
|
|
4381
4722
|
...reason ? { reason } : {},
|
|
4382
4723
|
...left ? { left } : {},
|
|
4383
|
-
projects: changes.map(([root, c]) => ({ project:
|
|
4724
|
+
projects: changes.map(([root, c]) => ({ project: basename11(root), path: root, change: withDirs(root, [c])[0] }))
|
|
4384
4725
|
}))
|
|
4385
4726
|
);
|
|
4386
4727
|
}
|
|
4387
4728
|
for (const m of moves) {
|
|
4388
|
-
if (m.moved) success(`${m.name} now loads only in ${m.changes.map(([r]) =>
|
|
4729
|
+
if (m.moved) success(`${m.name} now loads only in ${m.changes.map(([r]) => basename11(r)).join(", ")} (the global copy is in skilllib restore)`);
|
|
4389
4730
|
else error(`${m.name}: ${m.reason}`);
|
|
4390
|
-
for (const [root, c] of m.changes) warnHeldLinks(root, c, `in ${
|
|
4731
|
+
for (const [root, c] of m.changes) warnHeldLinks(root, c, `in ${basename11(root)}`);
|
|
4391
4732
|
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
4733
|
}
|
|
4393
|
-
const toCommit = [...new Set(moves.filter((m) => m.moved).flatMap((m) => m.changes.
|
|
4394
|
-
if (toCommit.length) info(dim(`Commit skilllib.json and the skill folders in ${toCommit.map((r) =>
|
|
4734
|
+
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)))];
|
|
4735
|
+
if (toCommit.length) info(dim(`Commit skilllib.json and the skill folders in ${toCommit.map((r) => basename11(r)).join(", ")}.`));
|
|
4395
4736
|
}
|
|
4396
4737
|
function tidy(args) {
|
|
4397
4738
|
const names = args.positional;
|
|
4398
|
-
const keepFor = (base) => args.keep ? Object.fromEntries(names.map((n) => [n, base ?
|
|
4739
|
+
const keepFor = (base) => args.keep ? Object.fromEntries(names.map((n) => [n, base ? resolve9(base, args.keep) : expandHome(args.keep)])) : {};
|
|
4399
4740
|
if (args.keep && !names.length) {
|
|
4400
4741
|
throw new Error("Usage: skilllib tidy <name>... --keep <folder>");
|
|
4401
4742
|
}
|
|
@@ -4404,13 +4745,13 @@ function tidy(args) {
|
|
|
4404
4745
|
for (const name of names) {
|
|
4405
4746
|
const where = targets.map((root) => ({ root, ...foldersOf(root, name) })).filter((w) => w.all.length);
|
|
4406
4747
|
if (!where.length) {
|
|
4407
|
-
problems.push(`${name}: no skill by that name ${args.global ? "in your global folders" : args.all ? "in your projects" : `in ${
|
|
4748
|
+
problems.push(`${name}: no skill by that name ${args.global ? "in your global folders" : args.all ? "in your projects" : `in ${basename11(targets[0])}`}`);
|
|
4408
4749
|
continue;
|
|
4409
4750
|
}
|
|
4410
4751
|
if (!args.keep) continue;
|
|
4411
4752
|
for (const { root, real } of where) {
|
|
4412
4753
|
if (real.includes(keepFor(root)[name])) continue;
|
|
4413
|
-
const label = root ? `${
|
|
4754
|
+
const label = root ? `${basename11(root)} \xB7 ` : "";
|
|
4414
4755
|
problems.push(
|
|
4415
4756
|
`${label}${name}: no copy in ${args.keep}; ${real.length ? `pick one of: ${real.map((d) => folderLabel(root, d)).join(", ")}` : "it has no real copy to keep"}`
|
|
4416
4757
|
);
|
|
@@ -4420,7 +4761,7 @@ function tidy(args) {
|
|
|
4420
4761
|
throw new Error(problems.join("\n"));
|
|
4421
4762
|
}
|
|
4422
4763
|
const reports = targets.map(
|
|
4423
|
-
(root) => root === null ? { label: "global folders", report: planGlobalTidy({ keep: keepFor(null) }) } : { label:
|
|
4764
|
+
(root) => root === null ? { label: "global folders", report: planGlobalTidy({ keep: keepFor(null) }) } : { label: basename11(root), report: planProjectTidy(root, { keep: keepFor(root) }) }
|
|
4424
4765
|
);
|
|
4425
4766
|
let held = 0;
|
|
4426
4767
|
let changed = 0;
|
|
@@ -4452,7 +4793,7 @@ function tidy(args) {
|
|
|
4452
4793
|
}
|
|
4453
4794
|
if (args.dryRun) info(dim("\nNothing changed (--dry-run)."));
|
|
4454
4795
|
else if (changed) info(dim(`
|
|
4455
|
-
Replaced copies are in ${tildify(
|
|
4796
|
+
Replaced copies are in ${tildify(join16(skilllibHome(), "tidy-backup"))}; \`skilllib restore\` puts them back.`));
|
|
4456
4797
|
if (held) {
|
|
4457
4798
|
process.exitCode = 1;
|
|
4458
4799
|
info(dim(`${held} change${held === 1 ? "" : "s"} held back because git would see them. Re-run with --allow-git to make them.`));
|
|
@@ -4483,11 +4824,108 @@ function agentSkill(args) {
|
|
|
4483
4824
|
else if (state === "outdated") warn("The skilllib skill is out of date. Run `skilllib agent-skill install` to update it.");
|
|
4484
4825
|
else info("Your agents don't know about skilllib yet. Run `skilllib agent-skill install` so you can ask them about your skills.");
|
|
4485
4826
|
}
|
|
4827
|
+
function installsOf(n) {
|
|
4828
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
|
|
4829
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}K`;
|
|
4830
|
+
return String(n);
|
|
4831
|
+
}
|
|
4832
|
+
async function search(args) {
|
|
4833
|
+
const query = args.positional.join(" ").trim();
|
|
4834
|
+
if (!query) throw new Error("Usage: skilllib search <query>");
|
|
4835
|
+
const hits = await searchSkills(query);
|
|
4836
|
+
if (args.json) return json("search", hits);
|
|
4837
|
+
if (!hits.length) return info(`No skills on skills.sh for \u201C${query}\u201D.`);
|
|
4838
|
+
table(hits.map((h) => ({ Skill: h.name, Source: h.source, Installs: dim(installsOf(h.installs)) })));
|
|
4839
|
+
info(dim("\nInstall one: skilllib install <source> e.g. skilllib install owner/repo@skill"));
|
|
4840
|
+
}
|
|
4841
|
+
async function install(args) {
|
|
4842
|
+
if (!args.positional.length) {
|
|
4843
|
+
throw new Error("Usage: skilllib install <owner/repo[@skill] | url | folder>... [--global] [--all] [--force]\nSearch first: skilllib search <query>");
|
|
4844
|
+
}
|
|
4845
|
+
const results = [];
|
|
4846
|
+
for (const input of args.positional) {
|
|
4847
|
+
const spec = parseInstallSpec(input);
|
|
4848
|
+
if (!spec) {
|
|
4849
|
+
const hits = await searchSkills(input).catch(() => []);
|
|
4850
|
+
const exact = hits.filter((h) => h.skillId === input || h.name === input);
|
|
4851
|
+
const pick = exact.length === 1 ? exact[0] : hits.length === 1 ? hits[0] : void 0;
|
|
4852
|
+
if (!pick || !pick.source.includes("/")) {
|
|
4853
|
+
process.exitCode = 1;
|
|
4854
|
+
if (args.json) results.push({ name: input, source: input, status: "skipped", reason: hits.length ? `several matches: ${hits.slice(0, 8).map((h) => `${h.source}@${h.skillId}`).join(", ")}` : "not a GitHub repo, skills.sh URL, or folder" });
|
|
4855
|
+
else if (hits.length) {
|
|
4856
|
+
warn(`\u201C${input}\u201D matches several skills. Pass one of these:`);
|
|
4857
|
+
for (const h of hits.slice(0, 8)) info(` skilllib install ${h.source}@${h.skillId} ${dim(installsOf(h.installs))}`);
|
|
4858
|
+
} else error(`${input}: not a GitHub repo, skills.sh URL, or folder. Try skilllib search ${input}`);
|
|
4859
|
+
continue;
|
|
4860
|
+
}
|
|
4861
|
+
args.positional.push(`${pick.source}@${pick.skillId}`);
|
|
4862
|
+
continue;
|
|
4863
|
+
}
|
|
4864
|
+
let resolved;
|
|
4865
|
+
try {
|
|
4866
|
+
resolved = await resolveInstallDir(spec);
|
|
4867
|
+
} catch (err) {
|
|
4868
|
+
process.exitCode = 1;
|
|
4869
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
4870
|
+
if (args.json) results.push({ name: input, source: input, status: "skipped", reason });
|
|
4871
|
+
else error(reason);
|
|
4872
|
+
continue;
|
|
4873
|
+
}
|
|
4874
|
+
try {
|
|
4875
|
+
if (!resolved.dirs.length) {
|
|
4876
|
+
process.exitCode = 1;
|
|
4877
|
+
const reason = "skill" in spec && spec.skill ? `no skill named ${spec.skill} in ${spec.owner}/${spec.repo}` : "no SKILL.md found";
|
|
4878
|
+
if (args.json) results.push({ name: input, source: resolved.source, status: "skipped", reason });
|
|
4879
|
+
else error(`${resolved.source}: ${reason}`);
|
|
4880
|
+
continue;
|
|
4881
|
+
}
|
|
4882
|
+
if (resolved.many && !args.all) {
|
|
4883
|
+
process.exitCode = 1;
|
|
4884
|
+
const names = resolved.dirs.map((d) => basename11(d));
|
|
4885
|
+
const reason = `${names.length} skills. Pass @name, or --all. ${names.slice(0, 12).join(", ")}`;
|
|
4886
|
+
if (args.json) results.push({ name: input, source: resolved.source, status: "skipped", reason });
|
|
4887
|
+
else {
|
|
4888
|
+
warn(`${resolved.source} has ${names.length} skills. Install one, or all of them:`);
|
|
4889
|
+
const base = "owner" in spec ? `${spec.owner}/${spec.repo}` : resolved.source;
|
|
4890
|
+
for (const name of names.slice(0, 12)) info(` skilllib install ${base}@${name}`);
|
|
4891
|
+
if (names.length > 12) info(dim(` \u2026 and ${names.length - 12} more`));
|
|
4892
|
+
info(dim(` skilllib install ${base} --all`));
|
|
4893
|
+
}
|
|
4894
|
+
continue;
|
|
4895
|
+
}
|
|
4896
|
+
const installed = installFound(resolved.dirs, "owner" in spec ? `${spec.owner}/${spec.repo}` : resolved.source, {
|
|
4897
|
+
force: args.force,
|
|
4898
|
+
global: args.global,
|
|
4899
|
+
allowTracked: args.allowTracked
|
|
4900
|
+
});
|
|
4901
|
+
results.push(...installed);
|
|
4902
|
+
if (!args.json) {
|
|
4903
|
+
for (const row of installed) {
|
|
4904
|
+
if (row.status === "exists" || row.status === "skipped") {
|
|
4905
|
+
process.exitCode = 1;
|
|
4906
|
+
warn(`${row.name}: ${row.reason}`);
|
|
4907
|
+
} else {
|
|
4908
|
+
const where = row.global ? `global ${row.global.map((d) => tildify(d)).join(", ")}` : row.project ? `this project (${row.project.action})` : "your library";
|
|
4909
|
+
success(`${row.name} ${row.status === "unchanged" ? "already in your library" : "installed"} \u2192 ${where}`);
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
if (!args.global && !projectHere() && installed.some((r) => r.status !== "exists" && r.status !== "skipped")) {
|
|
4913
|
+
info(dim("Not in a project, so nothing was added here. In a repo: skilllib add <name>. Everywhere: skilllib install <source> --global"));
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
} finally {
|
|
4917
|
+
if (resolved.tmp) rmSync7(resolved.tmp, { recursive: true, force: true });
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
if (args.json) json("install", results);
|
|
4921
|
+
else if (results.some((r) => r.project && r.project.action !== "skipped")) info(dim("Commit skilllib.json and the skill folders."));
|
|
4922
|
+
}
|
|
4486
4923
|
|
|
4487
4924
|
export {
|
|
4488
4925
|
realPath,
|
|
4489
4926
|
skilllibHome,
|
|
4490
4927
|
PROJECT_SKILLS_DIR,
|
|
4928
|
+
FEATURED_HARNESSES,
|
|
4491
4929
|
HARNESSES,
|
|
4492
4930
|
harness,
|
|
4493
4931
|
installDirs,
|
|
@@ -4497,11 +4935,14 @@ export {
|
|
|
4497
4935
|
versionHistory,
|
|
4498
4936
|
forgetLatest,
|
|
4499
4937
|
latestVersion,
|
|
4938
|
+
versionForHash,
|
|
4500
4939
|
projectHere,
|
|
4501
4940
|
readManifest,
|
|
4941
|
+
personalPinsFile,
|
|
4502
4942
|
knownProjects,
|
|
4503
4943
|
rememberProjects,
|
|
4504
4944
|
readableRoots,
|
|
4945
|
+
pinsIn,
|
|
4505
4946
|
setJsonMode,
|
|
4506
4947
|
isJsonMode,
|
|
4507
4948
|
json,
|
|
@@ -4600,5 +5041,7 @@ export {
|
|
|
4600
5041
|
link,
|
|
4601
5042
|
globalCommand,
|
|
4602
5043
|
tidy,
|
|
4603
|
-
agentSkill
|
|
5044
|
+
agentSkill,
|
|
5045
|
+
search,
|
|
5046
|
+
install
|
|
4604
5047
|
};
|