skills 1.5.18 → 1.5.20
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 +5 -1
- package/dist/_chunks/libs/@clack/core.mjs +1 -55
- package/dist/_chunks/libs/@clack/prompts.mjs +2 -96
- package/dist/cli.mjs +389 -129
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The CLI for the open agent skills ecosystem.
|
|
4
4
|
|
|
5
5
|
<!-- agent-list:start -->
|
|
6
|
-
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [
|
|
6
|
+
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [70 more](#supported-agents).
|
|
7
7
|
<!-- agent-list:end -->
|
|
8
8
|
|
|
9
9
|
[](https://skills.sh/vercel-labs/skills)
|
|
@@ -272,12 +272,14 @@ Skills can be installed to any of these agents:
|
|
|
272
272
|
| Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
|
|
273
273
|
| GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
|
|
274
274
|
| Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
|
|
275
|
+
| Grok Build | `grok` | `.grok/skills/` | `~/.grok/skills/` |
|
|
275
276
|
| Hermes Agent | `hermes-agent` | `.hermes/skills/` | `~/.hermes/skills/` |
|
|
276
277
|
| inference.sh | `inference-sh` | `.inferencesh/skills/` | `~/.inferencesh/skills/` |
|
|
277
278
|
| Jazz | `jazz` | `.jazz/skills/` | `~/.jazz/skills/` |
|
|
278
279
|
| Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
|
|
279
280
|
| iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
|
|
280
281
|
| Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` |
|
|
282
|
+
| Kimchi | `kimchi` | `.kimchi/skills/` | `~/.config/kimchi/harness/skills/` |
|
|
281
283
|
| Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` |
|
|
282
284
|
| Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
|
|
283
285
|
| Lingma | `lingma` | `.lingma/skills/` | `~/.lingma/skills/` |
|
|
@@ -402,12 +404,14 @@ to also discover `SKILL.md` files outside these container directories
|
|
|
402
404
|
- `agent/skills/`
|
|
403
405
|
- `.forge/skills/`
|
|
404
406
|
- `.goose/skills/`
|
|
407
|
+
- `.grok/skills/`
|
|
405
408
|
- `.hermes/skills/`
|
|
406
409
|
- `.inferencesh/skills/`
|
|
407
410
|
- `.jazz/skills/`
|
|
408
411
|
- `.junie/skills/`
|
|
409
412
|
- `.iflow/skills/`
|
|
410
413
|
- `.kilocode/skills/`
|
|
414
|
+
- `.kimchi/skills/`
|
|
411
415
|
- `.kiro/skills/`
|
|
412
416
|
- `.kode/skills/`
|
|
413
417
|
- `.lingma/skills/`
|
|
@@ -627,60 +627,6 @@ var r = class extends V {
|
|
|
627
627
|
});
|
|
628
628
|
}
|
|
629
629
|
};
|
|
630
|
-
let u$2 = class u extends V {
|
|
631
|
-
options;
|
|
632
|
-
cursor = 0;
|
|
633
|
-
#t;
|
|
634
|
-
getGroupItems(t) {
|
|
635
|
-
return this.options.filter((r) => r.group === t);
|
|
636
|
-
}
|
|
637
|
-
isGroupSelected(t) {
|
|
638
|
-
const r = this.getGroupItems(t), e = this.value;
|
|
639
|
-
return e === void 0 ? false : r.every((s) => e.includes(s.value));
|
|
640
|
-
}
|
|
641
|
-
toggleValue() {
|
|
642
|
-
const t = this.options[this.cursor];
|
|
643
|
-
if (t !== void 0) if (this.value === void 0 && (this.value = []), t.group === true) {
|
|
644
|
-
const r = t.value, e = this.getGroupItems(r);
|
|
645
|
-
this.isGroupSelected(r) ? this.value = this.value.filter((s) => e.findIndex((i) => i.value === s) === -1) : this.value = [...this.value, ...e.map((s) => s.value)], this.value = Array.from(new Set(this.value));
|
|
646
|
-
} else {
|
|
647
|
-
const r = this.value.includes(t.value);
|
|
648
|
-
this.value = r ? this.value.filter((e) => e !== t.value) : [...this.value, t.value];
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
constructor(t) {
|
|
652
|
-
super(t, false);
|
|
653
|
-
const { options: r } = t;
|
|
654
|
-
this.#t = t.selectableGroups !== false, this.options = Object.entries(r).flatMap(([e, s]) => [{
|
|
655
|
-
value: e,
|
|
656
|
-
group: true,
|
|
657
|
-
label: e
|
|
658
|
-
}, ...s.map((i) => ({
|
|
659
|
-
...i,
|
|
660
|
-
group: e
|
|
661
|
-
}))]), this.value = [...t.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: e }) => e === t.cursorAt), this.#t ? 0 : 1), this.on("cursor", (e) => {
|
|
662
|
-
switch (e) {
|
|
663
|
-
case "left":
|
|
664
|
-
case "up": {
|
|
665
|
-
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
666
|
-
const s = this.options[this.cursor]?.group === true;
|
|
667
|
-
!this.#t && s && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
|
|
668
|
-
break;
|
|
669
|
-
}
|
|
670
|
-
case "down":
|
|
671
|
-
case "right": {
|
|
672
|
-
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
673
|
-
const s = this.options[this.cursor]?.group === true;
|
|
674
|
-
!this.#t && s && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
|
|
675
|
-
break;
|
|
676
|
-
}
|
|
677
|
-
case "space":
|
|
678
|
-
this.toggleValue();
|
|
679
|
-
break;
|
|
680
|
-
}
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
630
|
var a = class extends V {
|
|
685
631
|
options;
|
|
686
632
|
cursor = 0;
|
|
@@ -755,4 +701,4 @@ let n$1 = class n extends V {
|
|
|
755
701
|
});
|
|
756
702
|
}
|
|
757
703
|
};
|
|
758
|
-
export { a, block, fastStringWidth, getColumns, getRows, isCancel, n$1, r, require_src, settings,
|
|
704
|
+
export { a, block, fastStringWidth, getColumns, getRows, isCancel, n$1, r, require_src, settings, wrapAnsi, wrapTextWithPrefix };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a, block, fastStringWidth, getColumns, getRows, n$1, r, require_src, settings,
|
|
1
|
+
import { a, block, fastStringWidth, getColumns, getRows, n$1, r, require_src, settings, wrapAnsi, wrapTextWithPrefix } from "./core.mjs";
|
|
2
2
|
import { styleText } from "node:util";
|
|
3
3
|
import process$1 from "node:process";
|
|
4
4
|
var import_src = require_src();
|
|
@@ -213,100 +213,6 @@ ${h}
|
|
|
213
213
|
}
|
|
214
214
|
}).prompt();
|
|
215
215
|
};
|
|
216
|
-
const groupMultiselect = (o) => {
|
|
217
|
-
const { selectableGroups: h = true, groupSpacing: x = 0 } = o, m = (n, l, g = []) => {
|
|
218
|
-
const a = n.label ?? String(n.value), t = typeof n.group == "string", s = t && (g[g.indexOf(n) + 1] ?? { group: true }), u = t && s && s.group === true;
|
|
219
|
-
let r = "", c = "";
|
|
220
|
-
t && (h ? (r = u ? `${S_BAR_END} ` : `${S_BAR} `, c = u ? " " : `${S_BAR} `) : r = " ");
|
|
221
|
-
let i = "";
|
|
222
|
-
if (x > 0 && !t && (i = `
|
|
223
|
-
`.repeat(x)), l === "active") return wrapTextWithPrefix(o.output, `${a}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`, `${i}${styleText("dim", r)} `, `${i}${styleText("dim", r)}${styleText("cyan", S_CHECKBOX_ACTIVE)} `, `${i}${styleText("dim", c)} `);
|
|
224
|
-
if (l === "group-active") return wrapTextWithPrefix(o.output, a, `${i}${r} `, `${i}${r}${styleText("cyan", S_CHECKBOX_ACTIVE)} `, `${i}${c} `, (d) => styleText("dim", d));
|
|
225
|
-
if (l === "group-active-selected") return wrapTextWithPrefix(o.output, a, `${i}${r} `, `${i}${r}${styleText("green", S_CHECKBOX_SELECTED)} `, `${i}${c} `, (d) => styleText("dim", d));
|
|
226
|
-
if (l === "selected") {
|
|
227
|
-
const d = t || h ? styleText("green", S_CHECKBOX_SELECTED) : "";
|
|
228
|
-
return wrapTextWithPrefix(o.output, `${a}${n.hint ? ` (${n.hint})` : ""}`, `${i}${styleText("dim", r)} `, `${i}${styleText("dim", r)}${d} `, `${i}${styleText("dim", c)} `, (S) => styleText("dim", S));
|
|
229
|
-
}
|
|
230
|
-
if (l === "cancelled") return `${styleText(["strikethrough", "dim"], a)}`;
|
|
231
|
-
if (l === "active-selected") return wrapTextWithPrefix(o.output, `${a}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`, `${i}${styleText("dim", r)} `, `${i}${styleText("dim", r)}${styleText("green", S_CHECKBOX_SELECTED)} `, `${i}${styleText("dim", c)} `);
|
|
232
|
-
if (l === "submitted") return `${styleText("dim", a)}`;
|
|
233
|
-
const f = t || h ? styleText("dim", S_CHECKBOX_INACTIVE) : "";
|
|
234
|
-
return wrapTextWithPrefix(o.output, a, `${i}${styleText("dim", r)} `, `${i}${styleText("dim", r)}${f} `, `${i}${styleText("dim", c)} `, (d) => styleText("dim", d));
|
|
235
|
-
}, y = o.required ?? true, I = o.showInstructions ?? true;
|
|
236
|
-
return new u$2({
|
|
237
|
-
options: o.options,
|
|
238
|
-
signal: o.signal,
|
|
239
|
-
input: o.input,
|
|
240
|
-
output: o.output,
|
|
241
|
-
initialValues: o.initialValues,
|
|
242
|
-
required: y,
|
|
243
|
-
cursorAt: o.cursorAt,
|
|
244
|
-
selectableGroups: h,
|
|
245
|
-
validate(n) {
|
|
246
|
-
if (y && (n === void 0 || n.length === 0)) return `Please select at least one option.
|
|
247
|
-
${styleText("reset", styleText("dim", `Press ${styleText([
|
|
248
|
-
"gray",
|
|
249
|
-
"bgWhite",
|
|
250
|
-
"inverse"
|
|
251
|
-
], " space ")} to select, ${styleText("gray", styleText(["bgWhite", "inverse"], " enter "))} to submit`))}`;
|
|
252
|
-
},
|
|
253
|
-
render() {
|
|
254
|
-
const n = o.withGuide ?? settings.withGuide, l = `${n ? `${styleText("gray", S_BAR)}
|
|
255
|
-
` : ""}${symbol(this.state)} ${o.message}
|
|
256
|
-
`, g = this.value ?? [], a = (t, s) => {
|
|
257
|
-
const u = this.options, r = g.includes(t.value) || t.group === true && this.isGroupSelected(`${t.value}`);
|
|
258
|
-
return !s && typeof t.group == "string" && this.options[this.cursor]?.value === t.group ? m(t, r ? "group-active-selected" : "group-active", u) : s && r ? m(t, "active-selected", u) : r ? m(t, "selected", u) : m(t, s ? "active" : "inactive", u);
|
|
259
|
-
};
|
|
260
|
-
switch (this.state) {
|
|
261
|
-
case "submit": {
|
|
262
|
-
const t = this.options.filter(({ value: u }) => g.includes(u)).map((u) => m(u, "submitted")), s = t.length === 0 ? "" : ` ${t.join(styleText("dim", ", "))}`;
|
|
263
|
-
return `${l}${n ? styleText("gray", S_BAR) : ""}${s}`;
|
|
264
|
-
}
|
|
265
|
-
case "cancel": {
|
|
266
|
-
const t = this.options.filter(({ value: s }) => g.includes(s)).map((s) => m(s, "cancelled")).join(styleText("dim", ", "));
|
|
267
|
-
return `${l}${n ? `${styleText("gray", S_BAR)} ` : ""}${t.trim() ? `${t}${n ? `
|
|
268
|
-
${styleText("gray", S_BAR)}` : ""}` : ""}`;
|
|
269
|
-
}
|
|
270
|
-
case "error": {
|
|
271
|
-
const t = n ? `${styleText("yellow", S_BAR)} ` : "", s = this.error.split(`
|
|
272
|
-
`).map((i, f) => f === 0 ? `${n ? `${styleText("yellow", S_BAR_END)} ` : ""}${styleText("yellow", i)}` : ` ${i}`).join(`
|
|
273
|
-
`), u = l.split(`
|
|
274
|
-
`).length, r = s.split(`
|
|
275
|
-
`).length + 1;
|
|
276
|
-
return `${l}${t}${limitOptions({
|
|
277
|
-
output: o.output,
|
|
278
|
-
options: this.options,
|
|
279
|
-
cursor: this.cursor,
|
|
280
|
-
maxItems: o.maxItems,
|
|
281
|
-
columnPadding: t.length,
|
|
282
|
-
rowPadding: u + r,
|
|
283
|
-
style: a
|
|
284
|
-
}).join(`
|
|
285
|
-
${t}`)}
|
|
286
|
-
${s}
|
|
287
|
-
`;
|
|
288
|
-
}
|
|
289
|
-
default: {
|
|
290
|
-
const t = n ? `${styleText("cyan", S_BAR)} ` : "", s = l.split(`
|
|
291
|
-
`).length, u = I ? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, n) : n ? [styleText("cyan", S_BAR_END)] : [], r = u.join(`
|
|
292
|
-
`), c = u.length + 1;
|
|
293
|
-
return `${l}${t}${limitOptions({
|
|
294
|
-
output: o.output,
|
|
295
|
-
options: this.options,
|
|
296
|
-
cursor: this.cursor,
|
|
297
|
-
maxItems: o.maxItems,
|
|
298
|
-
columnPadding: t.length,
|
|
299
|
-
rowPadding: s + c,
|
|
300
|
-
style: a
|
|
301
|
-
}).join(`
|
|
302
|
-
${t}`)}
|
|
303
|
-
${r}
|
|
304
|
-
`;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}).prompt();
|
|
309
|
-
};
|
|
310
216
|
const log = {
|
|
311
217
|
message: (s = [], { symbol: e = styleText("gray", S_BAR), secondarySymbol: r = styleText("gray", S_BAR), output: m = process.stdout, spacing: l = 1, withGuide: c } = {}) => {
|
|
312
218
|
const t = [], o = c ?? settings.withGuide, f = o ? r : "", O = o ? `${e} ` : "", u = o ? `${r} ` : "";
|
|
@@ -536,4 +442,4 @@ ${b}
|
|
|
536
442
|
}).prompt();
|
|
537
443
|
};
|
|
538
444
|
`${styleText("gray", S_BAR)}`;
|
|
539
|
-
export { cancel, confirm,
|
|
445
|
+
export { cancel, confirm, intro, log, multiselect, note, outro, select, spinner };
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { __toESM } from "./_chunks/rolldown-runtime.mjs";
|
|
3
3
|
import { isCancel } from "./_chunks/libs/@clack/core.mjs";
|
|
4
|
-
import { cancel, confirm,
|
|
4
|
+
import { cancel, confirm, intro, log, multiselect as multiselect$1, note, outro, select, spinner } from "./_chunks/libs/@clack/prompts.mjs";
|
|
5
5
|
import { require_picocolors } from "./_chunks/libs/picocolors.mjs";
|
|
6
6
|
import { esm_default } from "./_chunks/libs/simple-git.mjs";
|
|
7
7
|
import { xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
|
|
@@ -21,6 +21,22 @@ import { createHash } from "crypto";
|
|
|
21
21
|
import { createHash as createHash$1 } from "node:crypto";
|
|
22
22
|
import { gunzipSync, inflateRawSync } from "node:zlib";
|
|
23
23
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
24
|
+
const DEFAULT_GITHUB_HOST = "github.com";
|
|
25
|
+
function getGitHubHost() {
|
|
26
|
+
const configuredHost = process.env.GH_HOST?.trim();
|
|
27
|
+
if (!configuredHost) return DEFAULT_GITHUB_HOST;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = new URL(`https://${configuredHost}`);
|
|
30
|
+
if (parsed.username || parsed.password || parsed.port || parsed.pathname !== "/" || parsed.search || parsed.hash) return DEFAULT_GITHUB_HOST;
|
|
31
|
+
return parsed.hostname;
|
|
32
|
+
} catch {
|
|
33
|
+
return DEFAULT_GITHUB_HOST;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function isGitHubHost(host) {
|
|
37
|
+
const normalizedHost = host.toLowerCase();
|
|
38
|
+
return normalizedHost === DEFAULT_GITHUB_HOST || normalizedHost === getGitHubHost().toLowerCase();
|
|
39
|
+
}
|
|
24
40
|
function getOwnerRepo(parsed) {
|
|
25
41
|
if (parsed.type === "local") return null;
|
|
26
42
|
const sshMatch = parsed.url.match(/^git@[^:]+:(.+)$/);
|
|
@@ -71,7 +87,10 @@ function sanitizeSubpath(subpath) {
|
|
|
71
87
|
function isLocalPath(input) {
|
|
72
88
|
return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
|
|
73
89
|
}
|
|
74
|
-
const SOURCE_ALIASES = {
|
|
90
|
+
const SOURCE_ALIASES = {
|
|
91
|
+
"coinbase/agentWallet": "coinbase/agentic-wallet-skills",
|
|
92
|
+
"vercel-labs/vercel-skills": "vercel-labs/agent-skills"
|
|
93
|
+
};
|
|
75
94
|
function decodeFragmentValue(value) {
|
|
76
95
|
try {
|
|
77
96
|
return decodeURIComponent(value);
|
|
@@ -85,7 +104,7 @@ function looksLikeGitSource(input) {
|
|
|
85
104
|
if (input.startsWith("http://") || input.startsWith("https://")) try {
|
|
86
105
|
const parsed = new URL(input);
|
|
87
106
|
const pathname = parsed.pathname;
|
|
88
|
-
if (parsed.
|
|
107
|
+
if (isGitHubHost(parsed.host)) return /^\/[^/]+\/[^/]+(?:\.git)?(?:\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
|
|
89
108
|
if (parsed.hostname === "gitlab.com") return /^\/.+?\/[^/]+(?:\.git)?(?:\/-\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
|
|
90
109
|
} catch {}
|
|
91
110
|
if (/^https?:\/\/.+\.git(?:$|[/?])/i.test(input)) return true;
|
|
@@ -131,6 +150,22 @@ function parseSource(input) {
|
|
|
131
150
|
if (githubPrefixMatch) return parseSource(appendFragmentRef(githubPrefixMatch[1], fragmentRef, fragmentSkillFilter));
|
|
132
151
|
const gitlabPrefixMatch = input.match(/^gitlab:(.+)$/);
|
|
133
152
|
if (gitlabPrefixMatch) return parseSource(appendFragmentRef(`https://gitlab.com/${gitlabPrefixMatch[1]}`, fragmentRef, fragmentSkillFilter));
|
|
153
|
+
if (getGitHubHost() !== "github.com" && /^https?:\/\//.test(input)) try {
|
|
154
|
+
const parsedUrl = new URL(input);
|
|
155
|
+
if (isGitHubHost(parsedUrl.host) && parsedUrl.host !== "github.com") {
|
|
156
|
+
const [owner, rawRepo, marker, ref, ...subpathSegments] = parsedUrl.pathname.split("/").filter(Boolean);
|
|
157
|
+
if (owner && rawRepo) {
|
|
158
|
+
const repo = rawRepo.replace(/\.git$/, "");
|
|
159
|
+
const isTreeUrl = marker === "tree" && ref;
|
|
160
|
+
return {
|
|
161
|
+
type: "git",
|
|
162
|
+
url: `${parsedUrl.protocol}//${parsedUrl.host}/${owner}/${repo}.git`,
|
|
163
|
+
...isTreeUrl ? { ref } : fragmentRef ? { ref: fragmentRef } : {},
|
|
164
|
+
...isTreeUrl && subpathSegments.length > 0 ? { subpath: sanitizeSubpath(subpathSegments.join("/")) } : {}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
134
169
|
const githubTreeWithPathMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
|
|
135
170
|
if (githubTreeWithPathMatch) {
|
|
136
171
|
const [, owner, repo, ref, subpath] = githubTreeWithPathMatch;
|
|
@@ -187,12 +222,14 @@ function parseSource(input) {
|
|
|
187
222
|
...fragmentRef ? { ref: fragmentRef } : {}
|
|
188
223
|
};
|
|
189
224
|
}
|
|
225
|
+
const githubHost = getGitHubHost();
|
|
226
|
+
const shorthandSourceType = githubHost === "github.com" ? "github" : "git";
|
|
190
227
|
const atSkillMatch = input.match(/^([^/]+)\/([^/@]+)@(.+)$/);
|
|
191
228
|
if (atSkillMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
|
|
192
229
|
const [, owner, repo, skillFilter] = atSkillMatch;
|
|
193
230
|
return {
|
|
194
|
-
type:
|
|
195
|
-
url: `https
|
|
231
|
+
type: shorthandSourceType,
|
|
232
|
+
url: `https://${githubHost}/${owner}/${repo}.git`,
|
|
196
233
|
...fragmentRef ? { ref: fragmentRef } : {},
|
|
197
234
|
skillFilter: fragmentSkillFilter || skillFilter
|
|
198
235
|
};
|
|
@@ -201,8 +238,8 @@ function parseSource(input) {
|
|
|
201
238
|
if (shorthandMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
|
|
202
239
|
const [, owner, repo, subpath] = shorthandMatch;
|
|
203
240
|
return {
|
|
204
|
-
type:
|
|
205
|
-
url: `https
|
|
241
|
+
type: shorthandSourceType,
|
|
242
|
+
url: `https://${githubHost}/${owner}/${repo}.git`,
|
|
206
243
|
...fragmentRef ? { ref: fragmentRef } : {},
|
|
207
244
|
subpath: subpath ? sanitizeSubpath(subpath) : subpath,
|
|
208
245
|
...fragmentSkillFilter ? { skillFilter: fragmentSkillFilter } : {}
|
|
@@ -277,8 +314,88 @@ function countVisualRowsForLines(lines, columns) {
|
|
|
277
314
|
const cols = columns !== void 0 && columns > 0 ? columns : process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
278
315
|
return lines.reduce((sum, line) => sum + visualRowsForLine(line, cols), 0);
|
|
279
316
|
}
|
|
317
|
+
function truncateToWidth(text, width) {
|
|
318
|
+
let truncated = "";
|
|
319
|
+
for (const char of text) {
|
|
320
|
+
if (approxStringWidth(truncated + char) > width) break;
|
|
321
|
+
truncated += char;
|
|
322
|
+
}
|
|
323
|
+
return truncated;
|
|
324
|
+
}
|
|
325
|
+
function formatDetailLines(detail, width, maxLines) {
|
|
326
|
+
const safeWidth = Math.max(1, width);
|
|
327
|
+
const normalized = detail?.replace(/\s+/g, " ").trim() ?? "";
|
|
328
|
+
const lines = [];
|
|
329
|
+
let remaining = normalized;
|
|
330
|
+
while (remaining && lines.length < maxLines) {
|
|
331
|
+
if (approxStringWidth(remaining) <= safeWidth) {
|
|
332
|
+
lines.push(remaining);
|
|
333
|
+
remaining = "";
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
const candidate = truncateToWidth(remaining, safeWidth);
|
|
337
|
+
const breakAt = candidate.lastIndexOf(" ");
|
|
338
|
+
if (breakAt > 0) {
|
|
339
|
+
lines.push(candidate.slice(0, breakAt).trimEnd());
|
|
340
|
+
remaining = remaining.slice(breakAt).trimStart();
|
|
341
|
+
} else {
|
|
342
|
+
lines.push(candidate);
|
|
343
|
+
remaining = remaining.slice(candidate.length).trimStart();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (remaining && lines.length > 0) {
|
|
347
|
+
const last = lines.length - 1;
|
|
348
|
+
lines[last] = `${truncateToWidth(lines[last], Math.max(0, safeWidth - 1)).trimEnd()}…`;
|
|
349
|
+
}
|
|
350
|
+
while (lines.length < maxLines) lines.push("");
|
|
351
|
+
return lines;
|
|
352
|
+
}
|
|
353
|
+
function buildSearchEntries(items, selectGroups, collapsedGroups = /* @__PURE__ */ new Set()) {
|
|
354
|
+
if (!selectGroups) return items.map((item) => ({
|
|
355
|
+
type: "item",
|
|
356
|
+
item
|
|
357
|
+
}));
|
|
358
|
+
const entries = [];
|
|
359
|
+
let index = 0;
|
|
360
|
+
while (index < items.length) {
|
|
361
|
+
const item = items[index];
|
|
362
|
+
if (!item.group) {
|
|
363
|
+
entries.push({
|
|
364
|
+
type: "item",
|
|
365
|
+
item
|
|
366
|
+
});
|
|
367
|
+
index += 1;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const groupItems = [];
|
|
371
|
+
while (index < items.length && items[index].group === item.group) {
|
|
372
|
+
groupItems.push(items[index]);
|
|
373
|
+
index += 1;
|
|
374
|
+
}
|
|
375
|
+
const collapsed = collapsedGroups.has(item.group);
|
|
376
|
+
entries.push({
|
|
377
|
+
type: "group",
|
|
378
|
+
group: item.group,
|
|
379
|
+
items: groupItems,
|
|
380
|
+
collapsed
|
|
381
|
+
});
|
|
382
|
+
if (!collapsed) entries.push(...groupItems.map((groupItem) => ({
|
|
383
|
+
type: "item",
|
|
384
|
+
item: groupItem
|
|
385
|
+
})));
|
|
386
|
+
}
|
|
387
|
+
return entries;
|
|
388
|
+
}
|
|
389
|
+
function toggleSearchEntry(selected, entry) {
|
|
390
|
+
if (entry?.type === "group") {
|
|
391
|
+
const allSelected = entry.items.every((item) => selected.has(item.value));
|
|
392
|
+
for (const item of entry.items) if (allSelected) selected.delete(item.value);
|
|
393
|
+
else selected.add(item.value);
|
|
394
|
+
} else if (entry?.type === "item") if (selected.has(entry.item.value)) selected.delete(entry.item.value);
|
|
395
|
+
else selected.add(entry.item.value);
|
|
396
|
+
}
|
|
280
397
|
async function searchMultiselect(options) {
|
|
281
|
-
const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection } = options;
|
|
398
|
+
const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection, searchable = true, showDetail = false, detailLines = 2, showSelectedSummary = true, selectGroups = false } = options;
|
|
282
399
|
return new Promise((resolve) => {
|
|
283
400
|
const rl = readline.createInterface({
|
|
284
401
|
input: process.stdin,
|
|
@@ -290,6 +407,7 @@ async function searchMultiselect(options) {
|
|
|
290
407
|
let query = "";
|
|
291
408
|
let cursor = 0;
|
|
292
409
|
const selected = new Set(initialSelected);
|
|
410
|
+
const collapsedGroups = /* @__PURE__ */ new Set();
|
|
293
411
|
let lastRenderHeight = 0;
|
|
294
412
|
const lockedValues = lockedSection ? lockedSection.items.map((i) => i.value) : [];
|
|
295
413
|
const filter = (item, q) => {
|
|
@@ -300,17 +418,10 @@ async function searchMultiselect(options) {
|
|
|
300
418
|
const getFiltered = () => {
|
|
301
419
|
return items.filter((item) => filter(item, query));
|
|
302
420
|
};
|
|
303
|
-
const clearRender = () => {
|
|
304
|
-
if (lastRenderHeight > 0) {
|
|
305
|
-
process.stdout.write(`\x1b[${lastRenderHeight}A`);
|
|
306
|
-
for (let i = 0; i < lastRenderHeight; i++) process.stdout.write("\x1B[2K\x1B[1B");
|
|
307
|
-
process.stdout.write(`\x1b[${lastRenderHeight}A`);
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
421
|
const render = (state = "active") => {
|
|
311
|
-
clearRender();
|
|
312
422
|
const lines = [];
|
|
313
423
|
const filtered = getFiltered();
|
|
424
|
+
const entries = buildSearchEntries(filtered, selectGroups, collapsedGroups);
|
|
314
425
|
const icon = state === "active" ? S_STEP_ACTIVE : state === "cancel" ? S_STEP_CANCEL : S_STEP_SUBMIT;
|
|
315
426
|
lines.push(`${icon} ${import_picocolors.default.bold(message)}`);
|
|
316
427
|
if (state === "active") {
|
|
@@ -323,28 +434,41 @@ async function searchMultiselect(options) {
|
|
|
323
434
|
lines.push(`${S_BAR}`);
|
|
324
435
|
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Additional agents")} ${S_BAR_H.repeat(29)}`);
|
|
325
436
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const
|
|
437
|
+
if (searchable) {
|
|
438
|
+
const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
|
|
439
|
+
lines.push(searchLine);
|
|
440
|
+
lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, space select, enter confirm")}`);
|
|
441
|
+
lines.push(`${S_BAR}`);
|
|
442
|
+
}
|
|
443
|
+
const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), entries.length - maxVisible));
|
|
444
|
+
const visibleEnd = Math.min(entries.length, visibleStart + maxVisible);
|
|
445
|
+
const visibleEntries = entries.slice(visibleStart, visibleEnd);
|
|
333
446
|
if (filtered.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("No matches found")}`);
|
|
334
447
|
else {
|
|
335
|
-
for (let i = 0; i <
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
448
|
+
for (let i = 0; i < visibleEntries.length; i++) {
|
|
449
|
+
const entry = visibleEntries[i];
|
|
450
|
+
const isCursor = visibleStart + i === cursor;
|
|
451
|
+
if (entry.type === "group") {
|
|
452
|
+
const selectedCount = entry.items.filter((item) => selected.has(item.value)).length;
|
|
453
|
+
const radio = selectedCount === entry.items.length ? S_RADIO_ACTIVE : selectedCount > 0 ? import_picocolors.default.yellow("◐") : S_RADIO_INACTIVE;
|
|
454
|
+
const label = isCursor ? import_picocolors.default.underline(import_picocolors.default.bold(entry.group)) : import_picocolors.default.bold(entry.group);
|
|
455
|
+
const prefix = isCursor ? import_picocolors.default.cyan("❯") : " ";
|
|
456
|
+
const disclosure = import_picocolors.default.dim(entry.collapsed ? "▸" : "▾");
|
|
457
|
+
lines.push(`${S_BAR} ${prefix} ${disclosure} ${radio} ${label}`);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const item = entry.item;
|
|
461
|
+
const radio = selected.has(item.value) ? S_RADIO_ACTIVE : S_RADIO_INACTIVE;
|
|
341
462
|
const label = isCursor ? import_picocolors.default.underline(item.label) : item.label;
|
|
342
463
|
const hint = item.hint ? import_picocolors.default.dim(` (${item.hint})`) : "";
|
|
343
464
|
const prefix = isCursor ? import_picocolors.default.cyan("❯") : " ";
|
|
344
|
-
|
|
465
|
+
const groupItems = selectGroups && item.group ? filtered.filter((i) => i.group === item.group) : [];
|
|
466
|
+
const isLastInGroup = groupItems.at(-1) === item;
|
|
467
|
+
const tree = groupItems.length > 0 ? `${import_picocolors.default.dim(isLastInGroup ? "└─" : "├─")} ` : "";
|
|
468
|
+
lines.push(`${S_BAR} ${prefix} ${tree}${radio} ${label}${hint}`);
|
|
345
469
|
}
|
|
346
470
|
const hiddenBefore = visibleStart;
|
|
347
|
-
const hiddenAfter =
|
|
471
|
+
const hiddenAfter = entries.length - visibleEnd;
|
|
348
472
|
if (hiddenBefore > 0 || hiddenAfter > 0) {
|
|
349
473
|
const parts = [];
|
|
350
474
|
if (hiddenBefore > 0) parts.push(`↑ ${hiddenBefore} more`);
|
|
@@ -352,19 +476,35 @@ async function searchMultiselect(options) {
|
|
|
352
476
|
lines.push(`${S_BAR} ${import_picocolors.default.dim(parts.join(" "))}`);
|
|
353
477
|
}
|
|
354
478
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
lines.push(`${S_BAR}
|
|
479
|
+
if (showDetail) {
|
|
480
|
+
const entry = entries[cursor];
|
|
481
|
+
const detail = entry?.type === "group" ? `Select all ${entry.items.length} skills in ${entry.group}.` : entry?.item.detail;
|
|
482
|
+
const columns = process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
483
|
+
const detailWidth = Math.max(1, columns - 5);
|
|
484
|
+
lines.push(`${S_BAR}`);
|
|
485
|
+
lines.push(`${S_BAR} ${import_picocolors.default.dim("Description")}`);
|
|
486
|
+
for (const line of formatDetailLines(detail, detailWidth, detailLines)) lines.push(`${S_BAR} ${import_picocolors.default.dim(line)}`);
|
|
487
|
+
}
|
|
488
|
+
if (showSelectedSummary) {
|
|
489
|
+
lines.push(`${S_BAR}`);
|
|
490
|
+
const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
|
|
491
|
+
if (allSelectedLabels.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
|
|
492
|
+
else {
|
|
493
|
+
const summary = allSelectedLabels.length <= 3 ? allSelectedLabels.join(", ") : `${allSelectedLabels.slice(0, 3).join(", ")} +${allSelectedLabels.length - 3} more`;
|
|
494
|
+
lines.push(`${S_BAR} ${import_picocolors.default.green("Selected:")} ${summary}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (!searchable) {
|
|
498
|
+
lines.push(`${S_BAR}`);
|
|
499
|
+
lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, ←→ collapse/expand, space select, enter confirm")}`);
|
|
361
500
|
}
|
|
362
501
|
lines.push(`${import_picocolors.default.dim("└")}`);
|
|
363
502
|
} else if (state === "submit") {
|
|
364
503
|
const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
|
|
365
504
|
lines.push(`${S_BAR} ${import_picocolors.default.dim(allSelectedLabels.join(", "))}`);
|
|
366
505
|
} else if (state === "cancel") lines.push(`${S_BAR} ${import_picocolors.default.strikethrough(import_picocolors.default.dim("Cancelled"))}`);
|
|
367
|
-
|
|
506
|
+
const clearPreviousFrame = lastRenderHeight > 0 ? `\x1b[${lastRenderHeight}A\x1b[J` : "";
|
|
507
|
+
process.stdout.write(clearPreviousFrame + lines.join("\n") + "\n");
|
|
368
508
|
lastRenderHeight = countVisualRowsForLines(lines, process.stdout.columns);
|
|
369
509
|
};
|
|
370
510
|
const cleanup = () => {
|
|
@@ -385,7 +525,7 @@ async function searchMultiselect(options) {
|
|
|
385
525
|
};
|
|
386
526
|
const keypressHandler = (_str, key) => {
|
|
387
527
|
if (!key) return;
|
|
388
|
-
const
|
|
528
|
+
const entries = buildSearchEntries(getFiltered(), selectGroups, collapsedGroups);
|
|
389
529
|
if (key.name === "return") {
|
|
390
530
|
submit();
|
|
391
531
|
return;
|
|
@@ -400,14 +540,31 @@ async function searchMultiselect(options) {
|
|
|
400
540
|
return;
|
|
401
541
|
}
|
|
402
542
|
if (key.name === "down") {
|
|
403
|
-
cursor = Math.min(
|
|
543
|
+
cursor = Math.min(entries.length - 1, cursor + 1);
|
|
404
544
|
render();
|
|
405
545
|
return;
|
|
406
546
|
}
|
|
547
|
+
if (selectGroups && key.name === "right") {
|
|
548
|
+
const entry = entries[cursor];
|
|
549
|
+
if (entry?.type === "group" && entry.collapsed) {
|
|
550
|
+
collapsedGroups.delete(entry.group);
|
|
551
|
+
render();
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (selectGroups && key.name === "left") {
|
|
556
|
+
const entry = entries[cursor];
|
|
557
|
+
const group = entry?.type === "group" ? entry.group : entry?.item.group;
|
|
558
|
+
if (group) {
|
|
559
|
+
collapsedGroups.add(group);
|
|
560
|
+
cursor = buildSearchEntries(getFiltered(), selectGroups, collapsedGroups).findIndex((collapsedEntry) => collapsedEntry.type === "group" && collapsedEntry.group === group);
|
|
561
|
+
render();
|
|
562
|
+
}
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
407
565
|
if (key.name === "space") {
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
else selected.add(item.value);
|
|
566
|
+
const entry = entries[cursor];
|
|
567
|
+
toggleSearchEntry(selected, entry);
|
|
411
568
|
render();
|
|
412
569
|
return;
|
|
413
570
|
}
|
|
@@ -417,7 +574,7 @@ async function searchMultiselect(options) {
|
|
|
417
574
|
render();
|
|
418
575
|
return;
|
|
419
576
|
}
|
|
420
|
-
if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
577
|
+
if (searchable && key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
421
578
|
query += key.sequence;
|
|
422
579
|
cursor = 0;
|
|
423
580
|
render();
|
|
@@ -429,6 +586,7 @@ async function searchMultiselect(options) {
|
|
|
429
586
|
});
|
|
430
587
|
}
|
|
431
588
|
const DEFAULT_CLONE_TIMEOUT_MS = 3e5;
|
|
589
|
+
const ALLOWED_GIT_PROTOCOLS = "https:http:ssh:git:file";
|
|
432
590
|
const CLONE_TIMEOUT_MS = (() => {
|
|
433
591
|
const raw = process.env.SKILLS_CLONE_TIMEOUT_MS;
|
|
434
592
|
if (!raw) return DEFAULT_CLONE_TIMEOUT_MS;
|
|
@@ -449,20 +607,21 @@ var GitCloneError = class extends Error {
|
|
|
449
607
|
}
|
|
450
608
|
};
|
|
451
609
|
function parseGitHubRepoUrl(url) {
|
|
452
|
-
const sshMatch = url.match(/^git@
|
|
453
|
-
if (sshMatch) {
|
|
454
|
-
const
|
|
455
|
-
const
|
|
610
|
+
const sshMatch = url.match(/^git@([^:]+):([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
611
|
+
if (sshMatch && isGitHubHost(sshMatch[1])) {
|
|
612
|
+
const host = sshMatch[1];
|
|
613
|
+
const owner = sshMatch[2];
|
|
614
|
+
const repo = sshMatch[3];
|
|
456
615
|
return {
|
|
457
616
|
owner,
|
|
458
617
|
repo,
|
|
459
618
|
slug: `${owner}/${repo}`,
|
|
460
|
-
sshUrl: `git
|
|
619
|
+
sshUrl: `git@${host}:${owner}/${repo}.git`
|
|
461
620
|
};
|
|
462
621
|
}
|
|
463
622
|
try {
|
|
464
623
|
const parsed = new URL(url);
|
|
465
|
-
if (parsed.
|
|
624
|
+
if (!isGitHubHost(parsed.host)) return null;
|
|
466
625
|
const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
467
626
|
if (!match) return null;
|
|
468
627
|
const owner = match[1];
|
|
@@ -471,7 +630,7 @@ function parseGitHubRepoUrl(url) {
|
|
|
471
630
|
owner,
|
|
472
631
|
repo,
|
|
473
632
|
slug: `${owner}/${repo}`,
|
|
474
|
-
sshUrl: `git
|
|
633
|
+
sshUrl: `git@${parsed.host}:${owner}/${repo}.git`
|
|
475
634
|
};
|
|
476
635
|
} catch {
|
|
477
636
|
return null;
|
|
@@ -480,7 +639,7 @@ function parseGitHubRepoUrl(url) {
|
|
|
480
639
|
function isGitHubHttpsCloneUrl(url) {
|
|
481
640
|
try {
|
|
482
641
|
const parsed = new URL(url);
|
|
483
|
-
return parsed.protocol === "https:" && parsed.
|
|
642
|
+
return parsed.protocol === "https:" && isGitHubHost(parsed.host);
|
|
484
643
|
} catch {
|
|
485
644
|
return false;
|
|
486
645
|
}
|
|
@@ -526,6 +685,7 @@ function createGitClient(sshCommand) {
|
|
|
526
685
|
git.env({
|
|
527
686
|
...process.env,
|
|
528
687
|
GIT_TERMINAL_PROMPT: "0",
|
|
688
|
+
GIT_ALLOW_PROTOCOL: ALLOWED_GIT_PROTOCOLS,
|
|
529
689
|
GIT_LFS_SKIP_SMUDGE: "1",
|
|
530
690
|
...sshCommand ? { GIT_SSH_COMMAND: sshCommand } : {}
|
|
531
691
|
});
|
|
@@ -540,12 +700,13 @@ async function resetTempDir(dir) {
|
|
|
540
700
|
}
|
|
541
701
|
async function tryGhClone(repo, tempDir, ref) {
|
|
542
702
|
let cloneTarget = repo.slug;
|
|
703
|
+
const host = repo.sshUrl.match(/^git@([^:]+):/)?.[1] || "github.com";
|
|
543
704
|
try {
|
|
544
705
|
const { stdout, stderr } = await execFileAsync("gh", [
|
|
545
706
|
"auth",
|
|
546
707
|
"status",
|
|
547
708
|
"-h",
|
|
548
|
-
|
|
709
|
+
host
|
|
549
710
|
], {
|
|
550
711
|
timeout: 5e3,
|
|
551
712
|
env: {
|
|
@@ -573,14 +734,16 @@ async function tryGhClone(repo, tempDir, ref) {
|
|
|
573
734
|
timeout: CLONE_TIMEOUT_MS,
|
|
574
735
|
env: {
|
|
575
736
|
...process.env,
|
|
576
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
737
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
738
|
+
GIT_ALLOW_PROTOCOL: ALLOWED_GIT_PROTOCOLS
|
|
577
739
|
}
|
|
578
740
|
});
|
|
579
741
|
return true;
|
|
580
742
|
}
|
|
581
743
|
function buildGitHubAuthError(url, repo, message) {
|
|
582
|
-
|
|
583
|
-
if (repo) return `
|
|
744
|
+
const host = repo?.sshUrl.match(/^git@([^:]+):/)?.[1] || "github.com";
|
|
745
|
+
if (repo && isGitHubSsoAuthError(message)) return `GitHub blocked HTTPS access to ${url} because the organization enforces SAML SSO.\n skills tried your existing git credentials and available fallbacks, but none succeeded.\n - Re-authorize your GitHub credentials/app for that org's SSO policy\n - Or rerun with SSH: npx skills add ${repo.sshUrl}\n - Verify access with: gh auth status -h ${host} or ssh -T git@${host}`;
|
|
746
|
+
if (repo) return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - Retry with SSH: npx skills add ${repo.sshUrl}\n - Check access with: gh auth status -h ${host} or ssh -T git@${host}`;
|
|
584
747
|
return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`;
|
|
585
748
|
}
|
|
586
749
|
async function cloneRepo(url, ref) {
|
|
@@ -771,6 +934,13 @@ async function addSkillToLocalLock(skillName, entry, cwd) {
|
|
|
771
934
|
lock.skills[skillName] = entry;
|
|
772
935
|
await writeLocalLock(lock, cwd);
|
|
773
936
|
}
|
|
937
|
+
async function removeSkillFromLocalLock(skillName, cwd) {
|
|
938
|
+
const lock = await readLocalLock(cwd);
|
|
939
|
+
if (!(skillName in lock.skills)) return false;
|
|
940
|
+
delete lock.skills[skillName];
|
|
941
|
+
await writeLocalLock(lock, cwd);
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
774
944
|
function createEmptyLocalLock() {
|
|
775
945
|
return {
|
|
776
946
|
version: CURRENT_VERSION$1,
|
|
@@ -794,9 +964,11 @@ const AGENT_PROJECT_SKILL_DIRS = [
|
|
|
794
964
|
".continue/skills",
|
|
795
965
|
".github/skills",
|
|
796
966
|
".goose/skills",
|
|
967
|
+
".grok/skills",
|
|
797
968
|
".iflow/skills",
|
|
798
969
|
".junie/skills",
|
|
799
970
|
".kilocode/skills",
|
|
971
|
+
".kimchi/skills",
|
|
800
972
|
".kiro/skills",
|
|
801
973
|
".mux/skills",
|
|
802
974
|
".neovate/skills",
|
|
@@ -830,24 +1002,44 @@ async function hasSkillMd(dir) {
|
|
|
830
1002
|
return false;
|
|
831
1003
|
}
|
|
832
1004
|
}
|
|
1005
|
+
function warnSkippedSkill(skillMdPath, reason) {
|
|
1006
|
+
console.warn(`⚠ Skipped ${sanitizeMetadata(skillMdPath)} — ${stripTerminalEscapes(reason)}`);
|
|
1007
|
+
}
|
|
833
1008
|
async function parseSkillMd(skillMdPath, options) {
|
|
1009
|
+
let content;
|
|
834
1010
|
try {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1011
|
+
content = await readFile(skillMdPath, "utf-8");
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
warnSkippedSkill(skillMdPath, `failed to read file: ${err.message}`);
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
let data;
|
|
1017
|
+
try {
|
|
1018
|
+
({data} = parseFrontmatter(content));
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
warnSkippedSkill(skillMdPath, `YAML parse error: ${err.message}`);
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
if (!data.name || !data.description) {
|
|
1024
|
+
const missing = [];
|
|
1025
|
+
if (!data.name) missing.push("name");
|
|
1026
|
+
if (!data.description) missing.push("description");
|
|
1027
|
+
warnSkippedSkill(skillMdPath, `missing required frontmatter field(s): ${missing.join(", ")}`);
|
|
849
1028
|
return null;
|
|
850
1029
|
}
|
|
1030
|
+
if (typeof data.name !== "string" || typeof data.description !== "string") {
|
|
1031
|
+
warnSkippedSkill(skillMdPath, `frontmatter "name" and "description" must be strings (got ${typeof data.name} and ${typeof data.description})`);
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
const metadata = isRecord(data.metadata) ? data.metadata : void 0;
|
|
1035
|
+
if (metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
|
|
1036
|
+
return {
|
|
1037
|
+
name: sanitizeMetadata(data.name),
|
|
1038
|
+
description: sanitizeMetadata(data.description),
|
|
1039
|
+
path: dirname(skillMdPath),
|
|
1040
|
+
rawContent: content,
|
|
1041
|
+
metadata
|
|
1042
|
+
};
|
|
851
1043
|
}
|
|
852
1044
|
async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
|
|
853
1045
|
if (depth > maxDepth) return [];
|
|
@@ -868,6 +1060,7 @@ function isSubpathSafe(basePath, subpath) {
|
|
|
868
1060
|
async function discoverSkills(basePath, subpath, options) {
|
|
869
1061
|
const skills = [];
|
|
870
1062
|
const seenNames = /* @__PURE__ */ new Set();
|
|
1063
|
+
const parsedSkillPaths = /* @__PURE__ */ new Set();
|
|
871
1064
|
const localLock = await readLocalLock(basePath);
|
|
872
1065
|
const lockedSkillNames = new Set(Object.keys(localLock.skills).map(normalizeSkillName));
|
|
873
1066
|
if (subpath && !isSubpathSafe(basePath, subpath)) throw new Error(`Invalid subpath: "${subpath}" resolves outside the repository directory. Subpath must not contain ".." segments that escape the base path.`);
|
|
@@ -886,8 +1079,14 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
886
1079
|
const directoryName = normalizeSkillName(basename(skill.path));
|
|
887
1080
|
return lockedSkillNames.has(skillName) || lockedSkillNames.has(directoryName);
|
|
888
1081
|
};
|
|
1082
|
+
const parseSkillAt = async (skillDir) => {
|
|
1083
|
+
const skillMdPath = resolve(skillDir, "SKILL.md");
|
|
1084
|
+
if (parsedSkillPaths.has(skillMdPath)) return null;
|
|
1085
|
+
parsedSkillPaths.add(skillMdPath);
|
|
1086
|
+
return parseSkillMd(skillMdPath, options);
|
|
1087
|
+
};
|
|
889
1088
|
if (await hasSkillMd(searchPath)) {
|
|
890
|
-
let skill = await
|
|
1089
|
+
let skill = await parseSkillAt(searchPath);
|
|
891
1090
|
if (skill) {
|
|
892
1091
|
if (!isInstalledProjectSkill(skill)) {
|
|
893
1092
|
skill = enhanceSkill(skill);
|
|
@@ -909,7 +1108,7 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
909
1108
|
prioritySearchDirs.push(...await getPluginSkillPaths(searchPath));
|
|
910
1109
|
const tryAddSkillAt = async (skillDir) => {
|
|
911
1110
|
if (!await hasSkillMd(skillDir)) return false;
|
|
912
|
-
let skill = await
|
|
1111
|
+
let skill = await parseSkillAt(skillDir);
|
|
913
1112
|
if (!skill || seenNames.has(skill.name)) return true;
|
|
914
1113
|
if (isInstalledProjectSkill(skill)) return true;
|
|
915
1114
|
skill = enhanceSkill(skill);
|
|
@@ -939,7 +1138,7 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
939
1138
|
if (skills.length === 0 || options?.fullDepth) {
|
|
940
1139
|
const allSkillDirs = await findSkillDirs(searchPath);
|
|
941
1140
|
for (const skillDir of allSkillDirs) {
|
|
942
|
-
let skill = await
|
|
1141
|
+
let skill = await parseSkillAt(skillDir);
|
|
943
1142
|
if (skill && !seenNames.has(skill.name) && !isInstalledProjectSkill(skill)) {
|
|
944
1143
|
skill = enhanceSkill(skill);
|
|
945
1144
|
skills.push(skill);
|
|
@@ -967,6 +1166,7 @@ const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude"
|
|
|
967
1166
|
const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
|
|
968
1167
|
const hermesHome = process.env.HERMES_HOME?.trim() || join(home, ".hermes");
|
|
969
1168
|
const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home, ".autohand");
|
|
1169
|
+
const grokHome = process.env.GROK_HOME?.trim() || join(home, ".grok");
|
|
970
1170
|
const zedAppDataHome = process.env.APPDATA?.trim();
|
|
971
1171
|
const zedFlatpakConfigHome = process.env.FLATPAK_XDG_CONFIG_HOME?.trim();
|
|
972
1172
|
function packageJsonHasDependency(packageJsonPath, dependencyName) {
|
|
@@ -986,6 +1186,9 @@ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
|
986
1186
|
function isZCodeInstalled(homeDir = home, pathExists = existsSync) {
|
|
987
1187
|
return pathExists(join(homeDir, ".zcode")) || pathExists("/Applications/ZCode.app");
|
|
988
1188
|
}
|
|
1189
|
+
function isKimchiInstalled(homeDir = home, pathExists = existsSync) {
|
|
1190
|
+
return pathExists(join(homeDir, ".config", "kimchi"));
|
|
1191
|
+
}
|
|
989
1192
|
const agents = {
|
|
990
1193
|
"aider-desk": {
|
|
991
1194
|
name: "aider-desk",
|
|
@@ -1269,6 +1472,15 @@ const agents = {
|
|
|
1269
1472
|
return existsSync(join(configHome, "goose"));
|
|
1270
1473
|
}
|
|
1271
1474
|
},
|
|
1475
|
+
grok: {
|
|
1476
|
+
name: "grok",
|
|
1477
|
+
displayName: "Grok Build",
|
|
1478
|
+
skillsDir: ".grok/skills",
|
|
1479
|
+
globalSkillsDir: join(grokHome, "skills"),
|
|
1480
|
+
detectInstalled: async () => {
|
|
1481
|
+
return existsSync(grokHome);
|
|
1482
|
+
}
|
|
1483
|
+
},
|
|
1272
1484
|
"hermes-agent": {
|
|
1273
1485
|
name: "hermes-agent",
|
|
1274
1486
|
displayName: "Hermes Agent",
|
|
@@ -1323,6 +1535,15 @@ const agents = {
|
|
|
1323
1535
|
return existsSync(join(home, ".kilocode"));
|
|
1324
1536
|
}
|
|
1325
1537
|
},
|
|
1538
|
+
kimchi: {
|
|
1539
|
+
name: "kimchi",
|
|
1540
|
+
displayName: "Kimchi",
|
|
1541
|
+
skillsDir: ".kimchi/skills",
|
|
1542
|
+
globalSkillsDir: join(home, ".config", "kimchi", "harness", "skills"),
|
|
1543
|
+
detectInstalled: async () => {
|
|
1544
|
+
return isKimchiInstalled();
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1326
1547
|
"kimi-code-cli": {
|
|
1327
1548
|
name: "kimi-code-cli",
|
|
1328
1549
|
displayName: "Kimi Code CLI",
|
|
@@ -2118,7 +2339,7 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
2118
2339
|
mode: "symlink"
|
|
2119
2340
|
};
|
|
2120
2341
|
if (!isGlobal && !isUniversalAgent(agentType)) {
|
|
2121
|
-
if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0]))) return {
|
|
2342
|
+
if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0])) && agentType !== "claude-code") return {
|
|
2122
2343
|
success: true,
|
|
2123
2344
|
path: canonicalDir,
|
|
2124
2345
|
canonicalPath: canonicalDir,
|
|
@@ -3039,9 +3260,11 @@ const PRIORITY_PREFIXES = [
|
|
|
3039
3260
|
".continue/skills/",
|
|
3040
3261
|
".github/skills/",
|
|
3041
3262
|
".goose/skills/",
|
|
3263
|
+
".grok/skills/",
|
|
3042
3264
|
".iflow/skills/",
|
|
3043
3265
|
".junie/skills/",
|
|
3044
3266
|
".kilocode/skills/",
|
|
3267
|
+
".kimchi/skills/",
|
|
3045
3268
|
".kiro/skills/",
|
|
3046
3269
|
".mux/skills/",
|
|
3047
3270
|
".neovate/skills/",
|
|
@@ -3208,7 +3431,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
3208
3431
|
tree
|
|
3209
3432
|
};
|
|
3210
3433
|
}
|
|
3211
|
-
var version$1 = "1.5.
|
|
3434
|
+
var version$1 = "1.5.20";
|
|
3212
3435
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
3213
3436
|
const EVE_AGENT_LABEL = "eve agent";
|
|
3214
3437
|
async function isSourcePrivate(source) {
|
|
@@ -3759,6 +3982,7 @@ async function runAdd(args, options = {}) {
|
|
|
3759
3982
|
spinner$3.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
|
|
3760
3983
|
const ownerRepoRaw = getOwnerRepo(parsed);
|
|
3761
3984
|
const repoPrivacyPromise = (() => {
|
|
3985
|
+
if (parsed.type !== "github") return Promise.resolve(null);
|
|
3762
3986
|
if (!ownerRepoRaw) return Promise.resolve(null);
|
|
3763
3987
|
const ownerRepo = parseOwnerRepo(ownerRepoRaw);
|
|
3764
3988
|
if (!ownerRepo) return Promise.resolve(null);
|
|
@@ -3900,34 +4124,24 @@ async function runAdd(args, options = {}) {
|
|
|
3900
4124
|
return getSkillDisplayName(a).localeCompare(getSkillDisplayName(b));
|
|
3901
4125
|
});
|
|
3902
4126
|
const hasGroups = sortedSkills.some((s) => s.pluginName);
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
required: true
|
|
3920
|
-
});
|
|
3921
|
-
} else selected = await multiselect({
|
|
3922
|
-
message: "Select skills to install",
|
|
3923
|
-
options: sortedSkills.map((s) => ({
|
|
3924
|
-
value: s,
|
|
3925
|
-
label: getSkillDisplayName(s),
|
|
3926
|
-
hint: s.description.length > 60 ? s.description.slice(0, 57) + "…" : s.description
|
|
3927
|
-
})),
|
|
3928
|
-
required: true
|
|
4127
|
+
const kebabToTitle = (s) => s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4128
|
+
const skillChoices = sortedSkills.map((s) => ({
|
|
4129
|
+
value: s,
|
|
4130
|
+
label: getSkillDisplayName(s),
|
|
4131
|
+
group: hasGroups ? s.pluginName ? kebabToTitle(s.pluginName) : "Other" : void 0,
|
|
4132
|
+
detail: s.description
|
|
4133
|
+
}));
|
|
4134
|
+
const selected = await searchMultiselect({
|
|
4135
|
+
message: hasGroups ? `Select skills to install ${import_picocolors.default.dim("(space to toggle)")}` : "Select skills to install",
|
|
4136
|
+
items: skillChoices,
|
|
4137
|
+
required: true,
|
|
4138
|
+
maxVisible: 20,
|
|
4139
|
+
searchable: !hasGroups,
|
|
4140
|
+
showDetail: true,
|
|
4141
|
+
showSelectedSummary: false,
|
|
4142
|
+
selectGroups: hasGroups
|
|
3929
4143
|
});
|
|
3930
|
-
if (
|
|
4144
|
+
if (isCancelled$1(selected)) {
|
|
3931
4145
|
cancel("Installation cancelled");
|
|
3932
4146
|
await cleanup(tempDir);
|
|
3933
4147
|
process.exit(0);
|
|
@@ -5039,7 +5253,7 @@ function deriveSkillFolder(skillPath) {
|
|
|
5039
5253
|
return folder;
|
|
5040
5254
|
}
|
|
5041
5255
|
function supportsAppendedSubpath(source) {
|
|
5042
|
-
if (source.startsWith("git@")) return false;
|
|
5256
|
+
if (source.startsWith("git@") || source.startsWith("ssh://")) return false;
|
|
5043
5257
|
if (source.endsWith(".git")) return false;
|
|
5044
5258
|
if (source.startsWith("http://") || source.startsWith("https://")) try {
|
|
5045
5259
|
const host = new URL(source).hostname;
|
|
@@ -5057,6 +5271,11 @@ function getLocalSource(entry) {
|
|
|
5057
5271
|
if ((entry.sourceType === "git" || entry.sourceType === "gitlab") && isBareShorthand(entry.source)) return null;
|
|
5058
5272
|
return entry.source;
|
|
5059
5273
|
}
|
|
5274
|
+
function shouldUseFullDepthForUpdate(entry) {
|
|
5275
|
+
if (!entry.skillPath) return false;
|
|
5276
|
+
const source = entry.sourceType && entry.sourceType !== "github" ? getLocalSource(entry) : entry.source;
|
|
5277
|
+
return source !== null && !supportsAppendedSubpath(source);
|
|
5278
|
+
}
|
|
5060
5279
|
function appendFolderAndRef(source, skillPath, ref) {
|
|
5061
5280
|
if (!supportsAppendedSubpath(source)) return formatSourceInput(source, ref);
|
|
5062
5281
|
const folder = deriveSkillFolder(skillPath);
|
|
@@ -5180,18 +5399,26 @@ async function runList(args) {
|
|
|
5180
5399
|
global: scope,
|
|
5181
5400
|
agentFilter
|
|
5182
5401
|
});
|
|
5402
|
+
const cwd = process.cwd();
|
|
5403
|
+
const lockedSkills = scope ? await getAllLockedSkills() : (await readLocalLock(cwd)).skills;
|
|
5404
|
+
const lockEntriesBySanitizedName = new Map(Object.entries(lockedSkills).map(([name, entry]) => [sanitizeName(name), entry]));
|
|
5405
|
+
const getLockEntry = (skillName) => lockedSkills[skillName] ?? lockEntriesBySanitizedName.get(sanitizeName(skillName));
|
|
5183
5406
|
if (options.json) {
|
|
5184
|
-
const jsonOutput = installedSkills.map((skill) =>
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5407
|
+
const jsonOutput = installedSkills.map((skill) => {
|
|
5408
|
+
const lockEntry = getLockEntry(skill.name);
|
|
5409
|
+
return {
|
|
5410
|
+
name: skill.name,
|
|
5411
|
+
path: skill.canonicalPath,
|
|
5412
|
+
scope: skill.scope,
|
|
5413
|
+
agents: skill.agents.map((a) => agents[a].displayName),
|
|
5414
|
+
source: lockEntry?.source ?? null,
|
|
5415
|
+
sourceUrl: lockEntry?.sourceUrl ?? null,
|
|
5416
|
+
sourceType: lockEntry?.sourceType ?? null
|
|
5417
|
+
};
|
|
5418
|
+
});
|
|
5190
5419
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
5191
5420
|
return;
|
|
5192
5421
|
}
|
|
5193
|
-
const lockedSkills = await getAllLockedSkills();
|
|
5194
|
-
const cwd = process.cwd();
|
|
5195
5422
|
const scopeLabel = scope ? "Global" : "Project";
|
|
5196
5423
|
if (installedSkills.length === 0) {
|
|
5197
5424
|
if (options.json) {
|
|
@@ -5210,14 +5437,17 @@ async function runList(args) {
|
|
|
5210
5437
|
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$2}`;
|
|
5211
5438
|
const paddedName = sanitizeMetadata(skill.name).padEnd(maxNameLength);
|
|
5212
5439
|
const paddedPath = shortPath.padEnd(maxPathLength);
|
|
5213
|
-
|
|
5440
|
+
const source = getLockEntry(skill.name)?.source ?? null;
|
|
5441
|
+
const sourceLabel = source ? sanitizeMetadata(source) : "local";
|
|
5442
|
+
console.log(`${prefix}${CYAN}${paddedName}${RESET$2} ${DIM$2}${paddedPath}${RESET$2}`);
|
|
5443
|
+
console.log(`${prefix} ${DIM$2}Agents:${RESET$2} ${agentInfo} ${DIM$2}Source:${RESET$2} ${sourceLabel}`);
|
|
5214
5444
|
}
|
|
5215
5445
|
console.log(`${BOLD$2}${scopeLabel} Skills${RESET$2}`);
|
|
5216
5446
|
console.log();
|
|
5217
5447
|
const groupedSkills = {};
|
|
5218
5448
|
const ungroupedSkills = [];
|
|
5219
5449
|
for (const skill of installedSkills) {
|
|
5220
|
-
const lockEntry =
|
|
5450
|
+
const lockEntry = getLockEntry(skill.name);
|
|
5221
5451
|
if (lockEntry?.pluginName) {
|
|
5222
5452
|
const group = lockEntry.pluginName;
|
|
5223
5453
|
if (!groupedSkills[group]) groupedSkills[group] = [];
|
|
@@ -5269,6 +5499,17 @@ async function runList(args) {
|
|
|
5269
5499
|
console.log();
|
|
5270
5500
|
}
|
|
5271
5501
|
}
|
|
5502
|
+
function resolveSkillsToRemove(requested, folderNames, lockKeys = []) {
|
|
5503
|
+
const identityBySanitized = /* @__PURE__ */ new Map();
|
|
5504
|
+
for (const folder of folderNames) identityBySanitized.set(sanitizeName(folder), folder);
|
|
5505
|
+
for (const key of lockKeys) identityBySanitized.set(sanitizeName(key), key);
|
|
5506
|
+
const matched = /* @__PURE__ */ new Set();
|
|
5507
|
+
for (const name of requested) {
|
|
5508
|
+
const hit = identityBySanitized.get(sanitizeName(name));
|
|
5509
|
+
if (hit) matched.add(hit);
|
|
5510
|
+
}
|
|
5511
|
+
return Array.from(matched);
|
|
5512
|
+
}
|
|
5272
5513
|
async function removeCommand(skillNames, options) {
|
|
5273
5514
|
const agentResult = await detectAgent();
|
|
5274
5515
|
if (agentResult.isAgent) {
|
|
@@ -5298,7 +5539,10 @@ async function removeCommand(skillNames, options) {
|
|
|
5298
5539
|
}
|
|
5299
5540
|
const installedSkills = Array.from(skillNamesSet).sort();
|
|
5300
5541
|
spinner$1.stop(`Found ${installedSkills.length} unique installed skill(s)`);
|
|
5301
|
-
|
|
5542
|
+
const lockSkillsKeys = isGlobal ? Object.keys((await readSkillLock()).skills) : Object.keys((await readLocalLock(cwd)).skills);
|
|
5543
|
+
const requestedSkills = options.all ? [...installedSkills, ...lockSkillsKeys] : skillNames;
|
|
5544
|
+
const resolvedRequestedSkills = options.all || skillNames.length > 0 ? resolveSkillsToRemove(requestedSkills, installedSkills, lockSkillsKeys) : [];
|
|
5545
|
+
if (installedSkills.length === 0 && resolvedRequestedSkills.length === 0) {
|
|
5302
5546
|
outro(import_picocolors.default.yellow("No skills found to remove."));
|
|
5303
5547
|
return;
|
|
5304
5548
|
}
|
|
@@ -5312,9 +5556,9 @@ async function removeCommand(skillNames, options) {
|
|
|
5312
5556
|
}
|
|
5313
5557
|
}
|
|
5314
5558
|
let selectedSkills = [];
|
|
5315
|
-
if (options.all) selectedSkills =
|
|
5559
|
+
if (options.all) selectedSkills = resolvedRequestedSkills;
|
|
5316
5560
|
else if (skillNames.length > 0) {
|
|
5317
|
-
selectedSkills =
|
|
5561
|
+
selectedSkills = resolvedRequestedSkills;
|
|
5318
5562
|
if (selectedSkills.length === 0) {
|
|
5319
5563
|
log.error(`No matching skills found for: ${skillNames.join(", ")}`);
|
|
5320
5564
|
return;
|
|
@@ -5333,7 +5577,7 @@ async function removeCommand(skillNames, options) {
|
|
|
5333
5577
|
cancel("Removal cancelled");
|
|
5334
5578
|
process.exit(0);
|
|
5335
5579
|
}
|
|
5336
|
-
selectedSkills = selected;
|
|
5580
|
+
selectedSkills = resolveSkillsToRemove(selected, installedSkills, lockSkillsKeys);
|
|
5337
5581
|
}
|
|
5338
5582
|
let targetAgents;
|
|
5339
5583
|
if (options.agent && options.agent.length > 0) targetAgents = options.agent;
|
|
@@ -5397,10 +5641,19 @@ async function removeCommand(skillNames, options) {
|
|
|
5397
5641
|
recursive: true,
|
|
5398
5642
|
force: true
|
|
5399
5643
|
});
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5644
|
+
let effectiveSource = "local";
|
|
5645
|
+
let effectiveSourceType = "local";
|
|
5646
|
+
if (isGlobal) {
|
|
5647
|
+
const lockEntry = await getSkillFromLock(skillName);
|
|
5648
|
+
effectiveSource = lockEntry?.source || "local";
|
|
5649
|
+
effectiveSourceType = lockEntry?.sourceType || "local";
|
|
5650
|
+
await removeSkillFromLock(skillName);
|
|
5651
|
+
} else {
|
|
5652
|
+
const lockEntry = (await readLocalLock(cwd)).skills[skillName];
|
|
5653
|
+
effectiveSource = lockEntry?.source || "local";
|
|
5654
|
+
effectiveSourceType = lockEntry?.sourceType || "local";
|
|
5655
|
+
await removeSkillFromLocalLock(skillName, cwd);
|
|
5656
|
+
}
|
|
5404
5657
|
results.push({
|
|
5405
5658
|
skill: skillName,
|
|
5406
5659
|
success: true,
|
|
@@ -5670,7 +5923,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
5670
5923
|
console.log(` ${DIM$1}✗ Failed to fetch tree for ${source}${RESET$1}`);
|
|
5671
5924
|
continue;
|
|
5672
5925
|
}
|
|
5673
|
-
const discoveredPaths =
|
|
5926
|
+
const discoveredPaths = tree.tree.filter((entry) => entry.type === "blob").map((entry) => entry.path);
|
|
5674
5927
|
const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
|
|
5675
5928
|
const deletedSkillSet = new Set(deletedSkills);
|
|
5676
5929
|
for (const { name: skillName, entry } of itemsForSource) {
|
|
@@ -5685,7 +5938,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
5685
5938
|
continue;
|
|
5686
5939
|
}
|
|
5687
5940
|
tempDir = await cloneRepo(sourceUrl, firstEntry.ref);
|
|
5688
|
-
const discoveredPaths = (await discoverSkills(tempDir)).map((skill) => {
|
|
5941
|
+
const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((skill) => {
|
|
5689
5942
|
return join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/");
|
|
5690
5943
|
});
|
|
5691
5944
|
const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
|
|
@@ -5750,10 +6003,12 @@ async function updateGlobalSkills(options = {}) {
|
|
|
5750
6003
|
console.log(` ${DIM$1}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET$1}`);
|
|
5751
6004
|
continue;
|
|
5752
6005
|
}
|
|
6006
|
+
const fullDepthArgs = shouldUseFullDepthForUpdate(update.entry) ? ["--full-depth"] : [];
|
|
5753
6007
|
if (spawnSync(process.execPath, [
|
|
5754
6008
|
cliEntry,
|
|
5755
6009
|
"add",
|
|
5756
6010
|
installUrl,
|
|
6011
|
+
...fullDepthArgs,
|
|
5757
6012
|
"-g",
|
|
5758
6013
|
"-y"
|
|
5759
6014
|
], {
|
|
@@ -5851,7 +6106,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
5851
6106
|
}
|
|
5852
6107
|
try {
|
|
5853
6108
|
tempDir = await cloneRepo(sourceUrl, ref);
|
|
5854
|
-
const discoveredPaths = (await discoverSkills(tempDir)).map((s) => {
|
|
6109
|
+
const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((s) => {
|
|
5855
6110
|
return join(relative(tempDir, s.path), "SKILL.md").split(sep).join("/");
|
|
5856
6111
|
});
|
|
5857
6112
|
deletedSkills = await checkAndPromptForDeletions(source, allLockedForSource, localLock.skills, false, options, discoveredPaths);
|
|
@@ -5871,6 +6126,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
5871
6126
|
continue;
|
|
5872
6127
|
}
|
|
5873
6128
|
const subagentArgs = skill.entry.subagents?.length ? ["--subagent", ...skill.entry.subagents.map((s) => s === "" ? "root" : s)] : [];
|
|
6129
|
+
const fullDepthArgs = shouldUseFullDepthForUpdate(skill.entry) ? ["--full-depth"] : [];
|
|
5874
6130
|
if (spawnSync(process.execPath, [
|
|
5875
6131
|
cliEntry,
|
|
5876
6132
|
"add",
|
|
@@ -5878,6 +6134,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
5878
6134
|
"--skill",
|
|
5879
6135
|
skill.name,
|
|
5880
6136
|
...subagentArgs,
|
|
6137
|
+
...fullDepthArgs,
|
|
5881
6138
|
"-y"
|
|
5882
6139
|
], {
|
|
5883
6140
|
stdio: [
|
|
@@ -6558,6 +6815,11 @@ async function main() {
|
|
|
6558
6815
|
}
|
|
6559
6816
|
const command = args[0];
|
|
6560
6817
|
const restArgs = args.slice(1);
|
|
6818
|
+
if (command !== "--help" && command !== "-h" && command !== "--version" && command !== "-v" && (restArgs.includes("--help") || restArgs.includes("-h"))) {
|
|
6819
|
+
if (command === "remove" || command === "rm" || command === "r") showRemoveHelp();
|
|
6820
|
+
else showHelp();
|
|
6821
|
+
return;
|
|
6822
|
+
}
|
|
6561
6823
|
switch (command) {
|
|
6562
6824
|
case "find":
|
|
6563
6825
|
case "search":
|
|
@@ -6597,14 +6859,11 @@ async function main() {
|
|
|
6597
6859
|
}
|
|
6598
6860
|
case "remove":
|
|
6599
6861
|
case "rm":
|
|
6600
|
-
case "r":
|
|
6601
|
-
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
6602
|
-
showRemoveHelp();
|
|
6603
|
-
break;
|
|
6604
|
-
}
|
|
6862
|
+
case "r": {
|
|
6605
6863
|
const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
|
|
6606
6864
|
await removeCommand(skills, removeOptions);
|
|
6607
6865
|
break;
|
|
6866
|
+
}
|
|
6608
6867
|
case "experimental_sync": {
|
|
6609
6868
|
if (!inAgent) showLogo();
|
|
6610
6869
|
const { options: syncOptions } = parseSyncOptions(restArgs);
|
|
@@ -6631,6 +6890,7 @@ async function main() {
|
|
|
6631
6890
|
default:
|
|
6632
6891
|
console.log(`Unknown command: ${command}`);
|
|
6633
6892
|
console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
|
|
6893
|
+
process.exitCode = 1;
|
|
6634
6894
|
}
|
|
6635
6895
|
}
|
|
6636
6896
|
main().finally(() => flushTelemetry().then(() => process.exit(process.exitCode ?? 0)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skills",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.20",
|
|
4
4
|
"description": "The open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,12 +67,14 @@
|
|
|
67
67
|
"gemini-cli",
|
|
68
68
|
"github-copilot",
|
|
69
69
|
"goose",
|
|
70
|
+
"grok",
|
|
70
71
|
"hermes-agent",
|
|
71
72
|
"inference-sh",
|
|
72
73
|
"jazz",
|
|
73
74
|
"junie",
|
|
74
75
|
"iflow-cli",
|
|
75
76
|
"kilo",
|
|
77
|
+
"kimchi",
|
|
76
78
|
"kimi-code-cli",
|
|
77
79
|
"kiro-cli",
|
|
78
80
|
"kode",
|
|
@@ -136,7 +138,7 @@
|
|
|
136
138
|
"xdg-basedir": "^5.1.0"
|
|
137
139
|
},
|
|
138
140
|
"engines": {
|
|
139
|
-
"node": ">=
|
|
141
|
+
"node": ">=22.20.0"
|
|
140
142
|
},
|
|
141
143
|
"packageManager": "pnpm@10.17.1",
|
|
142
144
|
"dependencies": {
|