skild 0.3.2 → 0.4.1
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 +320 -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 { deriveChildSource, 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,153 @@ 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
|
+
|
|
74
221
|
// src/commands/install.ts
|
|
75
222
|
function looksLikeAlias(input) {
|
|
76
223
|
const s = input.trim();
|
|
@@ -78,20 +225,39 @@ function looksLikeAlias(input) {
|
|
|
78
225
|
if (s.startsWith("@")) return false;
|
|
79
226
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
80
227
|
if (/^https?:\/\//i.test(s) || s.includes("github.com")) return false;
|
|
81
|
-
if (
|
|
228
|
+
if (fs2.existsSync(path2.resolve(s))) return false;
|
|
82
229
|
if (!isValidAlias(s)) return false;
|
|
83
230
|
return true;
|
|
84
231
|
}
|
|
232
|
+
function printJson(value) {
|
|
233
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
function previewDiscovered(found, limit = 12) {
|
|
237
|
+
const preview = found.slice(0, limit).map((s) => ` - ${s.relPath}`).join("\n");
|
|
238
|
+
return `${preview}${found.length > limit ? "\n ..." : ""}`;
|
|
239
|
+
}
|
|
240
|
+
function asDiscoveredSkills(discovered, toSuggestedSource, toMaterializedDir) {
|
|
241
|
+
return discovered.map((d) => ({
|
|
242
|
+
relPath: d.relPath,
|
|
243
|
+
suggestedSource: toSuggestedSource(d),
|
|
244
|
+
materializedDir: toMaterializedDir ? toMaterializedDir(d) : void 0
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
85
247
|
async function install(source, options = {}) {
|
|
86
248
|
const scope = options.local ? "project" : "global";
|
|
87
249
|
const auth = loadRegistryAuth();
|
|
88
250
|
const registryUrlForDeps = options.registry || auth?.registryUrl;
|
|
89
251
|
const all = Boolean(options.all);
|
|
90
252
|
const jsonOnly = Boolean(options.json);
|
|
253
|
+
const recursive = Boolean(options.recursive);
|
|
254
|
+
const yes = Boolean(options.yes);
|
|
255
|
+
const maxDepth = parsePositiveInt(options.depth, 6);
|
|
256
|
+
const maxSkills = parsePositiveInt(options.maxSkills, 200);
|
|
91
257
|
const platform = options.target || "claude";
|
|
92
258
|
if (all && options.target) {
|
|
93
259
|
const message = "Invalid options: use either --all or --target, not both.";
|
|
94
|
-
if (jsonOnly)
|
|
260
|
+
if (jsonOnly) printJson({ ok: false, error: message });
|
|
95
261
|
else console.error(chalk2.red(message));
|
|
96
262
|
process.exitCode = 1;
|
|
97
263
|
return;
|
|
@@ -106,7 +272,7 @@ async function install(source, options = {}) {
|
|
|
106
272
|
}
|
|
107
273
|
} catch (error) {
|
|
108
274
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
109
|
-
if (jsonOnly)
|
|
275
|
+
if (jsonOnly) printJson({ ok: false, error: message });
|
|
110
276
|
else console.error(chalk2.red(message));
|
|
111
277
|
process.exitCode = 1;
|
|
112
278
|
return;
|
|
@@ -114,33 +280,148 @@ async function install(source, options = {}) {
|
|
|
114
280
|
const targets = all ? [...PLATFORMS] : [platform];
|
|
115
281
|
const results = [];
|
|
116
282
|
const errors = [];
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
283
|
+
let effectiveRecursive = recursive;
|
|
284
|
+
let recursiveSkillCount = null;
|
|
285
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !jsonOnly;
|
|
286
|
+
async function installOne(inputSource, materializedDir) {
|
|
121
287
|
for (const targetPlatform of targets) {
|
|
122
|
-
if (spinner) spinner.text = `Installing ${chalk2.cyan(source)} to ${chalk2.dim(targetPlatform)} (${scope})...`;
|
|
123
288
|
try {
|
|
124
|
-
const record =
|
|
125
|
-
{ spec:
|
|
289
|
+
const record = inputSource.startsWith("@") && inputSource.includes("/") ? await installRegistrySkill(
|
|
290
|
+
{ spec: inputSource, registryUrl: registryUrlForDeps },
|
|
126
291
|
{ platform: targetPlatform, scope, force: Boolean(options.force) }
|
|
127
292
|
) : await installSkill(
|
|
128
|
-
{ source:
|
|
293
|
+
{ source: inputSource, materializedDir },
|
|
129
294
|
{ platform: targetPlatform, scope, force: Boolean(options.force), registryUrl: registryUrlForDeps }
|
|
130
295
|
);
|
|
131
296
|
results.push(record);
|
|
132
297
|
void reportDownload(record, registryUrlForDeps);
|
|
133
298
|
} catch (error) {
|
|
134
299
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
135
|
-
errors.push({ platform: targetPlatform, error: message });
|
|
300
|
+
errors.push({ platform: targetPlatform, error: message, inputSource });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function maybeEnableRecursiveAndInstall(found) {
|
|
305
|
+
if (found.length === 0) return false;
|
|
306
|
+
if (found.length > maxSkills) {
|
|
307
|
+
const message = `Found more than ${maxSkills} skills. Increase --max-skills to proceed.`;
|
|
308
|
+
if (jsonOnly) printJson({ ok: false, error: "TOO_MANY_SKILLS", message, source, resolvedSource, maxSkills });
|
|
309
|
+
else console.error(chalk2.red(message));
|
|
310
|
+
process.exitCode = 1;
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
if (!effectiveRecursive) {
|
|
314
|
+
if (jsonOnly) {
|
|
315
|
+
const foundOutput = found.map(({ relPath, suggestedSource }) => ({ relPath, suggestedSource }));
|
|
316
|
+
printJson({
|
|
317
|
+
ok: false,
|
|
318
|
+
error: "MULTI_SKILL_SOURCE",
|
|
319
|
+
message: "Source is not a skill root (missing SKILL.md). Multiple skills were found.",
|
|
320
|
+
source,
|
|
321
|
+
resolvedSource,
|
|
322
|
+
found: foundOutput
|
|
323
|
+
});
|
|
324
|
+
process.exitCode = 1;
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
const headline = found.length === 1 ? `No SKILL.md found at root. Found 1 skill:
|
|
328
|
+
${previewDiscovered(found)}
|
|
329
|
+
` : `No SKILL.md found at root. Found ${found.length} skills:
|
|
330
|
+
${previewDiscovered(found)}
|
|
331
|
+
`;
|
|
332
|
+
console.log(chalk2.yellow(headline));
|
|
333
|
+
const question = found.length === 1 ? "Install the discovered skill?" : "Install all discovered skills?";
|
|
334
|
+
const confirm = yes || interactive && await promptConfirm(question, { defaultValue: found.length === 1 });
|
|
335
|
+
if (!confirm) {
|
|
336
|
+
console.log(chalk2.dim(`Tip: rerun with ${chalk2.cyan("skild install <source> --recursive")} to install all.`));
|
|
337
|
+
process.exitCode = 1;
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
effectiveRecursive = true;
|
|
341
|
+
}
|
|
342
|
+
recursiveSkillCount = found.length;
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const spinner = jsonOnly ? null : createSpinner(
|
|
347
|
+
all ? `Installing ${chalk2.cyan(source)} to ${chalk2.dim("all platforms")} (${scope})...` : `Installing ${chalk2.cyan(source)} to ${chalk2.dim(platform)} (${scope})...`
|
|
348
|
+
);
|
|
349
|
+
let cleanupMaterialized = null;
|
|
350
|
+
let materializedRoot = null;
|
|
351
|
+
try {
|
|
352
|
+
if (resolvedSource.startsWith("@") && resolvedSource.includes("/")) {
|
|
353
|
+
await installOne(resolvedSource);
|
|
354
|
+
} else {
|
|
355
|
+
const maybeLocalRoot = path2.resolve(resolvedSource);
|
|
356
|
+
const isLocal = fs2.existsSync(maybeLocalRoot);
|
|
357
|
+
if (isLocal) {
|
|
358
|
+
const hasSkillMd = fs2.existsSync(path2.join(maybeLocalRoot, "SKILL.md"));
|
|
359
|
+
if (hasSkillMd) {
|
|
360
|
+
await installOne(resolvedSource);
|
|
361
|
+
} else {
|
|
362
|
+
const discovered = discoverSkillDirsWithHeuristics(maybeLocalRoot, { maxDepth, maxSkills });
|
|
363
|
+
if (discovered.length === 0) {
|
|
364
|
+
const message = `No SKILL.md found at ${maybeLocalRoot} (or within subdirectories).`;
|
|
365
|
+
if (jsonOnly) {
|
|
366
|
+
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source, resolvedSource });
|
|
367
|
+
} else {
|
|
368
|
+
console.error(chalk2.red(message));
|
|
369
|
+
}
|
|
370
|
+
process.exitCode = 1;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const found = asDiscoveredSkills(discovered, (d) => path2.join(maybeLocalRoot, d.relPath));
|
|
374
|
+
const didReturn = await maybeEnableRecursiveAndInstall(found);
|
|
375
|
+
if (didReturn) return;
|
|
376
|
+
if (spinner) spinner.text = `Installing ${chalk2.cyan(source)} \u2014 discovered ${found.length} skills...`;
|
|
377
|
+
for (const skill of found) {
|
|
378
|
+
if (spinner) spinner.text = `Installing ${chalk2.cyan(skill.relPath)} (${scope})...`;
|
|
379
|
+
await installOne(skill.suggestedSource, skill.materializedDir);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
const materialized = await materializeSourceToTemp(resolvedSource);
|
|
384
|
+
cleanupMaterialized = materialized.cleanup;
|
|
385
|
+
materializedRoot = materialized.dir;
|
|
386
|
+
const hasSkillMd = fs2.existsSync(path2.join(materializedRoot, "SKILL.md"));
|
|
387
|
+
if (hasSkillMd) {
|
|
388
|
+
await installOne(resolvedSource, materializedRoot);
|
|
389
|
+
} else {
|
|
390
|
+
const discovered = discoverSkillDirsWithHeuristics(materializedRoot, { maxDepth, maxSkills });
|
|
391
|
+
if (discovered.length === 0) {
|
|
392
|
+
const message = `No SKILL.md found in source "${resolvedSource}".`;
|
|
393
|
+
if (jsonOnly) {
|
|
394
|
+
printJson({ ok: false, error: "SKILL_MD_NOT_FOUND", message, source, resolvedSource });
|
|
395
|
+
} else {
|
|
396
|
+
console.error(chalk2.red(message));
|
|
397
|
+
}
|
|
398
|
+
process.exitCode = 1;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const found = asDiscoveredSkills(
|
|
402
|
+
discovered,
|
|
403
|
+
(d) => deriveChildSource(resolvedSource, d.relPath),
|
|
404
|
+
(d) => d.absDir
|
|
405
|
+
);
|
|
406
|
+
const didReturn = await maybeEnableRecursiveAndInstall(found);
|
|
407
|
+
if (didReturn) return;
|
|
408
|
+
if (spinner) spinner.text = `Installing ${chalk2.cyan(source)} \u2014 discovered ${found.length} skills...`;
|
|
409
|
+
for (const skill of found) {
|
|
410
|
+
if (spinner) spinner.text = `Installing ${chalk2.cyan(skill.relPath)} (${scope})...`;
|
|
411
|
+
await installOne(skill.suggestedSource, skill.materializedDir);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
136
415
|
}
|
|
416
|
+
} finally {
|
|
417
|
+
if (cleanupMaterialized) cleanupMaterialized();
|
|
137
418
|
}
|
|
138
419
|
if (jsonOnly) {
|
|
139
|
-
if (!all) {
|
|
140
|
-
if (errors.length)
|
|
141
|
-
else
|
|
420
|
+
if (!all && !effectiveRecursive) {
|
|
421
|
+
if (errors.length) printJson({ ok: false, error: errors[0]?.error || "Install failed." });
|
|
422
|
+
else printJson(results[0] ?? null);
|
|
142
423
|
} else {
|
|
143
|
-
|
|
424
|
+
printJson({ ok: errors.length === 0, source, resolvedSource, scope, recursive: effectiveRecursive, all, recursiveSkillCount, results, errors });
|
|
144
425
|
}
|
|
145
426
|
process.exitCode = errors.length ? 1 : 0;
|
|
146
427
|
return;
|
|
@@ -148,14 +429,17 @@ async function install(source, options = {}) {
|
|
|
148
429
|
if (errors.length === 0) {
|
|
149
430
|
const displayName = results[0]?.canonicalName || results[0]?.name || source;
|
|
150
431
|
spinner.succeed(
|
|
151
|
-
all ? `Installed ${chalk2.green(displayName)} to ${chalk2.dim(`${results.length} platforms`)}` : `Installed ${chalk2.green(displayName)} to ${chalk2.dim(results[0]?.installDir || "")}`
|
|
432
|
+
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
433
|
);
|
|
153
434
|
} else {
|
|
154
|
-
|
|
435
|
+
const attempted = results.length + errors.length;
|
|
436
|
+
spinner.fail(
|
|
437
|
+
effectiveRecursive ? `Install had failures (${errors.length}/${attempted} installs failed)` : `Failed to install ${chalk2.red(source)} to ${errors.length}/${targets.length} platforms`
|
|
438
|
+
);
|
|
155
439
|
process.exitCode = 1;
|
|
156
440
|
if (!all && errors[0]) console.error(chalk2.red(errors[0].error));
|
|
157
441
|
}
|
|
158
|
-
if (!all && results[0]) {
|
|
442
|
+
if (!effectiveRecursive && !all && results[0]) {
|
|
159
443
|
const record = results[0];
|
|
160
444
|
if (record.hasSkillMd) logger.installDetail("SKILL.md found \u2713");
|
|
161
445
|
else logger.installDetail("Warning: No SKILL.md found", true);
|
|
@@ -164,12 +448,13 @@ async function install(source, options = {}) {
|
|
|
164
448
|
} else if (record.skill?.validation?.ok) {
|
|
165
449
|
logger.installDetail(`Validation: ${chalk2.green("ok")}`);
|
|
166
450
|
}
|
|
167
|
-
} else if (all) {
|
|
168
|
-
for (const r of results) {
|
|
451
|
+
} else if (effectiveRecursive || all) {
|
|
452
|
+
for (const r of results.slice(0, 60)) {
|
|
169
453
|
const displayName = r.canonicalName || r.name;
|
|
170
454
|
const suffix = r.hasSkillMd ? chalk2.green("\u2713") : chalk2.yellow("\u26A0");
|
|
171
455
|
console.log(` ${suffix} ${chalk2.cyan(displayName)} \u2192 ${chalk2.dim(r.platform)}`);
|
|
172
456
|
}
|
|
457
|
+
if (results.length > 60) console.log(chalk2.dim(` ... and ${results.length - 60} more`));
|
|
173
458
|
if (errors.length) {
|
|
174
459
|
console.log(chalk2.yellow("\nFailures:"));
|
|
175
460
|
for (const e of errors) console.log(chalk2.yellow(` - ${e.platform}: ${e.error}`));
|
|
@@ -178,7 +463,7 @@ async function install(source, options = {}) {
|
|
|
178
463
|
}
|
|
179
464
|
} catch (error) {
|
|
180
465
|
const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
|
|
181
|
-
if (jsonOnly)
|
|
466
|
+
if (jsonOnly) printJson({ ok: false, error: message });
|
|
182
467
|
else console.error(chalk2.red(message));
|
|
183
468
|
process.exitCode = 1;
|
|
184
469
|
}
|
|
@@ -500,66 +785,6 @@ async function init(name, options = {}) {
|
|
|
500
785
|
// src/commands/signup.ts
|
|
501
786
|
import chalk9 from "chalk";
|
|
502
787
|
import { fetchWithTimeout as fetchWithTimeout2, resolveRegistryUrl as resolveRegistryUrl2, SkildError as SkildError6 } from "@skild/core";
|
|
503
|
-
|
|
504
|
-
// src/utils/prompt.ts
|
|
505
|
-
import readline from "readline";
|
|
506
|
-
async function promptLine(question, defaultValue) {
|
|
507
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
508
|
-
try {
|
|
509
|
-
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
510
|
-
const answer = await new Promise((resolve) => rl.question(`${question}${suffix}: `, resolve));
|
|
511
|
-
const trimmed = answer.trim();
|
|
512
|
-
return trimmed || defaultValue || "";
|
|
513
|
-
} finally {
|
|
514
|
-
rl.close();
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
async function promptPassword(question) {
|
|
518
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
519
|
-
return promptLine(question);
|
|
520
|
-
}
|
|
521
|
-
const stdin = process.stdin;
|
|
522
|
-
const stdout = process.stdout;
|
|
523
|
-
stdout.write(`${question}: `);
|
|
524
|
-
const wasRaw = Boolean(stdin.isRaw);
|
|
525
|
-
stdin.setRawMode(true);
|
|
526
|
-
stdin.resume();
|
|
527
|
-
readline.emitKeypressEvents(stdin);
|
|
528
|
-
const buf = [];
|
|
529
|
-
return await new Promise((resolve, reject) => {
|
|
530
|
-
function cleanup() {
|
|
531
|
-
stdin.off("keypress", onKeypress);
|
|
532
|
-
stdin.setRawMode(wasRaw);
|
|
533
|
-
stdin.pause();
|
|
534
|
-
}
|
|
535
|
-
function onKeypress(str, key) {
|
|
536
|
-
if (key?.ctrl && key?.name === "c") {
|
|
537
|
-
stdout.write("\n");
|
|
538
|
-
cleanup();
|
|
539
|
-
const err = new Error("Prompt cancelled");
|
|
540
|
-
err.code = "PROMPT_CANCELLED";
|
|
541
|
-
reject(err);
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
if (key?.name === "return" || key?.name === "enter") {
|
|
545
|
-
stdout.write("\n");
|
|
546
|
-
cleanup();
|
|
547
|
-
resolve(buf.join(""));
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
if (key?.name === "backspace" || key?.name === "delete") {
|
|
551
|
-
if (buf.length) buf.pop();
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
if (!str) return;
|
|
555
|
-
if (key?.ctrl || key?.meta) return;
|
|
556
|
-
buf.push(str);
|
|
557
|
-
}
|
|
558
|
-
stdin.on("keypress", onKeypress);
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// src/commands/signup.ts
|
|
563
788
|
async function signup(options) {
|
|
564
789
|
const registry = resolveRegistryUrl2(options.registry);
|
|
565
790
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -752,9 +977,9 @@ async function whoami() {
|
|
|
752
977
|
}
|
|
753
978
|
|
|
754
979
|
// src/commands/publish.ts
|
|
755
|
-
import
|
|
980
|
+
import fs3 from "fs";
|
|
756
981
|
import os from "os";
|
|
757
|
-
import
|
|
982
|
+
import path3 from "path";
|
|
758
983
|
import crypto from "crypto";
|
|
759
984
|
import * as tar from "tar";
|
|
760
985
|
import chalk13 from "chalk";
|
|
@@ -777,7 +1002,7 @@ async function publish(options = {}) {
|
|
|
777
1002
|
process.exitCode = 1;
|
|
778
1003
|
return;
|
|
779
1004
|
}
|
|
780
|
-
const dir =
|
|
1005
|
+
const dir = path3.resolve(options.dir || process.cwd());
|
|
781
1006
|
const validation = validateSkillDir(dir);
|
|
782
1007
|
if (!validation.ok) {
|
|
783
1008
|
console.error(chalk13.red("Skill validation failed:"));
|
|
@@ -847,8 +1072,8 @@ async function publish(options = {}) {
|
|
|
847
1072
|
}
|
|
848
1073
|
}
|
|
849
1074
|
const spinner = createSpinner(`Publishing ${chalk13.cyan(`${name}@${version2}`)} to ${chalk13.dim(registry)}...`);
|
|
850
|
-
const tempDir =
|
|
851
|
-
const tarballPath =
|
|
1075
|
+
const tempDir = fs3.mkdtempSync(path3.join(os.tmpdir(), "skild-publish-"));
|
|
1076
|
+
const tarballPath = path3.join(tempDir, "skill.tgz");
|
|
852
1077
|
try {
|
|
853
1078
|
await tar.c(
|
|
854
1079
|
{
|
|
@@ -860,7 +1085,7 @@ async function publish(options = {}) {
|
|
|
860
1085
|
},
|
|
861
1086
|
["."]
|
|
862
1087
|
);
|
|
863
|
-
const buf =
|
|
1088
|
+
const buf = fs3.readFileSync(tarballPath);
|
|
864
1089
|
const integrity = sha256Hex(buf);
|
|
865
1090
|
const form = new FormData();
|
|
866
1091
|
form.set("version", version2);
|
|
@@ -927,7 +1152,7 @@ async function publish(options = {}) {
|
|
|
927
1152
|
console.error(chalk13.red(message));
|
|
928
1153
|
process.exitCode = 1;
|
|
929
1154
|
} finally {
|
|
930
|
-
|
|
1155
|
+
fs3.rmSync(tempDir, { recursive: true, force: true });
|
|
931
1156
|
}
|
|
932
1157
|
}
|
|
933
1158
|
|
|
@@ -969,7 +1194,7 @@ var require2 = createRequire(import.meta.url);
|
|
|
969
1194
|
var { version } = require2("../package.json");
|
|
970
1195
|
var program = new Command();
|
|
971
1196
|
program.name("skild").description("The npm for Agent Skills \u2014 Discover, install, manage, and publish AI Agent Skills with ease.").version(version);
|
|
972
|
-
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) => {
|
|
1197
|
+
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) => {
|
|
973
1198
|
await install(source, options);
|
|
974
1199
|
});
|
|
975
1200
|
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.1",
|
|
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.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^20.10.0",
|