skild 0.1.3 → 0.2.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 (3) hide show
  1. package/README.md +5 -0
  2. package/dist/index.js +449 -29
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -33,3 +33,8 @@ skild uninstall pdf -t codex --local
33
33
  # Create a new Skill
34
34
  skild init my-skill
35
35
  ```
36
+
37
+ Full user guide (CLI + registry + console):
38
+
39
+ - `docs/usage.md`
40
+ - `docs/usage.zh-CN.md`
package/dist/index.js CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk9 from "chalk";
5
+ import chalk15 from "chalk";
6
6
  import { createRequire } from "module";
7
7
 
8
8
  // src/commands/install.ts
9
9
  import chalk2 from "chalk";
10
- import { installSkill, SkildError } from "@skild/core";
10
+ import { fetchWithTimeout, installRegistrySkill, installSkill, resolveRegistryUrl, SkildError } from "@skild/core";
11
11
 
12
12
  // src/utils/logger.ts
13
13
  import chalk from "chalk";
@@ -55,10 +55,10 @@ var logger = {
55
55
  /**
56
56
  * Log a skill entry with status indicator.
57
57
  */
58
- skillEntry: (name, path, hasSkillMd) => {
58
+ skillEntry: (name, path2, hasSkillMd) => {
59
59
  const status = hasSkillMd ? chalk.green("\u2713") : chalk.yellow("\u26A0");
60
60
  console.log(` ${status} ${chalk.cyan(name)}`);
61
- console.log(chalk.dim(` \u2514\u2500 ${path}`));
61
+ console.log(chalk.dim(` \u2514\u2500 ${path2}`));
62
62
  },
63
63
  /**
64
64
  * Log installation result details.
@@ -75,15 +75,12 @@ async function install(source, options = {}) {
75
75
  const scope = options.local ? "project" : "global";
76
76
  const spinner = createSpinner(`Installing ${chalk2.cyan(source)} to ${chalk2.dim(platform)} (${scope})...`);
77
77
  try {
78
- const record = await installSkill(
79
- { source },
80
- {
81
- platform,
82
- scope,
83
- force: Boolean(options.force)
84
- }
85
- );
86
- spinner.succeed(`Installed ${chalk2.green(record.name)} to ${chalk2.dim(record.installDir)}`);
78
+ const record = source.trim().startsWith("@") && source.includes("/") ? await installRegistrySkill(
79
+ { spec: source, registryUrl: options.registry },
80
+ { platform, scope, force: Boolean(options.force) }
81
+ ) : await installSkill({ source }, { platform, scope, force: Boolean(options.force) });
82
+ const displayName = record.canonicalName || record.name;
83
+ spinner.succeed(`Installed ${chalk2.green(displayName)} to ${chalk2.dim(record.installDir)}`);
87
84
  if (options.json) {
88
85
  console.log(JSON.stringify(record, null, 2));
89
86
  return;
@@ -98,6 +95,7 @@ async function install(source, options = {}) {
98
95
  } else if (record.skill?.validation?.ok) {
99
96
  logger.installDetail(`Validation: ${chalk2.green("ok")}`);
100
97
  }
98
+ void reportDownload(record, options.registry);
101
99
  } catch (error) {
102
100
  spinner.fail(`Failed to install ${chalk2.red(source)}`);
103
101
  const message = error instanceof SkildError ? error.message : error instanceof Error ? error.message : String(error);
@@ -105,6 +103,36 @@ async function install(source, options = {}) {
105
103
  process.exitCode = 1;
106
104
  }
107
105
  }
106
+ async function reportDownload(record, registryOverride) {
107
+ try {
108
+ if (record.sourceType === "local") return;
109
+ const registryUrl = resolveRegistryUrl(record.registryUrl || registryOverride);
110
+ const endpoint = `${registryUrl}/stats/downloads`;
111
+ const payload = { source: "cli" };
112
+ if (record.sourceType === "registry") {
113
+ payload.entityType = "registry";
114
+ payload.entityId = record.canonicalName || record.source;
115
+ } else if (record.sourceType === "github-url") {
116
+ payload.entityType = "linked";
117
+ payload.sourceInput = { provider: "github", url: record.source };
118
+ } else if (record.sourceType === "degit-shorthand") {
119
+ payload.entityType = "linked";
120
+ payload.sourceInput = { provider: "github", spec: record.source };
121
+ } else {
122
+ return;
123
+ }
124
+ await fetchWithTimeout(
125
+ endpoint,
126
+ {
127
+ method: "POST",
128
+ headers: { "content-type": "application/json" },
129
+ body: JSON.stringify(payload)
130
+ },
131
+ 3e3
132
+ );
133
+ } catch {
134
+ }
135
+ }
108
136
 
109
137
  // src/commands/list.ts
110
138
  import chalk3 from "chalk";
@@ -128,7 +156,8 @@ async function list(options = {}) {
128
156
  `));
