@nimblebrain/mpak 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +10 -198
  2. package/README.md +50 -383
  3. package/dist/index.d.ts +0 -2
  4. package/dist/index.js +2113 -4
  5. package/dist/index.js.map +1 -1
  6. package/package.json +32 -29
  7. package/.claude/settings.local.json +0 -19
  8. package/.env.example +0 -13
  9. package/.github/workflows/ci.yml +0 -27
  10. package/CLAUDE.md +0 -283
  11. package/dist/commands/config.d.ts +0 -31
  12. package/dist/commands/config.d.ts.map +0 -1
  13. package/dist/commands/config.js +0 -129
  14. package/dist/commands/config.js.map +0 -1
  15. package/dist/commands/packages/pull.d.ts +0 -11
  16. package/dist/commands/packages/pull.d.ts.map +0 -1
  17. package/dist/commands/packages/pull.js +0 -72
  18. package/dist/commands/packages/pull.js.map +0 -1
  19. package/dist/commands/packages/run.d.ts +0 -47
  20. package/dist/commands/packages/run.d.ts.map +0 -1
  21. package/dist/commands/packages/run.js +0 -419
  22. package/dist/commands/packages/run.js.map +0 -1
  23. package/dist/commands/packages/search.d.ts +0 -12
  24. package/dist/commands/packages/search.d.ts.map +0 -1
  25. package/dist/commands/packages/search.js +0 -63
  26. package/dist/commands/packages/search.js.map +0 -1
  27. package/dist/commands/packages/show.d.ts +0 -8
  28. package/dist/commands/packages/show.d.ts.map +0 -1
  29. package/dist/commands/packages/show.js +0 -109
  30. package/dist/commands/packages/show.js.map +0 -1
  31. package/dist/commands/search.d.ts +0 -12
  32. package/dist/commands/search.d.ts.map +0 -1
  33. package/dist/commands/search.js +0 -144
  34. package/dist/commands/search.js.map +0 -1
  35. package/dist/commands/skills/index.d.ts +0 -8
  36. package/dist/commands/skills/index.d.ts.map +0 -1
  37. package/dist/commands/skills/index.js +0 -8
  38. package/dist/commands/skills/index.js.map +0 -1
  39. package/dist/commands/skills/install.d.ts +0 -9
  40. package/dist/commands/skills/install.d.ts.map +0 -1
  41. package/dist/commands/skills/install.js +0 -110
  42. package/dist/commands/skills/install.js.map +0 -1
  43. package/dist/commands/skills/list.d.ts +0 -8
  44. package/dist/commands/skills/list.d.ts.map +0 -1
  45. package/dist/commands/skills/list.js +0 -89
  46. package/dist/commands/skills/list.js.map +0 -1
  47. package/dist/commands/skills/pack.d.ts +0 -22
  48. package/dist/commands/skills/pack.d.ts.map +0 -1
  49. package/dist/commands/skills/pack.js +0 -116
  50. package/dist/commands/skills/pack.js.map +0 -1
  51. package/dist/commands/skills/pull.d.ts +0 -9
  52. package/dist/commands/skills/pull.d.ts.map +0 -1
  53. package/dist/commands/skills/pull.js +0 -68
  54. package/dist/commands/skills/pull.js.map +0 -1
  55. package/dist/commands/skills/search.d.ts +0 -14
  56. package/dist/commands/skills/search.d.ts.map +0 -1
  57. package/dist/commands/skills/search.js +0 -53
  58. package/dist/commands/skills/search.js.map +0 -1
  59. package/dist/commands/skills/show.d.ts +0 -8
  60. package/dist/commands/skills/show.d.ts.map +0 -1
  61. package/dist/commands/skills/show.js +0 -64
  62. package/dist/commands/skills/show.js.map +0 -1
  63. package/dist/commands/skills/validate.d.ts +0 -25
  64. package/dist/commands/skills/validate.d.ts.map +0 -1
  65. package/dist/commands/skills/validate.js +0 -191
  66. package/dist/commands/skills/validate.js.map +0 -1
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/lib/api/registry-client.d.ts +0 -63
  69. package/dist/lib/api/registry-client.d.ts.map +0 -1
  70. package/dist/lib/api/registry-client.js +0 -167
  71. package/dist/lib/api/registry-client.js.map +0 -1
  72. package/dist/lib/api/skills-client.d.ts +0 -30
  73. package/dist/lib/api/skills-client.d.ts.map +0 -1
  74. package/dist/lib/api/skills-client.js +0 -110
  75. package/dist/lib/api/skills-client.js.map +0 -1
  76. package/dist/program.d.ts +0 -12
  77. package/dist/program.d.ts.map +0 -1
  78. package/dist/program.js +0 -186
  79. package/dist/program.js.map +0 -1
  80. package/dist/schemas/generated/api-responses.d.ts +0 -541
  81. package/dist/schemas/generated/api-responses.d.ts.map +0 -1
  82. package/dist/schemas/generated/api-responses.js +0 -313
  83. package/dist/schemas/generated/api-responses.js.map +0 -1
  84. package/dist/schemas/generated/auth.d.ts +0 -18
  85. package/dist/schemas/generated/auth.d.ts.map +0 -1
  86. package/dist/schemas/generated/auth.js +0 -18
  87. package/dist/schemas/generated/auth.js.map +0 -1
  88. package/dist/schemas/generated/index.d.ts +0 -5
  89. package/dist/schemas/generated/index.d.ts.map +0 -1
  90. package/dist/schemas/generated/index.js +0 -6
  91. package/dist/schemas/generated/index.js.map +0 -1
  92. package/dist/schemas/generated/package.d.ts +0 -43
  93. package/dist/schemas/generated/package.d.ts.map +0 -1
  94. package/dist/schemas/generated/package.js +0 -20
  95. package/dist/schemas/generated/package.js.map +0 -1
  96. package/dist/schemas/generated/skill.d.ts +0 -381
  97. package/dist/schemas/generated/skill.d.ts.map +0 -1
  98. package/dist/schemas/generated/skill.js +0 -216
  99. package/dist/schemas/generated/skill.js.map +0 -1
  100. package/dist/utils/config-manager.d.ts +0 -66
  101. package/dist/utils/config-manager.d.ts.map +0 -1
  102. package/dist/utils/config-manager.js +0 -193
  103. package/dist/utils/config-manager.js.map +0 -1
  104. package/dist/utils/errors.d.ts +0 -12
  105. package/dist/utils/errors.d.ts.map +0 -1
  106. package/dist/utils/errors.js +0 -27
  107. package/dist/utils/errors.js.map +0 -1
  108. package/dist/utils/version.d.ts +0 -5
  109. package/dist/utils/version.d.ts.map +0 -1
  110. package/dist/utils/version.js +0 -19
  111. package/dist/utils/version.js.map +0 -1
  112. package/eslint.config.js +0 -63
  113. package/src/commands/config.ts +0 -162
  114. package/src/commands/packages/pull.ts +0 -96
  115. package/src/commands/packages/run.test.ts +0 -261
  116. package/src/commands/packages/run.ts +0 -536
  117. package/src/commands/packages/search.ts +0 -83
  118. package/src/commands/packages/show.ts +0 -128
  119. package/src/commands/search.ts +0 -191
  120. package/src/commands/skills/index.ts +0 -7
  121. package/src/commands/skills/install.ts +0 -129
  122. package/src/commands/skills/list.ts +0 -116
  123. package/src/commands/skills/pack.test.ts +0 -260
  124. package/src/commands/skills/pack.ts +0 -145
  125. package/src/commands/skills/pull.ts +0 -88
  126. package/src/commands/skills/search.ts +0 -73
  127. package/src/commands/skills/show.ts +0 -72
  128. package/src/commands/skills/validate.test.ts +0 -466
  129. package/src/commands/skills/validate.ts +0 -227
  130. package/src/index.ts +0 -11
  131. package/src/lib/api/registry-client.ts +0 -223
  132. package/src/lib/api/schema.d.ts +0 -520
  133. package/src/lib/api/skills-client.ts +0 -148
  134. package/src/program.test.ts +0 -22
  135. package/src/program.ts +0 -226
  136. package/src/schemas/config.v1.schema.json +0 -37
  137. package/src/schemas/generated/api-responses.ts +0 -386
  138. package/src/schemas/generated/auth.ts +0 -21
  139. package/src/schemas/generated/index.ts +0 -5
  140. package/src/schemas/generated/package.ts +0 -29
  141. package/src/schemas/generated/skill.ts +0 -271
  142. package/src/utils/config-manager.test.ts +0 -330
  143. package/src/utils/config-manager.ts +0 -272
  144. package/src/utils/errors.test.ts +0 -25
  145. package/src/utils/errors.ts +0 -33
  146. package/src/utils/version.test.ts +0 -16
  147. package/src/utils/version.ts +0 -18
  148. package/test/integration/registry-client.test.ts +0 -180
  149. package/tsconfig.check.json +0 -9
  150. package/tsconfig.json +0 -25
  151. package/vitest.config.ts +0 -14
package/dist/index.js CHANGED
@@ -1,9 +1,2118 @@
1
1
  #!/usr/bin/env node
