skillmds 1.3.7 → 1.3.9
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/CHANGELOG.md +12 -0
- package/assets/skillmd-banner.jpg +0 -0
- package/dist/cli.js +875 -317
- package/dist/mcp.js +16 -17
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -36,39 +36,622 @@ import {
|
|
|
36
36
|
// src/index.ts
|
|
37
37
|
import { Command as Command13 } from "commander";
|
|
38
38
|
import { readFileSync as readFileSync3 } from "fs";
|
|
39
|
-
import
|
|
39
|
+
import pc17 from "picocolors";
|
|
40
40
|
|
|
41
41
|
// src/commands/lint.ts
|
|
42
42
|
import { Command } from "commander";
|
|
43
43
|
import { writeFileSync } from "fs";
|
|
44
44
|
import { homedir as homedir2 } from "os";
|
|
45
45
|
import { resolve as resolve2 } from "path";
|
|
46
|
+
import pc4 from "picocolors";
|
|
47
|
+
|
|
48
|
+
// src/prompts.ts
|
|
49
|
+
import { ConfirmPrompt, MultiSelectPrompt, PasswordPrompt, SelectPrompt, TextPrompt, block } from "@clack/core";
|
|
46
50
|
import pc2 from "picocolors";
|
|
47
|
-
|
|
51
|
+
|
|
52
|
+
// src/brand.ts
|
|
53
|
+
import pc from "picocolors";
|
|
54
|
+
|
|
55
|
+
// src/env.ts
|
|
56
|
+
function detectHostAgent(env = process.env) {
|
|
57
|
+
if (env.CLAUDECODE) return "claude-code";
|
|
58
|
+
if (env.CURSOR_AGENT || env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec") return "cursor";
|
|
59
|
+
if (env.CODEX_SANDBOX || env.CODEX_THREAD_ID) return "codex";
|
|
60
|
+
if (env.GEMINI_CLI) return "gemini-cli";
|
|
61
|
+
if (env.CI && env.CI !== "false" && env.CI !== "0") return "ci";
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
var realTerminal = () => ({
|
|
65
|
+
stdinTTY: Boolean(process.stdin.isTTY),
|
|
66
|
+
stdoutTTY: Boolean(process.stdout.isTTY),
|
|
67
|
+
env: process.env
|
|
68
|
+
});
|
|
69
|
+
function isInteractive(flags, term = realTerminal()) {
|
|
70
|
+
if (flags.yes || flags.json) return false;
|
|
71
|
+
if (!term.stdinTTY || !term.stdoutTTY) return false;
|
|
72
|
+
return detectHostAgent(term.env) === null;
|
|
73
|
+
}
|
|
74
|
+
function scopesFor(flags) {
|
|
75
|
+
return flags.global && !flags.project ? [true] : flags.project && !flags.global ? [false] : [false, true];
|
|
76
|
+
}
|
|
77
|
+
function nonInteractiveHint(flags) {
|
|
78
|
+
return `Interactive prompt required but stdin is not a terminal. Nothing was changed. Re-run with ${flags} to run non-interactively.`;
|
|
79
|
+
}
|
|
80
|
+
function promptIf(flags, prompt) {
|
|
81
|
+
const term = { stdinTTY: Boolean(process.stdin.isTTY), stdoutTTY: Boolean(process.stdout.isTTY), env: flags.env ?? process.env };
|
|
82
|
+
return isInteractive(flags, term) ? { prompt } : {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/brand.ts
|
|
86
|
+
function unicodeSupported(env = process.env, platform = process.platform) {
|
|
87
|
+
if (platform !== "win32") return env.TERM !== "linux";
|
|
88
|
+
return Boolean(env.CI) || Boolean(env.WT_SESSION) || Boolean(env.TERMINUS_SUBLIME) || env.ConEmuTask === "{cmd::Cmder}" || env.TERM_PROGRAM === "Terminus-Sublime" || env.TERM_PROGRAM === "vscode" || env.TERM === "xterm-256color" || env.TERM === "alacritty" || env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
89
|
+
}
|
|
90
|
+
function truecolor(env = process.env) {
|
|
91
|
+
if (/^(truecolor|24bit)$/i.test(env.COLORTERM ?? "")) return true;
|
|
92
|
+
if (env.WT_SESSION || env.KITTY_WINDOW_ID) return true;
|
|
93
|
+
if (/^(vscode|iTerm\.app|WezTerm|Hyper|ghostty)$/.test(env.TERM_PROGRAM ?? "")) return true;
|
|
94
|
+
return /(truecolor|24bit|direct|kitty|ghostty)/i.test(env.TERM ?? "");
|
|
95
|
+
}
|
|
96
|
+
function orange(text2, env = process.env, colour = pc.isColorSupported) {
|
|
97
|
+
if (!colour) return text2;
|
|
98
|
+
const open = truecolor(env) ? "\x1B[38;2;214;83;41m" : "\x1B[38;5;166m";
|
|
99
|
+
return `${open}${text2}\x1B[39m`;
|
|
100
|
+
}
|
|
101
|
+
function canAnimate(stream, env = process.env) {
|
|
102
|
+
return Boolean(stream.isTTY) && detectHostAgent(env) === null && env.TERM !== "dumb" && unicodeSupported(env);
|
|
103
|
+
}
|
|
104
|
+
var SPINNERS = {
|
|
105
|
+
drop: { frames: [" ", "\u2580 ", "\u2584 ", "\u2584\u2580", "\u2584\u2580", "\u2584\u2580", "\u2584\u2580", "\u2584\u2580", " "], interval: 90 },
|
|
106
|
+
hop: { frames: ["\u2584\u2580", "\u2580\u2584"], interval: 240 },
|
|
107
|
+
walk: { frames: ["\u2584\u2580 ", " \u2584\u2580 ", " \u2584\u2580", " \u2584", " ", "\u2580 "], interval: 110 }
|
|
108
|
+
};
|
|
109
|
+
var ASCII_SPINNER = { frames: ["\u2022", "o", "O", "0"], interval: 120 };
|
|
110
|
+
async function waitFor(kind, message, work, opts = {}) {
|
|
111
|
+
const stream = opts.stream ?? process.stderr;
|
|
112
|
+
const env = opts.env ?? process.env;
|
|
113
|
+
const job = typeof work === "function" ? work() : work;
|
|
114
|
+
const show = !opts.json && canAnimate(stream, env) && canAnimate(opts.also ?? process.stdout, env);
|
|
115
|
+
if (!show) return job;
|
|
116
|
+
const { frames, interval } = SPINNERS[kind];
|
|
117
|
+
const text2 = message.replace(/[.…]+$/, "");
|
|
118
|
+
let i = 0;
|
|
119
|
+
const draw = () => stream.write(`\r\x1B[2K${orange(frames[i++ % frames.length], env)} ${text2}\u2026`);
|
|
120
|
+
stream.write("\x1B[?25l");
|
|
121
|
+
draw();
|
|
122
|
+
const timer = setInterval(draw, interval);
|
|
123
|
+
const restore = () => stream.write("\x1B[?25h");
|
|
124
|
+
process.once("exit", restore);
|
|
125
|
+
try {
|
|
126
|
+
return await job;
|
|
127
|
+
} finally {
|
|
128
|
+
clearInterval(timer);
|
|
129
|
+
stream.write("\r\x1B[2K\x1B[?25h");
|
|
130
|
+
process.removeListener("exit", restore);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
var LETTERS = [
|
|
134
|
+
{ x: 0, rows: { 3: ".########", 4: "#########", 5: "##.......", 6: "########.", 7: ".########", 8: ".......##", 9: "#########", 10: "########." } },
|
|
135
|
+
// s
|
|
136
|
+
{ x: 10, rows: { 0: "##", 1: "##", 2: "##", 3: "##...##", 4: "##..##", 5: "##.##", 6: "####", 7: "####", 8: "##.##", 9: "##..##", 10: "##...##" } },
|
|
137
|
+
// k
|
|
138
|
+
{ x: 18, rows: { 0: "##", 1: "##", 3: "##", 4: "##", 5: "##", 6: "##", 7: "##", 8: "##", 9: "##", 10: "##" } },
|
|
139
|
+
// i
|
|
140
|
+
{ x: 21, rows: Object.fromEntries([...Array(11).keys()].map((r) => [r, "##"])) },
|
|
141
|
+
// l
|
|
142
|
+
{ x: 24, rows: Object.fromEntries([...Array(11).keys()].map((r) => [r, "##"])) },
|
|
143
|
+
// l
|
|
144
|
+
{ x: 28, md: true, rows: { 3: ".#########", 4: "##########", 5: "##...##...##", 6: "##...##...##", 7: "##...##...##", 8: "##...##...##", 9: "##...##...##", 10: "##...##...##" } },
|
|
145
|
+
// m
|
|
146
|
+
{ x: 41, md: true, rows: { 0: "......##", 1: "......##", 2: "......##", 3: ".#######", 4: "########", 5: "##....##", 6: "##....##", 7: "##....##", 8: "##....##", 9: "######", 10: "######" } }
|
|
147
|
+
// d
|
|
148
|
+
];
|
|
149
|
+
var WORDMARK_WIDTH = 54;
|
|
150
|
+
var CANVAS_ROWS = 12;
|
|
151
|
+
var SQUARES = [{ x: 50, y: 10 }, { x: 52, y: 8 }];
|
|
152
|
+
function wordmarkLines(squares = SQUARES, paint = (s2) => orange(s2)) {
|
|
153
|
+
const grid = [...Array(CANVAS_ROWS)].map(() => Array(WORDMARK_WIDTH).fill(null));
|
|
154
|
+
for (const L of LETTERS) {
|
|
155
|
+
for (const [r, row] of Object.entries(L.rows)) {
|
|
156
|
+
[...row].forEach((c, i) => {
|
|
157
|
+
if (c === "#") grid[Number(r) + 1][L.x + i] = L.md ? "md" : "ink";
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const sq of squares) {
|
|
162
|
+
for (let dy = 0; dy < 2; dy++) for (let dx = 0; dx < 2; dx++) {
|
|
163
|
+
const y = Math.round(sq.y) + dy, x = Math.round(sq.x) + dx;
|
|
164
|
+
if (y >= 0 && y < CANVAS_ROWS && x >= 0 && x < WORDMARK_WIDTH) grid[y][x] = "md";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const lines = [];
|
|
168
|
+
for (let r = 0; r < CANVAS_ROWS; r += 2) {
|
|
169
|
+
let line = "";
|
|
170
|
+
let run2 = "";
|
|
171
|
+
let runMd = false;
|
|
172
|
+
const flush = () => {
|
|
173
|
+
if (run2) line += runMd ? paint(run2) : run2;
|
|
174
|
+
run2 = "";
|
|
175
|
+
};
|
|
176
|
+
let end = WORDMARK_WIDTH;
|
|
177
|
+
while (end > 0 && !grid[r][end - 1] && !grid[r + 1][end - 1]) end--;
|
|
178
|
+
for (let x = 0; x < end; x++) {
|
|
179
|
+
const top = grid[r][x], bottom = grid[r + 1][x];
|
|
180
|
+
const md = (top ?? bottom) === "md";
|
|
181
|
+
const ch = top && bottom ? "\u2588" : top ? "\u2580" : bottom ? "\u2584" : " ";
|
|
182
|
+
if (ch !== " " && md !== runMd) flush();
|
|
183
|
+
if (ch !== " ") runMd = md;
|
|
184
|
+
run2 += ch;
|
|
185
|
+
}
|
|
186
|
+
flush();
|
|
187
|
+
lines.push(line);
|
|
188
|
+
}
|
|
189
|
+
return lines;
|
|
190
|
+
}
|
|
191
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
192
|
+
var easeIn = (t) => t * t;
|
|
193
|
+
async function playLogo(opts = {}) {
|
|
194
|
+
const out = opts.out ?? process.stdout;
|
|
195
|
+
const input = opts.input ?? process.stdin;
|
|
196
|
+
const env = opts.env ?? process.env;
|
|
197
|
+
const indent = opts.indent ?? " ";
|
|
198
|
+
const animate = opts.animate ?? (canAnimate(out, env) && pc.isColorSupported && Boolean(input.isTTY));
|
|
199
|
+
const show = (lines) => lines.map((l) => l ? indent + l : "").join("\n");
|
|
200
|
+
if (!animate) {
|
|
201
|
+
out.write(`${show(wordmarkLines())}
|
|
202
|
+
`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
let skipped = false;
|
|
206
|
+
const onKey = (key) => {
|
|
207
|
+
skipped = true;
|
|
208
|
+
if (key.toString() === "") {
|
|
209
|
+
out.write("\x1B[?25h\n");
|
|
210
|
+
process.exit(130);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const restoreCursor = () => out.write("\x1B[?25h");
|
|
214
|
+
out.write("\x1B[?25l");
|
|
215
|
+
process.once("exit", restoreCursor);
|
|
216
|
+
input.setRawMode?.(true);
|
|
217
|
+
input.on("data", onKey);
|
|
218
|
+
input.resume();
|
|
219
|
+
let drawn = 0;
|
|
220
|
+
const paint = (squares) => {
|
|
221
|
+
const lines = wordmarkLines(squares);
|
|
222
|
+
out.write(`${drawn ? `\x1B[${drawn}A` : ""}${lines.map((l) => `\x1B[2K${l ? indent + l : ""}`).join("\n")}
|
|
223
|
+
`);
|
|
224
|
+
drawn = lines.length;
|
|
225
|
+
};
|
|
226
|
+
try {
|
|
227
|
+
paint([]);
|
|
228
|
+
await sleep(160);
|
|
229
|
+
const [first, second] = SQUARES;
|
|
230
|
+
for (const [n, target] of [first, second].entries()) {
|
|
231
|
+
const landed = n === 0 ? [] : [first];
|
|
232
|
+
const steps = 9;
|
|
233
|
+
for (let s2 = 0; s2 <= steps && !skipped; s2++) {
|
|
234
|
+
paint([...landed, { x: target.x, y: -2 + (target.y + 2) * easeIn(s2 / steps) }]);
|
|
235
|
+
await sleep(22);
|
|
236
|
+
}
|
|
237
|
+
if (skipped) break;
|
|
238
|
+
paint([...landed, { x: target.x, y: target.y - 1 }]);
|
|
239
|
+
await sleep(40);
|
|
240
|
+
paint([...landed, target]);
|
|
241
|
+
await sleep(90);
|
|
242
|
+
}
|
|
243
|
+
} finally {
|
|
244
|
+
paint(SQUARES);
|
|
245
|
+
input.removeListener("data", onKey);
|
|
246
|
+
input.setRawMode?.(false);
|
|
247
|
+
input.pause();
|
|
248
|
+
out.write("\x1B[?25h");
|
|
249
|
+
process.removeListener("exit", restoreCursor);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function brandWord(env = process.env, colour = pc.isColorSupported) {
|
|
253
|
+
return `${colour ? pc.bold("skill") : "skill"}${colour ? pc.bold(orange("md", env, colour)) : "md"}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/prompts.ts
|
|
257
|
+
import { isCancel } from "@clack/core";
|
|
258
|
+
var unicode = unicodeSupported();
|
|
259
|
+
var s = (c, fallback) => unicode ? c : fallback;
|
|
260
|
+
var S_STEP_ACTIVE = s("\u25C6", "*");
|
|
261
|
+
var S_STEP_CANCEL = s("\u25A0", "x");
|
|
262
|
+
var S_STEP_ERROR = s("\u25B2", "x");
|
|
263
|
+
var S_STEP_SUBMIT = s("\u25C7", "o");
|
|
264
|
+
var S_BAR_START = s("\u250C", "T");
|
|
265
|
+
var S_BAR = s("\u2502", "|");
|
|
266
|
+
var S_BAR_END = s("\u2514", "\u2014");
|
|
267
|
+
var S_RADIO_ACTIVE = s("\u25CF", ">");
|
|
268
|
+
var S_RADIO_INACTIVE = s("\u25CB", " ");
|
|
269
|
+
var S_CHECKBOX_ACTIVE = s("\u25FB", "[\u2022]");
|
|
270
|
+
var S_CHECKBOX_SELECTED = s("\u25FC", "[+]");
|
|
271
|
+
var S_CHECKBOX_INACTIVE = s("\u25FB", "[ ]");
|
|
272
|
+
var S_PASSWORD_MASK = s("\u25AA", "\u2022");
|
|
273
|
+
var S_BAR_H = s("\u2500", "-");
|
|
274
|
+
var S_CORNER_TOP_RIGHT = s("\u256E", "+");
|
|
275
|
+
var S_CONNECT_LEFT = s("\u251C", "+");
|
|
276
|
+
var S_CORNER_BOTTOM_RIGHT = s("\u256F", "+");
|
|
277
|
+
var S_INFO = s("\u25CF", "\u2022");
|
|
278
|
+
var S_SUCCESS = s("\u25C6", "*");
|
|
279
|
+
var S_WARN = s("\u25B2", "!");
|
|
280
|
+
var S_ERROR = s("\u25A0", "x");
|
|
281
|
+
var accent = (text2) => orange(text2);
|
|
282
|
+
var bar = pc2.gray(S_BAR);
|
|
283
|
+
var symbol = (state) => {
|
|
284
|
+
switch (state) {
|
|
285
|
+
case "initial":
|
|
286
|
+
case "active":
|
|
287
|
+
return accent(S_STEP_ACTIVE);
|
|
288
|
+
case "cancel":
|
|
289
|
+
return pc2.red(S_STEP_CANCEL);
|
|
290
|
+
case "error":
|
|
291
|
+
return pc2.yellow(S_STEP_ERROR);
|
|
292
|
+
case "submit":
|
|
293
|
+
return accent(S_STEP_SUBMIT);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
var skipLead = false;
|
|
297
|
+
function startFrameAfterLogo() {
|
|
298
|
+
skipLead = true;
|
|
299
|
+
}
|
|
300
|
+
var lead = () => {
|
|
301
|
+
if (skipLead) {
|
|
302
|
+
skipLead = false;
|
|
303
|
+
return "";
|
|
304
|
+
}
|
|
305
|
+
return `${bar}
|
|
306
|
+
`;
|
|
307
|
+
};
|
|
308
|
+
var hint = (text2) => text2 ? ` ${pc2.dim(text2)}` : "";
|
|
309
|
+
var keys = (text2) => pc2.dim(text2);
|
|
310
|
+
function limitOptions(params) {
|
|
311
|
+
const { cursor, options, style } = params;
|
|
312
|
+
const outputMaxItems = Math.max((process.stdout.rows ?? 24) - 4, 0);
|
|
313
|
+
const maxItems = Math.min(outputMaxItems, Math.max(params.maxItems ?? Infinity, 5));
|
|
314
|
+
let window = 0;
|
|
315
|
+
if (cursor >= window + maxItems - 3) window = Math.max(Math.min(cursor - maxItems + 3, options.length - maxItems), 0);
|
|
316
|
+
else if (cursor < window + 2) window = Math.max(cursor - 2, 0);
|
|
317
|
+
const topEllipsis = maxItems < options.length && window > 0;
|
|
318
|
+
const bottomEllipsis = maxItems < options.length && window + maxItems < options.length;
|
|
319
|
+
return options.slice(window, window + maxItems).map((option, i, arr) => i === 0 && topEllipsis || i === arr.length - 1 && bottomEllipsis ? pc2.dim("...") : style(option, i + window === cursor));
|
|
320
|
+
}
|
|
321
|
+
var text = (opts) => {
|
|
322
|
+
const top = lead();
|
|
323
|
+
return new TextPrompt({
|
|
324
|
+
validate: opts.validate,
|
|
325
|
+
placeholder: opts.placeholder,
|
|
326
|
+
defaultValue: opts.defaultValue,
|
|
327
|
+
initialValue: opts.initialValue,
|
|
328
|
+
render() {
|
|
329
|
+
const title = `${top}${symbol(this.state)} ${opts.message}
|
|
330
|
+
`;
|
|
331
|
+
const placeholder = opts.placeholder ? pc2.inverse(opts.placeholder[0]) + pc2.dim(opts.placeholder.slice(1)) : pc2.inverse(pc2.hidden("_"));
|
|
332
|
+
const value = !this.value ? placeholder : this.valueWithCursor;
|
|
333
|
+
switch (this.state) {
|
|
334
|
+
case "error":
|
|
335
|
+
return `${title.trim()}
|
|
336
|
+
${pc2.yellow(S_BAR)} ${value}
|
|
337
|
+
${pc2.yellow(S_BAR_END)} ${pc2.yellow(this.error)}
|
|
338
|
+
`;
|
|
339
|
+
case "submit":
|
|
340
|
+
return `${title}${bar} ${pc2.dim(this.value || opts.placeholder || "")}`;
|
|
341
|
+
case "cancel":
|
|
342
|
+
return `${title}${bar} ${pc2.strikethrough(pc2.dim(this.value ?? ""))}${this.value?.trim() ? `
|
|
343
|
+
${bar}` : ""}`;
|
|
344
|
+
default:
|
|
345
|
+
return `${title}${bar} ${value}
|
|
346
|
+
${pc2.gray(S_BAR_END)}
|
|
347
|
+
`;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}).prompt();
|
|
351
|
+
};
|
|
352
|
+
var password = (opts) => {
|
|
353
|
+
const top = lead();
|
|
354
|
+
return new PasswordPrompt({
|
|
355
|
+
validate: opts.validate,
|
|
356
|
+
mask: opts.mask ?? S_PASSWORD_MASK,
|
|
357
|
+
render() {
|
|
358
|
+
const title = `${top}${symbol(this.state)} ${opts.message}
|
|
359
|
+
`;
|
|
360
|
+
const masked = this.masked;
|
|
361
|
+
switch (this.state) {
|
|
362
|
+
case "error":
|
|
363
|
+
return `${title.trim()}
|
|
364
|
+
${pc2.yellow(S_BAR)} ${masked}
|
|
365
|
+
${pc2.yellow(S_BAR_END)} ${pc2.yellow(this.error)}
|
|
366
|
+
`;
|
|
367
|
+
case "submit":
|
|
368
|
+
return `${title}${bar} ${pc2.dim(masked)}`;
|
|
369
|
+
case "cancel":
|
|
370
|
+
return `${title}${bar} ${pc2.strikethrough(pc2.dim(masked ?? ""))}${masked ? `
|
|
371
|
+
${bar}` : ""}`;
|
|
372
|
+
default:
|
|
373
|
+
return `${title}${bar} ${this.valueWithCursor}
|
|
374
|
+
${pc2.gray(S_BAR_END)}
|
|
375
|
+
`;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}).prompt();
|
|
379
|
+
};
|
|
380
|
+
var confirm = (opts) => {
|
|
381
|
+
const active = opts.active ?? "Yes";
|
|
382
|
+
const inactive = opts.inactive ?? "No";
|
|
383
|
+
const top = lead();
|
|
384
|
+
return new ConfirmPrompt({
|
|
385
|
+
active,
|
|
386
|
+
inactive,
|
|
387
|
+
initialValue: opts.initialValue ?? true,
|
|
388
|
+
render() {
|
|
389
|
+
const title = `${top}${symbol(this.state)} ${opts.message}
|
|
390
|
+
`;
|
|
391
|
+
const value = this.value ? active : inactive;
|
|
392
|
+
switch (this.state) {
|
|
393
|
+
case "submit":
|
|
394
|
+
return `${title}${bar} ${pc2.dim(value)}`;
|
|
395
|
+
case "cancel":
|
|
396
|
+
return `${title}${bar} ${pc2.strikethrough(pc2.dim(value))}
|
|
397
|
+
${bar}`;
|
|
398
|
+
default: {
|
|
399
|
+
const on = (label2) => `${accent(S_RADIO_ACTIVE)} ${label2}`;
|
|
400
|
+
const off = (label2) => `${pc2.dim(S_RADIO_INACTIVE)} ${pc2.dim(label2)}`;
|
|
401
|
+
return `${title}${bar} ${this.value ? on(active) : off(active)} ${pc2.dim("/")} ${!this.value ? on(inactive) : off(inactive)}
|
|
402
|
+
${pc2.gray(S_BAR_END)}
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}).prompt();
|
|
408
|
+
};
|
|
409
|
+
var select = (opts) => {
|
|
410
|
+
const opt = (option, state) => {
|
|
411
|
+
const label2 = option.label ?? String(option.value);
|
|
412
|
+
switch (state) {
|
|
413
|
+
case "selected":
|
|
414
|
+
return pc2.dim(label2);
|
|
415
|
+
case "active":
|
|
416
|
+
return `${accent(S_RADIO_ACTIVE)} ${label2}${hint(option.hint)}`;
|
|
417
|
+
case "cancelled":
|
|
418
|
+
return pc2.strikethrough(pc2.dim(label2));
|
|
419
|
+
default:
|
|
420
|
+
return `${pc2.dim(S_RADIO_INACTIVE)} ${pc2.dim(label2)}`;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
const top = lead();
|
|
424
|
+
return new SelectPrompt({
|
|
425
|
+
options: opts.options,
|
|
426
|
+
initialValue: opts.initialValue,
|
|
427
|
+
render() {
|
|
428
|
+
const title = `${top}${symbol(this.state)} ${opts.message}
|
|
429
|
+
`;
|
|
430
|
+
switch (this.state) {
|
|
431
|
+
case "submit":
|
|
432
|
+
return `${title}${bar} ${opt(this.options[this.cursor], "selected")}`;
|
|
433
|
+
case "cancel":
|
|
434
|
+
return `${title}${bar} ${opt(this.options[this.cursor], "cancelled")}
|
|
435
|
+
${bar}`;
|
|
436
|
+
default:
|
|
437
|
+
return `${title}${bar} ${limitOptions({
|
|
438
|
+
cursor: this.cursor,
|
|
439
|
+
options: this.options,
|
|
440
|
+
maxItems: opts.maxItems,
|
|
441
|
+
style: (item, isActive) => opt(item, isActive ? "active" : "inactive")
|
|
442
|
+
}).join(`
|
|
443
|
+
${bar} `)}
|
|
444
|
+
${pc2.gray(S_BAR_END)} ${keys("\u2191/\u2193 to move \xB7 enter to choose")}
|
|
445
|
+
`;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}).prompt();
|
|
449
|
+
};
|
|
450
|
+
var multiselect = (opts) => {
|
|
451
|
+
const opt = (option, state) => {
|
|
452
|
+
const label2 = option.label ?? String(option.value);
|
|
453
|
+
switch (state) {
|
|
454
|
+
case "active":
|
|
455
|
+
return `${accent(S_CHECKBOX_ACTIVE)} ${label2}${hint(option.hint)}`;
|
|
456
|
+
case "selected":
|
|
457
|
+
return `${accent(S_CHECKBOX_SELECTED)} ${pc2.dim(label2)}`;
|
|
458
|
+
case "cancelled":
|
|
459
|
+
return pc2.strikethrough(pc2.dim(label2));
|
|
460
|
+
case "active-selected":
|
|
461
|
+
return `${accent(S_CHECKBOX_SELECTED)} ${label2}${hint(option.hint)}`;
|
|
462
|
+
case "submitted":
|
|
463
|
+
return pc2.dim(label2);
|
|
464
|
+
default:
|
|
465
|
+
return `${pc2.dim(S_CHECKBOX_INACTIVE)} ${pc2.dim(label2)}`;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const top = lead();
|
|
469
|
+
return new MultiSelectPrompt({
|
|
470
|
+
options: opts.options,
|
|
471
|
+
initialValues: opts.initialValues,
|
|
472
|
+
required: opts.required ?? true,
|
|
473
|
+
cursorAt: opts.cursorAt,
|
|
474
|
+
validate(selected) {
|
|
475
|
+
if (this.required && selected.length === 0) {
|
|
476
|
+
return `Please select at least one option.
|
|
477
|
+
${pc2.reset(pc2.dim("Press space to select, enter to confirm"))}`;
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
render() {
|
|
481
|
+
const title = `${top}${symbol(this.state)} ${opts.message}
|
|
482
|
+
`;
|
|
483
|
+
const styleOption = (option, isActive) => {
|
|
484
|
+
const selected = this.value.includes(option.value);
|
|
485
|
+
if (isActive && selected) return opt(option, "active-selected");
|
|
486
|
+
if (selected) return opt(option, "selected");
|
|
487
|
+
return opt(option, isActive ? "active" : "inactive");
|
|
488
|
+
};
|
|
489
|
+
const list = (edge) => limitOptions({
|
|
490
|
+
options: this.options,
|
|
491
|
+
cursor: this.cursor,
|
|
492
|
+
maxItems: opts.maxItems,
|
|
493
|
+
style: styleOption
|
|
494
|
+
}).join(`
|
|
495
|
+
${edge} `);
|
|
496
|
+
switch (this.state) {
|
|
497
|
+
case "submit":
|
|
498
|
+
return `${title}${bar} ${this.options.filter(({ value }) => this.value.includes(value)).map((o) => opt(o, "submitted")).join(pc2.dim(", ")) || pc2.dim("none")}`;
|
|
499
|
+
case "cancel": {
|
|
500
|
+
const label2 = this.options.filter(({ value }) => this.value.includes(value)).map((o) => opt(o, "cancelled")).join(pc2.dim(", "));
|
|
501
|
+
return `${title}${bar} ${label2.trim() ? `${label2}
|
|
502
|
+
${bar}` : ""}`;
|
|
503
|
+
}
|
|
504
|
+
case "error": {
|
|
505
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc2.yellow(S_BAR_END)} ${pc2.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
506
|
+
return `${title}${pc2.yellow(S_BAR)} ${list(pc2.yellow(S_BAR))}
|
|
507
|
+
${footer}
|
|
508
|
+
`;
|
|
509
|
+
}
|
|
510
|
+
default:
|
|
511
|
+
return `${title}${bar} ${list(bar)}
|
|
512
|
+
${pc2.gray(S_BAR_END)} ${keys("space to select \xB7 enter to confirm")}
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}).prompt();
|
|
517
|
+
};
|
|
518
|
+
var strip = (str) => str.replace(ansiRegex(), "");
|
|
519
|
+
var note = (message = "", title = "") => {
|
|
520
|
+
const lines = `
|
|
521
|
+
${message}
|
|
522
|
+
`.split("\n");
|
|
523
|
+
const titleLen = strip(title).length;
|
|
524
|
+
const len = Math.max(lines.reduce((max, ln) => Math.max(max, strip(ln).length), 0), titleLen) + 2;
|
|
525
|
+
const msg2 = lines.map((ln) => `${bar} ${pc2.dim(ln)}${" ".repeat(len - strip(ln).length)}${bar}`).join("\n");
|
|
526
|
+
process.stdout.write(
|
|
527
|
+
`${lead()}${accent(S_STEP_SUBMIT)} ${pc2.reset(title)} ${pc2.gray(S_BAR_H.repeat(Math.max(len - titleLen - 1, 1)) + S_CORNER_TOP_RIGHT)}
|
|
528
|
+
${msg2}
|
|
529
|
+
${pc2.gray(S_CONNECT_LEFT + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}
|
|
530
|
+
`
|
|
531
|
+
);
|
|
532
|
+
};
|
|
533
|
+
var cancel = (message = "") => {
|
|
534
|
+
process.stdout.write(`${pc2.gray(S_BAR_END)} ${pc2.red(message)}
|
|
535
|
+
|
|
536
|
+
`);
|
|
537
|
+
};
|
|
538
|
+
var intro = (title = "") => {
|
|
539
|
+
skipLead = false;
|
|
540
|
+
process.stdout.write(`${pc2.gray(S_BAR_START)} ${title}
|
|
541
|
+
`);
|
|
542
|
+
};
|
|
543
|
+
var outro = (message = "") => {
|
|
544
|
+
process.stdout.write(`${bar}
|
|
545
|
+
${pc2.gray(S_BAR_END)} ${message}
|
|
546
|
+
|
|
547
|
+
`);
|
|
548
|
+
};
|
|
549
|
+
var log = {
|
|
550
|
+
message: (message = "", { symbol: mark = bar } = {}) => {
|
|
551
|
+
const top = lead().replace(/\n$/, "");
|
|
552
|
+
const parts = top ? [top] : [];
|
|
553
|
+
if (message) {
|
|
554
|
+
const [first, ...rest] = message.split("\n");
|
|
555
|
+
parts.push(`${mark} ${first}`, ...rest.map((ln) => `${bar} ${ln}`));
|
|
556
|
+
}
|
|
557
|
+
process.stdout.write(`${parts.join("\n")}
|
|
558
|
+
`);
|
|
559
|
+
},
|
|
560
|
+
info: (message) => log.message(message, { symbol: accent(S_INFO) }),
|
|
561
|
+
success: (message) => log.message(message, { symbol: pc2.green(S_SUCCESS) }),
|
|
562
|
+
step: (message) => log.message(message, { symbol: accent(S_STEP_SUBMIT) }),
|
|
563
|
+
warn: (message) => log.message(message, { symbol: pc2.yellow(S_WARN) }),
|
|
564
|
+
warning: (message) => log.warn(message),
|
|
565
|
+
error: (message) => log.message(message, { symbol: pc2.red(S_ERROR) })
|
|
566
|
+
};
|
|
567
|
+
var spinner = (kind = "drop") => {
|
|
568
|
+
const { frames, interval } = unicode ? SPINNERS[kind] : ASCII_SPINNER;
|
|
569
|
+
let unblock = () => {
|
|
570
|
+
};
|
|
571
|
+
let loop;
|
|
572
|
+
let active = false;
|
|
573
|
+
let message = "";
|
|
574
|
+
const handleExit = (code) => {
|
|
575
|
+
if (active) stop(code > 1 ? "Something went wrong" : "Canceled", code);
|
|
576
|
+
};
|
|
577
|
+
const onError = () => handleExit(2);
|
|
578
|
+
const onSignal = () => handleExit(1);
|
|
579
|
+
const registerHooks = () => {
|
|
580
|
+
process.on("uncaughtExceptionMonitor", onError);
|
|
581
|
+
process.on("unhandledRejection", onError);
|
|
582
|
+
process.on("SIGINT", onSignal);
|
|
583
|
+
process.on("SIGTERM", onSignal);
|
|
584
|
+
process.on("exit", handleExit);
|
|
585
|
+
};
|
|
586
|
+
const clearHooks = () => {
|
|
587
|
+
process.removeListener("uncaughtExceptionMonitor", onError);
|
|
588
|
+
process.removeListener("unhandledRejection", onError);
|
|
589
|
+
process.removeListener("SIGINT", onSignal);
|
|
590
|
+
process.removeListener("SIGTERM", onSignal);
|
|
591
|
+
process.removeListener("exit", handleExit);
|
|
592
|
+
};
|
|
593
|
+
const start = (msg2 = "") => {
|
|
594
|
+
active = true;
|
|
595
|
+
unblock = block();
|
|
596
|
+
message = msg2.replace(/[.…]+$/, "");
|
|
597
|
+
process.stdout.write(lead());
|
|
598
|
+
let i = 0;
|
|
599
|
+
const draw = () => {
|
|
600
|
+
process.stdout.write(`\r\x1B[J${accent(frames[i])} ${message}\u2026`);
|
|
601
|
+
i = (i + 1) % frames.length;
|
|
602
|
+
};
|
|
603
|
+
process.stdout.write("\x1B[?25l");
|
|
604
|
+
registerHooks();
|
|
605
|
+
draw();
|
|
606
|
+
loop = setInterval(draw, interval);
|
|
607
|
+
};
|
|
608
|
+
const stop = (msg2 = "", code = 0) => {
|
|
609
|
+
message = msg2 || message;
|
|
610
|
+
active = false;
|
|
611
|
+
if (loop) clearInterval(loop);
|
|
612
|
+
const mark = code === 0 ? pc2.green(S_STEP_SUBMIT) : code === 1 ? pc2.red(S_STEP_CANCEL) : pc2.red(S_STEP_ERROR);
|
|
613
|
+
process.stdout.write(`\r\x1B[J${mark} ${message}
|
|
614
|
+
\x1B[?25h`);
|
|
615
|
+
clearHooks();
|
|
616
|
+
unblock();
|
|
617
|
+
};
|
|
618
|
+
return { start, stop, message: (msg2 = "") => {
|
|
619
|
+
message = msg2 || message;
|
|
620
|
+
} };
|
|
621
|
+
};
|
|
622
|
+
function ansiRegex() {
|
|
623
|
+
const pattern = [
|
|
624
|
+
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
|
625
|
+
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
|
|
626
|
+
].join("|");
|
|
627
|
+
return new RegExp(pattern, "g");
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/commands/lint.ts
|
|
48
631
|
import { lint, toJson, toSarif, toGithub } from "@skillmds/core";
|
|
49
632
|
|
|
50
633
|
// src/ui.ts
|
|
51
634
|
import { homedir } from "os";
|
|
52
635
|
import { relative, resolve, sep } from "path";
|
|
53
|
-
import
|
|
54
|
-
var decorate = (
|
|
636
|
+
import pc3 from "picocolors";
|
|
637
|
+
var decorate = (text2, kind) => {
|
|
55
638
|
switch (kind) {
|
|
56
639
|
case "error":
|
|
57
|
-
return
|
|
640
|
+
return pc3.red(text2);
|
|
58
641
|
case "warn":
|
|
59
|
-
return
|
|
642
|
+
return pc3.yellow(text2);
|
|
60
643
|
case "info":
|
|
61
|
-
return
|
|
644
|
+
return pc3.cyan(text2);
|
|
62
645
|
case "ok":
|
|
63
|
-
return
|
|
646
|
+
return pc3.green(text2);
|
|
64
647
|
case "dim":
|
|
65
|
-
return
|
|
648
|
+
return pc3.dim(text2);
|
|
66
649
|
}
|
|
67
650
|
};
|
|
68
651
|
var SEV_COLOR = {
|
|
69
|
-
error:
|
|
70
|
-
warn:
|
|
71
|
-
info:
|
|
652
|
+
error: pc3.red,
|
|
653
|
+
warn: pc3.yellow,
|
|
654
|
+
info: pc3.cyan
|
|
72
655
|
};
|
|
73
656
|
var GLYPH = {
|
|
74
657
|
error: "\u2716",
|
|
@@ -84,7 +667,7 @@ var SEV_ORDER = ["error", "warn", "info"];
|
|
|
84
667
|
function chips(result) {
|
|
85
668
|
const bySev = { error: [], warn: [], info: [] };
|
|
86
669
|
for (const d of result.diagnostics) bySev[d.severity].push(d.id);
|
|
87
|
-
return SEV_ORDER.filter((
|
|
670
|
+
return SEV_ORDER.filter((s2) => bySev[s2].length).map((s2) => `${GLYPH[s2]} ${SEV_COLOR[s2](bySev[s2].join(" "))}`).join(" ");
|
|
88
671
|
}
|
|
89
672
|
var SEC_LABEL = {
|
|
90
673
|
network_calls: "Calls Network",
|
|
@@ -92,7 +675,7 @@ var SEC_LABEL = {
|
|
|
92
675
|
reads_secrets: "Reads Secrets"
|
|
93
676
|
};
|
|
94
677
|
var titleCase = (flag) => flag.split("_").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
95
|
-
var scoreColor = (
|
|
678
|
+
var scoreColor = (s2) => s2 >= 80 ? pc3.green : s2 >= 50 ? pc3.yellow : pc3.red;
|
|
96
679
|
function label(file) {
|
|
97
680
|
const parts = file.split(/[\\/]/).filter(Boolean);
|
|
98
681
|
const i = parts.lastIndexOf("SKILL.md");
|
|
@@ -103,27 +686,27 @@ function securityFlags(result) {
|
|
|
103
686
|
}
|
|
104
687
|
function secLabels(result) {
|
|
105
688
|
const flags = securityFlags(result);
|
|
106
|
-
return flags.map((f) =>
|
|
689
|
+
return flags.map((f) => pc3.magenta(`${SEC_GLYPH} ${SEC_LABEL[f] ?? titleCase(f)}`)).join(" ");
|
|
107
690
|
}
|
|
108
691
|
function statusDot(result) {
|
|
109
|
-
if (!result.ok) return
|
|
692
|
+
if (!result.ok) return pc3.red("\u25CF");
|
|
110
693
|
const attention = result.diagnostics.some((d) => d.severity === "warn") || securityFlags(result).length > 0;
|
|
111
|
-
return attention ?
|
|
694
|
+
return attention ? pc3.yellow("\u25CF") : pc3.green("\u25CF");
|
|
112
695
|
}
|
|
113
696
|
function failureBlock(file, result) {
|
|
114
|
-
const lines = [`${
|
|
697
|
+
const lines = [`${pc3.red(GLYPH.error)} ${pc3.bold(label(file))}`];
|
|
115
698
|
for (const d of result.diagnostics.filter((x) => x.severity === "error")) {
|
|
116
|
-
const loc = d.line !== void 0 ?
|
|
699
|
+
const loc = d.line !== void 0 ? pc3.dim(`:${d.line}`) : "";
|
|
117
700
|
lines.push(` ${SEV_COLOR.error(d.id)}${loc} ${d.message}`);
|
|
118
701
|
}
|
|
119
|
-
lines.push(` ${
|
|
702
|
+
lines.push(` ${pc3.dim(file)}`);
|
|
120
703
|
return lines.join("\n");
|
|
121
704
|
}
|
|
122
705
|
function renderOne(file, result) {
|
|
123
|
-
const status = result.ok ?
|
|
124
|
-
const out = [`${status} ${
|
|
706
|
+
const status = result.ok ? pc3.green("\u25CF PASS") : pc3.red("\u25CF FAIL");
|
|
707
|
+
const out = [`${status} ${pc3.bold(label(file))} ${pc3.dim("quality")} ${scoreColor(result.score)(`${result.score}/100`)}`];
|
|
125
708
|
for (const d of result.diagnostics) {
|
|
126
|
-
const loc = d.line !== void 0 ?
|
|
709
|
+
const loc = d.line !== void 0 ? pc3.dim(`:${d.line}`) : "";
|
|
127
710
|
out.push(` ${GLYPH[d.severity]} ${SEV_COLOR[d.severity](d.id)}${loc} ${d.message}`);
|
|
128
711
|
}
|
|
129
712
|
const sec = secLabels(result);
|
|
@@ -147,32 +730,32 @@ function renderMany(results) {
|
|
|
147
730
|
const failed = results.filter((r) => !r.result.ok).length;
|
|
148
731
|
const warned = results.filter((r) => r.result.ok && r.result.diagnostics.some((d) => d.severity === "warn")).length;
|
|
149
732
|
const clean = results.length - failed - warned;
|
|
150
|
-
const avg = Math.round(results.reduce((
|
|
733
|
+
const avg = Math.round(results.reduce((s2, r) => s2 + r.result.score, 0) / results.length);
|
|
151
734
|
out.push("");
|
|
152
|
-
const parts = [
|
|
153
|
-
if (warned) parts.push(
|
|
154
|
-
if (failed) parts.push(
|
|
155
|
-
out.push(`${parts.join(
|
|
156
|
-
if (anySec) out.push(
|
|
735
|
+
const parts = [pc3.green(`${clean} clean`)];
|
|
736
|
+
if (warned) parts.push(pc3.yellow(`${warned} warn`));
|
|
737
|
+
if (failed) parts.push(pc3.red(`${failed} fail`));
|
|
738
|
+
out.push(`${parts.join(pc3.dim(" \xB7 "))} ${pc3.dim("avg score")} ${scoreColor(avg)(`${avg}/100`)}`);
|
|
739
|
+
if (anySec) out.push(pc3.dim(`${SEC_GLYPH} = risky behaviour, factored into the score alongside spec correctness and completeness`));
|
|
157
740
|
const fails = results.filter((r) => !r.result.ok);
|
|
158
741
|
if (fails.length) {
|
|
159
742
|
out.push("");
|
|
160
|
-
out.push(
|
|
743
|
+
out.push(pc3.red(pc3.bold(`Failed (${fails.length}):`)));
|
|
161
744
|
for (const f of fails.slice(0, 25)) out.push(failureBlock(f.file, f.result));
|
|
162
|
-
if (fails.length > 25) out.push(
|
|
745
|
+
if (fails.length > 25) out.push(pc3.dim(` \u2026and ${fails.length - 25} more \u2014 run with --errors-only to see all`));
|
|
163
746
|
}
|
|
164
747
|
return out.join("\n");
|
|
165
748
|
}
|
|
166
749
|
function renderFailuresView(results) {
|
|
167
750
|
const fails = results.filter((r) => !r.result.ok);
|
|
168
|
-
if (!fails.length) return
|
|
751
|
+
if (!fails.length) return pc3.green(`${GLYPH.ok} No failures across ${results.length} skill${results.length === 1 ? "" : "s"}.`);
|
|
169
752
|
const out = fails.map((f) => failureBlock(f.file, f.result));
|
|
170
753
|
out.push("");
|
|
171
|
-
out.push(`${
|
|
754
|
+
out.push(`${pc3.red(`${fails.length} failed`)} ${pc3.dim(`of ${results.length}`)}`);
|
|
172
755
|
return out.join("\n");
|
|
173
756
|
}
|
|
174
757
|
function renderReport(results, opts = {}) {
|
|
175
|
-
if (results.length === 0) return
|
|
758
|
+
if (results.length === 0) return pc3.dim("No skills found.");
|
|
176
759
|
if (opts.errorsOnly) return renderFailuresView(results);
|
|
177
760
|
if (results.length === 1) return renderOne(results[0].file, results[0].result);
|
|
178
761
|
return renderMany(results);
|
|
@@ -186,11 +769,11 @@ function exitCodeFor(results, policy = {}) {
|
|
|
186
769
|
}
|
|
187
770
|
return 0;
|
|
188
771
|
}
|
|
189
|
-
function shortPath(
|
|
772
|
+
function shortPath(p, ctx = {}) {
|
|
190
773
|
const home = resolve(ctx.home ?? homedir());
|
|
191
774
|
const cwd = resolve(ctx.cwd ?? process.cwd());
|
|
192
|
-
const abs = resolve(
|
|
193
|
-
const norm = (
|
|
775
|
+
const abs = resolve(p);
|
|
776
|
+
const norm = (s2) => s2.split(sep).join("/");
|
|
194
777
|
if (abs === cwd) return ".";
|
|
195
778
|
if (abs.startsWith(cwd + sep)) return `./${norm(relative(cwd, abs))}`;
|
|
196
779
|
if (abs === home) return "~";
|
|
@@ -200,20 +783,20 @@ function shortPath(p7, ctx = {}) {
|
|
|
200
783
|
var SGR = /\x1b\[[0-9;]*m/g;
|
|
201
784
|
var SGR_AT = /\x1b\[[0-9;]*m/y;
|
|
202
785
|
var RESET = "\x1B[0m";
|
|
203
|
-
function stripAnsi(
|
|
204
|
-
return
|
|
786
|
+
function stripAnsi(s2) {
|
|
787
|
+
return s2.replace(SGR, "");
|
|
205
788
|
}
|
|
206
|
-
var visible = (
|
|
207
|
-
function truncateVisible(
|
|
789
|
+
var visible = (s2) => stripAnsi(s2).length;
|
|
790
|
+
function truncateVisible(s2, max) {
|
|
208
791
|
if (max <= 0) return "";
|
|
209
|
-
if (visible(
|
|
792
|
+
if (visible(s2) <= max) return s2;
|
|
210
793
|
let out = "";
|
|
211
794
|
let shown = 0;
|
|
212
795
|
let open = false;
|
|
213
|
-
for (let i = 0; i <
|
|
214
|
-
if (
|
|
796
|
+
for (let i = 0; i < s2.length && shown < max - 1; ) {
|
|
797
|
+
if (s2[i] === "\x1B") {
|
|
215
798
|
SGR_AT.lastIndex = i;
|
|
216
|
-
const m = SGR_AT.exec(
|
|
799
|
+
const m = SGR_AT.exec(s2);
|
|
217
800
|
if (m) {
|
|
218
801
|
out += m[0];
|
|
219
802
|
open = m[0] !== RESET && m[0] !== "\x1B[m";
|
|
@@ -221,7 +804,7 @@ function truncateVisible(s, max) {
|
|
|
221
804
|
continue;
|
|
222
805
|
}
|
|
223
806
|
}
|
|
224
|
-
out +=
|
|
807
|
+
out += s2[i];
|
|
225
808
|
shown++;
|
|
226
809
|
i++;
|
|
227
810
|
}
|
|
@@ -252,7 +835,7 @@ function renderInstallSummary(i, ctx = {}) {
|
|
|
252
835
|
const width = termWidth(ctx.width);
|
|
253
836
|
const src = i.source.startsWith("local:") ? `local:${shortPath(i.source.slice(6), ctx)}` : i.source;
|
|
254
837
|
const head = truncateVisible(
|
|
255
|
-
|
|
838
|
+
pc3.green(`${GLYPH.ok} ${i.name}`) + pc3.dim(` score ${i.score}${i.flags.length ? ` ${i.flags.join(", ")}` : ""} from ${src}`),
|
|
256
839
|
width
|
|
257
840
|
);
|
|
258
841
|
const byPath = /* @__PURE__ */ new Map();
|
|
@@ -261,13 +844,13 @@ function renderInstallSummary(i, ctx = {}) {
|
|
|
261
844
|
if (e) e.agents.push(t.agent);
|
|
262
845
|
else byPath.set(t.path, { agents: [t.agent], mode: t.mode });
|
|
263
846
|
}
|
|
264
|
-
const rows = [...byPath.entries()].map(([path, e]) => [
|
|
265
|
-
const body = rows.length ? table(rows, { width, indent: " " }) : truncateVisible(
|
|
266
|
-
const skipped = i.skipped.map((
|
|
267
|
-
const replaced = i.replaced ? [truncateVisible(
|
|
847
|
+
const rows = [...byPath.entries()].map(([path, e]) => [pc3.cyan(e.agents.join(", ")), pc3.dim(e.mode), shortPath(path, ctx)]);
|
|
848
|
+
const body = rows.length ? table(rows, { width, indent: " " }) : truncateVisible(pc3.dim(` ${shortPath(i.canonical, ctx)} canonical only`), width);
|
|
849
|
+
const skipped = i.skipped.map((s2) => truncateVisible(pc3.dim(` ${GLYPH.skip} ${s2.agent}: ${s2.reason}`), width));
|
|
850
|
+
const replaced = i.replaced ? [truncateVisible(pc3.yellow(` ${GLYPH.replaced} replaced previous install (${i.replaced === "untracked" ? "untracked directory" : i.replaced})`), width)] : [];
|
|
268
851
|
return [head, body, ...skipped, ...replaced].join("\n");
|
|
269
852
|
}
|
|
270
|
-
var banner = () =>
|
|
853
|
+
var banner = () => brandWord();
|
|
271
854
|
|
|
272
855
|
// src/commands/lint.ts
|
|
273
856
|
function applyLicenseFix(raw) {
|
|
@@ -312,23 +895,23 @@ function lintCommand() {
|
|
|
312
895
|
return new Command("lint").description("Validate SKILL.md file(s) against the spec and report a quality score").argument("[target]", "local path or GitHub source", ".").option("--format <fmt>", "text | json | sarif | github", "text").option("--strict", "treat warnings as errors").option("--fail-on-warning", "exit non-zero when warnings exist").option("--fix", "apply safe automatic fixes (e.g. add missing license)").option("--errors-only", "show only skills that failed").action(async (target, opts) => {
|
|
313
896
|
const ui = Boolean(process.stdout.isTTY) && opts.format === "text";
|
|
314
897
|
if (resolve2(target) === homedir2()) {
|
|
315
|
-
process.stderr.write(
|
|
898
|
+
process.stderr.write(pc4.yellow(`Warning: scanning your entire home directory. Pass a narrower path, e.g. skillmd lint ./my-skill
|
|
316
899
|
`));
|
|
317
900
|
}
|
|
318
901
|
if (ui) {
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
902
|
+
intro(`${brandWord()} ${pc4.dim("lint")}`);
|
|
903
|
+
const s2 = spinner("walk");
|
|
904
|
+
s2.start(`Scanning ${target}`);
|
|
322
905
|
try {
|
|
323
906
|
const run3 = await runLint(target, opts);
|
|
324
|
-
|
|
907
|
+
s2.stop(`Scanned ${run3.results.length} skill${run3.results.length === 1 ? "" : "s"}`);
|
|
325
908
|
console.log(run3.output);
|
|
326
909
|
const failed = run3.results.filter((r) => !r.result.ok).length;
|
|
327
|
-
|
|
910
|
+
outro(failed === 0 ? pc4.green("All good") : pc4.red(`${failed} skill${failed === 1 ? "" : "s"} failed \u2014 details above`));
|
|
328
911
|
process.exitCode = run3.exitCode;
|
|
329
912
|
} catch (err) {
|
|
330
|
-
|
|
331
|
-
|
|
913
|
+
s2.stop(pc4.red("No skills found"));
|
|
914
|
+
outro(err instanceof Error ? err.message : String(err));
|
|
332
915
|
process.exitCode = 1;
|
|
333
916
|
}
|
|
334
917
|
return;
|
|
@@ -344,7 +927,7 @@ function lintCommand() {
|
|
|
344
927
|
// src/commands/scan.ts
|
|
345
928
|
import { Command as Command2 } from "commander";
|
|
346
929
|
import { scanSecurity, parseSkillMd } from "@skillmds/core";
|
|
347
|
-
import
|
|
930
|
+
import pc5 from "picocolors";
|
|
348
931
|
async function runScan(target, flags) {
|
|
349
932
|
const resolved = await resolveSource(target);
|
|
350
933
|
const deny = new Set(flags.deny ?? []);
|
|
@@ -363,11 +946,11 @@ async function runScan(target, flags) {
|
|
|
363
946
|
const lines = [];
|
|
364
947
|
for (const e of entries) {
|
|
365
948
|
const flagStr = e.security.flags.join(", ");
|
|
366
|
-
const head = e.denied.length ?
|
|
367
|
-
lines.push(`${head} ${
|
|
949
|
+
const head = e.denied.length ? pc5.red(`DENY ${e.file}`) : pc5.green(`OK ${e.file}`);
|
|
950
|
+
lines.push(`${head} ${pc5.dim(flagStr)}`);
|
|
368
951
|
for (const f of e.security.findings) {
|
|
369
|
-
const mark = deny.has(f.flag) ?
|
|
370
|
-
lines.push(` ${mark} :${f.line} ${
|
|
952
|
+
const mark = deny.has(f.flag) ? pc5.red(f.flag) : pc5.yellow(f.flag);
|
|
953
|
+
lines.push(` ${mark} :${f.line} ${pc5.dim(f.snippet)}`);
|
|
371
954
|
}
|
|
372
955
|
}
|
|
373
956
|
output = lines.join("\n");
|
|
@@ -385,7 +968,7 @@ function scanCommand() {
|
|
|
385
968
|
// src/commands/rules.ts
|
|
386
969
|
import { Command as Command3 } from "commander";
|
|
387
970
|
import { RULES } from "@skillmds/core";
|
|
388
|
-
import
|
|
971
|
+
import pc6 from "picocolors";
|
|
389
972
|
function rulesCommand() {
|
|
390
973
|
return new Command3("rules").description("List lint rules, or show detail for one rule id").argument("[id]", "a rule id, e.g. SK010").option("--json", "machine-readable output").action((id, opts) => {
|
|
391
974
|
const list = id ? RULES.filter((r) => r.id.toLowerCase() === id.toLowerCase()) : RULES;
|
|
@@ -398,9 +981,9 @@ function rulesCommand() {
|
|
|
398
981
|
console.log(JSON.stringify(list.map((r) => ({ id: r.id, severity: r.severity, description: r.description })), null, 2));
|
|
399
982
|
return;
|
|
400
983
|
}
|
|
401
|
-
const badge = (sev) => sev === "error" ?
|
|
402
|
-
console.log(
|
|
403
|
-
for (const r of list) console.log(` ${
|
|
984
|
+
const badge = (sev) => sev === "error" ? pc6.bgRed(pc6.white(" error ")) : sev === "warn" ? pc6.bgYellow(pc6.black(" warn ")) : pc6.bgCyan(pc6.black(" info "));
|
|
985
|
+
console.log(pc6.bold("\nLint rules\n"));
|
|
986
|
+
for (const r of list) console.log(` ${pc6.bold(r.id)} ${badge(r.severity)} ${r.description}`);
|
|
404
987
|
console.log("");
|
|
405
988
|
});
|
|
406
989
|
}
|
|
@@ -410,40 +993,7 @@ import { Command as Command4 } from "commander";
|
|
|
410
993
|
import { mkdirSync, writeFileSync as writeFileSync2, existsSync } from "fs";
|
|
411
994
|
import { join } from "path";
|
|
412
995
|
import { slugify } from "@skillmds/core";
|
|
413
|
-
import
|
|
414
|
-
import pc5 from "picocolors";
|
|
415
|
-
|
|
416
|
-
// src/env.ts
|
|
417
|
-
function detectHostAgent(env = process.env) {
|
|
418
|
-
if (env.CLAUDECODE) return "claude-code";
|
|
419
|
-
if (env.CURSOR_AGENT || env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec") return "cursor";
|
|
420
|
-
if (env.CODEX_SANDBOX || env.CODEX_THREAD_ID) return "codex";
|
|
421
|
-
if (env.GEMINI_CLI) return "gemini-cli";
|
|
422
|
-
if (env.CI && env.CI !== "false" && env.CI !== "0") return "ci";
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
var realTerminal = () => ({
|
|
426
|
-
stdinTTY: Boolean(process.stdin.isTTY),
|
|
427
|
-
stdoutTTY: Boolean(process.stdout.isTTY),
|
|
428
|
-
env: process.env
|
|
429
|
-
});
|
|
430
|
-
function isInteractive(flags, term = realTerminal()) {
|
|
431
|
-
if (flags.yes || flags.json) return false;
|
|
432
|
-
if (!term.stdinTTY || !term.stdoutTTY) return false;
|
|
433
|
-
return detectHostAgent(term.env) === null;
|
|
434
|
-
}
|
|
435
|
-
function scopesFor(flags) {
|
|
436
|
-
return flags.global && !flags.project ? [true] : flags.project && !flags.global ? [false] : [false, true];
|
|
437
|
-
}
|
|
438
|
-
function nonInteractiveHint(flags) {
|
|
439
|
-
return `Interactive prompt required but stdin is not a terminal. Nothing was changed. Re-run with ${flags} to run non-interactively.`;
|
|
440
|
-
}
|
|
441
|
-
function promptIf(flags, prompt) {
|
|
442
|
-
const term = { stdinTTY: Boolean(process.stdin.isTTY), stdoutTTY: Boolean(process.stdout.isTTY), env: flags.env ?? process.env };
|
|
443
|
-
return isInteractive(flags, term) ? { prompt } : {};
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// src/commands/init.ts
|
|
996
|
+
import pc7 from "picocolors";
|
|
447
997
|
function skillTemplate(name, description, license) {
|
|
448
998
|
const desc = description.trim() || `A skill that helps agents with ${name}.`;
|
|
449
999
|
return `---
|
|
@@ -480,8 +1030,8 @@ function initSkill(name, opts = {}) {
|
|
|
480
1030
|
return file;
|
|
481
1031
|
}
|
|
482
1032
|
var defaultInitDeps = (flags) => promptIf(flags, async () => {
|
|
483
|
-
const answer = await
|
|
484
|
-
return
|
|
1033
|
+
const answer = await text({ message: "Skill name?", placeholder: "my-skill" });
|
|
1034
|
+
return isCancel(answer) ? null : answer;
|
|
485
1035
|
});
|
|
486
1036
|
function finish(flags, result, doc) {
|
|
487
1037
|
return flags.json ? { ...result, output: JSON.stringify(doc, null, 2) } : result;
|
|
@@ -491,22 +1041,22 @@ async function runInit(nameArg, flags, deps = defaultInitDeps(flags)) {
|
|
|
491
1041
|
if (!name) {
|
|
492
1042
|
if (!deps.prompt) {
|
|
493
1043
|
const error = nonInteractiveHint("a name argument or --name <name>");
|
|
494
|
-
return finish(flags, { exitCode: 1, output:
|
|
1044
|
+
return finish(flags, { exitCode: 1, output: pc7.red(error) }, { ok: false, error });
|
|
495
1045
|
}
|
|
496
1046
|
const answer = await deps.prompt();
|
|
497
|
-
if (answer === null) return finish(flags, { exitCode: 0, output:
|
|
1047
|
+
if (answer === null) return finish(flags, { exitCode: 0, output: pc7.dim("Cancelled.") }, { ok: false, cancelled: true });
|
|
498
1048
|
name = answer;
|
|
499
1049
|
}
|
|
500
1050
|
if (!name) {
|
|
501
1051
|
const error = "A skill name is required (pass a name or --name).";
|
|
502
|
-
return finish(flags, { exitCode: 1, output:
|
|
1052
|
+
return finish(flags, { exitCode: 1, output: pc7.red(error) }, { ok: false, error });
|
|
503
1053
|
}
|
|
504
1054
|
try {
|
|
505
1055
|
const file = initSkill(name, { dir: flags.dir, description: flags.description, license: flags.license });
|
|
506
1056
|
return { exitCode: 0, file, output: flags.json ? JSON.stringify({ ok: true, file }, null, 2) : `Created ${file}` };
|
|
507
1057
|
} catch (e) {
|
|
508
1058
|
const msg2 = e instanceof Error ? e.message : String(e);
|
|
509
|
-
return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg2 }, null, 2) :
|
|
1059
|
+
return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg2 }, null, 2) : pc7.red(msg2) };
|
|
510
1060
|
}
|
|
511
1061
|
}
|
|
512
1062
|
function initCommand() {
|
|
@@ -519,7 +1069,7 @@ function initCommand() {
|
|
|
519
1069
|
|
|
520
1070
|
// src/commands/search.ts
|
|
521
1071
|
import { Command as Command5 } from "commander";
|
|
522
|
-
import
|
|
1072
|
+
import pc8 from "picocolors";
|
|
523
1073
|
var MAX_LINE_WIDTH = 100;
|
|
524
1074
|
function formatStars(n) {
|
|
525
1075
|
if (n >= 1e6) {
|
|
@@ -532,19 +1082,19 @@ function formatStars(n) {
|
|
|
532
1082
|
}
|
|
533
1083
|
return String(n);
|
|
534
1084
|
}
|
|
535
|
-
function truncate(
|
|
536
|
-
if (
|
|
1085
|
+
function truncate(text2, width) {
|
|
1086
|
+
if (text2.length <= width) return text2;
|
|
537
1087
|
if (width <= 1) return "\u2026";
|
|
538
|
-
return `${
|
|
1088
|
+
return `${text2.slice(0, width - 1).trimEnd()}\u2026`;
|
|
539
1089
|
}
|
|
540
1090
|
function formatSearchResult(item, width = process.stdout.columns ?? 80) {
|
|
541
|
-
const stars = item.repo_stars != null ?
|
|
542
|
-
const check = item.verified ?
|
|
543
|
-
const pack = item.type === "pack" ?
|
|
544
|
-
const head = [
|
|
1091
|
+
const stars = item.repo_stars != null ? pc8.dim(`\u2605 ${formatStars(item.repo_stars)}`) : "";
|
|
1092
|
+
const check = item.verified ? pc8.green("\u2713") : "";
|
|
1093
|
+
const pack = item.type === "pack" ? pc8.dim("[pack]") : "";
|
|
1094
|
+
const head = [pc8.bold(safeText(item.slug, 200)), stars, check, pack].filter(Boolean).join(" ");
|
|
545
1095
|
const indent = " ";
|
|
546
1096
|
const lineWidth = Math.max(10, Math.min(width, MAX_LINE_WIDTH) - indent.length);
|
|
547
|
-
const desc = `${indent}${
|
|
1097
|
+
const desc = `${indent}${pc8.dim(truncate(safeText(item.description ?? ""), lineWidth))}`;
|
|
548
1098
|
return `${head}
|
|
549
1099
|
${desc}`;
|
|
550
1100
|
}
|
|
@@ -558,63 +1108,63 @@ async function runSearch(query, flags) {
|
|
|
558
1108
|
const r = await api(`/api/search?${params}`);
|
|
559
1109
|
const items = r.items ?? [];
|
|
560
1110
|
if (flags.json) return { items, output: JSON.stringify(items, null, 2) };
|
|
561
|
-
const lines = items.map((
|
|
1111
|
+
const lines = items.map((s2) => formatSearchResult(s2));
|
|
562
1112
|
return { items, output: lines.join("\n\n") || "No skills found." };
|
|
563
1113
|
}
|
|
564
1114
|
function searchCommand() {
|
|
565
1115
|
return new Command5("search").description("Search the SkillMD registry").argument("<query>", "search terms").option("--category <slug>", "filter by category").option("--verified", "only verified skills").option("--type <type>", "single | pack").option("--min-rating <n>", "minimum average rating (0-5)").option("--limit <n>", "max results", "20").option("--json", "machine-readable output").action(async (query, opts, cmd) => {
|
|
566
1116
|
const merged = { ...cmd.parent?.opts(), ...opts };
|
|
567
|
-
const run2 = await runSearch(query, merged);
|
|
1117
|
+
const run2 = await waitFor("hop", `Searching for ${query}`, runSearch(query, merged), { json: Boolean(merged.json) });
|
|
568
1118
|
console.log(run2.output);
|
|
569
1119
|
});
|
|
570
1120
|
}
|
|
571
1121
|
|
|
572
1122
|
// src/commands/info.ts
|
|
573
1123
|
import { Command as Command6 } from "commander";
|
|
574
|
-
import
|
|
1124
|
+
import pc9 from "picocolors";
|
|
575
1125
|
async function runInfo(slug, flags, deps = {}) {
|
|
576
1126
|
const [owner, name] = slug.split("/");
|
|
577
1127
|
if (!owner || !name || slug.split("/").length !== 2) {
|
|
578
|
-
return { ok: false, exitCode: 1, output:
|
|
1128
|
+
return { ok: false, exitCode: 1, output: pc9.red("Expected owner/name, e.g. anthropic/pdf") };
|
|
579
1129
|
}
|
|
580
1130
|
const api = deps.api ?? createClient(flags).api;
|
|
581
|
-
let
|
|
1131
|
+
let s2;
|
|
582
1132
|
try {
|
|
583
|
-
|
|
1133
|
+
s2 = await api(`/api/skills/${owner}/${name}`);
|
|
584
1134
|
} catch (err) {
|
|
585
1135
|
const message = err instanceof Error ? err.message : String(err);
|
|
586
1136
|
if (/^SkillMD API 404 /.test(message)) {
|
|
587
|
-
return { ok: false, exitCode: 1, output:
|
|
1137
|
+
return { ok: false, exitCode: 1, output: pc9.red(`Skill not found on the registry: ${slug}`) };
|
|
588
1138
|
}
|
|
589
|
-
return { ok: false, exitCode: 1, output:
|
|
1139
|
+
return { ok: false, exitCode: 1, output: pc9.red(`Could not reach the SkillMD registry: ${message}`) };
|
|
590
1140
|
}
|
|
591
|
-
if (flags.json) return { ok: true, exitCode: 0, output: JSON.stringify(
|
|
592
|
-
const slugTxt = safeText(
|
|
1141
|
+
if (flags.json) return { ok: true, exitCode: 0, output: JSON.stringify(s2, null, 2), skill: s2 };
|
|
1142
|
+
const slugTxt = safeText(s2.slug, 200);
|
|
593
1143
|
const lines = [];
|
|
594
1144
|
lines.push(
|
|
595
|
-
`${
|
|
1145
|
+
`${pc9.bold(slugTxt)} ${pc9.dim(`[${safeText(s2.type ?? "single", 40)}]`)} ${s2.verified ? pc9.green("\u2713 verified") : pc9.yellow("unverified")}`
|
|
596
1146
|
);
|
|
597
|
-
lines.push(` ${safeText(
|
|
1147
|
+
lines.push(` ${safeText(s2.description)}`);
|
|
598
1148
|
lines.push(
|
|
599
|
-
|
|
600
|
-
` category: ${safeText(
|
|
1149
|
+
pc9.dim(
|
|
1150
|
+
` category: ${safeText(s2.category ?? "-", 100) || "-"} \xB7 rating: ${s2.avg_rating != null ? s2.avg_rating.toFixed(1) : "-"} (${s2.rating_count ?? 0}) \xB7 installs: ${s2.install_count ?? 0}`
|
|
601
1151
|
)
|
|
602
1152
|
);
|
|
603
|
-
const securityFlags2 = (
|
|
1153
|
+
const securityFlags2 = (s2.security_flags ?? []).map((f) => safeText(f, 60)).filter(Boolean);
|
|
604
1154
|
lines.push(
|
|
605
|
-
|
|
1155
|
+
pc9.dim(` security: ${securityFlags2.join(", ") || "unscanned"} \xB7 license: ${safeText(s2.provenance?.license ?? s2.license ?? "unknown", 100) || "unknown"}`)
|
|
606
1156
|
);
|
|
607
|
-
const sourceRepo = safeText(
|
|
608
|
-
const commitSha = safeText(
|
|
609
|
-
if (sourceRepo) lines.push(
|
|
610
|
-
lines.push(
|
|
611
|
-
lines.push(
|
|
612
|
-
return { ok: true, exitCode: 0, output: lines.join("\n"), skill:
|
|
1157
|
+
const sourceRepo = safeText(s2.provenance?.source_repo ?? s2.source_repo, 300);
|
|
1158
|
+
const commitSha = safeText(s2.provenance?.commit_sha ?? s2.commit_sha, 100);
|
|
1159
|
+
if (sourceRepo) lines.push(pc9.dim(` source: ${sourceRepo}${commitSha ? ` @ ${commitSha.slice(0, 8)}` : ""}`));
|
|
1160
|
+
lines.push(pc9.dim(` page: https://skillmd.com/skills/${slugTxt}`));
|
|
1161
|
+
lines.push(pc9.dim(` install: ${safeText(s2.install?.cli, 300) || `npx get-skill add ${slugTxt}`}`));
|
|
1162
|
+
return { ok: true, exitCode: 0, output: lines.join("\n"), skill: s2 };
|
|
613
1163
|
}
|
|
614
1164
|
function infoCommand() {
|
|
615
1165
|
return new Command6("info").description("Show registry details for a skill: verification, security flags, provenance, rating").argument("<slug>", "owner/name, e.g. anthropic/pdf").action(async (slug, _opts, cmd) => {
|
|
616
1166
|
const flags = { ...cmd.parent?.opts() };
|
|
617
|
-
const run2 = await runInfo(slug, flags);
|
|
1167
|
+
const run2 = await waitFor("drop", `Fetching ${slug}`, runInfo(slug, flags), { json: flags.json });
|
|
618
1168
|
if (run2.output) (run2.ok ? console.log : console.error)(run2.output);
|
|
619
1169
|
process.exitCode = run2.exitCode;
|
|
620
1170
|
});
|
|
@@ -623,9 +1173,8 @@ function infoCommand() {
|
|
|
623
1173
|
// src/commands/add.ts
|
|
624
1174
|
import { Command as Command7, Option } from "commander";
|
|
625
1175
|
import { homedir as homedir4 } from "os";
|
|
626
|
-
import * as p3 from "@clack/prompts";
|
|
627
1176
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
628
|
-
import
|
|
1177
|
+
import pc10 from "picocolors";
|
|
629
1178
|
import { lint as lint2, parseSkillMd as parseSkillMd2 } from "@skillmds/core";
|
|
630
1179
|
|
|
631
1180
|
// src/claudePlugin.ts
|
|
@@ -669,7 +1218,7 @@ function exec(deps) {
|
|
|
669
1218
|
});
|
|
670
1219
|
}
|
|
671
1220
|
function marketplacePluginName(owner, slug) {
|
|
672
|
-
const clean = (
|
|
1221
|
+
const clean = (s2) => s2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
673
1222
|
const base = clean(slug) || "plugin";
|
|
674
1223
|
return owner.toLowerCase() === "skillmd" ? base : `${clean(owner)}-${base}`;
|
|
675
1224
|
}
|
|
@@ -707,21 +1256,21 @@ ${r.stderr}`;
|
|
|
707
1256
|
}
|
|
708
1257
|
function msg(e) {
|
|
709
1258
|
if (e && typeof e === "object" && "stderr" in e) {
|
|
710
|
-
const
|
|
711
|
-
if (
|
|
1259
|
+
const s2 = String(e.stderr ?? "").trim();
|
|
1260
|
+
if (s2) return s2.split(/\r?\n/)[0];
|
|
712
1261
|
}
|
|
713
1262
|
return e instanceof Error ? e.message.split(/\r?\n/)[0] : String(e);
|
|
714
1263
|
}
|
|
715
1264
|
var SITE_URL = "https://skillmd.com";
|
|
716
1265
|
function readJson(path) {
|
|
717
|
-
let
|
|
1266
|
+
let text2;
|
|
718
1267
|
try {
|
|
719
|
-
|
|
1268
|
+
text2 = readFileSync(path, "utf8");
|
|
720
1269
|
} catch {
|
|
721
1270
|
return "absent";
|
|
722
1271
|
}
|
|
723
1272
|
try {
|
|
724
|
-
const v = JSON.parse(
|
|
1273
|
+
const v = JSON.parse(text2);
|
|
725
1274
|
return v && typeof v === "object" && !Array.isArray(v) ? v : "unparsable";
|
|
726
1275
|
} catch {
|
|
727
1276
|
return "unparsable";
|
|
@@ -812,7 +1361,7 @@ async function installPluginNatively(owner, slug, deps = {}) {
|
|
|
812
1361
|
const plugins = obj(installedDoc.plugins);
|
|
813
1362
|
const prior = Array.isArray(plugins[qualified]) ? plugins[qualified] : [];
|
|
814
1363
|
plugins[qualified] = [
|
|
815
|
-
...prior.filter((
|
|
1364
|
+
...prior.filter((p) => p?.scope !== "user"),
|
|
816
1365
|
{ scope: "user", installPath, version: version2, installedAt: now, lastUpdated: now }
|
|
817
1366
|
];
|
|
818
1367
|
installedDoc.plugins = plugins;
|
|
@@ -831,22 +1380,22 @@ async function installPluginNatively(owner, slug, deps = {}) {
|
|
|
831
1380
|
}
|
|
832
1381
|
|
|
833
1382
|
// src/commands/add.ts
|
|
834
|
-
var infoNotice = (
|
|
835
|
-
var warnNotice = (
|
|
836
|
-
var skipNotice = (
|
|
837
|
-
var replacedNotice = (
|
|
1383
|
+
var infoNotice = (text2) => ({ text: text2, style: "info" });
|
|
1384
|
+
var warnNotice = (text2) => ({ text: text2, style: "warn" });
|
|
1385
|
+
var skipNotice = (text2) => ({ text: text2, style: "skip" });
|
|
1386
|
+
var replacedNotice = (text2) => ({ text: text2, style: "warn", suppressInSummary: true });
|
|
838
1387
|
function noticeLine(n) {
|
|
839
1388
|
switch (n.style) {
|
|
840
1389
|
case "info":
|
|
841
|
-
return
|
|
1390
|
+
return pc10.dim(`${GLYPH.info} ${n.text}`);
|
|
842
1391
|
case "skip":
|
|
843
|
-
return
|
|
1392
|
+
return pc10.dim(`${GLYPH.skip} ${n.text}`);
|
|
844
1393
|
case "warn":
|
|
845
|
-
return
|
|
1394
|
+
return pc10.yellow(`${GLYPH.warn} ${n.text}`);
|
|
846
1395
|
}
|
|
847
1396
|
}
|
|
848
1397
|
async function promptScopeInteractive(ctx) {
|
|
849
|
-
const choice = await
|
|
1398
|
+
const choice = await select({
|
|
850
1399
|
message: "Installation scope",
|
|
851
1400
|
initialValue: ctx.suggestGlobal,
|
|
852
1401
|
options: [
|
|
@@ -854,7 +1403,7 @@ async function promptScopeInteractive(ctx) {
|
|
|
854
1403
|
{ value: true, label: "Global", hint: "install in your home directory (available across all projects)" }
|
|
855
1404
|
]
|
|
856
1405
|
});
|
|
857
|
-
return
|
|
1406
|
+
return isCancel(choice) ? null : choice;
|
|
858
1407
|
}
|
|
859
1408
|
function agentsPromptMessage(managed) {
|
|
860
1409
|
return managed ? `Claude Code already has ${managed} as a plugin. Which other agents should get its skills too?` : "Which agents do you want to install to?";
|
|
@@ -862,13 +1411,13 @@ function agentsPromptMessage(managed) {
|
|
|
862
1411
|
async function promptAgentsInteractive(ctx) {
|
|
863
1412
|
if (ctx.detected.length < 2) return ctx.detected;
|
|
864
1413
|
const remembered = (readConfig().lastAgents ?? []).filter((id) => ctx.detected.includes(id));
|
|
865
|
-
const picked = await
|
|
1414
|
+
const picked = await multiselect({
|
|
866
1415
|
message: agentsPromptMessage(ctx.managed),
|
|
867
1416
|
options: ctx.detected.map((id) => ({ value: id, label: id, hint: ctx.dirFor(id) })),
|
|
868
1417
|
initialValues: remembered.length ? remembered : ctx.detected,
|
|
869
1418
|
required: true
|
|
870
1419
|
});
|
|
871
|
-
return
|
|
1420
|
+
return isCancel(picked) ? null : picked;
|
|
872
1421
|
}
|
|
873
1422
|
function unverifiedFilesNote(n) {
|
|
874
1423
|
const [f, it] = n === 1 ? ["file", "it"] : ["files", "them"];
|
|
@@ -920,19 +1469,20 @@ async function registryCandidate(owner, name, source, client) {
|
|
|
920
1469
|
type: skill.type,
|
|
921
1470
|
securityFlags: skill.security_flags
|
|
922
1471
|
};
|
|
1472
|
+
const missing = [];
|
|
1473
|
+
const bundle = await fetchBundle(client.base, registrySlug, client.token, { missing });
|
|
1474
|
+
if (bundle && bundle.length) {
|
|
1475
|
+
const sk = bundle.find((file) => file.path === "SKILL.md");
|
|
1476
|
+
const note2 = bundleNote(bundle, missing);
|
|
1477
|
+
return {
|
|
1478
|
+
...meta,
|
|
1479
|
+
type: bundle.length > 1 ? "pack" : meta.type,
|
|
1480
|
+
raw: sk ? sk.contents.toString("utf8") : skillMdFor(skill),
|
|
1481
|
+
files: bundle,
|
|
1482
|
+
...note2 ? { note: note2 } : {}
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
923
1485
|
if (skill.type === "pack") {
|
|
924
|
-
const missing = [];
|
|
925
|
-
const bundle = await fetchBundle(client.base, registrySlug, client.token, { missing });
|
|
926
|
-
if (bundle && bundle.length) {
|
|
927
|
-
const sk = bundle.find((file) => file.path === "SKILL.md");
|
|
928
|
-
const note2 = bundleNote(bundle, missing);
|
|
929
|
-
return {
|
|
930
|
-
...meta,
|
|
931
|
-
raw: sk ? sk.contents.toString("utf8") : skillMdFor(skill),
|
|
932
|
-
files: bundle,
|
|
933
|
-
...note2 ? { note: note2 } : {}
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
1486
|
const pack = await resolvePackFiles(skill);
|
|
937
1487
|
if (pack) return { ...meta, ...pack };
|
|
938
1488
|
}
|
|
@@ -1143,7 +1693,7 @@ function finish2(flags, outcome) {
|
|
|
1143
1693
|
};
|
|
1144
1694
|
}
|
|
1145
1695
|
case "list": {
|
|
1146
|
-
const human = outcome.skills.length ? outcome.skills.map((
|
|
1696
|
+
const human = outcome.skills.length ? outcome.skills.map((s2) => `${pc10.bold(s2.name)} ${pc10.dim(s2.description)}`).join("\n") : "No skills found.";
|
|
1147
1697
|
return { written: [], blocked: [], exitCode: 0, output: json ? JSON.stringify({ ok: true, skills: outcome.skills }, null, 2) : human };
|
|
1148
1698
|
}
|
|
1149
1699
|
case "cancel": {
|
|
@@ -1153,7 +1703,7 @@ function finish2(flags, outcome) {
|
|
|
1153
1703
|
blocked: [],
|
|
1154
1704
|
exitCode: 0,
|
|
1155
1705
|
cancelled: true,
|
|
1156
|
-
output: json ? JSON.stringify(doc, null, 2) :
|
|
1706
|
+
output: json ? JSON.stringify(doc, null, 2) : pc10.dim("Installation cancelled \u2014 nothing was installed.")
|
|
1157
1707
|
};
|
|
1158
1708
|
}
|
|
1159
1709
|
case "fail": {
|
|
@@ -1163,7 +1713,7 @@ function finish2(flags, outcome) {
|
|
|
1163
1713
|
if (warnings.length) doc.warnings = warnings;
|
|
1164
1714
|
const human = [
|
|
1165
1715
|
...(outcome.notices ?? []).map(noticeLine),
|
|
1166
|
-
...outcome.blocked?.length ? outcome.blocked.map((b) =>
|
|
1716
|
+
...outcome.blocked?.length ? outcome.blocked.map((b) => pc10.red(`${GLYPH.blocked} ${b.name} blocked \u2014 ${b.reason}`)) : [pc10.red(outcome.error)]
|
|
1167
1717
|
].join("\n");
|
|
1168
1718
|
return { written: [], blocked: outcome.blocked ?? [], exitCode: 1, output: json ? JSON.stringify(doc, null, 2) : human };
|
|
1169
1719
|
}
|
|
@@ -1173,7 +1723,7 @@ function renderHuman(notices, records, paths) {
|
|
|
1173
1723
|
const lines = notices.map(noticeLine);
|
|
1174
1724
|
for (const r of records) {
|
|
1175
1725
|
if (r.kind === "blocked") {
|
|
1176
|
-
lines.push(
|
|
1726
|
+
lines.push(pc10.red(r.message));
|
|
1177
1727
|
continue;
|
|
1178
1728
|
}
|
|
1179
1729
|
const { doc } = r;
|
|
@@ -1385,14 +1935,14 @@ function addCommand() {
|
|
|
1385
1935
|
});
|
|
1386
1936
|
}
|
|
1387
1937
|
var clackUI = {
|
|
1388
|
-
intro: (m) =>
|
|
1389
|
-
spinner: () =>
|
|
1390
|
-
message: (m) =>
|
|
1391
|
-
outro: (m) =>
|
|
1392
|
-
cancel: (m) =>
|
|
1938
|
+
intro: (m) => intro(m),
|
|
1939
|
+
spinner: () => spinner("drop"),
|
|
1940
|
+
message: (m) => log.message(m),
|
|
1941
|
+
outro: (m) => outro(m),
|
|
1942
|
+
cancel: (m) => cancel(m),
|
|
1393
1943
|
confirm: async (m) => {
|
|
1394
|
-
const answer = await
|
|
1395
|
-
return
|
|
1944
|
+
const answer = await confirm({ message: m, initialValue: true });
|
|
1945
|
+
return isCancel(answer) ? null : answer;
|
|
1396
1946
|
}
|
|
1397
1947
|
};
|
|
1398
1948
|
var FIND_SKILLS = "skillmd/find-skills";
|
|
@@ -1421,7 +1971,7 @@ async function offerFindSkills(source, flags, run2, ui, deps) {
|
|
|
1421
1971
|
project: !global,
|
|
1422
1972
|
agent: run2.agents
|
|
1423
1973
|
}, deps);
|
|
1424
|
-
ui.message(r.exitCode === 0 ? `${
|
|
1974
|
+
ui.message(r.exitCode === 0 ? `${pc10.green(GLYPH.ok)} find-skills added` : pc10.yellow(`${GLYPH.warn} find-skills could not be added. Try later: npx get-skill add ${FIND_SKILLS}`));
|
|
1425
1975
|
}
|
|
1426
1976
|
async function runAddWithUI(source, flags, opts = {}) {
|
|
1427
1977
|
const term = opts.term ?? { ...realTerminal(), env: flags.env ?? process.env };
|
|
@@ -1438,14 +1988,14 @@ async function runAddWithUI(source, flags, opts = {}) {
|
|
|
1438
1988
|
// this spread.
|
|
1439
1989
|
...base,
|
|
1440
1990
|
resolve: async (arg, f) => {
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1991
|
+
const s2 = ui.spinner();
|
|
1992
|
+
s2.start(`Resolving ${arg}`);
|
|
1443
1993
|
try {
|
|
1444
1994
|
const c = await base.resolve(arg, f);
|
|
1445
|
-
|
|
1995
|
+
s2.stop(`${c.length} skill${c.length === 1 ? "" : "s"} found`);
|
|
1446
1996
|
return c;
|
|
1447
1997
|
} catch (e) {
|
|
1448
|
-
|
|
1998
|
+
s2.stop(pc10.red("Could not resolve the source"));
|
|
1449
1999
|
throw e;
|
|
1450
2000
|
}
|
|
1451
2001
|
}
|
|
@@ -1456,10 +2006,10 @@ async function runAddWithUI(source, flags, opts = {}) {
|
|
|
1456
2006
|
}
|
|
1457
2007
|
ui.message(run2.output);
|
|
1458
2008
|
await offerFindSkills(source, flags, run2, ui, base);
|
|
1459
|
-
ui.outro(run2.exitCode === 0 ? `Done. Run ${
|
|
2009
|
+
ui.outro(run2.exitCode === 0 ? `Done. Run ${pc10.cyan("npx get-skill list")} to see everything installed.` : pc10.red("Some skills were not installed."));
|
|
1460
2010
|
return { run: run2, printed: true };
|
|
1461
2011
|
} catch (e) {
|
|
1462
|
-
ui.outro(
|
|
2012
|
+
ui.outro(pc10.red("Nothing was installed."));
|
|
1463
2013
|
throw e;
|
|
1464
2014
|
}
|
|
1465
2015
|
}
|
|
@@ -1468,20 +2018,20 @@ async function runAddWithUI(source, flags, opts = {}) {
|
|
|
1468
2018
|
import { Command as Command8 } from "commander";
|
|
1469
2019
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1470
2020
|
import { join as join4 } from "path";
|
|
1471
|
-
import
|
|
2021
|
+
import pc11 from "picocolors";
|
|
1472
2022
|
import { parseSkillMd as parseSkillMd3 } from "@skillmds/core";
|
|
1473
2023
|
function runList(flags = {}) {
|
|
1474
2024
|
const scopes = scopesFor(flags);
|
|
1475
2025
|
let items = [];
|
|
1476
2026
|
for (const global of scopes) {
|
|
1477
|
-
for (const
|
|
1478
|
-
const md = join4(
|
|
2027
|
+
for (const s2 of listInstalled({ global, cwd: flags.cwd, home: flags.home })) {
|
|
2028
|
+
const md = join4(s2.path, "SKILL.md");
|
|
1479
2029
|
let description = "";
|
|
1480
2030
|
if (existsSync3(md)) {
|
|
1481
2031
|
const parsed = parseSkillMd3(readFileSync2(md, "utf8"));
|
|
1482
2032
|
description = "error" in parsed ? `(invalid SKILL.md \u2014 ${parsed.error})` : parsed.description;
|
|
1483
2033
|
}
|
|
1484
|
-
items.push({ ...
|
|
2034
|
+
items.push({ ...s2, description: safeText(description, 80) });
|
|
1485
2035
|
}
|
|
1486
2036
|
}
|
|
1487
2037
|
if (flags.agent?.length) items = items.filter((i) => i.agents.some((a) => flags.agent.includes(a)));
|
|
@@ -1489,13 +2039,13 @@ function runList(flags = {}) {
|
|
|
1489
2039
|
if (flags.json) {
|
|
1490
2040
|
return { items, output: JSON.stringify(items.map(({ name, scope, path, agents, source, mode, tracked, description }) => ({ name, scope, path, agents, source: source ?? null, mode, tracked, description })), null, 2) };
|
|
1491
2041
|
}
|
|
1492
|
-
if (items.length === 0) return { items, output:
|
|
2042
|
+
if (items.length === 0) return { items, output: pc11.dim("No installed skills found. Install one with `npx get-skill add owner/name`.") };
|
|
1493
2043
|
const w = Math.max(...items.map((i) => i.name.length), 4);
|
|
1494
2044
|
const lines = items.map((i) => {
|
|
1495
|
-
const tag = i.tracked ?
|
|
2045
|
+
const tag = i.tracked ? pc11.dim(i.source ?? "") : pc11.yellow("untracked");
|
|
1496
2046
|
const modes = [...new Set(Object.values(i.mode))].join("/");
|
|
1497
|
-
return `${
|
|
1498
|
-
${" ".repeat(w + 2)}${
|
|
2047
|
+
return `${pc11.bold(i.name.padEnd(w))} ${pc11.cyan(i.scope.padEnd(7))} ${i.agents.join(", ").padEnd(24)} ${pc11.dim(modes.padEnd(9))} ${tag}
|
|
2048
|
+
${" ".repeat(w + 2)}${pc11.dim(i.description)}`;
|
|
1499
2049
|
});
|
|
1500
2050
|
return { items, output: lines.join("\n") };
|
|
1501
2051
|
}
|
|
@@ -1509,16 +2059,15 @@ function listCommand() {
|
|
|
1509
2059
|
import { Command as Command9 } from "commander";
|
|
1510
2060
|
import { rmSync } from "fs";
|
|
1511
2061
|
import { join as join5, resolve as resolve4 } from "path";
|
|
1512
|
-
import
|
|
1513
|
-
import pc10 from "picocolors";
|
|
2062
|
+
import pc12 from "picocolors";
|
|
1514
2063
|
var defaultDeps = (flags) => isInteractive(flags) ? {
|
|
1515
2064
|
confirm: async (msg2) => {
|
|
1516
|
-
const a = await
|
|
1517
|
-
return !
|
|
2065
|
+
const a = await confirm({ message: msg2, initialValue: false });
|
|
2066
|
+
return !isCancel(a) && a;
|
|
1518
2067
|
},
|
|
1519
2068
|
pick: async ({ names }) => {
|
|
1520
|
-
const a = await
|
|
1521
|
-
return
|
|
2069
|
+
const a = await multiselect({ message: "Which skills do you want to remove?", options: names.map((n) => ({ value: n, label: n })), required: true });
|
|
2070
|
+
return isCancel(a) ? null : a;
|
|
1522
2071
|
}
|
|
1523
2072
|
} : {};
|
|
1524
2073
|
function finish3(flags, result, doc) {
|
|
@@ -1531,32 +2080,32 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
|
|
|
1531
2080
|
const lines = [];
|
|
1532
2081
|
if (flags.all && names.length) {
|
|
1533
2082
|
const error = "--all cannot be combined with skill names";
|
|
1534
|
-
return finish3(flags, { removed: [], exitCode: 1, output:
|
|
2083
|
+
return finish3(flags, { removed: [], exitCode: 1, output: pc12.red(error) }, { ok: false, error });
|
|
1535
2084
|
}
|
|
1536
2085
|
const perScope = scopes.map((scope) => ({ scope, installed: listInstalled(scope) }));
|
|
1537
|
-
const allNames = [...new Set(perScope.flatMap((
|
|
2086
|
+
const allNames = [...new Set(perScope.flatMap((s2) => s2.installed.map((i) => i.name)))];
|
|
1538
2087
|
const nothingInstalled = `No installed skills found in ${where} scope.`;
|
|
1539
2088
|
let targets = names;
|
|
1540
2089
|
if (flags.all) {
|
|
1541
2090
|
if (allNames.length === 0) {
|
|
1542
|
-
return finish3(flags, { removed: [], exitCode: 1, output:
|
|
2091
|
+
return finish3(flags, { removed: [], exitCode: 1, output: pc12.dim(nothingInstalled) }, { ok: false, error: nothingInstalled });
|
|
1543
2092
|
}
|
|
1544
2093
|
targets = allNames;
|
|
1545
2094
|
} else if (targets.length === 0) {
|
|
1546
2095
|
if (!deps.pick) {
|
|
1547
2096
|
const error = "Give one or more skill names, or --all.";
|
|
1548
|
-
return finish3(flags, { removed: [], exitCode: 1, output:
|
|
2097
|
+
return finish3(flags, { removed: [], exitCode: 1, output: pc12.red(`${error} `) + pc12.dim(nonInteractiveHint("skill names and -y")) }, { ok: false, error });
|
|
1549
2098
|
}
|
|
1550
2099
|
if (allNames.length === 0) {
|
|
1551
|
-
return finish3(flags, { removed: [], exitCode: 1, output:
|
|
2100
|
+
return finish3(flags, { removed: [], exitCode: 1, output: pc12.dim(nothingInstalled) }, { ok: false, error: nothingInstalled });
|
|
1552
2101
|
}
|
|
1553
2102
|
const picked = await deps.pick({ names: allNames });
|
|
1554
|
-
if (!picked) return finish3(flags, { removed: [], exitCode: 0, cancelled: true, output:
|
|
2103
|
+
if (!picked) return finish3(flags, { removed: [], exitCode: 0, cancelled: true, output: pc12.dim("Nothing removed.") }, { ok: true, removed: [], cancelled: true });
|
|
1555
2104
|
targets = picked;
|
|
1556
2105
|
}
|
|
1557
2106
|
const known = targets.filter((n) => allNames.includes(n));
|
|
1558
2107
|
const missing = targets.filter((n) => !known.includes(n));
|
|
1559
|
-
for (const n of missing) lines.push(
|
|
2108
|
+
for (const n of missing) lines.push(pc12.yellow(`- ${n} not installed (${where} scope)`));
|
|
1560
2109
|
if (known.length === 0) {
|
|
1561
2110
|
const output = lines.join("\n");
|
|
1562
2111
|
return finish3(flags, { removed: [], exitCode: 1, output }, { ok: false, error: `not installed: ${missing.join(", ")}`, missing });
|
|
@@ -1566,17 +2115,17 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
|
|
|
1566
2115
|
const ok = deps.confirm ? await deps.confirm(`Remove ${known.length} skill${known.length === 1 ? "" : "s"} (${known.join(", ")}) ${what} in ${where} scope?`) : null;
|
|
1567
2116
|
if (ok === null) {
|
|
1568
2117
|
const error = nonInteractiveHint("-y");
|
|
1569
|
-
return finish3(flags, { removed: [], exitCode: 1, output:
|
|
2118
|
+
return finish3(flags, { removed: [], exitCode: 1, output: pc12.red(error) }, { ok: false, error });
|
|
1570
2119
|
}
|
|
1571
|
-
if (!ok) return finish3(flags, { removed: [], exitCode: 0, cancelled: true, output:
|
|
2120
|
+
if (!ok) return finish3(flags, { removed: [], exitCode: 0, cancelled: true, output: pc12.dim("Nothing removed.") }, { ok: true, removed: [], cancelled: true });
|
|
1572
2121
|
}
|
|
1573
2122
|
const removed = [];
|
|
1574
2123
|
const skipped = [];
|
|
1575
2124
|
let changed = 0;
|
|
1576
2125
|
for (const name of known) {
|
|
1577
2126
|
for (const { scope, installed } of perScope) {
|
|
1578
|
-
if (!installed.some((
|
|
1579
|
-
const tag = scopes.length > 1 ?
|
|
2127
|
+
if (!installed.some((s2) => s2.name === name)) continue;
|
|
2128
|
+
const tag = scopes.length > 1 ? pc12.dim(` [${scopeLabel(scope)}]`) : "";
|
|
1580
2129
|
if (flags.agent?.length) {
|
|
1581
2130
|
const lock = readLock({ global: Boolean(scope.global), cwd: scope.cwd, home: scope.home });
|
|
1582
2131
|
const entry = lock.skills[name];
|
|
@@ -1587,11 +2136,11 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
|
|
|
1587
2136
|
try {
|
|
1588
2137
|
dir = agentDir(a, scope);
|
|
1589
2138
|
} catch (e) {
|
|
1590
|
-
lines.push(
|
|
2139
|
+
lines.push(pc12.yellow(`- ${a}: ${e instanceof Error ? e.message : String(e)}`));
|
|
1591
2140
|
continue;
|
|
1592
2141
|
}
|
|
1593
2142
|
if (resolve4(dir) === canonRoot || entry?.mode[a] === "canonical") {
|
|
1594
|
-
lines.push(
|
|
2143
|
+
lines.push(pc12.dim(`${GLYPH.skip} ${name}: ${a} reads the canonical copy directly \u2014 unlinking is a no-op (use remove without -a to delete the skill)`));
|
|
1595
2144
|
skipped.push({ skill: name, agent: a, scope: scopeLabel(scope), reason: "reads the canonical copy directly" });
|
|
1596
2145
|
acted = true;
|
|
1597
2146
|
continue;
|
|
@@ -1601,17 +2150,17 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
|
|
|
1601
2150
|
rmSync(pth, { recursive: true, force: true });
|
|
1602
2151
|
removed.push(pth);
|
|
1603
2152
|
acted = true;
|
|
1604
|
-
lines.push(
|
|
2153
|
+
lines.push(pc12.green(`${GLYPH.ok} removed ${name} from ${a}`) + tag);
|
|
1605
2154
|
}
|
|
1606
2155
|
}
|
|
1607
|
-
if (!acted) lines.push(
|
|
2156
|
+
if (!acted) lines.push(pc12.yellow(`- ${name} is not linked into ${flags.agent.join(", ")}`) + tag);
|
|
1608
2157
|
if (entry) {
|
|
1609
2158
|
const left = entry.agents.filter((a) => !flags.agent.includes(a));
|
|
1610
2159
|
if (left.length) upsertEntry({ global: Boolean(scope.global), cwd: scope.cwd, home: scope.home }, name, { ...entry, agents: left, mode: Object.fromEntries(Object.entries(entry.mode).filter(([a]) => left.includes(a))) });
|
|
1611
2160
|
else {
|
|
1612
2161
|
const r2 = uninstallSkill(name, scope);
|
|
1613
2162
|
removed.push(...r2.removed);
|
|
1614
|
-
lines.push(
|
|
2163
|
+
lines.push(pc12.dim(` (no agents left \u2014 removed the canonical copy too)`));
|
|
1615
2164
|
}
|
|
1616
2165
|
}
|
|
1617
2166
|
if (acted) changed++;
|
|
@@ -1620,7 +2169,7 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
|
|
|
1620
2169
|
const r = uninstallSkill(name, scope);
|
|
1621
2170
|
removed.push(...r.removed);
|
|
1622
2171
|
changed++;
|
|
1623
|
-
lines.push(
|
|
2172
|
+
lines.push(pc12.green(`${GLYPH.ok} removed ${name}`) + tag + pc12.dim(` (${r.removed.length} path${r.removed.length === 1 ? "" : "s"}${r.untracked ? ", untracked" : ""})`));
|
|
1624
2173
|
}
|
|
1625
2174
|
}
|
|
1626
2175
|
const exitCode = changed ? 0 : 1;
|
|
@@ -1638,7 +2187,7 @@ function removeCommand() {
|
|
|
1638
2187
|
|
|
1639
2188
|
// src/commands/update.ts
|
|
1640
2189
|
import { Command as Command10 } from "commander";
|
|
1641
|
-
import
|
|
2190
|
+
import pc13 from "picocolors";
|
|
1642
2191
|
import { lint as lint3 } from "@skillmds/core";
|
|
1643
2192
|
function pickSkillTree(files, want) {
|
|
1644
2193
|
const norm = files.map((f) => ({ path: f.path.replace(/\\/g, "/"), contents: f.contents }));
|
|
@@ -1666,9 +2215,9 @@ function makeDefaultUpdateDeps(opts = {}) {
|
|
|
1666
2215
|
if (e.status === 404) return null;
|
|
1667
2216
|
throw e;
|
|
1668
2217
|
}
|
|
2218
|
+
const bundle = await fetchBundle(base, slug, token);
|
|
2219
|
+
if (bundle?.length) return { files: bundle, commit_sha: skill.commit_sha ?? void 0 };
|
|
1669
2220
|
if (skill.type === "pack") {
|
|
1670
|
-
const bundle = await fetchBundle(base, slug, token);
|
|
1671
|
-
if (bundle?.length) return { files: bundle, commit_sha: skill.commit_sha ?? void 0 };
|
|
1672
2221
|
const pack = await resolvePackFiles(skill);
|
|
1673
2222
|
if (pack) return { files: pack.files, commit_sha: skill.commit_sha ?? void 0 };
|
|
1674
2223
|
}
|
|
@@ -1701,64 +2250,64 @@ async function runUpdate(names, flags, deps = defaultUpdateDeps) {
|
|
|
1701
2250
|
for (const global of scopes) {
|
|
1702
2251
|
const scope = { global, cwd: flags.cwd, home: flags.home };
|
|
1703
2252
|
const lock = readLock(scope);
|
|
1704
|
-
const installed = listInstalled(scope).filter((
|
|
1705
|
-
for (const
|
|
1706
|
-
seen.add(
|
|
1707
|
-
const label2 = `${
|
|
1708
|
-
const entry = lock.skills[
|
|
2253
|
+
const installed = listInstalled(scope).filter((s2) => names.length === 0 || names.includes(s2.name));
|
|
2254
|
+
for (const s2 of installed) {
|
|
2255
|
+
seen.add(s2.name);
|
|
2256
|
+
const label2 = `${s2.name}${scopes.length > 1 ? pc13.dim(` [${s2.scope}]`) : ""}`;
|
|
2257
|
+
const entry = lock.skills[s2.name];
|
|
1709
2258
|
if (!entry) {
|
|
1710
|
-
lines.push(
|
|
2259
|
+
lines.push(pc13.dim(`- ${label2} untracked \u2014 reinstall with \`npx get-skill add\` to track updates`));
|
|
1711
2260
|
continue;
|
|
1712
2261
|
}
|
|
1713
2262
|
if (entry.source.startsWith("local:")) {
|
|
1714
|
-
lines.push(
|
|
2263
|
+
lines.push(pc13.dim(`- ${label2} local source \u2014 not auto-updated`));
|
|
1715
2264
|
continue;
|
|
1716
2265
|
}
|
|
1717
2266
|
try {
|
|
1718
|
-
const latest = await deps.fetchLatest(entry.source, flags,
|
|
2267
|
+
const latest = await deps.fetchLatest(entry.source, flags, s2.name);
|
|
1719
2268
|
if (!latest) {
|
|
1720
|
-
failed.push(
|
|
1721
|
-
lines.push(
|
|
2269
|
+
failed.push(s2.name);
|
|
2270
|
+
lines.push(pc13.red(`${GLYPH.blocked} ${label2} no longer available upstream (${entry.source})`));
|
|
1722
2271
|
continue;
|
|
1723
2272
|
}
|
|
1724
2273
|
if (entry.source.startsWith("registry:") && latest.commit_sha && entry.commit_sha && latest.commit_sha === entry.commit_sha) {
|
|
1725
|
-
lines.push(
|
|
2274
|
+
lines.push(pc13.dim(`- ${label2} up to date`));
|
|
1726
2275
|
continue;
|
|
1727
2276
|
}
|
|
1728
2277
|
if (digestOf(latest.files) === entry.digest) {
|
|
1729
|
-
lines.push(
|
|
2278
|
+
lines.push(pc13.dim(`- ${label2} up to date`));
|
|
1730
2279
|
continue;
|
|
1731
2280
|
}
|
|
1732
2281
|
if (flags.check) {
|
|
1733
|
-
available.push(
|
|
1734
|
-
lines.push(
|
|
2282
|
+
available.push(s2.name);
|
|
2283
|
+
lines.push(pc13.yellow(`\u2191 ${label2} update available`));
|
|
1735
2284
|
continue;
|
|
1736
2285
|
}
|
|
1737
2286
|
const md = latest.files.find((f) => f.path === "SKILL.md");
|
|
1738
2287
|
const raw = md ? typeof md.contents === "string" ? md.contents : md.contents.toString("utf8") : "";
|
|
1739
|
-
const result = lint3(raw, { slug:
|
|
2288
|
+
const result = lint3(raw, { slug: s2.name });
|
|
1740
2289
|
if (!result.ok) {
|
|
1741
|
-
failed.push(
|
|
1742
|
-
lines.push(
|
|
2290
|
+
failed.push(s2.name);
|
|
2291
|
+
lines.push(pc13.red(`${GLYPH.blocked} ${label2} update failed lint (${result.diagnostics.filter((d) => d.severity === "error").map((d) => d.id).join(", ")}) \u2014 kept the installed version`));
|
|
1743
2292
|
continue;
|
|
1744
2293
|
}
|
|
1745
|
-
await installSkill({ name:
|
|
1746
|
-
updated.push(
|
|
1747
|
-
lines.push(
|
|
2294
|
+
await installSkill({ name: s2.name, files: latest.files, source: entry.source, commit_sha: latest.commit_sha, scope, agents: entry.agents, explicitAgents: true, mode: Object.values(entry.mode).includes("copy") ? "copy" : "link" });
|
|
2295
|
+
updated.push(s2.name);
|
|
2296
|
+
lines.push(pc13.green(`${GLYPH.ok} ${label2} updated`));
|
|
1748
2297
|
} catch (e) {
|
|
1749
|
-
failed.push(
|
|
1750
|
-
lines.push(
|
|
2298
|
+
failed.push(s2.name);
|
|
2299
|
+
lines.push(pc13.red(`${GLYPH.blocked} ${label2} ${e instanceof Error ? e.message : String(e)}`));
|
|
1751
2300
|
}
|
|
1752
2301
|
}
|
|
1753
2302
|
}
|
|
1754
2303
|
for (const n of names) {
|
|
1755
2304
|
if (seen.has(n)) continue;
|
|
1756
2305
|
failed.push(n);
|
|
1757
|
-
lines.push(
|
|
2306
|
+
lines.push(pc13.yellow(`- ${n} not installed`));
|
|
1758
2307
|
}
|
|
1759
2308
|
const exitCode = failed.length ? 1 : 0;
|
|
1760
2309
|
if (flags.json) return { updated, available, failed, exitCode, output: JSON.stringify({ ok: exitCode === 0, updated: [...new Set(updated)], available: [...new Set(available)], failed: [...new Set(failed)] }, null, 2) };
|
|
1761
|
-
if (lines.length === 0) lines.push(
|
|
2310
|
+
if (lines.length === 0) lines.push(pc13.dim("No installed skills found."));
|
|
1762
2311
|
return { updated, available, failed, exitCode, output: lines.join("\n") };
|
|
1763
2312
|
}
|
|
1764
2313
|
function scopeOptions(cmd) {
|
|
@@ -1766,14 +2315,16 @@ function scopeOptions(cmd) {
|
|
|
1766
2315
|
}
|
|
1767
2316
|
function updateCommand() {
|
|
1768
2317
|
return scopeOptions(new Command10("update").description("Update installed skills from the source recorded in the lock file (re-lints before writing)").argument("[names...]", "specific skills (default: all tracked)").option("--check", "only report which skills have updates")).action(async (names, opts, cmd) => {
|
|
1769
|
-
const
|
|
2318
|
+
const flags = { ...cmd.parent?.opts(), ...opts };
|
|
2319
|
+
const run2 = await waitFor("walk", flags.check ? "Checking for updates" : "Updating skills", runUpdate(names, flags), { json: flags.json });
|
|
1770
2320
|
console.log(run2.output);
|
|
1771
2321
|
process.exitCode = run2.exitCode;
|
|
1772
2322
|
});
|
|
1773
2323
|
}
|
|
1774
2324
|
function checkCommand() {
|
|
1775
2325
|
return scopeOptions(new Command10("check").description("Report installed skills that have updates (alias of `update --check`)").argument("[names...]", "specific skills (default: all tracked)")).action(async (names, opts, cmd) => {
|
|
1776
|
-
const
|
|
2326
|
+
const flags = { ...cmd.parent?.opts(), ...opts, check: true };
|
|
2327
|
+
const run2 = await waitFor("walk", "Checking for updates", runUpdate(names, flags), { json: flags.json });
|
|
1777
2328
|
console.log(run2.output);
|
|
1778
2329
|
process.exitCode = run2.exitCode;
|
|
1779
2330
|
});
|
|
@@ -1781,7 +2332,7 @@ function checkCommand() {
|
|
|
1781
2332
|
|
|
1782
2333
|
// src/commands/publish.ts
|
|
1783
2334
|
import { Command as Command11 } from "commander";
|
|
1784
|
-
import
|
|
2335
|
+
import pc14 from "picocolors";
|
|
1785
2336
|
import { lint as lint4, toText } from "@skillmds/core";
|
|
1786
2337
|
var defaultDeps2 = {
|
|
1787
2338
|
resolve: resolveSource,
|
|
@@ -1805,25 +2356,25 @@ async function runPublish(target, flags, deps = defaultDeps2) {
|
|
|
1805
2356
|
const report = toText([{ file: src.path, result }], { decorate });
|
|
1806
2357
|
if (!result.ok) {
|
|
1807
2358
|
return { result, published: false, exitCode: 1, output: `${report}
|
|
1808
|
-
${
|
|
2359
|
+
${pc14.red("\u2717 Refusing to publish: fix the errors above.")}` };
|
|
1809
2360
|
}
|
|
1810
2361
|
const hasWarn = result.diagnostics.some((d) => d.severity === "warn");
|
|
1811
2362
|
if (hasWarn && !flags.force) {
|
|
1812
2363
|
return { result, published: false, exitCode: 1, output: `${report}
|
|
1813
|
-
${
|
|
2364
|
+
${pc14.yellow("\u2717 Warnings present. Re-run with --force to publish anyway.")}` };
|
|
1814
2365
|
}
|
|
1815
2366
|
if (flags.dryRun) {
|
|
1816
2367
|
return { result, published: false, exitCode: 0, output: `${report}
|
|
1817
|
-
${
|
|
2368
|
+
${pc14.green("\u2713 Dry run: would publish.")}` };
|
|
1818
2369
|
}
|
|
1819
2370
|
const res = await deps.submit({ content: src.raw, type: flags.type ?? "single" }, flags);
|
|
1820
2371
|
return { result, published: true, slug: res.slug, exitCode: 0, output: `${report}
|
|
1821
|
-
${
|
|
2372
|
+
${pc14.green(`\u2713 Published ${res.slug ?? src.slug} (status: ${res.status ?? "pending"})`)}` };
|
|
1822
2373
|
}
|
|
1823
2374
|
function publishCommand() {
|
|
1824
2375
|
return new Command11("publish").alias("submit").description("Publish a skill to the registry (refuses to publish on lint errors)").argument("[target]", "local path to the skill", ".").option("--type <type>", "single | pack", "single").option("--force", "publish despite warnings (never bypasses errors)").option("--dry-run", "lint only; do not publish").action(async (target, opts, cmd) => {
|
|
1825
2376
|
const merged = { ...cmd.parent?.opts(), ...opts };
|
|
1826
|
-
const run2 = await runPublish(target, merged);
|
|
2377
|
+
const run2 = await waitFor("walk", merged.dryRun ? "Checking" : "Publishing", runPublish(target, merged));
|
|
1827
2378
|
console.log(run2.output);
|
|
1828
2379
|
process.exitCode = run2.exitCode;
|
|
1829
2380
|
});
|
|
@@ -1831,11 +2382,10 @@ function publishCommand() {
|
|
|
1831
2382
|
|
|
1832
2383
|
// src/commands/login.ts
|
|
1833
2384
|
import { Command as Command12 } from "commander";
|
|
1834
|
-
import
|
|
1835
|
-
import pc13 from "picocolors";
|
|
2385
|
+
import pc15 from "picocolors";
|
|
1836
2386
|
var defaultLoginDeps = (flags) => promptIf(flags, async () => {
|
|
1837
|
-
const answer = await
|
|
1838
|
-
return
|
|
2387
|
+
const answer = await password({ message: "Paste your SkillMD token (from your account page)" });
|
|
2388
|
+
return isCancel(answer) ? null : answer;
|
|
1839
2389
|
});
|
|
1840
2390
|
function finish4(flags, result, doc) {
|
|
1841
2391
|
return flags.json ? { ...result, output: JSON.stringify(doc, null, 2) } : result;
|
|
@@ -1846,15 +2396,15 @@ async function runLogin(flags, deps = defaultLoginDeps(flags)) {
|
|
|
1846
2396
|
if (!token) {
|
|
1847
2397
|
if (!deps.prompt) {
|
|
1848
2398
|
const error = nonInteractiveHint("--token <token> (or set SKILLMD_TOKEN)");
|
|
1849
|
-
return finish4(flags, { exitCode: 1, output:
|
|
2399
|
+
return finish4(flags, { exitCode: 1, output: pc15.red(error) }, { ok: false, error });
|
|
1850
2400
|
}
|
|
1851
2401
|
const answer = await deps.prompt();
|
|
1852
|
-
if (answer === null) return finish4(flags, { exitCode: 0, output:
|
|
2402
|
+
if (answer === null) return finish4(flags, { exitCode: 0, output: pc15.dim("Cancelled.") }, { ok: false, cancelled: true });
|
|
1853
2403
|
token = answer;
|
|
1854
2404
|
}
|
|
1855
2405
|
if (!token) {
|
|
1856
2406
|
const error = "A token is required.";
|
|
1857
|
-
return finish4(flags, { exitCode: 1, output:
|
|
2407
|
+
return finish4(flags, { exitCode: 1, output: pc15.red(error) }, { ok: false, error });
|
|
1858
2408
|
}
|
|
1859
2409
|
const cfg = readConfig(flags.home);
|
|
1860
2410
|
let host;
|
|
@@ -1862,7 +2412,7 @@ async function runLogin(flags, deps = defaultLoginDeps(flags)) {
|
|
|
1862
2412
|
host = hostOf(resolveApi({ api: flags.api, insecureHttp: flags.insecureHttp }, { env, config: { ...cfg, api: flags.api ?? cfg.api } }));
|
|
1863
2413
|
} catch (e) {
|
|
1864
2414
|
const msg2 = e instanceof Error ? e.message : String(e);
|
|
1865
|
-
return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg2 }, null, 2) :
|
|
2415
|
+
return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg2 }, null, 2) : pc15.red(msg2) };
|
|
1866
2416
|
}
|
|
1867
2417
|
cfg.token = token;
|
|
1868
2418
|
if (flags.api) cfg.api = flags.api;
|
|
@@ -1901,11 +2451,10 @@ function logoutCommand() {
|
|
|
1901
2451
|
}
|
|
1902
2452
|
|
|
1903
2453
|
// src/commands/interactive.ts
|
|
1904
|
-
import
|
|
1905
|
-
import pc14 from "picocolors";
|
|
2454
|
+
import pc16 from "picocolors";
|
|
1906
2455
|
function bail(v) {
|
|
1907
|
-
if (
|
|
1908
|
-
|
|
2456
|
+
if (isCancel(v)) {
|
|
2457
|
+
cancel("Cancelled.");
|
|
1909
2458
|
return true;
|
|
1910
2459
|
}
|
|
1911
2460
|
return false;
|
|
@@ -1913,7 +2462,7 @@ function bail(v) {
|
|
|
1913
2462
|
async function browseSearchResults(items) {
|
|
1914
2463
|
const cols = process.stdout.columns ?? 80;
|
|
1915
2464
|
for (; ; ) {
|
|
1916
|
-
const picked = await
|
|
2465
|
+
const picked = await select({
|
|
1917
2466
|
message: "Search results",
|
|
1918
2467
|
options: [
|
|
1919
2468
|
...items.map((item2) => {
|
|
@@ -1921,7 +2470,7 @@ async function browseSearchResults(items) {
|
|
|
1921
2470
|
const checkTxt = item2.verified ? " \u2713" : "";
|
|
1922
2471
|
const slugTxt = safeText(item2.slug, 200);
|
|
1923
2472
|
const plainLabel = `${slugTxt}${starsTxt}${checkTxt}`;
|
|
1924
|
-
const label2 = `${
|
|
2473
|
+
const label2 = `${pc16.bold(slugTxt)}${starsTxt ? pc16.dim(starsTxt) : ""}${checkTxt ? pc16.green(checkTxt) : ""}`;
|
|
1925
2474
|
const hintWidth = Math.max(10, cols - plainLabel.length - 6);
|
|
1926
2475
|
return {
|
|
1927
2476
|
value: item2.slug,
|
|
@@ -1932,7 +2481,7 @@ async function browseSearchResults(items) {
|
|
|
1932
2481
|
{ value: "__back", label: "\u2190 Back to menu" }
|
|
1933
2482
|
]
|
|
1934
2483
|
});
|
|
1935
|
-
if (
|
|
2484
|
+
if (isCancel(picked) || picked === "__back") return;
|
|
1936
2485
|
const item = items.find((i) => i.slug === picked);
|
|
1937
2486
|
if (!item) continue;
|
|
1938
2487
|
await browseOneResult(item);
|
|
@@ -1943,12 +2492,12 @@ async function browseOneResult(item) {
|
|
|
1943
2492
|
const slugTxt = safeText(item.slug, 200);
|
|
1944
2493
|
const meta = [
|
|
1945
2494
|
starsTxt,
|
|
1946
|
-
item.verified ?
|
|
2495
|
+
item.verified ? pc16.green("\u2713 verified") : "unverified",
|
|
1947
2496
|
item.type === "pack" ? "pack" : ""
|
|
1948
2497
|
].filter(Boolean).join(" \xB7 ");
|
|
1949
|
-
|
|
2498
|
+
note([safeText(item.description ?? ""), pc16.dim(meta)].join("\n"), slugTxt);
|
|
1950
2499
|
for (; ; ) {
|
|
1951
|
-
const action = await
|
|
2500
|
+
const action = await select({
|
|
1952
2501
|
message: "What next?",
|
|
1953
2502
|
options: [
|
|
1954
2503
|
{ value: "project", label: "Install to this project" },
|
|
@@ -1957,26 +2506,31 @@ async function browseOneResult(item) {
|
|
|
1957
2506
|
{ value: "back", label: "\u2190 Back to results" }
|
|
1958
2507
|
]
|
|
1959
2508
|
});
|
|
1960
|
-
if (
|
|
2509
|
+
if (isCancel(action) || action === "back") return;
|
|
1961
2510
|
if (action === "url") {
|
|
1962
|
-
|
|
2511
|
+
log.message(pc16.dim(`https://skillmd.com/skills/${slugTxt}`));
|
|
1963
2512
|
continue;
|
|
1964
2513
|
}
|
|
1965
|
-
|
|
2514
|
+
log.step(`Installing ${slugTxt}\u2026`);
|
|
1966
2515
|
try {
|
|
1967
2516
|
const run2 = await runAdd(item.slug, action === "global" ? { global: true } : { project: true });
|
|
1968
2517
|
console.log(run2.output);
|
|
1969
|
-
if (run2.exitCode === 0)
|
|
1970
|
-
else
|
|
2518
|
+
if (run2.exitCode === 0) log.success("Installed.");
|
|
2519
|
+
else log.warn("Not installed.");
|
|
1971
2520
|
} catch (err) {
|
|
1972
|
-
|
|
2521
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
1973
2522
|
}
|
|
1974
2523
|
}
|
|
1975
2524
|
}
|
|
1976
|
-
async function runInteractive() {
|
|
1977
|
-
|
|
2525
|
+
async function runInteractive(opts = {}) {
|
|
2526
|
+
console.log();
|
|
2527
|
+
await playLogo();
|
|
2528
|
+
console.log(`
|
|
2529
|
+
${pc16.dim("Teach your agent anything.")}${opts.version ? ` ${pc16.dim(`v${opts.version}`)}` : ""}
|
|
2530
|
+
`);
|
|
2531
|
+
startFrameAfterLogo();
|
|
1978
2532
|
for (; ; ) {
|
|
1979
|
-
const action = await
|
|
2533
|
+
const action = await select({
|
|
1980
2534
|
message: "What would you like to do?",
|
|
1981
2535
|
options: [
|
|
1982
2536
|
{ value: "lint", label: "Lint a skill", hint: "validate a SKILL.md" },
|
|
@@ -1995,66 +2549,66 @@ async function runInteractive() {
|
|
|
1995
2549
|
switch (action) {
|
|
1996
2550
|
case "lint":
|
|
1997
2551
|
case "scan": {
|
|
1998
|
-
const path = await
|
|
2552
|
+
const path = await text({ message: "Path to a skill or folder", placeholder: ".", defaultValue: "." });
|
|
1999
2553
|
if (bail(path)) break;
|
|
2000
2554
|
const target = String(path || ".");
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2555
|
+
const s2 = spinner("walk");
|
|
2556
|
+
s2.start(action === "lint" ? "Linting" : "Scanning");
|
|
2003
2557
|
try {
|
|
2004
2558
|
const run2 = action === "lint" ? await runLint(target, { format: "text" }) : await runScan(target, { format: "text" });
|
|
2005
|
-
|
|
2559
|
+
s2.stop("Done");
|
|
2006
2560
|
console.log(run2.output);
|
|
2007
2561
|
process.exitCode = run2.exitCode;
|
|
2008
2562
|
} catch (err) {
|
|
2009
|
-
|
|
2563
|
+
s2.stop(pc16.red(err instanceof Error ? err.message : String(err)));
|
|
2010
2564
|
process.exitCode = 1;
|
|
2011
2565
|
}
|
|
2012
2566
|
break;
|
|
2013
2567
|
}
|
|
2014
2568
|
case "search": {
|
|
2015
|
-
const query = await
|
|
2569
|
+
const query = await text({ message: "Search the registry for" });
|
|
2016
2570
|
if (bail(query)) break;
|
|
2017
|
-
const verified = await
|
|
2571
|
+
const verified = await confirm({ message: "Verified skills only?", initialValue: false });
|
|
2018
2572
|
if (bail(verified)) break;
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2573
|
+
const s2 = spinner("hop");
|
|
2574
|
+
s2.start(`Searching for ${safeText(String(query))}`);
|
|
2021
2575
|
try {
|
|
2022
2576
|
const run2 = await runSearch(String(query), { verified: Boolean(verified), limit: "20" });
|
|
2023
|
-
|
|
2577
|
+
s2.stop(`${run2.items.length} result${run2.items.length === 1 ? "" : "s"}`);
|
|
2024
2578
|
if (run2.items.length === 0) {
|
|
2025
|
-
|
|
2579
|
+
log.info("No results.");
|
|
2026
2580
|
break;
|
|
2027
2581
|
}
|
|
2028
2582
|
await browseSearchResults(run2.items);
|
|
2029
2583
|
} catch (err) {
|
|
2030
|
-
|
|
2584
|
+
s2.stop(pc16.red(err instanceof Error ? err.message : String(err)));
|
|
2031
2585
|
process.exitCode = 1;
|
|
2032
2586
|
}
|
|
2033
2587
|
break;
|
|
2034
2588
|
}
|
|
2035
2589
|
case "init": {
|
|
2036
|
-
const name = await
|
|
2590
|
+
const name = await text({ message: "Skill name", placeholder: "my-skill" });
|
|
2037
2591
|
if (bail(name)) break;
|
|
2038
|
-
const description = await
|
|
2592
|
+
const description = await text({ message: "One-line description", placeholder: "What this skill helps an agent do" });
|
|
2039
2593
|
if (bail(description)) break;
|
|
2040
2594
|
try {
|
|
2041
2595
|
const file = initSkill(String(name), { description: String(description || "") });
|
|
2042
|
-
|
|
2596
|
+
log.success(`Created ${pc16.bold(file)}`);
|
|
2043
2597
|
} catch (err) {
|
|
2044
|
-
|
|
2598
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
2045
2599
|
process.exitCode = 1;
|
|
2046
2600
|
}
|
|
2047
2601
|
break;
|
|
2048
2602
|
}
|
|
2049
2603
|
case "install": {
|
|
2050
|
-
const src = await
|
|
2604
|
+
const src = await text({ message: "Skill source", placeholder: "owner/name, https://github.com/\u2026, or ./path" });
|
|
2051
2605
|
if (bail(src)) break;
|
|
2052
2606
|
try {
|
|
2053
2607
|
const run2 = await runAdd(String(src), {});
|
|
2054
2608
|
console.log(run2.output);
|
|
2055
2609
|
process.exitCode = run2.exitCode;
|
|
2056
2610
|
} catch (err) {
|
|
2057
|
-
|
|
2611
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
2058
2612
|
process.exitCode = 1;
|
|
2059
2613
|
}
|
|
2060
2614
|
break;
|
|
@@ -2064,12 +2618,16 @@ async function runInteractive() {
|
|
|
2064
2618
|
break;
|
|
2065
2619
|
}
|
|
2066
2620
|
case "update": {
|
|
2621
|
+
const s2 = spinner("walk");
|
|
2622
|
+
s2.start("Updating installed skills");
|
|
2067
2623
|
try {
|
|
2068
2624
|
const run2 = await runUpdate([], {});
|
|
2625
|
+
const n = new Set(run2.updated).size;
|
|
2626
|
+
s2.stop(n ? `Updated ${n} skill${n === 1 ? "" : "s"}` : "Done", run2.exitCode === 0 ? 0 : 2);
|
|
2069
2627
|
console.log(run2.output);
|
|
2070
2628
|
process.exitCode = run2.exitCode;
|
|
2071
2629
|
} catch (err) {
|
|
2072
|
-
|
|
2630
|
+
s2.stop(pc16.red(err instanceof Error ? err.message : String(err)), 2);
|
|
2073
2631
|
process.exitCode = 1;
|
|
2074
2632
|
}
|
|
2075
2633
|
break;
|
|
@@ -2080,23 +2638,23 @@ async function runInteractive() {
|
|
|
2080
2638
|
console.log(run2.output);
|
|
2081
2639
|
process.exitCode = run2.exitCode;
|
|
2082
2640
|
} catch (err) {
|
|
2083
|
-
|
|
2641
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
2084
2642
|
process.exitCode = 1;
|
|
2085
2643
|
}
|
|
2086
2644
|
break;
|
|
2087
2645
|
}
|
|
2088
2646
|
case "help": {
|
|
2089
|
-
|
|
2647
|
+
note(
|
|
2090
2648
|
[
|
|
2091
|
-
`${
|
|
2092
|
-
`${
|
|
2093
|
-
`${
|
|
2094
|
-
`${
|
|
2095
|
-
`${
|
|
2096
|
-
`${
|
|
2097
|
-
`${
|
|
2098
|
-
`${
|
|
2099
|
-
`${
|
|
2649
|
+
`${pc16.bold("lint")} [path] validate SKILL.md + quality score`,
|
|
2650
|
+
`${pc16.bold("scan")} [path] security scan`,
|
|
2651
|
+
`${pc16.bold("search")} <query> search the registry`,
|
|
2652
|
+
`${pc16.bold("add")} <source> install a skill (lints first)`,
|
|
2653
|
+
`${pc16.bold("info")} <slug> registry details for a skill`,
|
|
2654
|
+
`${pc16.bold("init")} [name] scaffold a new skill`,
|
|
2655
|
+
`${pc16.bold("list / remove / update")} manage installed skills`,
|
|
2656
|
+
`${pc16.bold("publish")} [path] publish (fails on lint errors)`,
|
|
2657
|
+
`${pc16.bold("rules / login")}`
|
|
2100
2658
|
].join("\n"),
|
|
2101
2659
|
"Commands (run: skillmd <command>)"
|
|
2102
2660
|
);
|
|
@@ -2104,7 +2662,7 @@ async function runInteractive() {
|
|
|
2104
2662
|
}
|
|
2105
2663
|
}
|
|
2106
2664
|
}
|
|
2107
|
-
|
|
2665
|
+
outro(pc16.dim("skillmd.com"));
|
|
2108
2666
|
}
|
|
2109
2667
|
|
|
2110
2668
|
// src/index.ts
|
|
@@ -2134,7 +2692,7 @@ Docs: https://skillmd.com/docs/cli`);
|
|
|
2134
2692
|
program.hook("preAction", (thisCmd) => {
|
|
2135
2693
|
const opts = thisCmd.opts();
|
|
2136
2694
|
if (opts.token && process.stderr.isTTY) {
|
|
2137
|
-
console.error(
|
|
2695
|
+
console.error(pc17.yellow("\u26A0 --token is visible in your shell history and process list; prefer `npx get-skill login` or SKILLMD_TOKEN."));
|
|
2138
2696
|
}
|
|
2139
2697
|
});
|
|
2140
2698
|
program.addCommand(lintCommand());
|
|
@@ -2153,7 +2711,7 @@ program.addCommand(loginCommand());
|
|
|
2153
2711
|
program.addCommand(logoutCommand());
|
|
2154
2712
|
if (process.argv.slice(2).length === 0) {
|
|
2155
2713
|
if (process.stdout.isTTY) {
|
|
2156
|
-
await runInteractive();
|
|
2714
|
+
await runInteractive({ version });
|
|
2157
2715
|
process.exit(process.exitCode ?? 0);
|
|
2158
2716
|
}
|
|
2159
2717
|
program.outputHelp();
|