129
157
  for (const s of skills) {
130
158
  const status = s.hasSkillMd ? chalk3.green("\u2713") : chalk3.yellow("\u26A0");
131
- console.log(` ${status} ${chalk3.cyan(s.name)}`);
159
+ const displayName = s.record?.canonicalName || s.name;
160
+ console.log(` ${status} ${chalk3.cyan(displayName)}`);
132
161
  console.log(chalk3.dim(` \u2514\u2500 ${s.installDir}`));
133
162
  }
134
163
  console.log("");
@@ -157,7 +186,8 @@ async function list(options = {}) {
157
186
  }
158
187
  for (const s of platformSkills) {
159
188
  const status = s.hasSkillMd ? chalk3.green("\u2713") : chalk3.yellow("\u26A0");
160
- console.log(` ${status} ${chalk3.cyan(s.name)}`);
189
+ const displayName = s.record?.canonicalName || s.name;
190
+ console.log(` ${status} ${chalk3.cyan(displayName)}`);
161
191
  console.log(chalk3.dim(` \u2514\u2500 ${s.installDir}`));
162
192
  }
163
193
  }
@@ -166,18 +196,20 @@ async function list(options = {}) {
166
196
 
167
197
  // src/commands/info.ts
168
198
  import chalk4 from "chalk";
169
- import { getSkillInfo, SkildError as SkildError2 } from "@skild/core";
199
+ import { canonicalNameToInstallDirName, getSkillInfo, SkildError as SkildError2 } from "@skild/core";
170
200
  async function info(skill, options = {}) {
171
201
  const platform = options.target || "claude";
172
202
  const scope = options.local ? "project" : "global";
173
203
  try {
174
- const record = getSkillInfo(skill, { platform, scope });
204
+ const resolvedName = skill.trim().startsWith("@") && skill.includes("/") ? canonicalNameToInstallDirName(skill.trim()) : skill;
205
+ const record = getSkillInfo(resolvedName, { platform, scope });
175
206
  if (options.json) {
176
207
  console.log(JSON.stringify(record, null, 2));
177
208
  return;
178
209
  }
210
+ const displayName = record.canonicalName || record.name;
179
211
  console.log(chalk4.bold(`
180
- ${chalk4.cyan(record.name)}
212
+ ${chalk4.cyan(displayName)}
181
213
  `));
182
214
  console.log(` ${chalk4.dim("Path:")} ${record.installDir}`);
183
215
  console.log(` ${chalk4.dim("Source:")} ${record.source}`);
@@ -206,16 +238,18 @@ ${chalk4.cyan(record.name)}
206
238
 
207
239
  // src/commands/uninstall.ts
208
240
  import chalk5 from "chalk";
209
- import { uninstallSkill, SkildError as SkildError3 } from "@skild/core";
241
+ import { canonicalNameToInstallDirName as canonicalNameToInstallDirName2, uninstallSkill, SkildError as SkildError3 } from "@skild/core";
210
242
  async function uninstall(skill, options = {}) {
211
243
  const platform = options.target || "claude";
212
244
  const scope = options.local ? "project" : "global";
213
- const spinner = createSpinner(`Uninstalling ${chalk5.cyan(skill)} from ${chalk5.dim(platform)} (${scope})...`);
245
+ const canonical = skill.trim();
246
+ const resolvedName = canonical.startsWith("@") && canonical.includes("/") ? canonicalNameToInstallDirName2(canonical) : canonical;
247
+ const spinner = createSpinner(`Uninstalling ${chalk5.cyan(canonical)} from ${chalk5.dim(platform)} (${scope})...`);
214
248
  try {
215
- uninstallSkill(skill, { platform, scope, allowMissingMetadata: Boolean(options.force) });
216
- spinner.succeed(`Uninstalled ${chalk5.green(skill)}`);
249
+ uninstallSkill(resolvedName, { platform, scope, allowMissingMetadata: Boolean(options.force) });
250
+ spinner.succeed(`Uninstalled ${chalk5.green(canonical)}`);
217
251
  } catch (error) {
218
- spinner.fail(`Failed to uninstall ${chalk5.red(skill)}`);
252
+ spinner.fail(`Failed to uninstall ${chalk5.red(canonical)}`);
219
253
  const message = error instanceof SkildError3 ? error.message : error instanceof Error ? error.message : String(error);
220
254
  console.error(chalk5.red(message));
221
255
  process.exitCode = 1;
@@ -224,14 +258,15 @@ async function uninstall(skill, options = {}) {
224
258
 
225
259
  // src/commands/update.ts
226
260
  import chalk6 from "chalk";
227
- import { updateSkill, SkildError as SkildError4 } from "@skild/core";
261
+ import { canonicalNameToInstallDirName as canonicalNameToInstallDirName3, updateSkill, SkildError as SkildError4 } from "@skild/core";
228
262
  async function update(skill, options = {}) {
229
263
  const platform = options.target || "claude";
230
264
  const scope = options.local ? "project" : "global";
231
265
  const label = skill ? skill : "all skills";
266
+ const resolvedName = skill && skill.trim().startsWith("@") && skill.includes("/") ? canonicalNameToInstallDirName3(skill.trim()) : skill;
232
267
  const spinner = createSpinner(`Updating ${chalk6.cyan(label)} on ${chalk6.dim(platform)} (${scope})...`);
233
268
  try {
234
- const results = await updateSkill(skill, { platform, scope });
269
+ const results = await updateSkill(resolvedName, { platform, scope });
235
270
  spinner.succeed(`Updated ${chalk6.green(results.length.toString())} skill(s).`);
236
271
  if (options.json) {
237
272
  console.log(JSON.stringify(results, null, 2));
@@ -246,12 +281,13 @@ async function update(skill, options = {}) {
246
281
 
247
282
  // src/commands/validate.ts
248
283
  import chalk7 from "chalk";
249
- import { validateSkill } from "@skild/core";
284
+ import { canonicalNameToInstallDirName as canonicalNameToInstallDirName4, validateSkill } from "@skild/core";
250
285
  async function validate(target, options = {}) {
251
286
  const platform = options.target || "claude";
252
287
  const scope = options.local ? "project" : "global";
253
288
  const value = target || ".";
254
- const result = validateSkill(value, { platform, scope });
289
+ const resolvedValue = value.trim().startsWith("@") && value.includes("/") ? canonicalNameToInstallDirName4(value.trim()) : value;
290
+ const result = validateSkill(resolvedValue, { platform, scope });
255
291
  if (options.json) {
256
292
  console.log(JSON.stringify(result, null, 2));
257
293
  process.exitCode = result.ok ? 0 : 1;
@@ -291,13 +327,391 @@ async function init(name, options = {}) {
291
327
  }
292
328
  }
293
329
 
330
+ // src/commands/signup.ts
331
+ import chalk9 from "chalk";
332
+ import { fetchWithTimeout as fetchWithTimeout2, resolveRegistryUrl as resolveRegistryUrl2, SkildError as SkildError6 } from "@skild/core";
333
+
334
+ // src/utils/prompt.ts
335
+ import readline from "readline";
336
+ async function promptLine(question, defaultValue) {
337
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
338
+ try {
339
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
340
+ const answer = await new Promise((resolve) => rl.question(`${question}${suffix}: `, resolve));
341
+ const trimmed = answer.trim();
342
+ return trimmed || defaultValue || "";
343
+ } finally {
344
+ rl.close();
345
+ }
346
+ }
347
+ async function promptPassword(question) {
348
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
349
+ rl.stdoutMuted = true;
350
+ rl._writeToOutput = function _writeToOutput(stringToWrite) {
351
+ if (this.stdoutMuted) return;
352
+ this.output.write(stringToWrite);
353
+ };
354
+ try {
355
+ const answer = await new Promise((resolve) => rl.question(`${question}: `, resolve));
356
+ return String(answer || "");
357
+ } finally {
358
+ rl.stdoutMuted = false;
359
+ rl.close();
360
+ process.stdout.write("\n");
361
+ }
362
+ }
363
+
364
+ // src/commands/signup.ts
365
+ async function signup(options) {
366
+ const registry = resolveRegistryUrl2(options.registry);
367
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
368
+ const email = options.email?.trim() || "";
369
+ const handle = options.handle?.trim() || "";
370
+ const password = options.password || "";
371
+ if ((!email || !handle || !password) && (!interactive || options.json)) {
372
+ console.error(chalk9.red("Missing signup fields. Use --email/--handle/--password, or run `skild signup` interactively."));
373
+ process.exitCode = 1;
374
+ return;
375
+ }
376
+ const finalEmail = email || await promptLine("Email");
377
+ const finalHandle = handle || (await promptLine("Handle (publisher scope)", void 0)).toLowerCase();
378
+ const finalPassword = password || await promptPassword("Password");
379
+ let text = "";
380
+ try {
381
+ const res = await fetchWithTimeout2(
382
+ `${registry}/auth/signup`,
383
+ {
384
+ method: "POST",
385
+ headers: { "content-type": "application/json" },
386
+ body: JSON.stringify({
387
+ email: finalEmail,
388
+ handle: finalHandle,
389
+ password: finalPassword
390
+ })
391
+ },
392
+ 1e4
393
+ );
394
+ text = await res.text();
395
+ if (!res.ok) {
396
+ console.error(chalk9.red(`Signup failed (${res.status}): ${text}`));
397
+ process.exitCode = 1;
398
+ return;
399
+ }
400
+ } catch (error) {
401
+ const message = error instanceof SkildError6 ? error.message : error instanceof Error ? error.message : String(error);
402
+ console.error(chalk9.red(`Signup failed: ${message}`));
403
+ process.exitCode = 1;
404
+ return;
405
+ }
406
+ if (options.json) {
407
+ console.log(text || JSON.stringify({ ok: true }, null, 2));
408
+ return;
409
+ }
410
+ console.log(chalk9.green("Signup successful."));
411
+ try {
412
+ const parsed = JSON.parse(text);
413
+ if (parsed.verification?.requiredForPublish) {
414
+ if (parsed.verification.mode === "log") {
415
+ console.log(chalk9.dim("Dev mode: email sending is disabled. Check the registry dev logs for the verification link."));
416
+ } else if (parsed.verification.sent) {
417
+ console.log(chalk9.dim("Verification email sent. Check your inbox (and spam)."));
418
+ } else {
419
+ console.log(chalk9.yellow("Verification email was not sent. You may need to resend from the Console."));
420
+ }
421
+ console.log(chalk9.dim(`Verify/resend in Console: ${(parsed.verification.consoleUrl || "https://console.skild.sh").replace(/\/+$/, "")}/verify-email/request`));
422
+ } else if (parsed.verification) {
423
+ console.log(chalk9.dim("Email verification is recommended. Publishing may be restricted in the future."));
424
+ console.log(chalk9.dim(`Verify/resend in Console: ${(parsed.verification.consoleUrl || "https://console.skild.sh").replace(/\/+$/, "")}/verify-email/request`));
425
+ }
426
+ } catch {
427
+ }
428
+ console.log(chalk9.dim("Next: run `skild login`"));
429
+ }
430
+
431
+ // src/commands/login.ts
432
+ import chalk10 from "chalk";
433
+ import { fetchWithTimeout as fetchWithTimeout3, resolveRegistryUrl as resolveRegistryUrl3, saveRegistryAuth, SkildError as SkildError7 } from "@skild/core";
434
+ async function login(options) {
435
+ const registry = resolveRegistryUrl3(options.registry);
436
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
437
+ const handleOrEmail = options.handleOrEmail?.trim() || "";
438
+ const password = options.password || "";
439
+ if ((!handleOrEmail || !password) && (!interactive || options.json)) {
440
+ console.error(chalk10.red("Missing credentials. Use --handle-or-email and --password, or run `skild login` interactively."));
441
+ process.exitCode = 1;
442
+ return;
443
+ }
444
+ const finalHandleOrEmail = handleOrEmail || await promptLine("Handle or email");
445
+ const finalPassword = password || await promptPassword("Password");
446
+ const finalTokenName = options.tokenName?.trim() || void 0;
447
+ let text = "";
448
+ try {
449
+ const res = await fetchWithTimeout3(
450
+ `${registry}/auth/login`,
451
+ {
452
+ method: "POST",
453
+ headers: { "content-type": "application/json" },
454
+ body: JSON.stringify({
455
+ handleOrEmail: finalHandleOrEmail,
456
+ password: finalPassword,
457
+ tokenName: finalTokenName
458
+ })
459
+ },
460
+ 1e4
461
+ );
462
+ text = await res.text();
463
+ if (!res.ok) {
464
+ console.error(chalk10.red(`Login failed (${res.status}): ${text}`));
465
+ process.exitCode = 1;
466
+ return;
467
+ }
468
+ } catch (error) {
469
+ const message = error instanceof SkildError7 ? error.message : error instanceof Error ? error.message : String(error);
470
+ console.error(chalk10.red(`Login failed: ${message}`));
471
+ process.exitCode = 1;
472
+ return;
473
+ }
474
+ const json = JSON.parse(text);
475
+ saveRegistryAuth({
476
+ schemaVersion: 1,
477
+ registryUrl: registry,
478
+ token: json.token,
479
+ publisher: json.publisher,
480
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
481
+ });
482
+ if (options.json) {
483
+ console.log(JSON.stringify({ ok: true, publisher: json.publisher }, null, 2));
484
+ return;
485
+ }
486
+ console.log(chalk10.green(`Logged in as ${chalk10.cyan(json.publisher.handle)}.`));
487
+ if (json.publisher.emailVerified === false) {
488
+ console.log(chalk10.yellow("Email not verified. Publishing may be restricted by server policy."));
489
+ console.log(chalk10.dim("Open the Publisher Console to verify: https://console.skild.sh/verify-email/request"));
490
+ }
491
+ }
492
+
493
+ // src/commands/logout.ts
494
+ import chalk11 from "chalk";
495
+ import { clearRegistryAuth } from "@skild/core";
496
+ async function logout() {
497
+ clearRegistryAuth();
498
+ console.log(chalk11.green("Logged out."));
499
+ }
500
+
501
+ // src/commands/whoami.ts
502
+ import chalk12 from "chalk";
503
+ import { fetchWithTimeout as fetchWithTimeout4, loadRegistryAuth, resolveRegistryUrl as resolveRegistryUrl4, SkildError as SkildError8 } from "@skild/core";
504
+ async function whoami() {
505
+ const auth = loadRegistryAuth();
506
+ if (!auth) {
507
+ console.error(chalk12.red("Not logged in. Run `skild login` first."));
508
+ process.exitCode = 1;
509
+ return;
510
+ }
511
+ const registryUrl = resolveRegistryUrl4(auth.registryUrl);
512
+ try {
513
+ const res = await fetchWithTimeout4(
514
+ `${registryUrl}/auth/me`,
515
+ { headers: { authorization: `Bearer ${auth.token}`, accept: "application/json" } },
516
+ 5e3
517
+ );
518
+ const text = await res.text();
519
+ if (!res.ok) {
520
+ console.error(chalk12.red(`whoami failed (${res.status}): ${text}`));
521
+ process.exitCode = 1;
522
+ return;
523
+ }
524
+ const json = JSON.parse(text);
525
+ console.log(chalk12.cyan(json.publisher.handle));
526
+ } catch (error) {
527
+ const message = error instanceof SkildError8 ? error.message : error instanceof Error ? error.message : String(error);
528
+ console.error(chalk12.red(`whoami failed: ${message}`));
529
+ console.error(chalk12.dim("Tip: if you previously logged into a local registry, run `skild logout` then `skild login`."));
530
+ process.exitCode = 1;
531
+ }
532
+ }
533
+
534
+ // src/commands/publish.ts
535
+ import fs from "fs";
536
+ import os from "os";
537
+ import path from "path";
538
+ import crypto from "crypto";
539
+ import * as tar from "tar";
540
+ import chalk13 from "chalk";
541
+ import { fetchWithTimeout as fetchWithTimeout5, loadRegistryAuth as loadRegistryAuth2, resolveRegistryUrl as resolveRegistryUrl5, SkildError as SkildError9, splitCanonicalName, validateSkillDir } from "@skild/core";
542
+ function sha256Hex(buf) {
543
+ const h = crypto.createHash("sha256");
544
+ h.update(buf);
545
+ return h.digest("hex");
546
+ }
547
+ function parseTargets(raw) {
548
+ if (!raw?.trim()) return [];
549
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
550
+ }
551
+ async function publish(options = {}) {
552
+ const auth = loadRegistryAuth2();
553
+ const registry = resolveRegistryUrl5(options.registry || auth?.registryUrl);
554
+ const token = auth?.token;
555
+ if (!token) {
556
+ console.error(chalk13.red("Not logged in. Run `skild login` first."));
557
+ process.exitCode = 1;
558
+ return;
559
+ }
560
+ const dir = path.resolve(options.dir || process.cwd());
561
+ const validation = validateSkillDir(dir);
562
+ if (!validation.ok) {
563
+ console.error(chalk13.red("Skill validation failed:"));
564
+ for (const issue of validation.issues) console.error(chalk13.red(`- ${issue.message}`));
565
+ process.exitCode = 1;
566
+ return;
567
+ }
568
+ const fm = validation.frontmatter;
569
+ let name = (options.name || fm.name || "").trim();
570
+ const version2 = (options.skillVersion || fm.version || "").trim();
571
+ const description = (options.description || fm.description || "").trim();
572
+ const tag = (options.tag || "latest").trim() || "latest";
573
+ const targets = parseTargets(options.targets);
574
+ if (!name) {
575
+ console.error(chalk13.red("Missing name. Provide SKILL.md frontmatter.name or --name."));
576
+ process.exitCode = 1;
577
+ return;
578
+ }
579
+ if (!name.startsWith("@")) {
580
+ const seg = name.trim();
581
+ if (!/^[a-z0-9][a-z0-9-]{1,63}$/.test(seg)) {
582
+ console.error(chalk13.red("Invalid name. Use @publisher/skill or a simple skill name (lowercase letters/digits/dashes)."));
583
+ process.exitCode = 1;
584
+ return;
585
+ }
586
+ const meRes = await fetchWithTimeout5(
587
+ `${registry}/auth/me`,
588
+ { headers: { authorization: `Bearer ${token}` } },
589
+ 1e4
590
+ );
591
+ const meText = await meRes.text();
592
+ if (!meRes.ok) {
593
+ console.error(chalk13.red(`Failed to infer publisher scope (${meRes.status}): ${meText}`));
594
+ process.exitCode = 1;
595
+ return;
596
+ }
597
+ const meJson = JSON.parse(meText);
598
+ const handle = String(meJson?.publisher?.handle || "").trim().toLowerCase();
599
+ if (!handle) {
600
+ console.error(chalk13.red("Failed to infer publisher scope from registry response."));
601
+ process.exitCode = 1;
602
+ return;
603
+ }
604
+ name = `@${handle}/${seg}`;
605
+ }
606
+ if (!/^@[a-z0-9][a-z0-9-]{1,31}\/[a-z0-9][a-z0-9-]{1,63}$/.test(name)) {
607
+ console.error(chalk13.red("Invalid publish name. Expected @publisher/skill (lowercase letters/digits/dashes)."));
608
+ process.exitCode = 1;
609
+ return;
610
+ }
611
+ if (!version2) {
612
+ console.error(chalk13.red("Missing version. Provide semver like 1.2.3 via SKILL.md frontmatter or --skill-version."));
613
+ process.exitCode = 1;
614
+ return;
615
+ }
616
+ const spinner = createSpinner(`Publishing ${chalk13.cyan(`${name}@${version2}`)} to ${chalk13.dim(registry)}...`);
617
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "skild-publish-"));
618
+ const tarballPath = path.join(tempDir, "skill.tgz");
619
+ try {
620
+ await tar.c(
621
+ {
622
+ gzip: true,
623
+ file: tarballPath,
624
+ cwd: dir,
625
+ portable: true,
626
+ filter: (p) => !p.startsWith(".skild") && !p.startsWith(".git")
627
+ },
628
+ ["."]
629
+ );
630
+ const buf = fs.readFileSync(tarballPath);
631
+ const integrity = sha256Hex(buf);
632
+ const form = new FormData();
633
+ form.set("version", version2);
634
+ form.set("description", description);
635
+ form.set("targets", JSON.stringify(targets));
636
+ form.set("tag", tag);
637
+ form.append("tarball", new Blob([buf], { type: "application/gzip" }), "skill.tgz");
638
+ const { scope, name: skillName } = splitCanonicalName(name);
639
+ const res = await fetchWithTimeout5(
640
+ `${registry}/skills/${encodeURIComponent(scope)}/${encodeURIComponent(skillName)}/publish`,
641
+ {
642
+ method: "POST",
643
+ headers: { authorization: `Bearer ${token}` },
644
+ body: form
645
+ },
646
+ 3e4
647
+ );
648
+ const text = await res.text();
649
+ if (!res.ok) {
650
+ spinner.fail(`Publish failed (${res.status})`);
651
+ console.error(chalk13.red(text));
652
+ process.exitCode = 1;
653
+ return;
654
+ }
655
+ if (options.json) {
656
+ console.log(text);
657
+ return;
658
+ }
659
+ spinner.succeed(`Published ${chalk13.green(`${name}@${version2}`)} (sha256:${integrity.slice(0, 12)}\u2026)`);
660
+ try {
661
+ const parsed = JSON.parse(text);
662
+ const warnings = Array.isArray(parsed.warnings) ? parsed.warnings.map(String).filter(Boolean) : [];
663
+ for (const w of warnings) console.warn(chalk13.yellow(`Warning: ${w}`));
664
+ } catch {
665
+ }
666
+ } catch (error) {
667
+ spinner.fail("Publish failed");
668
+ const message = error instanceof SkildError9 ? error.message : error instanceof Error ? error.message : String(error);
669
+ console.error(chalk13.red(message));
670
+ process.exitCode = 1;
671
+ } finally {
672
+ fs.rmSync(tempDir, { recursive: true, force: true });
673
+ }
674
+ }
675
+
676
+ // src/commands/search.ts
677
+ import chalk14 from "chalk";
678
+ import { resolveRegistryUrl as resolveRegistryUrl6, searchRegistrySkills, SkildError as SkildError10 } from "@skild/core";
679
+ async function search(query, options = {}) {
680
+ const registryUrl = resolveRegistryUrl6(options.registry);
681
+ const limit = Math.min(Math.max(Number.parseInt(options.limit || "50", 10) || 50, 1), 100);
682
+ try {
683
+ const skills = await searchRegistrySkills(registryUrl, query, limit);
684
+ if (options.json) {
685
+ console.log(JSON.stringify({ ok: true, registryUrl, skills }, null, 2));
686
+ return;
687
+ }
688
+ if (!skills.length) {
689
+ console.log(chalk14.dim("No results."));
690
+ return;
691
+ }
692
+ console.log(chalk14.bold(`
693
+ \u{1F50E} Results (${skills.length}) \u2014 ${chalk14.dim(registryUrl)}
694
+ `));
695
+ for (const s of skills) {
696
+ const name = String(s.name || "").trim();
697
+ const desc = String(s.description || "").trim();
698
+ if (!name) continue;
699
+ console.log(` ${chalk14.cyan(name)}${desc ? chalk14.dim(` \u2014 ${desc}`) : ""}`);
700
+ }
701
+ } catch (error) {
702
+ const message = error instanceof SkildError10 ? error.message : error instanceof Error ? error.message : String(error);
703
+ console.error(chalk14.red(message));
704
+ process.exitCode = 1;
705
+ }
706
+ }
707
+
294
708
  // src/index.ts
295
709
  import { PLATFORMS as PLATFORMS2 } from "@skild/core";
296
710
  var require2 = createRequire(import.meta.url);
297
711
  var { version } = require2("../package.json");
298
712
  var program = new Command();
299
713
  program.name("skild").description("The npm for Agent Skills \u2014 Discover, install, manage, and publish AI Agent Skills with ease.").version(version);
300
- 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: ${PLATFORMS2.join(", ")}`, "claude").option("-l, --local", "Install to project-level directory instead of global").option("-f, --force", "Overwrite existing installation").option("--json", "Output JSON").action(async (source, options) => {
714
+ 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: ${PLATFORMS2.join(", ")}`, "claude").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) => {
301
715
  await install(source, options);
302
716
  });
303
717
  program.command("list").alias("ls").description("List installed Skills").option("-t, --target <platform>", `Target platform: ${PLATFORMS2.join(", ")} (optional; omit to list all)`).option("-l, --local", "List project-level directory instead of global").option("--json", "Output JSON").action(async (options) => list(options));
@@ -306,8 +720,14 @@ program.command("uninstall <skill>").alias("rm").description("Uninstall a Skill"
306
720
  program.command("update [skill]").alias("up").description("Update one or all installed Skills").option("-t, --target <platform>", `Target platform: ${PLATFORMS2.join(", ")}`, "claude").option("-l, --local", "Use project-level directory instead of global").option("--json", "Output JSON").action(async (skill, options) => update(skill, options));
307
721
  program.command("validate [target]").alias("v").description("Validate a Skill folder (path) or an installed Skill name").option("-t, --target <platform>", `Target platform: ${PLATFORMS2.join(", ")}`, "claude").option("-l, --local", "Use project-level directory instead of global").option("--json", "Output JSON").action(async (target, options) => validate(target, options));
308
722
  program.command("init <name>").description("Create a new Skill project").option("--dir <path>", "Target directory (defaults to <name>)").option("--description <text>", "Skill description").option("-f, --force", "Overwrite target directory if it exists").action(async (name, options) => init(name, options));
723
+ program.command("signup").description("Create a publisher account in the registry (no GitHub required)").option("--registry <url>", "Registry base URL (default: https://registry.skild.sh)").option("--email <email>", "Email (optional; will prompt)").option("--handle <handle>", "Publisher handle (owns @handle/* scope) (optional; will prompt)").option("--password <password>", "Password (optional; will prompt)").option("--json", "Output JSON").action(async (options) => signup(options));
724
+ program.command("login").description("Login to a registry and store an access token locally").option("--registry <url>", "Registry base URL (default: https://registry.skild.sh)").option("--handle-or-email <value>", "Handle or email (optional; will prompt)").option("--password <password>", "Password (optional; will prompt)").option("--token-name <name>", "Token label").option("--json", "Output JSON").action(async (options) => login(options));
725
+ program.command("logout").description("Remove stored registry credentials").action(async () => logout());
726
+ program.command("whoami").description("Show current registry identity").action(async () => whoami());
727
+ program.command("publish").description("Publish a Skill directory to the registry (hosted tarball)").option("--dir <path>", "Skill directory (defaults to cwd)").option("--name <@publisher/skill>", "Override skill name (defaults to SKILL.md frontmatter)").option("--skill-version <semver>", "Override version (defaults to SKILL.md frontmatter)").option("--description <text>", "Override description (defaults to SKILL.md frontmatter)").option("--targets <list>", "Comma-separated target platforms metadata (optional)").option("--tag <tag>", "Dist-tag (default: latest)", "latest").option("--registry <url>", "Registry base URL (defaults to saved login)").option("--json", "Output JSON").action(async (options) => publish(options));
728
+ program.command("search <query>").description("Search Skills in the registry").option("--registry <url>", "Registry base URL (default: https://registry.skild.sh)").option("--limit <n>", "Max results (default: 50)", "50").option("--json", "Output JSON").action(async (query, options) => search(query, options));
309
729
  program.action(() => {
310
- console.log(chalk9.bold("\n\u{1F6E1}\uFE0F skild \u2014 Get your agents skilled.\n"));
730
+ console.log(chalk15.bold("\n\u{1F6E1}\uFE0F skild \u2014 Get your agents skilled.\n"));
311
731
  program.outputHelp();
312
732
  });
313
733
  var argv = process.argv.slice();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skild",
3
- "version": "0.1.3",
3
+ "version": "0.2.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",
@@ -36,7 +36,8 @@
36
36
  "chalk": "^5.3.0",
37
37
  "commander": "^12.1.0",
38
38
  "ora": "^8.0.1",
39
- "@skild/core": "^0.1.3"
39
+ "tar": "^7.4.3",
40
+ "@skild/core": "^0.2.0"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/node": "^20.10.0",