skilllib 1.12.2 → 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-YQFLFWAY.js → chunk-C6TSR7NT.js} +513 -75
- package/dist/index.js +18 -6
- package/dist/json.d.cts +665 -2
- package/dist/json.d.ts +665 -2
- package/dist/{tui-P3HKRNHI.js → tui-3MBVYSVM.js} +24 -8
- package/package.json +6 -2
- package/skills/skilllib/SKILL.md +3 -3
|
@@ -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
|
|
@@ -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;
|
|
@@ -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));
|
|
@@ -2464,8 +2598,8 @@ function applyAll(plans, git2) {
|
|
|
2464
2598
|
}
|
|
2465
2599
|
function brokenLinksIn(root, opened) {
|
|
2466
2600
|
const where = basename7(root);
|
|
2467
|
-
const
|
|
2468
|
-
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")));
|
|
2469
2603
|
const mends = (project, dir, name) => {
|
|
2470
2604
|
const dep = pinsIn(project)[name];
|
|
2471
2605
|
return !!dep && (dep.links ?? []).includes(dir) && !syncBlocker(project, name, dep) && versionForHash(name, dep.hash) !== null;
|
|
@@ -2485,7 +2619,7 @@ function brokenLinksIn(root, opened) {
|
|
|
2485
2619
|
const seen = /* @__PURE__ */ new Set();
|
|
2486
2620
|
const aliased = (dir) => (realNative(join13(root, dir)) ?? "") !== join13(realRoot, dir);
|
|
2487
2621
|
const packageFolders = packageSkillFolders(root).map((f) => ({ dir: f.location, top: false })).sort((a, b) => Number(aliased(a.dir)) - Number(aliased(b.dir)));
|
|
2488
|
-
const folders2 = [...
|
|
2622
|
+
const folders2 = [...projectDirsInUse(enabledHarnesses()).map((dir) => ({ dir, top: true })), ...packageFolders].flatMap(({ dir, top }) => {
|
|
2489
2623
|
const real = realNative(join13(root, dir)) ?? join13(root, dir);
|
|
2490
2624
|
if (seen.has(real)) return [];
|
|
2491
2625
|
seen.add(real);
|
|
@@ -2867,7 +3001,7 @@ function findIssues(roots, machine, libraryNames, statusOf = projectStatus, load
|
|
|
2867
3001
|
}
|
|
2868
3002
|
const agentSkill2 = agentSkillState();
|
|
2869
3003
|
if (agentSkill2 !== "installed") {
|
|
2870
|
-
const
|
|
3004
|
+
const install2 = {
|
|
2871
3005
|
label: agentSkill2 === "missing" ? "Install the skilllib skill for your agents" : "Update the skilllib skill",
|
|
2872
3006
|
hint: "one global skill, in ~/.claude/skills (and ~/.agents/skills)",
|
|
2873
3007
|
run: () => {
|
|
@@ -2882,7 +3016,7 @@ function findIssues(roots, machine, libraryNames, statusOf = projectStatus, load
|
|
|
2882
3016
|
title: agentSkill2 === "missing" ? "Your agents don't know about skilllib" : "The skilllib skill for your agents is out of date",
|
|
2883
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.",
|
|
2884
3018
|
at: rows(null, ["skilllib"]),
|
|
2885
|
-
...agentSkill2 === "missing" ? { choices: [
|
|
3019
|
+
...agentSkill2 === "missing" ? { choices: [install2] } : { fix: install2 }
|
|
2886
3020
|
});
|
|
2887
3021
|
}
|
|
2888
3022
|
return issues;
|
|
@@ -2959,7 +3093,7 @@ function repoIssues(root, ctx) {
|
|
|
2959
3093
|
if (drifted.length) {
|
|
2960
3094
|
const seen = drifted.some((s) => isShared(gitOf(root)?.of(relativeTo(root, s.path))));
|
|
2961
3095
|
const yours = drifted.some((s) => untrackable(s));
|
|
2962
|
-
const
|
|
3096
|
+
const install2 = {
|
|
2963
3097
|
label: "Install the pinned versions",
|
|
2964
3098
|
hint: seen ? "git will see the copies change" : "your library keeps every version",
|
|
2965
3099
|
run: () => summarize(where, "synced", syncProject(root, { names: drifted.map((s) => s.name) }))
|
|
@@ -2970,7 +3104,7 @@ function repoIssues(root, ctx) {
|
|
|
2970
3104
|
title: `${where}: ${plural(drifted.length, "skill")} not at the pinned version`,
|
|
2971
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.` : ""}`,
|
|
2972
3106
|
at: rows(root, drifted.map((s) => s.name)),
|
|
2973
|
-
...seen || yours ? { choices: [
|
|
3107
|
+
...seen || yours ? { choices: [install2] } : { fix: install2 }
|
|
2974
3108
|
});
|
|
2975
3109
|
}
|
|
2976
3110
|
for (const s of unstuck.filter((s2) => s2.state === "folder missing" && s2.latest === null))
|
|
@@ -3625,12 +3759,217 @@ function usesByProject(uses, projectOf) {
|
|
|
3625
3759
|
return byProject;
|
|
3626
3760
|
}
|
|
3627
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
|
+
|
|
3628
3967
|
// src/move.ts
|
|
3629
|
-
import { basename as
|
|
3968
|
+
import { basename as basename10 } from "path";
|
|
3630
3969
|
function moveGlobalSkills(skills, roots, {
|
|
3631
3970
|
group,
|
|
3632
3971
|
allowTracked = false,
|
|
3633
|
-
label =
|
|
3972
|
+
label = basename10,
|
|
3634
3973
|
allowTrackedHint = "re-run with --allow-tracked"
|
|
3635
3974
|
} = {}) {
|
|
3636
3975
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -3749,9 +4088,9 @@ function projectOfFactory(projects2) {
|
|
|
3749
4088
|
const sorted2 = [...projects2].sort((a, b) => b.length - a.length);
|
|
3750
4089
|
return (cwd) => {
|
|
3751
4090
|
if (!cwd) return null;
|
|
3752
|
-
const known = sorted2.find((p) => cwd === p || cwd.startsWith(p +
|
|
4091
|
+
const known = sorted2.find((p) => cwd === p || cwd.startsWith(p + sep10));
|
|
3753
4092
|
if (known) return known;
|
|
3754
|
-
const repo = cwd.split(`${
|
|
4093
|
+
const repo = cwd.split(`${sep10}.claude${sep10}worktrees${sep10}`)[0];
|
|
3755
4094
|
return isProjectCandidate(repo) ? repo : null;
|
|
3756
4095
|
};
|
|
3757
4096
|
}
|
|
@@ -3765,7 +4104,7 @@ var projectsFor = (args) => {
|
|
|
3765
4104
|
if (!args.all) return [requireProject()];
|
|
3766
4105
|
const { readable, unreadable } = readableRoots(visibleProjects());
|
|
3767
4106
|
for (const u of unreadable) {
|
|
3768
|
-
error(`${
|
|
4107
|
+
error(`${basename11(u.root)}: ${u.reason}`);
|
|
3769
4108
|
process.exitCode = 1;
|
|
3770
4109
|
}
|
|
3771
4110
|
return readable;
|
|
@@ -3777,7 +4116,7 @@ function requireProject() {
|
|
|
3777
4116
|
if (!isProjectCandidate(findProjectRoot())) {
|
|
3778
4117
|
const top = findProjectRoot();
|
|
3779
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.`);
|
|
3780
|
-
const stray =
|
|
4119
|
+
const stray = existsSync15(join16(userHome(), MANIFEST_FILE)) ? `, though ${tildify(join16(userHome(), MANIFEST_FILE))} makes it look like one` : "";
|
|
3781
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.`);
|
|
3782
4121
|
}
|
|
3783
4122
|
throw new Error("Not in a git repo. Run this inside a project (or `git init` first).");
|
|
@@ -3799,7 +4138,7 @@ async function status(args) {
|
|
|
3799
4138
|
const root = requireProject();
|
|
3800
4139
|
const skills = projectStatus(root);
|
|
3801
4140
|
const nested = nestedSkills(root);
|
|
3802
|
-
info(`${
|
|
4141
|
+
info(`${basename11(root)} ${dim(tildify(root))}
|
|
3803
4142
|
`);
|
|
3804
4143
|
if (skills.length === 0 && nested.length === 0) info("No skills in this project yet. Add one from your library with: skilllib add <name>");
|
|
3805
4144
|
const usage2 = await usageBySkill(args.days, root);
|
|
@@ -3872,7 +4211,7 @@ async function list(args) {
|
|
|
3872
4211
|
const installs = /* @__PURE__ */ new Map();
|
|
3873
4212
|
for (const project of visibleProjects()) {
|
|
3874
4213
|
for (const name of Object.keys(pinsIn(project))) {
|
|
3875
|
-
installs.set(name, [...installs.get(name) ?? [],
|
|
4214
|
+
installs.set(name, [...installs.get(name) ?? [], basename11(project)]);
|
|
3876
4215
|
}
|
|
3877
4216
|
}
|
|
3878
4217
|
const usage2 = await usageBySkill(args.days);
|
|
@@ -3915,7 +4254,7 @@ function teamStateIn(root, extraDirs = []) {
|
|
|
3915
4254
|
function projects(args) {
|
|
3916
4255
|
const { readable: roots, unreadable } = readableRoots(visibleProjects());
|
|
3917
4256
|
for (const u of unreadable) {
|
|
3918
|
-
error(`${
|
|
4257
|
+
error(`${basename11(u.root)}: ${u.reason}`);
|
|
3919
4258
|
process.exitCode = 1;
|
|
3920
4259
|
}
|
|
3921
4260
|
if (args.json) {
|
|
@@ -3924,7 +4263,7 @@ function projects(args) {
|
|
|
3924
4263
|
roots.map((root) => {
|
|
3925
4264
|
const teamState = teamStateIn(root);
|
|
3926
4265
|
return {
|
|
3927
|
-
name:
|
|
4266
|
+
name: basename11(root),
|
|
3928
4267
|
path: root,
|
|
3929
4268
|
skills: projectStatus(root).map((s) => ({ name: s.name, managed: s.managed, state: teamState(s), version: s.version, latest: s.latest }))
|
|
3930
4269
|
};
|
|
@@ -3942,7 +4281,7 @@ function projects(args) {
|
|
|
3942
4281
|
const usual = /* @__PURE__ */ new Set(["ok", "local only", "repo skill", "repo skill, in library", "from npx skills"]);
|
|
3943
4282
|
const attention = skills.filter((s) => !usual.has(teamState(s))).length;
|
|
3944
4283
|
return {
|
|
3945
|
-
Project:
|
|
4284
|
+
Project: basename11(root),
|
|
3946
4285
|
Path: dim(tildify(root)),
|
|
3947
4286
|
Skills: skills.map((s) => s.name).join(", ") || dim("none"),
|
|
3948
4287
|
"Local only": String(skills.filter((s) => teamState(s) === "local only").length),
|
|
@@ -3977,11 +4316,11 @@ function changeEach(args, change, nothing) {
|
|
|
3977
4316
|
const changes = change(root);
|
|
3978
4317
|
if (changes.some(incomplete)) process.exitCode = 1;
|
|
3979
4318
|
if (!args.json) {
|
|
3980
|
-
if (args.all) info(`${
|
|
4319
|
+
if (args.all) info(`${basename11(root)} ${dim(tildify(root))}`);
|
|
3981
4320
|
if (changes.length === 0) nothing(root);
|
|
3982
4321
|
else printChanges(root, changes, args);
|
|
3983
4322
|
}
|
|
3984
|
-
return { project:
|
|
4323
|
+
return { project: basename11(root), path: root, changes: withDirs(root, changes) };
|
|
3985
4324
|
});
|
|
3986
4325
|
if (args.json) args.all ? json("changes --all", results) : json("changes", results[0].changes);
|
|
3987
4326
|
return roots;
|
|
@@ -4012,7 +4351,7 @@ function update(args) {
|
|
|
4012
4351
|
function outdated(args) {
|
|
4013
4352
|
const roots = projectsFor(args);
|
|
4014
4353
|
const behind = roots.flatMap(
|
|
4015
|
-
(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 }))
|
|
4016
4355
|
);
|
|
4017
4356
|
if (args.json) return json("outdated", behind);
|
|
4018
4357
|
const rows2 = behind.map((b) => ({ Project: b.project, Skill: b.skill, Installed: `v${b.installed}`, Latest: `v${b.latest}`, Status: colorState(b.state) }));
|
|
@@ -4023,7 +4362,7 @@ function outdated(args) {
|
|
|
4023
4362
|
}
|
|
4024
4363
|
function importSkills(args) {
|
|
4025
4364
|
if (args.global && args.positional.length) throw new Error("skilllib import --global takes no folders: it imports all your global skills");
|
|
4026
|
-
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));
|
|
4027
4366
|
if (dirs.length === 0) {
|
|
4028
4367
|
throw new Error("Usage: skilllib import <skill-dir>... [--force] | skilllib import --global");
|
|
4029
4368
|
}
|
|
@@ -4043,11 +4382,11 @@ function importSkills(args) {
|
|
|
4043
4382
|
continue;
|
|
4044
4383
|
}
|
|
4045
4384
|
success(`${name} ${dim(result === "unchanged" ? "already in library" : result === "added" ? "added to library" : "library updated")}`);
|
|
4046
|
-
const root =
|
|
4385
|
+
const root = resolve9(dir, "..", "..", "..");
|
|
4047
4386
|
const dep = projectHere(root) === root ? readManifest(root).skills[name] : void 0;
|
|
4048
|
-
if (dep && realpathOrNull(
|
|
4387
|
+
if (dep && realpathOrNull(join16(root, dep.dir ?? PROJECT_SKILLS_DIR, name)) === realpathOrNull(dir) && dep.hash !== treeHash(dir)) {
|
|
4049
4388
|
const change = addSkill(root, name);
|
|
4050
|
-
if (change.action !== "skipped") info(dim(` ${
|
|
4389
|
+
if (change.action !== "skipped") info(dim(` ${basename11(root)} now depends on ${name} v${change.to}`));
|
|
4051
4390
|
}
|
|
4052
4391
|
}
|
|
4053
4392
|
if (args.json) json("import", results);
|
|
@@ -4063,14 +4402,14 @@ function scan(args) {
|
|
|
4063
4402
|
const library = new Set(librarySkills().map((s) => s.name));
|
|
4064
4403
|
const skillNames = (root) => [
|
|
4065
4404
|
// Not a folder that links out of the repo: what's in it isn't the repo's (as in status).
|
|
4066
|
-
.../* @__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)])
|
|
4067
4406
|
];
|
|
4068
4407
|
const withSkills = found.filter((root) => skillNames(root).length > 0);
|
|
4069
4408
|
info(`Scanned ${roots.map((r) => tildify(r)).join(", ")}: ${found.length} project${found.length === 1 ? "" : "s"}, ${withSkills.length} with skills
|
|
4070
4409
|
`);
|
|
4071
4410
|
table(
|
|
4072
4411
|
withSkills.map((root) => ({
|
|
4073
|
-
Project:
|
|
4412
|
+
Project: basename11(root),
|
|
4074
4413
|
Path: dim(tildify(root)),
|
|
4075
4414
|
Skills: skillNames(root).map((n) => library.has(n) ? n : yellow(n)).join(", ")
|
|
4076
4415
|
}))
|
|
@@ -4127,7 +4466,7 @@ function newSkill(args) {
|
|
|
4127
4466
|
if (!r.ok) {
|
|
4128
4467
|
throw new Error(`Can't create ${name}: ${r.reason}`);
|
|
4129
4468
|
}
|
|
4130
|
-
success(`Created ${tildify(
|
|
4469
|
+
success(`Created ${tildify(join16(r.dir, "SKILL.md"))}`);
|
|
4131
4470
|
}
|
|
4132
4471
|
function restore(args) {
|
|
4133
4472
|
const backups = [...listBackups(), ...pluginBackups()];
|
|
@@ -4240,7 +4579,7 @@ async function usage(args) {
|
|
|
4240
4579
|
return json("usage", {
|
|
4241
4580
|
usageDays: args.days,
|
|
4242
4581
|
source: "Claude Code transcripts",
|
|
4243
|
-
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) })),
|
|
4244
4583
|
unusedLibrarySkills: unused
|
|
4245
4584
|
});
|
|
4246
4585
|
}
|
|
@@ -4248,13 +4587,13 @@ async function usage(args) {
|
|
|
4248
4587
|
info(`No skill usage in Claude Code transcripts from the last ${args.days} days.`);
|
|
4249
4588
|
return;
|
|
4250
4589
|
}
|
|
4251
|
-
info(`Skill usage, last ${args.days} days ${dim(`(from ${tildify(
|
|
4590
|
+
info(`Skill usage, last ${args.days} days ${dim(`(from ${tildify(join16(claudeDir(), "projects"))})`)}
|
|
4252
4591
|
`);
|
|
4253
4592
|
table(
|
|
4254
4593
|
summaries.map((s) => ({
|
|
4255
4594
|
Skill: s.skill,
|
|
4256
4595
|
Uses: String(s.uses),
|
|
4257
|
-
Projects: [...s.projects].map((p) =>
|
|
4596
|
+
Projects: [...s.projects].map((p) => basename11(p)).join(", ") || dim("-"),
|
|
4258
4597
|
"Last used": lastUsed(s),
|
|
4259
4598
|
Library: library.has(s.skill) ? green("yes") : dim("no")
|
|
4260
4599
|
}))
|
|
@@ -4266,7 +4605,7 @@ async function usage(args) {
|
|
|
4266
4605
|
}
|
|
4267
4606
|
function show(args) {
|
|
4268
4607
|
const name = args.positional[0];
|
|
4269
|
-
const dir = name ?
|
|
4608
|
+
const dir = name ? join16(libraryDir(), name) : "";
|
|
4270
4609
|
if (!name || !isSkillDir(dir)) {
|
|
4271
4610
|
throw new Error(name ? `${name} is not in the library` : "Usage: skilllib show <skill-name>");
|
|
4272
4611
|
}
|
|
@@ -4278,8 +4617,8 @@ function show(args) {
|
|
|
4278
4617
|
path: dir,
|
|
4279
4618
|
latest: latestVersion(name)?.version ?? null,
|
|
4280
4619
|
origin: libraryOrigins()[name] ?? "",
|
|
4281
|
-
projects: projectsUsing(name).map((p) =>
|
|
4282
|
-
skillMd: readFileSync7(
|
|
4620
|
+
projects: projectsUsing(name).map((p) => basename11(p)),
|
|
4621
|
+
skillMd: readFileSync7(join16(dir, "SKILL.md"), "utf-8")
|
|
4283
4622
|
});
|
|
4284
4623
|
}
|
|
4285
4624
|
info(`${name} ${dim(tildify(dir))}`);
|
|
@@ -4301,7 +4640,7 @@ function harnessesCommand(args) {
|
|
|
4301
4640
|
}
|
|
4302
4641
|
const enabled = enabledHarnesses();
|
|
4303
4642
|
for (const h of HARNESSES) {
|
|
4304
|
-
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" : ""}`)}`);
|
|
4305
4644
|
}
|
|
4306
4645
|
info(dim(`
|
|
4307
4646
|
New installs go to ${installDirs(enabled).join(" (real copy) + ")}${installDirs(enabled).length > 1 ? " (link)" : ""}.`));
|
|
@@ -4310,7 +4649,7 @@ function link(args) {
|
|
|
4310
4649
|
const roots = projectsFor(args);
|
|
4311
4650
|
for (const root of roots) {
|
|
4312
4651
|
const res = linkAll(root, { allowTracked: args.allowTracked });
|
|
4313
|
-
const label = args.all ? `${
|
|
4652
|
+
const label = args.all ? `${basename11(root)}: ` : "";
|
|
4314
4653
|
if (res.linked.length) success(`${label}linked ${res.linked.length} skill(s) for your other agents`);
|
|
4315
4654
|
else if (!args.all && !res.blocked.length && !res.uncommitted.length && !res.inTheWay.length) {
|
|
4316
4655
|
const missing = projectStatus(root).filter((s) => s.state === "folder missing").map((s) => s.name);
|
|
@@ -4356,11 +4695,11 @@ ${unreviewed} not reviewed. Keep one global on purpose: skilllib global keep <na
|
|
|
4356
4695
|
}
|
|
4357
4696
|
function repoRoot(repo) {
|
|
4358
4697
|
const looksLikePath = /[\\/]/.test(repo) || repo.startsWith(".") || repo.startsWith("~");
|
|
4359
|
-
const named = looksLikePath ? [] : visibleProjects().filter((p) =>
|
|
4698
|
+
const named = looksLikePath ? [] : visibleProjects().filter((p) => basename11(p) === repo);
|
|
4360
4699
|
if (named.length === 1) return named[0];
|
|
4361
4700
|
if (named.length > 1) throw new Error(`Several projects are named ${repo}: ${named.map(tildify).join(", ")}. Pass its path.`);
|
|
4362
4701
|
const path = expandHome(repo);
|
|
4363
|
-
if (!
|
|
4702
|
+
if (!existsSync15(path)) throw new Error(`No project ${repo} (pass its path, or see skilllib projects)`);
|
|
4364
4703
|
const root = projectHere(path);
|
|
4365
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`);
|
|
4366
4705
|
return root;
|
|
@@ -4382,22 +4721,22 @@ function moveGlobal(args, names, yours) {
|
|
|
4382
4721
|
moved,
|
|
4383
4722
|
...reason ? { reason } : {},
|
|
4384
4723
|
...left ? { left } : {},
|
|
4385
|
-
projects: changes.map(([root, c]) => ({ project:
|
|
4724
|
+
projects: changes.map(([root, c]) => ({ project: basename11(root), path: root, change: withDirs(root, [c])[0] }))
|
|
4386
4725
|
}))
|
|
4387
4726
|
);
|
|
4388
4727
|
}
|
|
4389
4728
|
for (const m of moves) {
|
|
4390
|
-
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)`);
|
|
4391
4730
|
else error(`${m.name}: ${m.reason}`);
|
|
4392
|
-
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)}`);
|
|
4393
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"}`);
|
|
4394
4733
|
}
|
|
4395
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)))];
|
|
4396
|
-
if (toCommit.length) info(dim(`Commit skilllib.json and the skill folders in ${toCommit.map((r) =>
|
|
4735
|
+
if (toCommit.length) info(dim(`Commit skilllib.json and the skill folders in ${toCommit.map((r) => basename11(r)).join(", ")}.`));
|
|
4397
4736
|
}
|
|
4398
4737
|
function tidy(args) {
|
|
4399
4738
|
const names = args.positional;
|
|
4400
|
-
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)])) : {};
|
|
4401
4740
|
if (args.keep && !names.length) {
|
|
4402
4741
|
throw new Error("Usage: skilllib tidy <name>... --keep <folder>");
|
|
4403
4742
|
}
|
|
@@ -4406,13 +4745,13 @@ function tidy(args) {
|
|
|
4406
4745
|
for (const name of names) {
|
|
4407
4746
|
const where = targets.map((root) => ({ root, ...foldersOf(root, name) })).filter((w) => w.all.length);
|
|
4408
4747
|
if (!where.length) {
|
|
4409
|
-
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])}`}`);
|
|
4410
4749
|
continue;
|
|
4411
4750
|
}
|
|
4412
4751
|
if (!args.keep) continue;
|
|
4413
4752
|
for (const { root, real } of where) {
|
|
4414
4753
|
if (real.includes(keepFor(root)[name])) continue;
|
|
4415
|
-
const label = root ? `${
|
|
4754
|
+
const label = root ? `${basename11(root)} \xB7 ` : "";
|
|
4416
4755
|
problems.push(
|
|
4417
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"}`
|
|
4418
4757
|
);
|
|
@@ -4422,7 +4761,7 @@ function tidy(args) {
|
|
|
4422
4761
|
throw new Error(problems.join("\n"));
|
|
4423
4762
|
}
|
|
4424
4763
|
const reports = targets.map(
|
|
4425
|
-
(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) }) }
|
|
4426
4765
|
);
|
|
4427
4766
|
let held = 0;
|
|
4428
4767
|
let changed = 0;
|
|
@@ -4454,7 +4793,7 @@ function tidy(args) {
|
|
|
4454
4793
|
}
|
|
4455
4794
|
if (args.dryRun) info(dim("\nNothing changed (--dry-run)."));
|
|
4456
4795
|
else if (changed) info(dim(`
|
|
4457
|
-
Replaced copies are in ${tildify(
|
|
4796
|
+
Replaced copies are in ${tildify(join16(skilllibHome(), "tidy-backup"))}; \`skilllib restore\` puts them back.`));
|
|
4458
4797
|
if (held) {
|
|
4459
4798
|
process.exitCode = 1;
|
|
4460
4799
|
info(dim(`${held} change${held === 1 ? "" : "s"} held back because git would see them. Re-run with --allow-git to make them.`));
|
|
@@ -4485,11 +4824,108 @@ function agentSkill(args) {
|
|
|
4485
4824
|
else if (state === "outdated") warn("The skilllib skill is out of date. Run `skilllib agent-skill install` to update it.");
|
|
4486
4825
|
else info("Your agents don't know about skilllib yet. Run `skilllib agent-skill install` so you can ask them about your skills.");
|
|
4487
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
|
+
}
|
|
4488
4923
|
|
|
4489
4924
|
export {
|
|
4490
4925
|
realPath,
|
|
4491
4926
|
skilllibHome,
|
|
4492
4927
|
PROJECT_SKILLS_DIR,
|
|
4928
|
+
FEATURED_HARNESSES,
|
|
4493
4929
|
HARNESSES,
|
|
4494
4930
|
harness,
|
|
4495
4931
|
installDirs,
|
|
@@ -4605,5 +5041,7 @@ export {
|
|
|
4605
5041
|
link,
|
|
4606
5042
|
globalCommand,
|
|
4607
5043
|
tidy,
|
|
4608
|
-
agentSkill
|
|
5044
|
+
agentSkill,
|
|
5045
|
+
search,
|
|
5046
|
+
install
|
|
4609
5047
|
};
|