@senso-ai/shipables 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +836 -227
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -101,6 +101,72 @@ var init_config = __esm({
|
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
// src/lib/errors.ts
|
|
105
|
+
var errors_exports = {};
|
|
106
|
+
__export(errors_exports, {
|
|
107
|
+
CliError: () => CliError,
|
|
108
|
+
EXIT_AUTH: () => EXIT_AUTH,
|
|
109
|
+
EXIT_CONFLICT: () => EXIT_CONFLICT,
|
|
110
|
+
EXIT_ERROR: () => EXIT_ERROR,
|
|
111
|
+
EXIT_NETWORK: () => EXIT_NETWORK,
|
|
112
|
+
EXIT_NOT_FOUND: () => EXIT_NOT_FOUND,
|
|
113
|
+
EXIT_SUCCESS: () => EXIT_SUCCESS,
|
|
114
|
+
EXIT_USAGE: () => EXIT_USAGE,
|
|
115
|
+
EXIT_VALIDATION: () => EXIT_VALIDATION,
|
|
116
|
+
authError: () => authError,
|
|
117
|
+
conflictError: () => conflictError,
|
|
118
|
+
networkError: () => networkError,
|
|
119
|
+
notFoundError: () => notFoundError,
|
|
120
|
+
usageError: () => usageError,
|
|
121
|
+
validationError: () => validationError
|
|
122
|
+
});
|
|
123
|
+
function authError(message, fix) {
|
|
124
|
+
return new CliError(message, "AUTH_REQUIRED", EXIT_AUTH, fix || "Run `shipables login` to authenticate.");
|
|
125
|
+
}
|
|
126
|
+
function notFoundError(message, fix) {
|
|
127
|
+
return new CliError(message, "NOT_FOUND", EXIT_NOT_FOUND, fix);
|
|
128
|
+
}
|
|
129
|
+
function networkError(message, fix) {
|
|
130
|
+
return new CliError(
|
|
131
|
+
message,
|
|
132
|
+
"NETWORK_ERROR",
|
|
133
|
+
EXIT_NETWORK,
|
|
134
|
+
fix || "Check your internet connection or registry URL with: shipables config get registry"
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
function validationError(message, fix) {
|
|
138
|
+
return new CliError(message, "VALIDATION_ERROR", EXIT_VALIDATION, fix);
|
|
139
|
+
}
|
|
140
|
+
function usageError(message, fix) {
|
|
141
|
+
return new CliError(message, "USAGE_ERROR", EXIT_USAGE, fix);
|
|
142
|
+
}
|
|
143
|
+
function conflictError(message, fix) {
|
|
144
|
+
return new CliError(message, "CONFLICT", EXIT_CONFLICT, fix);
|
|
145
|
+
}
|
|
146
|
+
var EXIT_SUCCESS, EXIT_ERROR, EXIT_USAGE, EXIT_AUTH, EXIT_NOT_FOUND, EXIT_NETWORK, EXIT_VALIDATION, EXIT_CONFLICT, CliError;
|
|
147
|
+
var init_errors = __esm({
|
|
148
|
+
"src/lib/errors.ts"() {
|
|
149
|
+
"use strict";
|
|
150
|
+
EXIT_SUCCESS = 0;
|
|
151
|
+
EXIT_ERROR = 1;
|
|
152
|
+
EXIT_USAGE = 2;
|
|
153
|
+
EXIT_AUTH = 3;
|
|
154
|
+
EXIT_NOT_FOUND = 4;
|
|
155
|
+
EXIT_NETWORK = 5;
|
|
156
|
+
EXIT_VALIDATION = 6;
|
|
157
|
+
EXIT_CONFLICT = 7;
|
|
158
|
+
CliError = class extends Error {
|
|
159
|
+
constructor(message, code, exitCode, fix) {
|
|
160
|
+
super(message);
|
|
161
|
+
this.code = code;
|
|
162
|
+
this.exitCode = exitCode;
|
|
163
|
+
this.fix = fix;
|
|
164
|
+
this.name = "CliError";
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
104
170
|
// src/registry/client.ts
|
|
105
171
|
import { createReadStream } from "fs";
|
|
106
172
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -122,6 +188,7 @@ var init_client = __esm({
|
|
|
122
188
|
"src/registry/client.ts"() {
|
|
123
189
|
"use strict";
|
|
124
190
|
init_config();
|
|
191
|
+
init_errors();
|
|
125
192
|
cachedVersion = null;
|
|
126
193
|
RegistryClient = class {
|
|
127
194
|
registryUrl = null;
|
|
@@ -165,7 +232,7 @@ var init_client = __esm({
|
|
|
165
232
|
return await fetch(url, init);
|
|
166
233
|
} catch (err) {
|
|
167
234
|
if (err instanceof TypeError && err.message === "fetch failed") {
|
|
168
|
-
throw
|
|
235
|
+
throw networkError(
|
|
169
236
|
`Could not connect to registry at ${base}. Check your internet connection or registry URL.`
|
|
170
237
|
);
|
|
171
238
|
}
|
|
@@ -186,7 +253,9 @@ var init_client = __esm({
|
|
|
186
253
|
headers: await this.getHeaders()
|
|
187
254
|
});
|
|
188
255
|
if (!resp.ok) {
|
|
189
|
-
throw
|
|
256
|
+
throw networkError(
|
|
257
|
+
`Search for '${params.q || ""}' failed (HTTP ${resp.status}). Verify your registry URL with: shipables config get registry`
|
|
258
|
+
);
|
|
190
259
|
}
|
|
191
260
|
return await resp.json();
|
|
192
261
|
}
|
|
@@ -198,10 +267,13 @@ var init_client = __esm({
|
|
|
198
267
|
});
|
|
199
268
|
if (!resp.ok) {
|
|
200
269
|
if (resp.status === 404) {
|
|
201
|
-
throw
|
|
270
|
+
throw notFoundError(
|
|
271
|
+
`Skill '${fullName}' not found in the registry.`,
|
|
272
|
+
`Search for available skills with: shipables search <query>`
|
|
273
|
+
);
|
|
202
274
|
}
|
|
203
|
-
throw
|
|
204
|
-
`Failed to get skill detail
|
|
275
|
+
throw networkError(
|
|
276
|
+
`Failed to get skill detail for '${fullName}' (HTTP ${resp.status}).`
|
|
205
277
|
);
|
|
206
278
|
}
|
|
207
279
|
return await resp.json();
|
|
@@ -215,10 +287,13 @@ var init_client = __esm({
|
|
|
215
287
|
);
|
|
216
288
|
if (!resp.ok) {
|
|
217
289
|
if (resp.status === 404) {
|
|
218
|
-
throw
|
|
290
|
+
throw notFoundError(
|
|
291
|
+
`Version '${fullName}@${version}' not found.`,
|
|
292
|
+
`List available versions with: shipables info ${fullName}`
|
|
293
|
+
);
|
|
219
294
|
}
|
|
220
|
-
throw
|
|
221
|
-
`Failed to get version
|
|
295
|
+
throw networkError(
|
|
296
|
+
`Failed to get version '${fullName}@${version}' (HTTP ${resp.status}).`
|
|
222
297
|
);
|
|
223
298
|
}
|
|
224
299
|
return await resp.json();
|
|
@@ -231,8 +306,9 @@ var init_client = __esm({
|
|
|
231
306
|
{ headers: await this.getHeaders() }
|
|
232
307
|
);
|
|
233
308
|
if (!resp.ok) {
|
|
234
|
-
throw
|
|
235
|
-
`Failed to download tarball
|
|
309
|
+
throw networkError(
|
|
310
|
+
`Failed to download tarball '${filename}' (HTTP ${resp.status}).`,
|
|
311
|
+
`Re-run the install command. If the error persists, the package may have been removed.`
|
|
236
312
|
);
|
|
237
313
|
}
|
|
238
314
|
const integrity = resp.headers.get("X-Integrity") || "";
|
|
@@ -243,8 +319,9 @@ var init_client = __esm({
|
|
|
243
319
|
const base = await this.getBaseUrl();
|
|
244
320
|
const headers = await this.getHeaders(true);
|
|
245
321
|
if (!headers["Authorization"]) {
|
|
246
|
-
throw
|
|
247
|
-
"Not authenticated.
|
|
322
|
+
throw authError(
|
|
323
|
+
"Not authenticated. You must be logged in to publish.",
|
|
324
|
+
"Run `shipables login` to authenticate, then re-run `shipables publish`."
|
|
248
325
|
);
|
|
249
326
|
}
|
|
250
327
|
const fileStream = createReadStream(tarballPath);
|
|
@@ -265,13 +342,25 @@ var init_client = __esm({
|
|
|
265
342
|
});
|
|
266
343
|
if (!resp.ok) {
|
|
267
344
|
const body = await resp.text();
|
|
268
|
-
let message = `Publish failed
|
|
345
|
+
let message = `Publish failed (HTTP ${resp.status}).`;
|
|
346
|
+
let details = "";
|
|
269
347
|
try {
|
|
270
348
|
const err = JSON.parse(body);
|
|
271
349
|
if (err.error?.message) message = err.error.message;
|
|
350
|
+
if (err.error?.details) details = JSON.stringify(err.error.details);
|
|
272
351
|
} catch {
|
|
273
352
|
}
|
|
274
|
-
|
|
353
|
+
if (resp.status === 401) {
|
|
354
|
+
throw authError(message, "Run `shipables login` to re-authenticate, then re-run `shipables publish`.");
|
|
355
|
+
}
|
|
356
|
+
if (resp.status === 409) {
|
|
357
|
+
const { conflictError: conflictError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
358
|
+
throw conflictError2(
|
|
359
|
+
message + (details ? ` Details: ${details}` : ""),
|
|
360
|
+
"Bump the version in shipables.json and re-run `shipables publish`."
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
throw new Error(message + (details ? ` Details: ${details}` : ""));
|
|
275
364
|
}
|
|
276
365
|
return await resp.json();
|
|
277
366
|
}
|
|
@@ -287,15 +376,21 @@ var init_client = __esm({
|
|
|
287
376
|
);
|
|
288
377
|
if (!resp.ok) {
|
|
289
378
|
if (resp.status === 404) {
|
|
290
|
-
throw
|
|
379
|
+
throw notFoundError(
|
|
380
|
+
`Version '${fullName}@${version}' not found.`,
|
|
381
|
+
`Check available versions with: shipables info ${fullName}`
|
|
382
|
+
);
|
|
291
383
|
}
|
|
292
384
|
const body = await resp.text();
|
|
293
|
-
let message = `Unpublish failed
|
|
385
|
+
let message = `Unpublish failed (HTTP ${resp.status}).`;
|
|
294
386
|
try {
|
|
295
387
|
const err = JSON.parse(body);
|
|
296
388
|
if (err.error?.message) message = err.error.message;
|
|
297
389
|
} catch {
|
|
298
390
|
}
|
|
391
|
+
if (resp.status === 401) {
|
|
392
|
+
throw authError(message, "Run `shipables login` to re-authenticate.");
|
|
393
|
+
}
|
|
299
394
|
throw new Error(message);
|
|
300
395
|
}
|
|
301
396
|
}
|
|
@@ -308,10 +403,13 @@ var init_client = __esm({
|
|
|
308
403
|
);
|
|
309
404
|
if (!resp.ok) {
|
|
310
405
|
if (resp.status === 404) {
|
|
311
|
-
throw
|
|
406
|
+
throw notFoundError(
|
|
407
|
+
`Skill '${fullName}' not found.`,
|
|
408
|
+
`Search for available skills with: shipables search <query>`
|
|
409
|
+
);
|
|
312
410
|
}
|
|
313
|
-
throw
|
|
314
|
-
`Failed to get download stats
|
|
411
|
+
throw networkError(
|
|
412
|
+
`Failed to get download stats for '${fullName}' (HTTP ${resp.status}).`
|
|
315
413
|
);
|
|
316
414
|
}
|
|
317
415
|
return await resp.json();
|
|
@@ -324,10 +422,13 @@ var init_client = __esm({
|
|
|
324
422
|
);
|
|
325
423
|
if (!resp.ok) {
|
|
326
424
|
if (resp.status === 404) {
|
|
327
|
-
throw
|
|
425
|
+
throw notFoundError(
|
|
426
|
+
`User '${username}' not found.`,
|
|
427
|
+
`Check the username and try again.`
|
|
428
|
+
);
|
|
328
429
|
}
|
|
329
|
-
throw
|
|
330
|
-
`Failed to get user profile
|
|
430
|
+
throw networkError(
|
|
431
|
+
`Failed to get user profile for '${username}' (HTTP ${resp.status}).`
|
|
331
432
|
);
|
|
332
433
|
}
|
|
333
434
|
return await resp.json();
|
|
@@ -339,8 +440,8 @@ var init_client = __esm({
|
|
|
339
440
|
{ headers: await this.getHeaders() }
|
|
340
441
|
);
|
|
341
442
|
if (!resp.ok) {
|
|
342
|
-
throw
|
|
343
|
-
`Failed to get user
|
|
443
|
+
throw networkError(
|
|
444
|
+
`Failed to get skills for user '${username}' (HTTP ${resp.status}).`
|
|
344
445
|
);
|
|
345
446
|
}
|
|
346
447
|
return await resp.json();
|
|
@@ -352,9 +453,12 @@ var init_client = __esm({
|
|
|
352
453
|
});
|
|
353
454
|
if (!resp.ok) {
|
|
354
455
|
if (resp.status === 401) {
|
|
355
|
-
throw
|
|
456
|
+
throw authError(
|
|
457
|
+
"Not authenticated or session expired.",
|
|
458
|
+
"Run `shipables login` to re-authenticate."
|
|
459
|
+
);
|
|
356
460
|
}
|
|
357
|
-
throw
|
|
461
|
+
throw networkError(`Failed to get profile (HTTP ${resp.status}).`);
|
|
358
462
|
}
|
|
359
463
|
return await resp.json();
|
|
360
464
|
}
|
|
@@ -825,27 +929,49 @@ var init_config2 = __esm({
|
|
|
825
929
|
|
|
826
930
|
// src/lib/output.ts
|
|
827
931
|
import chalk from "chalk";
|
|
932
|
+
import ora from "ora";
|
|
933
|
+
function isInteractive() {
|
|
934
|
+
return process.stdin.isTTY === true && !process.env.CI;
|
|
935
|
+
}
|
|
936
|
+
function setJsonMode(enabled) {
|
|
937
|
+
jsonMode = enabled;
|
|
938
|
+
}
|
|
939
|
+
function getJsonMode() {
|
|
940
|
+
return jsonMode;
|
|
941
|
+
}
|
|
828
942
|
function success(message) {
|
|
943
|
+
if (jsonMode) return;
|
|
829
944
|
console.log(chalk.green(" \u2713 ") + message);
|
|
830
945
|
}
|
|
831
946
|
function error(message) {
|
|
947
|
+
if (jsonMode) return;
|
|
948
|
+
console.error(chalk.red(" \u2717 ") + message);
|
|
949
|
+
}
|
|
950
|
+
function errorWithFix(message, fix) {
|
|
951
|
+
if (jsonMode) return;
|
|
832
952
|
console.error(chalk.red(" \u2717 ") + message);
|
|
953
|
+
console.error(chalk.dim(" Fix: ") + fix);
|
|
833
954
|
}
|
|
834
955
|
function warn(message) {
|
|
956
|
+
if (jsonMode) return;
|
|
835
957
|
console.log(chalk.yellow(" \u26A0 ") + message);
|
|
836
958
|
}
|
|
837
959
|
function info(message) {
|
|
960
|
+
if (jsonMode) return;
|
|
838
961
|
console.log(chalk.cyan(" ") + message);
|
|
839
962
|
}
|
|
840
963
|
function blank() {
|
|
964
|
+
if (jsonMode) return;
|
|
841
965
|
console.log();
|
|
842
966
|
}
|
|
843
967
|
function header(message) {
|
|
968
|
+
if (jsonMode) return;
|
|
844
969
|
console.log();
|
|
845
970
|
console.log(chalk.bold(" " + message));
|
|
846
971
|
console.log();
|
|
847
972
|
}
|
|
848
973
|
function table(headers, rows, columnWidths) {
|
|
974
|
+
if (jsonMode) return;
|
|
849
975
|
const widths = columnWidths || headers.map(
|
|
850
976
|
(h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || "").length))
|
|
851
977
|
);
|
|
@@ -864,9 +990,47 @@ function formatBytes(bytes) {
|
|
|
864
990
|
function formatNumber(n) {
|
|
865
991
|
return n.toLocaleString("en-US");
|
|
866
992
|
}
|
|
993
|
+
function spinner(text) {
|
|
994
|
+
if (process.stdin.isTTY && !process.env.CI && !jsonMode) {
|
|
995
|
+
return ora(text).start();
|
|
996
|
+
}
|
|
997
|
+
if (!jsonMode) {
|
|
998
|
+
console.error(` \u2026 ${text}`);
|
|
999
|
+
}
|
|
1000
|
+
return {
|
|
1001
|
+
succeed(msg) {
|
|
1002
|
+
if (!jsonMode && msg) console.error(` \u2713 ${msg}`);
|
|
1003
|
+
return this;
|
|
1004
|
+
},
|
|
1005
|
+
fail(msg) {
|
|
1006
|
+
if (!jsonMode && msg) console.error(` \u2717 ${msg}`);
|
|
1007
|
+
return this;
|
|
1008
|
+
},
|
|
1009
|
+
stop() {
|
|
1010
|
+
return this;
|
|
1011
|
+
},
|
|
1012
|
+
start(msg) {
|
|
1013
|
+
if (!jsonMode && msg) console.error(` \u2026 ${msg}`);
|
|
1014
|
+
return this;
|
|
1015
|
+
},
|
|
1016
|
+
warn(msg) {
|
|
1017
|
+
if (!jsonMode && msg) console.error(` \u26A0 ${msg}`);
|
|
1018
|
+
return this;
|
|
1019
|
+
},
|
|
1020
|
+
info(msg) {
|
|
1021
|
+
if (!jsonMode && msg) console.error(` \u2139 ${msg}`);
|
|
1022
|
+
return this;
|
|
1023
|
+
},
|
|
1024
|
+
text,
|
|
1025
|
+
isSpinning: false
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
var jsonMode;
|
|
867
1029
|
var init_output = __esm({
|
|
868
1030
|
"src/lib/output.ts"() {
|
|
869
1031
|
"use strict";
|
|
1032
|
+
init_errors();
|
|
1033
|
+
jsonMode = false;
|
|
870
1034
|
}
|
|
871
1035
|
});
|
|
872
1036
|
|
|
@@ -879,7 +1043,6 @@ import { cp, mkdir as mkdir3 } from "fs/promises";
|
|
|
879
1043
|
import { join as join5, resolve } from "path";
|
|
880
1044
|
import { Command } from "commander";
|
|
881
1045
|
import chalk2 from "chalk";
|
|
882
|
-
import ora from "ora";
|
|
883
1046
|
import { checkbox } from "@inquirer/prompts";
|
|
884
1047
|
import { input, password } from "@inquirer/prompts";
|
|
885
1048
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
@@ -909,13 +1072,24 @@ function getBareName(fullName) {
|
|
|
909
1072
|
return fullName;
|
|
910
1073
|
}
|
|
911
1074
|
function createInstallCommand() {
|
|
912
|
-
return new Command("install").argument("<skill>", "Skill name, optionally with version (e.g.,
|
|
1075
|
+
return new Command("install").argument("<skill>", "Skill name, optionally with version (e.g., Chippers255/ai-commits, Chippers255/ai-commits@1.0.0)").option("--claude", "Install for Claude Code").option("--cursor", "Install for Cursor").option("--codex", "Install for Codex CLI").option("--copilot", "Install for VS Code / Copilot").option("--gemini", "Install for Gemini CLI").option("--cline", "Install for Cline").option("--all", "Install for all detected agents").option("-g, --global", "Install to user-level skills directory").option("-y, --yes", "Skip confirmation prompts").option("--no-mcp", "Skip MCP server configuration").option("--env <values...>", "Set MCP environment variables (e.g., --env API_KEY=xxx DB_HOST=localhost)").option("--registry <url>", "Use a custom registry URL").description(
|
|
1076
|
+
"Install a skill from the registry into the current project. Downloads the skill package, extracts it to agent-specific directories, and configures MCP servers if the skill declares them."
|
|
1077
|
+
).addHelpText("after", `
|
|
1078
|
+
Examples:
|
|
1079
|
+
shipables install neo4j --claude Install latest version for Claude Code
|
|
1080
|
+
shipables install neo4j@1.2.0 --all Install specific version for all agents
|
|
1081
|
+
shipables install @myorg/tool --cursor -y Install scoped skill, skip prompts
|
|
1082
|
+
shipables install neo4j --claude --no-mcp Install without MCP configuration
|
|
1083
|
+
shipables install neo4j --claude --env API_KEY=xxx Pass MCP env vars
|
|
1084
|
+
`).action(async (skillArg, options) => {
|
|
913
1085
|
try {
|
|
914
1086
|
await runInstall(skillArg, options);
|
|
915
1087
|
} catch (err) {
|
|
916
|
-
|
|
917
|
-
err
|
|
918
|
-
|
|
1088
|
+
if (err instanceof CliError) {
|
|
1089
|
+
errorWithFix(err.message, err.fix || "");
|
|
1090
|
+
process.exit(err.exitCode);
|
|
1091
|
+
}
|
|
1092
|
+
error(err instanceof Error ? err.message : String(err));
|
|
919
1093
|
process.exit(1);
|
|
920
1094
|
}
|
|
921
1095
|
});
|
|
@@ -924,21 +1098,30 @@ async function runInstall(skillArg, options) {
|
|
|
924
1098
|
const { name: skillName, version: requestedVersion } = parseSkillArg(skillArg);
|
|
925
1099
|
const bareName = getBareName(skillName);
|
|
926
1100
|
const scope = options.global ? "global" : "project";
|
|
927
|
-
const
|
|
1101
|
+
const envMap = {};
|
|
1102
|
+
if (options.env) {
|
|
1103
|
+
for (const pair of options.env) {
|
|
1104
|
+
const eqIdx = pair.indexOf("=");
|
|
1105
|
+
if (eqIdx > 0) {
|
|
1106
|
+
envMap[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const resolveSpinner = spinner(`Resolving ${skillName}@${requestedVersion}...`);
|
|
928
1111
|
let versionDetail;
|
|
929
1112
|
try {
|
|
930
1113
|
versionDetail = await registry.getVersionDetail(skillName, requestedVersion);
|
|
931
|
-
|
|
1114
|
+
resolveSpinner.succeed(
|
|
932
1115
|
`Resolved ${skillName}@${requestedVersion} \u2192 ${chalk2.bold(versionDetail.version)}`
|
|
933
1116
|
);
|
|
934
1117
|
} catch (err) {
|
|
935
|
-
|
|
1118
|
+
resolveSpinner.fail(`Failed to resolve ${skillName}@${requestedVersion}`);
|
|
936
1119
|
throw err;
|
|
937
1120
|
}
|
|
938
1121
|
const filename = `${bareName}-${versionDetail.version}.tgz`;
|
|
939
|
-
const downloadSpinner =
|
|
1122
|
+
const downloadSpinner = spinner(
|
|
940
1123
|
`Downloading ${filename} (${formatSize(versionDetail.size_bytes)})...`
|
|
941
|
-
)
|
|
1124
|
+
);
|
|
942
1125
|
let tarballData;
|
|
943
1126
|
let serverIntegrity;
|
|
944
1127
|
try {
|
|
@@ -951,7 +1134,7 @@ async function runInstall(skillArg, options) {
|
|
|
951
1134
|
throw err;
|
|
952
1135
|
}
|
|
953
1136
|
if (serverIntegrity && versionDetail.tarball_sha512) {
|
|
954
|
-
const integritySpinner =
|
|
1137
|
+
const integritySpinner = spinner("Verifying integrity...");
|
|
955
1138
|
if (verifyIntegrity(tarballData, versionDetail.tarball_sha512)) {
|
|
956
1139
|
integritySpinner.succeed("Integrity verified");
|
|
957
1140
|
} else {
|
|
@@ -1002,7 +1185,7 @@ async function runInstall(skillArg, options) {
|
|
|
1002
1185
|
const envSchemas = manifest.config?.env || [];
|
|
1003
1186
|
if (envSchemas.length > 0) {
|
|
1004
1187
|
for (const envVar of envSchemas) {
|
|
1005
|
-
const value = await promptEnvVar(envVar, options.yes);
|
|
1188
|
+
const value = await promptEnvVar(envVar, options.yes, envMap);
|
|
1006
1189
|
envValues[envVar.name] = value;
|
|
1007
1190
|
}
|
|
1008
1191
|
blank();
|
|
@@ -1062,6 +1245,11 @@ async function selectAgents(options) {
|
|
|
1062
1245
|
return detected.length > 0 ? detected : ALL_ADAPTERS;
|
|
1063
1246
|
}
|
|
1064
1247
|
if (detected.length === 0) {
|
|
1248
|
+
if (!isInteractive()) {
|
|
1249
|
+
throw usageError(
|
|
1250
|
+
"No agents detected and no agent flags specified. Use --claude, --cursor, --codex, --copilot, --gemini, --cline, or --all."
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1065
1253
|
console.log(
|
|
1066
1254
|
chalk2.yellow(
|
|
1067
1255
|
"\n No agents auto-detected. Select which agents to install for:\n"
|
|
@@ -1079,6 +1267,9 @@ async function selectAgents(options) {
|
|
|
1079
1267
|
if (detected.length === 1) {
|
|
1080
1268
|
return detected;
|
|
1081
1269
|
}
|
|
1270
|
+
if (!isInteractive()) {
|
|
1271
|
+
return detected;
|
|
1272
|
+
}
|
|
1082
1273
|
const selected = await checkbox({
|
|
1083
1274
|
message: "Multiple agents detected. Select which to install for:",
|
|
1084
1275
|
choices: detected.map((a) => ({
|
|
@@ -1089,7 +1280,10 @@ async function selectAgents(options) {
|
|
|
1089
1280
|
});
|
|
1090
1281
|
return selected.map((name) => getAdapter(name)).filter(Boolean);
|
|
1091
1282
|
}
|
|
1092
|
-
async function promptEnvVar(envVar, autoYes) {
|
|
1283
|
+
async function promptEnvVar(envVar, autoYes, envMap) {
|
|
1284
|
+
if (envMap && envVar.name in envMap) {
|
|
1285
|
+
return envMap[envVar.name];
|
|
1286
|
+
}
|
|
1093
1287
|
const label = [
|
|
1094
1288
|
envVar.name,
|
|
1095
1289
|
envVar.required ? "(required" : "(optional",
|
|
@@ -1099,6 +1293,13 @@ async function promptEnvVar(envVar, autoYes) {
|
|
|
1099
1293
|
if (autoYes && envVar.default) {
|
|
1100
1294
|
return envVar.default;
|
|
1101
1295
|
}
|
|
1296
|
+
if (!isInteractive()) {
|
|
1297
|
+
if (envVar.default) {
|
|
1298
|
+
return envVar.default;
|
|
1299
|
+
}
|
|
1300
|
+
warn(`No value provided for ${envVar.name} \u2014 using empty string. Pass --env ${envVar.name}=<value> to set it.`);
|
|
1301
|
+
return "";
|
|
1302
|
+
}
|
|
1102
1303
|
if (envVar.secret) {
|
|
1103
1304
|
return await password({
|
|
1104
1305
|
message: ` Enter value for ${envVar.name}:`
|
|
@@ -1140,6 +1341,8 @@ var init_install = __esm({
|
|
|
1140
1341
|
init_config();
|
|
1141
1342
|
init_config2();
|
|
1142
1343
|
init_output();
|
|
1344
|
+
init_output();
|
|
1345
|
+
init_errors();
|
|
1143
1346
|
}
|
|
1144
1347
|
});
|
|
1145
1348
|
|
|
@@ -1151,15 +1354,28 @@ import { Command as Command15 } from "commander";
|
|
|
1151
1354
|
init_config();
|
|
1152
1355
|
init_adapters();
|
|
1153
1356
|
init_output();
|
|
1357
|
+
init_errors();
|
|
1154
1358
|
import { rm } from "fs/promises";
|
|
1155
1359
|
import { resolve as resolve2 } from "path";
|
|
1156
1360
|
import { Command as Command2 } from "commander";
|
|
1157
1361
|
import chalk3 from "chalk";
|
|
1158
1362
|
function createUninstallCommand() {
|
|
1159
|
-
return new Command2("uninstall").argument("<skill>", "Skill name to uninstall").option("--claude", "Uninstall from Claude Code").option("--cursor", "Uninstall from Cursor").option("--codex", "Uninstall from Codex CLI").option("--copilot", "Uninstall from VS Code / Copilot").option("--gemini", "Uninstall from Gemini CLI").option("--cline", "Uninstall from Cline").option("-g, --global", "Uninstall from global skills directory").description(
|
|
1363
|
+
return new Command2("uninstall").argument("<skill>", "Skill name to uninstall").option("--claude", "Uninstall from Claude Code only").option("--cursor", "Uninstall from Cursor only").option("--codex", "Uninstall from Codex CLI only").option("--copilot", "Uninstall from VS Code / Copilot only").option("--gemini", "Uninstall from Gemini CLI only").option("--cline", "Uninstall from Cline only").option("-g, --global", "Uninstall from global skills directory").description(
|
|
1364
|
+
"Remove a skill and its MCP configuration. Removes skill files and MCP server entries for all agents, or specific agents if flags are provided."
|
|
1365
|
+
).addHelpText("after", `
|
|
1366
|
+
Examples:
|
|
1367
|
+
shipables uninstall Chippers255/ai-commits Remove from all agents
|
|
1368
|
+
shipables uninstall Chippers255/ai-commits --claude Remove from Claude Code only
|
|
1369
|
+
shipables uninstall Chippers255/ai-commits -g Remove globally installed skill
|
|
1370
|
+
shipables uninstall @org/tool --cursor Remove scoped skill from Cursor
|
|
1371
|
+
`).action(async (skillName, options) => {
|
|
1160
1372
|
try {
|
|
1161
1373
|
await runUninstall(skillName, options);
|
|
1162
1374
|
} catch (err) {
|
|
1375
|
+
if (err instanceof CliError) {
|
|
1376
|
+
errorWithFix(err.message, err.fix || "");
|
|
1377
|
+
process.exit(err.exitCode);
|
|
1378
|
+
}
|
|
1163
1379
|
error(err instanceof Error ? err.message : String(err));
|
|
1164
1380
|
process.exit(1);
|
|
1165
1381
|
}
|
|
@@ -1169,8 +1385,9 @@ async function runUninstall(skillName, options) {
|
|
|
1169
1385
|
const projectPath = options.global ? "__global__" : process.cwd();
|
|
1170
1386
|
const record = await getInstallation(projectPath, skillName);
|
|
1171
1387
|
if (!record) {
|
|
1172
|
-
throw
|
|
1173
|
-
|
|
1388
|
+
throw notFoundError(
|
|
1389
|
+
`'${skillName}' is not installed${options.global ? " globally" : " in this project"}.`,
|
|
1390
|
+
`List installed skills with: shipables list${options.global ? " --global" : ""}`
|
|
1174
1391
|
);
|
|
1175
1392
|
}
|
|
1176
1393
|
const flagMap = {
|
|
@@ -1184,8 +1401,9 @@ async function runUninstall(skillName, options) {
|
|
|
1184
1401
|
const hasFlags = Object.values(flagMap).some(Boolean);
|
|
1185
1402
|
const targetAgents = hasFlags ? record.agents.filter((a) => flagMap[a]) : record.agents;
|
|
1186
1403
|
if (targetAgents.length === 0) {
|
|
1187
|
-
throw
|
|
1188
|
-
|
|
1404
|
+
throw notFoundError(
|
|
1405
|
+
`'${skillName}' is not installed for the specified agent(s).`,
|
|
1406
|
+
`Currently installed for: ${record.agents.join(", ")}`
|
|
1189
1407
|
);
|
|
1190
1408
|
}
|
|
1191
1409
|
for (const agentName of targetAgents) {
|
|
@@ -1235,40 +1453,56 @@ async function runUninstall(skillName, options) {
|
|
|
1235
1453
|
success(
|
|
1236
1454
|
`Uninstalled ${chalk3.bold(skillName)} from ${targetAgents.map((a) => getAdapter(a)?.displayName || a).join(", ")}`
|
|
1237
1455
|
);
|
|
1456
|
+
info("Verify with: shipables list");
|
|
1238
1457
|
blank();
|
|
1239
1458
|
}
|
|
1240
1459
|
|
|
1241
1460
|
// src/commands/search.ts
|
|
1242
1461
|
init_client();
|
|
1243
1462
|
init_output();
|
|
1463
|
+
init_output();
|
|
1464
|
+
init_errors();
|
|
1244
1465
|
import { Command as Command3 } from "commander";
|
|
1245
|
-
import ora2 from "ora";
|
|
1246
1466
|
function createSearchCommand() {
|
|
1247
|
-
return new Command3("search").argument("<query>", "Search query").option("--agent <name>", "Filter by agent compatibility").option("--category <slug>", "Filter by category").option("--limit <n>", "Max results", "10").option("--json", "Output as JSON").description(
|
|
1467
|
+
return new Command3("search").argument("<query>", "Search query").option("--agent <name>", "Filter by agent compatibility (claude, cursor, codex, copilot, gemini, cline)").option("--category <slug>", "Filter by category").option("--limit <n>", "Max results", "10").option("--json", "Output as JSON").description(
|
|
1468
|
+
"Search the registry for skills. Returns skill name, version, weekly downloads, and description. Use --json for structured output."
|
|
1469
|
+
).addHelpText("after", `
|
|
1470
|
+
Examples:
|
|
1471
|
+
shipables search "database" Search for database-related skills
|
|
1472
|
+
shipables search "auth" --agent claude Search skills compatible with Claude
|
|
1473
|
+
shipables search "testing" --category qa Search within a category
|
|
1474
|
+
shipables search "deploy" --json Get results as JSON
|
|
1475
|
+
shipables search "api" --limit 20 Get more results
|
|
1476
|
+
`).action(async (query, options) => {
|
|
1248
1477
|
try {
|
|
1249
1478
|
await runSearch(query, options);
|
|
1250
1479
|
} catch (err) {
|
|
1480
|
+
if (err instanceof CliError) {
|
|
1481
|
+
errorWithFix(err.message, err.fix || "");
|
|
1482
|
+
process.exit(err.exitCode);
|
|
1483
|
+
}
|
|
1251
1484
|
error(err instanceof Error ? err.message : String(err));
|
|
1252
1485
|
process.exit(1);
|
|
1253
1486
|
}
|
|
1254
1487
|
});
|
|
1255
1488
|
}
|
|
1256
1489
|
async function runSearch(query, options) {
|
|
1257
|
-
const
|
|
1490
|
+
const searchSpinner = spinner("Searching...");
|
|
1258
1491
|
const result = await registry.search({
|
|
1259
1492
|
q: query,
|
|
1260
1493
|
agent: options.agent,
|
|
1261
1494
|
category: options.category,
|
|
1262
1495
|
limit: parseInt(options.limit || "10", 10)
|
|
1263
1496
|
});
|
|
1264
|
-
|
|
1265
|
-
if (options.json) {
|
|
1497
|
+
searchSpinner.stop();
|
|
1498
|
+
if (options.json || getJsonMode()) {
|
|
1266
1499
|
console.log(JSON.stringify(result, null, 2));
|
|
1267
1500
|
return;
|
|
1268
1501
|
}
|
|
1269
1502
|
if (result.skills.length === 0) {
|
|
1270
1503
|
blank();
|
|
1271
|
-
info(
|
|
1504
|
+
info(`No skills found matching "${query}".`);
|
|
1505
|
+
info("Try a broader query or browse categories at https://shipables.dev");
|
|
1272
1506
|
blank();
|
|
1273
1507
|
return;
|
|
1274
1508
|
}
|
|
@@ -1287,6 +1521,10 @@ async function runSearch(query, options) {
|
|
|
1287
1521
|
info(
|
|
1288
1522
|
`${result.pagination.total} result${result.pagination.total === 1 ? "" : "s"} found`
|
|
1289
1523
|
);
|
|
1524
|
+
if (result.skills.length > 0) {
|
|
1525
|
+
info(`Get details: shipables info ${result.skills[0].full_name}`);
|
|
1526
|
+
info(`Install: shipables install ${result.skills[0].full_name} --<agent>`);
|
|
1527
|
+
}
|
|
1290
1528
|
blank();
|
|
1291
1529
|
}
|
|
1292
1530
|
function truncate(s, max) {
|
|
@@ -1297,24 +1535,36 @@ function truncate(s, max) {
|
|
|
1297
1535
|
// src/commands/info.ts
|
|
1298
1536
|
init_client();
|
|
1299
1537
|
init_output();
|
|
1538
|
+
init_output();
|
|
1539
|
+
init_errors();
|
|
1300
1540
|
import { Command as Command4 } from "commander";
|
|
1301
1541
|
import chalk4 from "chalk";
|
|
1302
|
-
import ora3 from "ora";
|
|
1303
1542
|
function createInfoCommand() {
|
|
1304
|
-
return new Command4("info").argument("<skill>", "Skill name").option("--json", "Output as JSON").description(
|
|
1543
|
+
return new Command4("info").argument("<skill>", "Skill name (e.g., Chippers255/ai-commits, @org/my-skill)").option("--json", "Output as JSON").description(
|
|
1544
|
+
"Show detailed information about a skill including versions, MCP servers, categories, and download counts."
|
|
1545
|
+
).addHelpText("after", `
|
|
1546
|
+
Examples:
|
|
1547
|
+
shipables info Chippers255/ai-commits Show details for a skill
|
|
1548
|
+
shipables info @org/my-skill Show details for a scoped skill
|
|
1549
|
+
shipables info Chippers255/ai-commits --json Get details as JSON
|
|
1550
|
+
`).action(async (skillName, options) => {
|
|
1305
1551
|
try {
|
|
1306
1552
|
await runInfo(skillName, options);
|
|
1307
1553
|
} catch (err) {
|
|
1554
|
+
if (err instanceof CliError) {
|
|
1555
|
+
errorWithFix(err.message, err.fix || "");
|
|
1556
|
+
process.exit(err.exitCode);
|
|
1557
|
+
}
|
|
1308
1558
|
error(err instanceof Error ? err.message : String(err));
|
|
1309
1559
|
process.exit(1);
|
|
1310
1560
|
}
|
|
1311
1561
|
});
|
|
1312
1562
|
}
|
|
1313
1563
|
async function runInfo(skillName, options) {
|
|
1314
|
-
const
|
|
1564
|
+
const infoSpinner = spinner(`Fetching info for ${skillName}...`);
|
|
1315
1565
|
const detail = await registry.getSkillDetail(skillName);
|
|
1316
|
-
|
|
1317
|
-
if (options.json) {
|
|
1566
|
+
infoSpinner.stop();
|
|
1567
|
+
if (options.json || getJsonMode()) {
|
|
1318
1568
|
console.log(JSON.stringify(detail, null, 2));
|
|
1319
1569
|
return;
|
|
1320
1570
|
}
|
|
@@ -1350,7 +1600,7 @@ async function runInfo(skillName, options) {
|
|
|
1350
1600
|
}
|
|
1351
1601
|
if (detail.config?.env && detail.config.env.length > 0) {
|
|
1352
1602
|
blank();
|
|
1353
|
-
info("
|
|
1603
|
+
info("Required environment variables:");
|
|
1354
1604
|
for (const envVar of detail.config.env) {
|
|
1355
1605
|
const flags = [
|
|
1356
1606
|
envVar.required ? "required" : "optional",
|
|
@@ -1376,17 +1626,26 @@ async function runInfo(skillName, options) {
|
|
|
1376
1626
|
}
|
|
1377
1627
|
}
|
|
1378
1628
|
blank();
|
|
1379
|
-
info(`Install:
|
|
1629
|
+
info(`Install: shipables install ${detail.full_name}`);
|
|
1630
|
+
info(`Install for specific agent: shipables install ${detail.full_name} --claude`);
|
|
1380
1631
|
blank();
|
|
1381
1632
|
}
|
|
1382
1633
|
|
|
1383
1634
|
// src/commands/list.ts
|
|
1384
1635
|
init_config();
|
|
1385
1636
|
init_output();
|
|
1637
|
+
init_output();
|
|
1386
1638
|
import { Command as Command5 } from "commander";
|
|
1387
|
-
import chalk5 from "chalk";
|
|
1388
1639
|
function createListCommand() {
|
|
1389
|
-
return new Command5("list").alias("ls").option("--json", "Output as JSON").option("-g, --global", "Show globally installed skills").description(
|
|
1640
|
+
return new Command5("list").alias("ls").option("--json", "Output as JSON").option("-g, --global", "Show globally installed skills").description(
|
|
1641
|
+
"List installed skills in the current project (or globally with -g). Shows skill name, version, and which agents it's installed for."
|
|
1642
|
+
).addHelpText("after", `
|
|
1643
|
+
Examples:
|
|
1644
|
+
shipables list List skills in current project
|
|
1645
|
+
shipables list --global List globally installed skills
|
|
1646
|
+
shipables list --json Get installed skills as JSON
|
|
1647
|
+
shipables ls Alias for list
|
|
1648
|
+
`).action(async (options) => {
|
|
1390
1649
|
try {
|
|
1391
1650
|
await runList(options);
|
|
1392
1651
|
} catch (err) {
|
|
@@ -1399,7 +1658,7 @@ async function runList(options) {
|
|
|
1399
1658
|
const projectPath = options.global ? "__global__" : process.cwd();
|
|
1400
1659
|
const installations = await getProjectInstallations(projectPath);
|
|
1401
1660
|
const entries = Object.entries(installations);
|
|
1402
|
-
if (options.json) {
|
|
1661
|
+
if (options.json || getJsonMode()) {
|
|
1403
1662
|
console.log(JSON.stringify(installations, null, 2));
|
|
1404
1663
|
return;
|
|
1405
1664
|
}
|
|
@@ -1408,9 +1667,8 @@ async function runList(options) {
|
|
|
1408
1667
|
info(
|
|
1409
1668
|
options.global ? "No skills installed globally." : "No skills installed in this project."
|
|
1410
1669
|
);
|
|
1411
|
-
info(
|
|
1412
|
-
|
|
1413
|
-
);
|
|
1670
|
+
info("Install one with: shipables install <skill>");
|
|
1671
|
+
info("Search for skills: shipables search <query>");
|
|
1414
1672
|
blank();
|
|
1415
1673
|
return;
|
|
1416
1674
|
}
|
|
@@ -1427,6 +1685,7 @@ async function runList(options) {
|
|
|
1427
1685
|
);
|
|
1428
1686
|
blank();
|
|
1429
1687
|
info(`${entries.length} skill${entries.length === 1 ? "" : "s"} installed`);
|
|
1688
|
+
info("Check for updates: shipables update");
|
|
1430
1689
|
blank();
|
|
1431
1690
|
}
|
|
1432
1691
|
|
|
@@ -1434,12 +1693,22 @@ async function runList(options) {
|
|
|
1434
1693
|
init_config();
|
|
1435
1694
|
init_client();
|
|
1436
1695
|
init_output();
|
|
1696
|
+
init_output();
|
|
1697
|
+
init_errors();
|
|
1437
1698
|
import { Command as Command6 } from "commander";
|
|
1438
|
-
import
|
|
1439
|
-
import ora4 from "ora";
|
|
1699
|
+
import chalk5 from "chalk";
|
|
1440
1700
|
import { confirm } from "@inquirer/prompts";
|
|
1441
1701
|
function createUpdateCommand() {
|
|
1442
|
-
return new Command6("update").argument("[skill]", "Skill to update (omit to update all)").option("--dry-run", "Show what would be updated without making changes").option("-g, --global", "Update globally installed skills").option("--
|
|
1702
|
+
return new Command6("update").argument("[skill]", "Skill to update (omit to update all)").option("--dry-run", "Show what would be updated without making changes").option("-g, --global", "Update globally installed skills").option("-y, --yes", "Skip confirmation prompt").option("--self", "Update the shipables CLI itself").description(
|
|
1703
|
+
"Update installed skills to latest version. If no skill is specified, checks all installed skills for updates. Use --yes to skip the confirmation prompt."
|
|
1704
|
+
).addHelpText("after", `
|
|
1705
|
+
Examples:
|
|
1706
|
+
shipables update Check and update all installed skills
|
|
1707
|
+
shipables update Chippers255/ai-commits Update a specific skill
|
|
1708
|
+
shipables update --dry-run Show available updates without installing
|
|
1709
|
+
shipables update --yes Update all without confirmation
|
|
1710
|
+
shipables update --self Update the shipables CLI itself
|
|
1711
|
+
`).action(async (skill, options) => {
|
|
1443
1712
|
try {
|
|
1444
1713
|
if (options.self) {
|
|
1445
1714
|
await runSelfUpdate();
|
|
@@ -1447,6 +1716,10 @@ function createUpdateCommand() {
|
|
|
1447
1716
|
}
|
|
1448
1717
|
await runUpdate(skill, options);
|
|
1449
1718
|
} catch (err) {
|
|
1719
|
+
if (err instanceof CliError) {
|
|
1720
|
+
errorWithFix(err.message, err.fix || "");
|
|
1721
|
+
process.exit(err.exitCode);
|
|
1722
|
+
}
|
|
1450
1723
|
error(err instanceof Error ? err.message : String(err));
|
|
1451
1724
|
process.exit(1);
|
|
1452
1725
|
}
|
|
@@ -1456,7 +1729,7 @@ async function runSelfUpdate() {
|
|
|
1456
1729
|
blank();
|
|
1457
1730
|
info("To update the shipables CLI, run:");
|
|
1458
1731
|
blank();
|
|
1459
|
-
console.log(
|
|
1732
|
+
console.log(chalk5.cyan(" npm update -g @senso-ai/shipables"));
|
|
1460
1733
|
blank();
|
|
1461
1734
|
info("Or if you installed with npx, it automatically uses the latest version.");
|
|
1462
1735
|
blank();
|
|
@@ -1468,14 +1741,18 @@ async function runUpdate(skill, options) {
|
|
|
1468
1741
|
if (entries.length === 0) {
|
|
1469
1742
|
blank();
|
|
1470
1743
|
info("No skills installed to update.");
|
|
1744
|
+
info("Install a skill with: shipables install <skill>");
|
|
1471
1745
|
blank();
|
|
1472
1746
|
return;
|
|
1473
1747
|
}
|
|
1474
1748
|
const toCheck = skill ? entries.filter(([name]) => name === skill) : entries;
|
|
1475
1749
|
if (toCheck.length === 0) {
|
|
1476
|
-
throw
|
|
1750
|
+
throw notFoundError(
|
|
1751
|
+
`'${skill}' is not installed${options.global ? " globally" : " in this project"}.`,
|
|
1752
|
+
`List installed skills with: shipables list${options.global ? " --global" : ""}`
|
|
1753
|
+
);
|
|
1477
1754
|
}
|
|
1478
|
-
const
|
|
1755
|
+
const checkSpinner = spinner("Checking for updates...");
|
|
1479
1756
|
const updates = [];
|
|
1480
1757
|
for (const [name, record] of toCheck) {
|
|
1481
1758
|
try {
|
|
@@ -1497,7 +1774,7 @@ async function runUpdate(skill, options) {
|
|
|
1497
1774
|
});
|
|
1498
1775
|
}
|
|
1499
1776
|
}
|
|
1500
|
-
|
|
1777
|
+
checkSpinner.stop();
|
|
1501
1778
|
blank();
|
|
1502
1779
|
table(
|
|
1503
1780
|
["SKILL", "CURRENT", "LATEST", "STATUS"],
|
|
@@ -1505,33 +1782,36 @@ async function runUpdate(skill, options) {
|
|
|
1505
1782
|
u.name,
|
|
1506
1783
|
u.currentVersion,
|
|
1507
1784
|
u.latestVersion,
|
|
1508
|
-
u.hasUpdate ?
|
|
1785
|
+
u.hasUpdate ? chalk5.yellow("update available") : chalk5.green("up to date")
|
|
1509
1786
|
]),
|
|
1510
1787
|
[25, 10, 10, 20]
|
|
1511
1788
|
);
|
|
1512
1789
|
const updatable = updates.filter((u) => u.hasUpdate);
|
|
1513
1790
|
if (updatable.length === 0) {
|
|
1514
1791
|
blank();
|
|
1515
|
-
success("All skills are up to date");
|
|
1792
|
+
success("All skills are up to date.");
|
|
1516
1793
|
blank();
|
|
1517
1794
|
return;
|
|
1518
1795
|
}
|
|
1519
1796
|
if (options.dryRun) {
|
|
1520
1797
|
blank();
|
|
1521
1798
|
info(
|
|
1522
|
-
`${updatable.length} skill${updatable.length === 1 ? "" : "s"} would be updated
|
|
1799
|
+
`${updatable.length} skill${updatable.length === 1 ? "" : "s"} would be updated.`
|
|
1523
1800
|
);
|
|
1801
|
+
info("Run without --dry-run to apply updates.");
|
|
1524
1802
|
blank();
|
|
1525
1803
|
return;
|
|
1526
1804
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1805
|
+
if (!options.yes && isInteractive()) {
|
|
1806
|
+
blank();
|
|
1807
|
+
const shouldUpdate = await confirm({
|
|
1808
|
+
message: `Update ${updatable.length} skill${updatable.length === 1 ? "" : "s"}?`,
|
|
1809
|
+
default: true
|
|
1810
|
+
});
|
|
1811
|
+
if (!shouldUpdate) {
|
|
1812
|
+
info("Update cancelled.");
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1535
1815
|
}
|
|
1536
1816
|
const { createInstallCommand: createInstallCommand2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
1537
1817
|
for (const u of updatable) {
|
|
@@ -1559,8 +1839,9 @@ async function runUpdate(skill, options) {
|
|
|
1559
1839
|
}
|
|
1560
1840
|
blank();
|
|
1561
1841
|
success(
|
|
1562
|
-
`${updatable.length} skill${updatable.length === 1 ? "" : "s"} updated
|
|
1842
|
+
`${updatable.length} skill${updatable.length === 1 ? "" : "s"} updated.`
|
|
1563
1843
|
);
|
|
1844
|
+
info("Run `shipables doctor` to verify installations.");
|
|
1564
1845
|
blank();
|
|
1565
1846
|
}
|
|
1566
1847
|
|
|
@@ -1568,8 +1849,7 @@ async function runUpdate(skill, options) {
|
|
|
1568
1849
|
import { access as access2, readFile as readFile7, stat as stat3 } from "fs/promises";
|
|
1569
1850
|
import { join as join6 } from "path";
|
|
1570
1851
|
import { Command as Command7 } from "commander";
|
|
1571
|
-
import
|
|
1572
|
-
import ora5 from "ora";
|
|
1852
|
+
import chalk6 from "chalk";
|
|
1573
1853
|
import { confirm as confirm2, select } from "@inquirer/prompts";
|
|
1574
1854
|
|
|
1575
1855
|
// src/skill/parser.ts
|
|
@@ -1670,15 +1950,30 @@ init_hash();
|
|
|
1670
1950
|
init_client();
|
|
1671
1951
|
init_config();
|
|
1672
1952
|
init_output();
|
|
1953
|
+
init_output();
|
|
1954
|
+
init_errors();
|
|
1673
1955
|
function createPublishCommand() {
|
|
1674
1956
|
return new Command7("publish").option("--dry-run", "Pack and validate without uploading").option("--tag <tag>", "Dist-tag (e.g., beta, next)", "latest").option(
|
|
1675
1957
|
"--access <level>",
|
|
1676
1958
|
"Access level for scoped packages",
|
|
1677
1959
|
"public"
|
|
1678
|
-
).
|
|
1960
|
+
).option("-y, --yes", "Skip confirmation prompt").option("--scope <scope>", "Publish scope: personal username or @org name (e.g., --scope @myorg)").description(
|
|
1961
|
+
"Pack and publish the skill in the current directory to the registry. Requires SKILL.md and shipables.json. You must be logged in (shipables login). Use --yes and --scope for non-interactive publishing."
|
|
1962
|
+
).addHelpText("after", `
|
|
1963
|
+
Examples:
|
|
1964
|
+
shipables publish Interactive publish
|
|
1965
|
+
shipables publish --dry-run Validate and pack without uploading
|
|
1966
|
+
shipables publish --yes Publish without confirmation prompt
|
|
1967
|
+
shipables publish --yes --scope @myorg Publish under an org, no prompts
|
|
1968
|
+
shipables publish --yes --scope personal Publish under your personal account
|
|
1969
|
+
`).action(async (options) => {
|
|
1679
1970
|
try {
|
|
1680
1971
|
await runPublish(options);
|
|
1681
1972
|
} catch (err) {
|
|
1973
|
+
if (err instanceof CliError) {
|
|
1974
|
+
errorWithFix(err.message, err.fix || "");
|
|
1975
|
+
process.exit(err.exitCode);
|
|
1976
|
+
}
|
|
1682
1977
|
error(err instanceof Error ? err.message : String(err));
|
|
1683
1978
|
process.exit(1);
|
|
1684
1979
|
}
|
|
@@ -1691,15 +1986,17 @@ async function runPublish(options) {
|
|
|
1691
1986
|
try {
|
|
1692
1987
|
await access2(skillMdPath);
|
|
1693
1988
|
} catch {
|
|
1694
|
-
throw
|
|
1695
|
-
"No SKILL.md found in the current directory.
|
|
1989
|
+
throw validationError(
|
|
1990
|
+
"No SKILL.md found in the current directory.",
|
|
1991
|
+
"Run `shipables init` to create one, or `shipables init --name <name> --description <desc>` for non-interactive scaffolding."
|
|
1696
1992
|
);
|
|
1697
1993
|
}
|
|
1698
1994
|
try {
|
|
1699
1995
|
await access2(manifestPath);
|
|
1700
1996
|
} catch {
|
|
1701
|
-
throw
|
|
1702
|
-
"No shipables.json found in the current directory.
|
|
1997
|
+
throw validationError(
|
|
1998
|
+
"No shipables.json found in the current directory.",
|
|
1999
|
+
"Run `shipables init` to create one, or `shipables init --name <name> --description <desc>` for non-interactive scaffolding."
|
|
1703
2000
|
);
|
|
1704
2001
|
}
|
|
1705
2002
|
header("Validating:");
|
|
@@ -1713,7 +2010,10 @@ async function runPublish(options) {
|
|
|
1713
2010
|
for (const err of manifestErrors) {
|
|
1714
2011
|
error(err);
|
|
1715
2012
|
}
|
|
1716
|
-
throw
|
|
2013
|
+
throw validationError(
|
|
2014
|
+
"shipables.json validation failed.",
|
|
2015
|
+
"Fix the errors listed above in shipables.json, then re-run: shipables publish"
|
|
2016
|
+
);
|
|
1717
2017
|
}
|
|
1718
2018
|
success(
|
|
1719
2019
|
`shipables.json \u2014 version: ${manifest.version}, schema valid`
|
|
@@ -1722,16 +2022,17 @@ async function runPublish(options) {
|
|
|
1722
2022
|
const version = manifest.version;
|
|
1723
2023
|
const token = await getToken();
|
|
1724
2024
|
if (!token && !options.dryRun) {
|
|
1725
|
-
throw
|
|
1726
|
-
"Not authenticated.
|
|
2025
|
+
throw authError(
|
|
2026
|
+
"Not authenticated. You must be logged in to publish.",
|
|
2027
|
+
"Run `shipables login` to authenticate, then re-run `shipables publish`."
|
|
1727
2028
|
);
|
|
1728
2029
|
}
|
|
1729
2030
|
let fullName = skillBareName;
|
|
1730
2031
|
if (!options.dryRun && token) {
|
|
1731
|
-
fullName = await resolvePublishScope(skillBareName);
|
|
2032
|
+
fullName = await resolvePublishScope(skillBareName, options.scope);
|
|
1732
2033
|
}
|
|
1733
2034
|
blank();
|
|
1734
|
-
console.log(` Publishing ${
|
|
2035
|
+
console.log(` Publishing ${chalk6.bold(`${fullName}@${version}`)}...`);
|
|
1735
2036
|
let ignorePatterns = [];
|
|
1736
2037
|
try {
|
|
1737
2038
|
const ignoreRaw = await readFile7(join6(cwd, ".shipablesignore"), "utf-8");
|
|
@@ -1740,7 +2041,7 @@ async function runPublish(options) {
|
|
|
1740
2041
|
}
|
|
1741
2042
|
const tarballName = `${skillBareName}-${version}.tgz`;
|
|
1742
2043
|
const tarballPath = join6(cwd, tarballName);
|
|
1743
|
-
const packSpinner =
|
|
2044
|
+
const packSpinner = spinner("Packing tarball...");
|
|
1744
2045
|
await createTarball(cwd, tarballPath, ignorePatterns);
|
|
1745
2046
|
const tarballStat = await stat3(tarballPath);
|
|
1746
2047
|
const hash = await sha512File(tarballPath);
|
|
@@ -1754,34 +2055,39 @@ async function runPublish(options) {
|
|
|
1754
2055
|
console.log(` ${file}`);
|
|
1755
2056
|
}
|
|
1756
2057
|
if (files.length > 20) {
|
|
1757
|
-
console.log(
|
|
2058
|
+
console.log(chalk6.dim(` ... and ${files.length - 20} more files`));
|
|
1758
2059
|
}
|
|
1759
2060
|
if (options.dryRun) {
|
|
1760
2061
|
blank();
|
|
1761
|
-
success(
|
|
2062
|
+
success(`Dry run complete \u2014 ${tarballName} (${formatBytes(tarballStat.size)}) packed but not uploaded.`);
|
|
2063
|
+
info(`Validated: SKILL.md (name: ${skillBareName}), shipables.json (version: ${version})`);
|
|
1762
2064
|
const { unlink } = await import("fs/promises");
|
|
1763
2065
|
await unlink(tarballPath);
|
|
1764
2066
|
blank();
|
|
1765
2067
|
return;
|
|
1766
2068
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2069
|
+
if (!options.yes && isInteractive()) {
|
|
2070
|
+
blank();
|
|
2071
|
+
const shouldPublish = await confirm2({
|
|
2072
|
+
message: `Publish ${fullName}@${version} to the shipables registry?`,
|
|
2073
|
+
default: true
|
|
2074
|
+
});
|
|
2075
|
+
if (!shouldPublish) {
|
|
2076
|
+
const { unlink } = await import("fs/promises");
|
|
2077
|
+
await unlink(tarballPath);
|
|
2078
|
+
info("Publish cancelled.");
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
1777
2081
|
}
|
|
1778
|
-
const uploadSpinner =
|
|
2082
|
+
const uploadSpinner = spinner("Publishing...");
|
|
1779
2083
|
try {
|
|
1780
2084
|
const result = await registry.publishWithName(fullName, tarballPath);
|
|
1781
|
-
uploadSpinner.succeed(`Published ${
|
|
2085
|
+
uploadSpinner.succeed(`Published ${chalk6.bold(`${fullName}@${version}`)}`);
|
|
2086
|
+
blank();
|
|
1782
2087
|
if (result.url) {
|
|
1783
|
-
info(result.url);
|
|
2088
|
+
info(`View: ${result.url}`);
|
|
1784
2089
|
}
|
|
2090
|
+
info(`Install with: npx shipables install ${fullName}`);
|
|
1785
2091
|
} catch (err) {
|
|
1786
2092
|
uploadSpinner.fail("Publish failed");
|
|
1787
2093
|
throw err;
|
|
@@ -1794,7 +2100,7 @@ async function runPublish(options) {
|
|
|
1794
2100
|
}
|
|
1795
2101
|
blank();
|
|
1796
2102
|
}
|
|
1797
|
-
async function resolvePublishScope(bareName) {
|
|
2103
|
+
async function resolvePublishScope(bareName, scopeFlag) {
|
|
1798
2104
|
let me;
|
|
1799
2105
|
try {
|
|
1800
2106
|
me = await registry.getMe();
|
|
@@ -1805,6 +2111,17 @@ async function resolvePublishScope(bareName) {
|
|
|
1805
2111
|
const publishableOrgs = orgs.filter(
|
|
1806
2112
|
(o) => o.role === "owner" || o.role === "admin"
|
|
1807
2113
|
);
|
|
2114
|
+
if (scopeFlag) {
|
|
2115
|
+
if (scopeFlag === "personal" || scopeFlag === me.username) {
|
|
2116
|
+
return `${me.username}/${bareName}`;
|
|
2117
|
+
}
|
|
2118
|
+
const orgName = scopeFlag.replace(/^@/, "");
|
|
2119
|
+
const org = publishableOrgs.find((o) => o.name === orgName);
|
|
2120
|
+
if (org) {
|
|
2121
|
+
return `@${org.name}/${bareName}`;
|
|
2122
|
+
}
|
|
2123
|
+
return `@${orgName}/${bareName}`;
|
|
2124
|
+
}
|
|
1808
2125
|
if (publishableOrgs.length === 0) {
|
|
1809
2126
|
return `${me.username}/${bareName}`;
|
|
1810
2127
|
}
|
|
@@ -1821,6 +2138,10 @@ async function resolvePublishScope(bareName) {
|
|
|
1821
2138
|
if (choices.length === 1) {
|
|
1822
2139
|
return choices[0].value;
|
|
1823
2140
|
}
|
|
2141
|
+
if (!isInteractive()) {
|
|
2142
|
+
info(`Auto-selected publish scope: ${choices[0].value}. Use --scope to specify a different scope.`);
|
|
2143
|
+
return choices[0].value;
|
|
2144
|
+
}
|
|
1824
2145
|
const selected = await select({
|
|
1825
2146
|
message: "Publish as:",
|
|
1826
2147
|
choices
|
|
@@ -1859,15 +2180,27 @@ async function listAllFiles(dir, ignorePatterns, prefix = "") {
|
|
|
1859
2180
|
init_client();
|
|
1860
2181
|
init_config();
|
|
1861
2182
|
init_output();
|
|
2183
|
+
init_output();
|
|
2184
|
+
init_errors();
|
|
1862
2185
|
import { Command as Command8 } from "commander";
|
|
1863
|
-
import
|
|
1864
|
-
import ora6 from "ora";
|
|
2186
|
+
import chalk7 from "chalk";
|
|
1865
2187
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
1866
2188
|
function createUnpublishCommand() {
|
|
1867
|
-
return new Command8("unpublish").argument("<skill>", "Skill name with version (e.g., my-skill@1.0.0)").option("-f, --force", "Skip confirmation prompt").description(
|
|
2189
|
+
return new Command8("unpublish").argument("<skill>", "Skill name with version (e.g., my-skill@1.0.0)").option("-f, --force", "Skip confirmation prompt").description(
|
|
2190
|
+
"Yank a published version from the registry (within 72 hours of publish). Yanked versions cannot be re-published with the same version number. Use --force to skip the confirmation prompt."
|
|
2191
|
+
).addHelpText("after", `
|
|
2192
|
+
Examples:
|
|
2193
|
+
shipables unpublish my-skill@1.0.0 Interactive confirmation
|
|
2194
|
+
shipables unpublish my-skill@1.0.0 --force Skip confirmation
|
|
2195
|
+
shipables unpublish @org/tool@2.1.0 -f Unpublish a scoped skill
|
|
2196
|
+
`).action(async (skillArg, options) => {
|
|
1868
2197
|
try {
|
|
1869
2198
|
await runUnpublish(skillArg, options);
|
|
1870
2199
|
} catch (err) {
|
|
2200
|
+
if (err instanceof CliError) {
|
|
2201
|
+
errorWithFix(err.message, err.fix || "");
|
|
2202
|
+
process.exit(err.exitCode);
|
|
2203
|
+
}
|
|
1871
2204
|
error(err instanceof Error ? err.message : String(err));
|
|
1872
2205
|
process.exit(1);
|
|
1873
2206
|
}
|
|
@@ -1876,21 +2209,25 @@ function createUnpublishCommand() {
|
|
|
1876
2209
|
async function runUnpublish(skillArg, options) {
|
|
1877
2210
|
const token = await getToken();
|
|
1878
2211
|
if (!token) {
|
|
1879
|
-
throw
|
|
2212
|
+
throw authError(
|
|
2213
|
+
"Not authenticated. You must be logged in to unpublish.",
|
|
2214
|
+
"Run `shipables login` to authenticate."
|
|
2215
|
+
);
|
|
1880
2216
|
}
|
|
1881
2217
|
const { name, version } = parseSkillVersion(skillArg);
|
|
1882
2218
|
if (!version) {
|
|
1883
|
-
throw
|
|
1884
|
-
"You must specify a version to unpublish (e.g., my-skill@1.0.0). Unpublishing an entire package is not supported."
|
|
2219
|
+
throw usageError(
|
|
2220
|
+
"You must specify a version to unpublish (e.g., my-skill@1.0.0). Unpublishing an entire package is not supported.",
|
|
2221
|
+
"Use the format: shipables unpublish <skill>@<version>"
|
|
1885
2222
|
);
|
|
1886
2223
|
}
|
|
1887
2224
|
blank();
|
|
1888
2225
|
warn(
|
|
1889
|
-
`This will yank ${
|
|
2226
|
+
`This will yank ${chalk7.bold(`${name}@${version}`)} from the registry.`
|
|
1890
2227
|
);
|
|
1891
2228
|
warn("Yanked versions cannot be re-published with the same version number.");
|
|
1892
2229
|
blank();
|
|
1893
|
-
if (!options.force) {
|
|
2230
|
+
if (!options.force && isInteractive()) {
|
|
1894
2231
|
const shouldProceed = await confirm3({
|
|
1895
2232
|
message: `Are you sure you want to unpublish ${name}@${version}?`,
|
|
1896
2233
|
default: false
|
|
@@ -1900,12 +2237,12 @@ async function runUnpublish(skillArg, options) {
|
|
|
1900
2237
|
return;
|
|
1901
2238
|
}
|
|
1902
2239
|
}
|
|
1903
|
-
const
|
|
2240
|
+
const unpubSpinner = spinner(`Unpublishing ${name}@${version}...`);
|
|
1904
2241
|
try {
|
|
1905
2242
|
await registry.unpublishVersion(name, version);
|
|
1906
|
-
|
|
2243
|
+
unpubSpinner.succeed(`Unpublished ${chalk7.bold(`${name}@${version}`)}`);
|
|
1907
2244
|
} catch (err) {
|
|
1908
|
-
|
|
2245
|
+
unpubSpinner.fail("Unpublish failed");
|
|
1909
2246
|
throw err;
|
|
1910
2247
|
}
|
|
1911
2248
|
blank();
|
|
@@ -1931,10 +2268,12 @@ function parseSkillVersion(arg) {
|
|
|
1931
2268
|
|
|
1932
2269
|
// src/commands/init.ts
|
|
1933
2270
|
init_output();
|
|
2271
|
+
init_errors();
|
|
2272
|
+
init_output();
|
|
1934
2273
|
import { mkdir as mkdir4, writeFile as writeFile5, access as access3 } from "fs/promises";
|
|
1935
2274
|
import { join as join7 } from "path";
|
|
1936
2275
|
import { Command as Command9 } from "commander";
|
|
1937
|
-
import
|
|
2276
|
+
import chalk8 from "chalk";
|
|
1938
2277
|
import { input as input2, confirm as confirm4, checkbox as checkbox2 } from "@inquirer/prompts";
|
|
1939
2278
|
var CATEGORIES = [
|
|
1940
2279
|
"databases",
|
|
@@ -1954,16 +2293,54 @@ var CATEGORIES = [
|
|
|
1954
2293
|
"other"
|
|
1955
2294
|
];
|
|
1956
2295
|
function createInitCommand() {
|
|
1957
|
-
return new Command9("init").description(
|
|
2296
|
+
return new Command9("init").description(
|
|
2297
|
+
"Scaffold a new skill in the current directory. Creates SKILL.md, shipables.json, README.md, and .shipablesignore. Use flags (--name, --description) for non-interactive mode."
|
|
2298
|
+
).option("--name <name>", "Skill name (lowercase, hyphens, max 64 chars)").option("--description <desc>", "Description of the skill").option("--author <name>", "Author name").option("--author-github <gh>", "Author GitHub username").option("--license <license>", "License identifier (default: MIT)").option("--category <cat...>", "Categories (repeatable)").option("--mcp-package <pkg>", "MCP server npm package name").option("--scripts", "Include example scripts/ directory").option("--no-scripts", "Do not include example scripts/ directory").option("--references", "Include example references/ directory").option("--no-references", "Do not include example references/ directory").option("-y, --yes", "Accept defaults for optional fields").addHelpText(
|
|
2299
|
+
"after",
|
|
2300
|
+
`
|
|
2301
|
+
Examples:
|
|
2302
|
+
shipables init Interactive mode
|
|
2303
|
+
shipables init --name my-skill --description "..." Non-interactive with required fields
|
|
2304
|
+
shipables init --name my-skill --description "..." --mcp-package @org/server --category databases`
|
|
2305
|
+
).action(async (opts) => {
|
|
1958
2306
|
try {
|
|
1959
|
-
await runInit();
|
|
2307
|
+
await runInit(opts);
|
|
1960
2308
|
} catch (err) {
|
|
2309
|
+
if (err instanceof CliError) {
|
|
2310
|
+
error(err.message);
|
|
2311
|
+
if (err.fix) {
|
|
2312
|
+
info(`Fix: ${err.fix}`);
|
|
2313
|
+
}
|
|
2314
|
+
process.exit(err.exitCode);
|
|
2315
|
+
}
|
|
1961
2316
|
error(err instanceof Error ? err.message : String(err));
|
|
1962
2317
|
process.exit(1);
|
|
1963
2318
|
}
|
|
1964
2319
|
});
|
|
1965
2320
|
}
|
|
1966
|
-
|
|
2321
|
+
function validateName(val) {
|
|
2322
|
+
if (!val) return "Name is required";
|
|
2323
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(val))
|
|
2324
|
+
return "Must be lowercase letters, numbers, and hyphens only";
|
|
2325
|
+
if (val.length > 64) return "Max 64 characters";
|
|
2326
|
+
return true;
|
|
2327
|
+
}
|
|
2328
|
+
function validateDescription(val) {
|
|
2329
|
+
if (!val) return "Description is required";
|
|
2330
|
+
if (val.length > 1024) return "Max 1024 characters";
|
|
2331
|
+
return true;
|
|
2332
|
+
}
|
|
2333
|
+
function validateCategories(cats) {
|
|
2334
|
+
for (const c of cats) {
|
|
2335
|
+
if (!CATEGORIES.includes(c)) {
|
|
2336
|
+
throw usageError(
|
|
2337
|
+
`Invalid category: "${c}"`,
|
|
2338
|
+
`Valid categories: ${CATEGORIES.join(", ")}`
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
async function runInit(opts) {
|
|
1967
2344
|
header("Create a new Shipables skill");
|
|
1968
2345
|
const cwd = process.cwd();
|
|
1969
2346
|
try {
|
|
@@ -1974,86 +2351,164 @@ async function runInit() {
|
|
|
1974
2351
|
throw err;
|
|
1975
2352
|
}
|
|
1976
2353
|
}
|
|
2354
|
+
const interactive = isInteractive();
|
|
2355
|
+
const hasRequiredFlags = opts.name !== void 0 && opts.description !== void 0;
|
|
2356
|
+
if (!interactive && !hasRequiredFlags) {
|
|
2357
|
+
throw usageError(
|
|
2358
|
+
"Non-interactive environment detected. --name and --description are required.",
|
|
2359
|
+
"Provide required flags: shipables init --name <name> --description <desc>"
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
if (hasRequiredFlags) {
|
|
2363
|
+
const nameResult = validateName(opts.name);
|
|
2364
|
+
if (nameResult !== true) {
|
|
2365
|
+
throw usageError(nameResult);
|
|
2366
|
+
}
|
|
2367
|
+
const descResult = validateDescription(opts.description);
|
|
2368
|
+
if (descResult !== true) {
|
|
2369
|
+
throw usageError(descResult);
|
|
2370
|
+
}
|
|
2371
|
+
const name2 = opts.name;
|
|
2372
|
+
const description2 = opts.description;
|
|
2373
|
+
const authorName2 = opts.author ?? process.env.USER ?? "your-name";
|
|
2374
|
+
const authorGithub2 = opts.authorGithub ?? authorName2;
|
|
2375
|
+
const license2 = opts.license ?? "MIT";
|
|
2376
|
+
const categories2 = opts.category ?? [];
|
|
2377
|
+
if (categories2.length > 0) {
|
|
2378
|
+
validateCategories(categories2);
|
|
2379
|
+
}
|
|
2380
|
+
const includeMcp2 = opts.mcpPackage !== void 0;
|
|
2381
|
+
const mcpPackage2 = opts.mcpPackage ?? "";
|
|
2382
|
+
const includeScripts2 = opts.scripts ?? false;
|
|
2383
|
+
const includeReferences2 = opts.references ?? false;
|
|
2384
|
+
await generateFiles({
|
|
2385
|
+
name: name2,
|
|
2386
|
+
description: description2,
|
|
2387
|
+
authorName: authorName2,
|
|
2388
|
+
authorGithub: authorGithub2,
|
|
2389
|
+
license: license2,
|
|
2390
|
+
categories: categories2,
|
|
2391
|
+
includeMcp: includeMcp2,
|
|
2392
|
+
mcpPackage: mcpPackage2,
|
|
2393
|
+
mcpEnvVars: [],
|
|
2394
|
+
includeScripts: includeScripts2,
|
|
2395
|
+
includeReferences: includeReferences2,
|
|
2396
|
+
skillDir: cwd
|
|
2397
|
+
});
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
1977
2400
|
const name = await input2({
|
|
1978
2401
|
message: "Skill name (lowercase, hyphens, max 64 chars):",
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(val))
|
|
1982
|
-
return "Must be lowercase letters, numbers, and hyphens only";
|
|
1983
|
-
if (val.length > 64) return "Max 64 characters";
|
|
1984
|
-
return true;
|
|
1985
|
-
}
|
|
2402
|
+
default: opts.name,
|
|
2403
|
+
validate: (val) => validateName(val)
|
|
1986
2404
|
});
|
|
1987
2405
|
const description = await input2({
|
|
1988
2406
|
message: "Description (what the skill does and when to use it):",
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
if (val.length > 1024) return "Max 1024 characters";
|
|
1992
|
-
return true;
|
|
1993
|
-
}
|
|
2407
|
+
default: opts.description,
|
|
2408
|
+
validate: (val) => validateDescription(val)
|
|
1994
2409
|
});
|
|
1995
|
-
const authorName = await input2({
|
|
2410
|
+
const authorName = opts.yes ? opts.author ?? process.env.USER ?? "your-name" : await input2({
|
|
1996
2411
|
message: "Author name:",
|
|
1997
|
-
default: process.env.USER
|
|
2412
|
+
default: opts.author ?? process.env.USER ?? "your-name"
|
|
1998
2413
|
});
|
|
1999
|
-
const authorGithub = await input2({
|
|
2414
|
+
const authorGithub = opts.yes ? opts.authorGithub ?? authorName : await input2({
|
|
2000
2415
|
message: "Author GitHub username:",
|
|
2001
|
-
default: authorName
|
|
2416
|
+
default: opts.authorGithub ?? authorName
|
|
2002
2417
|
});
|
|
2003
|
-
const license = await input2({
|
|
2418
|
+
const license = opts.yes ? opts.license ?? "MIT" : await input2({
|
|
2004
2419
|
message: "License:",
|
|
2005
|
-
default: "MIT"
|
|
2420
|
+
default: opts.license ?? "MIT"
|
|
2006
2421
|
});
|
|
2007
|
-
const categories = await checkbox2({
|
|
2422
|
+
const categories = opts.yes ? opts.category ?? [] : opts.category !== void 0 ? opts.category : await checkbox2({
|
|
2008
2423
|
message: "Categories (select with space):",
|
|
2009
2424
|
choices: CATEGORIES.map((c) => ({ name: c, value: c }))
|
|
2010
2425
|
});
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2426
|
+
if (categories.length > 0) {
|
|
2427
|
+
validateCategories(categories);
|
|
2428
|
+
}
|
|
2429
|
+
let includeMcp;
|
|
2015
2430
|
let mcpPackage = "";
|
|
2016
2431
|
let mcpEnvVars = [];
|
|
2017
|
-
if (
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2432
|
+
if (opts.mcpPackage !== void 0) {
|
|
2433
|
+
includeMcp = true;
|
|
2434
|
+
mcpPackage = opts.mcpPackage;
|
|
2435
|
+
} else if (opts.yes) {
|
|
2436
|
+
includeMcp = false;
|
|
2437
|
+
} else {
|
|
2438
|
+
includeMcp = await confirm4({
|
|
2439
|
+
message: "Include MCP server configuration?",
|
|
2440
|
+
default: false
|
|
2021
2441
|
});
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
});
|
|
2027
|
-
if (!envName) break;
|
|
2028
|
-
const envDesc = await input2({
|
|
2029
|
-
message: `Description for ${envName}:`
|
|
2030
|
-
});
|
|
2031
|
-
const envRequired = await confirm4({
|
|
2032
|
-
message: `Is ${envName} required?`,
|
|
2033
|
-
default: true
|
|
2034
|
-
});
|
|
2035
|
-
const envSecret = await confirm4({
|
|
2036
|
-
message: `Is ${envName} a secret?`,
|
|
2037
|
-
default: false
|
|
2038
|
-
});
|
|
2039
|
-
mcpEnvVars.push({
|
|
2040
|
-
name: envName,
|
|
2041
|
-
description: envDesc,
|
|
2042
|
-
required: envRequired,
|
|
2043
|
-
secret: envSecret
|
|
2442
|
+
if (includeMcp) {
|
|
2443
|
+
mcpPackage = await input2({
|
|
2444
|
+
message: "MCP server npm package name:",
|
|
2445
|
+
validate: (val) => val ? true : "Package name is required"
|
|
2044
2446
|
});
|
|
2447
|
+
let addMore = true;
|
|
2448
|
+
while (addMore) {
|
|
2449
|
+
const envName = await input2({
|
|
2450
|
+
message: "Environment variable name (or empty to finish):"
|
|
2451
|
+
});
|
|
2452
|
+
if (!envName) break;
|
|
2453
|
+
const envDesc = await input2({
|
|
2454
|
+
message: `Description for ${envName}:`
|
|
2455
|
+
});
|
|
2456
|
+
const envRequired = await confirm4({
|
|
2457
|
+
message: `Is ${envName} required?`,
|
|
2458
|
+
default: true
|
|
2459
|
+
});
|
|
2460
|
+
const envSecret = await confirm4({
|
|
2461
|
+
message: `Is ${envName} a secret?`,
|
|
2462
|
+
default: false
|
|
2463
|
+
});
|
|
2464
|
+
mcpEnvVars.push({
|
|
2465
|
+
name: envName,
|
|
2466
|
+
description: envDesc,
|
|
2467
|
+
required: envRequired,
|
|
2468
|
+
secret: envSecret
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2045
2471
|
}
|
|
2046
2472
|
}
|
|
2047
|
-
const includeScripts = await confirm4({
|
|
2473
|
+
const includeScripts = opts.scripts !== void 0 ? opts.scripts : opts.yes ? false : await confirm4({
|
|
2048
2474
|
message: "Include example scripts/ directory?",
|
|
2049
2475
|
default: false
|
|
2050
2476
|
});
|
|
2051
|
-
const includeReferences = await confirm4({
|
|
2477
|
+
const includeReferences = opts.references !== void 0 ? opts.references : opts.yes ? false : await confirm4({
|
|
2052
2478
|
message: "Include example references/ directory?",
|
|
2053
2479
|
default: false
|
|
2054
2480
|
});
|
|
2481
|
+
await generateFiles({
|
|
2482
|
+
name,
|
|
2483
|
+
description,
|
|
2484
|
+
authorName,
|
|
2485
|
+
authorGithub,
|
|
2486
|
+
license,
|
|
2487
|
+
categories,
|
|
2488
|
+
includeMcp,
|
|
2489
|
+
mcpPackage,
|
|
2490
|
+
mcpEnvVars,
|
|
2491
|
+
includeScripts,
|
|
2492
|
+
includeReferences,
|
|
2493
|
+
skillDir: cwd
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
async function generateFiles(opts) {
|
|
2497
|
+
const {
|
|
2498
|
+
name,
|
|
2499
|
+
description,
|
|
2500
|
+
authorName,
|
|
2501
|
+
authorGithub,
|
|
2502
|
+
license,
|
|
2503
|
+
categories,
|
|
2504
|
+
includeMcp,
|
|
2505
|
+
mcpPackage,
|
|
2506
|
+
mcpEnvVars,
|
|
2507
|
+
includeScripts,
|
|
2508
|
+
includeReferences,
|
|
2509
|
+
skillDir
|
|
2510
|
+
} = opts;
|
|
2055
2511
|
header("Creating files:");
|
|
2056
|
-
const skillDir = cwd;
|
|
2057
2512
|
const skillMd = generateSkillMd(
|
|
2058
2513
|
name,
|
|
2059
2514
|
description,
|
|
@@ -2118,12 +2573,12 @@ Detailed reference documentation goes here.
|
|
|
2118
2573
|
success("references/example.md");
|
|
2119
2574
|
}
|
|
2120
2575
|
blank();
|
|
2121
|
-
success(`Skill ${
|
|
2576
|
+
success(`Skill ${chalk8.bold(name)} initialized!`);
|
|
2122
2577
|
blank();
|
|
2123
2578
|
info("Next steps:");
|
|
2124
|
-
info(` 1. Edit ${
|
|
2125
|
-
info(` 2. Edit ${
|
|
2126
|
-
info(` 3. Run ${
|
|
2579
|
+
info(` 1. Edit ${chalk8.cyan("SKILL.md")} with your agent instructions`);
|
|
2580
|
+
info(` 2. Edit ${chalk8.cyan("shipables.json")} with your metadata`);
|
|
2581
|
+
info(` 3. Run ${chalk8.cyan("shipables publish")} to publish`);
|
|
2127
2582
|
blank();
|
|
2128
2583
|
}
|
|
2129
2584
|
function generateSkillMd(name, description, license, author, includeReferences, includeMcp, mcpPackage) {
|
|
@@ -2214,31 +2669,73 @@ function titleCase(s) {
|
|
|
2214
2669
|
// src/commands/login.ts
|
|
2215
2670
|
init_config();
|
|
2216
2671
|
init_output();
|
|
2672
|
+
init_output();
|
|
2673
|
+
init_errors();
|
|
2217
2674
|
import { createServer } from "http";
|
|
2218
2675
|
import { Command as Command10 } from "commander";
|
|
2219
|
-
import
|
|
2220
|
-
import ora7 from "ora";
|
|
2676
|
+
import chalk9 from "chalk";
|
|
2221
2677
|
function createLoginCommand() {
|
|
2222
|
-
return new Command10("login").description(
|
|
2678
|
+
return new Command10("login").description(
|
|
2679
|
+
"Authenticate with the Shipables registry. Uses GitHub OAuth by default. For CI or AI agents, use --token with a pre-generated API token."
|
|
2680
|
+
).option(
|
|
2681
|
+
"--token <token>",
|
|
2682
|
+
"Authenticate with an API token (for CI/AI agents). Generate at https://shipables.dev/settings/tokens"
|
|
2683
|
+
).addHelpText(
|
|
2684
|
+
"after",
|
|
2685
|
+
"\nExamples:\n shipables login Interactive GitHub OAuth\n shipables login --token cbl_xxxxx Non-interactive token auth (CI/AI agents)\n"
|
|
2686
|
+
).action(async (options) => {
|
|
2223
2687
|
try {
|
|
2224
|
-
await runLogin();
|
|
2688
|
+
await runLogin(options);
|
|
2225
2689
|
} catch (err) {
|
|
2690
|
+
if (err instanceof CliError) {
|
|
2691
|
+
error(err.message);
|
|
2692
|
+
process.exit(err.exitCode);
|
|
2693
|
+
}
|
|
2226
2694
|
error(err instanceof Error ? err.message : String(err));
|
|
2227
2695
|
process.exit(1);
|
|
2228
2696
|
}
|
|
2229
2697
|
});
|
|
2230
2698
|
}
|
|
2231
2699
|
function createLogoutCommand() {
|
|
2232
|
-
return new Command10("logout").description("Remove stored credentials").action(async () => {
|
|
2700
|
+
return new Command10("logout").description("Remove stored credentials from ~/.shipables/config.json").action(async () => {
|
|
2233
2701
|
try {
|
|
2234
2702
|
await runLogout();
|
|
2235
2703
|
} catch (err) {
|
|
2704
|
+
if (err instanceof CliError) {
|
|
2705
|
+
error(err.message);
|
|
2706
|
+
process.exit(err.exitCode);
|
|
2707
|
+
}
|
|
2236
2708
|
error(err instanceof Error ? err.message : String(err));
|
|
2237
2709
|
process.exit(1);
|
|
2238
2710
|
}
|
|
2239
2711
|
});
|
|
2240
2712
|
}
|
|
2241
|
-
async function runLogin() {
|
|
2713
|
+
async function runLogin(options) {
|
|
2714
|
+
if (options.token) {
|
|
2715
|
+
const registryUrl2 = await getRegistry();
|
|
2716
|
+
const config = await loadConfig();
|
|
2717
|
+
config.token = options.token;
|
|
2718
|
+
try {
|
|
2719
|
+
const resp = await fetch(`${registryUrl2}/api/v1/auth/me`, {
|
|
2720
|
+
headers: {
|
|
2721
|
+
Authorization: `Bearer ${options.token}`,
|
|
2722
|
+
"User-Agent": "shipables-cli"
|
|
2723
|
+
}
|
|
2724
|
+
});
|
|
2725
|
+
if (resp.ok) {
|
|
2726
|
+
const me = await resp.json();
|
|
2727
|
+
config.username = me.username;
|
|
2728
|
+
}
|
|
2729
|
+
} catch {
|
|
2730
|
+
}
|
|
2731
|
+
await saveConfig(config);
|
|
2732
|
+
blank();
|
|
2733
|
+
success(`Logged in as ${chalk9.bold(config.username || "unknown")}`);
|
|
2734
|
+
info(chalk9.dim("Token stored in ~/.shipables/config.json"));
|
|
2735
|
+
info("You can now publish skills with: shipables publish");
|
|
2736
|
+
blank();
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2242
2739
|
const registryUrl = await getRegistry();
|
|
2243
2740
|
const { port, tokenPromise, server } = await startCallbackServer();
|
|
2244
2741
|
const callbackUrl = `http://localhost:${port}/callback`;
|
|
@@ -2246,13 +2743,13 @@ async function runLogin() {
|
|
|
2246
2743
|
blank();
|
|
2247
2744
|
info("Open this URL in your browser to sign in with GitHub:");
|
|
2248
2745
|
blank();
|
|
2249
|
-
info(
|
|
2746
|
+
info(chalk9.cyan(loginUrl));
|
|
2250
2747
|
blank();
|
|
2251
2748
|
await tryOpenBrowser(loginUrl);
|
|
2252
|
-
const
|
|
2749
|
+
const spin = spinner("Waiting for authentication...");
|
|
2253
2750
|
try {
|
|
2254
2751
|
const token = await tokenPromise;
|
|
2255
|
-
|
|
2752
|
+
spin.stop();
|
|
2256
2753
|
const config = await loadConfig();
|
|
2257
2754
|
config.token = token;
|
|
2258
2755
|
try {
|
|
@@ -2271,14 +2768,14 @@ async function runLogin() {
|
|
|
2271
2768
|
await saveConfig(config);
|
|
2272
2769
|
blank();
|
|
2273
2770
|
success(
|
|
2274
|
-
`Logged in as ${
|
|
2771
|
+
`Logged in as ${chalk9.bold(config.username || "unknown")}`
|
|
2275
2772
|
);
|
|
2276
2773
|
info(
|
|
2277
|
-
|
|
2774
|
+
chalk9.dim("Token stored in ~/.shipables/config.json")
|
|
2278
2775
|
);
|
|
2279
2776
|
blank();
|
|
2280
2777
|
} catch (err) {
|
|
2281
|
-
|
|
2778
|
+
spin.fail("Authentication failed");
|
|
2282
2779
|
throw err;
|
|
2283
2780
|
} finally {
|
|
2284
2781
|
server.close();
|
|
@@ -2360,13 +2857,26 @@ init_config();
|
|
|
2360
2857
|
init_adapters();
|
|
2361
2858
|
init_client();
|
|
2362
2859
|
init_output();
|
|
2860
|
+
init_output();
|
|
2861
|
+
init_errors();
|
|
2363
2862
|
import { Command as Command11 } from "commander";
|
|
2364
|
-
import
|
|
2863
|
+
import chalk10 from "chalk";
|
|
2365
2864
|
function createDoctorCommand() {
|
|
2366
|
-
return new Command11("doctor").description(
|
|
2865
|
+
return new Command11("doctor").description(
|
|
2866
|
+
"Check health of installed skills and agent configurations. Validates skill directories exist, MCP configs are present, and checks for updates."
|
|
2867
|
+
).option("-g, --global", "Check globally installed skills").option("--json", "Output results as JSON").addHelpText("after", `
|
|
2868
|
+
Examples:
|
|
2869
|
+
shipables doctor Check all skills in current project
|
|
2870
|
+
shipables doctor --global Check globally installed skills
|
|
2871
|
+
shipables doctor --json Get results as JSON
|
|
2872
|
+
`).action(async (options) => {
|
|
2367
2873
|
try {
|
|
2368
2874
|
await runDoctor(options);
|
|
2369
2875
|
} catch (err) {
|
|
2876
|
+
if (err instanceof CliError) {
|
|
2877
|
+
errorWithFix(err.message, err.fix || "");
|
|
2878
|
+
process.exit(err.exitCode);
|
|
2879
|
+
}
|
|
2370
2880
|
error(err instanceof Error ? err.message : String(err));
|
|
2371
2881
|
process.exit(1);
|
|
2372
2882
|
}
|
|
@@ -2377,19 +2887,26 @@ async function runDoctor(options) {
|
|
|
2377
2887
|
const installations = await getProjectInstallations(projectPath);
|
|
2378
2888
|
const entries = Object.entries(installations);
|
|
2379
2889
|
if (entries.length === 0) {
|
|
2890
|
+
if (options.json || getJsonMode()) {
|
|
2891
|
+
console.log(JSON.stringify({ ok: true, skills: [], issues: 0, updates: 0 }, null, 2));
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2380
2894
|
blank();
|
|
2381
2895
|
info("No skills installed to check.");
|
|
2896
|
+
info("Install a skill with: shipables install <skill>");
|
|
2382
2897
|
blank();
|
|
2383
2898
|
return;
|
|
2384
2899
|
}
|
|
2385
2900
|
header("Checking installed skills...");
|
|
2386
2901
|
let totalIssues = 0;
|
|
2387
2902
|
let totalUpdates = 0;
|
|
2903
|
+
const jsonResults = [];
|
|
2388
2904
|
for (const [skillName, record] of entries) {
|
|
2389
2905
|
const agentList = record.agents.join(", ");
|
|
2390
2906
|
console.log(
|
|
2391
|
-
` ${
|
|
2907
|
+
` ${chalk10.bold(`${skillName}@${record.version}`)} (${agentList}):`
|
|
2392
2908
|
);
|
|
2909
|
+
const skillChecks = [];
|
|
2393
2910
|
for (const agentName of record.agents) {
|
|
2394
2911
|
const adapter = getAdapter(agentName);
|
|
2395
2912
|
if (!adapter) continue;
|
|
@@ -2398,6 +2915,7 @@ async function runDoctor(options) {
|
|
|
2398
2915
|
if (skillDir) {
|
|
2399
2916
|
const result = await adapter.validate(skillName, skillDir, mcpConfig);
|
|
2400
2917
|
for (const check of result.checks) {
|
|
2918
|
+
skillChecks.push({ agent: agentName, ...check });
|
|
2401
2919
|
if (check.status === "pass") {
|
|
2402
2920
|
success(check.label);
|
|
2403
2921
|
} else if (check.status === "fail") {
|
|
@@ -2409,9 +2927,11 @@ async function runDoctor(options) {
|
|
|
2409
2927
|
}
|
|
2410
2928
|
}
|
|
2411
2929
|
}
|
|
2930
|
+
let updateAvailable = null;
|
|
2412
2931
|
try {
|
|
2413
2932
|
const detail = await registry.getSkillDetail(skillName);
|
|
2414
2933
|
if (detail.latest_version !== record.version) {
|
|
2934
|
+
updateAvailable = detail.latest_version;
|
|
2415
2935
|
warn(
|
|
2416
2936
|
`Update available: ${record.version} \u2192 ${detail.latest_version}`
|
|
2417
2937
|
);
|
|
@@ -2419,10 +2939,21 @@ async function runDoctor(options) {
|
|
|
2419
2939
|
}
|
|
2420
2940
|
} catch {
|
|
2421
2941
|
}
|
|
2942
|
+
jsonResults.push({
|
|
2943
|
+
name: skillName,
|
|
2944
|
+
version: record.version,
|
|
2945
|
+
agents: record.agents,
|
|
2946
|
+
checks: skillChecks,
|
|
2947
|
+
update_available: updateAvailable
|
|
2948
|
+
});
|
|
2422
2949
|
blank();
|
|
2423
2950
|
}
|
|
2951
|
+
if (options.json || getJsonMode()) {
|
|
2952
|
+
console.log(JSON.stringify({ ok: totalIssues === 0, skills: jsonResults, issues: totalIssues, updates: totalUpdates }, null, 2));
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2424
2955
|
if (totalIssues === 0 && totalUpdates === 0) {
|
|
2425
|
-
success("All checks passed");
|
|
2956
|
+
success("All checks passed.");
|
|
2426
2957
|
} else {
|
|
2427
2958
|
const parts = [];
|
|
2428
2959
|
if (totalIssues > 0) {
|
|
@@ -2434,6 +2965,12 @@ async function runDoctor(options) {
|
|
|
2434
2965
|
);
|
|
2435
2966
|
}
|
|
2436
2967
|
info(`Summary: ${parts.join(", ")}`);
|
|
2968
|
+
if (totalIssues > 0) {
|
|
2969
|
+
info("Fix issues by re-installing: shipables install <skill> --<agent>");
|
|
2970
|
+
}
|
|
2971
|
+
if (totalUpdates > 0) {
|
|
2972
|
+
info("Apply updates with: shipables update");
|
|
2973
|
+
}
|
|
2437
2974
|
}
|
|
2438
2975
|
blank();
|
|
2439
2976
|
}
|
|
@@ -2441,15 +2978,29 @@ async function runDoctor(options) {
|
|
|
2441
2978
|
// src/commands/config.ts
|
|
2442
2979
|
init_config();
|
|
2443
2980
|
init_output();
|
|
2981
|
+
init_errors();
|
|
2444
2982
|
import { Command as Command12 } from "commander";
|
|
2445
|
-
import
|
|
2983
|
+
import chalk11 from "chalk";
|
|
2446
2984
|
var VALID_KEYS = ["registry", "defaultAgents", "scope"];
|
|
2447
2985
|
function createConfigCommand() {
|
|
2448
|
-
const cmd = new Command12("config").description(
|
|
2449
|
-
|
|
2986
|
+
const cmd = new Command12("config").description(
|
|
2987
|
+
"Manage CLI configuration. Config is stored in ~/.shipables/config.json. Valid keys: registry, defaultAgents, scope."
|
|
2988
|
+
).addHelpText("after", `
|
|
2989
|
+
Examples:
|
|
2990
|
+
shipables config list Show all config values
|
|
2991
|
+
shipables config get registry Get the registry URL
|
|
2992
|
+
shipables config set registry https://... Set a custom registry URL
|
|
2993
|
+
shipables config set defaultAgents claude,cursor Set default agents
|
|
2994
|
+
shipables config delete scope Remove a config value
|
|
2995
|
+
`);
|
|
2996
|
+
cmd.command("set <key> <value>").description("Set a config value (valid keys: registry, defaultAgents, scope)").action(async (key, value) => {
|
|
2450
2997
|
try {
|
|
2451
2998
|
await runConfigSet(key, value);
|
|
2452
2999
|
} catch (err) {
|
|
3000
|
+
if (err instanceof CliError) {
|
|
3001
|
+
errorWithFix(err.message, err.fix || "");
|
|
3002
|
+
process.exit(err.exitCode);
|
|
3003
|
+
}
|
|
2453
3004
|
error(err instanceof Error ? err.message : String(err));
|
|
2454
3005
|
process.exit(1);
|
|
2455
3006
|
}
|
|
@@ -2458,11 +3009,15 @@ function createConfigCommand() {
|
|
|
2458
3009
|
try {
|
|
2459
3010
|
await runConfigGet(key);
|
|
2460
3011
|
} catch (err) {
|
|
3012
|
+
if (err instanceof CliError) {
|
|
3013
|
+
errorWithFix(err.message, err.fix || "");
|
|
3014
|
+
process.exit(err.exitCode);
|
|
3015
|
+
}
|
|
2461
3016
|
error(err instanceof Error ? err.message : String(err));
|
|
2462
3017
|
process.exit(1);
|
|
2463
3018
|
}
|
|
2464
3019
|
});
|
|
2465
|
-
cmd.command("list").description("Show all config").action(async () => {
|
|
3020
|
+
cmd.command("list").description("Show all config values").action(async () => {
|
|
2466
3021
|
try {
|
|
2467
3022
|
await runConfigList();
|
|
2468
3023
|
} catch (err) {
|
|
@@ -2474,6 +3029,10 @@ function createConfigCommand() {
|
|
|
2474
3029
|
try {
|
|
2475
3030
|
await runConfigDelete(key);
|
|
2476
3031
|
} catch (err) {
|
|
3032
|
+
if (err instanceof CliError) {
|
|
3033
|
+
errorWithFix(err.message, err.fix || "");
|
|
3034
|
+
process.exit(err.exitCode);
|
|
3035
|
+
}
|
|
2477
3036
|
error(err instanceof Error ? err.message : String(err));
|
|
2478
3037
|
process.exit(1);
|
|
2479
3038
|
}
|
|
@@ -2482,8 +3041,9 @@ function createConfigCommand() {
|
|
|
2482
3041
|
}
|
|
2483
3042
|
function validateKey(key) {
|
|
2484
3043
|
if (!VALID_KEYS.includes(key)) {
|
|
2485
|
-
throw
|
|
2486
|
-
`Invalid config key: ${key}
|
|
3044
|
+
throw usageError(
|
|
3045
|
+
`Invalid config key: '${key}'.`,
|
|
3046
|
+
`Valid keys: ${VALID_KEYS.join(", ")}. Example: shipables config set registry https://api.shipables.dev`
|
|
2487
3047
|
);
|
|
2488
3048
|
}
|
|
2489
3049
|
return key;
|
|
@@ -2516,15 +3076,15 @@ async function runConfigList() {
|
|
|
2516
3076
|
blank();
|
|
2517
3077
|
for (const key of VALID_KEYS) {
|
|
2518
3078
|
const value = config[key];
|
|
2519
|
-
const display = value === void 0 ?
|
|
2520
|
-
console.log(` ${
|
|
3079
|
+
const display = value === void 0 ? chalk11.dim("(not set)") : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
3080
|
+
console.log(` ${chalk11.bold(key)}: ${display}`);
|
|
2521
3081
|
}
|
|
2522
3082
|
if (config.username) {
|
|
2523
3083
|
console.log(
|
|
2524
|
-
` ${
|
|
3084
|
+
` ${chalk11.bold("auth")}: logged in as ${chalk11.green(config.username)}`
|
|
2525
3085
|
);
|
|
2526
3086
|
} else {
|
|
2527
|
-
console.log(` ${
|
|
3087
|
+
console.log(` ${chalk11.bold("auth")}: ${chalk11.dim("not logged in")}`);
|
|
2528
3088
|
}
|
|
2529
3089
|
blank();
|
|
2530
3090
|
}
|
|
@@ -2539,30 +3099,42 @@ async function runConfigDelete(key) {
|
|
|
2539
3099
|
// src/commands/stats.ts
|
|
2540
3100
|
init_client();
|
|
2541
3101
|
init_output();
|
|
3102
|
+
init_output();
|
|
3103
|
+
init_errors();
|
|
2542
3104
|
import { Command as Command13 } from "commander";
|
|
2543
|
-
import
|
|
2544
|
-
import ora8 from "ora";
|
|
3105
|
+
import chalk12 from "chalk";
|
|
2545
3106
|
function createStatsCommand() {
|
|
2546
|
-
return new Command13("stats").argument("<skill>", "Skill name").option("--period <period>", "Time period: week, month, year", "month").option("--json", "Output as JSON").description(
|
|
3107
|
+
return new Command13("stats").argument("<skill>", "Skill name (e.g., Chippers255/ai-commits, @org/my-skill)").option("--period <period>", "Time period: week, month, year", "month").option("--json", "Output as JSON").description(
|
|
3108
|
+
"Show download statistics for a skill with a daily breakdown chart."
|
|
3109
|
+
).addHelpText("after", `
|
|
3110
|
+
Examples:
|
|
3111
|
+
shipables stats Chippers255/ai-commits Show monthly download stats
|
|
3112
|
+
shipables stats Chippers255/ai-commits --period week Show weekly stats
|
|
3113
|
+
shipables stats @org/tool --json Get stats as JSON
|
|
3114
|
+
`).action(async (skillName, options) => {
|
|
2547
3115
|
try {
|
|
2548
3116
|
await runStats(skillName, options);
|
|
2549
3117
|
} catch (err) {
|
|
3118
|
+
if (err instanceof CliError) {
|
|
3119
|
+
errorWithFix(err.message, err.fix || "");
|
|
3120
|
+
process.exit(err.exitCode);
|
|
3121
|
+
}
|
|
2550
3122
|
error(err instanceof Error ? err.message : String(err));
|
|
2551
3123
|
process.exit(1);
|
|
2552
3124
|
}
|
|
2553
3125
|
});
|
|
2554
3126
|
}
|
|
2555
3127
|
async function runStats(skillName, options) {
|
|
2556
|
-
const
|
|
3128
|
+
const statsSpinner = spinner(`Fetching download stats for ${skillName}...`);
|
|
2557
3129
|
const stats = await registry.getDownloadStats(skillName, options.period);
|
|
2558
|
-
|
|
2559
|
-
if (options.json) {
|
|
3130
|
+
statsSpinner.stop();
|
|
3131
|
+
if (options.json || getJsonMode()) {
|
|
2560
3132
|
console.log(JSON.stringify(stats, null, 2));
|
|
2561
3133
|
return;
|
|
2562
3134
|
}
|
|
2563
3135
|
blank();
|
|
2564
3136
|
console.log(
|
|
2565
|
-
` ${
|
|
3137
|
+
` ${chalk12.bold(stats.skill)} \u2014 ${stats.period} downloads: ${chalk12.bold(formatNumber(stats.total))}`
|
|
2566
3138
|
);
|
|
2567
3139
|
blank();
|
|
2568
3140
|
if (stats.daily.length > 0) {
|
|
@@ -2572,7 +3144,7 @@ async function runStats(skillName, options) {
|
|
|
2572
3144
|
const bar = "\u2588".repeat(Math.round(day.count / maxCount * barWidth));
|
|
2573
3145
|
const date = day.date.slice(5);
|
|
2574
3146
|
const count = String(day.count).padStart(6);
|
|
2575
|
-
console.log(` ${
|
|
3147
|
+
console.log(` ${chalk12.dim(date)} ${count} ${chalk12.cyan(bar)}`);
|
|
2576
3148
|
}
|
|
2577
3149
|
}
|
|
2578
3150
|
blank();
|
|
@@ -2582,14 +3154,26 @@ async function runStats(skillName, options) {
|
|
|
2582
3154
|
init_client();
|
|
2583
3155
|
init_config();
|
|
2584
3156
|
init_output();
|
|
3157
|
+
init_output();
|
|
3158
|
+
init_errors();
|
|
2585
3159
|
import { Command as Command14 } from "commander";
|
|
2586
|
-
import
|
|
2587
|
-
import ora9 from "ora";
|
|
3160
|
+
import chalk13 from "chalk";
|
|
2588
3161
|
function createProfileCommand() {
|
|
2589
|
-
return new Command14("profile").argument("[username]", "Username to look up (defaults to current user)").option("--json", "Output as JSON").description(
|
|
3162
|
+
return new Command14("profile").argument("[username]", "Username to look up (defaults to current user)").option("--json", "Output as JSON").description(
|
|
3163
|
+
"Show user profile and published skills. Defaults to the currently logged-in user if no username is provided."
|
|
3164
|
+
).addHelpText("after", `
|
|
3165
|
+
Examples:
|
|
3166
|
+
shipables profile Show your profile
|
|
3167
|
+
shipables profile johndoe Show another user's profile
|
|
3168
|
+
shipables profile --json Get your profile as JSON
|
|
3169
|
+
`).action(async (username, options) => {
|
|
2590
3170
|
try {
|
|
2591
3171
|
await runProfile(username, options);
|
|
2592
3172
|
} catch (err) {
|
|
3173
|
+
if (err instanceof CliError) {
|
|
3174
|
+
errorWithFix(err.message, err.fix || "");
|
|
3175
|
+
process.exit(err.exitCode);
|
|
3176
|
+
}
|
|
2593
3177
|
error(err instanceof Error ? err.message : String(err));
|
|
2594
3178
|
process.exit(1);
|
|
2595
3179
|
}
|
|
@@ -2603,29 +3187,30 @@ async function runProfile(username, options) {
|
|
|
2603
3187
|
} else {
|
|
2604
3188
|
const token = await getToken();
|
|
2605
3189
|
if (!token) {
|
|
2606
|
-
throw
|
|
2607
|
-
"No username provided and not logged in.
|
|
3190
|
+
throw authError(
|
|
3191
|
+
"No username provided and not logged in.",
|
|
3192
|
+
"Run `shipables login` first, or provide a username: shipables profile <username>"
|
|
2608
3193
|
);
|
|
2609
3194
|
}
|
|
2610
3195
|
const me = await registry.getMe();
|
|
2611
3196
|
username = me.username;
|
|
2612
3197
|
}
|
|
2613
3198
|
}
|
|
2614
|
-
const
|
|
3199
|
+
const profileSpinner = spinner(`Fetching profile for ${username}...`);
|
|
2615
3200
|
const [profile, skillsResult] = await Promise.all([
|
|
2616
3201
|
registry.getUserProfile(username),
|
|
2617
3202
|
registry.getUserSkills(username)
|
|
2618
3203
|
]);
|
|
2619
|
-
|
|
2620
|
-
if (options.json) {
|
|
3204
|
+
profileSpinner.stop();
|
|
3205
|
+
if (options.json || getJsonMode()) {
|
|
2621
3206
|
console.log(JSON.stringify({ profile, skills: skillsResult.skills }, null, 2));
|
|
2622
3207
|
return;
|
|
2623
3208
|
}
|
|
2624
3209
|
blank();
|
|
2625
3210
|
const displayName = profile.display_name || profile.username;
|
|
2626
|
-
console.log(` ${
|
|
3211
|
+
console.log(` ${chalk13.bold(displayName)}`);
|
|
2627
3212
|
if (profile.display_name && profile.display_name !== profile.username) {
|
|
2628
|
-
console.log(` ${
|
|
3213
|
+
console.log(` ${chalk13.dim(`@${profile.username}`)}`);
|
|
2629
3214
|
}
|
|
2630
3215
|
blank();
|
|
2631
3216
|
info(`Skills published: ${profile.skills_count}`);
|
|
@@ -2651,8 +3236,32 @@ function truncate2(s, max) {
|
|
|
2651
3236
|
}
|
|
2652
3237
|
|
|
2653
3238
|
// src/index.ts
|
|
3239
|
+
init_output();
|
|
2654
3240
|
var program = new Command15();
|
|
2655
|
-
program.name("shipables").description(
|
|
3241
|
+
program.name("shipables").description(
|
|
3242
|
+
"CLI for installing, managing, and publishing AI agent skills. Works with Claude Code, Cursor, Codex, Copilot, Gemini, and Cline. Skills follow the Agent Skills open standard (https://agentskills.io)."
|
|
3243
|
+
).version("0.1.0").option("--json", "Output structured JSON (supported by most commands)").on("option:json", () => {
|
|
3244
|
+
setJsonMode(true);
|
|
3245
|
+
}).addHelpText("after", `
|
|
3246
|
+
Examples:
|
|
3247
|
+
shipables search "database" Search for database-related skills
|
|
3248
|
+
shipables install Chippers255/ai-commits --claude Install a skill for Claude Code
|
|
3249
|
+
shipables install @org/tool --all Install a scoped skill for all agents
|
|
3250
|
+
shipables list List skills installed in current project
|
|
3251
|
+
shipables info Chippers255/ai-commits --json Get skill details as JSON
|
|
3252
|
+
shipables init --name my-skill --description "A skill for X"
|
|
3253
|
+
Scaffold a new skill (non-interactive)
|
|
3254
|
+
shipables publish --yes --scope @myorg Publish without prompts
|
|
3255
|
+
shipables login --token cbl_xxxxx Non-interactive auth for CI/AI agents
|
|
3256
|
+
|
|
3257
|
+
Non-interactive usage (CI/AI agents):
|
|
3258
|
+
Most commands work non-interactively. Use flags instead of prompts:
|
|
3259
|
+
- install: --claude/--cursor/--all for agents, --env KEY=VAL for MCP vars
|
|
3260
|
+
- init: --name and --description skip all prompts
|
|
3261
|
+
- publish: --yes skips confirmation, --scope sets org
|
|
3262
|
+
- login: --token skips browser OAuth
|
|
3263
|
+
- All commands: --json for structured output
|
|
3264
|
+
`);
|
|
2656
3265
|
program.addCommand(createInstallCommand());
|
|
2657
3266
|
program.addCommand(createUninstallCommand());
|
|
2658
3267
|
program.addCommand(createSearchCommand());
|