mcpman 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.
package/dist/index.cjs CHANGED
@@ -37,6 +37,90 @@ var init_cjs_shims = __esm({
37
37
  }
38
38
  });
39
39
 
40
+ // src/core/trust-scorer.ts
41
+ var trust_scorer_exports = {};
42
+ __export(trust_scorer_exports, {
43
+ computeTrustScore: () => computeTrustScore
44
+ });
45
+ function vulnScore(vulns) {
46
+ let score = 100;
47
+ for (const v of vulns) {
48
+ if (v.severity === "critical") score -= 25;
49
+ else if (v.severity === "high") score -= 15;
50
+ else if (v.severity === "moderate") score -= 10;
51
+ else score -= 5;
52
+ }
53
+ return Math.max(0, score);
54
+ }
55
+ function downloadScore(weeklyDownloads) {
56
+ if (weeklyDownloads <= 0) return 0;
57
+ if (weeklyDownloads >= 1e6) return 100;
58
+ if (weeklyDownloads >= 1e5) return 80;
59
+ if (weeklyDownloads >= 1e4) return 60;
60
+ if (weeklyDownloads >= 1e3) return 40;
61
+ if (weeklyDownloads >= 100) return 20;
62
+ return 10;
63
+ }
64
+ function ageScore(packageAge) {
65
+ if (packageAge <= 0) return 0;
66
+ if (packageAge >= 730) return 100;
67
+ if (packageAge >= 365) return 80;
68
+ if (packageAge >= 180) return 60;
69
+ if (packageAge >= 30) return 30;
70
+ return 10;
71
+ }
72
+ function publishScore(lastPublish) {
73
+ const daysSince = Math.floor((Date.now() - new Date(lastPublish).getTime()) / 864e5);
74
+ if (daysSince <= 30) return 100;
75
+ if (daysSince <= 90) return 80;
76
+ if (daysSince <= 180) return 60;
77
+ if (daysSince <= 365) return 40;
78
+ return 20;
79
+ }
80
+ function maintainerScore(count, deprecated) {
81
+ let score = 0;
82
+ if (count >= 3) score = 90;
83
+ else if (count === 2) score = 70;
84
+ else score = 50;
85
+ if (!deprecated) score += 10;
86
+ return Math.min(100, score);
87
+ }
88
+ function toRiskLevel(score) {
89
+ if (score >= 80) return "LOW";
90
+ if (score >= 50) return "MEDIUM";
91
+ if (score >= 20) return "HIGH";
92
+ return "CRITICAL";
93
+ }
94
+ function computeTrustScore(metadata, vulns) {
95
+ if (!metadata) {
96
+ const vScore = vulnScore(vulns);
97
+ const score2 = Math.round(vScore * WEIGHT_VULNS * 100) / 100;
98
+ return { score: Math.round(score2), riskLevel: toRiskLevel(score2) };
99
+ }
100
+ const scores = {
101
+ vulns: vulnScore(vulns),
102
+ downloads: downloadScore(metadata.weeklyDownloads),
103
+ age: ageScore(metadata.packageAge),
104
+ publish: publishScore(metadata.lastPublish),
105
+ maintainers: maintainerScore(metadata.maintainerCount, metadata.deprecated)
106
+ };
107
+ const weighted = scores.vulns * WEIGHT_VULNS + scores.downloads * WEIGHT_DOWNLOADS + scores.age * WEIGHT_AGE + scores.publish * WEIGHT_PUBLISH_FREQ + scores.maintainers * WEIGHT_MAINTAINERS;
108
+ const score = Math.min(100, Math.max(0, Math.round(weighted)));
109
+ return { score, riskLevel: toRiskLevel(score) };
110
+ }
111
+ var WEIGHT_VULNS, WEIGHT_DOWNLOADS, WEIGHT_AGE, WEIGHT_PUBLISH_FREQ, WEIGHT_MAINTAINERS;
112
+ var init_trust_scorer = __esm({
113
+ "src/core/trust-scorer.ts"() {
114
+ "use strict";
115
+ init_cjs_shims();
116
+ WEIGHT_VULNS = 0.3;
117
+ WEIGHT_DOWNLOADS = 0.2;
118
+ WEIGHT_AGE = 0.15;
119
+ WEIGHT_PUBLISH_FREQ = 0.15;
120
+ WEIGHT_MAINTAINERS = 0.2;
121
+ }
122
+ });
123
+
40
124
  // src/clients/types.ts
41
125
  var ConfigParseError, ConfigWriteError;
