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.
- package/README.md +5 -0
- package/dist/index.js +449 -29
- package/package.json +3 -2
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
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,
|
|
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 ${
|
|
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
|
|
79
|
-
{ source },
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
216
|
-
spinner.succeed(`Uninstalled ${chalk5.green(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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.
|
|
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
|
-
"
|
|
39
|
+
"tar": "^7.4.3",
|
|
40
|
+
"@skild/core": "^0.2.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@types/node": "^20.10.0",
|