@mcpak/cli 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.
Files changed (2) hide show
  1. package/dist/index.js +705 -0
  2. package/package.json +31 -0
package/dist/index.js ADDED
@@ -0,0 +1,705 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command12 } from "commander";
5
+
6
+ // src/commands/health.ts
7
+ import { Command } from "commander";
8
+
9
+ // src/lib/registry-client.ts
10
+ var RegistryClient = class {
11
+ constructor(baseUrl, token) {
12
+ this.baseUrl = baseUrl;
13
+ this.token = token;
14
+ }
15
+ headers() {
16
+ const h = {};
17
+ if (this.token) {
18
+ h["Authorization"] = `Bearer ${this.token}`;
19
+ }
20
+ return h;
21
+ }
22
+ async health() {
23
+ const res = await fetch(`${this.baseUrl}/health`);
24
+ if (!res.ok) throw new Error(`Health check failed: ${res.status}`);
25
+ return res.json();
26
+ }
27
+ async getPackage(name) {
28
+ const res = await fetch(`${this.baseUrl}/packages/${name}`);
29
+ if (!res.ok) {
30
+ const body = await res.json().catch(() => ({}));
31
+ throw new Error(
32
+ body.error ?? `Failed to get package: ${res.status}`
33
+ );
34
+ }
35
+ return res.json();
36
+ }
37
+ async downloadVersion(name, version) {
38
+ const res = await fetch(
39
+ `${this.baseUrl}/packages/${name}/${version}/download`
40
+ );
41
+ if (!res.ok) throw new Error(`Download failed: ${res.status}`);
42
+ return res.arrayBuffer();
43
+ }
44
+ async publish(metadata, tarball) {
45
+ const form = new FormData();
46
+ form.append("metadata", JSON.stringify(metadata));
47
+ form.append(
48
+ "tarball",
49
+ new Blob([tarball], { type: "application/gzip" }),
50
+ `${metadata.name}-${metadata.version}.tar.gz`
51
+ );
52
+ const res = await fetch(`${this.baseUrl}/packages/publish`, {
53
+ method: "POST",
54
+ headers: this.headers(),
55
+ body: form
56
+ });
57
+ if (!res.ok) {
58
+ const body = await res.json().catch(() => ({}));
59
+ throw new Error(
60
+ body.error ?? `Publish failed: ${res.status}`
61
+ );
62
+ }
63
+ return res.json();
64
+ }
65
+ async search(query, limit = 20, offset = 0) {
66
+ const params = new URLSearchParams({
67
+ q: query,
68
+ limit: String(limit),
69
+ offset: String(offset)
70
+ });
71
+ const res = await fetch(
72
+ `${this.baseUrl}/packages/search?${params}`
73
+ );
74
+ if (!res.ok) {
75
+ const body = await res.json().catch(() => ({}));
76
+ throw new Error(
77
+ body.error ?? `Search failed: ${res.status}`
78
+ );
79
+ }
80
+ return res.json();
81
+ }
82
+ async checkUpdate(name, currentVersion) {
83
+ const res = await fetch(
84
+ `${this.baseUrl}/packages/${name}/check-update`,
85
+ {
86
+ method: "POST",
87
+ headers: { "Content-Type": "application/json" },
88
+ body: JSON.stringify({ currentVersion })
89
+ }
90
+ );
91
+ if (!res.ok) throw new Error(`Check update failed: ${res.status}`);
92
+ return res.json();
93
+ }
94
+ };
95
+
96
+ // src/lib/paths.ts
97
+ import { join } from "path";
98
+ import { homedir } from "os";
99
+ var MCPAK_HOME = process.env.MCPAK_HOME ?? join(homedir(), ".mcpak");
100
+ var PACKAGES_DIR = join(MCPAK_HOME, "packages");
101
+ var LOCKFILE_PATH = join(MCPAK_HOME, "lockfile.json");
102
+ var CLAUDE_CONFIG_PATH = process.env.CLAUDE_CONFIG ?? join(homedir(), ".claude.json");
103
+ var AUTH_CONFIG_PATH = join(MCPAK_HOME, "auth.json");
104
+ var DEFAULT_REGISTRY = process.env.MCPAK_REGISTRY ?? "https://registry.mcpak.org";
105
+
106
+ // src/lib/ui.ts
107
+ import chalk from "chalk";
108
+ import ora from "ora";
109
+ function success(msg) {
110
+ console.log(chalk.green(`\u2714 ${msg}`));
111
+ }
112
+ function error(msg) {
113
+ console.error(chalk.red(`\u2716 ${msg}`));
114
+ }
115
+ function info(msg) {
116
+ console.log(chalk.cyan(`\u2139 ${msg}`));
117
+ }
118
+ async function withSpinner(text, fn) {
119
+ const spinner = ora(text).start();
120
+ try {
121
+ const result = await fn();
122
+ spinner.succeed();
123
+ return result;
124
+ } catch (err) {
125
+ spinner.fail();
126
+ throw err;
127
+ }
128
+ }
129
+
130
+ // src/commands/health.ts
131
+ var healthCommand = new Command("health").description("Check registry health").option("-r, --registry <url>", "Registry URL", DEFAULT_REGISTRY).action(async (opts) => {
132
+ const client = new RegistryClient(opts.registry);
133
+ const data = await withSpinner(
134
+ "Checking registry health\u2026",
135
+ () => client.health()
136
+ );
137
+ success(`Registry status: ${data.status} (${data.timestamp})`);
138
+ });
139
+
140
+ // src/commands/install.ts
141
+ import { Command as Command2 } from "commander";
142
+ import { join as join3 } from "path";
143
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
144
+
145
+ // src/lib/tar.ts
146
+ import * as tar from "tar";
147
+ import { mkdirSync, existsSync, writeFileSync } from "fs";
148
+ import { join as join2 } from "path";
149
+ import { tmpdir } from "os";
150
+ import { randomBytes } from "crypto";
151
+ async function packDirectory(dir) {
152
+ const tmpFile = join2(
153
+ tmpdir(),
154
+ `mcpak-${randomBytes(8).toString("hex")}.tar.gz`
155
+ );
156
+ await tar.create(
157
+ {
158
+ gzip: true,
159
+ file: tmpFile,
160
+ cwd: dir
161
+ },
162
+ ["."]
163
+ );
164
+ const { readFileSync: readFileSync7, unlinkSync } = await import("fs");
165
+ const buf = readFileSync7(tmpFile);
166
+ unlinkSync(tmpFile);
167
+ return buf;
168
+ }
169
+ async function extractTarball(tarballBuf, targetDir) {
170
+ if (!existsSync(targetDir)) {
171
+ mkdirSync(targetDir, { recursive: true });
172
+ }
173
+ const tmpFile = join2(
174
+ tmpdir(),
175
+ `mcpak-${randomBytes(8).toString("hex")}.tar.gz`
176
+ );
177
+ writeFileSync(tmpFile, Buffer.from(tarballBuf));
178
+ await tar.extract({
179
+ file: tmpFile,
180
+ cwd: targetDir
181
+ });
182
+ const { unlinkSync } = await import("fs");
183
+ unlinkSync(tmpFile);
184
+ }
185
+
186
+ // src/lib/lockfile.ts
187
+ import { readFileSync, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
188
+ import { dirname } from "path";
189
+ function readLockfile() {
190
+ if (!existsSync2(LOCKFILE_PATH)) {
191
+ return { packages: {} };
192
+ }
193
+ return JSON.parse(readFileSync(LOCKFILE_PATH, "utf-8"));
194
+ }
195
+ function writeLockfile(lockfile) {
196
+ const dir = dirname(LOCKFILE_PATH);
197
+ if (!existsSync2(dir)) {
198
+ mkdirSync2(dir, { recursive: true });
199
+ }
200
+ writeFileSync2(LOCKFILE_PATH, JSON.stringify(lockfile, null, 2) + "\n");
201
+ }
202
+ function setLockfileEntry(name, entry) {
203
+ const lf = readLockfile();
204
+ lf.packages[name] = entry;
205
+ writeLockfile(lf);
206
+ }
207
+ function removeLockfileEntry(name) {
208
+ const lf = readLockfile();
209
+ delete lf.packages[name];
210
+ writeLockfile(lf);
211
+ }
212
+
213
+ // src/lib/config.ts
214
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync3 } from "fs";
215
+ function readConfig() {
216
+ if (!existsSync3(CLAUDE_CONFIG_PATH)) {
217
+ return { mcpServers: {} };
218
+ }
219
+ const raw = JSON.parse(readFileSync2(CLAUDE_CONFIG_PATH, "utf-8"));
220
+ return { mcpServers: raw.mcpServers ?? {}, ...raw };
221
+ }
222
+ function writeConfig(config) {
223
+ writeFileSync3(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
224
+ }
225
+ function addMcpServer(name, entry) {
226
+ const config = readConfig();
227
+ config.mcpServers[name] = entry;
228
+ writeConfig(config);
229
+ }
230
+ function removeMcpServer(name) {
231
+ const config = readConfig();
232
+ delete config.mcpServers[name];
233
+ writeConfig(config);
234
+ }
235
+
236
+ // src/lib/errors.ts
237
+ var CliError = class extends Error {
238
+ constructor(message, exitCode = 1) {
239
+ super(message);
240
+ this.exitCode = exitCode;
241
+ this.name = "CliError";
242
+ }
243
+ };
244
+ function handleError(err) {
245
+ if (err instanceof CliError) {
246
+ error(err.message);
247
+ process.exit(err.exitCode);
248
+ }
249
+ error("An unexpected error occurred:");
250
+ console.error(err);
251
+ process.exit(1);
252
+ }
253
+
254
+ // src/commands/install.ts
255
+ var installCommand = new Command2("install").description("Install an MCP server package from the registry").argument("<name>", "Package name (optionally with @version, e.g. foo@1.2.0)").option("-r, --registry <url>", "Registry URL", DEFAULT_REGISTRY).action(async (nameArg, opts) => {
256
+ let name;
257
+ let requestedVersion;
258
+ const atIndex = nameArg.lastIndexOf("@");
259
+ if (atIndex > 0) {
260
+ name = nameArg.slice(0, atIndex);
261
+ requestedVersion = nameArg.slice(atIndex + 1);
262
+ } else {
263
+ name = nameArg;
264
+ }
265
+ const client = new RegistryClient(opts.registry);
266
+ const info2 = await withSpinner(
267
+ `Fetching package info for ${name}\u2026`,
268
+ () => client.getPackage(name)
269
+ );
270
+ if (info2.versions.length === 0) {
271
+ throw new CliError(`No versions published for ${name}`);
272
+ }
273
+ let version;
274
+ let checksum;
275
+ if (requestedVersion) {
276
+ const match = info2.versions.find((v) => v.version === requestedVersion);
277
+ if (!match) {
278
+ throw new CliError(
279
+ `Version ${requestedVersion} not found for ${name}. Available: ${info2.versions.map((v) => v.version).join(", ")}`
280
+ );
281
+ }
282
+ version = match.version;
283
+ checksum = match.checksum;
284
+ } else {
285
+ const latest = info2.versions[0];
286
+ version = latest.version;
287
+ checksum = latest.checksum;
288
+ }
289
+ const tarball = await withSpinner(
290
+ `Downloading ${name}@${version}\u2026`,
291
+ () => client.downloadVersion(name, version)
292
+ );
293
+ const pkgDir = join3(PACKAGES_DIR, name);
294
+ await withSpinner(
295
+ `Extracting to ${pkgDir}\u2026`,
296
+ () => extractTarball(tarball, pkgDir)
297
+ );
298
+ const manifestPath = join3(pkgDir, "mcpak.json");
299
+ if (!existsSync4(manifestPath)) {
300
+ throw new CliError("Package does not contain mcpak.json");
301
+ }
302
+ const manifest = JSON.parse(
303
+ readFileSync3(manifestPath, "utf-8")
304
+ );
305
+ addMcpServer(name, {
306
+ command: manifest.mcpServer.command,
307
+ args: manifest.mcpServer.args,
308
+ env: manifest.mcpServer.env
309
+ });
310
+ setLockfileEntry(name, {
311
+ name,
312
+ version,
313
+ checksum,
314
+ registryUrl: opts.registry,
315
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
316
+ });
317
+ success(
318
+ `Installed ${name}@${version} and updated Claude config.`
319
+ );
320
+ });
321
+
322
+ // src/commands/list.ts
323
+ import { Command as Command3 } from "commander";
324
+ var listCommand = new Command3("list").description("List installed MCP server packages").action(() => {
325
+ const lockfile = readLockfile();
326
+ const entries = Object.values(lockfile.packages);
327
+ if (entries.length === 0) {
328
+ info("No packages installed.");
329
+ return;
330
+ }
331
+ info("Installed packages:\n");
332
+ for (const entry of entries) {
333
+ console.log(` ${entry.name}@${entry.version}`);
334
+ console.log(` checksum: ${entry.checksum.slice(0, 12)}\u2026`);
335
+ console.log(` installed: ${entry.installedAt}`);
336
+ console.log();
337
+ }
338
+ });
339
+
340
+ // src/commands/uninstall.ts
341
+ import { Command as Command4 } from "commander";
342
+ import { join as join4 } from "path";
343
+ import { existsSync as existsSync5, rmSync } from "fs";
344
+ var uninstallCommand = new Command4("uninstall").description("Uninstall an MCP server package").argument("<name>", "Package name").action((name) => {
345
+ const lockfile = readLockfile();
346
+ if (!lockfile.packages[name]) {
347
+ throw new CliError(`Package ${name} is not installed.`);
348
+ }
349
+ const pkgDir = join4(PACKAGES_DIR, name);
350
+ if (existsSync5(pkgDir)) {
351
+ rmSync(pkgDir, { recursive: true, force: true });
352
+ }
353
+ removeMcpServer(name);
354
+ removeLockfileEntry(name);
355
+ success(`Uninstalled ${name}.`);
356
+ });
357
+
358
+ // src/commands/publish.ts
359
+ import { Command as Command5 } from "commander";
360
+ import { join as join5, resolve } from "path";
361
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
362
+
363
+ // src/lib/validate.ts
364
+ var SEMVER_RE = /^\d+\.\d+\.\d+$/;
365
+ function validateManifest(obj) {
366
+ const errors = [];
367
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
368
+ errors.push({ field: "root", message: "Manifest must be an object" });
369
+ return errors;
370
+ }
371
+ const m = obj;
372
+ if (typeof m.name !== "string" || m.name.length === 0) {
373
+ errors.push({ field: "name", message: "name must be a non-empty string" });
374
+ }
375
+ if (typeof m.version !== "string" || !SEMVER_RE.test(m.version)) {
376
+ errors.push({ field: "version", message: "version must be a valid semver string (e.g. 1.0.0)" });
377
+ }
378
+ if (m.description !== void 0 && typeof m.description !== "string") {
379
+ errors.push({ field: "description", message: "description must be a string" });
380
+ }
381
+ if (typeof m.mcpServer !== "object" || m.mcpServer === null || Array.isArray(m.mcpServer)) {
382
+ errors.push({ field: "mcpServer", message: "mcpServer must be an object" });
383
+ return errors;
384
+ }
385
+ const server = m.mcpServer;
386
+ if (typeof server.command !== "string" || server.command.length === 0) {
387
+ errors.push({ field: "mcpServer.command", message: "mcpServer.command must be a non-empty string" });
388
+ }
389
+ if (server.args !== void 0) {
390
+ if (!Array.isArray(server.args) || !server.args.every((a) => typeof a === "string")) {
391
+ errors.push({ field: "mcpServer.args", message: "mcpServer.args must be an array of strings" });
392
+ }
393
+ }
394
+ if (server.env !== void 0) {
395
+ if (typeof server.env !== "object" || server.env === null || Array.isArray(server.env) || !Object.values(server.env).every((v) => typeof v === "string")) {
396
+ errors.push({ field: "mcpServer.env", message: "mcpServer.env must be a Record<string, string>" });
397
+ }
398
+ }
399
+ return errors;
400
+ }
401
+
402
+ // src/lib/auth.ts
403
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
404
+ import { dirname as dirname2 } from "path";
405
+ function readAuth() {
406
+ if (!existsSync6(AUTH_CONFIG_PATH)) {
407
+ return { registries: {} };
408
+ }
409
+ return JSON.parse(readFileSync4(AUTH_CONFIG_PATH, "utf-8"));
410
+ }
411
+ function writeAuth(config) {
412
+ const dir = dirname2(AUTH_CONFIG_PATH);
413
+ if (!existsSync6(dir)) {
414
+ mkdirSync3(dir, { recursive: true });
415
+ }
416
+ writeFileSync4(AUTH_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
417
+ }
418
+ function getToken(registryUrl) {
419
+ const config = readAuth();
420
+ return config.registries[registryUrl]?.token;
421
+ }
422
+ function setToken(registryUrl, token) {
423
+ const config = readAuth();
424
+ config.registries[registryUrl] = {
425
+ token,
426
+ loginTime: (/* @__PURE__ */ new Date()).toISOString()
427
+ };
428
+ writeAuth(config);
429
+ }
430
+ function removeToken(registryUrl) {
431
+ const config = readAuth();
432
+ delete config.registries[registryUrl];
433
+ writeAuth(config);
434
+ }
435
+
436
+ // src/commands/publish.ts
437
+ var publishCommand = new Command5("publish").description("Publish an MCP server package to the registry").option("-r, --registry <url>", "Registry URL", DEFAULT_REGISTRY).option("-t, --token <token>", "Auth token").option("-d, --dir <dir>", "Package directory", ".").action(
438
+ async (opts) => {
439
+ const pkgDir = resolve(opts.dir);
440
+ const manifestPath = join5(pkgDir, "mcpak.json");
441
+ if (!existsSync7(manifestPath)) {
442
+ throw new CliError("mcpak.json not found in package directory.");
443
+ }
444
+ const raw = JSON.parse(readFileSync5(manifestPath, "utf-8"));
445
+ const errors = validateManifest(raw);
446
+ if (errors.length > 0) {
447
+ const messages = errors.map((e) => ` - ${e.field}: ${e.message}`).join("\n");
448
+ throw new CliError(`Invalid mcpak.json:
449
+ ${messages}`);
450
+ }
451
+ const manifest = raw;
452
+ const token = opts.token ?? getToken(opts.registry);
453
+ const tarball = await withSpinner(
454
+ `Packing ${manifest.name}@${manifest.version}\u2026`,
455
+ () => packDirectory(pkgDir)
456
+ );
457
+ const client = new RegistryClient(opts.registry, token);
458
+ const result = await withSpinner(
459
+ `Publishing to ${opts.registry}\u2026`,
460
+ () => client.publish(
461
+ {
462
+ name: manifest.name,
463
+ version: manifest.version,
464
+ description: manifest.description,
465
+ manifest
466
+ },
467
+ tarball
468
+ )
469
+ );
470
+ success(`Published ${result.name}@${result.version}`);
471
+ }
472
+ );
473
+
474
+ // src/commands/update.ts
475
+ import { Command as Command6 } from "commander";
476
+ import { join as join6 } from "path";
477
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
478
+ var updateCommand = new Command6("update").description("Update installed MCP server packages").argument("[name]", "Package name (omit to update all)").option("-r, --registry <url>", "Registry URL", DEFAULT_REGISTRY).action(async (name, opts) => {
479
+ const lockfile = readLockfile();
480
+ const entries = Object.values(lockfile.packages);
481
+ if (entries.length === 0) {
482
+ info("No packages installed.");
483
+ return;
484
+ }
485
+ const targets = name ? entries.filter((e) => e.name === name) : entries;
486
+ if (targets.length === 0) {
487
+ throw new CliError(`Package ${name} is not installed.`);
488
+ }
489
+ const client = new RegistryClient(opts.registry);
490
+ const updates = [];
491
+ for (const entry of targets) {
492
+ const registryClient = new RegistryClient(
493
+ entry.registryUrl || opts.registry
494
+ );
495
+ const check = await withSpinner(
496
+ `Checking updates for ${entry.name}\u2026`,
497
+ () => registryClient.checkUpdate(entry.name, entry.version)
498
+ );
499
+ if (check.updateAvailable) {
500
+ updates.push({
501
+ name: check.name,
502
+ currentVersion: check.currentVersion,
503
+ latestVersion: check.latestVersion
504
+ });
505
+ }
506
+ }
507
+ if (updates.length === 0) {
508
+ success("All packages are up to date.");
509
+ return;
510
+ }
511
+ info(`${updates.length} update(s) available:`);
512
+ for (const u of updates) {
513
+ console.log(` ${u.name}: ${u.currentVersion} \u2192 ${u.latestVersion}`);
514
+ }
515
+ console.log();
516
+ for (const u of updates) {
517
+ const entry = lockfile.packages[u.name];
518
+ const registryClient = new RegistryClient(
519
+ entry.registryUrl || opts.registry
520
+ );
521
+ const tarball = await withSpinner(
522
+ `Downloading ${u.name}@${u.latestVersion}\u2026`,
523
+ () => registryClient.downloadVersion(u.name, u.latestVersion)
524
+ );
525
+ const pkgDir = join6(PACKAGES_DIR, u.name);
526
+ await withSpinner(
527
+ `Extracting ${u.name}\u2026`,
528
+ () => extractTarball(tarball, pkgDir)
529
+ );
530
+ const manifestPath = join6(pkgDir, "mcpak.json");
531
+ if (!existsSync8(manifestPath)) {
532
+ throw new CliError(
533
+ `Updated package ${u.name} does not contain mcpak.json`
534
+ );
535
+ }
536
+ const manifest = JSON.parse(
537
+ readFileSync6(manifestPath, "utf-8")
538
+ );
539
+ addMcpServer(u.name, {
540
+ command: manifest.mcpServer.command,
541
+ args: manifest.mcpServer.args,
542
+ env: manifest.mcpServer.env
543
+ });
544
+ setLockfileEntry(u.name, {
545
+ name: u.name,
546
+ version: u.latestVersion,
547
+ checksum: "",
548
+ // Will be populated by registry in future
549
+ registryUrl: entry.registryUrl || opts.registry,
550
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
551
+ });
552
+ success(`Updated ${u.name} to ${u.latestVersion}`);
553
+ }
554
+ });
555
+
556
+ // src/commands/init.ts
557
+ import { Command as Command7 } from "commander";
558
+ import { createInterface } from "readline";
559
+ import { join as join7, basename } from "path";
560
+ import { existsSync as existsSync9 } from "fs";
561
+ import { writeFileSync as writeFileSync5 } from "fs";
562
+ function ask(rl, question, defaultValue) {
563
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
564
+ return new Promise((resolve2) => {
565
+ rl.question(`${question}${suffix}: `, (answer) => {
566
+ resolve2(answer.trim() || defaultValue || "");
567
+ });
568
+ });
569
+ }
570
+ var initCommand = new Command7("init").description("Initialize a new mcpak.json in the current directory").option("-y, --yes", "Skip prompts and use defaults").action(async (opts) => {
571
+ const outputPath = join7(process.cwd(), "mcpak.json");
572
+ if (existsSync9(outputPath)) {
573
+ throw new CliError("mcpak.json already exists in this directory.");
574
+ }
575
+ if (opts.yes) {
576
+ const manifest = {
577
+ name: basename(process.cwd()),
578
+ version: "1.0.0",
579
+ mcpServer: {
580
+ command: "node",
581
+ args: []
582
+ }
583
+ };
584
+ writeFileSync5(outputPath, JSON.stringify(manifest, null, 2) + "\n");
585
+ success(`Created mcpak.json`);
586
+ return;
587
+ }
588
+ const rl = createInterface({
589
+ input: process.stdin,
590
+ output: process.stdout
591
+ });
592
+ try {
593
+ const name = await ask(rl, "Package name");
594
+ if (!name) {
595
+ throw new CliError("Package name is required.");
596
+ }
597
+ const version = await ask(rl, "Version", "1.0.0");
598
+ const description = await ask(rl, "Description", "");
599
+ const command = await ask(rl, "MCP server command", "node");
600
+ const argsRaw = await ask(
601
+ rl,
602
+ "MCP server args (comma-separated)",
603
+ ""
604
+ );
605
+ const args = argsRaw ? argsRaw.split(",").map((a) => a.trim()) : [];
606
+ const manifest = {
607
+ name,
608
+ version,
609
+ description: description || void 0,
610
+ mcpServer: {
611
+ command,
612
+ args
613
+ }
614
+ };
615
+ writeFileSync5(outputPath, JSON.stringify(manifest, null, 2) + "\n");
616
+ success(`Created mcpak.json`);
617
+ } finally {
618
+ rl.close();
619
+ }
620
+ });
621
+
622
+ // src/commands/search.ts
623
+ import { Command as Command8 } from "commander";
624
+ var searchCommand = new Command8("search").description("Search for MCP server packages in the registry").argument("<query>", "Search term").option("-r, --registry <url>", "Registry URL", DEFAULT_REGISTRY).option("-l, --limit <n>", "Max results", "20").action(async (query, opts) => {
625
+ const client = new RegistryClient(opts.registry);
626
+ const data = await withSpinner(
627
+ "Searching registry\u2026",
628
+ () => client.search(query, Number(opts.limit))
629
+ );
630
+ if (data.results.length === 0) {
631
+ info(`No packages found for "${query}".`);
632
+ return;
633
+ }
634
+ info(`Found ${data.total} package(s) for "${query}":
635
+ `);
636
+ for (const pkg of data.results) {
637
+ console.log(` ${pkg.name}`);
638
+ if (pkg.description) {
639
+ console.log(` ${pkg.description}`);
640
+ }
641
+ if (pkg.author) {
642
+ console.log(` author: ${pkg.author}`);
643
+ }
644
+ console.log(` updated: ${pkg.updatedAt}`);
645
+ console.log();
646
+ }
647
+ });
648
+
649
+ // src/commands/login.ts
650
+ import { Command as Command9 } from "commander";
651
+ var loginCommand = new Command9("login").description("Save an auth token for a registry").option("-t, --token <token>", "Auth token").option("-r, --registry <url>", "Registry URL", DEFAULT_REGISTRY).action(async (opts) => {
652
+ if (!opts.token) {
653
+ throw new CliError("Token is required. Use --token <token>");
654
+ }
655
+ setToken(opts.registry, opts.token);
656
+ success(`Logged in to ${opts.registry}`);
657
+ });
658
+
659
+ // src/commands/logout.ts
660
+ import { Command as Command10 } from "commander";
661
+ var logoutCommand = new Command10("logout").description("Remove auth token for a registry").option("-r, --registry <url>", "Registry URL", DEFAULT_REGISTRY).action(async (opts) => {
662
+ removeToken(opts.registry);
663
+ success(`Logged out from ${opts.registry}`);
664
+ });
665
+
666
+ // src/commands/info.ts
667
+ import { Command as Command11 } from "commander";
668
+ var infoCommand = new Command11("info").description("Show package information from the registry").argument("<name>", "Package name").option("-r, --registry <url>", "Registry URL", DEFAULT_REGISTRY).action(async (name, opts) => {
669
+ const client = new RegistryClient(opts.registry);
670
+ const info2 = await withSpinner(
671
+ `Fetching info for ${name}\u2026`,
672
+ () => client.getPackage(name)
673
+ );
674
+ console.log(`
675
+ Package: ${info2.name}`);
676
+ if (info2.description) {
677
+ console.log(`Description: ${info2.description}`);
678
+ }
679
+ if (info2.author) {
680
+ console.log(`Author: ${info2.author}`);
681
+ }
682
+ console.log(`Created: ${info2.createdAt}`);
683
+ console.log(`Updated: ${info2.updatedAt}`);
684
+ console.log(`
685
+ Versions:`);
686
+ for (const v of info2.versions) {
687
+ console.log(` ${v.version} (published ${v.publishedAt})`);
688
+ }
689
+ });
690
+
691
+ // src/index.ts
692
+ var program = new Command12();
693
+ program.name("mcpak").description("Package manager for MCP servers").version("0.0.1");
694
+ program.addCommand(healthCommand);
695
+ program.addCommand(installCommand);
696
+ program.addCommand(listCommand);
697
+ program.addCommand(uninstallCommand);
698
+ program.addCommand(publishCommand);
699
+ program.addCommand(updateCommand);
700
+ program.addCommand(initCommand);
701
+ program.addCommand(searchCommand);
702
+ program.addCommand(loginCommand);
703
+ program.addCommand(logoutCommand);
704
+ program.addCommand(infoCommand);
705
+ program.parseAsync().catch(handleError);
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@mcpak/cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "mcpak": "dist/index.js"
7
+ },
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "dependencies": {
12
+ "chalk": "^5.6.2",
13
+ "commander": "^13.0.0",
14
+ "ora": "^9.3.0",
15
+ "tar": "^7.5.9",
16
+ "@mcpak/shared": "0.1.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.0.0",
20
+ "@types/tar": "^7.0.87",
21
+ "@vitest/coverage-v8": "^4.0.18",
22
+ "tsup": "^8.3.0",
23
+ "typescript": "^5.7.0",
24
+ "vitest": "^4.0.18"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "test": "vitest run",
29
+ "clean": "rm -rf dist"
30
+ }
31
+ }