42
126
  var init_types = __esm({
@@ -64,43 +148,43 @@ var init_types = __esm({
64
148
  async function atomicWrite(filePath, content) {
65
149
  const tmpPath = `${filePath}.tmp`;
66
150
  try {
67
- await import_node_fs.default.promises.mkdir(import_node_path.default.dirname(filePath), { recursive: true });
68
- await import_node_fs.default.promises.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
69
- await import_node_fs.default.promises.rename(tmpPath, filePath);
151
+ await import_node_fs3.default.promises.mkdir(import_node_path3.default.dirname(filePath), { recursive: true });
152
+ await import_node_fs3.default.promises.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
153
+ await import_node_fs3.default.promises.rename(tmpPath, filePath);
70
154
  } catch (err) {
71
155
  try {
72
- await import_node_fs.default.promises.unlink(tmpPath);
156
+ await import_node_fs3.default.promises.unlink(tmpPath);
73
157
  } catch {
74
158
  }
75
159
  throw err;
76
160
  }
77
161
  }
78
- async function pathExists(p5) {
162
+ async function pathExists(p9) {
79
163
  try {
80
- await import_node_fs.default.promises.access(p5);
164
+ await import_node_fs3.default.promises.access(p9);
81
165
  return true;
82
166
  } catch {
83
167
  return false;
84
168
  }
85
169
  }
86
- var import_node_fs, import_node_path, BaseClientHandler;
170
+ var import_node_fs3, import_node_path3, BaseClientHandler;
87
171
  var init_base_client_handler = __esm({
88
172
  "src/clients/base-client-handler.ts"() {
89
173
  "use strict";
90
174
  init_cjs_shims();
91
- import_node_fs = __toESM(require("fs"), 1);
92
- import_node_path = __toESM(require("path"), 1);
175
+ import_node_fs3 = __toESM(require("fs"), 1);
176
+ import_node_path3 = __toESM(require("path"), 1);
93
177
  init_types();
94
178
  BaseClientHandler = class {
95
179
  async isInstalled() {
96
- const dir = import_node_path.default.dirname(this.getConfigPath());
180
+ const dir = import_node_path3.default.dirname(this.getConfigPath());
97
181
  return pathExists(dir);
98
182
  }
99
183
  /** Read raw JSON from disk, return empty object if file missing */
100
184
  async readRaw() {
101
185
  const configPath = this.getConfigPath();
102
186
  try {
103
- const raw = await import_node_fs.default.promises.readFile(configPath, "utf-8");
187
+ const raw = await import_node_fs3.default.promises.readFile(configPath, "utf-8");
104
188
  return JSON.parse(raw);
105
189
  } catch (err) {
106
190
  if (err.code === "ENOENT") {
@@ -151,26 +235,26 @@ var init_base_client_handler = __esm({
151
235
 
152
236
  // src/utils/paths.ts
153
237
  function getHomedir() {
154
- return import_node_os.default.homedir();
238
+ return import_node_os3.default.homedir();
155
239
  }
156
240
  function getAppDataDir() {
157
241
  const home = getHomedir();
158
242
  if (process.platform === "darwin") {
159
- return import_node_path2.default.join(home, "Library", "Application Support");
243
+ return import_node_path4.default.join(home, "Library", "Application Support");
160
244
  }
161
245
  if (process.platform === "win32") {
162
- return process.env.APPDATA ?? import_node_path2.default.join(home, "AppData", "Roaming");
246
+ return process.env.APPDATA ?? import_node_path4.default.join(home, "AppData", "Roaming");
163
247
  }
164
- return process.env.XDG_CONFIG_HOME ?? import_node_path2.default.join(home, ".config");
248
+ return process.env.XDG_CONFIG_HOME ?? import_node_path4.default.join(home, ".config");
165
249
  }
166
250
  function resolveConfigPath(client) {
167
251
  const appData = getAppDataDir();
168
252
  const home = getHomedir();
169
253
  switch (client) {
170
254
  case "claude-desktop":
171
- return import_node_path2.default.join(appData, "Claude", "claude_desktop_config.json");
255
+ return import_node_path4.default.join(appData, "Claude", "claude_desktop_config.json");
172
256
  case "cursor":
173
- return import_node_path2.default.join(
257
+ return import_node_path4.default.join(
174
258
  appData,
175
259
  "Cursor",
176
260
  "User",
@@ -179,7 +263,7 @@ function resolveConfigPath(client) {
179
263
  "mcp.json"
180
264
  );
181
265
  case "windsurf":
182
- return import_node_path2.default.join(
266
+ return import_node_path4.default.join(
183
267
  appData,
184
268
  "Windsurf",
185
269
  "User",
@@ -189,21 +273,21 @@ function resolveConfigPath(client) {
189
273
  );
190
274
  case "vscode":
191
275
  if (process.platform === "darwin") {
192
- return import_node_path2.default.join(appData, "Code", "User", "settings.json");
276
+ return import_node_path4.default.join(appData, "Code", "User", "settings.json");
193
277
  }
194
278
  if (process.platform === "win32") {
195
- return import_node_path2.default.join(appData, "Code", "User", "settings.json");
279
+ return import_node_path4.default.join(appData, "Code", "User", "settings.json");
196
280
  }
197
- return import_node_path2.default.join(home, ".config", "Code", "User", "settings.json");
281
+ return import_node_path4.default.join(home, ".config", "Code", "User", "settings.json");
198
282
  }
199
283
  }
200
- var import_node_os, import_node_path2;
284
+ var import_node_os3, import_node_path4;
201
285
  var init_paths = __esm({
202
286
  "src/utils/paths.ts"() {
203
287
  "use strict";
204
288
  init_cjs_shims();
205
- import_node_os = __toESM(require("os"), 1);
206
- import_node_path2 = __toESM(require("path"), 1);
289
+ import_node_os3 = __toESM(require("os"), 1);
290
+ import_node_path4 = __toESM(require("path"), 1);
207
291
  }
208
292
  });
209
293
 
@@ -331,14 +415,515 @@ var init_client_detector = __esm({
331
415
  }
332
416
  });
333
417
 
418
+ // src/core/vault-service.ts
419
+ var vault_service_exports = {};
420
+ __export(vault_service_exports, {
421
+ clearPasswordCache: () => clearPasswordCache,
422
+ decrypt: () => decrypt,
423
+ encrypt: () => encrypt,
424
+ getMasterPassword: () => getMasterPassword,
425
+ getSecret: () => getSecret,
426
+ getSecretsForServer: () => getSecretsForServer,
427
+ getVaultPath: () => getVaultPath,
428
+ listSecrets: () => listSecrets,
429
+ readVault: () => readVault,
430
+ removeSecret: () => removeSecret,
431
+ setSecret: () => setSecret,
432
+ writeVault: () => writeVault
433
+ });
434
+ function getVaultPath() {
435
+ return import_node_path6.default.join(import_node_os4.default.homedir(), ".mcpman", "vault.enc");
436
+ }
437
+ function readVault(vaultPath = getVaultPath()) {
438
+ const empty = { version: 1, servers: {} };
439
+ try {
440
+ const raw = import_node_fs4.default.readFileSync(vaultPath, "utf-8");
441
+ const parsed = JSON.parse(raw);
442
+ if (parsed.version !== 1 || typeof parsed.servers !== "object") return empty;
443
+ return parsed;
444
+ } catch {
445
+ return empty;
446
+ }
447
+ }
448
+ function writeVault(data, vaultPath = getVaultPath()) {
449
+ const dir = import_node_path6.default.dirname(vaultPath);
450
+ import_node_fs4.default.mkdirSync(dir, { recursive: true });
451
+ const tmp = `${vaultPath}.tmp`;
452
+ import_node_fs4.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
453
+ if (process.platform !== "win32") {
454
+ import_node_fs4.default.chmodSync(tmp, 384);
455
+ }
456
+ import_node_fs4.default.renameSync(tmp, vaultPath);
457
+ if (process.platform !== "win32") {
458
+ import_node_fs4.default.chmodSync(vaultPath, 384);
459
+ }
460
+ }
461
+ function encrypt(value, password2) {
462
+ const salt = import_node_crypto2.default.randomBytes(16);
463
+ const key = import_node_crypto2.default.pbkdf2Sync(password2, salt, 1e5, 32, "sha256");
464
+ const iv = import_node_crypto2.default.randomBytes(16);
465
+ const cipher = import_node_crypto2.default.createCipheriv("aes-256-cbc", key, iv);
466
+ const encrypted = Buffer.concat([cipher.update(value, "utf-8"), cipher.final()]);
467
+ return {
468
+ salt: salt.toString("hex"),
469
+ iv: iv.toString("hex"),
470
+ data: encrypted.toString("hex")
471
+ };
472
+ }
473
+ function decrypt(entry, password2) {
474
+ const salt = Buffer.from(entry.salt, "hex");
475
+ const key = import_node_crypto2.default.pbkdf2Sync(password2, salt, 1e5, 32, "sha256");
476
+ const iv = Buffer.from(entry.iv, "hex");
477
+ const decipher = import_node_crypto2.default.createDecipheriv("aes-256-cbc", key, iv);
478
+ const decrypted = Buffer.concat([
479
+ decipher.update(Buffer.from(entry.data, "hex")),
480
+ decipher.final()
481
+ // throws ERR_OSSL_BAD_DECRYPT on wrong password
482
+ ]);
483
+ return decrypted.toString("utf-8");
484
+ }
485
+ async function getMasterPassword(confirm6 = false) {
486
+ if (_cachedPassword) return _cachedPassword;
487
+ const password2 = await p5.password({
488
+ message: "Enter vault master password:",
489
+ validate: (v) => v.length < 8 ? "Password must be at least 8 characters" : void 0
490
+ });
491
+ if (p5.isCancel(password2)) {
492
+ p5.cancel("Vault access cancelled.");
493
+ process.exit(0);
494
+ }
495
+ if (confirm6) {
496
+ const confirm22 = await p5.password({ message: "Confirm master password:" });
497
+ if (p5.isCancel(confirm22) || confirm22 !== password2) {
498
+ p5.cancel("Passwords do not match.");
499
+ process.exit(1);
500
+ }
501
+ }
502
+ _cachedPassword = password2;
503
+ return _cachedPassword;
504
+ }
505
+ function clearPasswordCache() {
506
+ _cachedPassword = null;
507
+ }
508
+ function setSecret(server, key, value, password2, vaultPath = getVaultPath()) {
509
+ const vault = readVault(vaultPath);
510
+ if (!vault.servers[server]) vault.servers[server] = {};
511
+ vault.servers[server][key] = encrypt(value, password2);
512
+ writeVault(vault, vaultPath);
513
+ }
514
+ function getSecret(server, key, password2, vaultPath = getVaultPath()) {
515
+ const vault = readVault(vaultPath);
516
+ const entry = vault.servers[server]?.[key];
517
+ if (!entry) return null;
518
+ return decrypt(entry, password2);
519
+ }
520
+ function getSecretsForServer(server, password2, vaultPath = getVaultPath()) {
521
+ const vault = readVault(vaultPath);
522
+ const entries = vault.servers[server];
523
+ if (!entries) return {};
524
+ const result = {};
525
+ for (const [k, entry] of Object.entries(entries)) {
526
+ result[k] = decrypt(entry, password2);
527
+ }
528
+ return result;
529
+ }
530
+ function removeSecret(server, key, vaultPath = getVaultPath()) {
531
+ const vault = readVault(vaultPath);
532
+ if (vault.servers[server]) {
533
+ delete vault.servers[server][key];
534
+ if (Object.keys(vault.servers[server]).length === 0) {
535
+ delete vault.servers[server];
536
+ }
537
+ writeVault(vault, vaultPath);
538
+ }
539
+ }
540
+ function listSecrets(server, vaultPath = getVaultPath()) {
541
+ const vault = readVault(vaultPath);
542
+ const entries = server ? vault.servers[server] ? { [server]: vault.servers[server] } : {} : vault.servers;
543
+ return Object.entries(entries).map(([srv, keys]) => ({
544
+ server: srv,
545
+ keys: Object.keys(keys)
546
+ }));
547
+ }
548
+ var import_node_crypto2, import_node_fs4, import_node_os4, import_node_path6, p5, _cachedPassword;
549
+ var init_vault_service = __esm({
550
+ "src/core/vault-service.ts"() {
551
+ "use strict";
552
+ init_cjs_shims();
553
+ import_node_crypto2 = __toESM(require("crypto"), 1);
554
+ import_node_fs4 = __toESM(require("fs"), 1);
555
+ import_node_os4 = __toESM(require("os"), 1);
556
+ import_node_path6 = __toESM(require("path"), 1);
557
+ p5 = __toESM(require("@clack/prompts"), 1);
558
+ _cachedPassword = null;
559
+ process.on("exit", () => {
560
+ _cachedPassword = null;
561
+ });
562
+ }
563
+ });
564
+
334
565
  // src/index.ts
335
566
  init_cjs_shims();
336
- var import_citty6 = require("citty");
567
+ var import_citty10 = require("citty");
337
568
 
338
- // src/commands/doctor.ts
569
+ // src/commands/audit.ts
339
570
  init_cjs_shims();
340
571
  var import_citty = require("citty");
341
572
  var import_picocolors = __toESM(require("picocolors"), 1);
573
+ var import_nanospinner = require("nanospinner");
574
+
575
+ // src/core/lockfile.ts
576
+ init_cjs_shims();
577
+ var import_node_fs = __toESM(require("fs"), 1);
578
+ var import_node_path = __toESM(require("path"), 1);
579
+ var import_node_os = __toESM(require("os"), 1);
580
+ var LOCKFILE_NAME = "mcpman.lock";
581
+ function findLockfile() {
582
+ let dir = process.cwd();
583
+ while (true) {
584
+ const candidate = import_node_path.default.join(dir, LOCKFILE_NAME);
585
+ if (import_node_fs.default.existsSync(candidate)) return candidate;
586
+ const parent = import_node_path.default.dirname(dir);
587
+ if (parent === dir) break;
588
+ dir = parent;
589
+ }
590
+ return null;
591
+ }
592
+ function getGlobalLockfilePath() {
593
+ return import_node_path.default.join(import_node_os.default.homedir(), ".mcpman", LOCKFILE_NAME);
594
+ }
595
+ function resolveLockfilePath() {
596
+ return findLockfile() ?? getGlobalLockfilePath();
597
+ }
598
+ function readLockfile(filePath) {
599
+ const target = filePath ?? resolveLockfilePath();
600
+ if (!import_node_fs.default.existsSync(target)) {
601
+ return { lockfileVersion: 1, servers: {} };
602
+ }
603
+ try {
604
+ const raw = import_node_fs.default.readFileSync(target, "utf-8");
605
+ return JSON.parse(raw);
606
+ } catch {
607
+ return { lockfileVersion: 1, servers: {} };
608
+ }
609
+ }
610
+ function serialize(data) {
611
+ const sorted = {
612
+ lockfileVersion: data.lockfileVersion,
613
+ servers: Object.fromEntries(
614
+ Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
615
+ )
616
+ };
617
+ return JSON.stringify(sorted, null, 2) + "\n";
618
+ }
619
+ function writeLockfile(data, filePath) {
620
+ const target = filePath ?? resolveLockfilePath();
621
+ const dir = import_node_path.default.dirname(target);
622
+ if (!import_node_fs.default.existsSync(dir)) {
623
+ import_node_fs.default.mkdirSync(dir, { recursive: true });
624
+ }
625
+ const tmp = `${target}.tmp`;
626
+ import_node_fs.default.writeFileSync(tmp, serialize(data), "utf-8");
627
+ import_node_fs.default.renameSync(tmp, target);
628
+ }
629
+ function addEntry(name, entry, filePath) {
630
+ const data = readLockfile(filePath);
631
+ data.servers[name] = entry;
632
+ writeLockfile(data, filePath);
633
+ }
634
+ function createEmptyLockfile(filePath) {
635
+ writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
636
+ }
637
+
638
+ // src/core/security-scanner.ts
639
+ init_cjs_shims();
640
+ var import_node_fs2 = __toESM(require("fs"), 1);
641
+ var import_node_os2 = __toESM(require("os"), 1);
642
+ var import_node_path2 = __toESM(require("path"), 1);
643
+ var CACHE_PATH = import_node_path2.default.join(import_node_os2.default.homedir(), ".mcpman", ".audit-cache.json");
644
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
645
+ function readCache() {
646
+ try {
647
+ if (!import_node_fs2.default.existsSync(CACHE_PATH)) return {};
648
+ return JSON.parse(import_node_fs2.default.readFileSync(CACHE_PATH, "utf-8"));
649
+ } catch {
650
+ return {};
651
+ }
652
+ }
653
+ function writeCache(cache) {
654
+ const dir = import_node_path2.default.dirname(CACHE_PATH);
655
+ if (!import_node_fs2.default.existsSync(dir)) import_node_fs2.default.mkdirSync(dir, { recursive: true });
656
+ const tmp = `${CACHE_PATH}.tmp`;
657
+ import_node_fs2.default.writeFileSync(tmp, JSON.stringify(cache, null, 2), "utf-8");
658
+ import_node_fs2.default.renameSync(tmp, CACHE_PATH);
659
+ }
660
+ function getCachedReport(name, version) {
661
+ const cache = readCache();
662
+ const key = `${name}@${version}`;
663
+ const entry = cache[key];
664
+ if (!entry) return null;
665
+ if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
666
+ return entry.report;
667
+ }
668
+ function cacheReport(name, version, report) {
669
+ const cache = readCache();
670
+ cache[`${name}@${version}`] = { report, timestamp: Date.now() };
671
+ writeCache(cache);
672
+ }
673
+ async function fetchNpmMetadata(packageName) {
674
+ const timeout = 1e4;
675
+ const signal = AbortSignal.timeout(timeout);
676
+ try {
677
+ const [regRes, dlRes] = await Promise.all([
678
+ fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, { signal }),
679
+ fetch(`https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`, {
680
+ signal: AbortSignal.timeout(timeout)
681
+ })
682
+ ]);
683
+ if (!regRes.ok) return null;
684
+ const reg = await regRes.json();
685
+ const time = reg["time"] ?? {};
686
+ const created = time["created"] ? new Date(time["created"]) : null;
687
+ const modified = time["modified"] ? new Date(time["modified"]) : null;
688
+ const packageAge = created ? Math.floor((Date.now() - created.getTime()) / 864e5) : 0;
689
+ const maintainers = Array.isArray(reg["maintainers"]) ? reg["maintainers"] : [];
690
+ const latestVersion = reg["dist-tags"]?.["latest"] ?? "";
691
+ const versionData = reg["versions"]?.[latestVersion];
692
+ const deprecated = typeof versionData?.["deprecated"] === "string";
693
+ let weeklyDownloads = 0;
694
+ if (dlRes.ok) {
695
+ const dl = await dlRes.json();
696
+ weeklyDownloads = typeof dl["downloads"] === "number" ? dl["downloads"] : 0;
697
+ }
698
+ return {
699
+ weeklyDownloads,
700
+ lastPublish: modified?.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString(),
701
+ packageAge,
702
+ maintainerCount: maintainers.length,
703
+ deprecated
704
+ };
705
+ } catch {
706
+ return null;
707
+ }
708
+ }
709
+ async function fetchVulnerabilities(packageName, version) {
710
+ try {
711
+ const res = await fetch("https://api.osv.dev/v1/query", {
712
+ method: "POST",
713
+ headers: { "Content-Type": "application/json" },
714
+ body: JSON.stringify({ package: { name: packageName, ecosystem: "npm" }, version }),
715
+ signal: AbortSignal.timeout(1e4)
716
+ });
717
+ if (!res.ok) return [];
718
+ const data = await res.json();
719
+ const vulns = Array.isArray(data["vulns"]) ? data["vulns"] : [];
720
+ return vulns.map((v) => {
721
+ const severity = v["database_specific"]?.["severity"];
722
+ const sev = typeof severity === "string" ? severity.toLowerCase() : "moderate";
723
+ const refs = Array.isArray(v["references"]) ? v["references"] : [];
724
+ return {
725
+ severity: ["low", "moderate", "high", "critical"].includes(sev) ? sev : "moderate",
726
+ title: typeof v["summary"] === "string" ? v["summary"] : typeof v["id"] === "string" ? v["id"] : "Unknown vulnerability",
727
+ url: typeof refs[0]?.["url"] === "string" ? refs[0]["url"] : void 0
728
+ };
729
+ });
730
+ } catch {
731
+ return [];
732
+ }
733
+ }
734
+ async function scanServer(name, entry) {
735
+ if (entry.source !== "npm") {
736
+ return {
737
+ server: name,
738
+ source: entry.source,
739
+ score: null,
740
+ riskLevel: "UNKNOWN",
741
+ vulnerabilities: [],
742
+ metadata: null
743
+ };
744
+ }
745
+ const cached = getCachedReport(name, entry.version);
746
+ if (cached) return cached;
747
+ const [metadata, vulnerabilities] = await Promise.all([
748
+ fetchNpmMetadata(name),
749
+ fetchVulnerabilities(name, entry.version)
750
+ ]);
751
+ const { computeTrustScore: computeTrustScore2 } = await Promise.resolve().then(() => (init_trust_scorer(), trust_scorer_exports));
752
+ const { score, riskLevel } = computeTrustScore2(metadata, vulnerabilities);
753
+ const report = {
754
+ server: name,
755
+ source: "npm",
756
+ score,
757
+ riskLevel,
758
+ vulnerabilities,
759
+ metadata
760
+ };
761
+ cacheReport(name, entry.version, report);
762
+ return report;
763
+ }
764
+ async function scanAllServers(servers, concurrency = 3) {
765
+ const entries = Object.entries(servers);
766
+ const results = [];
767
+ const executing = /* @__PURE__ */ new Set();
768
+ for (const [name, entry] of entries) {
769
+ const p9 = scanServer(name, entry).then((r) => {
770
+ results.push(r);
771
+ executing.delete(p9);
772
+ });
773
+ executing.add(p9);
774
+ if (executing.size >= concurrency) await Promise.race(executing);
775
+ }
776
+ await Promise.all(executing);
777
+ return results;
778
+ }
779
+
780
+ // src/commands/audit.ts
781
+ function colorRisk(level, score) {
782
+ const label = score !== null ? `${score}/100 (${level})` : level;
783
+ if (level === "LOW") return import_picocolors.default.green(label);
784
+ if (level === "MEDIUM") return import_picocolors.default.yellow(label);
785
+ if (level === "HIGH") return import_picocolors.default.red(label);
786
+ if (level === "CRITICAL") return import_picocolors.default.bold(import_picocolors.default.red(label));
787
+ return import_picocolors.default.dim(label);
788
+ }
789
+ function daysAgo(isoDate) {
790
+ const days = Math.floor((Date.now() - new Date(isoDate).getTime()) / 864e5);
791
+ if (days === 0) return "today";
792
+ if (days === 1) return "1 day ago";
793
+ return `${days} days ago`;
794
+ }
795
+ function countVulns(vulns) {
796
+ const c = { critical: 0, high: 0, moderate: 0, low: 0 };
797
+ for (const v of vulns) c[v.severity]++;
798
+ if (vulns.length === 0) return import_picocolors.default.green("none");
799
+ const parts = [];
800
+ if (c.critical) parts.push(import_picocolors.default.bold(import_picocolors.default.red(`${c.critical} critical`)));
801
+ if (c.high) parts.push(import_picocolors.default.red(`${c.high} high`));
802
+ if (c.moderate) parts.push(import_picocolors.default.yellow(`${c.moderate} moderate`));
803
+ if (c.low) parts.push(import_picocolors.default.dim(`${c.low} low`));
804
+ return parts.join(", ");
805
+ }
806
+ function printReport(report) {
807
+ const riskColored = colorRisk(report.riskLevel, report.score);
808
+ const icon = report.riskLevel === "LOW" ? import_picocolors.default.green("\u25CF") : report.riskLevel === "MEDIUM" ? import_picocolors.default.yellow("\u25CF") : report.riskLevel === "UNKNOWN" ? import_picocolors.default.dim("\u25CB") : import_picocolors.default.red("\u25CF");
809
+ console.log(` ${icon} ${import_picocolors.default.bold(report.server)} Score: ${riskColored}`);
810
+ if (report.source !== "npm") {
811
+ console.log(` ${import_picocolors.default.dim("Non-npm source \u2014 security data unavailable")}`);
812
+ console.log();
813
+ return;
814
+ }
815
+ if (report.metadata) {
816
+ const { weeklyDownloads, packageAge, lastPublish, maintainerCount, deprecated } = report.metadata;
817
+ const dlStr = weeklyDownloads.toLocaleString();
818
+ console.log(
819
+ ` ${import_picocolors.default.dim("Downloads:")} ${dlStr}/week ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Age:")} ${packageAge}d ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Last publish:")} ${daysAgo(lastPublish)} ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Maintainers:")} ${maintainerCount}` + (deprecated ? import_picocolors.default.red(" [DEPRECATED]") : "")
820
+ );
821
+ }
822
+ console.log(` ${import_picocolors.default.dim("Vulnerabilities:")} ${countVulns(report.vulnerabilities)}`);
823
+ if (report.vulnerabilities.length > 0) {
824
+ for (const v of report.vulnerabilities) {
825
+ const sevColor = v.severity === "critical" || v.severity === "high" ? import_picocolors.default.red : import_picocolors.default.yellow;
826
+ const url = v.url ? import_picocolors.default.dim(` ${v.url}`) : "";
827
+ console.log(` ${sevColor("\u25B8")} [${v.severity}] ${v.title}${url}`);
828
+ }
829
+ }
830
+ console.log();
831
+ }
832
+ var audit_default = (0, import_citty.defineCommand)({
833
+ meta: {
834
+ name: "audit",
835
+ description: "Scan installed MCP servers for security vulnerabilities and trust scores"
836
+ },
837
+ args: {
838
+ server: {
839
+ type: "positional",
840
+ description: "Specific server to audit (omit to audit all)",
841
+ required: false
842
+ },
843
+ json: {
844
+ type: "boolean",
845
+ description: "Output results as JSON",
846
+ default: false
847
+ },
848
+ fix: {
849
+ type: "boolean",
850
+ description: "Show available fix versions for vulnerable packages",
851
+ default: false
852
+ }
853
+ },
854
+ async run({ args }) {
855
+ const lockfile = readLockfile();
856
+ const { servers } = lockfile;
857
+ if (Object.keys(servers).length === 0) {
858
+ console.log(import_picocolors.default.dim("\n No MCP servers installed. Run mcpman install <server> to get started.\n"));
859
+ return;
860
+ }
861
+ const targets = {};
862
+ if (args.server) {
863
+ if (!servers[args.server]) {
864
+ console.error(import_picocolors.default.red(`
865
+ Server "${args.server}" not found in lockfile.
866
+ `));
867
+ process.exit(1);
868
+ }
869
+ targets[args.server] = servers[args.server];
870
+ } else {
871
+ Object.assign(targets, servers);
872
+ }
873
+ const spinner5 = (0, import_nanospinner.createSpinner)(`Scanning ${Object.keys(targets).length} server(s)...`).start();
874
+ let reports;
875
+ try {
876
+ reports = args.server ? [await scanServer(args.server, targets[args.server])] : await scanAllServers(targets);
877
+ } catch (err) {
878
+ spinner5.error({ text: "Scan failed" });
879
+ console.error(import_picocolors.default.red(String(err)));
880
+ process.exit(1);
881
+ }
882
+ spinner5.success({ text: `Scanned ${reports.length} server(s)` });
883
+ if (args.json) {
884
+ console.log(JSON.stringify(reports, null, 2));
885
+ return;
886
+ }
887
+ console.log(import_picocolors.default.bold("\n mcpman audit\n"));
888
+ console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
889
+ for (const report of reports) {
890
+ printReport(report);
891
+ }
892
+ console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
893
+ const withIssues = reports.filter(
894
+ (r) => r.riskLevel !== "LOW" && r.riskLevel !== "UNKNOWN"
895
+ );
896
+ const npmReports = reports.filter((r) => r.source === "npm");
897
+ const parts = [];
898
+ parts.push(`${reports.length} server(s) scanned`);
899
+ if (npmReports.length < reports.length) {
900
+ parts.push(import_picocolors.default.dim(`${reports.length - npmReports.length} non-npm (unverified)`));
901
+ }
902
+ if (withIssues.length > 0) {
903
+ parts.push(import_picocolors.default.yellow(`${withIssues.length} with issues`));
904
+ } else {
905
+ parts.push(import_picocolors.default.green("all clear"));
906
+ }
907
+ console.log(`
908
+ Summary: ${parts.join(" | ")}
909
+ `);
910
+ if (args.fix) {
911
+ const withVulns = reports.filter((r) => r.vulnerabilities.length > 0);
912
+ if (withVulns.length > 0) {
913
+ console.log(import_picocolors.default.bold(" Fix suggestions:"));
914
+ for (const r of withVulns) {
915
+ console.log(` ${import_picocolors.default.cyan("\u2192")} Run ${import_picocolors.default.cyan(`mcpman install ${r.server}@latest`)} to update`);
916
+ }
917
+ console.log();
918
+ }
919
+ }
920
+ }
921
+ });
922
+
923
+ // src/commands/doctor.ts
924
+ init_cjs_shims();
925
+ var import_citty2 = require("citty");
926
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
342
927
 
343
928
  // src/core/server-inventory.ts
344
929
  init_cjs_shims();
@@ -609,12 +1194,12 @@ async function quickHealthProbe(config, timeoutMs = 3e3) {
609
1194
 
610
1195
  // src/commands/doctor.ts
611
1196
  var CHECK_ICON = {
612
- pass: import_picocolors.default.green("\u2713"),
613
- fail: import_picocolors.default.red("\u2717"),
614
- skip: import_picocolors.default.dim("-"),
615
- warn: import_picocolors.default.yellow("\u26A0")
1197
+ pass: import_picocolors2.default.green("\u2713"),
1198
+ fail: import_picocolors2.default.red("\u2717"),
1199
+ skip: import_picocolors2.default.dim("-"),
1200
+ warn: import_picocolors2.default.yellow("\u26A0")
616
1201
  };
617
- var doctor_default = (0, import_citty.defineCommand)({
1202
+ var doctor_default = (0, import_citty2.defineCommand)({
618
1203
  meta: {
619
1204
  name: "doctor",
620
1205
  description: "Check MCP server health and configuration"
@@ -627,10 +1212,10 @@ var doctor_default = (0, import_citty.defineCommand)({
627
1212
  }
628
1213
  },
629
1214
  async run({ args }) {
630
- console.log(import_picocolors.default.bold("\n mcpman doctor\n"));
1215
+ console.log(import_picocolors2.default.bold("\n mcpman doctor\n"));
631
1216
  const servers = await getInstalledServers();
632
1217
  if (servers.length === 0) {
633
- console.log(import_picocolors.default.dim(" No MCP servers installed. Run mcpman install <server> to get started."));
1218
+ console.log(import_picocolors2.default.dim(" No MCP servers installed. Run mcpman install <server> to get started."));
634
1219
  return;
635
1220
  }
636
1221
  const tasks = servers.map((s) => () => checkServerHealth(s.name, s.config));
@@ -642,14 +1227,14 @@ var doctor_default = (0, import_citty.defineCommand)({
642
1227
  if (result.status === "healthy") passed++;
643
1228
  else failed++;
644
1229
  }
645
- console.log(import_picocolors.default.dim(" " + "\u2500".repeat(50)));
1230
+ console.log(import_picocolors2.default.dim(" " + "\u2500".repeat(50)));
646
1231
  const parts = [];
647
- if (passed > 0) parts.push(import_picocolors.default.green(`${passed} healthy`));
648
- if (failed > 0) parts.push(import_picocolors.default.red(`${failed} unhealthy`));
1232
+ if (passed > 0) parts.push(import_picocolors2.default.green(`${passed} healthy`));
1233
+ if (failed > 0) parts.push(import_picocolors2.default.red(`${failed} unhealthy`));
649
1234
  console.log(` Summary: ${parts.join(", ")}`);
650
1235
  if (failed > 0) {
651
1236
  if (!args.fix) {
652
- console.log(import_picocolors.default.dim(` Run ${import_picocolors.default.cyan("mcpman doctor --fix")} for fix suggestions.
1237
+ console.log(import_picocolors2.default.dim(` Run ${import_picocolors2.default.cyan("mcpman doctor --fix")} for fix suggestions.
653
1238
  `));
654
1239
  }
655
1240
  process.exit(1);
@@ -658,13 +1243,13 @@ var doctor_default = (0, import_citty.defineCommand)({
658
1243
  }
659
1244
  });
660
1245
  function printServerResult(result, showFix) {
661
- const icon = result.status === "healthy" ? import_picocolors.default.green("\u25CF") : import_picocolors.default.red("\u25CF");
662
- console.log(` ${icon} ${import_picocolors.default.bold(result.serverName)}`);
1246
+ const icon = result.status === "healthy" ? import_picocolors2.default.green("\u25CF") : import_picocolors2.default.red("\u25CF");
1247
+ console.log(` ${icon} ${import_picocolors2.default.bold(result.serverName)}`);
663
1248
  for (const check of result.checks) {
664
1249
  const checkIcon = check.skipped ? CHECK_ICON.skip : check.passed ? CHECK_ICON.pass : CHECK_ICON.fail;
665
1250
  console.log(` ${checkIcon} ${check.name}: ${check.message}`);
666
1251
  if (showFix && !check.passed && !check.skipped && check.fix) {
667
- console.log(` ${import_picocolors.default.yellow("\u2192")} Fix: ${import_picocolors.default.cyan(check.fix)}`);
1252
+ console.log(` ${import_picocolors2.default.yellow("\u2192")} Fix: ${import_picocolors2.default.cyan(check.fix)}`);
668
1253
  }
669
1254
  }
670
1255
  console.log();
@@ -673,11 +1258,11 @@ async function runParallel(tasks, concurrency) {
673
1258
  const results = [];
674
1259
  const executing = /* @__PURE__ */ new Set();
675
1260
  for (const task of tasks) {
676
- const p5 = task().then((r) => {
1261
+ const p9 = task().then((r) => {
677
1262
  results.push(r);
678
- executing.delete(p5);
1263
+ executing.delete(p9);
679
1264
  });
680
- executing.add(p5);
1265
+ executing.add(p9);
681
1266
  if (executing.size >= concurrency) {
682
1267
  await Promise.race(executing);
683
1268
  }
@@ -688,72 +1273,9 @@ async function runParallel(tasks, concurrency) {
688
1273
 
689
1274
  // src/commands/init.ts
690
1275
  init_cjs_shims();
691
- var import_citty2 = require("citty");
1276
+ var import_citty3 = require("citty");
692
1277
  var p = __toESM(require("@clack/prompts"), 1);
693
- var import_node_path4 = __toESM(require("path"), 1);
694
-
695
- // src/core/lockfile.ts
696
- init_cjs_shims();
697
- var import_node_fs2 = __toESM(require("fs"), 1);
698
- var import_node_path3 = __toESM(require("path"), 1);
699
- var import_node_os2 = __toESM(require("os"), 1);
700
- var LOCKFILE_NAME = "mcpman.lock";
701
- function findLockfile() {
702
- let dir = process.cwd();
703
- while (true) {
704
- const candidate = import_node_path3.default.join(dir, LOCKFILE_NAME);
705
- if (import_node_fs2.default.existsSync(candidate)) return candidate;
706
- const parent = import_node_path3.default.dirname(dir);
707
- if (parent === dir) break;
708
- dir = parent;
709
- }
710
- return null;
711
- }
712
- function getGlobalLockfilePath() {
713
- return import_node_path3.default.join(import_node_os2.default.homedir(), ".mcpman", LOCKFILE_NAME);
714
- }
715
- function resolveLockfilePath() {
716
- return findLockfile() ?? getGlobalLockfilePath();
717
- }
718
- function readLockfile(filePath) {
719
- const target = filePath ?? resolveLockfilePath();
720
- if (!import_node_fs2.default.existsSync(target)) {
721
- return { lockfileVersion: 1, servers: {} };
722
- }
723
- try {
724
- const raw = import_node_fs2.default.readFileSync(target, "utf-8");
725
- return JSON.parse(raw);
726
- } catch {
727
- return { lockfileVersion: 1, servers: {} };
728
- }
729
- }
730
- function serialize(data) {
731
- const sorted = {
732
- lockfileVersion: data.lockfileVersion,
733
- servers: Object.fromEntries(
734
- Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
735
- )
736
- };
737
- return JSON.stringify(sorted, null, 2) + "\n";
738
- }
739
- function writeLockfile(data, filePath) {
740
- const target = filePath ?? resolveLockfilePath();
741
- const dir = import_node_path3.default.dirname(target);
742
- if (!import_node_fs2.default.existsSync(dir)) {
743
- import_node_fs2.default.mkdirSync(dir, { recursive: true });
744
- }
745
- const tmp = `${target}.tmp`;
746
- import_node_fs2.default.writeFileSync(tmp, serialize(data), "utf-8");
747
- import_node_fs2.default.renameSync(tmp, target);
748
- }
749
- function addEntry(name, entry, filePath) {
750
- const data = readLockfile(filePath);
751
- data.servers[name] = entry;
752
- writeLockfile(data, filePath);
753
- }
754
- function createEmptyLockfile(filePath) {
755
- writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
756
- }
1278
+ var import_node_path5 = __toESM(require("path"), 1);
757
1279
 
758
1280
  // src/core/registry.ts
759
1281
  init_cjs_shims();
@@ -865,22 +1387,34 @@ async function resolveFromGitHub(githubUrl) {
865
1387
  }
866
1388
 
867
1389
  // src/commands/init.ts
868
- var init_default = (0, import_citty2.defineCommand)({
1390
+ var init_default = (0, import_citty3.defineCommand)({
869
1391
  meta: {
870
1392
  name: "init",
871
1393
  description: "Initialize mcpman.lock in the current project"
872
1394
  },
873
- args: {},
874
- async run() {
875
- p.intro("mcpman init");
876
- const targetPath = import_node_path4.default.join(process.cwd(), LOCKFILE_NAME);
877
- const existing = findLockfile();
1395
+ args: {
1396
+ yes: {
1397
+ type: "boolean",
1398
+ alias: "y",
1399
+ description: "Auto-import all servers without prompting",
1400
+ default: false
1401
+ }
1402
+ },
1403
+ async run({ args }) {
1404
+ const nonInteractive = args.yes || !process.stdout.isTTY;
1405
+ p.intro("mcpman init");
1406
+ const targetPath = import_node_path5.default.join(process.cwd(), LOCKFILE_NAME);
1407
+ const existing = findLockfile();
878
1408
  if (existing) {
879
- p.log.warn(`Lockfile already exists: ${existing}`);
880
- const overwrite = await p.confirm({ message: "Overwrite?" });
881
- if (p.isCancel(overwrite) || !overwrite) {
882
- p.outro("Cancelled.");
883
- return;
1409
+ if (nonInteractive) {
1410
+ p.log.warn(`Lockfile already exists: ${existing} \u2014 overwriting (non-interactive).`);
1411
+ } else {
1412
+ p.log.warn(`Lockfile already exists: ${existing}`);
1413
+ const overwrite = await p.confirm({ message: "Overwrite?" });
1414
+ if (p.isCancel(overwrite) || !overwrite) {
1415
+ p.outro("Cancelled.");
1416
+ return;
1417
+ }
884
1418
  }
885
1419
  }
886
1420
  let clients = [];
@@ -906,20 +1440,26 @@ var init_default = (0, import_citty2.defineCommand)({
906
1440
  p.outro(`Created ${LOCKFILE_NAME} \u2014 add it to version control!`);
907
1441
  return;
908
1442
  }
909
- const options = clientServers.map((cs) => ({
910
- value: cs.client.type,
911
- label: `${cs.client.displayName} (${Object.keys(cs.servers).length} servers)`
912
- }));
913
- const toImport = await p.multiselect({
914
- message: "Import existing servers into lockfile?",
915
- options,
916
- required: false
917
- });
918
- if (p.isCancel(toImport)) {
919
- p.outro(`Created empty ${LOCKFILE_NAME}`);
920
- return;
1443
+ let selected;
1444
+ if (nonInteractive) {
1445
+ selected = clientServers.map((cs) => cs.client.type);
1446
+ p.log.info(`Non-interactive mode: importing all ${clientServers.length} client(s).`);
1447
+ } else {
1448
+ const options = clientServers.map((cs) => ({
1449
+ value: cs.client.type,
1450
+ label: `${cs.client.displayName} (${Object.keys(cs.servers).length} servers)`
1451
+ }));
1452
+ const toImport = await p.multiselect({
1453
+ message: "Import existing servers into lockfile?",
1454
+ options,
1455
+ required: false
1456
+ });
1457
+ if (p.isCancel(toImport)) {
1458
+ p.outro(`Created empty ${LOCKFILE_NAME}`);
1459
+ return;
1460
+ }
1461
+ selected = toImport;
921
1462
  }
922
- const selected = toImport;
923
1463
  let importCount = 0;
924
1464
  for (const cs of clientServers) {
925
1465
  if (!selected.includes(cs.client.type)) continue;
@@ -949,7 +1489,7 @@ var init_default = (0, import_citty2.defineCommand)({
949
1489
 
950
1490
  // src/commands/install.ts
951
1491
  init_cjs_shims();
952
- var import_citty3 = require("citty");
1492
+ var import_citty4 = require("citty");
953
1493
 
954
1494
  // src/core/installer.ts
955
1495
  init_cjs_shims();
@@ -1001,17 +1541,17 @@ async function loadClients() {
1001
1541
  }
1002
1542
  async function installServer(input, options = {}) {
1003
1543
  p2.intro("mcpman install");
1004
- const spinner2 = p2.spinner();
1005
- spinner2.start("Resolving server...");
1544
+ const spinner5 = p2.spinner();
1545
+ spinner5.start("Resolving server...");
1006
1546
  let metadata;
1007
1547
  try {
1008
1548
  metadata = await resolveServer(input);
1009
1549
  } catch (err) {
1010
- spinner2.stop("Resolution failed");
1550
+ spinner5.stop("Resolution failed");
1011
1551
  p2.log.error(err instanceof Error ? err.message : String(err));
1012
1552
  process.exit(1);
1013
1553
  }
1014
- spinner2.stop(`Found: ${metadata.name}@${metadata.version}`);
1554
+ spinner5.stop(`Found: ${metadata.name}@${metadata.version}`);
1015
1555
  const clients = await loadClients();
1016
1556
  if (clients.length === 0) {
1017
1557
  p2.log.warn("No supported AI clients detected on this machine.");
@@ -1065,18 +1605,18 @@ async function installServer(input, options = {}) {
1065
1605
  args: metadata.args,
1066
1606
  ...Object.keys(collectedEnv).length > 0 ? { env: collectedEnv } : {}
1067
1607
  };
1068
- spinner2.start("Writing config...");
1608
+ spinner5.start("Writing config...");
1069
1609
  const clientTypes = [];
1070
1610
  for (const client of selectedClients) {
1071
1611
  try {
1072
1612
  await client.addServer(metadata.name, entry);
1073
1613
  clientTypes.push(client.type);
1074
1614
  } catch (err) {
1075
- spinner2.stop("Partial failure");
1615
+ spinner5.stop("Partial failure");
1076
1616
  p2.log.warn(`Failed to write to ${client.displayName}: ${err instanceof Error ? err.message : String(err)}`);
1077
1617
  }
1078
1618
  }
1079
- spinner2.stop("Config written");
1619
+ spinner5.stop("Config written");
1080
1620
  const source = detectSource(input);
1081
1621
  const integrity = computeIntegrity(metadata.resolved);
1082
1622
  addEntry(metadata.name, {
@@ -1098,7 +1638,7 @@ async function installServer(input, options = {}) {
1098
1638
 
1099
1639
  // src/utils/logger.ts
1100
1640
  init_cjs_shims();
1101
- var import_picocolors2 = __toESM(require("picocolors"), 1);
1641
+ var import_picocolors3 = __toESM(require("picocolors"), 1);
1102
1642
  var noColor = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
1103
1643
  var isVerbose = process.argv.includes("--verbose");
1104
1644
  var isJson = process.argv.includes("--json");
@@ -1107,11 +1647,11 @@ function colorize(fn, text2) {
1107
1647
  }
1108
1648
  function info(message) {
1109
1649
  if (isJson) return;
1110
- console.log(`${colorize(import_picocolors2.default.cyan, "i")} ${message}`);
1650
+ console.log(`${colorize(import_picocolors3.default.cyan, "i")} ${message}`);
1111
1651
  }
1112
1652
  function error(message) {
1113
1653
  if (isJson) return;
1114
- console.error(`${colorize(import_picocolors2.default.red, "\u2717")} ${message}`);
1654
+ console.error(`${colorize(import_picocolors3.default.red, "\u2717")} ${message}`);
1115
1655
  }
1116
1656
  function json(data) {
1117
1657
  console.log(JSON.stringify(data, null, 2));
@@ -1119,7 +1659,7 @@ function json(data) {
1119
1659
 
1120
1660
  // src/commands/install.ts
1121
1661
  var p3 = __toESM(require("@clack/prompts"), 1);
1122
- var install_default = (0, import_citty3.defineCommand)({
1662
+ var install_default = (0, import_citty4.defineCommand)({
1123
1663
  meta: {
1124
1664
  name: "install",
1125
1665
  description: "Install an MCP server into one or more AI clients"
@@ -1182,14 +1722,14 @@ async function restoreFromLockfile() {
1182
1722
 
1183
1723
  // src/commands/list.ts
1184
1724
  init_cjs_shims();
1185
- var import_citty4 = require("citty");
1186
- var import_picocolors3 = __toESM(require("picocolors"), 1);
1725
+ var import_citty5 = require("citty");
1726
+ var import_picocolors4 = __toESM(require("picocolors"), 1);
1187
1727
  var STATUS_ICON = {
1188
- healthy: import_picocolors3.default.green("\u25CF"),
1189
- unhealthy: import_picocolors3.default.red("\u25CF"),
1190
- unknown: import_picocolors3.default.dim("\u25CB")
1728
+ healthy: import_picocolors4.default.green("\u25CF"),
1729
+ unhealthy: import_picocolors4.default.red("\u25CF"),
1730
+ unknown: import_picocolors4.default.dim("\u25CB")
1191
1731
  };
1192
- var list_default = (0, import_citty4.defineCommand)({
1732
+ var list_default = (0, import_citty5.defineCommand)({
1193
1733
  meta: {
1194
1734
  name: "list",
1195
1735
  description: "List installed MCP servers"
@@ -1209,13 +1749,13 @@ var list_default = (0, import_citty4.defineCommand)({
1209
1749
  const servers = await getInstalledServers(args.client);
1210
1750
  if (servers.length === 0) {
1211
1751
  const filter = args.client ? ` for client "${args.client}"` : "";
1212
- console.log(import_picocolors3.default.dim(`No MCP servers installed${filter}. Run ${import_picocolors3.default.cyan("mcpman install <server>")} to get started.`));
1752
+ console.log(import_picocolors4.default.dim(`No MCP servers installed${filter}. Run ${import_picocolors4.default.cyan("mcpman install <server>")} to get started.`));
1213
1753
  return;
1214
1754
  }
1215
1755
  const withStatus = await Promise.all(
1216
1756
  servers.map(async (s) => ({
1217
1757
  ...s,
1218
- status: await quickHealthProbe(s.config, 3e3)
1758
+ status: await quickHealthProbe(s.config, 5e3)
1219
1759
  }))
1220
1760
  );
1221
1761
  if (args.json) {
@@ -1233,8 +1773,8 @@ var list_default = (0, import_citty4.defineCommand)({
1233
1773
  const nameWidth = Math.max(4, ...withStatus.map((s) => s.name.length));
1234
1774
  const clientsWidth = Math.max(7, ...withStatus.map((s) => formatClients(s.clients).length));
1235
1775
  const header = ` ${pad("NAME", nameWidth)} ${pad("CLIENT(S)", clientsWidth)} ${pad("COMMAND", 20)} STATUS`;
1236
- console.log(import_picocolors3.default.dim(header));
1237
- console.log(import_picocolors3.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`));
1776
+ console.log(import_picocolors4.default.dim(header));
1777
+ console.log(import_picocolors4.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`));
1238
1778
  for (const s of withStatus) {
1239
1779
  const icon = STATUS_ICON[s.status];
1240
1780
  const clientsStr = formatClients(s.clients);
@@ -1242,7 +1782,7 @@ var list_default = (0, import_citty4.defineCommand)({
1242
1782
  console.log(` ${pad(s.name, nameWidth)} ${pad(clientsStr, clientsWidth)} ${pad(cmdStr, 20)} ${icon} ${s.status}`);
1243
1783
  }
1244
1784
  const clientSet = new Set(withStatus.flatMap((s) => s.clients));
1245
- console.log(import_picocolors3.default.dim(`
1785
+ console.log(import_picocolors4.default.dim(`
1246
1786
  ${withStatus.length} server${withStatus.length !== 1 ? "s" : ""} \xB7 ${clientSet.size} client${clientSet.size !== 1 ? "s" : ""}`));
1247
1787
  }
1248
1788
  });
@@ -1264,9 +1804,9 @@ function formatClients(clients) {
1264
1804
 
1265
1805
  // src/commands/remove.ts
1266
1806
  init_cjs_shims();
1267
- var import_citty5 = require("citty");
1807
+ var import_citty6 = require("citty");
1268
1808
  var p4 = __toESM(require("@clack/prompts"), 1);
1269
- var import_picocolors4 = __toESM(require("picocolors"), 1);
1809
+ var import_picocolors5 = __toESM(require("picocolors"), 1);
1270
1810
  init_client_detector();
1271
1811
  var CLIENT_DISPLAY2 = {
1272
1812
  "claude-desktop": "Claude",
@@ -1277,7 +1817,7 @@ var CLIENT_DISPLAY2 = {
1277
1817
  function clientDisplayName(type) {
1278
1818
  return CLIENT_DISPLAY2[type] ?? type;
1279
1819
  }
1280
- var remove_default = (0, import_citty5.defineCommand)({
1820
+ var remove_default = (0, import_citty6.defineCommand)({
1281
1821
  meta: {
1282
1822
  name: "remove",
1283
1823
  description: "Remove an MCP server from one or more AI clients"
@@ -1304,7 +1844,7 @@ var remove_default = (0, import_citty5.defineCommand)({
1304
1844
  }
1305
1845
  },
1306
1846
  async run({ args }) {
1307
- p4.intro(import_picocolors4.default.bold("mcpman remove"));
1847
+ p4.intro(import_picocolors5.default.bold("mcpman remove"));
1308
1848
  const serverName = args.server;
1309
1849
  const servers = await getInstalledServers();
1310
1850
  const match = servers.find((s) => s.name === serverName);
@@ -1312,7 +1852,7 @@ var remove_default = (0, import_citty5.defineCommand)({
1312
1852
  p4.log.warn(`Server "${serverName}" is not installed.`);
1313
1853
  const similar = servers.filter((s) => s.name.includes(serverName) || serverName.includes(s.name));
1314
1854
  if (similar.length > 0) {
1315
- p4.log.info(`Did you mean: ${similar.map((s) => import_picocolors4.default.cyan(s.name)).join(", ")}?`);
1855
+ p4.log.info(`Did you mean: ${similar.map((s) => import_picocolors5.default.cyan(s.name)).join(", ")}?`);
1316
1856
  }
1317
1857
  p4.outro("Nothing to remove.");
1318
1858
  return;
@@ -1347,7 +1887,7 @@ var remove_default = (0, import_citty5.defineCommand)({
1347
1887
  if (!args.yes) {
1348
1888
  const clientNames = targetClients.map(clientDisplayName).join(", ");
1349
1889
  const confirmed = await p4.confirm({
1350
- message: `Remove ${import_picocolors4.default.cyan(serverName)} from ${import_picocolors4.default.yellow(clientNames)}?`
1890
+ message: `Remove ${import_picocolors5.default.cyan(serverName)} from ${import_picocolors5.default.yellow(clientNames)}?`
1351
1891
  });
1352
1892
  if (p4.isCancel(confirmed) || !confirmed) {
1353
1893
  p4.outro("Cancelled.");
@@ -1372,17 +1912,697 @@ var remove_default = (0, import_citty5.defineCommand)({
1372
1912
  }
1373
1913
  if (errors.length > 0) {
1374
1914
  for (const e of errors) p4.log.error(e);
1375
- p4.outro(import_picocolors4.default.red("Completed with errors."));
1915
+ p4.outro(import_picocolors5.default.red("Completed with errors."));
1916
+ process.exit(1);
1917
+ }
1918
+ p4.outro(import_picocolors5.default.green(`Removed "${serverName}" successfully.`));
1919
+ }
1920
+ });
1921
+
1922
+ // src/commands/secrets.ts
1923
+ init_cjs_shims();
1924
+ var import_citty7 = require("citty");
1925
+ var import_picocolors6 = __toESM(require("picocolors"), 1);
1926
+ var p6 = __toESM(require("@clack/prompts"), 1);
1927
+ init_vault_service();
1928
+ function maskValue(value) {
1929
+ if (value.length <= 8) return "***";
1930
+ return `${value.slice(0, 4)}***${value.slice(-3)}`;
1931
+ }
1932
+ function parseKeyValue(input) {
1933
+ const idx = input.indexOf("=");
1934
+ if (idx <= 0) return null;
1935
+ return { key: input.slice(0, idx), value: input.slice(idx + 1) };
1936
+ }
1937
+ var setCommand = (0, import_citty7.defineCommand)({
1938
+ meta: { name: "set", description: "Store an encrypted secret for a server" },
1939
+ args: {
1940
+ server: {
1941
+ type: "positional",
1942
+ description: "Server name (e.g. @modelcontextprotocol/server-github)",
1943
+ required: true
1944
+ },
1945
+ keyvalue: {
1946
+ type: "positional",
1947
+ description: "KEY=VALUE pair to store",
1948
+ required: true
1949
+ }
1950
+ },
1951
+ async run({ args }) {
1952
+ const parsed = parseKeyValue(args.keyvalue);
1953
+ if (!parsed) {
1954
+ console.error(import_picocolors6.default.red("\u2717") + " Invalid format. Expected KEY=VALUE");
1955
+ process.exit(1);
1956
+ }
1957
+ p6.intro(import_picocolors6.default.cyan("mcpman secrets set"));
1958
+ const isNew = listSecrets(args.server).length === 0 || !listSecrets(args.server)[0]?.keys.includes(parsed.key);
1959
+ const vaultPath = (await Promise.resolve().then(() => (init_vault_service(), vault_service_exports))).getVaultPath();
1960
+ const vaultExists = (await import("fs")).existsSync(vaultPath);
1961
+ const password2 = await getMasterPassword(!vaultExists && isNew);
1962
+ const spin = p6.spinner();
1963
+ spin.start("Encrypting secret...");
1964
+ try {
1965
+ setSecret(args.server, parsed.key, parsed.value, password2);
1966
+ spin.stop(
1967
+ `${import_picocolors6.default.green("\u2713")} Stored ${import_picocolors6.default.bold(parsed.key)} for ${import_picocolors6.default.cyan(args.server)}`
1968
+ );
1969
+ } catch (err) {
1970
+ spin.stop(import_picocolors6.default.red("\u2717") + " Failed to store secret");
1971
+ console.error(import_picocolors6.default.dim(String(err)));
1972
+ process.exit(1);
1973
+ }
1974
+ p6.outro(import_picocolors6.default.dim("Secret encrypted and saved to vault."));
1975
+ }
1976
+ });
1977
+ var listCommand = (0, import_citty7.defineCommand)({
1978
+ meta: { name: "list", description: "List secret keys stored in the vault" },
1979
+ args: {
1980
+ server: {
1981
+ type: "positional",
1982
+ description: "Filter by server name (optional)",
1983
+ required: false
1984
+ }
1985
+ },
1986
+ async run({ args }) {
1987
+ const results = listSecrets(args.server || void 0);
1988
+ if (results.length === 0) {
1989
+ const filter = args.server ? ` for ${import_picocolors6.default.cyan(args.server)}` : "";
1990
+ console.log(import_picocolors6.default.dim(`No secrets stored${filter}.`));
1991
+ return;
1992
+ }
1993
+ console.log("");
1994
+ for (const { server, keys } of results) {
1995
+ console.log(import_picocolors6.default.bold(import_picocolors6.default.cyan(server)));
1996
+ for (const key of keys) {
1997
+ console.log(` ${import_picocolors6.default.green("\u25CF")} ${import_picocolors6.default.bold(key)} ${import_picocolors6.default.dim(maskValue("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"))}`);
1998
+ }
1999
+ console.log("");
2000
+ }
2001
+ const total = results.reduce((n, r) => n + r.keys.length, 0);
2002
+ console.log(import_picocolors6.default.dim(` ${total} secret${total !== 1 ? "s" : ""} in ${results.length} server${results.length !== 1 ? "s" : ""}`));
2003
+ }
2004
+ });
2005
+ var removeCommand = (0, import_citty7.defineCommand)({
2006
+ meta: { name: "remove", description: "Delete a secret from the vault" },
2007
+ args: {
2008
+ server: {
2009
+ type: "positional",
2010
+ description: "Server name",
2011
+ required: true
2012
+ },
2013
+ key: {
2014
+ type: "positional",
2015
+ description: "Secret key to remove",
2016
+ required: true
2017
+ }
2018
+ },
2019
+ async run({ args }) {
2020
+ const confirmed = await p6.confirm({
2021
+ message: `Remove ${import_picocolors6.default.bold(args.key)} from ${import_picocolors6.default.cyan(args.server)}?`,
2022
+ initialValue: false
2023
+ });
2024
+ if (p6.isCancel(confirmed) || !confirmed) {
2025
+ p6.cancel("Cancelled.");
2026
+ return;
2027
+ }
2028
+ try {
2029
+ removeSecret(args.server, args.key);
2030
+ console.log(`${import_picocolors6.default.green("\u2713")} Removed ${import_picocolors6.default.bold(args.key)} from ${import_picocolors6.default.cyan(args.server)}`);
2031
+ } catch (err) {
2032
+ console.error(import_picocolors6.default.red("\u2717") + " Failed to remove secret");
2033
+ console.error(import_picocolors6.default.dim(String(err)));
2034
+ process.exit(1);
2035
+ }
2036
+ }
2037
+ });
2038
+ var secrets_default = (0, import_citty7.defineCommand)({
2039
+ meta: {
2040
+ name: "secrets",
2041
+ description: "Manage encrypted secrets for MCP servers"
2042
+ },
2043
+ subCommands: {
2044
+ set: setCommand,
2045
+ list: listCommand,
2046
+ remove: removeCommand
2047
+ }
2048
+ });
2049
+
2050
+ // src/commands/sync.ts
2051
+ init_cjs_shims();
2052
+ var import_citty8 = require("citty");
2053
+ var p7 = __toESM(require("@clack/prompts"), 1);
2054
+ var import_picocolors7 = __toESM(require("picocolors"), 1);
2055
+
2056
+ // src/core/config-diff.ts
2057
+ init_cjs_shims();
2058
+ function reconstructServerEntry(lockEntry) {
2059
+ const entry = {
2060
+ command: lockEntry.command
2061
+ };
2062
+ if (lockEntry.args && lockEntry.args.length > 0) {
2063
+ entry.args = lockEntry.args;
2064
+ }
2065
+ if (lockEntry.envVars && lockEntry.envVars.length > 0) {
2066
+ entry.env = Object.fromEntries(lockEntry.envVars.map((k) => [k, ""]));
2067
+ }
2068
+ return entry;
2069
+ }
2070
+ function computeDiff(lockfile, clientConfigs) {
2071
+ const actions = [];
2072
+ for (const [server, lockEntry] of Object.entries(lockfile.servers)) {
2073
+ for (const client of lockEntry.clients) {
2074
+ const config = clientConfigs.get(client);
2075
+ if (!config) continue;
2076
+ if (server in config.servers) {
2077
+ actions.push({ server, client, action: "ok" });
2078
+ } else {
2079
+ actions.push({
2080
+ server,
2081
+ client,
2082
+ action: "add",
2083
+ entry: reconstructServerEntry(lockEntry)
2084
+ });
2085
+ }
2086
+ }
2087
+ }
2088
+ for (const [client, config] of clientConfigs) {
2089
+ for (const server of Object.keys(config.servers)) {
2090
+ if (!(server in lockfile.servers)) {
2091
+ actions.push({ server, client, action: "extra" });
2092
+ }
2093
+ }
2094
+ }
2095
+ return actions;
2096
+ }
2097
+ function computeDiffFromClient(sourceClient, clientConfigs) {
2098
+ const actions = [];
2099
+ const sourceConfig = clientConfigs.get(sourceClient);
2100
+ if (!sourceConfig) return [];
2101
+ for (const [client, config] of clientConfigs) {
2102
+ if (client === sourceClient) continue;
2103
+ for (const [server, entry] of Object.entries(sourceConfig.servers)) {
2104
+ if (server in config.servers) {
2105
+ actions.push({ server, client, action: "ok" });
2106
+ } else {
2107
+ actions.push({ server, client, action: "add", entry });
2108
+ }
2109
+ }
2110
+ for (const server of Object.keys(config.servers)) {
2111
+ if (!(server in sourceConfig.servers)) {
2112
+ actions.push({ server, client, action: "extra" });
2113
+ }
2114
+ }
2115
+ }
2116
+ return actions;
2117
+ }
2118
+
2119
+ // src/core/sync-engine.ts
2120
+ init_cjs_shims();
2121
+ init_client_detector();
2122
+ async function applySyncActions(actions, clients) {
2123
+ const result = { applied: 0, failed: 0, errors: [] };
2124
+ const addActions = actions.filter((a) => a.action === "add" && a.entry);
2125
+ for (const action of addActions) {
2126
+ const handler = clients.get(action.client);
2127
+ if (!handler || !action.entry) {
2128
+ result.failed++;
2129
+ result.errors.push({
2130
+ server: action.server,
2131
+ client: action.client,
2132
+ error: "No handler available for client"
2133
+ });
2134
+ continue;
2135
+ }
2136
+ try {
2137
+ await handler.addServer(action.server, action.entry);
2138
+ result.applied++;
2139
+ } catch (err) {
2140
+ result.failed++;
2141
+ result.errors.push({
2142
+ server: action.server,
2143
+ client: action.client,
2144
+ error: String(err)
2145
+ });
2146
+ }
2147
+ }
2148
+ return result;
2149
+ }
2150
+ async function getClientConfigs() {
2151
+ const configs = /* @__PURE__ */ new Map();
2152
+ const handlers = /* @__PURE__ */ new Map();
2153
+ let installedClients;
2154
+ try {
2155
+ installedClients = await getInstalledClients();
2156
+ } catch {
2157
+ return { configs, handlers };
2158
+ }
2159
+ await Promise.all(
2160
+ installedClients.map(async (handler) => {
2161
+ try {
2162
+ const config = await handler.readConfig();
2163
+ configs.set(handler.type, config);
2164
+ handlers.set(handler.type, handler);
2165
+ } catch (err) {
2166
+ console.warn(`[mcpman] Warning: could not read config for ${handler.displayName}: ${String(err)}`);
2167
+ }
2168
+ })
2169
+ );
2170
+ return { configs, handlers };
2171
+ }
2172
+
2173
+ // src/commands/sync.ts
2174
+ var VALID_CLIENTS = ["claude-desktop", "cursor", "vscode", "windsurf"];
2175
+ var CLIENT_DISPLAY3 = {
2176
+ "claude-desktop": "Claude Desktop",
2177
+ cursor: "Cursor",
2178
+ vscode: "VS Code",
2179
+ windsurf: "Windsurf"
2180
+ };
2181
+ var sync_default = (0, import_citty8.defineCommand)({
2182
+ meta: {
2183
+ name: "sync",
2184
+ description: "Sync MCP server configs across all detected AI clients"
2185
+ },
2186
+ args: {
2187
+ "dry-run": {
2188
+ type: "boolean",
2189
+ description: "Preview changes without applying them",
2190
+ default: false
2191
+ },
2192
+ source: {
2193
+ type: "string",
2194
+ description: "Use a specific client as source of truth (claude-desktop, cursor, vscode, windsurf)"
2195
+ },
2196
+ yes: {
2197
+ type: "boolean",
2198
+ description: "Skip confirmation prompt",
2199
+ default: false
2200
+ }
2201
+ },
2202
+ async run({ args }) {
2203
+ p7.intro(`${import_picocolors7.default.cyan("mcpman sync")}`);
2204
+ const sourceClient = args.source;
2205
+ if (sourceClient && !VALID_CLIENTS.includes(sourceClient)) {
2206
+ p7.log.error(`Invalid --source "${sourceClient}". Must be one of: ${VALID_CLIENTS.join(", ")}`);
2207
+ process.exit(1);
2208
+ }
2209
+ const spinner5 = p7.spinner();
2210
+ spinner5.start("Detecting clients and reading configs...");
2211
+ const { configs, handlers } = await getClientConfigs();
2212
+ spinner5.stop(`Found ${configs.size} client(s)`);
2213
+ if (configs.size === 0) {
2214
+ p7.log.warn("No AI clients detected. Install Claude Desktop, Cursor, VS Code, or Windsurf first.");
2215
+ process.exit(0);
2216
+ }
2217
+ let actions;
2218
+ if (sourceClient) {
2219
+ if (!configs.has(sourceClient)) {
2220
+ p7.log.error(`Source client "${sourceClient}" is not detected or its config is unreadable.`);
2221
+ process.exit(1);
2222
+ }
2223
+ p7.log.info(`Using ${CLIENT_DISPLAY3[sourceClient]} as source of truth`);
2224
+ actions = computeDiffFromClient(sourceClient, configs);
2225
+ } else {
2226
+ const lockfile = readLockfile();
2227
+ actions = computeDiff(lockfile, configs);
2228
+ }
2229
+ printDiffTable(actions);
2230
+ const addCount = actions.filter((a) => a.action === "add").length;
2231
+ const extraCount = actions.filter((a) => a.action === "extra").length;
2232
+ if (addCount === 0 && extraCount === 0) {
2233
+ p7.outro(import_picocolors7.default.green("All clients are in sync."));
2234
+ process.exit(0);
2235
+ }
2236
+ const parts = [];
2237
+ if (addCount > 0) parts.push(import_picocolors7.default.green(`${addCount} to add`));
2238
+ if (extraCount > 0) parts.push(import_picocolors7.default.yellow(`${extraCount} extra (informational)`));
2239
+ p7.log.info(parts.join(" \xB7 "));
2240
+ if (args["dry-run"]) {
2241
+ p7.outro(import_picocolors7.default.dim("Dry run \u2014 no changes applied."));
2242
+ process.exit(1);
2243
+ }
2244
+ if (addCount === 0) {
2245
+ p7.outro(import_picocolors7.default.dim("No additions needed. Extra servers left untouched."));
2246
+ process.exit(1);
2247
+ }
2248
+ if (!args.yes) {
2249
+ const confirmed = await p7.confirm({
2250
+ message: `Apply ${addCount} addition(s) to client configs?`,
2251
+ initialValue: true
2252
+ });
2253
+ if (p7.isCancel(confirmed) || !confirmed) {
2254
+ p7.outro(import_picocolors7.default.dim("Cancelled \u2014 no changes applied."));
2255
+ process.exit(0);
2256
+ }
2257
+ }
2258
+ spinner5.start("Applying sync changes...");
2259
+ const result = await applySyncActions(actions, handlers);
2260
+ spinner5.stop("Done");
2261
+ if (result.applied > 0) {
2262
+ p7.log.success(`Added ${result.applied} server(s) to client configs.`);
2263
+ }
2264
+ if (result.failed > 0) {
2265
+ for (const e of result.errors) {
2266
+ p7.log.error(`Failed to add "${e.server}" to ${e.client}: ${e.error}`);
2267
+ }
2268
+ }
2269
+ p7.outro(result.failed === 0 ? import_picocolors7.default.green("Sync complete.") : import_picocolors7.default.yellow("Sync complete with errors."));
2270
+ process.exit(result.failed > 0 ? 1 : 0);
2271
+ }
2272
+ });
2273
+ function printDiffTable(actions) {
2274
+ if (actions.length === 0) {
2275
+ p7.log.info("No actions to display.");
2276
+ return;
2277
+ }
2278
+ const nameWidth = Math.max(6, ...actions.map((a) => a.server.length));
2279
+ const clientWidth = Math.max(6, ...actions.map((a) => CLIENT_DISPLAY3[a.client]?.length ?? a.client.length));
2280
+ const header = ` ${pad2("SERVER", nameWidth)} ${pad2("CLIENT", clientWidth)} STATUS`;
2281
+ console.log(import_picocolors7.default.dim(header));
2282
+ console.log(import_picocolors7.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientWidth)} ------`));
2283
+ for (const action of actions) {
2284
+ const clientDisplay = CLIENT_DISPLAY3[action.client] ?? action.client;
2285
+ const [icon, statusText] = formatAction(action.action);
2286
+ console.log(` ${pad2(action.server, nameWidth)} ${pad2(clientDisplay, clientWidth)} ${icon} ${statusText}`);
2287
+ }
2288
+ console.log("");
2289
+ }
2290
+ function formatAction(action) {
2291
+ switch (action) {
2292
+ case "add":
2293
+ return [import_picocolors7.default.green("+"), import_picocolors7.default.green("missing \u2014 will add")];
2294
+ case "extra":
2295
+ return [import_picocolors7.default.yellow("?"), import_picocolors7.default.yellow("extra (not in lockfile)")];
2296
+ case "ok":
2297
+ return [import_picocolors7.default.dim("\xB7"), import_picocolors7.default.dim("in sync")];
2298
+ }
2299
+ }
2300
+ function pad2(s, width) {
2301
+ return s.length >= width ? s : s + " ".repeat(width - s.length);
2302
+ }
2303
+
2304
+ // src/commands/update.ts
2305
+ init_cjs_shims();
2306
+ var import_citty9 = require("citty");
2307
+ var p8 = __toESM(require("@clack/prompts"), 1);
2308
+ var import_picocolors9 = __toESM(require("picocolors"), 1);
2309
+
2310
+ // src/core/version-checker.ts
2311
+ init_cjs_shims();
2312
+ function compareVersions(a, b) {
2313
+ const aParts = a.replace(/^v/, "").split(".").map(Number);
2314
+ const bParts = b.replace(/^v/, "").split(".").map(Number);
2315
+ const len = Math.max(aParts.length, bParts.length);
2316
+ for (let i = 0; i < len; i++) {
2317
+ const aN = aParts[i] ?? 0;
2318
+ const bN = bParts[i] ?? 0;
2319
+ if (Number.isNaN(aN) || Number.isNaN(bN)) return 0;
2320
+ if (aN < bN) return -1;
2321
+ if (aN > bN) return 1;
2322
+ }
2323
+ return 0;
2324
+ }
2325
+ function detectUpdateType(current, latest) {
2326
+ const cParts = current.replace(/^v/, "").split(".").map(Number);
2327
+ const lParts = latest.replace(/^v/, "").split(".").map(Number);
2328
+ if ((lParts[0] ?? 0) > (cParts[0] ?? 0)) return "major";
2329
+ if ((lParts[1] ?? 0) > (cParts[1] ?? 0)) return "minor";
2330
+ return "patch";
2331
+ }
2332
+ async function fetchNpmLatest(packageName) {
2333
+ try {
2334
+ const res = await fetch(
2335
+ `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
2336
+ {
2337
+ headers: { Accept: "application/json" },
2338
+ signal: AbortSignal.timeout(8e3)
2339
+ }
2340
+ );
2341
+ if (!res.ok) return null;
2342
+ const data = await res.json();
2343
+ return typeof data.version === "string" ? data.version : null;
2344
+ } catch {
2345
+ return null;
2346
+ }
2347
+ }
2348
+ async function fetchSmitheryLatest(name) {
2349
+ try {
2350
+ const res = await fetch(
2351
+ `https://registry.smithery.ai/servers/${encodeURIComponent(name)}`,
2352
+ {
2353
+ headers: { Accept: "application/json" },
2354
+ signal: AbortSignal.timeout(8e3)
2355
+ }
2356
+ );
2357
+ if (!res.ok) return null;
2358
+ const data = await res.json();
2359
+ return typeof data.version === "string" ? data.version : null;
2360
+ } catch {
2361
+ return null;
2362
+ }
2363
+ }
2364
+ async function fetchGithubLatest(resolved) {
2365
+ const match = resolved.match(/github\.com\/([^/]+)\/([^/]+)/);
2366
+ if (!match) return null;
2367
+ const [, owner, repo] = match;
2368
+ try {
2369
+ const res = await fetch(
2370
+ `https://api.github.com/repos/${owner}/${repo}/releases/latest`,
2371
+ {
2372
+ headers: { Accept: "application/json" },
2373
+ signal: AbortSignal.timeout(8e3)
2374
+ }
2375
+ );
2376
+ if (!res.ok) return null;
2377
+ const data = await res.json();
2378
+ return typeof data.tag_name === "string" ? data.tag_name.replace(/^v/, "") : null;
2379
+ } catch {
2380
+ return null;
2381
+ }
2382
+ }
2383
+ async function checkVersion(name, lockEntry) {
2384
+ const current = lockEntry.version;
2385
+ let latest = null;
2386
+ if (lockEntry.source === "npm") {
2387
+ latest = await fetchNpmLatest(name);
2388
+ } else if (lockEntry.source === "smithery") {
2389
+ latest = await fetchSmitheryLatest(name);
2390
+ } else if (lockEntry.source === "github") {
2391
+ latest = await fetchGithubLatest(lockEntry.resolved);
2392
+ }
2393
+ if (!latest || latest === current) {
2394
+ return {
2395
+ server: name,
2396
+ source: lockEntry.source,
2397
+ currentVersion: current,
2398
+ latestVersion: latest ?? current,
2399
+ hasUpdate: false
2400
+ };
2401
+ }
2402
+ const hasUpdate = compareVersions(current, latest) === -1;
2403
+ return {
2404
+ server: name,
2405
+ source: lockEntry.source,
2406
+ currentVersion: current,
2407
+ latestVersion: latest,
2408
+ hasUpdate,
2409
+ updateType: hasUpdate ? detectUpdateType(current, latest) : void 0
2410
+ };
2411
+ }
2412
+ async function checkAllVersions(lockfile) {
2413
+ const entries = Object.entries(lockfile.servers);
2414
+ if (entries.length === 0) return [];
2415
+ const results = [];
2416
+ const executing = /* @__PURE__ */ new Set();
2417
+ for (const [name, entry] of entries) {
2418
+ const p9 = checkVersion(name, entry).then((r) => {
2419
+ results.push(r);
2420
+ executing.delete(p9);
2421
+ });
2422
+ executing.add(p9);
2423
+ if (executing.size >= 5) {
2424
+ await Promise.race(executing);
2425
+ }
2426
+ }
2427
+ await Promise.all(executing);
2428
+ return results;
2429
+ }
2430
+
2431
+ // src/core/update-notifier.ts
2432
+ init_cjs_shims();
2433
+ var import_node_fs5 = __toESM(require("fs"), 1);
2434
+ var import_node_path7 = __toESM(require("path"), 1);
2435
+ var import_node_os5 = __toESM(require("os"), 1);
2436
+ var import_picocolors8 = __toESM(require("picocolors"), 1);
2437
+ var CACHE_FILE = import_node_path7.default.join(import_node_os5.default.homedir(), ".mcpman", ".update-check");
2438
+ var TTL_MS = 24 * 60 * 60 * 1e3;
2439
+ function writeUpdateCache(data) {
2440
+ try {
2441
+ const dir = import_node_path7.default.dirname(CACHE_FILE);
2442
+ if (!import_node_fs5.default.existsSync(dir)) import_node_fs5.default.mkdirSync(dir, { recursive: true });
2443
+ const tmp = `${CACHE_FILE}.tmp`;
2444
+ import_node_fs5.default.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
2445
+ import_node_fs5.default.renameSync(tmp, CACHE_FILE);
2446
+ } catch {
2447
+ }
2448
+ }
2449
+
2450
+ // src/commands/update.ts
2451
+ async function loadClients2() {
2452
+ try {
2453
+ const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
2454
+ return mod.getInstalledClients();
2455
+ } catch {
2456
+ return [];
2457
+ }
2458
+ }
2459
+ function printTable(updates) {
2460
+ const NAME_W = 28;
2461
+ const VER_W = 10;
2462
+ const header = [
2463
+ "NAME".padEnd(NAME_W),
2464
+ "CURRENT".padEnd(VER_W),
2465
+ "LATEST".padEnd(VER_W),
2466
+ "STATUS"
2467
+ ].join(" ");
2468
+ console.log(import_picocolors9.default.bold(`
2469
+ ${header}`));
2470
+ console.log(import_picocolors9.default.dim(` ${"\u2500".repeat(NAME_W + VER_W * 2 + 20)}`));
2471
+ for (const u of updates) {
2472
+ const nameCol = u.server.slice(0, NAME_W).padEnd(NAME_W);
2473
+ const curCol = u.currentVersion.padEnd(VER_W);
2474
+ const latCol = u.latestVersion.padEnd(VER_W);
2475
+ const statusCol = u.hasUpdate ? import_picocolors9.default.yellow(`Update available${u.updateType ? ` [${u.updateType}]` : ""}`) : import_picocolors9.default.green("Up to date");
2476
+ console.log(` ${nameCol} ${curCol} ${latCol} ${statusCol}`);
2477
+ }
2478
+ console.log();
2479
+ }
2480
+ var update_default = (0, import_citty9.defineCommand)({
2481
+ meta: {
2482
+ name: "update",
2483
+ description: "Check for and apply updates to installed MCP servers"
2484
+ },
2485
+ args: {
2486
+ server: {
2487
+ type: "positional",
2488
+ description: "Server name to update (omit to update all)",
2489
+ required: false
2490
+ },
2491
+ check: {
2492
+ type: "boolean",
2493
+ description: "Check only \u2014 do not apply updates",
2494
+ default: false
2495
+ },
2496
+ yes: {
2497
+ type: "boolean",
2498
+ description: "Skip confirmation prompt",
2499
+ default: false
2500
+ },
2501
+ json: {
2502
+ type: "boolean",
2503
+ description: "Output results as JSON",
2504
+ default: false
2505
+ }
2506
+ },
2507
+ async run({ args }) {
2508
+ const lockfile = readLockfile();
2509
+ const servers = lockfile.servers;
2510
+ const targetEntries = args.server ? Object.entries(servers).filter(([name]) => name === args.server) : Object.entries(servers);
2511
+ if (targetEntries.length === 0) {
2512
+ if (args.server) {
2513
+ console.error(`Server '${args.server}' not found in lockfile.`);
2514
+ } else {
2515
+ console.log("No servers installed. Run mcpman install <server> first.");
2516
+ }
1376
2517
  process.exit(1);
1377
2518
  }
1378
- p4.outro(import_picocolors4.default.green(`Removed "${serverName}" successfully.`));
2519
+ const spinner5 = p8.spinner();
2520
+ spinner5.start("Checking versions...");
2521
+ let updates;
2522
+ try {
2523
+ const partialLock = {
2524
+ lockfileVersion: 1,
2525
+ servers: Object.fromEntries(targetEntries)
2526
+ };
2527
+ updates = await checkAllVersions(partialLock);
2528
+ } catch (err) {
2529
+ spinner5.stop("Version check failed");
2530
+ console.error(err instanceof Error ? err.message : String(err));
2531
+ process.exit(1);
2532
+ }
2533
+ spinner5.stop(`Checked ${updates.length} server(s)`);
2534
+ if (args.json) {
2535
+ console.log(JSON.stringify(updates, null, 2));
2536
+ return;
2537
+ }
2538
+ printTable(updates);
2539
+ const outdated = updates.filter((u) => u.hasUpdate);
2540
+ if (outdated.length === 0) {
2541
+ console.log(import_picocolors9.default.green(" All servers are up to date."));
2542
+ return;
2543
+ }
2544
+ if (args.check) {
2545
+ console.log(import_picocolors9.default.yellow(` ${outdated.length} update(s) available. Run mcpman update to apply.`));
2546
+ return;
2547
+ }
2548
+ if (!args.yes) {
2549
+ const confirmed = await p8.confirm({
2550
+ message: `Apply ${outdated.length} update(s)?`,
2551
+ initialValue: true
2552
+ });
2553
+ if (p8.isCancel(confirmed) || !confirmed) {
2554
+ p8.outro("Cancelled.");
2555
+ return;
2556
+ }
2557
+ }
2558
+ const clients = await loadClients2();
2559
+ let successCount = 0;
2560
+ for (const update of outdated) {
2561
+ const lockEntry = servers[update.server];
2562
+ const input = lockEntry.source === "smithery" ? `smithery:${update.server}` : lockEntry.source === "github" ? lockEntry.resolved : update.server;
2563
+ const s = p8.spinner();
2564
+ s.start(`Updating ${update.server}...`);
2565
+ try {
2566
+ const metadata = await resolveServer(input);
2567
+ const integrity = computeIntegrity(metadata.resolved);
2568
+ addEntry(update.server, {
2569
+ ...lockEntry,
2570
+ version: metadata.version,
2571
+ resolved: metadata.resolved,
2572
+ integrity,
2573
+ command: metadata.command,
2574
+ args: metadata.args,
2575
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
2576
+ });
2577
+ const entryClients = clients.filter(
2578
+ (c) => lockEntry.clients.includes(c.type)
2579
+ );
2580
+ for (const client of entryClients) {
2581
+ try {
2582
+ await client.addServer(update.server, {
2583
+ command: metadata.command,
2584
+ args: metadata.args
2585
+ });
2586
+ } catch {
2587
+ }
2588
+ }
2589
+ s.stop(`${import_picocolors9.default.green("\u2713")} ${update.server}: ${update.currentVersion} \u2192 ${metadata.version}`);
2590
+ successCount++;
2591
+ } catch (err) {
2592
+ s.stop(`${import_picocolors9.default.red("\u2717")} ${update.server}: ${err instanceof Error ? err.message : String(err)}`);
2593
+ }
2594
+ }
2595
+ const freshLockfile = readLockfile(resolveLockfilePath());
2596
+ const freshUpdates = await checkAllVersions(freshLockfile);
2597
+ writeUpdateCache({ lastCheck: (/* @__PURE__ */ new Date()).toISOString(), updates: freshUpdates });
2598
+ p8.outro(`${successCount} of ${outdated.length} server(s) updated.`);
1379
2599
  }
1380
2600
  });
1381
2601
 
1382
2602
  // src/utils/constants.ts
1383
2603
  init_cjs_shims();
1384
2604
  var APP_NAME = "mcpman";
1385
- var APP_VERSION = "0.1.0";
2605
+ var APP_VERSION = "0.2.0";
1386
2606
  var APP_DESCRIPTION = "The package manager for MCP servers";
1387
2607
 
1388
2608
  // src/index.ts
@@ -1390,7 +2610,7 @@ process.on("SIGINT", () => {
1390
2610
  console.log("\nAborted.");
1391
2611
  process.exit(130);
1392
2612
  });
1393
- var main = (0, import_citty6.defineCommand)({
2613
+ var main = (0, import_citty10.defineCommand)({
1394
2614
  meta: {
1395
2615
  name: APP_NAME,
1396
2616
  version: APP_VERSION,
@@ -1401,7 +2621,11 @@ var main = (0, import_citty6.defineCommand)({
1401
2621
  list: list_default,
1402
2622
  remove: remove_default,
1403
2623
  doctor: doctor_default,
1404
- init: init_default
2624
+ init: init_default,
2625
+ secrets: secrets_default,
2626
+ sync: sync_default,
2627
+ audit: audit_default,
2628
+ update: update_default
1405
2629
  }
1406
2630
  });
1407
- (0, import_citty6.runMain)(main);
2631
+ (0, import_citty10.runMain)(main);