@senso-ai/shipables 0.1.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/dist/index.js ADDED
@@ -0,0 +1,2672 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/lib/config.ts
13
+ import { readFile, writeFile, mkdir } from "fs/promises";
14
+ import { homedir } from "os";
15
+ import { join } from "path";
16
+ async function ensureDir(dir) {
17
+ await mkdir(dir, { recursive: true });
18
+ }
19
+ async function loadConfig() {
20
+ try {
21
+ const raw = await readFile(CONFIG_FILE, "utf-8");
22
+ return JSON.parse(raw);
23
+ } catch {
24
+ return { registry: DEFAULT_REGISTRY };
25
+ }
26
+ }
27
+ async function saveConfig(config) {
28
+ await ensureDir(CONFIG_DIR);
29
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
30
+ }
31
+ async function getRegistry() {
32
+ const config = await loadConfig();
33
+ return config.registry || DEFAULT_REGISTRY;
34
+ }
35
+ async function getToken() {
36
+ const config = await loadConfig();
37
+ return config.token;
38
+ }
39
+ async function loadInstalled() {
40
+ try {
41
+ const raw = await readFile(INSTALLED_FILE, "utf-8");
42
+ return JSON.parse(raw);
43
+ } catch {
44
+ return { version: 1, installations: {} };
45
+ }
46
+ }
47
+ async function saveInstalled(data) {
48
+ await ensureDir(CONFIG_DIR);
49
+ await writeFile(
50
+ INSTALLED_FILE,
51
+ JSON.stringify(data, null, 2) + "\n",
52
+ "utf-8"
53
+ );
54
+ }
55
+ async function addInstallation(projectPath, skillName, record) {
56
+ const data = await loadInstalled();
57
+ if (!data.installations[projectPath]) {
58
+ data.installations[projectPath] = {};
59
+ }
60
+ data.installations[projectPath][skillName] = record;
61
+ await saveInstalled(data);
62
+ }
63
+ async function removeInstallation(projectPath, skillName) {
64
+ const data = await loadInstalled();
65
+ if (data.installations[projectPath]) {
66
+ delete data.installations[projectPath][skillName];
67
+ if (Object.keys(data.installations[projectPath]).length === 0) {
68
+ delete data.installations[projectPath];
69
+ }
70
+ }
71
+ await saveInstalled(data);
72
+ }
73
+ async function updateInstallation(projectPath, skillName, record) {
74
+ const data = await loadInstalled();
75
+ if (data.installations[projectPath]) {
76
+ data.installations[projectPath][skillName] = record;
77
+ await saveInstalled(data);
78
+ }
79
+ }
80
+ async function getInstallation(projectPath, skillName) {
81
+ const data = await loadInstalled();
82
+ return data.installations[projectPath]?.[skillName];
83
+ }
84
+ async function getProjectInstallations(projectPath) {
85
+ const data = await loadInstalled();
86
+ return data.installations[projectPath] || {};
87
+ }
88
+ async function ensureCacheDir() {
89
+ await ensureDir(CACHE_DIR);
90
+ return CACHE_DIR;
91
+ }
92
+ var CONFIG_DIR, CONFIG_FILE, INSTALLED_FILE, CACHE_DIR, DEFAULT_REGISTRY;
93
+ var init_config = __esm({
94
+ "src/lib/config.ts"() {
95
+ "use strict";
96
+ CONFIG_DIR = join(homedir(), ".shipables");
97
+ CONFIG_FILE = join(CONFIG_DIR, "config.json");
98
+ INSTALLED_FILE = join(CONFIG_DIR, "installed.json");
99
+ CACHE_DIR = join(CONFIG_DIR, "cache");
100
+ DEFAULT_REGISTRY = "https://api.shipables.dev";
101
+ }
102
+ });
103
+
104
+ // src/registry/client.ts
105
+ import { createReadStream } from "fs";
106
+ import { readFile as readFile2 } from "fs/promises";
107
+ import { basename, join as join2, dirname } from "path";
108
+ import { fileURLToPath } from "url";
109
+ async function getCliVersion() {
110
+ if (cachedVersion) return cachedVersion;
111
+ try {
112
+ const pkgPath = join2(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
113
+ const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
114
+ cachedVersion = pkg.version || "0.0.0";
115
+ } catch {
116
+ cachedVersion = "0.0.0";
117
+ }
118
+ return cachedVersion;
119
+ }
120
+ var cachedVersion, RegistryClient, registry;
121
+ var init_client = __esm({
122
+ "src/registry/client.ts"() {
123
+ "use strict";
124
+ init_config();
125
+ cachedVersion = null;
126
+ RegistryClient = class {
127
+ registryUrl = null;
128
+ token;
129
+ async getBaseUrl() {
130
+ if (!this.registryUrl) {
131
+ this.registryUrl = await getRegistry();
132
+ }
133
+ return this.registryUrl;
134
+ }
135
+ async getHeaders(auth = false) {
136
+ const version = await getCliVersion();
137
+ const headers = {
138
+ "User-Agent": `shipables-cli/${version}`
139
+ };
140
+ if (auth) {
141
+ if (!this.token) {
142
+ this.token = await getToken();
143
+ }
144
+ if (this.token) {
145
+ headers["Authorization"] = `Bearer ${this.token}`;
146
+ }
147
+ }
148
+ return headers;
149
+ }
150
+ encodeFullName(fullName) {
151
+ if (fullName.startsWith("@")) {
152
+ const withoutAt = fullName.slice(1);
153
+ const [scope, name] = withoutAt.split("/");
154
+ return `${encodeURIComponent(scope)}/${encodeURIComponent(name)}`;
155
+ }
156
+ if (fullName.includes("/")) {
157
+ const [scope, name] = fullName.split("/");
158
+ return `${encodeURIComponent(scope)}/${encodeURIComponent(name)}`;
159
+ }
160
+ return encodeURIComponent(fullName);
161
+ }
162
+ async fetchJson(url, init) {
163
+ const base = await this.getBaseUrl();
164
+ try {
165
+ return await fetch(url, init);
166
+ } catch (err) {
167
+ if (err instanceof TypeError && err.message === "fetch failed") {
168
+ throw new Error(
169
+ `Could not connect to registry at ${base}. Check your internet connection or registry URL.`
170
+ );
171
+ }
172
+ throw err;
173
+ }
174
+ }
175
+ async search(params) {
176
+ const base = await this.getBaseUrl();
177
+ const url = new URL(`${base}/api/v1/skills`);
178
+ if (params.q) url.searchParams.set("q", params.q);
179
+ if (params.category) url.searchParams.set("category", params.category);
180
+ if (params.agent) url.searchParams.set("agent", params.agent);
181
+ if (params.scope) url.searchParams.set("scope", params.scope);
182
+ if (params.sort) url.searchParams.set("sort", params.sort);
183
+ if (params.limit) url.searchParams.set("per_page", String(params.limit));
184
+ if (params.page) url.searchParams.set("page", String(params.page));
185
+ const resp = await this.fetchJson(url.toString(), {
186
+ headers: await this.getHeaders()
187
+ });
188
+ if (!resp.ok) {
189
+ throw new Error(`Search failed: ${resp.status} ${resp.statusText}`);
190
+ }
191
+ return await resp.json();
192
+ }
193
+ async getSkillDetail(fullName) {
194
+ const base = await this.getBaseUrl();
195
+ const encoded = this.encodeFullName(fullName);
196
+ const resp = await this.fetchJson(`${base}/api/v1/skills/${encoded}`, {
197
+ headers: await this.getHeaders()
198
+ });
199
+ if (!resp.ok) {
200
+ if (resp.status === 404) {
201
+ throw new Error(`Skill not found: ${fullName}`);
202
+ }
203
+ throw new Error(
204
+ `Failed to get skill detail: ${resp.status} ${resp.statusText}`
205
+ );
206
+ }
207
+ return await resp.json();
208
+ }
209
+ async getVersionDetail(fullName, version) {
210
+ const base = await this.getBaseUrl();
211
+ const encoded = this.encodeFullName(fullName);
212
+ const resp = await this.fetchJson(
213
+ `${base}/api/v1/skills/${encoded}/versions/${encodeURIComponent(version)}`,
214
+ { headers: await this.getHeaders() }
215
+ );
216
+ if (!resp.ok) {
217
+ if (resp.status === 404) {
218
+ throw new Error(`Version not found: ${fullName}@${version}`);
219
+ }
220
+ throw new Error(
221
+ `Failed to get version: ${resp.status} ${resp.statusText}`
222
+ );
223
+ }
224
+ return await resp.json();
225
+ }
226
+ async downloadTarball(fullName, filename) {
227
+ const base = await this.getBaseUrl();
228
+ const encoded = this.encodeFullName(fullName);
229
+ const resp = await this.fetchJson(
230
+ `${base}/api/v1/skills/${encoded}/-/${encodeURIComponent(filename)}`,
231
+ { headers: await this.getHeaders() }
232
+ );
233
+ if (!resp.ok) {
234
+ throw new Error(
235
+ `Failed to download tarball: ${resp.status} ${resp.statusText}`
236
+ );
237
+ }
238
+ const integrity = resp.headers.get("X-Integrity") || "";
239
+ const arrayBuf = await resp.arrayBuffer();
240
+ return { data: Buffer.from(arrayBuf), integrity };
241
+ }
242
+ async publishWithName(fullName, tarballPath) {
243
+ const base = await this.getBaseUrl();
244
+ const headers = await this.getHeaders(true);
245
+ if (!headers["Authorization"]) {
246
+ throw new Error(
247
+ "Not authenticated. Run `shipables login` first."
248
+ );
249
+ }
250
+ const fileStream = createReadStream(tarballPath);
251
+ const chunks = [];
252
+ for await (const chunk of fileStream) {
253
+ chunks.push(chunk);
254
+ }
255
+ const fileBuffer = Buffer.concat(chunks);
256
+ const fileName = basename(tarballPath);
257
+ const formData = new FormData();
258
+ const blob = new Blob([fileBuffer], { type: "application/gzip" });
259
+ formData.append("tarball", blob, fileName);
260
+ const encoded = this.encodeFullName(fullName);
261
+ const resp = await this.fetchJson(`${base}/api/v1/skills/${encoded}`, {
262
+ method: "PUT",
263
+ headers,
264
+ body: formData
265
+ });
266
+ if (!resp.ok) {
267
+ const body = await resp.text();
268
+ let message = `Publish failed: ${resp.status} ${resp.statusText}`;
269
+ try {
270
+ const err = JSON.parse(body);
271
+ if (err.error?.message) message = err.error.message;
272
+ } catch {
273
+ }
274
+ throw new Error(message);
275
+ }
276
+ return await resp.json();
277
+ }
278
+ async unpublishVersion(fullName, version) {
279
+ const base = await this.getBaseUrl();
280
+ const encoded = this.encodeFullName(fullName);
281
+ const resp = await this.fetchJson(
282
+ `${base}/api/v1/skills/${encoded}/versions/${encodeURIComponent(version)}`,
283
+ {
284
+ method: "DELETE",
285
+ headers: await this.getHeaders(true)
286
+ }
287
+ );
288
+ if (!resp.ok) {
289
+ if (resp.status === 404) {
290
+ throw new Error(`Version not found: ${fullName}@${version}`);
291
+ }
292
+ const body = await resp.text();
293
+ let message = `Unpublish failed: ${resp.status} ${resp.statusText}`;
294
+ try {
295
+ const err = JSON.parse(body);
296
+ if (err.error?.message) message = err.error.message;
297
+ } catch {
298
+ }
299
+ throw new Error(message);
300
+ }
301
+ }
302
+ async getDownloadStats(fullName, period = "month") {
303
+ const base = await this.getBaseUrl();
304
+ const encoded = this.encodeFullName(fullName);
305
+ const resp = await this.fetchJson(
306
+ `${base}/api/v1/skills/${encoded}/downloads?period=${encodeURIComponent(period)}`,
307
+ { headers: await this.getHeaders() }
308
+ );
309
+ if (!resp.ok) {
310
+ if (resp.status === 404) {
311
+ throw new Error(`Skill not found: ${fullName}`);
312
+ }
313
+ throw new Error(
314
+ `Failed to get download stats: ${resp.status} ${resp.statusText}`
315
+ );
316
+ }
317
+ return await resp.json();
318
+ }
319
+ async getUserProfile(username) {
320
+ const base = await this.getBaseUrl();
321
+ const resp = await this.fetchJson(
322
+ `${base}/api/v1/users/${encodeURIComponent(username)}`,
323
+ { headers: await this.getHeaders() }
324
+ );
325
+ if (!resp.ok) {
326
+ if (resp.status === 404) {
327
+ throw new Error(`User not found: ${username}`);
328
+ }
329
+ throw new Error(
330
+ `Failed to get user profile: ${resp.status} ${resp.statusText}`
331
+ );
332
+ }
333
+ return await resp.json();
334
+ }
335
+ async getUserSkills(username) {
336
+ const base = await this.getBaseUrl();
337
+ const resp = await this.fetchJson(
338
+ `${base}/api/v1/users/${encodeURIComponent(username)}/skills`,
339
+ { headers: await this.getHeaders() }
340
+ );
341
+ if (!resp.ok) {
342
+ throw new Error(
343
+ `Failed to get user skills: ${resp.status} ${resp.statusText}`
344
+ );
345
+ }
346
+ return await resp.json();
347
+ }
348
+ async getMe() {
349
+ const base = await this.getBaseUrl();
350
+ const resp = await this.fetchJson(`${base}/api/v1/auth/me`, {
351
+ headers: await this.getHeaders(true)
352
+ });
353
+ if (!resp.ok) {
354
+ if (resp.status === 401) {
355
+ throw new Error("Not authenticated. Run `shipables login` first.");
356
+ }
357
+ throw new Error(`Failed to get profile: ${resp.status}`);
358
+ }
359
+ return await resp.json();
360
+ }
361
+ };
362
+ registry = new RegistryClient();
363
+ }
364
+ });
365
+
366
+ // src/adapters/base.ts
367
+ import { access, readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, stat } from "fs/promises";
368
+ import { join as join3 } from "path";
369
+ import { execSync } from "child_process";
370
+ function expandHome(p) {
371
+ if (p.startsWith("~/")) {
372
+ return join3(process.env.HOME || "", p.slice(2));
373
+ }
374
+ return p;
375
+ }
376
+ function binaryExists(name) {
377
+ try {
378
+ execSync(`which ${name}`, { stdio: "ignore" });
379
+ return true;
380
+ } catch {
381
+ return false;
382
+ }
383
+ }
384
+ async function dirExists(path) {
385
+ try {
386
+ const s = await stat(expandHome(path));
387
+ return s.isDirectory();
388
+ } catch {
389
+ return false;
390
+ }
391
+ }
392
+ async function fileExists(path) {
393
+ try {
394
+ await access(path);
395
+ return true;
396
+ } catch {
397
+ return false;
398
+ }
399
+ }
400
+ function createAdapter(config) {
401
+ return {
402
+ name: config.name,
403
+ displayName: config.displayName,
404
+ async detect() {
405
+ if (config.detectBinaries) {
406
+ for (const bin of config.detectBinaries) {
407
+ if (binaryExists(bin)) return true;
408
+ }
409
+ }
410
+ if (config.detectDirs) {
411
+ for (const dir of config.detectDirs) {
412
+ if (await dirExists(dir)) return true;
413
+ }
414
+ }
415
+ return false;
416
+ },
417
+ getSkillDir(skillName, scope) {
418
+ const template = scope === "global" ? config.globalSkillDir : config.projectSkillDir;
419
+ return expandHome(template.replace("{name}", skillName));
420
+ },
421
+ getMcpConfigPath(scope) {
422
+ const path = scope === "global" ? config.globalMcpConfig : config.projectMcpConfig;
423
+ return path ? expandHome(path) : null;
424
+ },
425
+ async writeMcpConfig(configPath, serverName, serverConfig) {
426
+ const fullPath = expandHome(configPath);
427
+ let existing = {};
428
+ if (await fileExists(fullPath)) {
429
+ try {
430
+ const raw = await readFile3(fullPath, "utf-8");
431
+ existing = JSON.parse(raw);
432
+ } catch {
433
+ }
434
+ } else {
435
+ const dir = join3(fullPath, "..");
436
+ await mkdir2(dir, { recursive: true });
437
+ }
438
+ const key = config.mcpServersKey;
439
+ if (!existing[key] || typeof existing[key] !== "object") {
440
+ existing[key] = {};
441
+ }
442
+ existing[key][serverName] = serverConfig;
443
+ await writeFile2(fullPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
444
+ },
445
+ async removeMcpConfig(configPath, serverName) {
446
+ const fullPath = expandHome(configPath);
447
+ if (!await fileExists(fullPath)) return;
448
+ try {
449
+ const raw = await readFile3(fullPath, "utf-8");
450
+ const existing = JSON.parse(raw);
451
+ const key = config.mcpServersKey;
452
+ const servers = existing[key];
453
+ if (servers && serverName in servers) {
454
+ delete servers[serverName];
455
+ await writeFile2(
456
+ fullPath,
457
+ JSON.stringify(existing, null, 2) + "\n",
458
+ "utf-8"
459
+ );
460
+ }
461
+ } catch {
462
+ }
463
+ },
464
+ async validate(skillName, skillDir, mcpConfigPath) {
465
+ const checks = [];
466
+ const skillDirFull = expandHome(skillDir);
467
+ if (await dirExists(skillDirFull)) {
468
+ checks.push({
469
+ label: `Skill directory present: ${skillDir}`,
470
+ status: "pass"
471
+ });
472
+ if (await fileExists(join3(skillDirFull, "SKILL.md"))) {
473
+ checks.push({ label: "SKILL.md present", status: "pass" });
474
+ } else {
475
+ checks.push({
476
+ label: "SKILL.md missing",
477
+ status: "fail",
478
+ message: `Expected at ${join3(skillDir, "SKILL.md")}`
479
+ });
480
+ }
481
+ } else {
482
+ checks.push({
483
+ label: `Skill directory missing: ${skillDir}`,
484
+ status: "fail"
485
+ });
486
+ }
487
+ if (mcpConfigPath) {
488
+ const mcpFull = expandHome(mcpConfigPath);
489
+ if (await fileExists(mcpFull)) {
490
+ try {
491
+ const raw = await readFile3(mcpFull, "utf-8");
492
+ const parsed = JSON.parse(raw);
493
+ const key = config.mcpServersKey;
494
+ const servers = parsed[key];
495
+ if (servers && skillName in servers) {
496
+ checks.push({
497
+ label: `MCP server config present in ${mcpConfigPath}`,
498
+ status: "pass"
499
+ });
500
+ } else {
501
+ checks.push({
502
+ label: `MCP server config missing in ${mcpConfigPath}`,
503
+ status: "warn",
504
+ message: "MCP server entry not found"
505
+ });
506
+ }
507
+ } catch {
508
+ checks.push({
509
+ label: `MCP config file invalid: ${mcpConfigPath}`,
510
+ status: "warn",
511
+ message: "Could not parse JSON"
512
+ });
513
+ }
514
+ }
515
+ }
516
+ const healthy = checks.every((c) => c.status !== "fail");
517
+ return { healthy, checks };
518
+ }
519
+ };
520
+ }
521
+ var init_base = __esm({
522
+ "src/adapters/base.ts"() {
523
+ "use strict";
524
+ }
525
+ });
526
+
527
+ // src/adapters/claude.ts
528
+ var claudeAdapter;
529
+ var init_claude = __esm({
530
+ "src/adapters/claude.ts"() {
531
+ "use strict";
532
+ init_base();
533
+ claudeAdapter = createAdapter({
534
+ name: "claude",
535
+ displayName: "Claude Code",
536
+ detectBinaries: ["claude"],
537
+ detectDirs: ["~/.claude"],
538
+ projectSkillDir: ".claude/skills/{name}",
539
+ globalSkillDir: "~/.claude/skills/{name}",
540
+ projectMcpConfig: ".claude/settings.json",
541
+ globalMcpConfig: "~/.claude/settings.json",
542
+ mcpServersKey: "mcpServers"
543
+ });
544
+ }
545
+ });
546
+
547
+ // src/adapters/cursor.ts
548
+ var cursorAdapter;
549
+ var init_cursor = __esm({
550
+ "src/adapters/cursor.ts"() {
551
+ "use strict";
552
+ init_base();
553
+ cursorAdapter = createAdapter({
554
+ name: "cursor",
555
+ displayName: "Cursor",
556
+ detectDirs: [".cursor", "~/.cursor"],
557
+ projectSkillDir: ".cursor/skills/{name}",
558
+ globalSkillDir: "~/.cursor/skills/{name}",
559
+ projectMcpConfig: ".cursor/mcp.json",
560
+ globalMcpConfig: "~/.cursor/mcp.json",
561
+ mcpServersKey: "mcpServers"
562
+ });
563
+ }
564
+ });
565
+
566
+ // src/adapters/codex.ts
567
+ var codexAdapter;
568
+ var init_codex = __esm({
569
+ "src/adapters/codex.ts"() {
570
+ "use strict";
571
+ init_base();
572
+ codexAdapter = createAdapter({
573
+ name: "codex",
574
+ displayName: "Codex CLI",
575
+ detectBinaries: ["codex"],
576
+ detectDirs: ["~/.codex"],
577
+ projectSkillDir: ".agents/skills/{name}",
578
+ globalSkillDir: "~/.agents/skills/{name}",
579
+ projectMcpConfig: ".codex/mcp.json",
580
+ globalMcpConfig: "~/.codex/mcp.json",
581
+ mcpServersKey: "mcpServers"
582
+ });
583
+ }
584
+ });
585
+
586
+ // src/adapters/copilot.ts
587
+ var copilotAdapter;
588
+ var init_copilot = __esm({
589
+ "src/adapters/copilot.ts"() {
590
+ "use strict";
591
+ init_base();
592
+ copilotAdapter = createAdapter({
593
+ name: "copilot",
594
+ displayName: "VS Code / Copilot",
595
+ detectDirs: [".vscode"],
596
+ projectSkillDir: ".agents/skills/{name}",
597
+ globalSkillDir: "~/.agents/skills/{name}",
598
+ projectMcpConfig: ".vscode/mcp.json",
599
+ globalMcpConfig: null,
600
+ mcpServersKey: "servers"
601
+ });
602
+ }
603
+ });
604
+
605
+ // src/adapters/gemini.ts
606
+ var geminiAdapter;
607
+ var init_gemini = __esm({
608
+ "src/adapters/gemini.ts"() {
609
+ "use strict";
610
+ init_base();
611
+ geminiAdapter = createAdapter({
612
+ name: "gemini",
613
+ displayName: "Gemini CLI",
614
+ detectBinaries: ["gemini"],
615
+ projectSkillDir: ".agents/skills/{name}",
616
+ globalSkillDir: "~/.agents/skills/{name}",
617
+ projectMcpConfig: ".gemini/settings.json",
618
+ globalMcpConfig: "~/.gemini/settings.json",
619
+ mcpServersKey: "mcpServers"
620
+ });
621
+ }
622
+ });
623
+
624
+ // src/adapters/cline.ts
625
+ var clineAdapter;
626
+ var init_cline = __esm({
627
+ "src/adapters/cline.ts"() {
628
+ "use strict";
629
+ init_base();
630
+ clineAdapter = createAdapter({
631
+ name: "cline",
632
+ displayName: "Cline",
633
+ detectDirs: [".cline"],
634
+ projectSkillDir: ".agents/skills/{name}",
635
+ globalSkillDir: "~/.agents/skills/{name}",
636
+ projectMcpConfig: ".cline/mcp_settings.json",
637
+ globalMcpConfig: "~/.cline/mcp_settings.json",
638
+ mcpServersKey: "mcpServers"
639
+ });
640
+ }
641
+ });
642
+
643
+ // src/adapters/index.ts
644
+ function getAdapter(name) {
645
+ return ALL_ADAPTERS.find((a) => a.name === name);
646
+ }
647
+ async function detectAgents() {
648
+ const detected = [];
649
+ for (const adapter of ALL_ADAPTERS) {
650
+ if (await adapter.detect()) {
651
+ detected.push(adapter);
652
+ }
653
+ }
654
+ return detected;
655
+ }
656
+ var ALL_ADAPTERS;
657
+ var init_adapters = __esm({
658
+ "src/adapters/index.ts"() {
659
+ "use strict";
660
+ init_claude();
661
+ init_cursor();
662
+ init_codex();
663
+ init_copilot();
664
+ init_gemini();
665
+ init_cline();
666
+ ALL_ADAPTERS = [
667
+ claudeAdapter,
668
+ cursorAdapter,
669
+ codexAdapter,
670
+ copilotAdapter,
671
+ geminiAdapter,
672
+ clineAdapter
673
+ ];
674
+ }
675
+ });
676
+
677
+ // src/lib/tarball.ts
678
+ import { writeFile as writeFile3, mkdtemp, readdir, stat as stat2 } from "fs/promises";
679
+ import { join as join4 } from "path";
680
+ import { tmpdir } from "os";
681
+ import * as tar from "tar";
682
+ async function extractTarball(tarballData, destDir) {
683
+ const extractDir = destDir || await mkdtemp(join4(tmpdir(), "shipables-"));
684
+ const tmpFile = join4(extractDir, "__tarball.tgz");
685
+ await writeFile3(tmpFile, tarballData);
686
+ await tar.extract({
687
+ file: tmpFile,
688
+ cwd: extractDir
689
+ });
690
+ const { unlink } = await import("fs/promises");
691
+ await unlink(tmpFile);
692
+ return extractDir;
693
+ }
694
+ async function createTarball(sourceDir, outputPath, ignorePatterns = []) {
695
+ const defaultIgnore = [
696
+ "node_modules",
697
+ ".git",
698
+ ".env",
699
+ ".DS_Store",
700
+ "*.tgz",
701
+ "__tarball.tgz"
702
+ ];
703
+ const allIgnore = [.../* @__PURE__ */ new Set([...defaultIgnore, ...ignorePatterns])];
704
+ const entries = await getEntries(sourceDir, allIgnore);
705
+ await tar.create(
706
+ {
707
+ gzip: true,
708
+ file: outputPath,
709
+ cwd: sourceDir
710
+ },
711
+ entries
712
+ );
713
+ }
714
+ async function getEntries(dir, ignorePatterns, prefix = "") {
715
+ const entries = [];
716
+ const items = await readdir(dir);
717
+ for (const item of items) {
718
+ const relativePath = prefix ? `${prefix}/${item}` : item;
719
+ if (shouldIgnore(item, relativePath, ignorePatterns)) {
720
+ continue;
721
+ }
722
+ const fullPath = join4(dir, item);
723
+ const itemStat = await stat2(fullPath);
724
+ if (itemStat.isDirectory()) {
725
+ const subEntries = await getEntries(fullPath, ignorePatterns, relativePath);
726
+ entries.push(...subEntries);
727
+ } else {
728
+ entries.push(relativePath);
729
+ }
730
+ }
731
+ return entries;
732
+ }
733
+ function shouldIgnore(name, relativePath, patterns) {
734
+ for (const pattern of patterns) {
735
+ if (pattern.startsWith("*.")) {
736
+ const ext = pattern.slice(1);
737
+ if (name.endsWith(ext)) return true;
738
+ } else if (name === pattern || relativePath === pattern) {
739
+ return true;
740
+ }
741
+ }
742
+ return false;
743
+ }
744
+ var init_tarball = __esm({
745
+ "src/lib/tarball.ts"() {
746
+ "use strict";
747
+ }
748
+ });
749
+
750
+ // src/lib/hash.ts
751
+ import { createHash } from "crypto";
752
+ import { readFile as readFile4 } from "fs/promises";
753
+ function sha512(data) {
754
+ return "sha512-" + createHash("sha512").update(data).digest("base64");
755
+ }
756
+ async function sha512File(filePath) {
757
+ const data = await readFile4(filePath);
758
+ return sha512(data);
759
+ }
760
+ function verifyIntegrity(data, expected) {
761
+ const actual = sha512(data);
762
+ return actual === expected;
763
+ }
764
+ var init_hash = __esm({
765
+ "src/lib/hash.ts"() {
766
+ "use strict";
767
+ }
768
+ });
769
+
770
+ // src/mcp/config.ts
771
+ function buildMcpServerConfig(server, envValues) {
772
+ const registry2 = server.registry || "npm";
773
+ let command;
774
+ let args;
775
+ if (registry2 === "npm") {
776
+ command = "npx";
777
+ args = ["-y", server.package];
778
+ if (server.version) {
779
+ args[1] = `${server.package}@${server.version}`;
780
+ }
781
+ } else if (registry2 === "pypi") {
782
+ command = "uvx";
783
+ args = [server.package];
784
+ if (server.version) {
785
+ args[0] = `${server.package}==${server.version}`;
786
+ }
787
+ } else {
788
+ command = server.package;
789
+ args = [];
790
+ }
791
+ if (server.args) {
792
+ for (const arg of server.args) {
793
+ args.push(resolveEnvVars(arg, envValues));
794
+ }
795
+ }
796
+ const env = {};
797
+ if (server.env) {
798
+ for (const [key, value] of Object.entries(server.env)) {
799
+ env[key] = resolveEnvVars(value, envValues);
800
+ }
801
+ }
802
+ return { command, args, env };
803
+ }
804
+ function resolveEnvVars(template, values) {
805
+ return template.replace(/\$\{([^}]+)\}/g, (_, name) => {
806
+ return values[name] || "";
807
+ });
808
+ }
809
+ async function writeMcpForAgents(agents, serverName, serverConfig, scope) {
810
+ const results = [];
811
+ for (const agent of agents) {
812
+ const configPath = agent.getMcpConfigPath(scope);
813
+ if (configPath) {
814
+ await agent.writeMcpConfig(configPath, serverName, serverConfig);
815
+ results.push({ agent: agent.name, configPath });
816
+ }
817
+ }
818
+ return results;
819
+ }
820
+ var init_config2 = __esm({
821
+ "src/mcp/config.ts"() {
822
+ "use strict";
823
+ }
824
+ });
825
+
826
+ // src/lib/output.ts
827
+ import chalk from "chalk";
828
+ function success(message) {
829
+ console.log(chalk.green(" \u2713 ") + message);
830
+ }
831
+ function error(message) {
832
+ console.error(chalk.red(" \u2717 ") + message);
833
+ }
834
+ function warn(message) {
835
+ console.log(chalk.yellow(" \u26A0 ") + message);
836
+ }
837
+ function info(message) {
838
+ console.log(chalk.cyan(" ") + message);
839
+ }
840
+ function blank() {
841
+ console.log();
842
+ }
843
+ function header(message) {
844
+ console.log();
845
+ console.log(chalk.bold(" " + message));
846
+ console.log();
847
+ }
848
+ function table(headers, rows, columnWidths) {
849
+ const widths = columnWidths || headers.map(
850
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || "").length))
851
+ );
852
+ const headerLine = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
853
+ console.log(chalk.dim(" " + headerLine));
854
+ for (const row of rows) {
855
+ const line = row.map((cell, i) => cell.padEnd(widths[i])).join(" ");
856
+ console.log(" " + line);
857
+ }
858
+ }
859
+ function formatBytes(bytes) {
860
+ if (bytes < 1024) return `${bytes} B`;
861
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
862
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
863
+ }
864
+ function formatNumber(n) {
865
+ return n.toLocaleString("en-US");
866
+ }
867
+ var init_output = __esm({
868
+ "src/lib/output.ts"() {
869
+ "use strict";
870
+ }
871
+ });
872
+
873
+ // src/commands/install.ts
874
+ var install_exports = {};
875
+ __export(install_exports, {
876
+ createInstallCommand: () => createInstallCommand
877
+ });
878
+ import { cp, mkdir as mkdir3 } from "fs/promises";
879
+ import { join as join5, resolve } from "path";
880
+ import { Command } from "commander";
881
+ import chalk2 from "chalk";
882
+ import ora from "ora";
883
+ import { checkbox } from "@inquirer/prompts";
884
+ import { input, password } from "@inquirer/prompts";
885
+ import { writeFile as writeFile4 } from "fs/promises";
886
+ function parseSkillArg(arg) {
887
+ if (arg.startsWith("@") || arg.includes("/")) {
888
+ const lastAt = arg.lastIndexOf("@");
889
+ const slashIdx = arg.indexOf("/");
890
+ if (lastAt > slashIdx) {
891
+ return {
892
+ name: arg.slice(0, lastAt),
893
+ version: arg.slice(lastAt + 1)
894
+ };
895
+ }
896
+ return { name: arg, version: "latest" };
897
+ }
898
+ const atIdx = arg.indexOf("@");
899
+ if (atIdx > 0) {
900
+ return { name: arg.slice(0, atIdx), version: arg.slice(atIdx + 1) };
901
+ }
902
+ return { name: arg, version: "latest" };
903
+ }
904
+ function getBareName(fullName) {
905
+ if (fullName.includes("/")) {
906
+ const parts = fullName.replace(/^@/, "").split("/");
907
+ return parts[parts.length - 1];
908
+ }
909
+ return fullName;
910
+ }
911
+ function createInstallCommand() {
912
+ return new Command("install").argument("<skill>", "Skill name, optionally with version (e.g., neo4j, neo4j@1.2.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("--registry <url>", "Use a custom registry URL").description("Install a skill from the registry").action(async (skillArg, options) => {
913
+ try {
914
+ await runInstall(skillArg, options);
915
+ } catch (err) {
916
+ error(
917
+ err instanceof Error ? err.message : String(err)
918
+ );
919
+ process.exit(1);
920
+ }
921
+ });
922
+ }
923
+ async function runInstall(skillArg, options) {
924
+ const { name: skillName, version: requestedVersion } = parseSkillArg(skillArg);
925
+ const bareName = getBareName(skillName);
926
+ const scope = options.global ? "global" : "project";
927
+ const spinner = ora(`Resolving ${skillName}@${requestedVersion}...`).start();
928
+ let versionDetail;
929
+ try {
930
+ versionDetail = await registry.getVersionDetail(skillName, requestedVersion);
931
+ spinner.succeed(
932
+ `Resolved ${skillName}@${requestedVersion} \u2192 ${chalk2.bold(versionDetail.version)}`
933
+ );
934
+ } catch (err) {
935
+ spinner.fail(`Failed to resolve ${skillName}@${requestedVersion}`);
936
+ throw err;
937
+ }
938
+ const filename = `${bareName}-${versionDetail.version}.tgz`;
939
+ const downloadSpinner = ora(
940
+ `Downloading ${filename} (${formatSize(versionDetail.size_bytes)})...`
941
+ ).start();
942
+ let tarballData;
943
+ let serverIntegrity;
944
+ try {
945
+ const result = await registry.downloadTarball(skillName, filename);
946
+ tarballData = result.data;
947
+ serverIntegrity = result.integrity;
948
+ downloadSpinner.succeed(`Downloaded ${filename}`);
949
+ } catch (err) {
950
+ downloadSpinner.fail(`Failed to download ${filename}`);
951
+ throw err;
952
+ }
953
+ if (serverIntegrity && versionDetail.tarball_sha512) {
954
+ const integritySpinner = ora("Verifying integrity...").start();
955
+ if (verifyIntegrity(tarballData, versionDetail.tarball_sha512)) {
956
+ integritySpinner.succeed("Integrity verified");
957
+ } else {
958
+ integritySpinner.fail("Integrity check failed \u2014 tarball may be corrupted");
959
+ throw new Error("SHA-512 integrity check failed");
960
+ }
961
+ }
962
+ const cacheDir = await ensureCacheDir();
963
+ await writeFile4(join5(cacheDir, filename), tarballData);
964
+ const extractDir = await extractTarball(tarballData);
965
+ const selectedAgents = await selectAgents(options);
966
+ if (selectedAgents.length === 0) {
967
+ throw new Error("No agents selected. Use --claude, --cursor, etc. or --all");
968
+ }
969
+ blank();
970
+ info(
971
+ `Detected agents: ${selectedAgents.map((a) => chalk2.bold(a.displayName)).join(", ")}`
972
+ );
973
+ header("Installing skill files:");
974
+ const skillDirs = {};
975
+ const mcpConfigs = {};
976
+ for (const agent of selectedAgents) {
977
+ const targetDir = agent.getSkillDir(bareName, scope);
978
+ const fullTargetDir = resolve(targetDir);
979
+ await mkdir3(fullTargetDir, { recursive: true });
980
+ const { readdirSync, statSync } = await import("fs");
981
+ const extractedItems = readdirSync(extractDir);
982
+ let sourceDir = extractDir;
983
+ if (extractedItems.length === 1) {
984
+ const singleItem = join5(extractDir, extractedItems[0]);
985
+ if (statSync(singleItem).isDirectory()) {
986
+ sourceDir = singleItem;
987
+ }
988
+ }
989
+ await cp(sourceDir, fullTargetDir, { recursive: true });
990
+ skillDirs[agent.name] = targetDir;
991
+ const installedFiles = await listFiles(fullTargetDir, targetDir);
992
+ for (const file of installedFiles) {
993
+ success(file);
994
+ }
995
+ }
996
+ const manifest = versionDetail.shipables_json;
997
+ const hasMcp = manifest?.mcp?.servers && manifest.mcp.servers.length > 0;
998
+ const skipMcp = options.mcp === false;
999
+ if (hasMcp && !skipMcp) {
1000
+ header("Configuring MCP server:");
1001
+ const envValues = {};
1002
+ const envSchemas = manifest.config?.env || [];
1003
+ if (envSchemas.length > 0) {
1004
+ for (const envVar of envSchemas) {
1005
+ const value = await promptEnvVar(envVar, options.yes);
1006
+ envValues[envVar.name] = value;
1007
+ }
1008
+ blank();
1009
+ }
1010
+ for (const server of manifest.mcp.servers) {
1011
+ const serverConfig = buildMcpServerConfig(server, envValues);
1012
+ const results = await writeMcpForAgents(
1013
+ selectedAgents,
1014
+ server.name,
1015
+ serverConfig,
1016
+ scope
1017
+ );
1018
+ for (const result of results) {
1019
+ mcpConfigs[result.agent] = result.configPath;
1020
+ success(
1021
+ `MCP server configured in ${result.configPath}`
1022
+ );
1023
+ }
1024
+ }
1025
+ }
1026
+ const projectPath = scope === "global" ? "__global__" : process.cwd();
1027
+ const record = {
1028
+ version: versionDetail.version,
1029
+ agents: selectedAgents.map((a) => a.name),
1030
+ installed_at: (/* @__PURE__ */ new Date()).toISOString(),
1031
+ skill_dirs: skillDirs,
1032
+ mcp_configs: mcpConfigs
1033
+ };
1034
+ await addInstallation(projectPath, skillName, record);
1035
+ blank();
1036
+ success(
1037
+ `Installed ${chalk2.bold(`${skillName}@${versionDetail.version}`)} for ${selectedAgents.map((a) => a.displayName).join(", ")}`
1038
+ );
1039
+ blank();
1040
+ const { rm: rm2 } = await import("fs/promises");
1041
+ await rm2(extractDir, { recursive: true, force: true });
1042
+ }
1043
+ async function selectAgents(options) {
1044
+ const flagged = [];
1045
+ const flagMap = {
1046
+ claude: options.claude,
1047
+ cursor: options.cursor,
1048
+ codex: options.codex,
1049
+ copilot: options.copilot,
1050
+ gemini: options.gemini,
1051
+ cline: options.cline
1052
+ };
1053
+ for (const [name, selected2] of Object.entries(flagMap)) {
1054
+ if (selected2) {
1055
+ const adapter = getAdapter(name);
1056
+ if (adapter) flagged.push(adapter);
1057
+ }
1058
+ }
1059
+ if (flagged.length > 0) return flagged;
1060
+ const detected = await detectAgents();
1061
+ if (options.all) {
1062
+ return detected.length > 0 ? detected : ALL_ADAPTERS;
1063
+ }
1064
+ if (detected.length === 0) {
1065
+ console.log(
1066
+ chalk2.yellow(
1067
+ "\n No agents auto-detected. Select which agents to install for:\n"
1068
+ )
1069
+ );
1070
+ const selected2 = await checkbox({
1071
+ message: "Select agents",
1072
+ choices: ALL_ADAPTERS.map((a) => ({
1073
+ name: a.displayName,
1074
+ value: a.name
1075
+ }))
1076
+ });
1077
+ return selected2.map((name) => getAdapter(name)).filter(Boolean);
1078
+ }
1079
+ if (detected.length === 1) {
1080
+ return detected;
1081
+ }
1082
+ const selected = await checkbox({
1083
+ message: "Multiple agents detected. Select which to install for:",
1084
+ choices: detected.map((a) => ({
1085
+ name: a.displayName,
1086
+ value: a.name,
1087
+ checked: true
1088
+ }))
1089
+ });
1090
+ return selected.map((name) => getAdapter(name)).filter(Boolean);
1091
+ }
1092
+ async function promptEnvVar(envVar, autoYes) {
1093
+ const label = [
1094
+ envVar.name,
1095
+ envVar.required ? "(required" : "(optional",
1096
+ envVar.secret ? ", secret)" : ")"
1097
+ ].join(" ");
1098
+ console.log(chalk2.dim(` ${label}: ${envVar.description}`));
1099
+ if (autoYes && envVar.default) {
1100
+ return envVar.default;
1101
+ }
1102
+ if (envVar.secret) {
1103
+ return await password({
1104
+ message: ` Enter value for ${envVar.name}:`
1105
+ });
1106
+ }
1107
+ return await input({
1108
+ message: ` Enter value for ${envVar.name}:`,
1109
+ default: envVar.default
1110
+ });
1111
+ }
1112
+ async function listFiles(dir, prefix) {
1113
+ const { readdir: readdir2, stat: stat4 } = await import("fs/promises");
1114
+ const { join: join8 } = await import("path");
1115
+ const files = [];
1116
+ const items = await readdir2(dir);
1117
+ for (const item of items) {
1118
+ const fullPath = join8(dir, item);
1119
+ const s = await stat4(fullPath);
1120
+ if (s.isDirectory()) {
1121
+ files.push(...await listFiles(fullPath, `${prefix}/${item}`));
1122
+ } else {
1123
+ files.push(`${prefix}/${item}`);
1124
+ }
1125
+ }
1126
+ return files;
1127
+ }
1128
+ function formatSize(bytes) {
1129
+ if (bytes < 1024) return `${bytes} B`;
1130
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1131
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1132
+ }
1133
+ var init_install = __esm({
1134
+ "src/commands/install.ts"() {
1135
+ "use strict";
1136
+ init_client();
1137
+ init_adapters();
1138
+ init_tarball();
1139
+ init_hash();
1140
+ init_config();
1141
+ init_config2();
1142
+ init_output();
1143
+ }
1144
+ });
1145
+
1146
+ // src/index.ts
1147
+ init_install();
1148
+ import { Command as Command15 } from "commander";
1149
+
1150
+ // src/commands/uninstall.ts
1151
+ init_config();
1152
+ init_adapters();
1153
+ init_output();
1154
+ import { rm } from "fs/promises";
1155
+ import { resolve as resolve2 } from "path";
1156
+ import { Command as Command2 } from "commander";
1157
+ import chalk3 from "chalk";
1158
+ 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("Remove a skill and its configuration").action(async (skillName, options) => {
1160
+ try {
1161
+ await runUninstall(skillName, options);
1162
+ } catch (err) {
1163
+ error(err instanceof Error ? err.message : String(err));
1164
+ process.exit(1);
1165
+ }
1166
+ });
1167
+ }
1168
+ async function runUninstall(skillName, options) {
1169
+ const projectPath = options.global ? "__global__" : process.cwd();
1170
+ const record = await getInstallation(projectPath, skillName);
1171
+ if (!record) {
1172
+ throw new Error(
1173
+ `${skillName} is not installed${options.global ? " globally" : " in this project"}`
1174
+ );
1175
+ }
1176
+ const flagMap = {
1177
+ claude: options.claude,
1178
+ cursor: options.cursor,
1179
+ codex: options.codex,
1180
+ copilot: options.copilot,
1181
+ gemini: options.gemini,
1182
+ cline: options.cline
1183
+ };
1184
+ const hasFlags = Object.values(flagMap).some(Boolean);
1185
+ const targetAgents = hasFlags ? record.agents.filter((a) => flagMap[a]) : record.agents;
1186
+ if (targetAgents.length === 0) {
1187
+ throw new Error(
1188
+ `${skillName} is not installed for the specified agent(s)`
1189
+ );
1190
+ }
1191
+ for (const agentName of targetAgents) {
1192
+ const adapter = getAdapter(agentName);
1193
+ if (!adapter) continue;
1194
+ header(`Uninstalling ${skillName} from ${adapter.displayName}:`);
1195
+ const skillDir = record.skill_dirs[agentName];
1196
+ if (skillDir) {
1197
+ try {
1198
+ await rm(resolve2(skillDir), { recursive: true, force: true });
1199
+ success(`Removed ${skillDir}`);
1200
+ } catch {
1201
+ warn(`Could not remove ${skillDir}`);
1202
+ }
1203
+ }
1204
+ const mcpConfig = record.mcp_configs[agentName];
1205
+ if (mcpConfig && adapter) {
1206
+ try {
1207
+ await adapter.removeMcpConfig(mcpConfig, skillName);
1208
+ success(
1209
+ `Removed MCP server "${skillName}" from ${mcpConfig}`
1210
+ );
1211
+ } catch {
1212
+ warn(`Could not update ${mcpConfig}`);
1213
+ }
1214
+ }
1215
+ }
1216
+ const isFullUninstall = !hasFlags || targetAgents.length === record.agents.length;
1217
+ if (isFullUninstall) {
1218
+ await removeInstallation(projectPath, skillName);
1219
+ } else {
1220
+ const remainingAgents = record.agents.filter((a) => !targetAgents.includes(a));
1221
+ const remainingSkillDirs = {};
1222
+ const remainingMcpConfigs = {};
1223
+ for (const agent of remainingAgents) {
1224
+ if (record.skill_dirs[agent]) remainingSkillDirs[agent] = record.skill_dirs[agent];
1225
+ if (record.mcp_configs[agent]) remainingMcpConfigs[agent] = record.mcp_configs[agent];
1226
+ }
1227
+ await updateInstallation(projectPath, skillName, {
1228
+ ...record,
1229
+ agents: remainingAgents,
1230
+ skill_dirs: remainingSkillDirs,
1231
+ mcp_configs: remainingMcpConfigs
1232
+ });
1233
+ }
1234
+ blank();
1235
+ success(
1236
+ `Uninstalled ${chalk3.bold(skillName)} from ${targetAgents.map((a) => getAdapter(a)?.displayName || a).join(", ")}`
1237
+ );
1238
+ blank();
1239
+ }
1240
+
1241
+ // src/commands/search.ts
1242
+ init_client();
1243
+ init_output();
1244
+ import { Command as Command3 } from "commander";
1245
+ import ora2 from "ora";
1246
+ 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("Search the registry for skills").action(async (query, options) => {
1248
+ try {
1249
+ await runSearch(query, options);
1250
+ } catch (err) {
1251
+ error(err instanceof Error ? err.message : String(err));
1252
+ process.exit(1);
1253
+ }
1254
+ });
1255
+ }
1256
+ async function runSearch(query, options) {
1257
+ const spinner = ora2("Searching...").start();
1258
+ const result = await registry.search({
1259
+ q: query,
1260
+ agent: options.agent,
1261
+ category: options.category,
1262
+ limit: parseInt(options.limit || "10", 10)
1263
+ });
1264
+ spinner.stop();
1265
+ if (options.json) {
1266
+ console.log(JSON.stringify(result, null, 2));
1267
+ return;
1268
+ }
1269
+ if (result.skills.length === 0) {
1270
+ blank();
1271
+ info("No skills found matching your query.");
1272
+ blank();
1273
+ return;
1274
+ }
1275
+ blank();
1276
+ table(
1277
+ ["SKILL", "VERSION", "DOWNLOADS/WK", "DESCRIPTION"],
1278
+ result.skills.map((s) => [
1279
+ s.full_name,
1280
+ s.latest_version,
1281
+ formatNumber(s.downloads_weekly),
1282
+ truncate(s.description, 50)
1283
+ ]),
1284
+ [25, 10, 14, 50]
1285
+ );
1286
+ blank();
1287
+ info(
1288
+ `${result.pagination.total} result${result.pagination.total === 1 ? "" : "s"} found`
1289
+ );
1290
+ blank();
1291
+ }
1292
+ function truncate(s, max) {
1293
+ if (s.length <= max) return s;
1294
+ return s.slice(0, max - 3) + "...";
1295
+ }
1296
+
1297
+ // src/commands/info.ts
1298
+ init_client();
1299
+ init_output();
1300
+ import { Command as Command4 } from "commander";
1301
+ import chalk4 from "chalk";
1302
+ import ora3 from "ora";
1303
+ function createInfoCommand() {
1304
+ return new Command4("info").argument("<skill>", "Skill name").option("--json", "Output as JSON").description("Show detailed information about a skill").action(async (skillName, options) => {
1305
+ try {
1306
+ await runInfo(skillName, options);
1307
+ } catch (err) {
1308
+ error(err instanceof Error ? err.message : String(err));
1309
+ process.exit(1);
1310
+ }
1311
+ });
1312
+ }
1313
+ async function runInfo(skillName, options) {
1314
+ const spinner = ora3(`Fetching info for ${skillName}...`).start();
1315
+ const detail = await registry.getSkillDetail(skillName);
1316
+ spinner.stop();
1317
+ if (options.json) {
1318
+ console.log(JSON.stringify(detail, null, 2));
1319
+ return;
1320
+ }
1321
+ blank();
1322
+ const titleParts = [
1323
+ chalk4.bold(`${detail.full_name}@${detail.latest_version}`)
1324
+ ];
1325
+ if (detail.license) titleParts.push(chalk4.dim(`| ${detail.license}`));
1326
+ if (detail.repository_url) titleParts.push(chalk4.dim(`| ${detail.repository_url}`));
1327
+ console.log(" " + titleParts.join(" "));
1328
+ blank();
1329
+ console.log(" " + detail.description);
1330
+ blank();
1331
+ if (detail.author) {
1332
+ info(`Published by ${chalk4.bold(detail.author.username)}`);
1333
+ }
1334
+ info(
1335
+ `Downloads: ${formatNumber(detail.downloads_total)} total | ${formatNumber(detail.downloads_weekly)} this week`
1336
+ );
1337
+ if (detail.categories.length > 0) {
1338
+ info(`Categories: ${detail.categories.join(", ")}`);
1339
+ }
1340
+ if (detail.keywords.length > 0) {
1341
+ info(`Keywords: ${detail.keywords.join(", ")}`);
1342
+ }
1343
+ if (detail.mcp?.servers && detail.mcp.servers.length > 0) {
1344
+ blank();
1345
+ for (const server of detail.mcp.servers) {
1346
+ info(
1347
+ `MCP Server: ${chalk4.bold(server.package)}${server.version ? `@${server.version}` : ""} (${server.registry || "npm"}, ${server.transport || "stdio"})`
1348
+ );
1349
+ }
1350
+ }
1351
+ if (detail.config?.env && detail.config.env.length > 0) {
1352
+ blank();
1353
+ info("Config:");
1354
+ for (const envVar of detail.config.env) {
1355
+ const flags = [
1356
+ envVar.required ? "required" : "optional",
1357
+ envVar.secret ? "secret" : null
1358
+ ].filter(Boolean).join(", ");
1359
+ info(
1360
+ ` ${chalk4.bold(envVar.name)} (${flags}): ${envVar.description}`
1361
+ );
1362
+ }
1363
+ }
1364
+ if (detail.versions.length > 0) {
1365
+ blank();
1366
+ info("Versions:");
1367
+ for (const v of detail.versions.slice(0, 5)) {
1368
+ info(
1369
+ ` ${v.version.padEnd(12)} ${chalk4.dim(v.created_at.slice(0, 10))} ${formatBytes(v.size_bytes)}`
1370
+ );
1371
+ }
1372
+ if (detail.versions.length > 5) {
1373
+ info(
1374
+ chalk4.dim(` ... and ${detail.versions.length - 5} more`)
1375
+ );
1376
+ }
1377
+ }
1378
+ blank();
1379
+ info(`Install: ${chalk4.cyan(`npx shipables install ${detail.full_name}`)}`);
1380
+ blank();
1381
+ }
1382
+
1383
+ // src/commands/list.ts
1384
+ init_config();
1385
+ init_output();
1386
+ import { Command as Command5 } from "commander";
1387
+ import chalk5 from "chalk";
1388
+ function createListCommand() {
1389
+ return new Command5("list").alias("ls").option("--json", "Output as JSON").option("-g, --global", "Show globally installed skills").description("List installed skills").action(async (options) => {
1390
+ try {
1391
+ await runList(options);
1392
+ } catch (err) {
1393
+ error(err instanceof Error ? err.message : String(err));
1394
+ process.exit(1);
1395
+ }
1396
+ });
1397
+ }
1398
+ async function runList(options) {
1399
+ const projectPath = options.global ? "__global__" : process.cwd();
1400
+ const installations = await getProjectInstallations(projectPath);
1401
+ const entries = Object.entries(installations);
1402
+ if (options.json) {
1403
+ console.log(JSON.stringify(installations, null, 2));
1404
+ return;
1405
+ }
1406
+ if (entries.length === 0) {
1407
+ blank();
1408
+ info(
1409
+ options.global ? "No skills installed globally." : "No skills installed in this project."
1410
+ );
1411
+ info(
1412
+ chalk5.dim("Install one with: npx shipables install <skill>")
1413
+ );
1414
+ blank();
1415
+ return;
1416
+ }
1417
+ const label = options.global ? "Installed skills (global):" : `Installed skills (project: ${process.cwd()}):`;
1418
+ header(label);
1419
+ table(
1420
+ ["SKILL", "VERSION", "AGENTS"],
1421
+ entries.map(([name, record]) => [
1422
+ name,
1423
+ record.version,
1424
+ record.agents.join(", ")
1425
+ ]),
1426
+ [25, 10, 30]
1427
+ );
1428
+ blank();
1429
+ info(`${entries.length} skill${entries.length === 1 ? "" : "s"} installed`);
1430
+ blank();
1431
+ }
1432
+
1433
+ // src/commands/update.ts
1434
+ init_config();
1435
+ init_client();
1436
+ init_output();
1437
+ import { Command as Command6 } from "commander";
1438
+ import chalk6 from "chalk";
1439
+ import ora4 from "ora";
1440
+ import { confirm } from "@inquirer/prompts";
1441
+ 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("--self", "Update the shipables CLI itself").description("Update installed skills to latest version").action(async (skill, options) => {
1443
+ try {
1444
+ if (options.self) {
1445
+ await runSelfUpdate();
1446
+ return;
1447
+ }
1448
+ await runUpdate(skill, options);
1449
+ } catch (err) {
1450
+ error(err instanceof Error ? err.message : String(err));
1451
+ process.exit(1);
1452
+ }
1453
+ });
1454
+ }
1455
+ async function runSelfUpdate() {
1456
+ blank();
1457
+ info("To update the shipables CLI, run:");
1458
+ blank();
1459
+ console.log(chalk6.cyan(" npm update -g shipables"));
1460
+ blank();
1461
+ info("Or if you installed with npx, it automatically uses the latest version.");
1462
+ blank();
1463
+ }
1464
+ async function runUpdate(skill, options) {
1465
+ const projectPath = options.global ? "__global__" : process.cwd();
1466
+ const installations = await getProjectInstallations(projectPath);
1467
+ const entries = Object.entries(installations);
1468
+ if (entries.length === 0) {
1469
+ blank();
1470
+ info("No skills installed to update.");
1471
+ blank();
1472
+ return;
1473
+ }
1474
+ const toCheck = skill ? entries.filter(([name]) => name === skill) : entries;
1475
+ if (toCheck.length === 0) {
1476
+ throw new Error(`${skill} is not installed`);
1477
+ }
1478
+ const spinner = ora4("Checking for updates...").start();
1479
+ const updates = [];
1480
+ for (const [name, record] of toCheck) {
1481
+ try {
1482
+ const detail = await registry.getSkillDetail(name);
1483
+ updates.push({
1484
+ name,
1485
+ currentVersion: record.version,
1486
+ latestVersion: detail.latest_version,
1487
+ hasUpdate: detail.latest_version !== record.version,
1488
+ agents: record.agents
1489
+ });
1490
+ } catch {
1491
+ updates.push({
1492
+ name,
1493
+ currentVersion: record.version,
1494
+ latestVersion: "unknown",
1495
+ hasUpdate: false,
1496
+ agents: record.agents
1497
+ });
1498
+ }
1499
+ }
1500
+ spinner.stop();
1501
+ blank();
1502
+ table(
1503
+ ["SKILL", "CURRENT", "LATEST", "STATUS"],
1504
+ updates.map((u) => [
1505
+ u.name,
1506
+ u.currentVersion,
1507
+ u.latestVersion,
1508
+ u.hasUpdate ? chalk6.yellow("update available") : chalk6.green("up to date")
1509
+ ]),
1510
+ [25, 10, 10, 20]
1511
+ );
1512
+ const updatable = updates.filter((u) => u.hasUpdate);
1513
+ if (updatable.length === 0) {
1514
+ blank();
1515
+ success("All skills are up to date");
1516
+ blank();
1517
+ return;
1518
+ }
1519
+ if (options.dryRun) {
1520
+ blank();
1521
+ info(
1522
+ `${updatable.length} skill${updatable.length === 1 ? "" : "s"} would be updated`
1523
+ );
1524
+ blank();
1525
+ return;
1526
+ }
1527
+ blank();
1528
+ const shouldUpdate = await confirm({
1529
+ message: `Update ${updatable.length} skill${updatable.length === 1 ? "" : "s"}?`,
1530
+ default: true
1531
+ });
1532
+ if (!shouldUpdate) {
1533
+ info("Update cancelled.");
1534
+ return;
1535
+ }
1536
+ const { createInstallCommand: createInstallCommand2 } = await Promise.resolve().then(() => (init_install(), install_exports));
1537
+ for (const u of updatable) {
1538
+ blank();
1539
+ header(`Updating ${u.name} ${u.currentVersion} \u2192 ${u.latestVersion}...`);
1540
+ try {
1541
+ const agentFlags = [];
1542
+ for (const agent of u.agents) {
1543
+ agentFlags.push(`--${agent}`);
1544
+ }
1545
+ const globalFlag = options.global ? ["-g"] : [];
1546
+ const cmd = createInstallCommand2();
1547
+ await cmd.parseAsync(
1548
+ ["node", "shipables", `${u.name}@${u.latestVersion}`, ...agentFlags, ...globalFlag, "-y"],
1549
+ { from: "user" }
1550
+ );
1551
+ success(
1552
+ `Updated ${u.name} ${u.currentVersion} \u2192 ${u.latestVersion}`
1553
+ );
1554
+ } catch (err) {
1555
+ error(
1556
+ `Failed to update ${u.name}: ${err instanceof Error ? err.message : String(err)}`
1557
+ );
1558
+ }
1559
+ }
1560
+ blank();
1561
+ success(
1562
+ `${updatable.length} skill${updatable.length === 1 ? "" : "s"} updated`
1563
+ );
1564
+ blank();
1565
+ }
1566
+
1567
+ // src/commands/publish.ts
1568
+ import { access as access2, readFile as readFile7, stat as stat3 } from "fs/promises";
1569
+ import { join as join6 } from "path";
1570
+ import { Command as Command7 } from "commander";
1571
+ import chalk7 from "chalk";
1572
+ import ora5 from "ora";
1573
+ import { confirm as confirm2, select } from "@inquirer/prompts";
1574
+
1575
+ // src/skill/parser.ts
1576
+ import { readFile as readFile5 } from "fs/promises";
1577
+ import { parse as parseYaml } from "yaml";
1578
+ var FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
1579
+ function parseSkillMd(raw) {
1580
+ const match = raw.match(FRONTMATTER_RE);
1581
+ if (!match) {
1582
+ throw new Error(
1583
+ "Invalid SKILL.md: missing YAML frontmatter (must start with ---)"
1584
+ );
1585
+ }
1586
+ const yamlStr = match[1];
1587
+ const body = match[2].trim();
1588
+ let frontmatter;
1589
+ try {
1590
+ frontmatter = parseYaml(yamlStr);
1591
+ } catch (err) {
1592
+ throw new Error(
1593
+ `Invalid SKILL.md frontmatter: ${err instanceof Error ? err.message : String(err)}`
1594
+ );
1595
+ }
1596
+ if (!frontmatter.name) {
1597
+ throw new Error("Invalid SKILL.md: missing required field 'name'");
1598
+ }
1599
+ if (!frontmatter.description) {
1600
+ throw new Error("Invalid SKILL.md: missing required field 'description'");
1601
+ }
1602
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(frontmatter.name)) {
1603
+ throw new Error(
1604
+ "Invalid SKILL.md: name must be lowercase letters, numbers, and hyphens only"
1605
+ );
1606
+ }
1607
+ if (frontmatter.name.length > 64) {
1608
+ throw new Error("Invalid SKILL.md: name must be 64 characters or less");
1609
+ }
1610
+ return { frontmatter, body, raw };
1611
+ }
1612
+ async function parseSkillMdFile(filePath) {
1613
+ const raw = await readFile5(filePath, "utf-8");
1614
+ return parseSkillMd(raw);
1615
+ }
1616
+
1617
+ // src/skill/manifest.ts
1618
+ import { readFile as readFile6 } from "fs/promises";
1619
+ var SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
1620
+ function validateManifest(manifest) {
1621
+ const errors = [];
1622
+ if (!manifest.version) {
1623
+ errors.push("shipables.json: missing required field 'version'");
1624
+ } else if (!SEMVER_RE.test(manifest.version)) {
1625
+ errors.push(
1626
+ `shipables.json: version '${manifest.version}' is not valid semver`
1627
+ );
1628
+ }
1629
+ if (manifest.keywords && !Array.isArray(manifest.keywords)) {
1630
+ errors.push("shipables.json: 'keywords' must be an array");
1631
+ }
1632
+ if (manifest.categories && !Array.isArray(manifest.categories)) {
1633
+ errors.push("shipables.json: 'categories' must be an array");
1634
+ }
1635
+ if (manifest.mcp?.servers) {
1636
+ for (const server of manifest.mcp.servers) {
1637
+ if (!server.name) {
1638
+ errors.push("shipables.json: MCP server missing 'name'");
1639
+ }
1640
+ if (!server.package) {
1641
+ errors.push("shipables.json: MCP server missing 'package'");
1642
+ }
1643
+ }
1644
+ }
1645
+ if (manifest.config?.env) {
1646
+ for (const envVar of manifest.config.env) {
1647
+ if (!envVar.name) {
1648
+ errors.push("shipables.json: env var missing 'name'");
1649
+ }
1650
+ }
1651
+ }
1652
+ return errors;
1653
+ }
1654
+ async function readManifest(filePath) {
1655
+ const raw = await readFile6(filePath, "utf-8");
1656
+ let manifest;
1657
+ try {
1658
+ manifest = JSON.parse(raw);
1659
+ } catch (err) {
1660
+ throw new Error(
1661
+ `Invalid shipables.json: ${err instanceof Error ? err.message : String(err)}`
1662
+ );
1663
+ }
1664
+ return manifest;
1665
+ }
1666
+
1667
+ // src/commands/publish.ts
1668
+ init_tarball();
1669
+ init_hash();
1670
+ init_client();
1671
+ init_config();
1672
+ init_output();
1673
+ function createPublishCommand() {
1674
+ return new Command7("publish").option("--dry-run", "Pack and validate without uploading").option("--tag <tag>", "Dist-tag (e.g., beta, next)", "latest").option(
1675
+ "--access <level>",
1676
+ "Access level for scoped packages",
1677
+ "public"
1678
+ ).description("Publish the skill in the current directory").action(async (options) => {
1679
+ try {
1680
+ await runPublish(options);
1681
+ } catch (err) {
1682
+ error(err instanceof Error ? err.message : String(err));
1683
+ process.exit(1);
1684
+ }
1685
+ });
1686
+ }
1687
+ async function runPublish(options) {
1688
+ const cwd = process.cwd();
1689
+ const skillMdPath = join6(cwd, "SKILL.md");
1690
+ const manifestPath = join6(cwd, "shipables.json");
1691
+ try {
1692
+ await access2(skillMdPath);
1693
+ } catch {
1694
+ throw new Error(
1695
+ "No SKILL.md found in the current directory. Run `shipables init` to create one."
1696
+ );
1697
+ }
1698
+ try {
1699
+ await access2(manifestPath);
1700
+ } catch {
1701
+ throw new Error(
1702
+ "No shipables.json found in the current directory. Run `shipables init` to create one."
1703
+ );
1704
+ }
1705
+ header("Validating:");
1706
+ const skillMd = await parseSkillMdFile(skillMdPath);
1707
+ success(
1708
+ `SKILL.md \u2014 name: ${skillMd.frontmatter.name}, description present`
1709
+ );
1710
+ const manifest = await readManifest(manifestPath);
1711
+ const manifestErrors = validateManifest(manifest);
1712
+ if (manifestErrors.length > 0) {
1713
+ for (const err of manifestErrors) {
1714
+ error(err);
1715
+ }
1716
+ throw new Error("shipables.json validation failed");
1717
+ }
1718
+ success(
1719
+ `shipables.json \u2014 version: ${manifest.version}, schema valid`
1720
+ );
1721
+ const skillBareName = skillMd.frontmatter.name;
1722
+ const version = manifest.version;
1723
+ const token = await getToken();
1724
+ if (!token && !options.dryRun) {
1725
+ throw new Error(
1726
+ "Not authenticated. Run `shipables login` first."
1727
+ );
1728
+ }
1729
+ let fullName = skillBareName;
1730
+ if (!options.dryRun && token) {
1731
+ fullName = await resolvePublishScope(skillBareName);
1732
+ }
1733
+ blank();
1734
+ console.log(` Publishing ${chalk7.bold(`${fullName}@${version}`)}...`);
1735
+ let ignorePatterns = [];
1736
+ try {
1737
+ const ignoreRaw = await readFile7(join6(cwd, ".shipablesignore"), "utf-8");
1738
+ ignorePatterns = ignoreRaw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1739
+ } catch {
1740
+ }
1741
+ const tarballName = `${skillBareName}-${version}.tgz`;
1742
+ const tarballPath = join6(cwd, tarballName);
1743
+ const packSpinner = ora5("Packing tarball...").start();
1744
+ await createTarball(cwd, tarballPath, ignorePatterns);
1745
+ const tarballStat = await stat3(tarballPath);
1746
+ const hash = await sha512File(tarballPath);
1747
+ packSpinner.succeed(
1748
+ `Packed ${tarballName} (${formatBytes(tarballStat.size)})`
1749
+ );
1750
+ blank();
1751
+ info(`Package contents:`);
1752
+ const files = await listAllFiles(cwd, ignorePatterns);
1753
+ for (const file of files.slice(0, 20)) {
1754
+ console.log(` ${file}`);
1755
+ }
1756
+ if (files.length > 20) {
1757
+ console.log(chalk7.dim(` ... and ${files.length - 20} more files`));
1758
+ }
1759
+ if (options.dryRun) {
1760
+ blank();
1761
+ success("Dry run complete \u2014 tarball created but not uploaded");
1762
+ const { unlink } = await import("fs/promises");
1763
+ await unlink(tarballPath);
1764
+ blank();
1765
+ return;
1766
+ }
1767
+ blank();
1768
+ const shouldPublish = await confirm2({
1769
+ message: `Publish ${fullName}@${version} to the shipables registry?`,
1770
+ default: true
1771
+ });
1772
+ if (!shouldPublish) {
1773
+ const { unlink } = await import("fs/promises");
1774
+ await unlink(tarballPath);
1775
+ info("Publish cancelled.");
1776
+ return;
1777
+ }
1778
+ const uploadSpinner = ora5("Publishing...").start();
1779
+ try {
1780
+ const result = await registry.publishWithName(fullName, tarballPath);
1781
+ uploadSpinner.succeed(`Published ${chalk7.bold(`${fullName}@${version}`)}`);
1782
+ if (result.url) {
1783
+ info(result.url);
1784
+ }
1785
+ } catch (err) {
1786
+ uploadSpinner.fail("Publish failed");
1787
+ throw err;
1788
+ } finally {
1789
+ const { unlink } = await import("fs/promises");
1790
+ try {
1791
+ await unlink(tarballPath);
1792
+ } catch {
1793
+ }
1794
+ }
1795
+ blank();
1796
+ }
1797
+ async function resolvePublishScope(bareName) {
1798
+ let me;
1799
+ try {
1800
+ me = await registry.getMe();
1801
+ } catch {
1802
+ return bareName;
1803
+ }
1804
+ const orgs = me.organizations || [];
1805
+ const publishableOrgs = orgs.filter(
1806
+ (o) => o.role === "owner" || o.role === "admin"
1807
+ );
1808
+ if (publishableOrgs.length === 0) {
1809
+ return `${me.username}/${bareName}`;
1810
+ }
1811
+ const choices = [
1812
+ {
1813
+ name: `${me.username}/${bareName} (personal)`,
1814
+ value: `${me.username}/${bareName}`
1815
+ },
1816
+ ...publishableOrgs.map((org) => ({
1817
+ name: `@${org.name}/${bareName} (${org.name})`,
1818
+ value: `@${org.name}/${bareName}`
1819
+ }))
1820
+ ];
1821
+ if (choices.length === 1) {
1822
+ return choices[0].value;
1823
+ }
1824
+ const selected = await select({
1825
+ message: "Publish as:",
1826
+ choices
1827
+ });
1828
+ return selected;
1829
+ }
1830
+ async function listAllFiles(dir, ignorePatterns, prefix = "") {
1831
+ const { readdir: readdir2, stat: stat4 } = await import("fs/promises");
1832
+ const defaultIgnore = [
1833
+ "node_modules",
1834
+ ".git",
1835
+ ".env",
1836
+ ".DS_Store",
1837
+ "*.tgz"
1838
+ ];
1839
+ const allIgnore = [.../* @__PURE__ */ new Set([...defaultIgnore, ...ignorePatterns])];
1840
+ const files = [];
1841
+ const items = await readdir2(dir);
1842
+ for (const item of items) {
1843
+ const relativePath = prefix ? `${prefix}/${item}` : item;
1844
+ if (allIgnore.some((p) => item === p || p.startsWith("*.") && item.endsWith(p.slice(1)))) {
1845
+ continue;
1846
+ }
1847
+ const fullPath = join6(dir, item);
1848
+ const s = await stat4(fullPath);
1849
+ if (s.isDirectory()) {
1850
+ files.push(...await listAllFiles(fullPath, ignorePatterns, relativePath));
1851
+ } else {
1852
+ files.push(relativePath);
1853
+ }
1854
+ }
1855
+ return files;
1856
+ }
1857
+
1858
+ // src/commands/unpublish.ts
1859
+ init_client();
1860
+ init_config();
1861
+ init_output();
1862
+ import { Command as Command8 } from "commander";
1863
+ import chalk8 from "chalk";
1864
+ import ora6 from "ora";
1865
+ import { confirm as confirm3 } from "@inquirer/prompts";
1866
+ 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("Yank a published version (within 72 hours of publish)").action(async (skillArg, options) => {
1868
+ try {
1869
+ await runUnpublish(skillArg, options);
1870
+ } catch (err) {
1871
+ error(err instanceof Error ? err.message : String(err));
1872
+ process.exit(1);
1873
+ }
1874
+ });
1875
+ }
1876
+ async function runUnpublish(skillArg, options) {
1877
+ const token = await getToken();
1878
+ if (!token) {
1879
+ throw new Error("Not authenticated. Run `shipables login` first.");
1880
+ }
1881
+ const { name, version } = parseSkillVersion(skillArg);
1882
+ if (!version) {
1883
+ throw new Error(
1884
+ "You must specify a version to unpublish (e.g., my-skill@1.0.0). Unpublishing an entire package is not supported."
1885
+ );
1886
+ }
1887
+ blank();
1888
+ warn(
1889
+ `This will yank ${chalk8.bold(`${name}@${version}`)} from the registry.`
1890
+ );
1891
+ warn("Yanked versions cannot be re-published with the same version number.");
1892
+ blank();
1893
+ if (!options.force) {
1894
+ const shouldProceed = await confirm3({
1895
+ message: `Are you sure you want to unpublish ${name}@${version}?`,
1896
+ default: false
1897
+ });
1898
+ if (!shouldProceed) {
1899
+ info("Unpublish cancelled.");
1900
+ return;
1901
+ }
1902
+ }
1903
+ const spinner = ora6(`Unpublishing ${name}@${version}...`).start();
1904
+ try {
1905
+ await registry.unpublishVersion(name, version);
1906
+ spinner.succeed(`Unpublished ${chalk8.bold(`${name}@${version}`)}`);
1907
+ } catch (err) {
1908
+ spinner.fail("Unpublish failed");
1909
+ throw err;
1910
+ }
1911
+ blank();
1912
+ }
1913
+ function parseSkillVersion(arg) {
1914
+ if (arg.startsWith("@") || arg.includes("/")) {
1915
+ const lastAt = arg.lastIndexOf("@");
1916
+ const slashIdx = arg.indexOf("/");
1917
+ if (lastAt > slashIdx) {
1918
+ return {
1919
+ name: arg.slice(0, lastAt),
1920
+ version: arg.slice(lastAt + 1)
1921
+ };
1922
+ }
1923
+ return { name: arg, version: null };
1924
+ }
1925
+ const atIdx = arg.indexOf("@");
1926
+ if (atIdx > 0) {
1927
+ return { name: arg.slice(0, atIdx), version: arg.slice(atIdx + 1) };
1928
+ }
1929
+ return { name: arg, version: null };
1930
+ }
1931
+
1932
+ // src/commands/init.ts
1933
+ init_output();
1934
+ import { mkdir as mkdir4, writeFile as writeFile5, access as access3 } from "fs/promises";
1935
+ import { join as join7 } from "path";
1936
+ import { Command as Command9 } from "commander";
1937
+ import chalk9 from "chalk";
1938
+ import { input as input2, confirm as confirm4, checkbox as checkbox2 } from "@inquirer/prompts";
1939
+ var CATEGORIES = [
1940
+ "databases",
1941
+ "apis",
1942
+ "testing",
1943
+ "devops",
1944
+ "security",
1945
+ "documentation",
1946
+ "code-quality",
1947
+ "frameworks",
1948
+ "cloud",
1949
+ "ai-ml",
1950
+ "data",
1951
+ "messaging",
1952
+ "monitoring",
1953
+ "authentication",
1954
+ "other"
1955
+ ];
1956
+ function createInitCommand() {
1957
+ return new Command9("init").description("Scaffold a new skill in the current directory").action(async () => {
1958
+ try {
1959
+ await runInit();
1960
+ } catch (err) {
1961
+ error(err instanceof Error ? err.message : String(err));
1962
+ process.exit(1);
1963
+ }
1964
+ });
1965
+ }
1966
+ async function runInit() {
1967
+ header("Create a new Shipables skill");
1968
+ const cwd = process.cwd();
1969
+ try {
1970
+ await access3(join7(cwd, "SKILL.md"));
1971
+ throw new Error("SKILL.md already exists in this directory");
1972
+ } catch (err) {
1973
+ if (err instanceof Error && err.message.includes("already exists")) {
1974
+ throw err;
1975
+ }
1976
+ }
1977
+ const name = await input2({
1978
+ message: "Skill name (lowercase, hyphens, max 64 chars):",
1979
+ validate: (val) => {
1980
+ if (!val) return "Name is required";
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
+ }
1986
+ });
1987
+ const description = await input2({
1988
+ message: "Description (what the skill does and when to use it):",
1989
+ validate: (val) => {
1990
+ if (!val) return "Description is required";
1991
+ if (val.length > 1024) return "Max 1024 characters";
1992
+ return true;
1993
+ }
1994
+ });
1995
+ const authorName = await input2({
1996
+ message: "Author name:",
1997
+ default: process.env.USER || "your-name"
1998
+ });
1999
+ const authorGithub = await input2({
2000
+ message: "Author GitHub username:",
2001
+ default: authorName
2002
+ });
2003
+ const license = await input2({
2004
+ message: "License:",
2005
+ default: "MIT"
2006
+ });
2007
+ const categories = await checkbox2({
2008
+ message: "Categories (select with space):",
2009
+ choices: CATEGORIES.map((c) => ({ name: c, value: c }))
2010
+ });
2011
+ const includeMcp = await confirm4({
2012
+ message: "Include MCP server configuration?",
2013
+ default: false
2014
+ });
2015
+ let mcpPackage = "";
2016
+ let mcpEnvVars = [];
2017
+ if (includeMcp) {
2018
+ mcpPackage = await input2({
2019
+ message: "MCP server npm package name:",
2020
+ validate: (val) => val ? true : "Package name is required"
2021
+ });
2022
+ let addMore = true;
2023
+ while (addMore) {
2024
+ const envName = await input2({
2025
+ message: "Environment variable name (or empty to finish):"
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
2044
+ });
2045
+ }
2046
+ }
2047
+ const includeScripts = await confirm4({
2048
+ message: "Include example scripts/ directory?",
2049
+ default: false
2050
+ });
2051
+ const includeReferences = await confirm4({
2052
+ message: "Include example references/ directory?",
2053
+ default: false
2054
+ });
2055
+ header("Creating files:");
2056
+ const skillDir = cwd;
2057
+ const skillMd = generateSkillMd(
2058
+ name,
2059
+ description,
2060
+ license,
2061
+ authorGithub,
2062
+ includeReferences,
2063
+ includeMcp,
2064
+ mcpPackage
2065
+ );
2066
+ await writeFile5(join7(skillDir, "SKILL.md"), skillMd, "utf-8");
2067
+ success("SKILL.md");
2068
+ const shipablesJson = generateShipablesJson(
2069
+ name,
2070
+ authorName,
2071
+ authorGithub,
2072
+ categories,
2073
+ includeMcp,
2074
+ mcpPackage,
2075
+ mcpEnvVars
2076
+ );
2077
+ await writeFile5(
2078
+ join7(skillDir, "shipables.json"),
2079
+ JSON.stringify(shipablesJson, null, 2) + "\n",
2080
+ "utf-8"
2081
+ );
2082
+ success("shipables.json");
2083
+ const readme = generateReadme(name, description);
2084
+ await writeFile5(join7(skillDir, "README.md"), readme, "utf-8");
2085
+ success("README.md");
2086
+ const ignoreContent = [
2087
+ "node_modules",
2088
+ ".git",
2089
+ ".env",
2090
+ ".DS_Store",
2091
+ "*.tgz",
2092
+ ".shipablesignore"
2093
+ ].join("\n") + "\n";
2094
+ await writeFile5(join7(skillDir, ".shipablesignore"), ignoreContent, "utf-8");
2095
+ success(".shipablesignore");
2096
+ if (includeScripts) {
2097
+ await mkdir4(join7(skillDir, "scripts"), { recursive: true });
2098
+ await writeFile5(
2099
+ join7(skillDir, "scripts", "example.sh"),
2100
+ `#!/bin/bash
2101
+ # Example script for the ${name} skill
2102
+ echo "Hello from ${name}"
2103
+ `,
2104
+ "utf-8"
2105
+ );
2106
+ success("scripts/example.sh");
2107
+ }
2108
+ if (includeReferences) {
2109
+ await mkdir4(join7(skillDir, "references"), { recursive: true });
2110
+ await writeFile5(
2111
+ join7(skillDir, "references", "example.md"),
2112
+ `# ${name} Reference
2113
+
2114
+ Detailed reference documentation goes here.
2115
+ `,
2116
+ "utf-8"
2117
+ );
2118
+ success("references/example.md");
2119
+ }
2120
+ blank();
2121
+ success(`Skill ${chalk9.bold(name)} initialized!`);
2122
+ blank();
2123
+ info("Next steps:");
2124
+ info(` 1. Edit ${chalk9.cyan("SKILL.md")} with your agent instructions`);
2125
+ info(` 2. Edit ${chalk9.cyan("shipables.json")} with your metadata`);
2126
+ info(` 3. Run ${chalk9.cyan("shipables publish")} to publish`);
2127
+ blank();
2128
+ }
2129
+ function generateSkillMd(name, description, license, author, includeReferences, includeMcp, mcpPackage) {
2130
+ let md = `---
2131
+ name: ${name}
2132
+ description: ${description}
2133
+ license: ${license}
2134
+ compatibility: Describe any requirements (tools, network access, etc.)
2135
+ metadata:
2136
+ author: ${author}
2137
+ version: "0.1.0"
2138
+ ---
2139
+
2140
+ # ${titleCase(name)}
2141
+
2142
+ Instructions for the AI agent go here. Describe patterns, best practices,
2143
+ and how to use the tools and resources this skill provides.
2144
+ `;
2145
+ if (includeMcp) {
2146
+ md += `
2147
+ ## MCP Server
2148
+
2149
+ This skill works best with the ${mcpPackage} MCP server.
2150
+ `;
2151
+ }
2152
+ if (includeReferences) {
2153
+ md += `
2154
+ ## References
2155
+
2156
+ - See [references/example.md](references/example.md) for detailed documentation.
2157
+ `;
2158
+ }
2159
+ return md;
2160
+ }
2161
+ function generateShipablesJson(name, authorName, authorGithub, categories, includeMcp, mcpPackage, mcpEnvVars) {
2162
+ const json = {
2163
+ version: "0.1.0",
2164
+ keywords: [],
2165
+ categories,
2166
+ author: {
2167
+ name: authorName,
2168
+ github: authorGithub
2169
+ }
2170
+ };
2171
+ if (includeMcp && mcpPackage) {
2172
+ json.mcp = {
2173
+ servers: [
2174
+ {
2175
+ name,
2176
+ package: mcpPackage,
2177
+ registry: "npm",
2178
+ transport: "stdio"
2179
+ }
2180
+ ]
2181
+ };
2182
+ if (mcpEnvVars.length > 0) {
2183
+ json.config = {
2184
+ env: mcpEnvVars
2185
+ };
2186
+ }
2187
+ }
2188
+ return json;
2189
+ }
2190
+ function generateReadme(name, description) {
2191
+ return `# ${titleCase(name)}
2192
+
2193
+ ${description}
2194
+
2195
+ ## Installation
2196
+
2197
+ \`\`\`bash
2198
+ npx shipables install ${name}
2199
+ \`\`\`
2200
+
2201
+ ## Usage
2202
+
2203
+ Describe how the AI agent uses this skill.
2204
+
2205
+ ## License
2206
+
2207
+ MIT
2208
+ `;
2209
+ }
2210
+ function titleCase(s) {
2211
+ return s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2212
+ }
2213
+
2214
+ // src/commands/login.ts
2215
+ init_config();
2216
+ init_output();
2217
+ import { createServer } from "http";
2218
+ import { Command as Command10 } from "commander";
2219
+ import chalk10 from "chalk";
2220
+ import ora7 from "ora";
2221
+ function createLoginCommand() {
2222
+ return new Command10("login").description("Authenticate with the Shipables registry via GitHub").action(async () => {
2223
+ try {
2224
+ await runLogin();
2225
+ } catch (err) {
2226
+ error(err instanceof Error ? err.message : String(err));
2227
+ process.exit(1);
2228
+ }
2229
+ });
2230
+ }
2231
+ function createLogoutCommand() {
2232
+ return new Command10("logout").description("Remove stored credentials").action(async () => {
2233
+ try {
2234
+ await runLogout();
2235
+ } catch (err) {
2236
+ error(err instanceof Error ? err.message : String(err));
2237
+ process.exit(1);
2238
+ }
2239
+ });
2240
+ }
2241
+ async function runLogin() {
2242
+ const registryUrl = await getRegistry();
2243
+ const { port, tokenPromise, server } = await startCallbackServer();
2244
+ const callbackUrl = `http://localhost:${port}/callback`;
2245
+ const loginUrl = `${registryUrl}/api/v1/auth/github?callback=${encodeURIComponent(callbackUrl)}&mode=cli`;
2246
+ blank();
2247
+ info("Open this URL in your browser to sign in with GitHub:");
2248
+ blank();
2249
+ info(chalk10.cyan(loginUrl));
2250
+ blank();
2251
+ await tryOpenBrowser(loginUrl);
2252
+ const spinner = ora7("Waiting for authentication...").start();
2253
+ try {
2254
+ const token = await tokenPromise;
2255
+ spinner.stop();
2256
+ const config = await loadConfig();
2257
+ config.token = token;
2258
+ try {
2259
+ const resp = await fetch(`${registryUrl}/api/v1/auth/me`, {
2260
+ headers: {
2261
+ Authorization: `Bearer ${token}`,
2262
+ "User-Agent": "shipables-cli"
2263
+ }
2264
+ });
2265
+ if (resp.ok) {
2266
+ const me = await resp.json();
2267
+ config.username = me.username;
2268
+ }
2269
+ } catch {
2270
+ }
2271
+ await saveConfig(config);
2272
+ blank();
2273
+ success(
2274
+ `Logged in as ${chalk10.bold(config.username || "unknown")}`
2275
+ );
2276
+ info(
2277
+ chalk10.dim("Token stored in ~/.shipables/config.json")
2278
+ );
2279
+ blank();
2280
+ } catch (err) {
2281
+ spinner.fail("Authentication failed");
2282
+ throw err;
2283
+ } finally {
2284
+ server.close();
2285
+ }
2286
+ }
2287
+ async function tryOpenBrowser(url) {
2288
+ const { exec } = await import("child_process");
2289
+ const { promisify } = await import("util");
2290
+ const execAsync = promisify(exec);
2291
+ const commands = process.platform === "darwin" ? ["open"] : process.platform === "win32" ? ["start"] : ["wslview", "xdg-open"];
2292
+ for (const cmd of commands) {
2293
+ try {
2294
+ await execAsync(`${cmd} "${url}"`);
2295
+ return;
2296
+ } catch {
2297
+ }
2298
+ }
2299
+ }
2300
+ async function startCallbackServer() {
2301
+ return new Promise((resolveServer) => {
2302
+ let resolveToken;
2303
+ let rejectToken;
2304
+ const tokenPromise = new Promise((res, rej) => {
2305
+ resolveToken = res;
2306
+ rejectToken = rej;
2307
+ });
2308
+ const timeout = setTimeout(() => {
2309
+ rejectToken(new Error("Authentication timed out after 2 minutes"));
2310
+ }, 12e4);
2311
+ const server = createServer((req, res) => {
2312
+ const url = new URL(req.url, `http://localhost`);
2313
+ if (url.pathname === "/callback") {
2314
+ const token = url.searchParams.get("token");
2315
+ if (token) {
2316
+ res.writeHead(200, { "Content-Type": "text/html" });
2317
+ res.end(
2318
+ "<html><body><h2>Authentication successful!</h2><p>You can close this tab and return to the terminal.</p></body></html>"
2319
+ );
2320
+ clearTimeout(timeout);
2321
+ resolveToken(token);
2322
+ } else {
2323
+ res.writeHead(400, { "Content-Type": "text/html" });
2324
+ res.end(
2325
+ "<html><body><h2>Authentication failed</h2><p>No token received.</p></body></html>"
2326
+ );
2327
+ clearTimeout(timeout);
2328
+ rejectToken(new Error("No token received in callback"));
2329
+ }
2330
+ } else {
2331
+ res.writeHead(404);
2332
+ res.end();
2333
+ }
2334
+ });
2335
+ server.listen(0, () => {
2336
+ const addr = server.address();
2337
+ const port = typeof addr === "object" && addr ? addr.port : 0;
2338
+ resolveServer({ port, tokenPromise, server });
2339
+ });
2340
+ });
2341
+ }
2342
+ async function runLogout() {
2343
+ const config = await loadConfig();
2344
+ if (!config.token) {
2345
+ blank();
2346
+ info("Not currently logged in.");
2347
+ blank();
2348
+ return;
2349
+ }
2350
+ delete config.token;
2351
+ delete config.username;
2352
+ await saveConfig(config);
2353
+ blank();
2354
+ success("Logged out. Token removed from ~/.shipables/config.json");
2355
+ blank();
2356
+ }
2357
+
2358
+ // src/commands/doctor.ts
2359
+ init_config();
2360
+ init_adapters();
2361
+ init_client();
2362
+ init_output();
2363
+ import { Command as Command11 } from "commander";
2364
+ import chalk11 from "chalk";
2365
+ function createDoctorCommand() {
2366
+ return new Command11("doctor").description("Check health of installed skills and agent configurations").option("-g, --global", "Check globally installed skills").action(async (options) => {
2367
+ try {
2368
+ await runDoctor(options);
2369
+ } catch (err) {
2370
+ error(err instanceof Error ? err.message : String(err));
2371
+ process.exit(1);
2372
+ }
2373
+ });
2374
+ }
2375
+ async function runDoctor(options) {
2376
+ const projectPath = options.global ? "__global__" : process.cwd();
2377
+ const installations = await getProjectInstallations(projectPath);
2378
+ const entries = Object.entries(installations);
2379
+ if (entries.length === 0) {
2380
+ blank();
2381
+ info("No skills installed to check.");
2382
+ blank();
2383
+ return;
2384
+ }
2385
+ header("Checking installed skills...");
2386
+ let totalIssues = 0;
2387
+ let totalUpdates = 0;
2388
+ for (const [skillName, record] of entries) {
2389
+ const agentList = record.agents.join(", ");
2390
+ console.log(
2391
+ ` ${chalk11.bold(`${skillName}@${record.version}`)} (${agentList}):`
2392
+ );
2393
+ for (const agentName of record.agents) {
2394
+ const adapter = getAdapter(agentName);
2395
+ if (!adapter) continue;
2396
+ const skillDir = record.skill_dirs[agentName];
2397
+ const mcpConfig = record.mcp_configs[agentName] || null;
2398
+ if (skillDir) {
2399
+ const result = await adapter.validate(skillName, skillDir, mcpConfig);
2400
+ for (const check of result.checks) {
2401
+ if (check.status === "pass") {
2402
+ success(check.label);
2403
+ } else if (check.status === "fail") {
2404
+ error(check.label + (check.message ? ` \u2014 ${check.message}` : ""));
2405
+ totalIssues++;
2406
+ } else {
2407
+ warn(check.label + (check.message ? ` \u2014 ${check.message}` : ""));
2408
+ }
2409
+ }
2410
+ }
2411
+ }
2412
+ try {
2413
+ const detail = await registry.getSkillDetail(skillName);
2414
+ if (detail.latest_version !== record.version) {
2415
+ warn(
2416
+ `Update available: ${record.version} \u2192 ${detail.latest_version}`
2417
+ );
2418
+ totalUpdates++;
2419
+ }
2420
+ } catch {
2421
+ }
2422
+ blank();
2423
+ }
2424
+ if (totalIssues === 0 && totalUpdates === 0) {
2425
+ success("All checks passed");
2426
+ } else {
2427
+ const parts = [];
2428
+ if (totalIssues > 0) {
2429
+ parts.push(`${totalIssues} issue${totalIssues === 1 ? "" : "s"} found`);
2430
+ }
2431
+ if (totalUpdates > 0) {
2432
+ parts.push(
2433
+ `${totalUpdates} update${totalUpdates === 1 ? "" : "s"} available`
2434
+ );
2435
+ }
2436
+ info(`Summary: ${parts.join(", ")}`);
2437
+ }
2438
+ blank();
2439
+ }
2440
+
2441
+ // src/commands/config.ts
2442
+ init_config();
2443
+ init_output();
2444
+ import { Command as Command12 } from "commander";
2445
+ import chalk12 from "chalk";
2446
+ var VALID_KEYS = ["registry", "defaultAgents", "scope"];
2447
+ function createConfigCommand() {
2448
+ const cmd = new Command12("config").description("Manage CLI configuration");
2449
+ cmd.command("set <key> <value>").description("Set a config value").action(async (key, value) => {
2450
+ try {
2451
+ await runConfigSet(key, value);
2452
+ } catch (err) {
2453
+ error(err instanceof Error ? err.message : String(err));
2454
+ process.exit(1);
2455
+ }
2456
+ });
2457
+ cmd.command("get <key>").description("Get a config value").action(async (key) => {
2458
+ try {
2459
+ await runConfigGet(key);
2460
+ } catch (err) {
2461
+ error(err instanceof Error ? err.message : String(err));
2462
+ process.exit(1);
2463
+ }
2464
+ });
2465
+ cmd.command("list").description("Show all config").action(async () => {
2466
+ try {
2467
+ await runConfigList();
2468
+ } catch (err) {
2469
+ error(err instanceof Error ? err.message : String(err));
2470
+ process.exit(1);
2471
+ }
2472
+ });
2473
+ cmd.command("delete <key>").description("Remove a config value").action(async (key) => {
2474
+ try {
2475
+ await runConfigDelete(key);
2476
+ } catch (err) {
2477
+ error(err instanceof Error ? err.message : String(err));
2478
+ process.exit(1);
2479
+ }
2480
+ });
2481
+ return cmd;
2482
+ }
2483
+ function validateKey(key) {
2484
+ if (!VALID_KEYS.includes(key)) {
2485
+ throw new Error(
2486
+ `Invalid config key: ${key}. Valid keys: ${VALID_KEYS.join(", ")}`
2487
+ );
2488
+ }
2489
+ return key;
2490
+ }
2491
+ async function runConfigSet(key, value) {
2492
+ const validKey = validateKey(key);
2493
+ const config = await loadConfig();
2494
+ if (validKey === "defaultAgents") {
2495
+ config[validKey] = value.split(",").map((s) => s.trim());
2496
+ } else {
2497
+ config[validKey] = value;
2498
+ }
2499
+ await saveConfig(config);
2500
+ success(`Set ${key} = ${value}`);
2501
+ }
2502
+ async function runConfigGet(key) {
2503
+ const validKey = validateKey(key);
2504
+ const config = await loadConfig();
2505
+ const value = config[validKey];
2506
+ if (value === void 0) {
2507
+ info(`${key}: (not set)`);
2508
+ } else {
2509
+ console.log(
2510
+ typeof value === "object" ? JSON.stringify(value) : String(value)
2511
+ );
2512
+ }
2513
+ }
2514
+ async function runConfigList() {
2515
+ const config = await loadConfig();
2516
+ blank();
2517
+ for (const key of VALID_KEYS) {
2518
+ const value = config[key];
2519
+ const display = value === void 0 ? chalk12.dim("(not set)") : typeof value === "object" ? JSON.stringify(value) : String(value);
2520
+ console.log(` ${chalk12.bold(key)}: ${display}`);
2521
+ }
2522
+ if (config.username) {
2523
+ console.log(
2524
+ ` ${chalk12.bold("auth")}: logged in as ${chalk12.green(config.username)}`
2525
+ );
2526
+ } else {
2527
+ console.log(` ${chalk12.bold("auth")}: ${chalk12.dim("not logged in")}`);
2528
+ }
2529
+ blank();
2530
+ }
2531
+ async function runConfigDelete(key) {
2532
+ const validKey = validateKey(key);
2533
+ const config = await loadConfig();
2534
+ delete config[validKey];
2535
+ await saveConfig(config);
2536
+ success(`Deleted ${key}`);
2537
+ }
2538
+
2539
+ // src/commands/stats.ts
2540
+ init_client();
2541
+ init_output();
2542
+ import { Command as Command13 } from "commander";
2543
+ import chalk13 from "chalk";
2544
+ import ora8 from "ora";
2545
+ 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("Show download statistics for a skill").action(async (skillName, options) => {
2547
+ try {
2548
+ await runStats(skillName, options);
2549
+ } catch (err) {
2550
+ error(err instanceof Error ? err.message : String(err));
2551
+ process.exit(1);
2552
+ }
2553
+ });
2554
+ }
2555
+ async function runStats(skillName, options) {
2556
+ const spinner = ora8(`Fetching download stats for ${skillName}...`).start();
2557
+ const stats = await registry.getDownloadStats(skillName, options.period);
2558
+ spinner.stop();
2559
+ if (options.json) {
2560
+ console.log(JSON.stringify(stats, null, 2));
2561
+ return;
2562
+ }
2563
+ blank();
2564
+ console.log(
2565
+ ` ${chalk13.bold(stats.skill)} \u2014 ${stats.period} downloads: ${chalk13.bold(formatNumber(stats.total))}`
2566
+ );
2567
+ blank();
2568
+ if (stats.daily.length > 0) {
2569
+ const maxCount = Math.max(...stats.daily.map((d) => d.count), 1);
2570
+ const barWidth = 30;
2571
+ for (const day of stats.daily) {
2572
+ const bar = "\u2588".repeat(Math.round(day.count / maxCount * barWidth));
2573
+ const date = day.date.slice(5);
2574
+ const count = String(day.count).padStart(6);
2575
+ console.log(` ${chalk13.dim(date)} ${count} ${chalk13.cyan(bar)}`);
2576
+ }
2577
+ }
2578
+ blank();
2579
+ }
2580
+
2581
+ // src/commands/profile.ts
2582
+ init_client();
2583
+ init_config();
2584
+ init_output();
2585
+ import { Command as Command14 } from "commander";
2586
+ import chalk14 from "chalk";
2587
+ import ora9 from "ora";
2588
+ function createProfileCommand() {
2589
+ return new Command14("profile").argument("[username]", "Username to look up (defaults to current user)").option("--json", "Output as JSON").description("Show user profile and published skills").action(async (username, options) => {
2590
+ try {
2591
+ await runProfile(username, options);
2592
+ } catch (err) {
2593
+ error(err instanceof Error ? err.message : String(err));
2594
+ process.exit(1);
2595
+ }
2596
+ });
2597
+ }
2598
+ async function runProfile(username, options) {
2599
+ if (!username) {
2600
+ const config = await loadConfig();
2601
+ if (config.username) {
2602
+ username = config.username;
2603
+ } else {
2604
+ const token = await getToken();
2605
+ if (!token) {
2606
+ throw new Error(
2607
+ "No username provided and not logged in. Run `shipables login` or provide a username."
2608
+ );
2609
+ }
2610
+ const me = await registry.getMe();
2611
+ username = me.username;
2612
+ }
2613
+ }
2614
+ const spinner = ora9(`Fetching profile for ${username}...`).start();
2615
+ const [profile, skillsResult] = await Promise.all([
2616
+ registry.getUserProfile(username),
2617
+ registry.getUserSkills(username)
2618
+ ]);
2619
+ spinner.stop();
2620
+ if (options.json) {
2621
+ console.log(JSON.stringify({ profile, skills: skillsResult.skills }, null, 2));
2622
+ return;
2623
+ }
2624
+ blank();
2625
+ const displayName = profile.display_name || profile.username;
2626
+ console.log(` ${chalk14.bold(displayName)}`);
2627
+ if (profile.display_name && profile.display_name !== profile.username) {
2628
+ console.log(` ${chalk14.dim(`@${profile.username}`)}`);
2629
+ }
2630
+ blank();
2631
+ info(`Skills published: ${profile.skills_count}`);
2632
+ info(`Joined: ${profile.created_at.slice(0, 10)}`);
2633
+ if (skillsResult.skills.length > 0) {
2634
+ blank();
2635
+ table(
2636
+ ["SKILL", "VERSION", "DOWNLOADS/WK", "DESCRIPTION"],
2637
+ skillsResult.skills.map((s) => [
2638
+ s.full_name,
2639
+ s.latest_version,
2640
+ formatNumber(s.downloads_weekly),
2641
+ truncate2(s.description, 40)
2642
+ ]),
2643
+ [25, 10, 14, 40]
2644
+ );
2645
+ }
2646
+ blank();
2647
+ }
2648
+ function truncate2(s, max) {
2649
+ if (s.length <= max) return s;
2650
+ return s.slice(0, max - 3) + "...";
2651
+ }
2652
+
2653
+ // src/index.ts
2654
+ var program = new Command15();
2655
+ program.name("shipables").description("CLI for installing, managing, and publishing AI agent skills").version("0.1.0");
2656
+ program.addCommand(createInstallCommand());
2657
+ program.addCommand(createUninstallCommand());
2658
+ program.addCommand(createSearchCommand());
2659
+ program.addCommand(createInfoCommand());
2660
+ program.addCommand(createListCommand());
2661
+ program.addCommand(createUpdateCommand());
2662
+ program.addCommand(createPublishCommand());
2663
+ program.addCommand(createUnpublishCommand());
2664
+ program.addCommand(createInitCommand());
2665
+ program.addCommand(createLoginCommand());
2666
+ program.addCommand(createLogoutCommand());
2667
+ program.addCommand(createDoctorCommand());
2668
+ program.addCommand(createConfigCommand());
2669
+ program.addCommand(createStatsCommand());
2670
+ program.addCommand(createProfileCommand());
2671
+ program.parse();
2672
+ //# sourceMappingURL=index.js.map