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.
Files changed (2) hide show
  1. package/dist/index.js +328 -95
  2. 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 fs from "fs";
10
- import path from "path";
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, path3, hasSkillMd) => {
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 ${path3}`));
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 (fs.existsSync(path.resolve(s))) return false;
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) console.log(JSON.stringify({ ok: false, error: message }, null, 2));
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) console.log(JSON.stringify({ ok: false, error: message }, null, 2));
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
- try {
118
- const spinner = jsonOnly ? null : createSpinner(
119
- all ? `Installing ${chalk2.cyan(source)} to ${chalk2.dim("all platforms")} (${scope})...` : `Installing ${chalk2.cyan(source)} to ${chalk2.dim(platform)} (${scope})...`
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 = resolvedSource.startsWith("@") && resolvedSource.includes("/") ? await installRegistrySkill(
125
- { spec: resolvedSource, registryUrl: registryUrlForDeps },
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: resolvedSource },
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) console.log(JSON.stringify({ ok: false, error: errors[0]?.error || "Install failed." }, null, 2));
141
- else console.log(JSON.stringify(results[0] ?? null, null, 2));
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
- console.log(JSON.stringify({ ok: errors.length === 0, source, resolvedSource, scope, results, errors }, null, 2));
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
- spinner.fail(`Failed to install ${chalk2.red(source)} to ${errors.length}/${targets.length} platforms`);
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) console.log(JSON.stringify({ ok: false, error: message }, null, 2));
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 fs2 from "fs";
986
+ import fs3 from "fs";
754
987
  import os from "os";
755
- import path2 from "path";
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 = path2.resolve(options.dir || process.cwd());
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 = fs2.mkdtempSync(path2.join(os.tmpdir(), "skild-publish-"));
849
- const tarballPath = path2.join(tempDir, "skill.tgz");
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 = fs2.readFileSync(tarballPath);
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
- fs2.rmSync(tempDir, { recursive: true, force: true });
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.1",
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.2.9"
40
+ "@skild/core": "^0.4.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^20.10.0",