2
- import { createProgram } from './program.js';
3
- import { handleError } from './utils/errors.js';
2
+
3
+ // src/program.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/utils/version.ts
7
+ function getVersion() {
8
+ return true ? "0.2.0" : "unknown";
9
+ }
10
+
11
+ // src/utils/format.ts
12
+ function table(headers, rows, opts) {
13
+ const rightAlign = new Set(opts?.rightAlign ?? []);
14
+ const widths = headers.map((h, i) => {
15
+ const maxData = rows.reduce(
16
+ (max, row) => Math.max(max, (row[i] ?? "").length),
17
+ 0
18
+ );
19
+ return Math.max(h.length, maxData);
20
+ });
21
+ const pad = (text, width, colIdx) => rightAlign.has(colIdx) ? text.padStart(width) : text.padEnd(width);
22
+ const lines = [];
23
+ lines.push(
24
+ headers.map((h, i) => pad(h, widths[i], i)).join(" ")
25
+ );
26
+ for (const row of rows) {
27
+ lines.push(
28
+ headers.map((_, i) => pad(row[i] ?? "", widths[i], i)).join(" ")
29
+ );
30
+ }
31
+ return lines.join("\n");
32
+ }
33
+ function certLabel(level) {
34
+ if (level == null) return "-";
35
+ return `L${level}`;
36
+ }
37
+ function formatSize(bytes) {
38
+ if (bytes < 1024) return `${bytes} B`;
39
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
40
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
41
+ }
42
+ function truncate(text, max) {
43
+ if (text.length <= max) return text;
44
+ return text.slice(0, max - 3) + "...";
45
+ }
46
+ function fmtError(message) {
47
+ console.error(`Error: ${message}`);
48
+ process.exit(1);
49
+ }
50
+
51
+ // src/utils/client.ts
52
+ import { MpakClient } from "@nimblebrain/mpak-sdk";
53
+
54
+ // src/utils/config-manager.ts
55
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
56
+ import { homedir } from "os";
57
+ import { join } from "path";
58
+ var CONFIG_VERSION = "1.0.0";
59
+ var ConfigCorruptedError = class extends Error {
60
+ constructor(message, configPath, cause) {
61
+ super(message);
62
+ this.configPath = configPath;
63
+ this.cause = cause;
64
+ this.name = "ConfigCorruptedError";
65
+ }
66
+ };
67
+ function validateConfig(data, configPath) {
68
+ if (typeof data !== "object" || data === null) {
69
+ throw new ConfigCorruptedError(
70
+ "Config file must be a JSON object",
71
+ configPath
72
+ );
73
+ }
74
+ const obj = data;
75
+ if (typeof obj["version"] !== "string") {
76
+ throw new ConfigCorruptedError(
77
+ "Config missing required field: version (string)",
78
+ configPath
79
+ );
80
+ }
81
+ if (typeof obj["lastUpdated"] !== "string") {
82
+ throw new ConfigCorruptedError(
83
+ "Config missing required field: lastUpdated (string)",
84
+ configPath
85
+ );
86
+ }
87
+ if (obj["registryUrl"] !== void 0 && typeof obj["registryUrl"] !== "string") {
88
+ throw new ConfigCorruptedError(
89
+ "Config field registryUrl must be a string",
90
+ configPath
91
+ );
92
+ }
93
+ if (obj["packages"] !== void 0) {
94
+ if (typeof obj["packages"] !== "object" || obj["packages"] === null) {
95
+ throw new ConfigCorruptedError(
96
+ "Config field packages must be an object",
97
+ configPath
98
+ );
99
+ }
100
+ for (const [pkgName, pkgConfig] of Object.entries(
101
+ obj["packages"]
102
+ )) {
103
+ if (typeof pkgConfig !== "object" || pkgConfig === null) {
104
+ throw new ConfigCorruptedError(
105
+ `Config packages.${pkgName} must be an object`,
106
+ configPath
107
+ );
108
+ }
109
+ for (const [key, value] of Object.entries(
110
+ pkgConfig
111
+ )) {
112
+ if (typeof value !== "string") {
113
+ throw new ConfigCorruptedError(
114
+ `Config packages.${pkgName}.${key} must be a string`,
115
+ configPath
116
+ );
117
+ }
118
+ }
119
+ }
120
+ }
121
+ const knownFields = /* @__PURE__ */ new Set([
122
+ "version",
123
+ "lastUpdated",
124
+ "registryUrl",
125
+ "packages"
126
+ ]);
127
+ for (const key of Object.keys(obj)) {
128
+ if (!knownFields.has(key)) {
129
+ throw new ConfigCorruptedError(
130
+ `Config contains unknown field: ${key}`,
131
+ configPath
132
+ );
133
+ }
134
+ }
135
+ return data;
136
+ }
137
+ var ConfigManager = class {
138
+ configDir;
139
+ configFile;
140
+ config = null;
141
+ constructor() {
142
+ this.configDir = join(homedir(), ".mpak");
143
+ this.configFile = join(this.configDir, "config.json");
144
+ this.ensureConfigDir();
145
+ }
146
+ ensureConfigDir() {
147
+ if (!existsSync(this.configDir)) {
148
+ mkdirSync(this.configDir, { recursive: true, mode: 448 });
149
+ }
150
+ }
151
+ loadConfig() {
152
+ if (this.config) {
153
+ return this.config;
154
+ }
155
+ if (!existsSync(this.configFile)) {
156
+ this.config = {
157
+ version: CONFIG_VERSION,
158
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
159
+ };
160
+ this.saveConfig();
161
+ return this.config;
162
+ }
163
+ let configJson;
164
+ try {
165
+ configJson = readFileSync(this.configFile, "utf8");
166
+ } catch (err) {
167
+ throw new ConfigCorruptedError(
168
+ `Failed to read config file: ${err instanceof Error ? err.message : String(err)}`,
169
+ this.configFile,
170
+ err instanceof Error ? err : void 0
171
+ );
172
+ }
173
+ let parsed;
174
+ try {
175
+ parsed = JSON.parse(configJson);
176
+ } catch (err) {
177
+ throw new ConfigCorruptedError(
178
+ `Config file contains invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
179
+ this.configFile,
180
+ err instanceof Error ? err : void 0
181
+ );
182
+ }
183
+ this.config = validateConfig(parsed, this.configFile);
184
+ return this.config;
185
+ }
186
+ saveConfig() {
187
+ if (!this.config) {
188
+ return;
189
+ }
190
+ this.config.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
191
+ const configJson = JSON.stringify(this.config, null, 2);
192
+ writeFileSync(this.configFile, configJson, { mode: 384 });
193
+ }
194
+ setRegistryUrl(url) {
195
+ const config = this.loadConfig();
196
+ config.registryUrl = url;
197
+ this.saveConfig();
198
+ }
199
+ getRegistryUrl() {
200
+ const config = this.loadConfig();
201
+ return config.registryUrl || process.env["MPAK_REGISTRY_URL"] || "https://registry.mpak.dev";
202
+ }
203
+ /**
204
+ * Get all stored config values for a package
205
+ */
206
+ getPackageConfig(packageName) {
207
+ const config = this.loadConfig();
208
+ return config.packages?.[packageName];
209
+ }
210
+ /**
211
+ * Get a specific config value for a package
212
+ */
213
+ getPackageConfigValue(packageName, key) {
214
+ const packageConfig = this.getPackageConfig(packageName);
215
+ return packageConfig?.[key];
216
+ }
217
+ /**
218
+ * Set a config value for a package
219
+ */
220
+ setPackageConfigValue(packageName, key, value) {
221
+ const config = this.loadConfig();
222
+ if (!config.packages) {
223
+ config.packages = {};
224
+ }
225
+ if (!config.packages[packageName]) {
226
+ config.packages[packageName] = {};
227
+ }
228
+ config.packages[packageName][key] = value;
229
+ this.saveConfig();
230
+ }
231
+ /**
232
+ * Clear all config values for a package
233
+ */
234
+ clearPackageConfig(packageName) {
235
+ const config = this.loadConfig();
236
+ if (config.packages?.[packageName]) {
237
+ delete config.packages[packageName];
238
+ this.saveConfig();
239
+ return true;
240
+ }
241
+ return false;
242
+ }
243
+ /**
244
+ * Clear a specific config value for a package
245
+ */
246
+ clearPackageConfigValue(packageName, key) {
247
+ const config = this.loadConfig();
248
+ if (config.packages?.[packageName]?.[key] !== void 0) {
249
+ delete config.packages[packageName][key];
250
+ if (Object.keys(config.packages[packageName]).length === 0) {
251
+ delete config.packages[packageName];
252
+ }
253
+ this.saveConfig();
254
+ return true;
255
+ }
256
+ return false;
257
+ }
258
+ /**
259
+ * List all packages with stored config
260
+ */
261
+ listPackagesWithConfig() {
262
+ const config = this.loadConfig();
263
+ return Object.keys(config.packages || {});
264
+ }
265
+ };
266
+
267
+ // src/utils/client.ts
268
+ function createClient() {
269
+ const configManager = new ConfigManager();
270
+ const version = getVersion();
271
+ return new MpakClient({
272
+ registryUrl: configManager.getRegistryUrl(),
273
+ userAgent: `mpak-cli/${version}`
274
+ });
275
+ }
276
+
277
+ // src/commands/search.ts
278
+ async function handleUnifiedSearch(query, options = {}) {
279
+ try {
280
+ const client = createClient();
281
+ const results = [];
282
+ let bundleTotal = 0;
283
+ let skillTotal = 0;
284
+ const searchBundles = !options.type || options.type === "bundle";
285
+ const searchSkillsFlag = !options.type || options.type === "skill";
286
+ const searchParams = { q: query };
287
+ if (options.sort) searchParams["sort"] = options.sort;
288
+ if (options.limit) searchParams["limit"] = options.limit;
289
+ if (options.offset) searchParams["offset"] = options.offset;
290
+ const [bundleResult, skillResult] = await Promise.all([
291
+ searchBundles ? client.searchBundles(searchParams) : null,
292
+ searchSkillsFlag ? client.searchSkills(searchParams).catch(() => null) : null
293
+ ]);
294
+ if (bundleResult) {
295
+ bundleTotal = bundleResult.total;
296
+ for (const bundle of bundleResult.bundles) {
297
+ results.push({
298
+ type: "bundle",
299
+ name: bundle.name,
300
+ description: bundle.description || "",
301
+ downloads: bundle.downloads || 0,
302
+ version: bundle.latest_version,
303
+ author: bundle.author?.name || void 0,
304
+ certLevel: bundle.certification_level,
305
+ serverType: bundle.server_type || void 0,
306
+ verified: bundle.verified,
307
+ provenance: !!bundle.provenance
308
+ });
309
+ }
310
+ }
311
+ if (skillResult) {
312
+ skillTotal = skillResult.total;
313
+ for (const skill of skillResult.skills) {
314
+ results.push({
315
+ type: "skill",
316
+ name: skill.name,
317
+ description: skill.description || "",
318
+ downloads: skill.downloads || 0,
319
+ version: skill.latest_version,
320
+ author: skill.author?.name || void 0,
321
+ category: skill.category || void 0
322
+ });
323
+ }
324
+ }
325
+ if (options.sort === "downloads") {
326
+ results.sort((a, b) => b.downloads - a.downloads);
327
+ } else if (options.sort === "name") {
328
+ results.sort((a, b) => a.name.localeCompare(b.name));
329
+ }
330
+ if (options.json) {
331
+ console.log(
332
+ JSON.stringify(
333
+ {
334
+ results,
335
+ totals: { bundles: bundleTotal, skills: skillTotal }
336
+ },
337
+ null,
338
+ 2
339
+ )
340
+ );
341
+ return;
342
+ }
343
+ if (results.length === 0) {
344
+ console.log(`
345
+ No results found for "${query}"`);
346
+ if (!searchBundles) console.log(" (searched skills only)");
347
+ if (!searchSkillsFlag) console.log(" (searched bundles only)");
348
+ return;
349
+ }
350
+ const totalResults = bundleTotal + skillTotal;
351
+ const typeFilter = options.type ? ` (${options.type}s only)` : "";
352
+ console.log(
353
+ `
354
+ Found ${totalResults} result(s) for "${query}"${typeFilter}:`
355
+ );
356
+ const bundles = results.filter((r) => r.type === "bundle");
357
+ const skills = results.filter((r) => r.type === "skill");
358
+ if (bundles.length > 0) {
359
+ console.log(`
360
+ Bundles (${bundleTotal}):
361
+ `);
362
+ const bundleRows = bundles.map((r) => [
363
+ r.name.length > 38 ? r.name.slice(0, 35) + "..." : r.name,
364
+ r.version || "-",
365
+ certLabel(r.certLevel),
366
+ truncate(r.description, 40)
367
+ ]);
368
+ console.log(table(["NAME", "VERSION", "TRUST", "DESCRIPTION"], bundleRows));
369
+ }
370
+ if (skills.length > 0) {
371
+ console.log(`
372
+ Skills (${skillTotal}):
373
+ `);
374
+ const skillRows = skills.map((r) => [
375
+ r.name.length > 38 ? r.name.slice(0, 35) + "..." : r.name,
376
+ r.version || "-",
377
+ r.category || "-",
378
+ truncate(r.description, 40)
379
+ ]);
380
+ console.log(table(["NAME", "VERSION", "CATEGORY", "DESCRIPTION"], skillRows));
381
+ }
382
+ const currentLimit = options.limit || 20;
383
+ const currentOffset = options.offset || 0;
384
+ if (bundleTotal + skillTotal > currentOffset + results.length) {
385
+ console.log(
386
+ `
387
+ Use --offset ${currentOffset + currentLimit} to see more results.`
388
+ );
389
+ }
390
+ console.log();
391
+ console.log(
392
+ 'Use "mpak bundle show <name>" or "mpak skill show <name>" for details.'
393
+ );
394
+ } catch (error) {
395
+ fmtError(error instanceof Error ? error.message : "Search failed");
396
+ }
397
+ }
398
+
399
+ // src/commands/packages/search.ts
400
+ async function handleSearch(query, options = {}) {
401
+ try {
402
+ const client = createClient();
403
+ const params = { q: query };
404
+ if (options.type) params["type"] = options.type;
405
+ if (options.sort) params["sort"] = options.sort;
406
+ if (options.limit) params["limit"] = options.limit;
407
+ if (options.offset) params["offset"] = options.offset;
408
+ const result = await client.searchBundles(params);
409
+ if (options.json) {
410
+ console.log(JSON.stringify(result, null, 2));
411
+ return;
412
+ }
413
+ if (result.bundles.length === 0) {
414
+ console.log(`
415
+ No bundles found for "${query}"`);
416
+ return;
417
+ }
418
+ console.log(`
419
+ Found ${result.total} bundle(s) for "${query}":
420
+ `);
421
+ const rows = result.bundles.map((b) => [
422
+ b.name,
423
+ `v${b.latest_version}`,
424
+ certLabel(b.certification_level),
425
+ truncate(b.description || "", 50)
426
+ ]);
427
+ console.log(table(["NAME", "VERSION", "TRUST", "DESCRIPTION"], rows));
428
+ console.log();
429
+ if (result.pagination.has_more) {
430
+ const nextOffset = (options.offset || 0) + (options.limit || 20);
431
+ console.log(
432
+ `More results available. Use --offset ${nextOffset} to see more.`
433
+ );
434
+ }
435
+ console.log('Use "mpak show <bundle>" for more details');
436
+ } catch (error) {
437
+ fmtError(error instanceof Error ? error.message : "Failed to search bundles");
438
+ }
439
+ }
440
+
441
+ // src/commands/packages/show.ts
442
+ var CERT_LEVEL_LABELS = {
443
+ 1: "L1 Basic",
444
+ 2: "L2 Verified",
445
+ 3: "L3 Hardened",
446
+ 4: "L4 Certified"
447
+ };
448
+ async function handleShow(packageName, options = {}) {
449
+ try {
450
+ const client = createClient();
451
+ const [bundle, versionsInfo] = await Promise.all([
452
+ client.getBundle(packageName),
453
+ client.getBundleVersions(packageName)
454
+ ]);
455
+ if (options.json) {
456
+ console.log(
457
+ JSON.stringify(
458
+ { ...bundle, versions_detail: versionsInfo.versions },
459
+ null,
460
+ 2
461
+ )
462
+ );
463
+ return;
464
+ }
465
+ const verified = bundle.verified ? "\u2713 " : "";
466
+ const provenance = bundle.provenance ? "\u{1F512} " : "";
467
+ console.log(
468
+ `
469
+ ${verified}${provenance}${bundle.display_name || bundle.name} v${bundle.latest_version}
470
+ `
471
+ );
472
+ if (bundle.description) {
473
+ console.log(bundle.description);
474
+ console.log();
475
+ }
476
+ console.log("Bundle Information:");
477
+ console.log(` Name: ${bundle.name}`);
478
+ if (bundle.author?.name) {
479
+ console.log(` Author: ${bundle.author.name}`);
480
+ }
481
+ if (bundle.server_type) {
482
+ console.log(` Type: ${bundle.server_type}`);
483
+ }
484
+ if (bundle.license) {
485
+ console.log(` License: ${bundle.license}`);
486
+ }
487
+ if (bundle.homepage) {
488
+ console.log(` Homepage: ${bundle.homepage}`);
489
+ }
490
+ console.log();
491
+ const certLevel = bundle.certification_level;
492
+ const certification = bundle.certification;
493
+ if (certLevel != null) {
494
+ const label = CERT_LEVEL_LABELS[certLevel] ?? `L${certLevel}`;
495
+ console.log(`Trust: ${label}`);
496
+ if (certification?.controls_passed != null && certification?.controls_total != null) {
497
+ console.log(` Controls: ${certification.controls_passed}/${certification.controls_total} passed`);
498
+ }
499
+ console.log();
500
+ }
501
+ if (bundle.provenance) {
502
+ console.log("Provenance:");
503
+ console.log(` Repository: ${bundle.provenance.repository}`);
504
+ console.log(` Commit: ${bundle.provenance.sha.substring(0, 12)}`);
505
+ console.log(` Provider: ${bundle.provenance.provider}`);
506
+ console.log();
507
+ }
508
+ console.log("Statistics:");
509
+ console.log(` Downloads: ${bundle.downloads.toLocaleString()}`);
510
+ console.log(
511
+ ` Published: ${new Date(bundle.published_at).toLocaleDateString()}`
512
+ );
513
+ console.log();
514
+ if (bundle.tools && bundle.tools.length > 0) {
515
+ console.log(`Tools (${bundle.tools.length}):`);
516
+ for (const tool of bundle.tools) {
517
+ console.log(` - ${tool.name}`);
518
+ if (tool.description) {
519
+ console.log(` ${tool.description}`);
520
+ }
521
+ }
522
+ console.log();
523
+ }
524
+ if (versionsInfo.versions && versionsInfo.versions.length > 0) {
525
+ console.log(`Versions (${versionsInfo.versions.length}):`);
526
+ const recentVersions = versionsInfo.versions.slice(0, 5);
527
+ for (const version of recentVersions) {
528
+ const date = new Date(
529
+ version.published_at
530
+ ).toLocaleDateString();
531
+ const downloads = version.downloads.toLocaleString();
532
+ const isLatest = version.version === versionsInfo.latest ? " (latest)" : "";
533
+ const provTag = version.provenance ? " \u{1F512}" : "";
534
+ const platformStrs = version.platforms.map(
535
+ (p) => `${p.os}-${p.arch}`
536
+ );
537
+ const platformsDisplay = platformStrs.length > 0 ? ` [${platformStrs.join(", ")}]` : "";
538
+ console.log(
539
+ ` ${version.version}${isLatest}${provTag} - ${date} - ${downloads} downloads${platformsDisplay}`
540
+ );
541
+ }
542
+ if (versionsInfo.versions.length > 5) {
543
+ console.log(
544
+ ` ... and ${versionsInfo.versions.length - 5} more`
545
+ );
546
+ }
547
+ console.log();
548
+ }
549
+ const latestVersion = versionsInfo.versions.find(
550
+ (v) => v.version === versionsInfo.latest
551
+ );
552
+ if (latestVersion && latestVersion.platforms.length > 0) {
553
+ console.log("Available Platforms:");
554
+ for (const platform of latestVersion.platforms) {
555
+ console.log(` - ${platform.os}-${platform.arch}`);
556
+ }
557
+ console.log();
558
+ }
559
+ console.log("Install:");
560
+ console.log(` mpak install ${bundle.name}`);
561
+ console.log();
562
+ console.log("Pull (download only):");
563
+ console.log(` mpak pull ${bundle.name}`);
564
+ } catch (error) {
565
+ fmtError(error instanceof Error ? error.message : "Failed to get bundle details");
566
+ }
567
+ }
568
+
569
+ // src/commands/packages/pull.ts
570
+ import { writeFileSync as writeFileSync2 } from "fs";
571
+ import { resolve } from "path";
572
+ import { MpakClient as MpakClient2 } from "@nimblebrain/mpak-sdk";
573
+ function parsePackageSpec(spec) {
574
+ const lastAtIndex = spec.lastIndexOf("@");
575
+ if (lastAtIndex <= 0) {
576
+ return { name: spec };
577
+ }
578
+ const name = spec.substring(0, lastAtIndex);
579
+ const version = spec.substring(lastAtIndex + 1);
580
+ if (!name.startsWith("@")) {
581
+ return { name: spec };
582
+ }
583
+ return { name, version };
584
+ }
585
+ async function handlePull(packageSpec, options = {}) {
586
+ try {
587
+ const { name, version } = parsePackageSpec(packageSpec);
588
+ const client = createClient();
589
+ const detectedPlatform = MpakClient2.detectPlatform();
590
+ const platform = {
591
+ os: options.os || detectedPlatform.os,
592
+ arch: options.arch || detectedPlatform.arch
593
+ };
594
+ console.log(
595
+ `=> Fetching ${version ? `${name}@${version}` : `${name} (latest)`}...`
596
+ );
597
+ console.log(` Platform: ${platform.os}-${platform.arch}`);
598
+ const downloadInfo = await client.getBundleDownload(
599
+ name,
600
+ version || "latest",
601
+ platform
602
+ );
603
+ if (options.json) {
604
+ console.log(JSON.stringify(downloadInfo, null, 2));
605
+ return;
606
+ }
607
+ const bundle = downloadInfo.bundle;
608
+ console.log(` Version: ${bundle.version}`);
609
+ console.log(
610
+ ` Artifact: ${bundle.platform.os}-${bundle.platform.arch}`
611
+ );
612
+ console.log(
613
+ ` Size: ${(bundle.size / (1024 * 1024)).toFixed(2)} MB`
614
+ );
615
+ const platformSuffix = `${bundle.platform.os}-${bundle.platform.arch}`;
616
+ const defaultFilename = `${name.replace("@", "").replace("/", "-")}-${bundle.version}-${platformSuffix}.mcpb`;
617
+ const outputPath = options.output ? resolve(options.output) : resolve(defaultFilename);
618
+ console.log(`
619
+ => Downloading to ${outputPath}...`);
620
+ const response = await fetch(downloadInfo.url);
621
+ if (!response.ok) {
622
+ throw new Error(`Failed to download bundle: ${response.statusText}`);
623
+ }
624
+ const arrayBuffer = await response.arrayBuffer();
625
+ writeFileSync2(outputPath, Buffer.from(arrayBuffer));
626
+ console.log(`
627
+ => Bundle downloaded successfully!`);
628
+ console.log(` File: ${outputPath}`);
629
+ console.log(` SHA256: ${bundle.sha256.substring(0, 16)}...`);
630
+ } catch (error) {
631
+ fmtError(error instanceof Error ? error.message : "Failed to pull bundle");
632
+ }
633
+ }
634
+
635
+ // src/commands/packages/run.ts
636
+ import { execFileSync, spawn, spawnSync } from "child_process";
637
+ import { createInterface } from "readline";
638
+ import {
639
+ existsSync as existsSync2,
640
+ mkdirSync as mkdirSync2,
641
+ readFileSync as readFileSync2,
642
+ writeFileSync as writeFileSync3,
643
+ chmodSync,
644
+ rmSync,
645
+ statSync
646
+ } from "fs";
647
+ import { createHash } from "crypto";
648
+ import { homedir as homedir2 } from "os";
649
+ import { join as join2, dirname, resolve as resolve2, basename } from "path";
650
+ import { MpakClient as MpakClient3 } from "@nimblebrain/mpak-sdk";
651
+ function parsePackageSpec2(spec) {
652
+ const lastAtIndex = spec.lastIndexOf("@");
653
+ if (lastAtIndex <= 0) {
654
+ return { name: spec };
655
+ }
656
+ const name = spec.substring(0, lastAtIndex);
657
+ const version = spec.substring(lastAtIndex + 1);
658
+ if (!name.startsWith("@")) {
659
+ return { name: spec };
660
+ }
661
+ return { name, version };
662
+ }
663
+ function getCacheDir(packageName) {
664
+ const cacheBase = join2(homedir2(), ".mpak", "cache");
665
+ const safeName = packageName.replace("@", "").replace("/", "-");
666
+ return join2(cacheBase, safeName);
667
+ }
668
+ function getCacheMetadata(cacheDir) {
669
+ const metaPath = join2(cacheDir, ".mpak-meta.json");
670
+ if (!existsSync2(metaPath)) {
671
+ return null;
672
+ }
673
+ try {
674
+ return JSON.parse(readFileSync2(metaPath, "utf8"));
675
+ } catch {
676
+ return null;
677
+ }
678
+ }
679
+ function writeCacheMetadata(cacheDir, metadata) {
680
+ const metaPath = join2(cacheDir, ".mpak-meta.json");
681
+ writeFileSync3(metaPath, JSON.stringify(metadata, null, 2));
682
+ }
683
+ var MAX_UNCOMPRESSED_SIZE = 500 * 1024 * 1024;
684
+ async function extractZip(zipPath, destDir) {
685
+ try {
686
+ const listOutput = execFileSync("unzip", ["-l", zipPath], {
687
+ stdio: "pipe",
688
+ encoding: "utf8"
689
+ });
690
+ const totalMatch = listOutput.match(/^\s*(\d+)\s+\d+\s+files?$/m);
691
+ if (totalMatch) {
692
+ const totalSize = parseInt(totalMatch[1], 10);
693
+ if (totalSize > MAX_UNCOMPRESSED_SIZE) {
694
+ throw new Error(
695
+ `Bundle uncompressed size (${Math.round(totalSize / 1024 / 1024)}MB) exceeds maximum allowed (${MAX_UNCOMPRESSED_SIZE / (1024 * 1024)}MB)`
696
+ );
697
+ }
698
+ }
699
+ } catch (error) {
700
+ if (error instanceof Error && error.message.includes("exceeds maximum allowed")) {
701
+ throw error;
702
+ }
703
+ const message = error instanceof Error ? error.message : String(error);
704
+ throw new Error(`Cannot verify bundle size before extraction: ${message}`);
705
+ }
706
+ mkdirSync2(destDir, { recursive: true });
707
+ try {
708
+ execFileSync("unzip", ["-o", "-q", zipPath, "-d", destDir], {
709
+ stdio: "pipe"
710
+ });
711
+ } catch (error) {
712
+ const message = error instanceof Error ? error.message : String(error);
713
+ throw new Error(`Failed to extract bundle: ${message}`);
714
+ }
715
+ }
716
+ function readManifest(cacheDir) {
717
+ const manifestPath = join2(cacheDir, "manifest.json");
718
+ if (!existsSync2(manifestPath)) {
719
+ throw new Error(`Manifest not found in bundle: ${manifestPath}`);
720
+ }
721
+ return JSON.parse(readFileSync2(manifestPath, "utf8"));
722
+ }
723
+ function resolveArgs(args, cacheDir) {
724
+ return args.map(
725
+ (arg) => arg.replace(/\$\{__dirname\}/g, cacheDir)
726
+ );
727
+ }
728
+ function substituteUserConfig(value, userConfigValues) {
729
+ return value.replace(
730
+ /\$\{user_config\.([^}]+)\}/g,
731
+ (match, key) => {
732
+ return userConfigValues[key] ?? match;
733
+ }
734
+ );
735
+ }
736
+ function substituteEnvVars(env, userConfigValues) {
737
+ if (!env) return {};
738
+ const result = {};
739
+ for (const [key, value] of Object.entries(env)) {
740
+ result[key] = substituteUserConfig(value, userConfigValues);
741
+ }
742
+ return result;
743
+ }
744
+ function getLocalCacheDir(bundlePath) {
745
+ const absolutePath = resolve2(bundlePath);
746
+ const hash = createHash("md5").update(absolutePath).digest("hex").slice(0, 12);
747
+ return join2(homedir2(), ".mpak", "cache", "_local", hash);
748
+ }
749
+ function localBundleNeedsExtract(bundlePath, cacheDir) {
750
+ const metaPath = join2(cacheDir, ".mpak-meta.json");
751
+ if (!existsSync2(metaPath)) return true;
752
+ try {
753
+ const meta = JSON.parse(readFileSync2(metaPath, "utf8"));
754
+ const bundleStat = statSync(bundlePath);
755
+ return bundleStat.mtimeMs > new Date(meta.extractedAt).getTime();
756
+ } catch {
757
+ return true;
758
+ }
759
+ }
760
+ async function promptForValue(field, key) {
761
+ return new Promise((resolvePrompt) => {
762
+ const rl = createInterface({
763
+ input: process.stdin,
764
+ output: process.stderr,
765
+ terminal: true
766
+ });
767
+ const label = field.title || key;
768
+ const hint = field.description ? ` (${field.description})` : "";
769
+ const defaultHint = field.default !== void 0 ? ` [${field.default}]` : "";
770
+ const prompt = `=> ${label}${hint}${defaultHint}: `;
771
+ if (field.sensitive) {
772
+ process.stderr.write(`=> (sensitive input)
773
+ `);
774
+ }
775
+ rl.question(prompt, (answer) => {
776
+ rl.close();
777
+ if (!answer && field.default !== void 0) {
778
+ resolvePrompt(String(field.default));
779
+ } else {
780
+ resolvePrompt(answer);
781
+ }
782
+ });
783
+ });
784
+ }
785
+ function isInteractive() {
786
+ return process.stdin.isTTY === true;
787
+ }
788
+ async function gatherUserConfigValues(packageName, userConfig, configManager) {
789
+ const result = {};
790
+ const storedConfig = configManager.getPackageConfig(packageName) || {};
791
+ const missingRequired = [];
792
+ for (const [key, field] of Object.entries(userConfig)) {
793
+ const storedValue = storedConfig[key];
794
+ if (storedValue !== void 0) {
795
+ result[key] = storedValue;
796
+ } else if (field.default !== void 0) {
797
+ result[key] = String(field.default);
798
+ } else if (field.required) {
799
+ missingRequired.push({ key, field });
800
+ }
801
+ }
802
+ if (missingRequired.length > 0) {
803
+ if (!isInteractive()) {
804
+ const missingKeys = missingRequired.map((m) => m.key).join(", ");
805
+ process.stderr.write(
806
+ `=> Error: Missing required config: ${missingKeys}
807
+ `
808
+ );
809
+ process.stderr.write(
810
+ `=> Run 'mpak config set ${packageName} <key>=<value>' to set values
811
+ `
812
+ );
813
+ process.exit(1);
814
+ }
815
+ process.stderr.write(`=> Package requires configuration:
816
+ `);
817
+ for (const { key, field } of missingRequired) {
818
+ const value = await promptForValue(field, key);
819
+ if (!value && field.required) {
820
+ process.stderr.write(
821
+ `=> Error: ${field.title || key} is required
822
+ `
823
+ );
824
+ process.exit(1);
825
+ }
826
+ result[key] = value;
827
+ if (value) {
828
+ const rl = createInterface({
829
+ input: process.stdin,
830
+ output: process.stderr,
831
+ terminal: true
832
+ });
833
+ await new Promise((resolvePrompt) => {
834
+ rl.question(
835
+ `=> Save ${field.title || key} for future runs? [Y/n]: `,
836
+ (answer) => {
837
+ rl.close();
838
+ if (answer.toLowerCase() !== "n") {
839
+ configManager.setPackageConfigValue(
840
+ packageName,
841
+ key,
842
+ value
843
+ );
844
+ process.stderr.write(
845
+ `=> Saved to ~/.mpak/config.json
846
+ `
847
+ );
848
+ }
849
+ resolvePrompt();
850
+ }
851
+ );
852
+ });
853
+ }
854
+ }
855
+ }
856
+ return result;
857
+ }
858
+ function findPythonCommand() {
859
+ const result = spawnSync("python3", ["--version"], { stdio: "pipe" });
860
+ if (result.status === 0) {
861
+ return "python3";
862
+ }
863
+ return "python";
864
+ }
865
+ async function downloadBundle(downloadUrl, outputPath) {
866
+ const response = await fetch(downloadUrl);
867
+ if (!response.ok) {
868
+ throw new Error(
869
+ `Failed to download bundle: ${response.statusText}`
870
+ );
871
+ }
872
+ const arrayBuffer = await response.arrayBuffer();
873
+ writeFileSync3(outputPath, Buffer.from(arrayBuffer));
874
+ }
875
+ async function handleRun(packageSpec, options = {}) {
876
+ if (!options.local && !packageSpec) {
877
+ process.stderr.write(
878
+ `=> Error: Either provide a package name or use --local <path>
879
+ `
880
+ );
881
+ process.exit(1);
882
+ }
883
+ let cacheDir;
884
+ let packageName;
885
+ if (options.local) {
886
+ const bundlePath = resolve2(options.local);
887
+ if (!existsSync2(bundlePath)) {
888
+ process.stderr.write(
889
+ `=> Error: Bundle not found: ${bundlePath}
890
+ `
891
+ );
892
+ process.exit(1);
893
+ }
894
+ if (!bundlePath.endsWith(".mcpb")) {
895
+ process.stderr.write(
896
+ `=> Error: Not an MCPB bundle: ${bundlePath}
897
+ `
898
+ );
899
+ process.exit(1);
900
+ }
901
+ cacheDir = getLocalCacheDir(bundlePath);
902
+ const needsExtract = options.update || localBundleNeedsExtract(bundlePath, cacheDir);
903
+ if (needsExtract) {
904
+ if (existsSync2(cacheDir)) {
905
+ rmSync(cacheDir, { recursive: true, force: true });
906
+ }
907
+ mkdirSync2(cacheDir, { recursive: true });
908
+ process.stderr.write(
909
+ `=> Extracting ${basename(bundlePath)}...
910
+ `
911
+ );
912
+ await extractZip(bundlePath, cacheDir);
913
+ writeFileSync3(
914
+ join2(cacheDir, ".mpak-meta.json"),
915
+ JSON.stringify({
916
+ localPath: bundlePath,
917
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString()
918
+ })
919
+ );
920
+ }
921
+ const manifest2 = readManifest(cacheDir);
922
+ packageName = manifest2.name;
923
+ process.stderr.write(`=> Running ${packageName} (local)
924
+ `);
925
+ } else {
926
+ const { name, version: requestedVersion } = parsePackageSpec2(packageSpec);
927
+ packageName = name;
928
+ const client = createClient();
929
+ const platform = MpakClient3.detectPlatform();
930
+ cacheDir = getCacheDir(name);
931
+ let needsPull = true;
932
+ const cachedMeta = getCacheMetadata(cacheDir);
933
+ if (cachedMeta && !options.update) {
934
+ if (requestedVersion) {
935
+ needsPull = cachedMeta.version !== requestedVersion;
936
+ } else {
937
+ needsPull = false;
938
+ }
939
+ }
940
+ if (needsPull) {
941
+ const downloadInfo = await client.getBundleDownload(
942
+ name,
943
+ requestedVersion || "latest",
944
+ platform
945
+ );
946
+ const bundle = downloadInfo.bundle;
947
+ if (cachedMeta && cachedMeta.version === bundle.version && !options.update) {
948
+ needsPull = false;
949
+ }
950
+ if (needsPull) {
951
+ const tempPath = join2(
952
+ homedir2(),
953
+ ".mpak",
954
+ "tmp",
955
+ `${Date.now()}.mcpb`
956
+ );
957
+ mkdirSync2(dirname(tempPath), { recursive: true });
958
+ process.stderr.write(
959
+ `=> Pulling ${name}@${bundle.version}...
960
+ `
961
+ );
962
+ await downloadBundle(downloadInfo.url, tempPath);
963
+ if (existsSync2(cacheDir)) {
964
+ rmSync(cacheDir, { recursive: true, force: true });
965
+ }
966
+ mkdirSync2(cacheDir, { recursive: true });
967
+ await extractZip(tempPath, cacheDir);
968
+ writeCacheMetadata(cacheDir, {
969
+ version: bundle.version,
970
+ pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
971
+ platform: bundle.platform
972
+ });
973
+ rmSync(tempPath, { force: true });
974
+ process.stderr.write(
975
+ `=> Cached ${name}@${bundle.version}
976
+ `
977
+ );
978
+ }
979
+ }
980
+ }
981
+ const manifest = readManifest(cacheDir);
982
+ const { type, entry_point, mcp_config } = manifest.server;
983
+ let userConfigValues = {};
984
+ if (manifest.user_config && Object.keys(manifest.user_config).length > 0) {
985
+ const configManager = new ConfigManager();
986
+ userConfigValues = await gatherUserConfigValues(
987
+ packageName,
988
+ manifest.user_config,
989
+ configManager
990
+ );
991
+ }
992
+ const substitutedEnv = substituteEnvVars(
993
+ mcp_config.env,
994
+ userConfigValues
995
+ );
996
+ let command;
997
+ let args;
998
+ const env = {
999
+ ...substitutedEnv,
1000
+ ...process.env
1001
+ };
1002
+ switch (type) {
1003
+ case "binary": {
1004
+ command = join2(cacheDir, entry_point);
1005
+ args = resolveArgs(mcp_config.args || [], cacheDir);
1006
+ try {
1007
+ chmodSync(command, 493);
1008
+ } catch {
1009
+ }
1010
+ break;
1011
+ }
1012
+ case "node": {
1013
+ command = mcp_config.command || "node";
1014
+ if (mcp_config.args && mcp_config.args.length > 0) {
1015
+ args = resolveArgs(mcp_config.args, cacheDir);
1016
+ } else {
1017
+ args = [join2(cacheDir, entry_point)];
1018
+ }
1019
+ break;
1020
+ }
1021
+ case "python": {
1022
+ command = mcp_config.command === "python" ? findPythonCommand() : mcp_config.command || findPythonCommand();
1023
+ if (mcp_config.args && mcp_config.args.length > 0) {
1024
+ args = resolveArgs(mcp_config.args, cacheDir);
1025
+ } else {
1026
+ args = [join2(cacheDir, entry_point)];
1027
+ }
1028
+ const depsDir = join2(cacheDir, "deps");
1029
+ const existingPythonPath = process.env["PYTHONPATH"];
1030
+ env["PYTHONPATH"] = existingPythonPath ? `${depsDir}:${existingPythonPath}` : depsDir;
1031
+ break;
1032
+ }
1033
+ default:
1034
+ throw new Error(`Unsupported server type: ${type}`);
1035
+ }
1036
+ const child = spawn(command, args, {
1037
+ stdio: ["inherit", "inherit", "inherit"],
1038
+ env,
1039
+ cwd: cacheDir
1040
+ });
1041
+ process.on("SIGINT", () => child.kill("SIGINT"));
1042
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
1043
+ child.on("exit", (code) => {
1044
+ process.exit(code ?? 0);
1045
+ });
1046
+ child.on("error", (error) => {
1047
+ process.stderr.write(
1048
+ `=> Failed to start server: ${error.message}
1049
+ `
1050
+ );
1051
+ process.exit(1);
1052
+ });
1053
+ }
1054
+
1055
+ // src/commands/config.ts
1056
+ function maskValue(value) {
1057
+ if (value.length <= 4) {
1058
+ return "*".repeat(value.length);
1059
+ }
1060
+ return value.substring(0, 4) + "*".repeat(value.length - 4);
1061
+ }
1062
+ async function handleConfigSet(packageName, keyValuePairs, _options = {}) {
1063
+ if (keyValuePairs.length === 0) {
1064
+ process.stderr.write(
1065
+ "Error: At least one key=value pair is required\n"
1066
+ );
1067
+ process.stderr.write(
1068
+ "Usage: mpak config set <package> <key>=<value> [<key>=<value>...]\n"
1069
+ );
1070
+ process.exit(1);
1071
+ }
1072
+ const configManager = new ConfigManager();
1073
+ let setCount = 0;
1074
+ for (const pair of keyValuePairs) {
1075
+ const eqIndex = pair.indexOf("=");
1076
+ if (eqIndex === -1) {
1077
+ process.stderr.write(
1078
+ `Error: Invalid format "${pair}". Expected key=value
1079
+ `
1080
+ );
1081
+ process.exit(1);
1082
+ }
1083
+ const key = pair.substring(0, eqIndex);
1084
+ const value = pair.substring(eqIndex + 1);
1085
+ if (!key) {
1086
+ process.stderr.write(`Error: Empty key in "${pair}"
1087
+ `);
1088
+ process.exit(1);
1089
+ }
1090
+ configManager.setPackageConfigValue(packageName, key, value);
1091
+ setCount++;
1092
+ }
1093
+ console.log(`Set ${setCount} config value(s) for ${packageName}`);
1094
+ }
1095
+ async function handleConfigGet(packageName, options = {}) {
1096
+ const configManager = new ConfigManager();
1097
+ const config = configManager.getPackageConfig(packageName);
1098
+ if (!config || Object.keys(config).length === 0) {
1099
+ if (options.json) {
1100
+ console.log(JSON.stringify({}, null, 2));
1101
+ } else {
1102
+ console.log(`No config stored for ${packageName}`);
1103
+ }
1104
+ return;
1105
+ }
1106
+ if (options.json) {
1107
+ const masked = {};
1108
+ for (const [key, value] of Object.entries(config)) {
1109
+ masked[key] = maskValue(value);
1110
+ }
1111
+ console.log(JSON.stringify(masked, null, 2));
1112
+ } else {
1113
+ console.log(`Config for ${packageName}:`);
1114
+ for (const [key, value] of Object.entries(config)) {
1115
+ console.log(` ${key}: ${maskValue(value)}`);
1116
+ }
1117
+ }
1118
+ }
1119
+ async function handleConfigList(options = {}) {
1120
+ const configManager = new ConfigManager();
1121
+ const packages = configManager.listPackagesWithConfig();
1122
+ if (packages.length === 0) {
1123
+ if (options.json) {
1124
+ console.log(JSON.stringify([], null, 2));
1125
+ } else {
1126
+ console.log("No packages have stored config");
1127
+ }
1128
+ return;
1129
+ }
1130
+ if (options.json) {
1131
+ console.log(JSON.stringify(packages, null, 2));
1132
+ } else {
1133
+ console.log("Packages with stored config:");
1134
+ for (const pkg of packages) {
1135
+ const config = configManager.getPackageConfig(pkg);
1136
+ const keyCount = config ? Object.keys(config).length : 0;
1137
+ console.log(
1138
+ ` ${pkg} (${keyCount} value${keyCount === 1 ? "" : "s"})`
1139
+ );
1140
+ }
1141
+ }
1142
+ }
1143
+ async function handleConfigClear(packageName, key, _options = {}) {
1144
+ const configManager = new ConfigManager();
1145
+ if (key) {
1146
+ const cleared = configManager.clearPackageConfigValue(packageName, key);
1147
+ if (cleared) {
1148
+ console.log(`Cleared ${key} for ${packageName}`);
1149
+ } else {
1150
+ console.log(`No value found for ${key} in ${packageName}`);
1151
+ }
1152
+ } else {
1153
+ const cleared = configManager.clearPackageConfig(packageName);
1154
+ if (cleared) {
1155
+ console.log(`Cleared all config for ${packageName}`);
1156
+ } else {
1157
+ console.log(`No config found for ${packageName}`);
1158
+ }
1159
+ }
1160
+ }
1161
+
1162
+ // src/commands/completion.ts
1163
+ var TOP_COMMANDS = [
1164
+ "search",
1165
+ "run",
1166
+ "bundle",
1167
+ "skill",
1168
+ "config",
1169
+ "completion",
1170
+ "help"
1171
+ ];
1172
+ var BUNDLE_SUBCOMMANDS = ["search", "show", "pull", "run"];
1173
+ var SKILL_SUBCOMMANDS = [
1174
+ "validate",
1175
+ "pack",
1176
+ "search",
1177
+ "show",
1178
+ "pull",
1179
+ "install",
1180
+ "list"
1181
+ ];
1182
+ var CONFIG_SUBCOMMANDS = ["set", "get", "list", "clear"];
1183
+ var COMPLETION_SHELLS = ["bash", "zsh", "fish"];
1184
+ function bashScript() {
1185
+ return `# mpak bash completion
1186
+ # Install: eval "$(mpak completion bash)"
1187
+ # Or: mpak completion bash >> ~/.bashrc
1188
+
1189
+ _mpak_completions() {
1190
+ local cur prev words cword
1191
+ _init_completion || return
1192
+
1193
+ local top_commands="${TOP_COMMANDS.join(" ")}"
1194
+ local bundle_sub="${BUNDLE_SUBCOMMANDS.join(" ")}"
1195
+ local skill_sub="${SKILL_SUBCOMMANDS.join(" ")}"
1196
+ local config_sub="${CONFIG_SUBCOMMANDS.join(" ")}"
1197
+ local completion_shells="${COMPLETION_SHELLS.join(" ")}"
1198
+
1199
+ case "\${cword}" in
1200
+ 1)
1201
+ COMPREPLY=( $(compgen -W "\${top_commands}" -- "\${cur}") )
1202
+ return
1203
+ ;;
1204
+ 2)
1205
+ case "\${prev}" in
1206
+ bundle)
1207
+ COMPREPLY=( $(compgen -W "\${bundle_sub}" -- "\${cur}") )
1208
+ return
1209
+ ;;
1210
+ skill)
1211
+ COMPREPLY=( $(compgen -W "\${skill_sub}" -- "\${cur}") )
1212
+ return
1213
+ ;;
1214
+ config)
1215
+ COMPREPLY=( $(compgen -W "\${config_sub}" -- "\${cur}") )
1216
+ return
1217
+ ;;
1218
+ completion)
1219
+ COMPREPLY=( $(compgen -W "\${completion_shells}" -- "\${cur}") )
1220
+ return
1221
+ ;;
1222
+ esac
1223
+ ;;
1224
+ esac
1225
+ }
1226
+
1227
+ complete -F _mpak_completions mpak
1228
+ `;
1229
+ }
1230
+ function zshScript() {
1231
+ return `#compdef mpak
1232
+ # mpak zsh completion
1233
+ # Install: eval "$(mpak completion zsh)"
1234
+ # Or: mpak completion zsh > ~/.zsh/completions/_mpak
1235
+
1236
+ _mpak() {
1237
+ local -a top_commands bundle_sub skill_sub config_sub completion_shells
1238
+
1239
+ top_commands=(
1240
+ 'search:Search bundles and skills'
1241
+ 'run:Run an MCP server'
1242
+ 'bundle:MCP bundle commands'
1243
+ 'skill:Agent skill commands'
1244
+ 'config:Manage per-package configuration'
1245
+ 'completion:Generate shell completions'
1246
+ 'help:Display help'
1247
+ )
1248
+
1249
+ bundle_sub=(
1250
+ 'search:Search public bundles'
1251
+ 'show:Show bundle details'
1252
+ 'pull:Download a bundle'
1253
+ 'run:Run an MCP server from the registry'
1254
+ )
1255
+
1256
+ skill_sub=(
1257
+ 'validate:Validate a skill directory'
1258
+ 'pack:Create a .skill bundle'
1259
+ 'search:Search skills in the registry'
1260
+ 'show:Show skill details'
1261
+ 'pull:Download a .skill bundle'
1262
+ 'install:Install a skill to ~/.claude/skills/'
1263
+ 'list:List installed skills'
1264
+ )
1265
+
1266
+ config_sub=(
1267
+ 'set:Set config value(s) for a package'
1268
+ 'get:Show stored config for a package'
1269
+ 'list:List all packages with stored config'
1270
+ 'clear:Clear config for a package'
1271
+ )
1272
+
1273
+ completion_shells=(
1274
+ 'bash:Generate bash completions'
1275
+ 'zsh:Generate zsh completions'
1276
+ 'fish:Generate fish completions'
1277
+ )
1278
+
1279
+ if (( CURRENT == 2 )); then
1280
+ _describe -t commands 'mpak commands' top_commands
1281
+ return
1282
+ fi
1283
+
1284
+ case "\${words[2]}" in
1285
+ bundle)
1286
+ if (( CURRENT == 3 )); then
1287
+ _describe -t commands 'bundle commands' bundle_sub
1288
+ fi
1289
+ ;;
1290
+ skill)
1291
+ if (( CURRENT == 3 )); then
1292
+ _describe -t commands 'skill commands' skill_sub
1293
+ fi
1294
+ ;;
1295
+ config)
1296
+ if (( CURRENT == 3 )); then
1297
+ _describe -t commands 'config commands' config_sub
1298
+ fi
1299
+ ;;
1300
+ completion)
1301
+ if (( CURRENT == 3 )); then
1302
+ _describe -t commands 'shells' completion_shells
1303
+ fi
1304
+ ;;
1305
+ esac
1306
+ }
1307
+
1308
+ _mpak "$@"
1309
+ `;
1310
+ }
1311
+ function fishScript() {
1312
+ return `# mpak fish completion
1313
+ # Install: mpak completion fish | source
1314
+ # Or: mpak completion fish > ~/.config/fish/completions/mpak.fish
1315
+
1316
+ # Disable file completions by default
1317
+ complete -c mpak -f
1318
+
1319
+ # Top-level commands
1320
+ complete -c mpak -n '__fish_use_subcommand' -a search -d 'Search bundles and skills'
1321
+ complete -c mpak -n '__fish_use_subcommand' -a run -d 'Run an MCP server'
1322
+ complete -c mpak -n '__fish_use_subcommand' -a bundle -d 'MCP bundle commands'
1323
+ complete -c mpak -n '__fish_use_subcommand' -a skill -d 'Agent skill commands'
1324
+ complete -c mpak -n '__fish_use_subcommand' -a config -d 'Manage per-package configuration'
1325
+ complete -c mpak -n '__fish_use_subcommand' -a completion -d 'Generate shell completions'
1326
+ complete -c mpak -n '__fish_use_subcommand' -a help -d 'Display help'
1327
+
1328
+ # bundle subcommands
1329
+ complete -c mpak -n '__fish_seen_subcommand_from bundle; and not __fish_seen_subcommand_from search show pull run' -a search -d 'Search public bundles'
1330
+ complete -c mpak -n '__fish_seen_subcommand_from bundle; and not __fish_seen_subcommand_from search show pull run' -a show -d 'Show bundle details'
1331
+ complete -c mpak -n '__fish_seen_subcommand_from bundle; and not __fish_seen_subcommand_from search show pull run' -a pull -d 'Download a bundle'
1332
+ complete -c mpak -n '__fish_seen_subcommand_from bundle; and not __fish_seen_subcommand_from search show pull run' -a run -d 'Run an MCP server from the registry'
1333
+
1334
+ # skill subcommands
1335
+ complete -c mpak -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from validate pack search show pull install list' -a validate -d 'Validate a skill directory'
1336
+ complete -c mpak -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from validate pack search show pull install list' -a pack -d 'Create a .skill bundle'
1337
+ complete -c mpak -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from validate pack search show pull install list' -a search -d 'Search skills in the registry'
1338
+ complete -c mpak -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from validate pack search show pull install list' -a show -d 'Show skill details'
1339
+ complete -c mpak -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from validate pack search show pull install list' -a pull -d 'Download a .skill bundle'
1340
+ complete -c mpak -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from validate pack search show pull install list' -a install -d 'Install a skill to ~/.claude/skills/'
1341
+ complete -c mpak -n '__fish_seen_subcommand_from skill; and not __fish_seen_subcommand_from validate pack search show pull install list' -a list -d 'List installed skills'
1342
+
1343
+ # config subcommands
1344
+ complete -c mpak -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list clear' -a set -d 'Set config value(s) for a package'
1345
+ complete -c mpak -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list clear' -a get -d 'Show stored config for a package'
1346
+ complete -c mpak -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list clear' -a list -d 'List all packages with stored config'
1347
+ complete -c mpak -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list clear' -a clear -d 'Clear config for a package'
1348
+
1349
+ # completion subcommands
1350
+ complete -c mpak -n '__fish_seen_subcommand_from completion; and not __fish_seen_subcommand_from bash zsh fish' -a bash -d 'Generate bash completions'
1351
+ complete -c mpak -n '__fish_seen_subcommand_from completion; and not __fish_seen_subcommand_from bash zsh fish' -a zsh -d 'Generate zsh completions'
1352
+ complete -c mpak -n '__fish_seen_subcommand_from completion; and not __fish_seen_subcommand_from bash zsh fish' -a fish -d 'Generate fish completions'
1353
+ `;
1354
+ }
1355
+ function handleCompletion(shell) {
1356
+ switch (shell) {
1357
+ case "bash":
1358
+ process.stdout.write(bashScript());
1359
+ break;
1360
+ case "zsh":
1361
+ process.stdout.write(zshScript());
1362
+ break;
1363
+ case "fish":
1364
+ process.stdout.write(fishScript());
1365
+ break;
1366
+ default:
1367
+ process.stderr.write(
1368
+ `Unsupported shell: ${shell}
1369
+ Supported shells: bash, zsh, fish
1370
+ `
1371
+ );
1372
+ process.exit(1);
1373
+ }
1374
+ }
1375
+
1376
+ // src/commands/skills/validate.ts
1377
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "fs";
1378
+ import { join as join3, basename as basename2 } from "path";
1379
+ import matter from "gray-matter";
1380
+ import { SkillFrontmatterSchema } from "@nimblebrain/mpak-schemas";
1381
+ function validateSkillDirectory(skillPath) {
1382
+ const result = {
1383
+ valid: true,
1384
+ name: null,
1385
+ path: skillPath,
1386
+ frontmatter: null,
1387
+ errors: [],
1388
+ warnings: []
1389
+ };
1390
+ if (!existsSync3(skillPath)) {
1391
+ result.valid = false;
1392
+ result.errors.push(`Directory not found: ${skillPath}`);
1393
+ return result;
1394
+ }
1395
+ const stats = statSync2(skillPath);
1396
+ if (!stats.isDirectory()) {
1397
+ result.valid = false;
1398
+ result.errors.push(`Path is not a directory: ${skillPath}`);
1399
+ return result;
1400
+ }
1401
+ const skillMdPath = join3(skillPath, "SKILL.md");
1402
+ if (!existsSync3(skillMdPath)) {
1403
+ result.valid = false;
1404
+ result.errors.push("SKILL.md not found");
1405
+ return result;
1406
+ }
1407
+ let content;
1408
+ try {
1409
+ content = readFileSync3(skillMdPath, "utf-8");
1410
+ } catch (err) {
1411
+ result.valid = false;
1412
+ result.errors.push(`Failed to read SKILL.md: ${err}`);
1413
+ return result;
1414
+ }
1415
+ let parsed;
1416
+ try {
1417
+ parsed = matter(content);
1418
+ } catch (err) {
1419
+ result.valid = false;
1420
+ result.errors.push(`Failed to parse frontmatter: ${err}`);
1421
+ return result;
1422
+ }
1423
+ if (!parsed.data || Object.keys(parsed.data).length === 0) {
1424
+ result.valid = false;
1425
+ result.errors.push("No frontmatter found in SKILL.md");
1426
+ return result;
1427
+ }
1428
+ const validation = SkillFrontmatterSchema.safeParse(parsed.data);
1429
+ if (!validation.success) {
1430
+ result.valid = false;
1431
+ for (const issue of validation.error.issues) {
1432
+ const path = issue.path.join(".");
1433
+ result.errors.push(`${path}: ${issue.message}`);
1434
+ }
1435
+ return result;
1436
+ }
1437
+ result.frontmatter = validation.data;
1438
+ result.name = validation.data.name;
1439
+ const dirName = basename2(skillPath);
1440
+ if (validation.data.name !== dirName) {
1441
+ result.valid = false;
1442
+ result.errors.push(
1443
+ `Skill name "${validation.data.name}" does not match directory name "${dirName}"`
1444
+ );
1445
+ }
1446
+ const contents = readdirSync(skillPath);
1447
+ const optionalDirs = ["scripts", "references", "assets"];
1448
+ for (const dir of optionalDirs) {
1449
+ if (contents.includes(dir)) {
1450
+ const dirPath = join3(skillPath, dir);
1451
+ if (!statSync2(dirPath).isDirectory()) {
1452
+ result.warnings.push(
1453
+ `"${dir}" exists but is not a directory`
1454
+ );
1455
+ }
1456
+ }
1457
+ }
1458
+ if (!validation.data.metadata) {
1459
+ result.warnings.push(
1460
+ "No metadata field - consider adding for better discovery"
1461
+ );
1462
+ } else {
1463
+ const meta = validation.data.metadata;
1464
+ if (!meta.tags || meta.tags.length === 0) {
1465
+ result.warnings.push(
1466
+ "No tags in metadata - consider adding for better discovery"
1467
+ );
1468
+ }
1469
+ if (!meta.category) {
1470
+ result.warnings.push(
1471
+ "No category in metadata - consider adding for better discovery"
1472
+ );
1473
+ }
1474
+ if (!meta.version) {
1475
+ result.warnings.push(
1476
+ "No version in metadata - required for registry publishing"
1477
+ );
1478
+ }
1479
+ }
1480
+ return result;
1481
+ }
1482
+ function formatValidationResult(result) {
1483
+ const lines = [];
1484
+ if (result.valid) {
1485
+ lines.push(`\u2713 Valid: ${result.name || result.path}`);
1486
+ } else {
1487
+ lines.push(`\u2717 Invalid: ${result.name || result.path}`);
1488
+ }
1489
+ lines.push("");
1490
+ if (result.frontmatter) {
1491
+ lines.push("\u2713 SKILL.md found");
1492
+ lines.push("\u2713 Required fields");
1493
+ lines.push(` \u251C\u2500 name: ${result.frontmatter.name}`);
1494
+ lines.push(
1495
+ ` \u2514\u2500 description: ${result.frontmatter.description.slice(0, 60)}${result.frontmatter.description.length > 60 ? "..." : ""} (${result.frontmatter.description.length} chars)`
1496
+ );
1497
+ const optionalFields = [];
1498
+ if (result.frontmatter.license)
1499
+ optionalFields.push(`license: ${result.frontmatter.license}`);
1500
+ if (result.frontmatter.compatibility)
1501
+ optionalFields.push(
1502
+ `compatibility: ${result.frontmatter.compatibility}`
1503
+ );
1504
+ if (result.frontmatter["allowed-tools"])
1505
+ optionalFields.push(
1506
+ `allowed-tools: ${result.frontmatter["allowed-tools"]}`
1507
+ );
1508
+ if (optionalFields.length > 0) {
1509
+ lines.push("");
1510
+ lines.push("\u2713 Optional fields");
1511
+ optionalFields.forEach((field, i) => {
1512
+ const prefix = i === optionalFields.length - 1 ? "\u2514\u2500" : "\u251C\u2500";
1513
+ lines.push(` ${prefix} ${field}`);
1514
+ });
1515
+ }
1516
+ if (result.frontmatter.metadata) {
1517
+ const meta = result.frontmatter.metadata;
1518
+ lines.push("");
1519
+ lines.push("\u2713 Discovery metadata (metadata:)");
1520
+ if (meta.tags)
1521
+ lines.push(
1522
+ ` \u251C\u2500 tags: [${meta.tags.join(", ")}]`
1523
+ );
1524
+ if (meta.category)
1525
+ lines.push(` \u251C\u2500 category: ${meta.category}`);
1526
+ if (meta.triggers)
1527
+ lines.push(
1528
+ ` \u251C\u2500 triggers: ${meta.triggers.length} defined`
1529
+ );
1530
+ if (meta.version)
1531
+ lines.push(` \u251C\u2500 version: ${meta.version}`);
1532
+ if (meta.author)
1533
+ lines.push(` \u2514\u2500 author: ${meta.author.name}`);
1534
+ }
1535
+ }
1536
+ if (result.errors.length > 0) {
1537
+ lines.push("");
1538
+ lines.push("Errors:");
1539
+ result.errors.forEach((err) => lines.push(` \u2717 ${err}`));
1540
+ }
1541
+ if (result.warnings.length > 0) {
1542
+ lines.push("");
1543
+ lines.push("Warnings:");
1544
+ result.warnings.forEach(
1545
+ (warn) => lines.push(` \u26A0 ${warn}`)
1546
+ );
1547
+ }
1548
+ return lines.join("\n");
1549
+ }
1550
+ async function handleSkillValidate(skillPath, options) {
1551
+ const result = validateSkillDirectory(skillPath);
1552
+ if (options.json) {
1553
+ console.log(JSON.stringify(result, null, 2));
1554
+ } else {
1555
+ console.log("");
1556
+ console.log(`Validating ${skillPath}...`);
1557
+ console.log("");
1558
+ console.log(formatValidationResult(result));
1559
+ }
1560
+ if (!result.valid) {
1561
+ process.exit(1);
1562
+ }
1563
+ }
1564
+
1565
+ // src/commands/skills/pack.ts
1566
+ import { createWriteStream, createReadStream } from "fs";
1567
+ import { createHash as createHash2 } from "crypto";
1568
+ import { basename as basename3, join as join4, resolve as resolve3 } from "path";
1569
+ import archiver from "archiver";
1570
+ async function packSkill(skillPath, outputPath) {
1571
+ const validation = validateSkillDirectory(skillPath);
1572
+ if (!validation.valid) {
1573
+ return {
1574
+ success: false,
1575
+ path: null,
1576
+ name: null,
1577
+ version: null,
1578
+ size: null,
1579
+ sha256: null,
1580
+ error: `Validation failed:
1581
+ ${formatValidationResult(validation)}`
1582
+ };
1583
+ }
1584
+ const skillName = validation.name;
1585
+ const version = validation.frontmatter?.metadata?.version || "0.0.0";
1586
+ const bundleName = `${skillName}-${version}.skill`;
1587
+ const finalOutputPath = outputPath || join4(process.cwd(), bundleName);
1588
+ return new Promise((resolvePromise) => {
1589
+ const output = createWriteStream(finalOutputPath);
1590
+ const archive = archiver("zip", { zlib: { level: 9 } });
1591
+ output.on("close", () => {
1592
+ const size = archive.pointer();
1593
+ const hash = createHash2("sha256");
1594
+ const stream = createReadStream(finalOutputPath);
1595
+ stream.on("data", (chunk) => hash.update(chunk));
1596
+ stream.on("end", () => {
1597
+ const sha256 = hash.digest("hex");
1598
+ resolvePromise({
1599
+ success: true,
1600
+ path: finalOutputPath,
1601
+ name: skillName,
1602
+ version,
1603
+ size,
1604
+ sha256,
1605
+ error: null
1606
+ });
1607
+ });
1608
+ stream.on("error", (err) => {
1609
+ resolvePromise({
1610
+ success: false,
1611
+ path: null,
1612
+ name: skillName,
1613
+ version,
1614
+ size: null,
1615
+ sha256: null,
1616
+ error: `Failed to calculate SHA256: ${err.message}`
1617
+ });
1618
+ });
1619
+ });
1620
+ archive.on("error", (err) => {
1621
+ resolvePromise({
1622
+ success: false,
1623
+ path: null,
1624
+ name: skillName,
1625
+ version,
1626
+ size: null,
1627
+ sha256: null,
1628
+ error: `Archive error: ${err.message}`
1629
+ });
1630
+ });
1631
+ archive.pipe(output);
1632
+ const dirName = basename3(resolve3(skillPath));
1633
+ archive.directory(skillPath, dirName);
1634
+ archive.finalize();
1635
+ });
1636
+ }
1637
+ function formatSize2(bytes) {
1638
+ if (bytes < 1024) return `${bytes} B`;
1639
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1640
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1641
+ }
1642
+ async function handleSkillPack(skillPath, options) {
1643
+ console.log("");
1644
+ console.log(`Validating ${skillPath}...`);
1645
+ const result = await packSkill(skillPath, options.output);
1646
+ if (options.json) {
1647
+ console.log(JSON.stringify(result, null, 2));
1648
+ } else if (result.success) {
1649
+ console.log(`\u2713 Valid: ${result.name}`);
1650
+ console.log("");
1651
+ console.log("Creating bundle...");
1652
+ console.log(
1653
+ `\u2713 Created: ${basename3(result.path)} (${formatSize2(result.size)})`
1654
+ );
1655
+ console.log(` SHA256: ${result.sha256}`);
1656
+ } else {
1657
+ console.log(result.error);
1658
+ }
1659
+ if (!result.success) {
1660
+ process.exit(1);
1661
+ }
1662
+ }
1663
+
1664
+ // src/commands/skills/search.ts
1665
+ async function handleSkillSearch(query, options) {
1666
+ try {
1667
+ const client = createClient();
1668
+ const params = { q: query };
1669
+ if (options.tags) params["tags"] = options.tags;
1670
+ if (options.category) params["category"] = options.category;
1671
+ if (options.sort) params["sort"] = options.sort;
1672
+ if (options.limit) params["limit"] = options.limit;
1673
+ if (options.offset) params["offset"] = options.offset;
1674
+ const result = await client.searchSkills(params);
1675
+ if (options.json) {
1676
+ console.log(JSON.stringify(result, null, 2));
1677
+ return;
1678
+ }
1679
+ if (result.skills.length === 0) {
1680
+ console.log(`No skills found for "${query}"`);
1681
+ return;
1682
+ }
1683
+ console.log();
1684
+ const rows = result.skills.map((s) => [
1685
+ s.name.length > 42 ? s.name.slice(0, 39) + "..." : s.name,
1686
+ s.latest_version || "-",
1687
+ s.category || "-",
1688
+ truncate(s.description || "", 40)
1689
+ ]);
1690
+ console.log(
1691
+ table(["NAME", "VERSION", "CATEGORY", "DESCRIPTION"], rows)
1692
+ );
1693
+ if (result.pagination.has_more) {
1694
+ console.log();
1695
+ console.log(
1696
+ `Showing ${result.skills.length} of ${result.total} results. Use --offset to see more.`
1697
+ );
1698
+ }
1699
+ } catch (err) {
1700
+ fmtError(err instanceof Error ? err.message : String(err));
1701
+ }
1702
+ }
1703
+
1704
+ // src/commands/skills/show.ts
1705
+ async function handleSkillShow(name, options) {
1706
+ try {
1707
+ const client = createClient();
1708
+ const skill = await client.getSkill(name);
1709
+ if (options.json) {
1710
+ console.log(JSON.stringify(skill, null, 2));
1711
+ return;
1712
+ }
1713
+ console.log("");
1714
+ console.log(`${skill.name}@${skill.latest_version}`);
1715
+ console.log("");
1716
+ console.log(skill.description);
1717
+ console.log("");
1718
+ console.log("Metadata:");
1719
+ if (skill.license) console.log(` License: ${skill.license}`);
1720
+ if (skill.category)
1721
+ console.log(` Category: ${skill.category}`);
1722
+ if (skill.tags && skill.tags.length > 0)
1723
+ console.log(` Tags: ${skill.tags.join(", ")}`);
1724
+ if (skill.author)
1725
+ console.log(
1726
+ ` Author: ${skill.author.name}${skill.author.url ? ` (${skill.author.url})` : ""}`
1727
+ );
1728
+ console.log(
1729
+ ` Downloads: ${skill.downloads.toLocaleString()}`
1730
+ );
1731
+ console.log(
1732
+ ` Published: ${new Date(skill.published_at).toLocaleDateString()}`
1733
+ );
1734
+ if (skill.triggers && skill.triggers.length > 0) {
1735
+ console.log("");
1736
+ console.log("Triggers:");
1737
+ skill.triggers.forEach(
1738
+ (t) => console.log(` - ${t}`)
1739
+ );
1740
+ }
1741
+ if (skill.examples && skill.examples.length > 0) {
1742
+ console.log("");
1743
+ console.log("Examples:");
1744
+ skill.examples.forEach(
1745
+ (ex) => {
1746
+ console.log(
1747
+ ` - "${ex.prompt}"${ex.context ? ` (${ex.context})` : ""}`
1748
+ );
1749
+ }
1750
+ );
1751
+ }
1752
+ if (skill.versions && skill.versions.length > 0) {
1753
+ console.log("");
1754
+ console.log("Versions:");
1755
+ skill.versions.slice(0, 5).forEach(
1756
+ (v) => {
1757
+ console.log(
1758
+ ` ${v.version.padEnd(12)} ${new Date(v.published_at).toLocaleDateString().padEnd(12)} ${v.downloads.toLocaleString()} downloads`
1759
+ );
1760
+ }
1761
+ );
1762
+ if (skill.versions.length > 5) {
1763
+ console.log(
1764
+ ` ... and ${skill.versions.length - 5} more`
1765
+ );
1766
+ }
1767
+ }
1768
+ console.log("");
1769
+ console.log(`Install: mpak skill install ${skill.name}`);
1770
+ } catch (err) {
1771
+ fmtError(err instanceof Error ? err.message : String(err));
1772
+ }
1773
+ }
1774
+
1775
+ // src/commands/skills/pull.ts
1776
+ import { writeFileSync as writeFileSync4 } from "fs";
1777
+ import { basename as basename4, join as join5 } from "path";
1778
+ function parseSkillSpec(spec) {
1779
+ const atIndex = spec.lastIndexOf("@");
1780
+ if (atIndex <= 0) {
1781
+ return { name: spec };
1782
+ }
1783
+ const slashIndex = spec.indexOf("/");
1784
+ if (atIndex > slashIndex) {
1785
+ return {
1786
+ name: spec.slice(0, atIndex),
1787
+ version: spec.slice(atIndex + 1)
1788
+ };
1789
+ }
1790
+ return { name: spec };
1791
+ }
1792
+ async function handleSkillPull(skillSpec, options) {
1793
+ try {
1794
+ const { name, version } = parseSkillSpec(skillSpec);
1795
+ const client = createClient();
1796
+ const downloadInfo = version ? await client.getSkillVersionDownload(name, version) : await client.getSkillDownload(name);
1797
+ console.log(
1798
+ `Pulling ${downloadInfo.skill.name}@${downloadInfo.skill.version}...`
1799
+ );
1800
+ const response = await fetch(downloadInfo.url);
1801
+ if (!response.ok) {
1802
+ throw new Error(`Download failed (${response.status})`);
1803
+ }
1804
+ const buffer = Buffer.from(await response.arrayBuffer());
1805
+ if (downloadInfo.skill.sha256) {
1806
+ const { createHash: createHash3 } = await import("crypto");
1807
+ const hash = createHash3("sha256").update(buffer).digest("hex");
1808
+ if (hash !== downloadInfo.skill.sha256) {
1809
+ throw new Error(
1810
+ `SHA256 mismatch: expected ${downloadInfo.skill.sha256}, got ${hash}`
1811
+ );
1812
+ }
1813
+ }
1814
+ const filename = `${basename4(downloadInfo.skill.name.replace("@", "").replace("/", "-"))}-${downloadInfo.skill.version}.skill`;
1815
+ const outputPath = options.output || join5(process.cwd(), filename);
1816
+ writeFileSync4(outputPath, buffer);
1817
+ if (options.json) {
1818
+ console.log(
1819
+ JSON.stringify(
1820
+ {
1821
+ path: outputPath,
1822
+ name: downloadInfo.skill.name,
1823
+ version: downloadInfo.skill.version,
1824
+ size: downloadInfo.skill.size,
1825
+ sha256: downloadInfo.skill.sha256
1826
+ },
1827
+ null,
1828
+ 2
1829
+ )
1830
+ );
1831
+ } else {
1832
+ console.log(
1833
+ `Downloaded ${filename} (${formatSize(downloadInfo.skill.size)})`
1834
+ );
1835
+ console.log(` SHA256: ${downloadInfo.skill.sha256}`);
1836
+ console.log(` Path: ${outputPath}`);
1837
+ }
1838
+ } catch (err) {
1839
+ fmtError(err instanceof Error ? err.message : String(err));
1840
+ }
1841
+ }
1842
+
1843
+ // src/commands/skills/install.ts
1844
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, rmSync as rmSync2 } from "fs";
1845
+ import { join as join6, basename as basename5 } from "path";
1846
+ import { homedir as homedir3, tmpdir } from "os";
1847
+ import { execFileSync as execFileSync2 } from "child_process";
1848
+ function getSkillsDir() {
1849
+ return join6(homedir3(), ".claude", "skills");
1850
+ }
1851
+ function parseSkillSpec2(spec) {
1852
+ const atIndex = spec.lastIndexOf("@");
1853
+ if (atIndex <= 0) {
1854
+ return { name: spec };
1855
+ }
1856
+ const slashIndex = spec.indexOf("/");
1857
+ if (atIndex > slashIndex) {
1858
+ return {
1859
+ name: spec.slice(0, atIndex),
1860
+ version: spec.slice(atIndex + 1)
1861
+ };
1862
+ }
1863
+ return { name: spec };
1864
+ }
1865
+ function getShortName(scopedName) {
1866
+ const parts = scopedName.replace("@", "").split("/");
1867
+ return parts[parts.length - 1];
1868
+ }
1869
+ async function handleSkillInstall(skillSpec, options) {
1870
+ try {
1871
+ const { name, version } = parseSkillSpec2(skillSpec);
1872
+ const client = createClient();
1873
+ const downloadInfo = version ? await client.getSkillVersionDownload(name, version) : await client.getSkillDownload(name);
1874
+ const shortName = getShortName(downloadInfo.skill.name);
1875
+ const skillsDir = getSkillsDir();
1876
+ const installPath = join6(skillsDir, shortName);
1877
+ if (existsSync4(installPath) && !options.force) {
1878
+ console.error(
1879
+ `Skill "${shortName}" is already installed at ${installPath}`
1880
+ );
1881
+ console.error("Use --force to overwrite");
1882
+ process.exit(1);
1883
+ }
1884
+ console.log(
1885
+ `Pulling ${downloadInfo.skill.name}@${downloadInfo.skill.version}...`
1886
+ );
1887
+ const response = await fetch(downloadInfo.url);
1888
+ if (!response.ok) {
1889
+ throw new Error(`Download failed (${response.status})`);
1890
+ }
1891
+ const buffer = Buffer.from(await response.arrayBuffer());
1892
+ if (downloadInfo.skill.sha256) {
1893
+ const { createHash: createHash3 } = await import("crypto");
1894
+ const hash = createHash3("sha256").update(buffer).digest("hex");
1895
+ if (hash !== downloadInfo.skill.sha256) {
1896
+ throw new Error(
1897
+ `SHA256 mismatch: expected ${downloadInfo.skill.sha256}, got ${hash}`
1898
+ );
1899
+ }
1900
+ }
1901
+ console.log(
1902
+ `Downloaded ${basename5(downloadInfo.skill.name)}-${downloadInfo.skill.version}.skill (${formatSize(downloadInfo.skill.size)})`
1903
+ );
1904
+ if (!existsSync4(skillsDir)) {
1905
+ mkdirSync3(skillsDir, { recursive: true });
1906
+ }
1907
+ const tempPath = join6(tmpdir(), `skill-${Date.now()}.skill`);
1908
+ writeFileSync5(tempPath, buffer);
1909
+ if (existsSync4(installPath)) {
1910
+ rmSync2(installPath, { recursive: true });
1911
+ }
1912
+ try {
1913
+ execFileSync2("unzip", ["-o", tempPath, "-d", skillsDir], {
1914
+ stdio: "pipe"
1915
+ });
1916
+ } catch (err) {
1917
+ throw new Error(`Failed to extract skill bundle: ${err}`);
1918
+ } finally {
1919
+ rmSync2(tempPath, { force: true });
1920
+ }
1921
+ if (options.json) {
1922
+ console.log(
1923
+ JSON.stringify(
1924
+ {
1925
+ installed: true,
1926
+ name: downloadInfo.skill.name,
1927
+ shortName,
1928
+ version: downloadInfo.skill.version,
1929
+ path: installPath
1930
+ },
1931
+ null,
1932
+ 2
1933
+ )
1934
+ );
1935
+ } else {
1936
+ console.log(`Extracting to ${installPath}/`);
1937
+ console.log(`\u2713 Installed: ${shortName}`);
1938
+ console.log("");
1939
+ console.log(
1940
+ "Skill available in Claude Code. Restart to activate."
1941
+ );
1942
+ }
1943
+ } catch (err) {
1944
+ fmtError(err instanceof Error ? err.message : String(err));
1945
+ }
1946
+ }
1947
+
1948
+ // src/commands/skills/list.ts
1949
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
1950
+ import { join as join7 } from "path";
1951
+ import { homedir as homedir4 } from "os";
1952
+ import matter2 from "gray-matter";
1953
+ function getSkillsDir2() {
1954
+ return join7(homedir4(), ".claude", "skills");
1955
+ }
1956
+ function listInstalledSkills() {
1957
+ const skillsDir = getSkillsDir2();
1958
+ if (!existsSync5(skillsDir)) {
1959
+ return [];
1960
+ }
1961
+ const skills = [];
1962
+ const entries = readdirSync2(skillsDir);
1963
+ for (const entry of entries) {
1964
+ if (entry.startsWith(".")) continue;
1965
+ const skillPath = join7(skillsDir, entry);
1966
+ const stat = statSync3(skillPath);
1967
+ if (!stat.isDirectory()) continue;
1968
+ const skillMdPath = join7(skillPath, "SKILL.md");
1969
+ if (!existsSync5(skillMdPath)) continue;
1970
+ try {
1971
+ const content = readFileSync4(skillMdPath, "utf-8");
1972
+ const parsed = matter2(content);
1973
+ skills.push({
1974
+ name: parsed.data["name"] || entry,
1975
+ description: parsed.data["description"] || "",
1976
+ version: parsed.data["metadata"]?.["version"] ?? null,
1977
+ path: skillPath
1978
+ });
1979
+ } catch {
1980
+ skills.push({
1981
+ name: entry,
1982
+ description: "(unable to parse SKILL.md)",
1983
+ version: null,
1984
+ path: skillPath
1985
+ });
1986
+ }
1987
+ }
1988
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
1989
+ }
1990
+ async function handleSkillList(options) {
1991
+ const skills = listInstalledSkills();
1992
+ if (options.json) {
1993
+ console.log(JSON.stringify(skills, null, 2));
1994
+ return;
1995
+ }
1996
+ if (skills.length === 0) {
1997
+ console.log("No skills installed.");
1998
+ console.log("");
1999
+ console.log("Install skills with: mpak skill install <name>");
2000
+ console.log("Or create your own in ~/.claude/skills/");
2001
+ return;
2002
+ }
2003
+ console.log("");
2004
+ console.log("Installed skills:");
2005
+ console.log("");
2006
+ const nameWidth = Math.max(20, ...skills.map((s) => s.name.length)) + 2;
2007
+ const versionWidth = 10;
2008
+ console.log(
2009
+ "NAME".padEnd(nameWidth) + "VERSION".padEnd(versionWidth) + "DESCRIPTION"
2010
+ );
2011
+ for (const skill of skills) {
2012
+ const name = skill.name.padEnd(nameWidth);
2013
+ const version = (skill.version || "-").padEnd(versionWidth);
2014
+ const desc = skill.description.length > 50 ? skill.description.slice(0, 47) + "..." : skill.description;
2015
+ console.log(name + version + desc);
2016
+ }
2017
+ console.log("");
2018
+ console.log(
2019
+ `${skills.length} skill(s) installed in ${getSkillsDir2()}`
2020
+ );
2021
+ }
2022
+
2023
+ // src/program.ts
2024
+ function createProgram() {
2025
+ const program = new Command();
2026
+ program.name("mpak").description("CLI for MCP bundles and Agent Skills").version(getVersion(), "-v, --version", "Output the current version");
2027
+ program.command("search <query>").description("Search bundles and skills").option("--type <type>", "Filter by type (bundle, skill)").option("--sort <field>", "Sort by: downloads, recent, name").option("--limit <number>", "Limit results", parseInt).option("--offset <number>", "Pagination offset", parseInt).option("--json", "Output as JSON").action(async (query, options) => {
2028
+ await handleUnifiedSearch(query, options);
2029
+ });
2030
+ program.command("run [package]").description('Run an MCP server (alias for "bundle run")').option("--update", "Force re-download even if cached").option("-l, --local <path>", "Run a local .mcpb bundle file").action(async (packageSpec, options) => {
2031
+ await handleRun(packageSpec || "", options);
2032
+ });
2033
+ const bundle = program.command("bundle").description("MCP bundle commands");
2034
+ bundle.command("search <query>").description("Search public bundles").option("--type <type>", "Filter by server type (node, python, binary)").option("--sort <field>", "Sort by: downloads, recent, name").option("--limit <number>", "Limit results", parseInt).option("--offset <number>", "Pagination offset", parseInt).option("--json", "Output as JSON").action(async (query, options) => {
2035
+ await handleSearch(query, options);
2036
+ });
2037
+ bundle.command("show <package>").description("Show detailed information about a bundle").option("--json", "Output as JSON").action(async (packageName, options) => {
2038
+ await handleShow(packageName, options);
2039
+ });
2040
+ bundle.command("pull <package>").description("Download a bundle from the registry").option("-o, --output <path>", "Output file path").option("--os <os>", "Target OS (darwin, linux, win32)").option("--arch <arch>", "Target architecture (x64, arm64)").option("--json", "Output download info as JSON").action(async (packageSpec, options) => {
2041
+ await handlePull(packageSpec, options);
2042
+ });
2043
+ bundle.command("run [package]").description("Run an MCP server from the registry").option("--update", "Force re-download even if cached").option("-l, --local <path>", "Run a local .mcpb bundle file").action(async (packageSpec, options) => {
2044
+ await handleRun(packageSpec || "", options);
2045
+ });
2046
+ const skill = program.command("skill").description("Agent skill commands");
2047
+ skill.command("validate <path>").description("Validate a skill directory against the Agent Skills spec").option("--json", "Output as JSON").action(async (path, options) => {
2048
+ await handleSkillValidate(path, options);
2049
+ });
2050
+ skill.command("pack <path>").description("Create a .skill bundle from a skill directory").option("-o, --output <path>", "Output file path").option("--json", "Output as JSON").action(async (path, options) => {
2051
+ await handleSkillPack(path, options);
2052
+ });
2053
+ skill.command("search <query>").description("Search skills in the registry").option("--tags <tags>", "Filter by tags (comma-separated)").option("--category <category>", "Filter by category").option(
2054
+ "--surface <surface>",
2055
+ "Filter by surface (claude-code, claude-api, claude-ai)"
2056
+ ).option("--sort <field>", "Sort by: downloads, recent, name").option("--limit <number>", "Limit results", parseInt).option("--offset <number>", "Pagination offset", parseInt).option("--json", "Output as JSON").action(async (query, options) => {
2057
+ await handleSkillSearch(query, options);
2058
+ });
2059
+ skill.command("show <name>").description("Show detailed information about a skill").option("--json", "Output as JSON").action(async (name, options) => {
2060
+ await handleSkillShow(name, options);
2061
+ });
2062
+ skill.command("pull <name>").description("Download a .skill bundle from the registry").option("-o, --output <path>", "Output file path").option("--json", "Output as JSON").action(async (name, options) => {
2063
+ await handleSkillPull(name, options);
2064
+ });
2065
+ skill.command("install <name>").description("Install a skill to ~/.claude/skills/").option("--force", "Overwrite existing installation").option("--json", "Output as JSON").action(async (name, options) => {
2066
+ await handleSkillInstall(name, options);
2067
+ });
2068
+ skill.command("list").description("List installed skills").option("--json", "Output as JSON").action(async (options) => {
2069
+ await handleSkillList(options);
2070
+ });
2071
+ const configCmd = program.command("config").description("Manage per-package configuration values");
2072
+ configCmd.command("set <package> <key=value...>").description("Set config value(s) for a package").action(async (packageName, keyValuePairs) => {
2073
+ await handleConfigSet(packageName, keyValuePairs);
2074
+ });
2075
+ configCmd.command("get <package>").description("Show stored config for a package (values are masked)").option("--json", "Output as JSON").action(async (packageName, options) => {
2076
+ await handleConfigGet(packageName, options);
2077
+ });
2078
+ configCmd.command("list").description("List all packages with stored config").option("--json", "Output as JSON").action(async (options) => {
2079
+ await handleConfigList(options);
2080
+ });
2081
+ configCmd.command("clear <package> [key]").description("Clear config for a package (all values or specific key)").action(async (packageName, key) => {
2082
+ await handleConfigClear(packageName, key);
2083
+ });
2084
+ program.command("completion <shell>").description("Generate shell completion script (bash, zsh, fish)").action((shell) => {
2085
+ handleCompletion(shell);
2086
+ });
2087
+ return program;
2088
+ }
2089
+
2090
+ // src/utils/errors.ts
2091
+ var CLIError = class extends Error {
2092
+ exitCode;
2093
+ constructor(message, exitCode = 1) {
2094
+ super(message);
2095
+ this.name = "CLIError";
2096
+ this.exitCode = exitCode;
2097
+ }
2098
+ };
2099
+ function handleError(error) {
2100
+ if (error instanceof CLIError) {
2101
+ console.error(`Error: ${error.message}`);
2102
+ process.exit(error.exitCode);
2103
+ }
2104
+ if (error instanceof Error) {
2105
+ console.error(`Error: ${error.message}`);
2106
+ process.exit(1);
2107
+ }
2108
+ console.error("An unexpected error occurred");
2109
+ process.exit(1);
2110
+ }
2111
+
2112
+ // src/index.ts
4
2113
  async function main() {
5
- const program = createProgram();
6
- await program.parseAsync(process.argv);
2114
+ const program = createProgram();
2115
+ await program.parseAsync(process.argv);
7
2116
  }
8
2117
  main().catch(handleError);
9
2118
  //# sourceMappingURL=index.js.map