skild 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +328 -95
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6,10 +6,10 @@ import chalk15 from "chalk";
|
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
|
|
8
8
|
// src/commands/install.ts
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import fs2 from "fs";
|
|
10
|
+
import path2 from "path";
|
|
11
11
|
import chalk2 from "chalk";
|
|
12
|
-
import { fetchWithTimeout, installRegistrySkill, installSkill, isValidAlias, loadRegistryAuth, resolveRegistryAlias, resolveRegistryUrl, SkildError, PLATFORMS } from "@skild/core";
|
|
12
|
+
import { fetchWithTimeout, installRegistrySkill, installSkill, isValidAlias, loadRegistryAuth, materializeSourceToTemp, resolveRegistryAlias, resolveRegistryUrl, SkildError, PLATFORMS } from "@skild/core";
|
|
13
13
|
|
|
14
14
|
// src/utils/logger.ts
|
|
15
15
|
import chalk from "chalk";
|
|
@@ -57,10 +57,10 @@ var logger = {
|
|
|
57
57
|
/**
|
|
58
58
|
* Log a skill entry with status indicator.
|
|
59
59
|
*/
|
|
60
|
-
skillEntry: (name,
|
|
60
|
+
skillEntry: (name, path4, hasSkillMd) => {
|
|
61
61
|
const status = hasSkillMd ? chalk.green("\u2713") : chalk.yellow("\u26A0");
|
|
62
62
|
console.log(` ${status} ${chalk.cyan(name)}`);
|
|
63
|
-
console.log(chalk.dim(` \u2514\u2500 ${
|
|
63
|
+
console.log(chalk.dim(` \u2514\u2500 ${path4}`));
|
|
64
64
|
},
|
|
65
65
|
/**
|
|
66
66
|
* Log installation result details.
|
|
@@ -71,6 +71,159 @@ var logger = {
|
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
// src/utils/prompt.ts
|
|
75
|
+
import readline from "readline";
|
|
76
|
+
async function promptLine(question, defaultValue) {
|
|
77
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
78
|
+
try {
|
|
79
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
80
|
+
const answer = await new Promise((resolve) => rl.question(`${question}${suffix}: `, resolve));
|
|
81
|
+
const trimmed = answer.trim();
|
|
82
|
+
return trimmed || defaultValue || "";
|
|
83
|
+
} finally {
|
|
84
|
+
rl.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function promptPassword(question) {
|
|
88
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
89
|
+
return promptLine(question);
|
|
90
|
+
}
|
|
91
|
+
const stdin = process.stdin;
|
|
92
|
+
const stdout = process.stdout;
|
|
93
|
+
stdout.write(`${question}: `);
|
|
94
|
+
const wasRaw = Boolean(stdin.isRaw);
|
|
95
|
+
stdin.setRawMode(true);
|
|
96
|
+
stdin.resume();
|
|
97
|
+
readline.emitKeypressEvents(stdin);
|
|
98
|
+
const buf = [];
|
|
99
|
+
return await new Promise((resolve, reject) => {
|
|
100
|
+
function cleanup() {
|
|
101
|
+
stdin.off("keypress", onKeypress);
|
|
102
|
+
stdin.setRawMode(wasRaw);
|
|
103
|
+
stdin.pause();
|
|
104
|
+
}
|
|
105
|
+
function onKeypress(str, key) {
|
|
106
|
+
if (key?.ctrl && key?.name === "c") {
|
|
107
|
+
stdout.write("\n");
|
|
108
|
+
cleanup();
|
|
109
|
+
const err = new Error("Prompt cancelled");
|
|
110
|
+
err.code = "PROMPT_CANCELLED";
|
|
111
|
+
reject(err);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (key?.name === "return" || key?.name === "enter") {
|
|
115
|
+
stdout.write("\n");
|
|
116
|
+
cleanup();
|
|
117
|
+
resolve(buf.join(""));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (key?.name === "backspace" || key?.name === "delete") {
|
|
121
|
+
if (buf.length) buf.pop();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!str) return;
|
|
125
|
+
if (key?.ctrl || key?.meta) return;
|
|
126
|
+
buf.push(str);
|
|
127
|
+
}
|
|
128
|
+
stdin.on("keypress", onKeypress);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async function promptConfirm(question, options = {}) {
|
|
132
|
+
const defaultValue = options.defaultValue ?? false;
|
|
133
|
+
const suffix = defaultValue ? " (Y/n)" : " (y/N)";
|
|
134
|
+
const answer = await promptLine(`${question}${suffix}`);
|
|
135
|
+
const v = answer.trim().toLowerCase();
|
|
136
|
+
if (!v) return defaultValue;
|
|
137
|
+
if (v === "y" || v === "yes") return true;
|
|
138
|
+
if (v === "n" || v === "no") return false;
|
|
139
|
+
return defaultValue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/commands/install-discovery.ts
|
|
143
|
+
import fs from "fs";
|
|
144
|
+
import path from "path";
|
|
145
|
+
function parsePositiveInt(input, fallback) {
|
|
146
|
+
if (input == null) return fallback;
|
|
147
|
+
const n = typeof input === "number" ? input : Number(String(input).trim());
|
|
148
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
149
|
+
return Math.floor(n);
|
|
150
|
+
}
|
|
151
|
+
function normalizeRelPath(relPath) {
|
|
152
|
+
return relPath.split(path.sep).join("/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
153
|
+
}
|
|
154
|
+
function shouldSkipDir(name) {
|
|
155
|
+
if (!name) return true;
|
|
156
|
+
if (name === ".git") return true;
|
|
157
|
+
if (name === ".skild") return true;
|
|
158
|
+
if (name === "node_modules") return true;
|
|
159
|
+
if (name === "dist") return true;
|
|
160
|
+
if (name === "build") return true;
|
|
161
|
+
if (name === ".wrangler") return true;
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
function discoverSkillDirs(rootDir, options) {
|
|
165
|
+
const root = path.resolve(rootDir);
|
|
166
|
+
const found = [];
|
|
167
|
+
const stack = [{ dir: root, depth: 0 }];
|
|
168
|
+
while (stack.length) {
|
|
169
|
+
const next = stack.pop();
|
|
170
|
+
const dir = next.dir;
|
|
171
|
+
const depth = next.depth;
|
|
172
|
+
const skillMd = path.join(dir, "SKILL.md");
|
|
173
|
+
if (fs.existsSync(skillMd)) {
|
|
174
|
+
const relPath = path.relative(root, dir) || ".";
|
|
175
|
+
found.push({ relPath: normalizeRelPath(relPath), absDir: dir });
|
|
176
|
+
if (found.length >= options.maxSkills + 1) return found;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (depth >= options.maxDepth) continue;
|
|
180
|
+
let entries;
|
|
181
|
+
try {
|
|
182
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
183
|
+
} catch {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
if (!entry.isDirectory()) continue;
|
|
188
|
+
if (shouldSkipDir(entry.name)) continue;
|
|
189
|
+
const child = path.join(dir, entry.name);
|
|
190
|
+
stack.push({ dir: child, depth: depth + 1 });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return found;
|
|
194
|
+
}
|
|
195
|
+
function discoverSkillDirsWithHeuristics(rootDir, options) {
|
|
196
|
+
const root = path.resolve(rootDir);
|
|
197
|
+
const candidates = [
|
|
198
|
+
"skills",
|
|
199
|
+
path.join(".agent", "skills"),
|
|
200
|
+
path.join(".claude", "skills"),
|
|
201
|
+
path.join(".codex", "skills"),
|
|
202
|
+
path.join(".github", "skills")
|
|
203
|
+
];
|
|
204
|
+
for (const rel of candidates) {
|
|
205
|
+
const candidateDir = path.join(root, rel);
|
|
206
|
+
if (!fs.existsSync(candidateDir)) continue;
|
|
207
|
+
try {
|
|
208
|
+
if (!fs.statSync(candidateDir).isDirectory()) continue;
|
|
209
|
+
} catch {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const found = discoverSkillDirs(candidateDir, options).map((s) => ({
|
|
213
|
+
relPath: normalizeRelPath(path.join(rel, s.relPath)),
|
|
214
|
+
absDir: s.absDir
|
|
215
|
+
}));
|
|
216
|
+
if (found.length) return found;
|
|
217
|
+
}
|
|
218
|
+
return discoverSkillDirs(root, options);
|
|
219
|
+
}
|
|
220
|
+
function deriveRemoteChildSource(baseSource, relPath) {
|
|
221
|
+
const [pathPart, ref] = baseSource.split("#", 2);
|
|
222
|
+
const clean = normalizeRelPath(relPath);
|
|
223
|
+
const joined = clean ? `${pathPart.replace(/\/+$/, "")}/${clean}` : pathPart;
|
|
224
|
+
return ref ? `${joined}#${ref}` : joined;
|
|
225
|
+
}
|
|
226
|
+
|
|
74
227
|
// src/commands/install.ts
|
|
75
228
|
function looksLikeAlias(input) {
|
|
76
229
|
const s = input.trim();
|
|
@@ -78,20 +231,39 @@ function looksLikeAlias(input) {
|
|
|
78
231
|
if (s.startsWith("@")) return false;
|
|
79
232
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
80
233
|
if (/^https?:\/\//i.test(s) || s.includes("github.com")) return false;
|
|
81
|
-
if (
|
|
234
|
+
if (fs2.existsSync(path2.resolve(s))) return false;
|
|
82
235
|
if (!isValidAlias(s)) return false;
|
|
83
236
|
return true;
|
|
84
237
|
}
|
|
238
|
+
function printJson(value) {
|
|
239
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
240
|
+
`);
|
|
241
|
+
}
|
|
242
|
+
function previewDiscovered(found, limit = 12) {
|
|
243
|
+
const preview = found.slice(0, limit).map((s) => ` - ${s.relPath}`).join("\n");
|
|
244
|
+
return `${preview}${found.length > limit ? "\n ..." : ""}`;
|
|
245
|
+
}
|
|
246
|
+
function asDiscoveredSkills(discovered, toSuggestedSource, toMaterializedDir) {
|
|
247
|
+
return discovered.map((d) => ({
|
|
248
|
+
relPath: d.relPath,
|
|
249
|
+
suggestedSource: toSuggestedSource(d),
|
|
250
|
+
materializedDir: toMaterializedDir ? toMaterializedDir(d) : void 0
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
85
253
|
async function install(source, options = {}) {
|
|
86
254
|
const scope = options.local ? "project" : "global";
|
|
87
255
|
const auth = loadRegistryAuth();
|
|
88
256
|
const registryUrlForDeps = options.registry || auth?.registryUrl;
|
|
89
257
|
const all = Boolean(options.all);
|
|
90
258
|
const jsonOnly = Boolean(options.json);
|
|
259
|
+
const recursive = Boolean(options.recursive);
|
|
260
|
+
const yes = Boolean(options.yes);
|
|
261
|
+
const maxDepth = parsePositiveInt(options.depth, 6);
|
|
262
|
+
const maxSkills = parsePositiveInt(options.maxSkills, 200);
|
|
91
263
|
const platform = options.target || "claude";
|
|
92
264
|
if (all && options.target) {
|
|
93
265
|
const message = "Invalid options: use either --all or --target, not both.";
|
|
94
|
-
if (jsonOnly)
|
|
266
|
+
if (jsonOnly) printJson({ ok: false, error: message });
|
|
95
267
|
else console.error(chalk2.red(message));
|
|
96
268
|
process.exitCode = 1;
|
|
97
269
|
return;
|
|
@@ -106,7 +278,7 @@ async function install(source, options = {}) {
|
|
|
106
278
|
}
|
|
107
279
|
} catch (error) {
|
|
108
280
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
109
|
-
if (jsonOnly)
|
|
281
|
+
if (jsonOnly) printJson({ ok: false, error: message });
|
|
110
282
|
else console.error(chalk2.red(message));
|
|
111
283
|
process.exitCode = 1;
|
|
112
284
|
return;
|
|
@@ -114,33 +286,148 @@ async function install(source, options = {}) {
|
|
|
114
286
|
const targets = all ? [...PLATFORMS] : [platform];
|
|
115
287
|
const results = [];
|
|
116
288
|
const errors = [];
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
289
|
+
let effectiveRecursive = recursive;
|
|
290
|
+
let recursiveSkillCount = null;
|
|
291
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !jsonOnly;
|
|
292
|
+
async function installOne(inputSource, materializedDir) {
|
|
121
293
|
for (const targetPlatform of targets) {
|
|
122
|
-
if (spinner) spinner.text = `Installing ${chalk2.cyan(source)} to ${chalk2.dim(targetPlatform)} (${scope})...`;
|
|
123
294
|
try {
|
|
124
|
-
const record =
|
|
125
|
-
{ spec:
|
|
295
|
+
const record = inputSource.startsWith("@") && inputSource.includes("/") ? await installRegistrySkill(
|
|
296
|
+
{ spec: inputSource, registryUrl: registryUrlForDeps },
|
|
126
297
|
{ platform: targetPlatform, scope, force: Boolean(options.force) }
|
|
127
298
|
) : await installSkill(
|
|
128
|
-
{ source:
|
|
299
|
+
{ source: inputSource, materializedDir },
|
|
129
300
|
{ platform: targetPlatform, scope, force: Boolean(options.force), registryUrl: registryUrlForDeps }
|
|
130
301
|
);
|
|
131
302
|
results.push(record);
|
|
132
303
|
void reportDownload(record, registryUrlForDeps);
|
|
133
304
|
} catch (error) {
|
|
134
305
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
135
|
-
errors.push({ platform: targetPlatform, error: message });
|
|
306
|
+
errors.push({ platform: targetPlatform, error: message, inputSource });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function maybeEnableRecursiveAndInstall(found) {
|
|
311
|
+
if (found.length === 0) return false;
|
|
312
|
+
if (found.length > maxSkills) {
|
|
313
|
+
const message = `Found more than ${maxSkills} skills. Increase --max-skills to proceed.`;
|
|
314
|
+
if (jsonOnly) printJson({ ok: false, error: "TOO_MANY_SKILLS", message, source, resolvedSource, maxSkills });
|
|
315
|
+
else console.error(chalk2.red(message));
|
|
316
|
+
process.exitCode = 1;
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
if (!effectiveRecursive) {
|
|
320
|
+
if (jsonOnly) {
|
|
321
|
+
const foundOutput = found.map(({ relPath, suggestedSource }) => ({ relPath, suggestedSource }));
|
|
322
|
+
printJson({
|
|
323
|
+
ok: false,
|
|
324
|
+
error: "MULTI_SKILL_SOURCE",
|
|
325
|
+
message: "Source is not a skill root (missing SKILL.md). Multiple skills were found.",
|
|
326
|
+
source,
|
|
327
|
+
resolvedSource,
|
|
328
|
+
found: foundOutput
|
|
329
|
+
});
|
|
330
|
+
process.exitCode = 1;
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
const headline = found.length === 1 ? `No SKILL.md found at root. Found 1 skill:
|
|
334
|
+
${previewDiscovered(found)}
|
|
335
|
+
` : `No SKILL.md found at root. Found ${found.length} skills:
|
|
336
|
+
${previewDiscovered(found)}
|
|
337
|
+
`;
|
|
338
|
+
console.log(chalk2.yellow(headline));
|
|
339
|
+
const question = found.length === 1 ? "Install the discovered skill?" : "Install all discovered skills?";
|
|
340
|
+
const confirm = yes || interactive && await promptConfirm(question, { defaultValue: found.length === 1 });
|
|
341
|
+
if (!confirm) {
|
|
342
|
+
console.log(chalk2.dim(`Tip: rerun with ${chalk2.cyan("skild install <source> --recursive")} to install all.`));
|
|
343
|
+
process.exitCode = 1;
|
|
344
|
+
return true;
|
|
136
345
|
}
|
|
346
|
+
effectiveRecursive = true;
|
|
347
|
+
}
|
|
348
|
+
recursiveSkillCount = found.length;
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const spinner = jsonOnly ? null : createSpinner(
|
|
353
|
+
all ? `Installing ${chalk2.cyan(source)} to ${chalk2.dim("all platforms")} (${scope})...` : `Installing ${chalk2.cyan(source)} to ${chalk2.dim(platform)} (${scope})...`
|
|
354
|
+
);
|
|
355
|
+
let cleanupMaterialized = null;
|
|
356
|
+
let materializedRoot = null;
|
|
357
|
+
try {
|
|
358
|
+
if (resolvedSource.startsWith("@") && resolvedSource.includes("/")) {
|
|
359
|
+
await installOne(resolvedSource);
|
|
360
|
+
} else {
|
|
361
|
+
const maybeLocalRoot = path2.resolve(resolvedSource);
|
|
362
|
+
const isLocal = fs2.existsSync(maybeLocalRoot);
|
|
363
|
+
if (isLocal) {
|
|
364
|
+
const hasSkillMd = fs2.existsSync(path2.join(maybeLocalRoot, "SKILL.md"));
|
|
365
|
+
if (hasSkillMd) {
|
|
366
|
+
await installOne(resolvedSource);
|
|
367
|
+
} else {
|
|
368
|
+
const discovered = discoverSkillDirsWithHeuristics(maybeLocalRoot, { maxDepth, maxSkills });
|
|
369
|
+
if (discovered.length === 0) {
|
|
370
|
+
const message = `No SKILL.md found at ${maybeLocalRoot} (or within subdirectories).`;
|
|
371
|
+
if (jsonOnly) {
|
|
372
|
+
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source, resolvedSource });
|
|
373
|
+
} else {
|
|
374
|
+
console.error(chalk2.red(message));
|
|
375
|
+
}
|
|
376
|
+
process.exitCode = 1;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const found = asDiscoveredSkills(discovered, (d) => path2.join(maybeLocalRoot, d.relPath));
|
|
380
|
+
const didReturn = await maybeEnableRecursiveAndInstall(found);
|
|
381
|
+
if (didReturn) return;
|
|
382
|
+
if (spinner) spinner.text = `Installing ${chalk2.cyan(source)} \u2014 discovered ${found.length} skills...`;
|
|
383
|
+
for (const skill of found) {
|
|
384
|
+
if (spinner) spinner.text = `Installing ${chalk2.cyan(skill.relPath)} (${scope})...`;
|
|
385
|
+
await installOne(skill.suggestedSource, skill.materializedDir);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
const materialized = await materializeSourceToTemp(resolvedSource);
|
|
390
|
+
cleanupMaterialized = materialized.cleanup;
|
|
391
|
+
materializedRoot = materialized.dir;
|
|
392
|
+
const hasSkillMd = fs2.existsSync(path2.join(materializedRoot, "SKILL.md"));
|
|
393
|
+
if (hasSkillMd) {
|
|
394
|
+
await installOne(resolvedSource, materializedRoot);
|
|
395
|
+
} else {
|
|
396
|
+
const discovered = discoverSkillDirsWithHeuristics(materializedRoot, { maxDepth, maxSkills });
|
|
397
|
+
if (discovered.length === 0) {
|
|
398
|
+
const message = `No SKILL.md found in source "${resolvedSource}".`;
|
|
399
|
+
if (jsonOnly) {
|
|
400
|
+
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source, resolvedSource });
|
|
401
|
+
} else {
|
|
402
|
+
console.error(chalk2.red(message));
|
|
403
|
+
}
|
|
404
|
+
process.exitCode = 1;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const found = asDiscoveredSkills(
|
|
408
|
+
discovered,
|
|
409
|
+
(d) => deriveRemoteChildSource(resolvedSource, d.relPath),
|
|
410
|
+
(d) => d.absDir
|
|
411
|
+
);
|
|
412
|
+
const didReturn = await maybeEnableRecursiveAndInstall(found);
|
|
413
|
+
if (didReturn) return;
|
|
414
|
+
if (spinner) spinner.text = `Installing ${chalk2.cyan(source)} \u2014 discovered ${found.length} skills...`;
|
|
415
|
+
for (const skill of found) {
|
|
416
|
+
if (spinner) spinner.text = `Installing ${chalk2.cyan(skill.relPath)} (${scope})...`;
|
|
417
|
+
await installOne(skill.suggestedSource, skill.materializedDir);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
} finally {
|
|
423
|
+
if (cleanupMaterialized) cleanupMaterialized();
|
|
137
424
|
}
|
|
138
425
|
if (jsonOnly) {
|
|
139
|
-
if (!all) {
|
|
140
|
-
if (errors.length)
|
|
141
|
-
else
|
|
426
|
+
if (!all && !effectiveRecursive) {
|
|
427
|
+
if (errors.length) printJson({ ok: false, error: errors[0]?.error || "Install failed." });
|
|
428
|
+
else printJson(results[0] ?? null);
|
|
142
429
|
} else {
|
|
143
|
-
|
|
430
|
+
printJson({ ok: errors.length === 0, source, resolvedSource, scope, recursive: effectiveRecursive, all, recursiveSkillCount, results, errors });
|
|
144
431
|
}
|
|
145
432
|
process.exitCode = errors.length ? 1 : 0;
|
|
146
433
|
return;
|
|
@@ -148,12 +435,17 @@ async function install(source, options = {}) {
|
|
|
148
435
|
if (errors.length === 0) {
|
|
149
436
|
const displayName = results[0]?.canonicalName || results[0]?.name || source;
|
|
150
437
|
spinner.succeed(
|
|
151
|
-
all ? `Installed ${chalk2.green(displayName)} to ${chalk2.dim(`${results.length} platforms`)}` : `Installed ${chalk2.green(displayName)} to ${chalk2.dim(results[0]?.installDir || "")}`
|
|
438
|
+
effectiveRecursive ? `Installed ${chalk2.green(String(recursiveSkillCount ?? results.length))}${chalk2.dim(" skills")} to ${chalk2.dim(`${targets.length} platforms`)}` : all ? `Installed ${chalk2.green(displayName)} to ${chalk2.dim(`${results.length} platforms`)}` : `Installed ${chalk2.green(displayName)} to ${chalk2.dim(results[0]?.installDir || "")}`
|
|
152
439
|
);
|
|
153
440
|
} else {
|
|
154
|
-
|
|
441
|
+
const attempted = results.length + errors.length;
|
|
442
|
+
spinner.fail(
|
|
443
|
+
effectiveRecursive ? `Install had failures (${errors.length}/${attempted} installs failed)` : `Failed to install ${chalk2.red(source)} to ${errors.length}/${targets.length} platforms`
|
|
444
|
+
);
|
|
445
|
+
process.exitCode = 1;
|
|
446
|
+
if (!all && errors[0]) console.error(chalk2.red(errors[0].error));
|
|
155
447
|
}
|
|
156
|
-
if (!all && results[0]) {
|
|
448
|
+
if (!effectiveRecursive && !all && results[0]) {
|
|
157
449
|
const record = results[0];
|
|
158
450
|
if (record.hasSkillMd) logger.installDetail("SKILL.md found \u2713");
|
|
159
451
|
else logger.installDetail("Warning: No SKILL.md found", true);
|
|
@@ -162,12 +454,13 @@ async function install(source, options = {}) {
|
|
|
162
454
|
} else if (record.skill?.validation?.ok) {
|
|
163
455
|
logger.installDetail(`Validation: ${chalk2.green("ok")}`);
|
|
164
456
|
}
|
|
165
|
-
} else if (all) {
|
|
166
|
-
for (const r of results) {
|
|
457
|
+
} else if (effectiveRecursive || all) {
|
|
458
|
+
for (const r of results.slice(0, 60)) {
|
|
167
459
|
const displayName = r.canonicalName || r.name;
|
|
168
460
|
const suffix = r.hasSkillMd ? chalk2.green("\u2713") : chalk2.yellow("\u26A0");
|
|
169
461
|
console.log(` ${suffix} ${chalk2.cyan(displayName)} \u2192 ${chalk2.dim(r.platform)}`);
|
|
170
462
|
}
|
|
463
|
+
if (results.length > 60) console.log(chalk2.dim(` ... and ${results.length - 60} more`));
|
|
171
464
|
if (errors.length) {
|
|
172
465
|
console.log(chalk2.yellow("\nFailures:"));
|
|
173
466
|
for (const e of errors) console.log(chalk2.yellow(` - ${e.platform}: ${e.error}`));
|
|
@@ -176,7 +469,7 @@ async function install(source, options = {}) {
|
|
|
176
469
|
}
|
|
177
470
|
} catch (error) {
|
|
178
471
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
179
|
-
if (jsonOnly)
|
|
472
|
+
if (jsonOnly) printJson({ ok: false, error: message });
|
|
180
473
|
else console.error(chalk2.red(message));
|
|
181
474
|
process.exitCode = 1;
|
|
182
475
|
}
|
|
@@ -498,66 +791,6 @@ async function init(name, options = {}) {
|
|
|
498
791
|
// src/commands/signup.ts
|
|
499
792
|
import chalk9 from "chalk";
|
|
500
793
|
import { fetchWithTimeout as fetchWithTimeout2, resolveRegistryUrl as resolveRegistryUrl2, SkildError as SkildError6 } from "@skild/core";
|
|
501
|
-
|
|
502
|
-
// src/utils/prompt.ts
|
|
503
|
-
import readline from "readline";
|
|
504
|
-
async function promptLine(question, defaultValue) {
|
|
505
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
506
|
-
try {
|
|
507
|
-
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
508
|
-
const answer = await new Promise((resolve) => rl.question(`${question}${suffix}: `, resolve));
|
|
509
|
-
const trimmed = answer.trim();
|
|
510
|
-
return trimmed || defaultValue || "";
|
|
511
|
-
} finally {
|
|
512
|
-
rl.close();
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async function promptPassword(question) {
|
|
516
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
517
|
-
return promptLine(question);
|
|
518
|
-
}
|
|
519
|
-
const stdin = process.stdin;
|
|
520
|
-
const stdout = process.stdout;
|
|
521
|
-
stdout.write(`${question}: `);
|
|
522
|
-
const wasRaw = Boolean(stdin.isRaw);
|
|
523
|
-
stdin.setRawMode(true);
|
|
524
|
-
stdin.resume();
|
|
525
|
-
readline.emitKeypressEvents(stdin);
|
|
526
|
-
const buf = [];
|
|
527
|
-
return await new Promise((resolve, reject) => {
|
|
528
|
-
function cleanup() {
|
|
529
|
-
stdin.off("keypress", onKeypress);
|
|
530
|
-
stdin.setRawMode(wasRaw);
|
|
531
|
-
stdin.pause();
|
|
532
|
-
}
|
|
533
|
-
function onKeypress(str, key) {
|
|
534
|
-
if (key?.ctrl && key?.name === "c") {
|
|
535
|
-
stdout.write("\n");
|
|
536
|
-
cleanup();
|
|
537
|
-
const err = new Error("Prompt cancelled");
|
|
538
|
-
err.code = "PROMPT_CANCELLED";
|
|
539
|
-
reject(err);
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
if (key?.name === "return" || key?.name === "enter") {
|
|
543
|
-
stdout.write("\n");
|
|
544
|
-
cleanup();
|
|
545
|
-
resolve(buf.join(""));
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
if (key?.name === "backspace" || key?.name === "delete") {
|
|
549
|
-
if (buf.length) buf.pop();
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
if (!str) return;
|
|
553
|
-
if (key?.ctrl || key?.meta) return;
|
|
554
|
-
buf.push(str);
|
|
555
|
-
}
|
|
556
|
-
stdin.on("keypress", onKeypress);
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// src/commands/signup.ts
|
|
561
794
|
async function signup(options) {
|
|
562
795
|
const registry = resolveRegistryUrl2(options.registry);
|
|
563
796
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -750,9 +983,9 @@ async function whoami() {
|
|
|
750
983
|
}
|
|
751
984
|
|
|
752
985
|
// src/commands/publish.ts
|
|
753
|
-
import
|
|
986
|
+
import fs3 from "fs";
|
|
754
987
|
import os from "os";
|
|
755
|
-
import
|
|
988
|
+
import path3 from "path";
|
|
756
989
|
import crypto from "crypto";
|
|
757
990
|
import * as tar from "tar";
|
|
758
991
|
import chalk13 from "chalk";
|
|
@@ -775,7 +1008,7 @@ async function publish(options = {}) {
|
|
|
775
1008
|
process.exitCode = 1;
|
|
776
1009
|
return;
|
|
777
1010
|
}
|
|
778
|
-
const dir =
|
|
1011
|
+
const dir = path3.resolve(options.dir || process.cwd());
|
|
779
1012
|
const validation = validateSkillDir(dir);
|
|
780
1013
|
if (!validation.ok) {
|
|
781
1014
|
console.error(chalk13.red("Skill validation failed:"));
|
|
@@ -845,8 +1078,8 @@ async function publish(options = {}) {
|
|
|
845
1078
|
}
|
|
846
1079
|
}
|
|
847
1080
|
const spinner = createSpinner(`Publishing ${chalk13.cyan(`${name}@${version2}`)} to ${chalk13.dim(registry)}...`);
|
|
848
|
-
const tempDir =
|
|
849
|
-
const tarballPath =
|
|
1081
|
+
const tempDir = fs3.mkdtempSync(path3.join(os.tmpdir(), "skild-publish-"));
|
|
1082
|
+
const tarballPath = path3.join(tempDir, "skill.tgz");
|
|
850
1083
|
try {
|
|
851
1084
|
await tar.c(
|
|
852
1085
|
{
|
|
@@ -858,7 +1091,7 @@ async function publish(options = {}) {
|
|
|
858
1091
|
},
|
|
859
1092
|
["."]
|
|
860
1093
|
);
|
|
861
|
-
const buf =
|
|
1094
|
+
const buf = fs3.readFileSync(tarballPath);
|
|
862
1095
|
const integrity = sha256Hex(buf);
|
|
863
1096
|
const form = new FormData();
|
|
864
1097
|
form.set("version", version2);
|
|
@@ -925,7 +1158,7 @@ async function publish(options = {}) {
|
|
|
925
1158
|
console.error(chalk13.red(message));
|
|
926
1159
|
process.exitCode = 1;
|
|
927
1160
|
} finally {
|
|
928
|
-
|
|
1161
|
+
fs3.rmSync(tempDir, { recursive: true, force: true });
|
|
929
1162
|
}
|
|
930
1163
|
}
|
|
931
1164
|
|
|
@@ -967,7 +1200,7 @@ var require2 = createRequire(import.meta.url);
|
|
|
967
1200
|
var { version } = require2("../package.json");
|
|
968
1201
|
var program = new Command();
|
|
969
1202
|
program.name("skild").description("The npm for Agent Skills \u2014 Discover, install, manage, and publish AI Agent Skills with ease.").version(version);
|
|
970
|
-
program.command("install <source>").alias("i").description("Install a Skill from a Git URL, degit shorthand, or local directory").option("-t, --target <platform>", `Target platform: ${PLATFORMS3.join(", ")}`).option("--all", `Install to all platforms: ${PLATFORMS3.join(", ")}`).option("-l, --local", "Install to project-level directory instead of global").option("-f, --force", "Overwrite existing installation").option("--registry <url>", "Registry base URL (default: https://registry.skild.sh)").option("--json", "Output JSON").action(async (source, options) => {
|
|
1203
|
+
program.command("install <source>").alias("i").description("Install a Skill from a Git URL, degit shorthand, or local directory").option("-t, --target <platform>", `Target platform: ${PLATFORMS3.join(", ")}`).option("--all", `Install to all platforms: ${PLATFORMS3.join(", ")}`).option("--recursive", "If source is a multi-skill directory/repo, install all discovered skills").option("-y, --yes", "Skip confirmation prompts (assume yes)").option("--depth <n>", "Max directory depth to scan for SKILL.md (default: 6)", "6").option("--max-skills <n>", "Max discovered skills to install (default: 200)", "200").option("-l, --local", "Install to project-level directory instead of global").option("-f, --force", "Overwrite existing installation").option("--registry <url>", "Registry base URL (default: https://registry.skild.sh)").option("--json", "Output JSON").action(async (source, options) => {
|
|
971
1204
|
await install(source, options);
|
|
972
1205
|
});
|
|
973
1206
|
program.command("list").alias("ls").description("List installed Skills").option("-t, --target <platform>", `Target platform: ${PLATFORMS3.join(", ")} (optional; omit to list all)`).option("-l, --local", "List project-level directory instead of global").option("--paths", "Show install paths").option("--verbose", "Show skillset dependency details").option("--json", "Output JSON").action(async (options) => list(options));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skild",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The npm for Agent Skills — Discover, install, manage, and publish AI Agent Skills with ease.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"commander": "^12.1.0",
|
|
38
38
|
"ora": "^8.0.1",
|
|
39
39
|
"tar": "^7.4.3",
|
|
40
|
-
"@skild/core": "^0.
|
|
40
|
+
"@skild/core": "^0.4.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^20.10.0",
|