mcpman 0.7.0 → 0.9.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/README.md +168 -0
- package/dist/chunk-6GGMDJQE.js +181 -0
- package/dist/{chunk-NS6HV723.js → chunk-CC7ICP7U.js} +6 -62
- package/dist/chunk-DSCBWQ3W.js +96 -0
- package/dist/{client-detector-CY7WPF3K.js → client-detector-O2HN4MUB.js} +2 -1
- package/dist/index.cjs +2854 -767
- package/dist/index.js +2606 -665
- package/dist/{lockfile-RBA7HB24.js → lockfile-ITEBE7HU.js} +2 -1
- package/package.json +1 -1
- package/dist/chunk-YZNTMR6O.js +0 -88
package/dist/index.js
CHANGED
|
@@ -4,19 +4,32 @@ import {
|
|
|
4
4
|
addEntry,
|
|
5
5
|
createEmptyLockfile,
|
|
6
6
|
findLockfile,
|
|
7
|
+
listSnapshots,
|
|
7
8
|
readLockfile,
|
|
9
|
+
readSnapshot,
|
|
8
10
|
resolveLockfilePath,
|
|
11
|
+
restoreSnapshot,
|
|
9
12
|
writeLockfile
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6GGMDJQE.js";
|
|
11
14
|
import {
|
|
12
15
|
computeTrustScore
|
|
13
16
|
} from "./chunk-RGKHLY5G.js";
|
|
14
17
|
import {
|
|
18
|
+
getInstalledClients
|
|
19
|
+
} from "./chunk-CC7ICP7U.js";
|
|
20
|
+
import {
|
|
21
|
+
getAliasesFile,
|
|
15
22
|
getConfigPath,
|
|
16
|
-
|
|
23
|
+
getEnvDir,
|
|
24
|
+
getGroupsFile,
|
|
25
|
+
getHistoryFile,
|
|
26
|
+
getNotifyFile,
|
|
27
|
+
getPinsFile,
|
|
17
28
|
getPluginDir,
|
|
18
|
-
getProfilesDir
|
|
19
|
-
|
|
29
|
+
getProfilesDir,
|
|
30
|
+
getTemplatesDir,
|
|
31
|
+
resolveConfigPath
|
|
32
|
+
} from "./chunk-DSCBWQ3W.js";
|
|
20
33
|
import {
|
|
21
34
|
getMasterPassword,
|
|
22
35
|
getSecretsForServer,
|
|
@@ -29,34 +42,136 @@ import {
|
|
|
29
42
|
} from "./chunk-6X6Q6UZC.js";
|
|
30
43
|
|
|
31
44
|
// src/index.ts
|
|
32
|
-
import { defineCommand as
|
|
45
|
+
import { defineCommand as defineCommand39, runMain } from "citty";
|
|
46
|
+
|
|
47
|
+
// src/commands/alias.ts
|
|
48
|
+
import { defineCommand } from "citty";
|
|
49
|
+
import pc from "picocolors";
|
|
50
|
+
|
|
51
|
+
// src/core/alias-manager.ts
|
|
52
|
+
import fs from "fs";
|
|
53
|
+
function readAliases(file) {
|
|
54
|
+
const target = file ?? getAliasesFile();
|
|
55
|
+
if (!fs.existsSync(target)) return {};
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(fs.readFileSync(target, "utf-8"));
|
|
58
|
+
} catch {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function writeAliases(store, file) {
|
|
63
|
+
const target = file ?? getAliasesFile();
|
|
64
|
+
const dir = target.substring(0, target.lastIndexOf("/"));
|
|
65
|
+
if (dir && !fs.existsSync(dir)) {
|
|
66
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
fs.writeFileSync(target, JSON.stringify(store, null, 2), "utf-8");
|
|
69
|
+
}
|
|
70
|
+
function addAlias(name, command, file) {
|
|
71
|
+
const store = readAliases(file);
|
|
72
|
+
store[name] = command;
|
|
73
|
+
writeAliases(store, file);
|
|
74
|
+
}
|
|
75
|
+
function removeAlias(name, file) {
|
|
76
|
+
const store = readAliases(file);
|
|
77
|
+
if (!(name in store)) return;
|
|
78
|
+
delete store[name];
|
|
79
|
+
writeAliases(store, file);
|
|
80
|
+
}
|
|
81
|
+
function listAliases(file) {
|
|
82
|
+
const store = readAliases(file);
|
|
83
|
+
return Object.entries(store).sort(([a], [b]) => a.localeCompare(b)).map(([name, command]) => ({ name, command }));
|
|
84
|
+
}
|
|
85
|
+
function aliasExists(name, file) {
|
|
86
|
+
return name in readAliases(file);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/commands/alias.ts
|
|
90
|
+
var addCmd = defineCommand({
|
|
91
|
+
meta: { name: "add", description: "Add a command alias" },
|
|
92
|
+
args: {
|
|
93
|
+
name: { type: "positional", description: "Alias name", required: true },
|
|
94
|
+
command: { type: "positional", description: "Full command string to alias", required: true }
|
|
95
|
+
},
|
|
96
|
+
run({ args }) {
|
|
97
|
+
addAlias(args.name, args.command);
|
|
98
|
+
console.log(
|
|
99
|
+
`${pc.green("\u2713")} Alias ${pc.cyan(pc.bold(args.name))} \u2192 ${pc.dim(args.command)} saved`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
var removeCmd = defineCommand({
|
|
104
|
+
meta: { name: "remove", description: "Remove an alias" },
|
|
105
|
+
args: {
|
|
106
|
+
name: { type: "positional", description: "Alias name to remove", required: true }
|
|
107
|
+
},
|
|
108
|
+
run({ args }) {
|
|
109
|
+
if (!aliasExists(args.name)) {
|
|
110
|
+
console.error(`${pc.red("\u2717")} Alias "${args.name}" does not exist.`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
removeAlias(args.name);
|
|
114
|
+
console.log(`${pc.green("\u2713")} Alias ${pc.cyan(args.name)} removed`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
var listCmd = defineCommand({
|
|
118
|
+
meta: { name: "list", description: "List all aliases" },
|
|
119
|
+
args: {},
|
|
120
|
+
run() {
|
|
121
|
+
const aliases = listAliases();
|
|
122
|
+
if (aliases.length === 0) {
|
|
123
|
+
console.log(pc.dim("\n No aliases defined. Use `mcpman alias add <name> <command>`.\n"));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const nameW = Math.max(4, ...aliases.map((a) => a.name.length));
|
|
127
|
+
console.log(pc.bold("\n mcpman aliases\n"));
|
|
128
|
+
console.log(pc.dim(` ${"\u2500".repeat(nameW + 30)}`));
|
|
129
|
+
for (const { name, command } of aliases) {
|
|
130
|
+
const padded = name.padEnd(nameW);
|
|
131
|
+
console.log(` ${pc.cyan(pc.bold(padded))} ${pc.dim("\u2192")} ${command}`);
|
|
132
|
+
}
|
|
133
|
+
console.log(pc.dim(` ${"\u2500".repeat(nameW + 30)}
|
|
134
|
+
`));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
var alias_default = defineCommand({
|
|
138
|
+
meta: {
|
|
139
|
+
name: "alias",
|
|
140
|
+
description: "Manage command aliases"
|
|
141
|
+
},
|
|
142
|
+
subCommands: {
|
|
143
|
+
add: addCmd,
|
|
144
|
+
remove: removeCmd,
|
|
145
|
+
list: listCmd
|
|
146
|
+
}
|
|
147
|
+
});
|
|
33
148
|
|
|
34
149
|
// src/commands/audit.ts
|
|
35
150
|
import * as p from "@clack/prompts";
|
|
36
|
-
import { defineCommand } from "citty";
|
|
151
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
37
152
|
import { createSpinner } from "nanospinner";
|
|
38
|
-
import
|
|
153
|
+
import pc2 from "picocolors";
|
|
39
154
|
|
|
40
155
|
// src/core/security-scanner.ts
|
|
41
|
-
import
|
|
156
|
+
import fs2 from "fs";
|
|
42
157
|
import os from "os";
|
|
43
158
|
import path from "path";
|
|
44
159
|
var CACHE_PATH = path.join(os.homedir(), ".mcpman", ".audit-cache.json");
|
|
45
160
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
46
161
|
function readCache() {
|
|
47
162
|
try {
|
|
48
|
-
if (!
|
|
49
|
-
return JSON.parse(
|
|
163
|
+
if (!fs2.existsSync(CACHE_PATH)) return {};
|
|
164
|
+
return JSON.parse(fs2.readFileSync(CACHE_PATH, "utf-8"));
|
|
50
165
|
} catch {
|
|
51
166
|
return {};
|
|
52
167
|
}
|
|
53
168
|
}
|
|
54
169
|
function writeCache(cache) {
|
|
55
170
|
const dir = path.dirname(CACHE_PATH);
|
|
56
|
-
if (!
|
|
171
|
+
if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
|
|
57
172
|
const tmp = `${CACHE_PATH}.tmp`;
|
|
58
|
-
|
|
59
|
-
|
|
173
|
+
fs2.writeFileSync(tmp, JSON.stringify(cache, null, 2), "utf-8");
|
|
174
|
+
fs2.renameSync(tmp, CACHE_PATH);
|
|
60
175
|
}
|
|
61
176
|
function getCachedReport(name, version) {
|
|
62
177
|
const cache = readCache();
|
|
@@ -167,11 +282,11 @@ async function scanAllServers(servers, concurrency = 3) {
|
|
|
167
282
|
const results = [];
|
|
168
283
|
const executing = /* @__PURE__ */ new Set();
|
|
169
284
|
for (const [name, entry] of entries) {
|
|
170
|
-
const
|
|
285
|
+
const p13 = scanServer(name, entry).then((r) => {
|
|
171
286
|
results.push(r);
|
|
172
|
-
executing.delete(
|
|
287
|
+
executing.delete(p13);
|
|
173
288
|
});
|
|
174
|
-
executing.add(
|
|
289
|
+
executing.add(p13);
|
|
175
290
|
if (executing.size >= concurrency) await Promise.race(executing);
|
|
176
291
|
}
|
|
177
292
|
await Promise.all(executing);
|
|
@@ -286,12 +401,12 @@ async function resolveFromGitHub(githubUrl) {
|
|
|
286
401
|
|
|
287
402
|
// src/core/plugin-loader.ts
|
|
288
403
|
import { execSync } from "child_process";
|
|
289
|
-
import
|
|
404
|
+
import fs4 from "fs";
|
|
290
405
|
import { createRequire } from "module";
|
|
291
406
|
import path3 from "path";
|
|
292
407
|
|
|
293
408
|
// src/core/config-service.ts
|
|
294
|
-
import
|
|
409
|
+
import fs3 from "fs";
|
|
295
410
|
import path2 from "path";
|
|
296
411
|
var VALID_KEYS = /* @__PURE__ */ new Set([
|
|
297
412
|
"defaultClient",
|
|
@@ -302,7 +417,7 @@ var VALID_KEYS = /* @__PURE__ */ new Set([
|
|
|
302
417
|
]);
|
|
303
418
|
function readConfig(configPath = getConfigPath()) {
|
|
304
419
|
try {
|
|
305
|
-
const raw =
|
|
420
|
+
const raw = fs3.readFileSync(configPath, "utf-8");
|
|
306
421
|
const parsed = JSON.parse(raw);
|
|
307
422
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
308
423
|
return {};
|
|
@@ -314,10 +429,10 @@ function readConfig(configPath = getConfigPath()) {
|
|
|
314
429
|
}
|
|
315
430
|
function writeConfig(data, configPath = getConfigPath()) {
|
|
316
431
|
const dir = path2.dirname(configPath);
|
|
317
|
-
|
|
432
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
318
433
|
const tmp = `${configPath}.tmp`;
|
|
319
|
-
|
|
320
|
-
|
|
434
|
+
fs3.writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
435
|
+
fs3.renameSync(tmp, configPath);
|
|
321
436
|
}
|
|
322
437
|
function getConfigValue(key, configPath = getConfigPath()) {
|
|
323
438
|
const data = readConfig(configPath);
|
|
@@ -336,8 +451,8 @@ function setConfigValue(key, value, configPath = getConfigPath()) {
|
|
|
336
451
|
// src/core/plugin-loader.ts
|
|
337
452
|
function isValidPlugin(obj) {
|
|
338
453
|
if (typeof obj !== "object" || obj === null) return false;
|
|
339
|
-
const
|
|
340
|
-
return typeof
|
|
454
|
+
const p13 = obj;
|
|
455
|
+
return typeof p13.name === "string" && typeof p13.prefix === "string" && typeof p13.resolve === "function";
|
|
341
456
|
}
|
|
342
457
|
function loadPlugin(pkg, pluginDir = getPluginDir()) {
|
|
343
458
|
try {
|
|
@@ -362,10 +477,10 @@ function loadAllPlugins(pluginDir = getPluginDir()) {
|
|
|
362
477
|
return plugins;
|
|
363
478
|
}
|
|
364
479
|
function installPluginPackage(name, pluginDir = getPluginDir()) {
|
|
365
|
-
|
|
480
|
+
fs4.mkdirSync(pluginDir, { recursive: true });
|
|
366
481
|
const pkgJsonPath = path3.join(pluginDir, "package.json");
|
|
367
|
-
if (!
|
|
368
|
-
|
|
482
|
+
if (!fs4.existsSync(pkgJsonPath)) {
|
|
483
|
+
fs4.writeFileSync(
|
|
369
484
|
pkgJsonPath,
|
|
370
485
|
JSON.stringify({ name: "mcpman-plugins", private: true }, null, 2)
|
|
371
486
|
);
|
|
@@ -391,7 +506,7 @@ function removePluginPackage(name, pluginDir = getPluginDir()) {
|
|
|
391
506
|
}
|
|
392
507
|
const config = readConfig();
|
|
393
508
|
const plugins = config.plugins ?? [];
|
|
394
|
-
config.plugins = plugins.filter((
|
|
509
|
+
config.plugins = plugins.filter((p13) => p13 !== name);
|
|
395
510
|
writeConfig(config);
|
|
396
511
|
}
|
|
397
512
|
function listPluginPackages() {
|
|
@@ -440,7 +555,7 @@ async function resolveServer(input) {
|
|
|
440
555
|
if (source.type.startsWith("plugin:")) {
|
|
441
556
|
const pluginName = source.type.slice(7);
|
|
442
557
|
const plugins = loadAllPlugins();
|
|
443
|
-
const plugin = plugins.find((
|
|
558
|
+
const plugin = plugins.find((p13) => p13.name === pluginName);
|
|
444
559
|
if (plugin) {
|
|
445
560
|
const resolved = await plugin.resolve(source.input);
|
|
446
561
|
return resolved;
|
|
@@ -494,6 +609,47 @@ async function applyServerUpdate(serverName, lockEntry, clients) {
|
|
|
494
609
|
}
|
|
495
610
|
}
|
|
496
611
|
|
|
612
|
+
// src/core/pin-service.ts
|
|
613
|
+
import fs5 from "fs";
|
|
614
|
+
function readPins(file) {
|
|
615
|
+
const target = file ?? getPinsFile();
|
|
616
|
+
if (!fs5.existsSync(target)) return {};
|
|
617
|
+
try {
|
|
618
|
+
return JSON.parse(fs5.readFileSync(target, "utf-8"));
|
|
619
|
+
} catch {
|
|
620
|
+
return {};
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function writePins(store, file) {
|
|
624
|
+
const target = file ?? getPinsFile();
|
|
625
|
+
const dir = target.slice(0, target.lastIndexOf("/"));
|
|
626
|
+
if (dir && !fs5.existsSync(dir)) {
|
|
627
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
628
|
+
}
|
|
629
|
+
fs5.writeFileSync(target, JSON.stringify(store, null, 2), "utf-8");
|
|
630
|
+
}
|
|
631
|
+
function pinServer(server, version, file) {
|
|
632
|
+
const store = readPins(file);
|
|
633
|
+
store[server] = version;
|
|
634
|
+
writePins(store, file);
|
|
635
|
+
}
|
|
636
|
+
function unpinServer(server, file) {
|
|
637
|
+
const store = readPins(file);
|
|
638
|
+
if (!(server in store)) return;
|
|
639
|
+
delete store[server];
|
|
640
|
+
writePins(store, file);
|
|
641
|
+
}
|
|
642
|
+
function getPinnedVersion(server, file) {
|
|
643
|
+
return readPins(file)[server] ?? null;
|
|
644
|
+
}
|
|
645
|
+
function isPinned(server, file) {
|
|
646
|
+
return server in readPins(file);
|
|
647
|
+
}
|
|
648
|
+
function listPins(file) {
|
|
649
|
+
const store = readPins(file);
|
|
650
|
+
return Object.entries(store).sort(([a], [b]) => a.localeCompare(b)).map(([server, version]) => ({ server, version }));
|
|
651
|
+
}
|
|
652
|
+
|
|
497
653
|
// src/core/version-checker.ts
|
|
498
654
|
function compareVersions(a, b) {
|
|
499
655
|
const aParts = a.replace(/^v/, "").split(".").map(Number);
|
|
@@ -595,11 +751,12 @@ async function checkAllVersions(lockfile) {
|
|
|
595
751
|
const results = [];
|
|
596
752
|
const executing = /* @__PURE__ */ new Set();
|
|
597
753
|
for (const [name, entry] of entries) {
|
|
598
|
-
|
|
754
|
+
if (isPinned(name)) continue;
|
|
755
|
+
const p13 = checkVersion(name, entry).then((r) => {
|
|
599
756
|
results.push(r);
|
|
600
|
-
executing.delete(
|
|
757
|
+
executing.delete(p13);
|
|
601
758
|
});
|
|
602
|
-
executing.add(
|
|
759
|
+
executing.add(p13);
|
|
603
760
|
if (executing.size >= 5) {
|
|
604
761
|
await Promise.race(executing);
|
|
605
762
|
}
|
|
@@ -611,11 +768,11 @@ async function checkAllVersions(lockfile) {
|
|
|
611
768
|
// src/commands/audit.ts
|
|
612
769
|
function colorRisk(level, score) {
|
|
613
770
|
const label = score !== null ? `${score}/100 (${level})` : level;
|
|
614
|
-
if (level === "LOW") return
|
|
615
|
-
if (level === "MEDIUM") return
|
|
616
|
-
if (level === "HIGH") return
|
|
617
|
-
if (level === "CRITICAL") return
|
|
618
|
-
return
|
|
771
|
+
if (level === "LOW") return pc2.green(label);
|
|
772
|
+
if (level === "MEDIUM") return pc2.yellow(label);
|
|
773
|
+
if (level === "HIGH") return pc2.red(label);
|
|
774
|
+
if (level === "CRITICAL") return pc2.bold(pc2.red(label));
|
|
775
|
+
return pc2.dim(label);
|
|
619
776
|
}
|
|
620
777
|
function daysAgo(isoDate) {
|
|
621
778
|
const days = Math.floor((Date.now() - new Date(isoDate).getTime()) / 864e5);
|
|
@@ -626,20 +783,20 @@ function daysAgo(isoDate) {
|
|
|
626
783
|
function countVulns(vulns) {
|
|
627
784
|
const c = { critical: 0, high: 0, moderate: 0, low: 0 };
|
|
628
785
|
for (const v of vulns) c[v.severity]++;
|
|
629
|
-
if (vulns.length === 0) return
|
|
786
|
+
if (vulns.length === 0) return pc2.green("none");
|
|
630
787
|
const parts = [];
|
|
631
|
-
if (c.critical) parts.push(
|
|
632
|
-
if (c.high) parts.push(
|
|
633
|
-
if (c.moderate) parts.push(
|
|
634
|
-
if (c.low) parts.push(
|
|
788
|
+
if (c.critical) parts.push(pc2.bold(pc2.red(`${c.critical} critical`)));
|
|
789
|
+
if (c.high) parts.push(pc2.red(`${c.high} high`));
|
|
790
|
+
if (c.moderate) parts.push(pc2.yellow(`${c.moderate} moderate`));
|
|
791
|
+
if (c.low) parts.push(pc2.dim(`${c.low} low`));
|
|
635
792
|
return parts.join(", ");
|
|
636
793
|
}
|
|
637
794
|
function printReport(report) {
|
|
638
795
|
const riskColored = colorRisk(report.riskLevel, report.score);
|
|
639
|
-
const icon = report.riskLevel === "LOW" ?
|
|
640
|
-
console.log(` ${icon} ${
|
|
796
|
+
const icon = report.riskLevel === "LOW" ? pc2.green("\u25CF") : report.riskLevel === "MEDIUM" ? pc2.yellow("\u25CF") : report.riskLevel === "UNKNOWN" ? pc2.dim("\u25CB") : pc2.red("\u25CF");
|
|
797
|
+
console.log(` ${icon} ${pc2.bold(report.server)} Score: ${riskColored}`);
|
|
641
798
|
if (report.source !== "npm") {
|
|
642
|
-
console.log(` ${
|
|
799
|
+
console.log(` ${pc2.dim("Non-npm source \u2014 security data unavailable")}`);
|
|
643
800
|
console.log();
|
|
644
801
|
return;
|
|
645
802
|
}
|
|
@@ -647,20 +804,20 @@ function printReport(report) {
|
|
|
647
804
|
const { weeklyDownloads, packageAge, lastPublish, maintainerCount, deprecated } = report.metadata;
|
|
648
805
|
const dlStr = weeklyDownloads.toLocaleString();
|
|
649
806
|
console.log(
|
|
650
|
-
` ${
|
|
807
|
+
` ${pc2.dim("Downloads:")} ${dlStr}/week ${pc2.dim("|")} ${pc2.dim("Age:")} ${packageAge}d ${pc2.dim("|")} ${pc2.dim("Last publish:")} ${daysAgo(lastPublish)} ${pc2.dim("|")} ${pc2.dim("Maintainers:")} ${maintainerCount}${deprecated ? pc2.red(" [DEPRECATED]") : ""}`
|
|
651
808
|
);
|
|
652
809
|
}
|
|
653
|
-
console.log(` ${
|
|
810
|
+
console.log(` ${pc2.dim("Vulnerabilities:")} ${countVulns(report.vulnerabilities)}`);
|
|
654
811
|
if (report.vulnerabilities.length > 0) {
|
|
655
812
|
for (const v of report.vulnerabilities) {
|
|
656
|
-
const sevColor = v.severity === "critical" || v.severity === "high" ?
|
|
657
|
-
const url = v.url ?
|
|
813
|
+
const sevColor = v.severity === "critical" || v.severity === "high" ? pc2.red : pc2.yellow;
|
|
814
|
+
const url = v.url ? pc2.dim(` ${v.url}`) : "";
|
|
658
815
|
console.log(` ${sevColor("\u25B8")} [${v.severity}] ${v.title}${url}`);
|
|
659
816
|
}
|
|
660
817
|
}
|
|
661
818
|
console.log();
|
|
662
819
|
}
|
|
663
|
-
var audit_default =
|
|
820
|
+
var audit_default = defineCommand2({
|
|
664
821
|
meta: {
|
|
665
822
|
name: "audit",
|
|
666
823
|
description: "Scan installed MCP servers for security vulnerabilities and trust scores"
|
|
@@ -692,14 +849,14 @@ var audit_default = defineCommand({
|
|
|
692
849
|
const { servers } = lockfile;
|
|
693
850
|
if (Object.keys(servers).length === 0) {
|
|
694
851
|
console.log(
|
|
695
|
-
|
|
852
|
+
pc2.dim("\n No MCP servers installed. Run mcpman install <server> to get started.\n")
|
|
696
853
|
);
|
|
697
854
|
return;
|
|
698
855
|
}
|
|
699
856
|
const targets = {};
|
|
700
857
|
if (args.server) {
|
|
701
858
|
if (!servers[args.server]) {
|
|
702
|
-
console.error(
|
|
859
|
+
console.error(pc2.red(`
|
|
703
860
|
Server "${args.server}" not found in lockfile.
|
|
704
861
|
`));
|
|
705
862
|
process.exit(1);
|
|
@@ -714,7 +871,7 @@ var audit_default = defineCommand({
|
|
|
714
871
|
reports = args.server ? [await scanServer(args.server, targets[args.server])] : await scanAllServers(targets);
|
|
715
872
|
} catch (err) {
|
|
716
873
|
spinner5.error({ text: "Scan failed" });
|
|
717
|
-
console.error(
|
|
874
|
+
console.error(pc2.red(String(err)));
|
|
718
875
|
process.exit(1);
|
|
719
876
|
}
|
|
720
877
|
spinner5.success({ text: `Scanned ${reports.length} server(s)` });
|
|
@@ -722,23 +879,23 @@ var audit_default = defineCommand({
|
|
|
722
879
|
console.log(JSON.stringify(reports, null, 2));
|
|
723
880
|
return;
|
|
724
881
|
}
|
|
725
|
-
console.log(
|
|
726
|
-
console.log(
|
|
882
|
+
console.log(pc2.bold("\n mcpman audit\n"));
|
|
883
|
+
console.log(pc2.dim(` ${"\u2500".repeat(60)}`));
|
|
727
884
|
for (const report of reports) {
|
|
728
885
|
printReport(report);
|
|
729
886
|
}
|
|
730
|
-
console.log(
|
|
887
|
+
console.log(pc2.dim(` ${"\u2500".repeat(60)}`));
|
|
731
888
|
const withIssues = reports.filter((r) => r.riskLevel !== "LOW" && r.riskLevel !== "UNKNOWN");
|
|
732
889
|
const npmReports = reports.filter((r) => r.source === "npm");
|
|
733
890
|
const parts = [];
|
|
734
891
|
parts.push(`${reports.length} server(s) scanned`);
|
|
735
892
|
if (npmReports.length < reports.length) {
|
|
736
|
-
parts.push(
|
|
893
|
+
parts.push(pc2.dim(`${reports.length - npmReports.length} non-npm (unverified)`));
|
|
737
894
|
}
|
|
738
895
|
if (withIssues.length > 0) {
|
|
739
|
-
parts.push(
|
|
896
|
+
parts.push(pc2.yellow(`${withIssues.length} with issues`));
|
|
740
897
|
} else {
|
|
741
|
-
parts.push(
|
|
898
|
+
parts.push(pc2.green("all clear"));
|
|
742
899
|
}
|
|
743
900
|
console.log(`
|
|
744
901
|
Summary: ${parts.join(" | ")}
|
|
@@ -750,7 +907,7 @@ var audit_default = defineCommand({
|
|
|
750
907
|
});
|
|
751
908
|
async function loadClients() {
|
|
752
909
|
try {
|
|
753
|
-
const mod = await import("./client-detector-
|
|
910
|
+
const mod = await import("./client-detector-O2HN4MUB.js");
|
|
754
911
|
return mod.getInstalledClients();
|
|
755
912
|
} catch {
|
|
756
913
|
return [];
|
|
@@ -760,14 +917,14 @@ async function runAuditFix(reports, servers, skipConfirm) {
|
|
|
760
917
|
const npmWithVulns = reports.filter((r) => r.vulnerabilities.length > 0 && r.source === "npm");
|
|
761
918
|
const nonNpmWithVulns = reports.filter((r) => r.vulnerabilities.length > 0 && r.source !== "npm");
|
|
762
919
|
if (nonNpmWithVulns.length > 0) {
|
|
763
|
-
console.log(
|
|
920
|
+
console.log(pc2.yellow(" Non-npm servers require manual update:"));
|
|
764
921
|
for (const r of nonNpmWithVulns) {
|
|
765
|
-
console.log(` ${
|
|
922
|
+
console.log(` ${pc2.dim("\u2192")} ${r.server} (${r.source})`);
|
|
766
923
|
}
|
|
767
924
|
console.log();
|
|
768
925
|
}
|
|
769
926
|
if (npmWithVulns.length === 0) {
|
|
770
|
-
console.log(
|
|
927
|
+
console.log(pc2.green(" No fixable vulnerabilities found.\n"));
|
|
771
928
|
return;
|
|
772
929
|
}
|
|
773
930
|
const versionSpinner = createSpinner("Checking for available updates...").start();
|
|
@@ -778,20 +935,20 @@ async function runAuditFix(reports, servers, skipConfirm) {
|
|
|
778
935
|
const updatable = versionChecks.filter((u) => u.hasUpdate);
|
|
779
936
|
if (updatable.length === 0) {
|
|
780
937
|
console.log(
|
|
781
|
-
|
|
938
|
+
pc2.yellow(
|
|
782
939
|
" Vulnerable servers have no newer versions available yet.\n Allow time for registry to publish fixes.\n"
|
|
783
940
|
)
|
|
784
941
|
);
|
|
785
942
|
return;
|
|
786
943
|
}
|
|
787
944
|
console.log(
|
|
788
|
-
|
|
945
|
+
pc2.bold(`
|
|
789
946
|
${updatable.length} server(s) can be updated to fix vulnerabilities:
|
|
790
947
|
`)
|
|
791
948
|
);
|
|
792
949
|
for (const u of updatable) {
|
|
793
950
|
console.log(
|
|
794
|
-
` ${
|
|
951
|
+
` ${pc2.cyan("\u2192")} ${u.server} ${pc2.dim(u.currentVersion)} \u2192 ${pc2.green(u.latestVersion)}`
|
|
795
952
|
);
|
|
796
953
|
}
|
|
797
954
|
console.log();
|
|
@@ -813,11 +970,11 @@ async function runAuditFix(reports, servers, skipConfirm) {
|
|
|
813
970
|
const result = await applyServerUpdate(u.server, servers[u.server], clients);
|
|
814
971
|
if (result.success) {
|
|
815
972
|
s.success({
|
|
816
|
-
text: `${
|
|
973
|
+
text: `${pc2.green("\u2713")} ${u.server}: ${result.fromVersion} \u2192 ${result.toVersion}`
|
|
817
974
|
});
|
|
818
975
|
successCount++;
|
|
819
976
|
} else {
|
|
820
|
-
s.error({ text: `${
|
|
977
|
+
s.error({ text: `${pc2.red("\u2717")} ${u.server}: ${result.error}` });
|
|
821
978
|
}
|
|
822
979
|
results.push({
|
|
823
980
|
server: u.server,
|
|
@@ -836,14 +993,14 @@ async function runAuditFix(reports, servers, skipConfirm) {
|
|
|
836
993
|
updatedNames.map((name) => scanServer(name, freshLockfile.servers[name]))
|
|
837
994
|
);
|
|
838
995
|
rescanSpinner.success({ text: "Re-scan complete" });
|
|
839
|
-
console.log(
|
|
996
|
+
console.log(pc2.bold("\n Before / After:\n"));
|
|
840
997
|
for (const after of afterReports) {
|
|
841
998
|
const before = reports.find((r) => r.server === after.server);
|
|
842
999
|
const beforeVulns = before?.vulnerabilities.length ?? 0;
|
|
843
1000
|
const afterVulns = after.vulnerabilities.length;
|
|
844
|
-
const improved = afterVulns < beforeVulns ?
|
|
1001
|
+
const improved = afterVulns < beforeVulns ? pc2.green("improved") : pc2.yellow("unchanged");
|
|
845
1002
|
console.log(
|
|
846
|
-
` ${
|
|
1003
|
+
` ${pc2.bold(after.server)} vulns: ${beforeVulns} \u2192 ${afterVulns} [${improved}]`
|
|
847
1004
|
);
|
|
848
1005
|
}
|
|
849
1006
|
console.log();
|
|
@@ -853,12 +1010,204 @@ async function runAuditFix(reports, servers, skipConfirm) {
|
|
|
853
1010
|
`);
|
|
854
1011
|
}
|
|
855
1012
|
|
|
1013
|
+
// src/commands/bench.ts
|
|
1014
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
1015
|
+
import pc3 from "picocolors";
|
|
1016
|
+
|
|
1017
|
+
// src/core/bench-service.ts
|
|
1018
|
+
import { spawn } from "child_process";
|
|
1019
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
1020
|
+
function measureOneRun(command, args, env, timeoutMs) {
|
|
1021
|
+
return new Promise((resolve, reject) => {
|
|
1022
|
+
const start = Date.now();
|
|
1023
|
+
let settled = false;
|
|
1024
|
+
let stdout = "";
|
|
1025
|
+
const child = spawn(command, args, {
|
|
1026
|
+
env: { ...process.env, ...env },
|
|
1027
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1028
|
+
});
|
|
1029
|
+
const finish = (err) => {
|
|
1030
|
+
if (settled) return;
|
|
1031
|
+
settled = true;
|
|
1032
|
+
try {
|
|
1033
|
+
child.kill("SIGTERM");
|
|
1034
|
+
} catch {
|
|
1035
|
+
}
|
|
1036
|
+
if (err) reject(err);
|
|
1037
|
+
else resolve(Date.now() - start);
|
|
1038
|
+
};
|
|
1039
|
+
const timer = setTimeout(() => {
|
|
1040
|
+
finish(new Error("Timeout waiting for initialize response"));
|
|
1041
|
+
}, timeoutMs);
|
|
1042
|
+
child.on("error", (err) => {
|
|
1043
|
+
clearTimeout(timer);
|
|
1044
|
+
finish(err);
|
|
1045
|
+
});
|
|
1046
|
+
child.on("exit", (code) => {
|
|
1047
|
+
clearTimeout(timer);
|
|
1048
|
+
if (!settled) finish(new Error(`Process exited with code ${code}`));
|
|
1049
|
+
});
|
|
1050
|
+
child.stdout?.on("data", (chunk) => {
|
|
1051
|
+
stdout += chunk.toString();
|
|
1052
|
+
for (const line of stdout.split("\n")) {
|
|
1053
|
+
const trimmed = line.trim();
|
|
1054
|
+
if (!trimmed) continue;
|
|
1055
|
+
try {
|
|
1056
|
+
const msg = JSON.parse(trimmed);
|
|
1057
|
+
if (msg.jsonrpc === "2.0" && msg.id === 1) {
|
|
1058
|
+
clearTimeout(timer);
|
|
1059
|
+
finish();
|
|
1060
|
+
}
|
|
1061
|
+
} catch {
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
const initReq = JSON.stringify({
|
|
1066
|
+
jsonrpc: "2.0",
|
|
1067
|
+
id: 1,
|
|
1068
|
+
method: "initialize",
|
|
1069
|
+
params: {
|
|
1070
|
+
protocolVersion: "2024-11-05",
|
|
1071
|
+
capabilities: {},
|
|
1072
|
+
clientInfo: { name: "mcpman-bench", version: "0.8.0" }
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
child.stdin?.write(`${initReq}
|
|
1076
|
+
`);
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
function percentile(sorted, p13) {
|
|
1080
|
+
if (sorted.length === 0) return 0;
|
|
1081
|
+
const idx = Math.ceil(p13 / 100 * sorted.length) - 1;
|
|
1082
|
+
return sorted[Math.max(0, idx)];
|
|
1083
|
+
}
|
|
1084
|
+
async function benchServer(command, args, env, runs = 5, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
1085
|
+
const allTimes = [];
|
|
1086
|
+
for (let i = 0; i < runs; i++) {
|
|
1087
|
+
try {
|
|
1088
|
+
const ms = await measureOneRun(command, args, env, timeoutMs);
|
|
1089
|
+
allTimes.push(ms);
|
|
1090
|
+
} catch (err) {
|
|
1091
|
+
return {
|
|
1092
|
+
runs,
|
|
1093
|
+
min: 0,
|
|
1094
|
+
max: 0,
|
|
1095
|
+
avg: 0,
|
|
1096
|
+
p50: 0,
|
|
1097
|
+
p95: 0,
|
|
1098
|
+
allTimes,
|
|
1099
|
+
error: String(err instanceof Error ? err.message : err)
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const sorted = [...allTimes].sort((a, b) => a - b);
|
|
1104
|
+
const sum = allTimes.reduce((a, b) => a + b, 0);
|
|
1105
|
+
return {
|
|
1106
|
+
runs,
|
|
1107
|
+
min: sorted[0] ?? 0,
|
|
1108
|
+
max: sorted[sorted.length - 1] ?? 0,
|
|
1109
|
+
avg: Math.round(sum / allTimes.length),
|
|
1110
|
+
p50: percentile(sorted, 50),
|
|
1111
|
+
p95: percentile(sorted, 95),
|
|
1112
|
+
allTimes
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// src/commands/bench.ts
|
|
1117
|
+
var bench_default = defineCommand3({
|
|
1118
|
+
meta: {
|
|
1119
|
+
name: "bench",
|
|
1120
|
+
description: "Benchmark MCP server latency (JSON-RPC initialize)"
|
|
1121
|
+
},
|
|
1122
|
+
args: {
|
|
1123
|
+
server: {
|
|
1124
|
+
type: "positional",
|
|
1125
|
+
description: "Server name as stored in lockfile",
|
|
1126
|
+
required: true
|
|
1127
|
+
},
|
|
1128
|
+
runs: {
|
|
1129
|
+
type: "string",
|
|
1130
|
+
description: "Number of benchmark runs (default: 5)",
|
|
1131
|
+
default: "5"
|
|
1132
|
+
},
|
|
1133
|
+
timeout: {
|
|
1134
|
+
type: "string",
|
|
1135
|
+
description: "Per-run timeout in ms (default: 10000)",
|
|
1136
|
+
default: "10000"
|
|
1137
|
+
},
|
|
1138
|
+
json: {
|
|
1139
|
+
type: "boolean",
|
|
1140
|
+
description: "Output results as JSON",
|
|
1141
|
+
default: false
|
|
1142
|
+
}
|
|
1143
|
+
},
|
|
1144
|
+
async run({ args }) {
|
|
1145
|
+
const lockfile = readLockfile();
|
|
1146
|
+
const entry = lockfile.servers[args.server];
|
|
1147
|
+
if (!entry) {
|
|
1148
|
+
console.error(`${pc3.red("\u2717")} Server "${args.server}" not found in lockfile.`);
|
|
1149
|
+
console.error(pc3.dim("Run `mcpman list` to see installed servers."));
|
|
1150
|
+
process.exit(1);
|
|
1151
|
+
}
|
|
1152
|
+
const runs = Math.max(1, Number.parseInt(args.runs, 10) || 5);
|
|
1153
|
+
const timeoutMs = Math.max(1e3, Number.parseInt(args.timeout, 10) || 1e4);
|
|
1154
|
+
if (!args.json) {
|
|
1155
|
+
console.log(`
|
|
1156
|
+
${pc3.cyan("mcpman bench")} \u2014 ${pc3.bold(args.server)}`);
|
|
1157
|
+
console.log(pc3.dim(` Command: ${entry.command} ${(entry.args ?? []).join(" ")}`));
|
|
1158
|
+
console.log(pc3.dim(` Runs: ${runs} Timeout: ${timeoutMs}ms
|
|
1159
|
+
`));
|
|
1160
|
+
process.stdout.write(pc3.dim(" Running"));
|
|
1161
|
+
}
|
|
1162
|
+
const env = {};
|
|
1163
|
+
for (const ev of entry.envVars ?? []) {
|
|
1164
|
+
const idx = ev.indexOf("=");
|
|
1165
|
+
if (idx > 0) env[ev.slice(0, idx)] = ev.slice(idx + 1);
|
|
1166
|
+
}
|
|
1167
|
+
const result = await benchServer(entry.command, entry.args ?? [], env, runs, timeoutMs);
|
|
1168
|
+
if (!args.json) process.stdout.write("\n");
|
|
1169
|
+
if (result.error) {
|
|
1170
|
+
if (args.json) {
|
|
1171
|
+
console.log(JSON.stringify({ server: args.server, error: result.error }));
|
|
1172
|
+
} else {
|
|
1173
|
+
console.error(`
|
|
1174
|
+
${pc3.red("\u2717")} Benchmark failed: ${result.error}`);
|
|
1175
|
+
}
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
if (args.json) {
|
|
1179
|
+
console.log(JSON.stringify({ server: args.server, ...result }, null, 2));
|
|
1180
|
+
if (result.p95 > timeoutMs) process.exit(1);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const pad5 = (s, w) => s.padEnd(w);
|
|
1184
|
+
const ms = (n) => `${n}ms`;
|
|
1185
|
+
console.log(`
|
|
1186
|
+
${pc3.bold("Latency statistics")} for ${pc3.cyan(args.server)}`);
|
|
1187
|
+
console.log(pc3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1188
|
+
console.log(` ${pad5("min", 8)} ${pc3.green(ms(result.min))}`);
|
|
1189
|
+
console.log(` ${pad5("avg", 8)} ${ms(result.avg)}`);
|
|
1190
|
+
console.log(` ${pad5("p50", 8)} ${ms(result.p50)}`);
|
|
1191
|
+
console.log(
|
|
1192
|
+
` ${pad5("p95", 8)} ${result.p95 > timeoutMs ? pc3.red(ms(result.p95)) : pc3.yellow(ms(result.p95))}`
|
|
1193
|
+
);
|
|
1194
|
+
console.log(` ${pad5("max", 8)} ${ms(result.max)}`);
|
|
1195
|
+
console.log(` ${pad5("runs", 8)} ${result.runs}`);
|
|
1196
|
+
console.log("");
|
|
1197
|
+
if (result.p95 > timeoutMs) {
|
|
1198
|
+
console.error(pc3.red(` \u2717 p95 (${result.p95}ms) exceeds timeout (${timeoutMs}ms)`));
|
|
1199
|
+
process.exit(1);
|
|
1200
|
+
}
|
|
1201
|
+
console.log(pc3.green(" \u2713 Benchmark complete"));
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
|
|
856
1205
|
// src/commands/completions.ts
|
|
857
|
-
import
|
|
1206
|
+
import fs6 from "fs";
|
|
858
1207
|
import os2 from "os";
|
|
859
1208
|
import path4 from "path";
|
|
860
|
-
import { defineCommand as
|
|
861
|
-
import
|
|
1209
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
1210
|
+
import pc4 from "picocolors";
|
|
862
1211
|
|
|
863
1212
|
// src/core/completion-generator.ts
|
|
864
1213
|
function getCommandList() {
|
|
@@ -888,7 +1237,13 @@ function getCommandList() {
|
|
|
888
1237
|
"watch",
|
|
889
1238
|
"registry",
|
|
890
1239
|
"completions",
|
|
891
|
-
"why"
|
|
1240
|
+
"why",
|
|
1241
|
+
"env",
|
|
1242
|
+
"bench",
|
|
1243
|
+
"diff",
|
|
1244
|
+
"group",
|
|
1245
|
+
"pin",
|
|
1246
|
+
"rollback"
|
|
892
1247
|
];
|
|
893
1248
|
}
|
|
894
1249
|
var SERVER_ARG_COMMANDS = [
|
|
@@ -1013,7 +1368,7 @@ complete -c mcpman -l runtime -s r \\
|
|
|
1013
1368
|
}
|
|
1014
1369
|
|
|
1015
1370
|
// src/commands/completions.ts
|
|
1016
|
-
var completions_default =
|
|
1371
|
+
var completions_default = defineCommand4({
|
|
1017
1372
|
meta: {
|
|
1018
1373
|
name: "completions",
|
|
1019
1374
|
description: "Generate shell completion scripts (bash, zsh, fish)"
|
|
@@ -1063,24 +1418,24 @@ var completions_default = defineCommand2({
|
|
|
1063
1418
|
await installCompletion();
|
|
1064
1419
|
break;
|
|
1065
1420
|
default:
|
|
1066
|
-
console.error(
|
|
1421
|
+
console.error(pc4.red(` Error: Unknown shell '${shell}'. Use: bash, zsh, or fish.`));
|
|
1067
1422
|
process.exit(1);
|
|
1068
1423
|
}
|
|
1069
1424
|
}
|
|
1070
1425
|
});
|
|
1071
1426
|
function printUsage() {
|
|
1072
|
-
console.log(
|
|
1427
|
+
console.log(pc4.bold("\n mcpman completions \u2014 Shell completion setup\n"));
|
|
1073
1428
|
console.log(" Usage:");
|
|
1074
|
-
console.log(` ${
|
|
1075
|
-
console.log(` ${
|
|
1076
|
-
console.log(` ${
|
|
1077
|
-
console.log(` ${
|
|
1429
|
+
console.log(` ${pc4.cyan("mcpman completions bash")} Output bash completion script`);
|
|
1430
|
+
console.log(` ${pc4.cyan("mcpman completions zsh")} Output zsh completion script`);
|
|
1431
|
+
console.log(` ${pc4.cyan("mcpman completions fish")} Output fish completion script`);
|
|
1432
|
+
console.log(` ${pc4.cyan("mcpman completions install")} Auto-detect shell and install
|
|
1078
1433
|
`);
|
|
1079
1434
|
console.log(" Quick setup:");
|
|
1080
|
-
console.log(` ${
|
|
1081
|
-
console.log(` ${
|
|
1082
|
-
console.log(` ${
|
|
1083
|
-
console.log(` ${
|
|
1435
|
+
console.log(` ${pc4.dim("# bash")}`);
|
|
1436
|
+
console.log(` ${pc4.cyan("source <(mcpman completions bash)")}`);
|
|
1437
|
+
console.log(` ${pc4.dim("# zsh")}`);
|
|
1438
|
+
console.log(` ${pc4.cyan("source <(mcpman completions zsh)")}
|
|
1084
1439
|
`);
|
|
1085
1440
|
}
|
|
1086
1441
|
async function installCompletion() {
|
|
@@ -1090,8 +1445,8 @@ async function installCompletion() {
|
|
|
1090
1445
|
else if (shellBin.includes("fish")) detectedShell = "fish";
|
|
1091
1446
|
else if (shellBin.includes("bash")) detectedShell = "bash";
|
|
1092
1447
|
if (!detectedShell) {
|
|
1093
|
-
console.error(
|
|
1094
|
-
console.error(
|
|
1448
|
+
console.error(pc4.red(" Could not detect shell from $SHELL. Run manually:"));
|
|
1449
|
+
console.error(pc4.dim(" source <(mcpman completions bash|zsh|fish)"));
|
|
1095
1450
|
process.exit(1);
|
|
1096
1451
|
}
|
|
1097
1452
|
const home = os2.homedir();
|
|
@@ -1102,10 +1457,10 @@ async function installCompletion() {
|
|
|
1102
1457
|
script = generateZshCompletion();
|
|
1103
1458
|
} else if (detectedShell === "fish") {
|
|
1104
1459
|
const fishDir = path4.join(home, ".config", "fish", "completions");
|
|
1105
|
-
|
|
1460
|
+
fs6.mkdirSync(fishDir, { recursive: true });
|
|
1106
1461
|
rcFile = path4.join(fishDir, "mcpman.fish");
|
|
1107
|
-
|
|
1108
|
-
console.log(
|
|
1462
|
+
fs6.writeFileSync(rcFile, generateFishCompletion(), "utf-8");
|
|
1463
|
+
console.log(pc4.green(` Installed fish completions to ${rcFile}`));
|
|
1109
1464
|
return;
|
|
1110
1465
|
} else {
|
|
1111
1466
|
rcFile = path4.join(home, ".bashrc");
|
|
@@ -1114,25 +1469,25 @@ async function installCompletion() {
|
|
|
1114
1469
|
const marker = "# mcpman completions";
|
|
1115
1470
|
let existing = "";
|
|
1116
1471
|
try {
|
|
1117
|
-
existing =
|
|
1472
|
+
existing = fs6.readFileSync(rcFile, "utf-8");
|
|
1118
1473
|
} catch {
|
|
1119
1474
|
}
|
|
1120
1475
|
if (existing.includes(marker)) {
|
|
1121
|
-
console.log(
|
|
1476
|
+
console.log(pc4.yellow(` Completions already installed in ${rcFile}. Skipping.`));
|
|
1122
1477
|
return;
|
|
1123
1478
|
}
|
|
1124
|
-
|
|
1479
|
+
fs6.appendFileSync(rcFile, `
|
|
1125
1480
|
${marker}
|
|
1126
1481
|
source <(mcpman completions ${detectedShell})
|
|
1127
1482
|
`);
|
|
1128
|
-
console.log(
|
|
1129
|
-
console.log(
|
|
1483
|
+
console.log(pc4.green(` Installed ${detectedShell} completions in ${rcFile}`));
|
|
1484
|
+
console.log(pc4.dim(` Restart your shell or run: source ${rcFile}`));
|
|
1130
1485
|
}
|
|
1131
1486
|
|
|
1132
1487
|
// src/commands/config.ts
|
|
1133
1488
|
import * as p2 from "@clack/prompts";
|
|
1134
|
-
import { defineCommand as
|
|
1135
|
-
import
|
|
1489
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
1490
|
+
import pc5 from "picocolors";
|
|
1136
1491
|
function coerceValue(raw) {
|
|
1137
1492
|
if (raw === "true") return true;
|
|
1138
1493
|
if (raw === "false") return false;
|
|
@@ -1140,7 +1495,7 @@ function coerceValue(raw) {
|
|
|
1140
1495
|
if (!Number.isNaN(num) && raw.trim() !== "") return num;
|
|
1141
1496
|
return raw;
|
|
1142
1497
|
}
|
|
1143
|
-
var setCommand =
|
|
1498
|
+
var setCommand = defineCommand5({
|
|
1144
1499
|
meta: { name: "set", description: "Set a config value" },
|
|
1145
1500
|
args: {
|
|
1146
1501
|
key: {
|
|
@@ -1158,14 +1513,14 @@ var setCommand = defineCommand3({
|
|
|
1158
1513
|
try {
|
|
1159
1514
|
const coerced = coerceValue(args.value);
|
|
1160
1515
|
setConfigValue(args.key, coerced);
|
|
1161
|
-
console.log(`${
|
|
1516
|
+
console.log(`${pc5.green("\u2713")} Set ${pc5.bold(args.key)} = ${pc5.cyan(String(coerced))}`);
|
|
1162
1517
|
} catch (err) {
|
|
1163
|
-
console.error(`${
|
|
1518
|
+
console.error(`${pc5.red("\u2717")} ${String(err)}`);
|
|
1164
1519
|
process.exit(1);
|
|
1165
1520
|
}
|
|
1166
1521
|
}
|
|
1167
1522
|
});
|
|
1168
|
-
var getCommand =
|
|
1523
|
+
var getCommand = defineCommand5({
|
|
1169
1524
|
meta: { name: "get", description: "Get a config value" },
|
|
1170
1525
|
args: {
|
|
1171
1526
|
key: {
|
|
@@ -1177,32 +1532,32 @@ var getCommand = defineCommand3({
|
|
|
1177
1532
|
run({ args }) {
|
|
1178
1533
|
const val = getConfigValue(args.key);
|
|
1179
1534
|
if (val === void 0) {
|
|
1180
|
-
console.log(
|
|
1535
|
+
console.log(pc5.dim(`${args.key}: (not set)`));
|
|
1181
1536
|
} else {
|
|
1182
|
-
console.log(`${
|
|
1537
|
+
console.log(`${pc5.bold(args.key)}: ${pc5.cyan(String(val))}`);
|
|
1183
1538
|
}
|
|
1184
1539
|
}
|
|
1185
1540
|
});
|
|
1186
|
-
var listCommand =
|
|
1541
|
+
var listCommand = defineCommand5({
|
|
1187
1542
|
meta: { name: "list", description: "List all config values" },
|
|
1188
1543
|
run() {
|
|
1189
1544
|
const data = readConfig();
|
|
1190
1545
|
const entries = Object.entries(data);
|
|
1191
1546
|
if (entries.length === 0) {
|
|
1192
|
-
console.log(
|
|
1547
|
+
console.log(pc5.dim("No config values set. Use `mcpman config set <key> <value>`."));
|
|
1193
1548
|
return;
|
|
1194
1549
|
}
|
|
1195
1550
|
console.log("");
|
|
1196
|
-
console.log(
|
|
1551
|
+
console.log(pc5.bold("mcpman config:"));
|
|
1197
1552
|
console.log("");
|
|
1198
1553
|
for (const [key, val] of entries) {
|
|
1199
|
-
console.log(` ${
|
|
1554
|
+
console.log(` ${pc5.green("\u25CF")} ${pc5.bold(key)} ${pc5.cyan(String(val))}`);
|
|
1200
1555
|
}
|
|
1201
1556
|
console.log("");
|
|
1202
|
-
console.log(
|
|
1557
|
+
console.log(pc5.dim(` ${entries.length} key${entries.length !== 1 ? "s" : ""} configured`));
|
|
1203
1558
|
}
|
|
1204
1559
|
});
|
|
1205
|
-
var resetCommand =
|
|
1560
|
+
var resetCommand = defineCommand5({
|
|
1206
1561
|
meta: { name: "reset", description: "Reset config to defaults (removes config file)" },
|
|
1207
1562
|
async run() {
|
|
1208
1563
|
const confirmed = await p2.confirm({
|
|
@@ -1214,10 +1569,10 @@ var resetCommand = defineCommand3({
|
|
|
1214
1569
|
return;
|
|
1215
1570
|
}
|
|
1216
1571
|
writeConfig({});
|
|
1217
|
-
console.log(`${
|
|
1572
|
+
console.log(`${pc5.green("\u2713")} Config reset to defaults.`);
|
|
1218
1573
|
}
|
|
1219
1574
|
});
|
|
1220
|
-
var config_default =
|
|
1575
|
+
var config_default = defineCommand5({
|
|
1221
1576
|
meta: {
|
|
1222
1577
|
name: "config",
|
|
1223
1578
|
description: "Manage mcpman CLI configuration"
|
|
@@ -1232,11 +1587,11 @@ var config_default = defineCommand3({
|
|
|
1232
1587
|
|
|
1233
1588
|
// src/commands/create.ts
|
|
1234
1589
|
import path6 from "path";
|
|
1235
|
-
import { defineCommand as
|
|
1236
|
-
import
|
|
1590
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
1591
|
+
import pc6 from "picocolors";
|
|
1237
1592
|
|
|
1238
1593
|
// src/core/scaffold-service.ts
|
|
1239
|
-
import
|
|
1594
|
+
import fs7 from "fs";
|
|
1240
1595
|
import path5 from "path";
|
|
1241
1596
|
function sanitizeName(name) {
|
|
1242
1597
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -1389,8 +1744,8 @@ if __name__ == "__main__":
|
|
|
1389
1744
|
};
|
|
1390
1745
|
}
|
|
1391
1746
|
function writeScaffold(dir, files) {
|
|
1392
|
-
if (
|
|
1393
|
-
const existing =
|
|
1747
|
+
if (fs7.existsSync(dir)) {
|
|
1748
|
+
const existing = fs7.readdirSync(dir);
|
|
1394
1749
|
if (existing.length > 0) {
|
|
1395
1750
|
throw new Error(`Directory '${dir}' already exists and is not empty.`);
|
|
1396
1751
|
}
|
|
@@ -1398,13 +1753,13 @@ function writeScaffold(dir, files) {
|
|
|
1398
1753
|
for (const [relativePath, content] of Object.entries(files)) {
|
|
1399
1754
|
const fullPath = path5.join(dir, relativePath);
|
|
1400
1755
|
const parentDir = path5.dirname(fullPath);
|
|
1401
|
-
|
|
1402
|
-
|
|
1756
|
+
fs7.mkdirSync(parentDir, { recursive: true });
|
|
1757
|
+
fs7.writeFileSync(fullPath, content, "utf-8");
|
|
1403
1758
|
}
|
|
1404
1759
|
}
|
|
1405
1760
|
|
|
1406
1761
|
// src/commands/create.ts
|
|
1407
|
-
var create_default =
|
|
1762
|
+
var create_default = defineCommand6({
|
|
1408
1763
|
meta: {
|
|
1409
1764
|
name: "create",
|
|
1410
1765
|
description: "Scaffold a new MCP server project"
|
|
@@ -1442,7 +1797,7 @@ var create_default = defineCommand4({
|
|
|
1442
1797
|
const readline = await import("readline");
|
|
1443
1798
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1444
1799
|
projectName = await new Promise((resolve) => {
|
|
1445
|
-
rl.question(
|
|
1800
|
+
rl.question(pc6.cyan(" Project name: "), (answer) => {
|
|
1446
1801
|
rl.close();
|
|
1447
1802
|
resolve(answer.trim());
|
|
1448
1803
|
});
|
|
@@ -1452,7 +1807,7 @@ var create_default = defineCommand4({
|
|
|
1452
1807
|
const readline = await import("readline");
|
|
1453
1808
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1454
1809
|
projectDescription = await new Promise((resolve) => {
|
|
1455
|
-
rl.question(
|
|
1810
|
+
rl.question(pc6.cyan(" Description (optional): "), (answer) => {
|
|
1456
1811
|
rl.close();
|
|
1457
1812
|
resolve(answer.trim());
|
|
1458
1813
|
});
|
|
@@ -1462,7 +1817,7 @@ var create_default = defineCommand4({
|
|
|
1462
1817
|
const readline = await import("readline");
|
|
1463
1818
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1464
1819
|
const answer = await new Promise((resolve) => {
|
|
1465
|
-
rl.question(
|
|
1820
|
+
rl.question(pc6.cyan(" Runtime [node/python] (default: node): "), (a) => {
|
|
1466
1821
|
rl.close();
|
|
1467
1822
|
resolve(a.trim() || "node");
|
|
1468
1823
|
});
|
|
@@ -1471,16 +1826,16 @@ var create_default = defineCommand4({
|
|
|
1471
1826
|
}
|
|
1472
1827
|
}
|
|
1473
1828
|
if (!projectName) {
|
|
1474
|
-
console.error(
|
|
1829
|
+
console.error(pc6.red(" Error: Project name is required."));
|
|
1475
1830
|
process.exit(1);
|
|
1476
1831
|
}
|
|
1477
1832
|
const sanitized = sanitizeName(projectName);
|
|
1478
1833
|
if (!sanitized) {
|
|
1479
|
-
console.error(
|
|
1834
|
+
console.error(pc6.red(` Error: Invalid project name '${projectName}'.`));
|
|
1480
1835
|
process.exit(1);
|
|
1481
1836
|
}
|
|
1482
1837
|
if (runtime !== "node" && runtime !== "python") {
|
|
1483
|
-
console.error(
|
|
1838
|
+
console.error(pc6.red(` Error: Unknown runtime '${runtime}'. Use node or python.`));
|
|
1484
1839
|
process.exit(1);
|
|
1485
1840
|
}
|
|
1486
1841
|
const options = {
|
|
@@ -1494,90 +1849,252 @@ var create_default = defineCommand4({
|
|
|
1494
1849
|
try {
|
|
1495
1850
|
writeScaffold(targetDir, files);
|
|
1496
1851
|
} catch (err) {
|
|
1497
|
-
console.error(
|
|
1852
|
+
console.error(pc6.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
1498
1853
|
process.exit(1);
|
|
1499
1854
|
}
|
|
1500
|
-
console.log(
|
|
1501
|
-
Created ${
|
|
1855
|
+
console.log(pc6.green(`
|
|
1856
|
+
Created ${pc6.bold(sanitized)}/
|
|
1502
1857
|
`));
|
|
1503
|
-
console.log(
|
|
1858
|
+
console.log(pc6.dim(" Files generated:"));
|
|
1504
1859
|
for (const file of Object.keys(files)) {
|
|
1505
|
-
console.log(` ${
|
|
1860
|
+
console.log(` ${pc6.cyan(file)}`);
|
|
1506
1861
|
}
|
|
1507
1862
|
console.log("\n Next steps:");
|
|
1508
1863
|
if (runtime === "node") {
|
|
1509
|
-
console.log(` ${
|
|
1510
|
-
console.log(` ${
|
|
1511
|
-
console.log(` ${
|
|
1864
|
+
console.log(` ${pc6.bold(`cd ${sanitized}`)}`);
|
|
1865
|
+
console.log(` ${pc6.bold("npm install")}`);
|
|
1866
|
+
console.log(` ${pc6.bold("mcpman link .")}`);
|
|
1512
1867
|
} else {
|
|
1513
|
-
console.log(` ${
|
|
1514
|
-
console.log(` ${
|
|
1515
|
-
console.log(` ${
|
|
1868
|
+
console.log(` ${pc6.bold(`cd ${sanitized}`)}`);
|
|
1869
|
+
console.log(` ${pc6.bold("pip install -e .")}`);
|
|
1870
|
+
console.log(` ${pc6.bold("mcpman link .")}`);
|
|
1516
1871
|
}
|
|
1517
1872
|
console.log();
|
|
1518
1873
|
}
|
|
1519
1874
|
});
|
|
1520
1875
|
|
|
1521
|
-
// src/commands/
|
|
1522
|
-
import { defineCommand as
|
|
1523
|
-
import
|
|
1524
|
-
|
|
1525
|
-
// src/core/diagnostics.ts
|
|
1526
|
-
import { exec } from "child_process";
|
|
1527
|
-
import { promisify } from "util";
|
|
1876
|
+
// src/commands/diff.ts
|
|
1877
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
1878
|
+
import pc7 from "picocolors";
|
|
1528
1879
|
|
|
1529
|
-
// src/core/
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1880
|
+
// src/core/config-differ.ts
|
|
1881
|
+
function entryDiffs(a, b) {
|
|
1882
|
+
const diffs = [];
|
|
1883
|
+
if (a.command !== b.command) {
|
|
1884
|
+
diffs.push(`command: ${a.command} \u2192 ${b.command}`);
|
|
1885
|
+
}
|
|
1886
|
+
const aArgs = JSON.stringify(a.args ?? []);
|
|
1887
|
+
const bArgs = JSON.stringify(b.args ?? []);
|
|
1888
|
+
if (aArgs !== bArgs) {
|
|
1889
|
+
diffs.push(`args: ${aArgs} \u2192 ${bArgs}`);
|
|
1890
|
+
}
|
|
1891
|
+
const aEnv = JSON.stringify(a.env ?? {});
|
|
1892
|
+
const bEnv = JSON.stringify(b.env ?? {});
|
|
1893
|
+
if (aEnv !== bEnv) {
|
|
1894
|
+
diffs.push(`env: ${aEnv} \u2192 ${bEnv}`);
|
|
1895
|
+
}
|
|
1896
|
+
return diffs;
|
|
1897
|
+
}
|
|
1898
|
+
function diffClientConfigs(configA, configB) {
|
|
1899
|
+
const results = [];
|
|
1900
|
+
const serversA = configA.servers;
|
|
1901
|
+
const serversB = configB.servers;
|
|
1902
|
+
for (const name of Object.keys(serversB)) {
|
|
1903
|
+
if (!(name in serversA)) {
|
|
1904
|
+
results.push({ server: name, change: "added" });
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
for (const name of Object.keys(serversA)) {
|
|
1908
|
+
if (!(name in serversB)) {
|
|
1909
|
+
results.push({ server: name, change: "removed" });
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
for (const name of Object.keys(serversA)) {
|
|
1913
|
+
if (name in serversB) {
|
|
1914
|
+
const details = entryDiffs(serversA[name], serversB[name]);
|
|
1915
|
+
if (details.length > 0) {
|
|
1916
|
+
results.push({ server: name, change: "changed", details });
|
|
1561
1917
|
}
|
|
1562
|
-
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
const order = { removed: 0, added: 1, changed: 2 };
|
|
1921
|
+
results.sort((a, b) => {
|
|
1922
|
+
const orderDiff = order[a.change] - order[b.change];
|
|
1923
|
+
return orderDiff !== 0 ? orderDiff : a.server.localeCompare(b.server);
|
|
1563
1924
|
});
|
|
1925
|
+
return results;
|
|
1564
1926
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
clientInfo: { name: "mcpman-doctor", version: "0.1.0" }
|
|
1927
|
+
async function loadClientConfig(type) {
|
|
1928
|
+
try {
|
|
1929
|
+
const { getClient } = await import("./client-detector-O2HN4MUB.js");
|
|
1930
|
+
const handler = getClient(type);
|
|
1931
|
+
return await handler.readConfig();
|
|
1932
|
+
} catch {
|
|
1933
|
+
return null;
|
|
1573
1934
|
}
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// src/commands/diff.ts
|
|
1938
|
+
var VALID_CLIENTS = ["claude-desktop", "cursor", "vscode", "windsurf"];
|
|
1939
|
+
var CLIENT_DISPLAY = {
|
|
1940
|
+
"claude-desktop": "Claude Desktop",
|
|
1941
|
+
cursor: "Cursor",
|
|
1942
|
+
vscode: "VS Code",
|
|
1943
|
+
windsurf: "Windsurf"
|
|
1944
|
+
};
|
|
1945
|
+
var diff_default = defineCommand7({
|
|
1946
|
+
meta: {
|
|
1947
|
+
name: "diff",
|
|
1948
|
+
description: "Show config diff between two AI clients"
|
|
1949
|
+
},
|
|
1950
|
+
args: {
|
|
1951
|
+
clientA: {
|
|
1952
|
+
type: "positional",
|
|
1953
|
+
description: `Source client (${VALID_CLIENTS.join("|")})`,
|
|
1954
|
+
required: true
|
|
1955
|
+
},
|
|
1956
|
+
clientB: {
|
|
1957
|
+
type: "positional",
|
|
1958
|
+
description: `Target client (${VALID_CLIENTS.join("|")})`,
|
|
1959
|
+
required: true
|
|
1960
|
+
},
|
|
1961
|
+
json: {
|
|
1962
|
+
type: "boolean",
|
|
1963
|
+
description: "Output results as JSON",
|
|
1964
|
+
default: false
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
async run({ args }) {
|
|
1968
|
+
const clientA = args.clientA;
|
|
1969
|
+
const clientB = args.clientB;
|
|
1970
|
+
if (!VALID_CLIENTS.includes(clientA)) {
|
|
1971
|
+
console.error(
|
|
1972
|
+
`${pc7.red("\u2717")} Unknown client "${clientA}". Valid: ${VALID_CLIENTS.join(", ")}`
|
|
1973
|
+
);
|
|
1974
|
+
process.exit(1);
|
|
1975
|
+
}
|
|
1976
|
+
if (!VALID_CLIENTS.includes(clientB)) {
|
|
1977
|
+
console.error(
|
|
1978
|
+
`${pc7.red("\u2717")} Unknown client "${clientB}". Valid: ${VALID_CLIENTS.join(", ")}`
|
|
1979
|
+
);
|
|
1980
|
+
process.exit(1);
|
|
1981
|
+
}
|
|
1982
|
+
if (clientA === clientB) {
|
|
1983
|
+
console.error(`${pc7.red("\u2717")} clientA and clientB must be different.`);
|
|
1984
|
+
process.exit(1);
|
|
1985
|
+
}
|
|
1986
|
+
const [configA, configB] = await Promise.all([
|
|
1987
|
+
loadClientConfig(clientA),
|
|
1988
|
+
loadClientConfig(clientB)
|
|
1989
|
+
]);
|
|
1990
|
+
if (!configA) {
|
|
1991
|
+
console.error(`${pc7.red("\u2717")} Could not read config for ${CLIENT_DISPLAY[clientA]}.`);
|
|
1992
|
+
process.exit(1);
|
|
1993
|
+
}
|
|
1994
|
+
if (!configB) {
|
|
1995
|
+
console.error(`${pc7.red("\u2717")} Could not read config for ${CLIENT_DISPLAY[clientB]}.`);
|
|
1996
|
+
process.exit(1);
|
|
1997
|
+
}
|
|
1998
|
+
const diffs = diffClientConfigs(configA, configB);
|
|
1999
|
+
if (args.json) {
|
|
2000
|
+
console.log(JSON.stringify({ clientA, clientB, diffs }, null, 2));
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const labelA = CLIENT_DISPLAY[clientA];
|
|
2004
|
+
const labelB = CLIENT_DISPLAY[clientB];
|
|
2005
|
+
console.log(`
|
|
2006
|
+
${pc7.bold("mcpman diff")} ${pc7.cyan(labelA)} \u2192 ${pc7.cyan(labelB)}
|
|
2007
|
+
`);
|
|
2008
|
+
if (diffs.length === 0) {
|
|
2009
|
+
console.log(pc7.green(" \u2713 No differences \u2014 configs are identical."));
|
|
2010
|
+
console.log("");
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
for (const d of diffs) {
|
|
2014
|
+
if (d.change === "added") {
|
|
2015
|
+
console.log(` ${pc7.green("+")} ${pc7.bold(d.server)} ${pc7.dim(`(only in ${labelB})`)}`);
|
|
2016
|
+
} else if (d.change === "removed") {
|
|
2017
|
+
console.log(` ${pc7.red("-")} ${pc7.bold(d.server)} ${pc7.dim(`(only in ${labelA})`)}`);
|
|
2018
|
+
} else {
|
|
2019
|
+
console.log(` ${pc7.yellow("~")} ${pc7.bold(d.server)} ${pc7.dim("(changed)")}`);
|
|
2020
|
+
for (const detail of d.details ?? []) {
|
|
2021
|
+
console.log(` ${pc7.dim(detail)}`);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
const added = diffs.filter((d) => d.change === "added").length;
|
|
2026
|
+
const removed = diffs.filter((d) => d.change === "removed").length;
|
|
2027
|
+
const changed = diffs.filter((d) => d.change === "changed").length;
|
|
2028
|
+
const parts = [];
|
|
2029
|
+
if (added > 0) parts.push(pc7.green(`+${added} added`));
|
|
2030
|
+
if (removed > 0) parts.push(pc7.red(`-${removed} removed`));
|
|
2031
|
+
if (changed > 0) parts.push(pc7.yellow(`~${changed} changed`));
|
|
2032
|
+
console.log(`
|
|
2033
|
+
${parts.join(" ")}
|
|
2034
|
+
`);
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
|
|
2038
|
+
// src/commands/doctor.ts
|
|
2039
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
2040
|
+
import pc8 from "picocolors";
|
|
2041
|
+
|
|
2042
|
+
// src/core/diagnostics.ts
|
|
2043
|
+
import { exec } from "child_process";
|
|
2044
|
+
import { promisify } from "util";
|
|
2045
|
+
|
|
2046
|
+
// src/core/mcp-process-checks.ts
|
|
2047
|
+
import { spawn as spawn2 } from "child_process";
|
|
2048
|
+
async function checkProcessSpawn(command, args, env, timeoutMs = 3e3) {
|
|
2049
|
+
return new Promise((resolve) => {
|
|
2050
|
+
let settled = false;
|
|
2051
|
+
const child = spawn2(command, args, {
|
|
2052
|
+
env: { ...process.env, ...env },
|
|
2053
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2054
|
+
});
|
|
2055
|
+
const timer = setTimeout(() => {
|
|
2056
|
+
if (!settled) {
|
|
2057
|
+
settled = true;
|
|
2058
|
+
child.kill("SIGTERM");
|
|
2059
|
+
resolve({ name: "Process", passed: true, message: "starts successfully (still running)" });
|
|
2060
|
+
}
|
|
2061
|
+
}, timeoutMs);
|
|
2062
|
+
child.on("error", (err) => {
|
|
2063
|
+
if (!settled) {
|
|
2064
|
+
settled = true;
|
|
2065
|
+
clearTimeout(timer);
|
|
2066
|
+
resolve({ name: "Process", passed: false, message: `spawn error: ${err.message}` });
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
child.on("exit", (code) => {
|
|
2070
|
+
if (!settled) {
|
|
2071
|
+
settled = true;
|
|
2072
|
+
clearTimeout(timer);
|
|
2073
|
+
if (code === 0 || code === null) {
|
|
2074
|
+
resolve({ name: "Process", passed: true, message: "exits cleanly" });
|
|
2075
|
+
} else {
|
|
2076
|
+
resolve({ name: "Process", passed: false, message: `exits with code ${code}` });
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
var MCP_INIT_REQUEST = JSON.stringify({
|
|
2083
|
+
jsonrpc: "2.0",
|
|
2084
|
+
id: 1,
|
|
2085
|
+
method: "initialize",
|
|
2086
|
+
params: {
|
|
2087
|
+
protocolVersion: "2024-11-05",
|
|
2088
|
+
capabilities: {},
|
|
2089
|
+
clientInfo: { name: "mcpman-doctor", version: "0.1.0" }
|
|
2090
|
+
}
|
|
2091
|
+
});
|
|
2092
|
+
async function checkMcpHandshake(command, args, env, timeoutMs = 5e3) {
|
|
2093
|
+
return new Promise((resolve) => {
|
|
2094
|
+
let settled = false;
|
|
2095
|
+
const start = Date.now();
|
|
1579
2096
|
let stdout = "";
|
|
1580
|
-
const child =
|
|
2097
|
+
const child = spawn2(command, args, {
|
|
1581
2098
|
env: { ...process.env, ...env },
|
|
1582
2099
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1583
2100
|
});
|
|
@@ -1865,12 +2382,12 @@ async function getInstalledServers(clientFilter) {
|
|
|
1865
2382
|
|
|
1866
2383
|
// src/commands/doctor.ts
|
|
1867
2384
|
var CHECK_ICON = {
|
|
1868
|
-
pass:
|
|
1869
|
-
fail:
|
|
1870
|
-
skip:
|
|
1871
|
-
warn:
|
|
2385
|
+
pass: pc8.green("\u2713"),
|
|
2386
|
+
fail: pc8.red("\u2717"),
|
|
2387
|
+
skip: pc8.dim("-"),
|
|
2388
|
+
warn: pc8.yellow("\u26A0")
|
|
1872
2389
|
};
|
|
1873
|
-
var doctor_default =
|
|
2390
|
+
var doctor_default = defineCommand8({
|
|
1874
2391
|
meta: {
|
|
1875
2392
|
name: "doctor",
|
|
1876
2393
|
description: "Check MCP server health and configuration"
|
|
@@ -1883,11 +2400,11 @@ var doctor_default = defineCommand5({
|
|
|
1883
2400
|
}
|
|
1884
2401
|
},
|
|
1885
2402
|
async run({ args }) {
|
|
1886
|
-
console.log(
|
|
2403
|
+
console.log(pc8.bold("\n mcpman doctor\n"));
|
|
1887
2404
|
const servers = await getInstalledServers();
|
|
1888
2405
|
if (servers.length === 0) {
|
|
1889
2406
|
console.log(
|
|
1890
|
-
|
|
2407
|
+
pc8.dim(" No MCP servers installed. Run mcpman install <server> to get started.")
|
|
1891
2408
|
);
|
|
1892
2409
|
return;
|
|
1893
2410
|
}
|
|
@@ -1904,20 +2421,20 @@ var doctor_default = defineCommand5({
|
|
|
1904
2421
|
if (pluginSummary.total > 0) {
|
|
1905
2422
|
printPluginSection(pluginSummary);
|
|
1906
2423
|
}
|
|
1907
|
-
console.log(
|
|
2424
|
+
console.log(pc8.dim(` ${"\u2500".repeat(50)}`));
|
|
1908
2425
|
const parts = [];
|
|
1909
|
-
if (passed > 0) parts.push(
|
|
1910
|
-
if (failed > 0) parts.push(
|
|
2426
|
+
if (passed > 0) parts.push(pc8.green(`${passed} healthy`));
|
|
2427
|
+
if (failed > 0) parts.push(pc8.red(`${failed} unhealthy`));
|
|
1911
2428
|
console.log(` Summary: ${parts.join(", ")}`);
|
|
1912
2429
|
if (pluginSummary.total > 0) {
|
|
1913
2430
|
const pParts = [];
|
|
1914
|
-
if (pluginSummary.healthy > 0) pParts.push(
|
|
1915
|
-
if (pluginSummary.unhealthy > 0) pParts.push(
|
|
2431
|
+
if (pluginSummary.healthy > 0) pParts.push(pc8.green(`${pluginSummary.healthy} ok`));
|
|
2432
|
+
if (pluginSummary.unhealthy > 0) pParts.push(pc8.red(`${pluginSummary.unhealthy} broken`));
|
|
1916
2433
|
console.log(` Plugins: ${pParts.join(", ")}`);
|
|
1917
2434
|
}
|
|
1918
2435
|
if (failed > 0 || pluginSummary.unhealthy > 0) {
|
|
1919
2436
|
if (!args.fix) {
|
|
1920
|
-
console.log(
|
|
2437
|
+
console.log(pc8.dim(` Run ${pc8.cyan("mcpman doctor --fix")} for fix suggestions.
|
|
1921
2438
|
`));
|
|
1922
2439
|
}
|
|
1923
2440
|
process.exit(1);
|
|
@@ -1926,26 +2443,26 @@ var doctor_default = defineCommand5({
|
|
|
1926
2443
|
}
|
|
1927
2444
|
});
|
|
1928
2445
|
function printPluginSection(summary) {
|
|
1929
|
-
console.log(
|
|
2446
|
+
console.log(pc8.bold(` Plugins (${summary.total})`));
|
|
1930
2447
|
for (const r of summary.results) {
|
|
1931
|
-
const icon = r.loadable && r.prefixUnique && r.resolvable ?
|
|
2448
|
+
const icon = r.loadable && r.prefixUnique && r.resolvable ? pc8.green("\u2713") : pc8.red("\u2717");
|
|
1932
2449
|
const label = r.pluginName ? `${r.packageName} (${r.pluginName})` : r.packageName;
|
|
1933
|
-
const prefix = r.prefix ?
|
|
2450
|
+
const prefix = r.prefix ? pc8.dim(` [${r.prefix}]`) : "";
|
|
1934
2451
|
console.log(` ${icon} ${label}${prefix}`);
|
|
1935
2452
|
if (r.error) {
|
|
1936
|
-
console.log(` ${
|
|
2453
|
+
console.log(` ${pc8.yellow("\u2192")} ${r.error}`);
|
|
1937
2454
|
}
|
|
1938
2455
|
}
|
|
1939
2456
|
console.log();
|
|
1940
2457
|
}
|
|
1941
2458
|
function printServerResult(result, showFix) {
|
|
1942
|
-
const icon = result.status === "healthy" ?
|
|
1943
|
-
console.log(` ${icon} ${
|
|
2459
|
+
const icon = result.status === "healthy" ? pc8.green("\u25CF") : pc8.red("\u25CF");
|
|
2460
|
+
console.log(` ${icon} ${pc8.bold(result.serverName)}`);
|
|
1944
2461
|
for (const check of result.checks) {
|
|
1945
2462
|
const checkIcon = check.skipped ? CHECK_ICON.skip : check.passed ? CHECK_ICON.pass : CHECK_ICON.fail;
|
|
1946
2463
|
console.log(` ${checkIcon} ${check.name}: ${check.message}`);
|
|
1947
2464
|
if (showFix && !check.passed && !check.skipped && check.fix) {
|
|
1948
|
-
console.log(` ${
|
|
2465
|
+
console.log(` ${pc8.yellow("\u2192")} Fix: ${pc8.cyan(check.fix)}`);
|
|
1949
2466
|
}
|
|
1950
2467
|
}
|
|
1951
2468
|
console.log();
|
|
@@ -1954,11 +2471,11 @@ async function runParallel(tasks, concurrency) {
|
|
|
1954
2471
|
const results = [];
|
|
1955
2472
|
const executing = /* @__PURE__ */ new Set();
|
|
1956
2473
|
for (const task of tasks) {
|
|
1957
|
-
const
|
|
2474
|
+
const p13 = task().then((r) => {
|
|
1958
2475
|
results.push(r);
|
|
1959
|
-
executing.delete(
|
|
2476
|
+
executing.delete(p13);
|
|
1960
2477
|
});
|
|
1961
|
-
executing.add(
|
|
2478
|
+
executing.add(p13);
|
|
1962
2479
|
if (executing.size >= concurrency) {
|
|
1963
2480
|
await Promise.race(executing);
|
|
1964
2481
|
}
|
|
@@ -1967,18 +2484,188 @@ async function runParallel(tasks, concurrency) {
|
|
|
1967
2484
|
return results;
|
|
1968
2485
|
}
|
|
1969
2486
|
|
|
1970
|
-
// src/commands/
|
|
1971
|
-
import
|
|
2487
|
+
// src/commands/env.ts
|
|
2488
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
2489
|
+
import pc9 from "picocolors";
|
|
2490
|
+
|
|
2491
|
+
// src/core/env-manager.ts
|
|
2492
|
+
import fs8 from "fs";
|
|
1972
2493
|
import path7 from "path";
|
|
1973
|
-
|
|
1974
|
-
|
|
2494
|
+
function envFilePath(server, dir) {
|
|
2495
|
+
const base = dir ?? getEnvDir();
|
|
2496
|
+
const safe = server.replace(/[/@]/g, "_");
|
|
2497
|
+
return path7.join(base, `${safe}.json`);
|
|
2498
|
+
}
|
|
2499
|
+
function readStore(server, dir) {
|
|
2500
|
+
const file = envFilePath(server, dir);
|
|
2501
|
+
if (!fs8.existsSync(file)) return {};
|
|
2502
|
+
try {
|
|
2503
|
+
return JSON.parse(fs8.readFileSync(file, "utf-8"));
|
|
2504
|
+
} catch {
|
|
2505
|
+
return {};
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
function writeStore(server, store, dir) {
|
|
2509
|
+
const base = dir ?? getEnvDir();
|
|
2510
|
+
if (!fs8.existsSync(base)) {
|
|
2511
|
+
fs8.mkdirSync(base, { recursive: true });
|
|
2512
|
+
}
|
|
2513
|
+
const file = envFilePath(server, dir);
|
|
2514
|
+
fs8.writeFileSync(file, JSON.stringify(store, null, 2), "utf-8");
|
|
2515
|
+
}
|
|
2516
|
+
function setEnv(server, key, value, dir) {
|
|
2517
|
+
const store = readStore(server, dir);
|
|
2518
|
+
store[key] = value;
|
|
2519
|
+
writeStore(server, store, dir);
|
|
2520
|
+
}
|
|
2521
|
+
function getEnv(server, key, dir) {
|
|
2522
|
+
const store = readStore(server, dir);
|
|
2523
|
+
return key in store ? store[key] : null;
|
|
2524
|
+
}
|
|
2525
|
+
function listEnv(server, dir) {
|
|
2526
|
+
return readStore(server, dir);
|
|
2527
|
+
}
|
|
2528
|
+
function deleteEnv(server, key, dir) {
|
|
2529
|
+
const store = readStore(server, dir);
|
|
2530
|
+
if (!(key in store)) return;
|
|
2531
|
+
delete store[key];
|
|
2532
|
+
writeStore(server, store, dir);
|
|
2533
|
+
}
|
|
2534
|
+
function clearEnv(server, dir) {
|
|
2535
|
+
const file = envFilePath(server, dir);
|
|
2536
|
+
if (fs8.existsSync(file)) {
|
|
2537
|
+
fs8.unlinkSync(file);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
function listEnvServers(dir) {
|
|
2541
|
+
const base = dir ?? getEnvDir();
|
|
2542
|
+
if (!fs8.existsSync(base)) return [];
|
|
2543
|
+
return fs8.readdirSync(base).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5));
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// src/commands/env.ts
|
|
2547
|
+
var setCmd = defineCommand9({
|
|
2548
|
+
meta: { name: "set", description: "Set env var(s) for a server" },
|
|
2549
|
+
args: {
|
|
2550
|
+
server: { type: "positional", description: "Server name", required: true },
|
|
2551
|
+
pairs: { type: "positional", description: "KEY=VALUE pair(s)", required: true }
|
|
2552
|
+
},
|
|
2553
|
+
run({ args }) {
|
|
2554
|
+
const pairs = Array.isArray(args.pairs) ? args.pairs : [args.pairs];
|
|
2555
|
+
for (const pair of pairs) {
|
|
2556
|
+
const idx = pair.indexOf("=");
|
|
2557
|
+
if (idx <= 0) {
|
|
2558
|
+
console.error(`${pc9.red("\u2717")} Invalid format: "${pair}". Expected KEY=VALUE`);
|
|
2559
|
+
process.exit(1);
|
|
2560
|
+
}
|
|
2561
|
+
const key = pair.slice(0, idx);
|
|
2562
|
+
const value = pair.slice(idx + 1);
|
|
2563
|
+
setEnv(args.server, key, value);
|
|
2564
|
+
console.log(
|
|
2565
|
+
`${pc9.green("\u2713")} Set ${pc9.bold(key)}=${pc9.dim(value)} for ${pc9.cyan(args.server)}`
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
var getCmd = defineCommand9({
|
|
2571
|
+
meta: { name: "get", description: "Get an env var for a server" },
|
|
2572
|
+
args: {
|
|
2573
|
+
server: { type: "positional", description: "Server name", required: true },
|
|
2574
|
+
key: { type: "positional", description: "Variable name", required: true }
|
|
2575
|
+
},
|
|
2576
|
+
run({ args }) {
|
|
2577
|
+
const value = getEnv(args.server, args.key);
|
|
2578
|
+
if (value === null) {
|
|
2579
|
+
console.error(`${pc9.red("\u2717")} No env var "${args.key}" for ${pc9.cyan(args.server)}`);
|
|
2580
|
+
process.exit(1);
|
|
2581
|
+
}
|
|
2582
|
+
console.log(value);
|
|
2583
|
+
}
|
|
2584
|
+
});
|
|
2585
|
+
var listCmd2 = defineCommand9({
|
|
2586
|
+
meta: { name: "list", description: "List env vars for a server (or all servers)" },
|
|
2587
|
+
args: {
|
|
2588
|
+
server: { type: "positional", description: "Server name (optional)", required: false }
|
|
2589
|
+
},
|
|
2590
|
+
run({ args }) {
|
|
2591
|
+
if (args.server) {
|
|
2592
|
+
const store = listEnv(args.server);
|
|
2593
|
+
const keys = Object.keys(store);
|
|
2594
|
+
if (keys.length === 0) {
|
|
2595
|
+
console.log(pc9.dim(`No env vars for ${pc9.cyan(args.server)}.`));
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
console.log(`
|
|
2599
|
+
${pc9.bold(pc9.cyan(args.server))}`);
|
|
2600
|
+
for (const [k, v] of Object.entries(store)) {
|
|
2601
|
+
console.log(` ${pc9.green("\u25CF")} ${pc9.bold(k)}=${pc9.dim(v)}`);
|
|
2602
|
+
}
|
|
2603
|
+
console.log("");
|
|
2604
|
+
} else {
|
|
2605
|
+
const servers = listEnvServers();
|
|
2606
|
+
if (servers.length === 0) {
|
|
2607
|
+
console.log(pc9.dim("No env vars stored."));
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
for (const srv of servers) {
|
|
2611
|
+
const store = listEnv(srv);
|
|
2612
|
+
console.log(`
|
|
2613
|
+
${pc9.bold(pc9.cyan(srv))}`);
|
|
2614
|
+
for (const [k, v] of Object.entries(store)) {
|
|
2615
|
+
console.log(` ${pc9.green("\u25CF")} ${pc9.bold(k)}=${pc9.dim(v)}`);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
console.log("");
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
var delCmd = defineCommand9({
|
|
2623
|
+
meta: { name: "del", description: "Delete an env var for a server" },
|
|
2624
|
+
args: {
|
|
2625
|
+
server: { type: "positional", description: "Server name", required: true },
|
|
2626
|
+
key: { type: "positional", description: "Variable name to delete", required: true }
|
|
2627
|
+
},
|
|
2628
|
+
run({ args }) {
|
|
2629
|
+
deleteEnv(args.server, args.key);
|
|
2630
|
+
console.log(`${pc9.green("\u2713")} Deleted ${pc9.bold(args.key)} from ${pc9.cyan(args.server)}`);
|
|
2631
|
+
}
|
|
2632
|
+
});
|
|
2633
|
+
var clearCmd = defineCommand9({
|
|
2634
|
+
meta: { name: "clear", description: "Clear all env vars for a server" },
|
|
2635
|
+
args: {
|
|
2636
|
+
server: { type: "positional", description: "Server name", required: true }
|
|
2637
|
+
},
|
|
2638
|
+
run({ args }) {
|
|
2639
|
+
clearEnv(args.server);
|
|
2640
|
+
console.log(`${pc9.green("\u2713")} Cleared all env vars for ${pc9.cyan(args.server)}`);
|
|
2641
|
+
}
|
|
2642
|
+
});
|
|
2643
|
+
var env_default = defineCommand9({
|
|
2644
|
+
meta: {
|
|
2645
|
+
name: "env",
|
|
2646
|
+
description: "Manage per-server environment variables (non-sensitive)"
|
|
2647
|
+
},
|
|
2648
|
+
subCommands: {
|
|
2649
|
+
set: setCmd,
|
|
2650
|
+
get: getCmd,
|
|
2651
|
+
list: listCmd2,
|
|
2652
|
+
del: delCmd,
|
|
2653
|
+
clear: clearCmd
|
|
2654
|
+
}
|
|
2655
|
+
});
|
|
2656
|
+
|
|
2657
|
+
// src/commands/export-command.ts
|
|
2658
|
+
import fs10 from "fs";
|
|
2659
|
+
import path8 from "path";
|
|
2660
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
2661
|
+
import pc10 from "picocolors";
|
|
1975
2662
|
|
|
1976
2663
|
// src/core/export-import-service.ts
|
|
1977
|
-
import
|
|
2664
|
+
import fs9 from "fs";
|
|
1978
2665
|
|
|
1979
2666
|
// src/utils/constants.ts
|
|
1980
2667
|
var APP_NAME = "mcpman";
|
|
1981
|
-
var APP_VERSION = "0.
|
|
2668
|
+
var APP_VERSION = "0.9.0";
|
|
1982
2669
|
var APP_DESCRIPTION = "The package manager for MCP servers";
|
|
1983
2670
|
|
|
1984
2671
|
// src/core/export-import-service.ts
|
|
@@ -1994,7 +2681,7 @@ function createExportBundle(opts = {}) {
|
|
|
1994
2681
|
};
|
|
1995
2682
|
if (includeVault) {
|
|
1996
2683
|
const vaultPath = getVaultPath();
|
|
1997
|
-
if (
|
|
2684
|
+
if (fs9.existsSync(vaultPath)) {
|
|
1998
2685
|
bundle.vault = readVault();
|
|
1999
2686
|
}
|
|
2000
2687
|
}
|
|
@@ -2060,7 +2747,7 @@ function importBundle(bundle, opts = {}) {
|
|
|
2060
2747
|
|
|
2061
2748
|
// src/commands/export-command.ts
|
|
2062
2749
|
var DEFAULT_OUTPUT = "mcpman-export.json";
|
|
2063
|
-
var export_command_default =
|
|
2750
|
+
var export_command_default = defineCommand10({
|
|
2064
2751
|
meta: {
|
|
2065
2752
|
name: "export",
|
|
2066
2753
|
description: "Export mcpman config, lockfile, vault, and plugins to a portable JSON file"
|
|
@@ -2084,29 +2771,239 @@ var export_command_default = defineCommand6({
|
|
|
2084
2771
|
},
|
|
2085
2772
|
run({ args }) {
|
|
2086
2773
|
const outputFile = args.output || DEFAULT_OUTPUT;
|
|
2087
|
-
const outputPath =
|
|
2774
|
+
const outputPath = path8.resolve(outputFile);
|
|
2088
2775
|
const bundle = createExportBundle({
|
|
2089
2776
|
includeVault: !args["no-vault"],
|
|
2090
2777
|
includePlugins: !args["no-plugins"]
|
|
2091
2778
|
});
|
|
2092
2779
|
const serverCount = Object.keys(bundle.lockfile.servers).length;
|
|
2093
2780
|
const configKeys = Object.keys(bundle.config).length;
|
|
2094
|
-
|
|
2095
|
-
console.log(`${
|
|
2096
|
-
console.log(
|
|
2097
|
-
console.log(
|
|
2098
|
-
console.log(
|
|
2099
|
-
console.log(
|
|
2781
|
+
fs10.writeFileSync(outputPath, JSON.stringify(bundle, null, 2), "utf-8");
|
|
2782
|
+
console.log(`${pc10.green("\u2713")} Exported to ${pc10.bold(outputFile)}`);
|
|
2783
|
+
console.log(pc10.dim(` Config keys: ${configKeys}`));
|
|
2784
|
+
console.log(pc10.dim(` Servers: ${serverCount}`));
|
|
2785
|
+
console.log(pc10.dim(` Vault: ${bundle.vault ? "included" : "excluded"}`));
|
|
2786
|
+
console.log(pc10.dim(` Plugins: ${bundle.plugins?.length ?? 0}`));
|
|
2787
|
+
}
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2790
|
+
// src/commands/group.ts
|
|
2791
|
+
import { spawn as spawn3 } from "child_process";
|
|
2792
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
2793
|
+
import pc11 from "picocolors";
|
|
2794
|
+
|
|
2795
|
+
// src/core/group-manager.ts
|
|
2796
|
+
import fs11 from "fs";
|
|
2797
|
+
function readGroups(file) {
|
|
2798
|
+
const target = file ?? getGroupsFile();
|
|
2799
|
+
if (!fs11.existsSync(target)) return {};
|
|
2800
|
+
try {
|
|
2801
|
+
return JSON.parse(fs11.readFileSync(target, "utf-8"));
|
|
2802
|
+
} catch {
|
|
2803
|
+
return {};
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
function writeGroups(store, file) {
|
|
2807
|
+
const target = file ?? getGroupsFile();
|
|
2808
|
+
const dir = target.slice(0, target.lastIndexOf("/"));
|
|
2809
|
+
if (dir && !fs11.existsSync(dir)) {
|
|
2810
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
2811
|
+
}
|
|
2812
|
+
fs11.writeFileSync(target, JSON.stringify(store, null, 2), "utf-8");
|
|
2813
|
+
}
|
|
2814
|
+
function addToGroup(group, servers, file) {
|
|
2815
|
+
const store = readGroups(file);
|
|
2816
|
+
const existing = new Set(store[group] ?? []);
|
|
2817
|
+
for (const s of servers) existing.add(s);
|
|
2818
|
+
store[group] = [...existing].sort();
|
|
2819
|
+
writeGroups(store, file);
|
|
2820
|
+
}
|
|
2821
|
+
function removeFromGroup(group, servers, file) {
|
|
2822
|
+
const store = readGroups(file);
|
|
2823
|
+
if (!store[group]) return;
|
|
2824
|
+
const toRemove = new Set(servers);
|
|
2825
|
+
store[group] = store[group].filter((s) => !toRemove.has(s));
|
|
2826
|
+
if (store[group].length === 0) delete store[group];
|
|
2827
|
+
writeGroups(store, file);
|
|
2828
|
+
}
|
|
2829
|
+
function getGroup(group, file) {
|
|
2830
|
+
return readGroups(file)[group] ?? [];
|
|
2831
|
+
}
|
|
2832
|
+
function listGroups(file) {
|
|
2833
|
+
return Object.keys(readGroups(file)).sort();
|
|
2834
|
+
}
|
|
2835
|
+
function deleteGroup(group, file) {
|
|
2836
|
+
const store = readGroups(file);
|
|
2837
|
+
if (!(group in store)) return;
|
|
2838
|
+
delete store[group];
|
|
2839
|
+
writeGroups(store, file);
|
|
2840
|
+
}
|
|
2841
|
+
function groupExists(group, file) {
|
|
2842
|
+
return group in readGroups(file);
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
// src/commands/group.ts
|
|
2846
|
+
var addCmd2 = defineCommand11({
|
|
2847
|
+
meta: { name: "add", description: "Add servers to a group" },
|
|
2848
|
+
args: {
|
|
2849
|
+
name: { type: "positional", description: "Group name", required: true },
|
|
2850
|
+
servers: { type: "positional", description: "Server name(s)", required: true }
|
|
2851
|
+
},
|
|
2852
|
+
run({ args }) {
|
|
2853
|
+
const servers = Array.isArray(args.servers) ? args.servers : [args.servers];
|
|
2854
|
+
addToGroup(args.name, servers);
|
|
2855
|
+
console.log(`${pc11.green("\u2713")} Added ${servers.join(", ")} to group ${pc11.cyan(args.name)}`);
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2858
|
+
var rmCmd = defineCommand11({
|
|
2859
|
+
meta: { name: "rm", description: "Remove servers from a group" },
|
|
2860
|
+
args: {
|
|
2861
|
+
name: { type: "positional", description: "Group name", required: true },
|
|
2862
|
+
servers: { type: "positional", description: "Server name(s)", required: true }
|
|
2863
|
+
},
|
|
2864
|
+
run({ args }) {
|
|
2865
|
+
const servers = Array.isArray(args.servers) ? args.servers : [args.servers];
|
|
2866
|
+
removeFromGroup(args.name, servers);
|
|
2867
|
+
console.log(`${pc11.green("\u2713")} Removed ${servers.join(", ")} from group ${pc11.cyan(args.name)}`);
|
|
2868
|
+
}
|
|
2869
|
+
});
|
|
2870
|
+
var listCmd3 = defineCommand11({
|
|
2871
|
+
meta: { name: "list", description: "List all groups (or members of a group)" },
|
|
2872
|
+
args: {
|
|
2873
|
+
name: { type: "positional", description: "Group name (optional)", required: false }
|
|
2874
|
+
},
|
|
2875
|
+
run({ args }) {
|
|
2876
|
+
if (args.name) {
|
|
2877
|
+
const members = getGroup(args.name);
|
|
2878
|
+
if (members.length === 0) {
|
|
2879
|
+
console.log(pc11.dim(`Group "${args.name}" is empty or does not exist.`));
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
console.log(`
|
|
2883
|
+
${pc11.bold(pc11.cyan(args.name))}`);
|
|
2884
|
+
for (const s of members) console.log(` ${pc11.green("\u25CF")} ${s}`);
|
|
2885
|
+
console.log("");
|
|
2886
|
+
} else {
|
|
2887
|
+
const groups = listGroups();
|
|
2888
|
+
if (groups.length === 0) {
|
|
2889
|
+
console.log(pc11.dim("No groups defined. Use `mcpman group add <name> <server>`."));
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
console.log("");
|
|
2893
|
+
for (const g of groups) {
|
|
2894
|
+
const members = getGroup(g);
|
|
2895
|
+
console.log(
|
|
2896
|
+
` ${pc11.cyan(pc11.bold(g))} ${pc11.dim(`(${members.length} server${members.length !== 1 ? "s" : ""})`)}`
|
|
2897
|
+
);
|
|
2898
|
+
for (const s of members) console.log(` ${pc11.dim("\xB7")} ${s}`);
|
|
2899
|
+
}
|
|
2900
|
+
console.log("");
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
});
|
|
2904
|
+
var deleteCmd = defineCommand11({
|
|
2905
|
+
meta: { name: "delete", description: "Delete an entire group" },
|
|
2906
|
+
args: {
|
|
2907
|
+
name: { type: "positional", description: "Group name", required: true }
|
|
2908
|
+
},
|
|
2909
|
+
run({ args }) {
|
|
2910
|
+
if (!groupExists(args.name)) {
|
|
2911
|
+
console.error(`${pc11.red("\u2717")} Group "${args.name}" does not exist.`);
|
|
2912
|
+
process.exit(1);
|
|
2913
|
+
}
|
|
2914
|
+
deleteGroup(args.name);
|
|
2915
|
+
console.log(`${pc11.green("\u2713")} Deleted group ${pc11.cyan(args.name)}`);
|
|
2916
|
+
}
|
|
2917
|
+
});
|
|
2918
|
+
var installCmd = defineCommand11({
|
|
2919
|
+
meta: { name: "install", description: "Install all servers in a group" },
|
|
2920
|
+
args: {
|
|
2921
|
+
name: { type: "positional", description: "Group name", required: true }
|
|
2922
|
+
},
|
|
2923
|
+
async run({ args }) {
|
|
2924
|
+
const members = getGroup(args.name);
|
|
2925
|
+
if (members.length === 0) {
|
|
2926
|
+
console.error(`${pc11.red("\u2717")} Group "${args.name}" is empty or does not exist.`);
|
|
2927
|
+
process.exit(1);
|
|
2928
|
+
}
|
|
2929
|
+
console.log(
|
|
2930
|
+
`${pc11.cyan("Installing")} group ${pc11.bold(args.name)} (${members.length} servers)...`
|
|
2931
|
+
);
|
|
2932
|
+
for (const server of members) {
|
|
2933
|
+
console.log(`
|
|
2934
|
+
${pc11.dim("\u2192")} Installing ${pc11.bold(server)}...`);
|
|
2935
|
+
await runInstall(server);
|
|
2936
|
+
}
|
|
2937
|
+
console.log(`
|
|
2938
|
+
${pc11.green("\u2713")} Group install complete.`);
|
|
2939
|
+
}
|
|
2940
|
+
});
|
|
2941
|
+
var runCmd = defineCommand11({
|
|
2942
|
+
meta: { name: "run", description: "Run all servers in a group concurrently" },
|
|
2943
|
+
args: {
|
|
2944
|
+
name: { type: "positional", description: "Group name", required: true }
|
|
2945
|
+
},
|
|
2946
|
+
run({ args }) {
|
|
2947
|
+
const members = getGroup(args.name);
|
|
2948
|
+
if (members.length === 0) {
|
|
2949
|
+
console.error(`${pc11.red("\u2717")} Group "${args.name}" is empty or does not exist.`);
|
|
2950
|
+
process.exit(1);
|
|
2951
|
+
}
|
|
2952
|
+
const lockfile = readLockfile();
|
|
2953
|
+
console.log(
|
|
2954
|
+
`${pc11.cyan("Spawning")} group ${pc11.bold(args.name)} (${members.length} servers)...
|
|
2955
|
+
`
|
|
2956
|
+
);
|
|
2957
|
+
for (const server of members) {
|
|
2958
|
+
const entry = lockfile.servers[server];
|
|
2959
|
+
if (!entry) {
|
|
2960
|
+
console.warn(` ${pc11.yellow("!")} ${server} not in lockfile \u2014 skipping`);
|
|
2961
|
+
continue;
|
|
2962
|
+
}
|
|
2963
|
+
const child = spawn3(entry.command, entry.args ?? [], {
|
|
2964
|
+
env: process.env,
|
|
2965
|
+
stdio: "inherit",
|
|
2966
|
+
detached: false
|
|
2967
|
+
});
|
|
2968
|
+
child.on("error", (err) => {
|
|
2969
|
+
console.error(` ${pc11.red("\u2717")} ${server}: ${err.message}`);
|
|
2970
|
+
});
|
|
2971
|
+
console.log(` ${pc11.green("\u2713")} Spawned ${pc11.bold(server)} (pid ${child.pid ?? "?"})`);
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
});
|
|
2975
|
+
function runInstall(server) {
|
|
2976
|
+
return new Promise((resolve, reject) => {
|
|
2977
|
+
const child = spawn3("mcpman", ["install", server], { stdio: "inherit" });
|
|
2978
|
+
child.on("close", (code) => {
|
|
2979
|
+
if (code === 0) resolve();
|
|
2980
|
+
else reject(new Error(`install exited with code ${code}`));
|
|
2981
|
+
});
|
|
2982
|
+
child.on("error", reject);
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
var group_default = defineCommand11({
|
|
2986
|
+
meta: {
|
|
2987
|
+
name: "group",
|
|
2988
|
+
description: "Manage named server groups"
|
|
2989
|
+
},
|
|
2990
|
+
subCommands: {
|
|
2991
|
+
add: addCmd2,
|
|
2992
|
+
rm: rmCmd,
|
|
2993
|
+
list: listCmd3,
|
|
2994
|
+
delete: deleteCmd,
|
|
2995
|
+
install: installCmd,
|
|
2996
|
+
run: runCmd
|
|
2100
2997
|
}
|
|
2101
2998
|
});
|
|
2102
2999
|
|
|
2103
3000
|
// src/commands/import-command.ts
|
|
2104
|
-
import
|
|
2105
|
-
import
|
|
3001
|
+
import fs12 from "fs";
|
|
3002
|
+
import path9 from "path";
|
|
2106
3003
|
import * as p3 from "@clack/prompts";
|
|
2107
|
-
import { defineCommand as
|
|
2108
|
-
import
|
|
2109
|
-
var import_command_default =
|
|
3004
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
3005
|
+
import pc12 from "picocolors";
|
|
3006
|
+
var import_command_default = defineCommand12({
|
|
2110
3007
|
meta: {
|
|
2111
3008
|
name: "import",
|
|
2112
3009
|
description: "Import mcpman config, lockfile, vault, and plugins from an export file"
|
|
@@ -2129,21 +3026,21 @@ var import_command_default = defineCommand7({
|
|
|
2129
3026
|
}
|
|
2130
3027
|
},
|
|
2131
3028
|
async run({ args }) {
|
|
2132
|
-
const filePath =
|
|
2133
|
-
if (!
|
|
2134
|
-
console.error(`${
|
|
3029
|
+
const filePath = path9.resolve(args.file);
|
|
3030
|
+
if (!fs12.existsSync(filePath)) {
|
|
3031
|
+
console.error(`${pc12.red("\u2717")} File not found: ${filePath}`);
|
|
2135
3032
|
process.exit(1);
|
|
2136
3033
|
}
|
|
2137
3034
|
let raw;
|
|
2138
3035
|
try {
|
|
2139
|
-
raw = JSON.parse(
|
|
3036
|
+
raw = JSON.parse(fs12.readFileSync(filePath, "utf-8"));
|
|
2140
3037
|
} catch {
|
|
2141
|
-
console.error(`${
|
|
3038
|
+
console.error(`${pc12.red("\u2717")} Invalid JSON in ${filePath}`);
|
|
2142
3039
|
process.exit(1);
|
|
2143
3040
|
}
|
|
2144
3041
|
const error2 = validateBundle(raw);
|
|
2145
3042
|
if (error2) {
|
|
2146
|
-
console.error(`${
|
|
3043
|
+
console.error(`${pc12.red("\u2717")} Invalid export bundle: ${error2}`);
|
|
2147
3044
|
process.exit(1);
|
|
2148
3045
|
}
|
|
2149
3046
|
const bundle = raw;
|
|
@@ -2153,16 +3050,16 @@ var import_command_default = defineCommand7({
|
|
|
2153
3050
|
const hasVault = !!bundle.vault;
|
|
2154
3051
|
const isDryRun = !!args["dry-run"];
|
|
2155
3052
|
console.log("");
|
|
2156
|
-
console.log(
|
|
2157
|
-
console.log(
|
|
2158
|
-
console.log(
|
|
2159
|
-
console.log(` Config keys: ${
|
|
2160
|
-
console.log(` Servers: ${
|
|
2161
|
-
console.log(` Vault: ${hasVault ?
|
|
2162
|
-
console.log(` Plugins: ${
|
|
3053
|
+
console.log(pc12.bold("Import summary:"));
|
|
3054
|
+
console.log(pc12.dim(` Source version: mcpman ${bundle.mcpmanVersion}`));
|
|
3055
|
+
console.log(pc12.dim(` Exported at: ${bundle.exportedAt}`));
|
|
3056
|
+
console.log(` Config keys: ${pc12.cyan(String(configKeys))}`);
|
|
3057
|
+
console.log(` Servers: ${pc12.cyan(String(serverCount))}`);
|
|
3058
|
+
console.log(` Vault: ${hasVault ? pc12.green("included") : pc12.dim("not included")}`);
|
|
3059
|
+
console.log(` Plugins: ${pc12.cyan(String(pluginCount))}`);
|
|
2163
3060
|
console.log("");
|
|
2164
3061
|
if (isDryRun) {
|
|
2165
|
-
console.log(
|
|
3062
|
+
console.log(pc12.yellow(" [dry-run] No changes applied."));
|
|
2166
3063
|
return;
|
|
2167
3064
|
}
|
|
2168
3065
|
if (!args.yes) {
|
|
@@ -2176,18 +3073,18 @@ var import_command_default = defineCommand7({
|
|
|
2176
3073
|
}
|
|
2177
3074
|
}
|
|
2178
3075
|
const summary = importBundle(bundle, { dryRun: false });
|
|
2179
|
-
console.log(`${
|
|
2180
|
-
console.log(
|
|
2181
|
-
console.log(
|
|
2182
|
-
console.log(
|
|
2183
|
-
console.log(
|
|
3076
|
+
console.log(`${pc12.green("\u2713")} Import complete`);
|
|
3077
|
+
console.log(pc12.dim(` Config keys restored: ${summary.configKeys}`));
|
|
3078
|
+
console.log(pc12.dim(` Servers restored: ${summary.servers}`));
|
|
3079
|
+
console.log(pc12.dim(` Vault: ${summary.vaultImported ? "restored" : "skipped"}`));
|
|
3080
|
+
console.log(pc12.dim(` Plugins installed: ${summary.pluginsInstalled}`));
|
|
2184
3081
|
}
|
|
2185
3082
|
});
|
|
2186
3083
|
|
|
2187
3084
|
// src/commands/info.ts
|
|
2188
|
-
import { defineCommand as
|
|
3085
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
2189
3086
|
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
2190
|
-
import
|
|
3087
|
+
import pc13 from "picocolors";
|
|
2191
3088
|
|
|
2192
3089
|
// src/core/package-info.ts
|
|
2193
3090
|
async function buildInfo(name, entry, source = "npm") {
|
|
@@ -2242,11 +3139,11 @@ async function getPackageInfo(serverName) {
|
|
|
2242
3139
|
// src/commands/info.ts
|
|
2243
3140
|
function colorRisk2(score, riskLevel) {
|
|
2244
3141
|
const label = score !== null ? `${score}/100 (${riskLevel})` : riskLevel;
|
|
2245
|
-
if (riskLevel === "LOW") return
|
|
2246
|
-
if (riskLevel === "MEDIUM") return
|
|
2247
|
-
if (riskLevel === "HIGH") return
|
|
2248
|
-
if (riskLevel === "CRITICAL") return
|
|
2249
|
-
return
|
|
3142
|
+
if (riskLevel === "LOW") return pc13.green(label);
|
|
3143
|
+
if (riskLevel === "MEDIUM") return pc13.yellow(label);
|
|
3144
|
+
if (riskLevel === "HIGH") return pc13.red(label);
|
|
3145
|
+
if (riskLevel === "CRITICAL") return pc13.bold(pc13.red(label));
|
|
3146
|
+
return pc13.dim(label);
|
|
2250
3147
|
}
|
|
2251
3148
|
function formatDaysAgo(isoDate) {
|
|
2252
3149
|
if (!isoDate) return "unknown";
|
|
@@ -2256,54 +3153,54 @@ function formatDaysAgo(isoDate) {
|
|
|
2256
3153
|
return `${days} days ago`;
|
|
2257
3154
|
}
|
|
2258
3155
|
function printInfo(info2) {
|
|
2259
|
-
const installedBadge = info2.isInstalled ?
|
|
3156
|
+
const installedBadge = info2.isInstalled ? pc13.green(" [installed]") : pc13.dim(" [not installed]");
|
|
2260
3157
|
console.log();
|
|
2261
|
-
console.log(
|
|
2262
|
-
console.log(
|
|
2263
|
-
console.log(` ${
|
|
2264
|
-
console.log(` ${
|
|
3158
|
+
console.log(pc13.bold(` ${info2.name}@${info2.version}`) + installedBadge);
|
|
3159
|
+
console.log(pc13.dim(` ${"\u2500".repeat(60)}`));
|
|
3160
|
+
console.log(` ${pc13.dim("Source:")} ${info2.source}`);
|
|
3161
|
+
console.log(` ${pc13.dim("Runtime:")} ${info2.runtime}`);
|
|
2265
3162
|
if (info2.description) {
|
|
2266
|
-
console.log(` ${
|
|
3163
|
+
console.log(` ${pc13.dim("Description:")} ${info2.description}`);
|
|
2267
3164
|
}
|
|
2268
3165
|
if (info2.deprecated) {
|
|
2269
|
-
console.log(` ${
|
|
3166
|
+
console.log(` ${pc13.red("[DEPRECATED]")} This package is deprecated`);
|
|
2270
3167
|
}
|
|
2271
3168
|
console.log();
|
|
2272
|
-
console.log(` ${
|
|
2273
|
-
console.log(` ${
|
|
3169
|
+
console.log(` ${pc13.bold("Trust & Security")}`);
|
|
3170
|
+
console.log(` ${pc13.dim("Trust score:")} ${colorRisk2(info2.trustScore, info2.riskLevel)}`);
|
|
2274
3171
|
if (info2.source === "npm") {
|
|
2275
3172
|
console.log(
|
|
2276
|
-
` ${
|
|
3173
|
+
` ${pc13.dim("Downloads:")} ${info2.weeklyDownloads.toLocaleString()}/week ${pc13.dim("|")} ${pc13.dim("Age:")} ${info2.packageAge}d ${pc13.dim("|")} ${pc13.dim("Maintainers:")} ${info2.maintainerCount}`
|
|
2277
3174
|
);
|
|
2278
3175
|
if (info2.lastPublish) {
|
|
2279
|
-
console.log(` ${
|
|
3176
|
+
console.log(` ${pc13.dim("Last publish:")} ${formatDaysAgo(info2.lastPublish)}`);
|
|
2280
3177
|
}
|
|
2281
3178
|
} else {
|
|
2282
|
-
console.log(
|
|
3179
|
+
console.log(pc13.dim(" (Trust data available for npm packages only)"));
|
|
2283
3180
|
}
|
|
2284
3181
|
console.log();
|
|
2285
|
-
console.log(` ${
|
|
3182
|
+
console.log(` ${pc13.bold("Environment Variables")}`);
|
|
2286
3183
|
if (info2.envVars.length > 0) {
|
|
2287
3184
|
for (const env of info2.envVars) {
|
|
2288
|
-
console.log(` ${
|
|
3185
|
+
console.log(` ${pc13.cyan("\u2022")} ${env}`);
|
|
2289
3186
|
}
|
|
2290
3187
|
} else {
|
|
2291
|
-
console.log(
|
|
3188
|
+
console.log(pc13.dim(" none required"));
|
|
2292
3189
|
}
|
|
2293
3190
|
console.log();
|
|
2294
|
-
console.log(` ${
|
|
3191
|
+
console.log(` ${pc13.bold("Installed Clients")}`);
|
|
2295
3192
|
if (info2.installedClients.length > 0) {
|
|
2296
3193
|
for (const client of info2.installedClients) {
|
|
2297
|
-
console.log(` ${
|
|
3194
|
+
console.log(` ${pc13.green("\u2713")} ${client}`);
|
|
2298
3195
|
}
|
|
2299
3196
|
} else {
|
|
2300
|
-
console.log(
|
|
3197
|
+
console.log(pc13.dim(" Not installed in any client"));
|
|
2301
3198
|
}
|
|
2302
3199
|
console.log();
|
|
2303
|
-
console.log(
|
|
3200
|
+
console.log(pc13.dim(` ${"\u2500".repeat(60)}`));
|
|
2304
3201
|
console.log();
|
|
2305
3202
|
}
|
|
2306
|
-
var info_default =
|
|
3203
|
+
var info_default = defineCommand13({
|
|
2307
3204
|
meta: {
|
|
2308
3205
|
name: "info",
|
|
2309
3206
|
description: "Show detailed metadata for an MCP server (installed or from registry)"
|
|
@@ -2327,13 +3224,13 @@ var info_default = defineCommand8({
|
|
|
2327
3224
|
info2 = await getPackageInfo(args.server);
|
|
2328
3225
|
} catch (err) {
|
|
2329
3226
|
spinner5.error({ text: "Failed to fetch package info" });
|
|
2330
|
-
console.error(
|
|
3227
|
+
console.error(pc13.red(String(err)));
|
|
2331
3228
|
process.exit(1);
|
|
2332
3229
|
}
|
|
2333
3230
|
if (!info2) {
|
|
2334
3231
|
spinner5.error({ text: `Package not found: ${args.server}` });
|
|
2335
3232
|
console.log(
|
|
2336
|
-
|
|
3233
|
+
pc13.dim(`
|
|
2337
3234
|
"${args.server}" was not found in the npm registry or your lockfile.
|
|
2338
3235
|
`)
|
|
2339
3236
|
);
|
|
@@ -2349,10 +3246,10 @@ var info_default = defineCommand8({
|
|
|
2349
3246
|
});
|
|
2350
3247
|
|
|
2351
3248
|
// src/commands/init.ts
|
|
2352
|
-
import
|
|
3249
|
+
import path10 from "path";
|
|
2353
3250
|
import * as p4 from "@clack/prompts";
|
|
2354
|
-
import { defineCommand as
|
|
2355
|
-
var init_default =
|
|
3251
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
3252
|
+
var init_default = defineCommand14({
|
|
2356
3253
|
meta: {
|
|
2357
3254
|
name: "init",
|
|
2358
3255
|
description: "Initialize mcpman.lock in the current project"
|
|
@@ -2368,7 +3265,7 @@ var init_default = defineCommand9({
|
|
|
2368
3265
|
async run({ args }) {
|
|
2369
3266
|
const nonInteractive = args.yes || !process.stdout.isTTY;
|
|
2370
3267
|
p4.intro("mcpman init");
|
|
2371
|
-
const targetPath =
|
|
3268
|
+
const targetPath = path10.join(process.cwd(), LOCKFILE_NAME);
|
|
2372
3269
|
const existing = findLockfile();
|
|
2373
3270
|
if (existing) {
|
|
2374
3271
|
if (nonInteractive) {
|
|
@@ -2384,7 +3281,7 @@ var init_default = defineCommand9({
|
|
|
2384
3281
|
}
|
|
2385
3282
|
let clients = [];
|
|
2386
3283
|
try {
|
|
2387
|
-
const mod = await import("./client-detector-
|
|
3284
|
+
const mod = await import("./client-detector-O2HN4MUB.js");
|
|
2388
3285
|
clients = await mod.getInstalledClients();
|
|
2389
3286
|
} catch {
|
|
2390
3287
|
p4.log.warn("Could not detect AI clients \u2014 creating empty lockfile.");
|
|
@@ -2452,7 +3349,7 @@ var init_default = defineCommand9({
|
|
|
2452
3349
|
|
|
2453
3350
|
// src/commands/install.ts
|
|
2454
3351
|
import * as p7 from "@clack/prompts";
|
|
2455
|
-
import { defineCommand as
|
|
3352
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
2456
3353
|
|
|
2457
3354
|
// src/core/installer.ts
|
|
2458
3355
|
import * as p6 from "@clack/prompts";
|
|
@@ -2492,7 +3389,7 @@ async function offerVaultSave(serverName, newVars, yes) {
|
|
|
2492
3389
|
// src/core/installer.ts
|
|
2493
3390
|
async function loadClients2() {
|
|
2494
3391
|
try {
|
|
2495
|
-
const mod = await import("./client-detector-
|
|
3392
|
+
const mod = await import("./client-detector-O2HN4MUB.js");
|
|
2496
3393
|
return mod.getInstalledClients();
|
|
2497
3394
|
} catch {
|
|
2498
3395
|
return [];
|
|
@@ -2604,7 +3501,7 @@ async function installServer(input, options = {}) {
|
|
|
2604
3501
|
}
|
|
2605
3502
|
|
|
2606
3503
|
// src/utils/logger.ts
|
|
2607
|
-
import
|
|
3504
|
+
import pc14 from "picocolors";
|
|
2608
3505
|
var noColor = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
2609
3506
|
var isVerbose = process.argv.includes("--verbose");
|
|
2610
3507
|
var isJson = process.argv.includes("--json");
|
|
@@ -2613,18 +3510,18 @@ function colorize(fn, text2) {
|
|
|
2613
3510
|
}
|
|
2614
3511
|
function info(message) {
|
|
2615
3512
|
if (isJson) return;
|
|
2616
|
-
console.log(`${colorize(
|
|
3513
|
+
console.log(`${colorize(pc14.cyan, "i")} ${message}`);
|
|
2617
3514
|
}
|
|
2618
3515
|
function error(message) {
|
|
2619
3516
|
if (isJson) return;
|
|
2620
|
-
console.error(`${colorize(
|
|
3517
|
+
console.error(`${colorize(pc14.red, "\u2717")} ${message}`);
|
|
2621
3518
|
}
|
|
2622
3519
|
function json(data) {
|
|
2623
3520
|
console.log(JSON.stringify(data, null, 2));
|
|
2624
3521
|
}
|
|
2625
3522
|
|
|
2626
3523
|
// src/commands/install.ts
|
|
2627
|
-
var install_default =
|
|
3524
|
+
var install_default = defineCommand15({
|
|
2628
3525
|
meta: {
|
|
2629
3526
|
name: "install",
|
|
2630
3527
|
description: "Install an MCP server into one or more AI clients"
|
|
@@ -2686,23 +3583,23 @@ async function restoreFromLockfile() {
|
|
|
2686
3583
|
}
|
|
2687
3584
|
|
|
2688
3585
|
// src/commands/link.ts
|
|
2689
|
-
import
|
|
2690
|
-
import { defineCommand as
|
|
2691
|
-
import
|
|
3586
|
+
import path12 from "path";
|
|
3587
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
3588
|
+
import pc15 from "picocolors";
|
|
2692
3589
|
|
|
2693
3590
|
// src/core/link-service.ts
|
|
2694
|
-
import
|
|
2695
|
-
import
|
|
3591
|
+
import fs13 from "fs";
|
|
3592
|
+
import path11 from "path";
|
|
2696
3593
|
function detectLocalServer(dir) {
|
|
2697
|
-
if (!
|
|
3594
|
+
if (!fs13.existsSync(dir)) {
|
|
2698
3595
|
throw new Error(`Directory does not exist: ${dir}`);
|
|
2699
3596
|
}
|
|
2700
|
-
const pkgPath =
|
|
2701
|
-
if (
|
|
3597
|
+
const pkgPath = path11.join(dir, "package.json");
|
|
3598
|
+
if (fs13.existsSync(pkgPath)) {
|
|
2702
3599
|
return detectNodeServer(dir, pkgPath);
|
|
2703
3600
|
}
|
|
2704
|
-
const pyprojectPath =
|
|
2705
|
-
if (
|
|
3601
|
+
const pyprojectPath = path11.join(dir, "pyproject.toml");
|
|
3602
|
+
if (fs13.existsSync(pyprojectPath)) {
|
|
2706
3603
|
return detectPythonServer(dir, pyprojectPath);
|
|
2707
3604
|
}
|
|
2708
3605
|
throw new Error(
|
|
@@ -2710,28 +3607,28 @@ function detectLocalServer(dir) {
|
|
|
2710
3607
|
);
|
|
2711
3608
|
}
|
|
2712
3609
|
function detectNodeServer(dir, pkgPath) {
|
|
2713
|
-
const raw =
|
|
3610
|
+
const raw = fs13.readFileSync(pkgPath, "utf-8");
|
|
2714
3611
|
const pkg = JSON.parse(raw);
|
|
2715
|
-
const name = String(pkg.name ??
|
|
3612
|
+
const name = String(pkg.name ?? path11.basename(dir));
|
|
2716
3613
|
const version = String(pkg.version ?? "0.0.0");
|
|
2717
3614
|
let entryPoint = null;
|
|
2718
3615
|
if (pkg.bin) {
|
|
2719
3616
|
if (typeof pkg.bin === "string") {
|
|
2720
|
-
entryPoint =
|
|
3617
|
+
entryPoint = path11.resolve(dir, pkg.bin);
|
|
2721
3618
|
} else if (typeof pkg.bin === "object" && pkg.bin !== null) {
|
|
2722
3619
|
const binObj = pkg.bin;
|
|
2723
3620
|
const firstBin = Object.values(binObj)[0];
|
|
2724
|
-
if (firstBin) entryPoint =
|
|
3621
|
+
if (firstBin) entryPoint = path11.resolve(dir, firstBin);
|
|
2725
3622
|
}
|
|
2726
3623
|
}
|
|
2727
3624
|
if (!entryPoint && pkg.main) {
|
|
2728
|
-
entryPoint =
|
|
3625
|
+
entryPoint = path11.resolve(dir, String(pkg.main));
|
|
2729
3626
|
}
|
|
2730
3627
|
if (!entryPoint) {
|
|
2731
3628
|
const candidates = ["src/index.ts", "src/index.js", "index.ts", "index.js"];
|
|
2732
3629
|
for (const c of candidates) {
|
|
2733
|
-
const candidate =
|
|
2734
|
-
if (
|
|
3630
|
+
const candidate = path11.join(dir, c);
|
|
3631
|
+
if (fs13.existsSync(candidate)) {
|
|
2735
3632
|
entryPoint = candidate;
|
|
2736
3633
|
break;
|
|
2737
3634
|
}
|
|
@@ -2756,20 +3653,20 @@ function detectNodeServer(dir, pkgPath) {
|
|
|
2756
3653
|
return { name, version, command, args, envVars, absolutePath: dir, runtime: "node" };
|
|
2757
3654
|
}
|
|
2758
3655
|
function detectPythonServer(dir, pyprojectPath) {
|
|
2759
|
-
const raw =
|
|
2760
|
-
const name = extractTomlValue(raw, "name") ??
|
|
3656
|
+
const raw = fs13.readFileSync(pyprojectPath, "utf-8");
|
|
3657
|
+
const name = extractTomlValue(raw, "name") ?? path11.basename(dir);
|
|
2761
3658
|
const version = extractTomlValue(raw, "version") ?? "0.0.0";
|
|
2762
3659
|
let pythonCmd = "python3";
|
|
2763
|
-
const venvPython =
|
|
2764
|
-
if (
|
|
3660
|
+
const venvPython = path11.join(dir, ".venv", "bin", "python");
|
|
3661
|
+
if (fs13.existsSync(venvPython)) {
|
|
2765
3662
|
pythonCmd = venvPython;
|
|
2766
3663
|
}
|
|
2767
3664
|
const entryCandidate = [
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
].find((
|
|
2772
|
-
const entryPoint = entryCandidate ??
|
|
3665
|
+
path11.join(dir, "main.py"),
|
|
3666
|
+
path11.join(dir, name.replace(/-/g, "_"), "main.py"),
|
|
3667
|
+
path11.join(dir, "__main__.py")
|
|
3668
|
+
].find((p13) => fs13.existsSync(p13));
|
|
3669
|
+
const entryPoint = entryCandidate ?? path11.join(dir, "main.py");
|
|
2773
3670
|
return {
|
|
2774
3671
|
name,
|
|
2775
3672
|
version,
|
|
@@ -2811,13 +3708,13 @@ async function registerLinkedServer(linkResult, clients, lockfilePath, nameOverr
|
|
|
2811
3708
|
};
|
|
2812
3709
|
const existing = readLockfile(lockfilePath);
|
|
2813
3710
|
existing.servers[serverName] = lockEntry;
|
|
2814
|
-
const { writeLockfile: writeLockfile2 } = await import("./lockfile-
|
|
3711
|
+
const { writeLockfile: writeLockfile2 } = await import("./lockfile-ITEBE7HU.js");
|
|
2815
3712
|
writeLockfile2(existing, lockfilePath);
|
|
2816
3713
|
return registered;
|
|
2817
3714
|
}
|
|
2818
3715
|
|
|
2819
3716
|
// src/commands/link.ts
|
|
2820
|
-
var link_default =
|
|
3717
|
+
var link_default = defineCommand16({
|
|
2821
3718
|
meta: {
|
|
2822
3719
|
name: "link",
|
|
2823
3720
|
description: "Register a local MCP server directory with AI clients"
|
|
@@ -2843,42 +3740,42 @@ var link_default = defineCommand11({
|
|
|
2843
3740
|
const dirArg = args.dir ?? ".";
|
|
2844
3741
|
const clientFilter = args.client;
|
|
2845
3742
|
const nameOverride = args.name;
|
|
2846
|
-
const absoluteDir =
|
|
3743
|
+
const absoluteDir = path12.resolve(dirArg);
|
|
2847
3744
|
let linkResult;
|
|
2848
3745
|
try {
|
|
2849
3746
|
linkResult = detectLocalServer(absoluteDir);
|
|
2850
3747
|
} catch (err) {
|
|
2851
|
-
console.error(
|
|
3748
|
+
console.error(pc15.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2852
3749
|
process.exit(1);
|
|
2853
3750
|
}
|
|
2854
3751
|
const serverName = nameOverride ?? linkResult.name;
|
|
2855
|
-
console.log(
|
|
2856
|
-
Detected: ${
|
|
2857
|
-
console.log(
|
|
2858
|
-
console.log(
|
|
3752
|
+
console.log(pc15.dim(`
|
|
3753
|
+
Detected: ${pc15.cyan(serverName)} (${linkResult.runtime})`));
|
|
3754
|
+
console.log(pc15.dim(` Path: ${absoluteDir}`));
|
|
3755
|
+
console.log(pc15.dim(` Command: ${linkResult.command} ${linkResult.args.join(" ")}`));
|
|
2859
3756
|
const allClients = await getInstalledClients();
|
|
2860
3757
|
const clients = clientFilter ? allClients.filter((c) => c.type === clientFilter) : allClients;
|
|
2861
3758
|
if (clientFilter && clients.length === 0) {
|
|
2862
|
-
console.error(
|
|
3759
|
+
console.error(pc15.red(` Error: Unknown client '${clientFilter}'.`));
|
|
2863
3760
|
process.exit(1);
|
|
2864
3761
|
}
|
|
2865
3762
|
let registered;
|
|
2866
3763
|
try {
|
|
2867
3764
|
registered = await registerLinkedServer(linkResult, clients, void 0, nameOverride);
|
|
2868
3765
|
} catch (err) {
|
|
2869
|
-
console.error(
|
|
3766
|
+
console.error(pc15.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2870
3767
|
process.exit(1);
|
|
2871
3768
|
}
|
|
2872
3769
|
if (registered.length === 0) {
|
|
2873
|
-
console.log(
|
|
2874
|
-
console.log(
|
|
3770
|
+
console.log(pc15.yellow(" Warning: No clients registered. Are any AI clients installed?"));
|
|
3771
|
+
console.log(pc15.dim(` Server saved to lockfile with source "local".`));
|
|
2875
3772
|
} else {
|
|
2876
|
-
console.log(
|
|
2877
|
-
Linked ${
|
|
3773
|
+
console.log(pc15.green(`
|
|
3774
|
+
Linked ${pc15.bold(serverName)} to: ${registered.join(", ")}
|
|
2878
3775
|
`));
|
|
2879
|
-
console.log(
|
|
3776
|
+
console.log(pc15.dim(` Run ${pc15.cyan("mcpman list")} to verify.`));
|
|
2880
3777
|
console.log(
|
|
2881
|
-
|
|
3778
|
+
pc15.dim(` Run ${pc15.cyan(`mcpman watch ${serverName}`)} to start with auto-restart.`)
|
|
2882
3779
|
);
|
|
2883
3780
|
}
|
|
2884
3781
|
console.log();
|
|
@@ -2886,14 +3783,14 @@ var link_default = defineCommand11({
|
|
|
2886
3783
|
});
|
|
2887
3784
|
|
|
2888
3785
|
// src/commands/list.ts
|
|
2889
|
-
import { defineCommand as
|
|
2890
|
-
import
|
|
3786
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
3787
|
+
import pc16 from "picocolors";
|
|
2891
3788
|
var STATUS_ICON = {
|
|
2892
|
-
healthy:
|
|
2893
|
-
unhealthy:
|
|
2894
|
-
unknown:
|
|
3789
|
+
healthy: pc16.green("\u25CF"),
|
|
3790
|
+
unhealthy: pc16.red("\u25CF"),
|
|
3791
|
+
unknown: pc16.dim("\u25CB")
|
|
2895
3792
|
};
|
|
2896
|
-
var list_default =
|
|
3793
|
+
var list_default = defineCommand17({
|
|
2897
3794
|
meta: {
|
|
2898
3795
|
name: "list",
|
|
2899
3796
|
description: "List installed MCP servers"
|
|
@@ -2914,8 +3811,8 @@ var list_default = defineCommand12({
|
|
|
2914
3811
|
if (servers.length === 0) {
|
|
2915
3812
|
const filter = args.client ? ` for client "${args.client}"` : "";
|
|
2916
3813
|
console.log(
|
|
2917
|
-
|
|
2918
|
-
`No MCP servers installed${filter}. Run ${
|
|
3814
|
+
pc16.dim(
|
|
3815
|
+
`No MCP servers installed${filter}. Run ${pc16.cyan("mcpman install <server>")} to get started.`
|
|
2919
3816
|
)
|
|
2920
3817
|
);
|
|
2921
3818
|
return;
|
|
@@ -2941,9 +3838,9 @@ var list_default = defineCommand12({
|
|
|
2941
3838
|
const nameWidth = Math.max(4, ...withStatus.map((s) => s.name.length));
|
|
2942
3839
|
const clientsWidth = Math.max(7, ...withStatus.map((s) => formatClients(s.clients).length));
|
|
2943
3840
|
const header = ` ${pad("NAME", nameWidth)} ${pad("CLIENT(S)", clientsWidth)} ${pad("COMMAND", 20)} STATUS`;
|
|
2944
|
-
console.log(
|
|
3841
|
+
console.log(pc16.dim(header));
|
|
2945
3842
|
console.log(
|
|
2946
|
-
|
|
3843
|
+
pc16.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`)
|
|
2947
3844
|
);
|
|
2948
3845
|
for (const s of withStatus) {
|
|
2949
3846
|
const icon = STATUS_ICON[s.status];
|
|
@@ -2958,7 +3855,7 @@ var list_default = defineCommand12({
|
|
|
2958
3855
|
}
|
|
2959
3856
|
const clientSet = new Set(withStatus.flatMap((s) => s.clients));
|
|
2960
3857
|
console.log(
|
|
2961
|
-
|
|
3858
|
+
pc16.dim(
|
|
2962
3859
|
`
|
|
2963
3860
|
${withStatus.length} server${withStatus.length !== 1 ? "s" : ""} \xB7 ${clientSet.size} client${clientSet.size !== 1 ? "s" : ""}`
|
|
2964
3861
|
)
|
|
@@ -2971,21 +3868,21 @@ function pad(s, width) {
|
|
|
2971
3868
|
function truncate(s, max) {
|
|
2972
3869
|
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
2973
3870
|
}
|
|
2974
|
-
var
|
|
3871
|
+
var CLIENT_DISPLAY2 = {
|
|
2975
3872
|
"claude-desktop": "Claude",
|
|
2976
3873
|
cursor: "Cursor",
|
|
2977
3874
|
vscode: "VS Code",
|
|
2978
3875
|
windsurf: "Windsurf"
|
|
2979
3876
|
};
|
|
2980
3877
|
function formatClients(clients) {
|
|
2981
|
-
return clients.map((c) =>
|
|
3878
|
+
return clients.map((c) => CLIENT_DISPLAY2[c] ?? c).join(", ");
|
|
2982
3879
|
}
|
|
2983
3880
|
|
|
2984
3881
|
// src/commands/logs.ts
|
|
2985
|
-
import { spawn as
|
|
2986
|
-
import { defineCommand as
|
|
2987
|
-
import
|
|
2988
|
-
var logs_default =
|
|
3882
|
+
import { spawn as spawn4 } from "child_process";
|
|
3883
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
3884
|
+
import pc17 from "picocolors";
|
|
3885
|
+
var logs_default = defineCommand18({
|
|
2989
3886
|
meta: {
|
|
2990
3887
|
name: "logs",
|
|
2991
3888
|
description: "Stream stdout/stderr from an MCP server"
|
|
@@ -3008,7 +3905,7 @@ var logs_default = defineCommand13({
|
|
|
3008
3905
|
const lockfile = readLockfile();
|
|
3009
3906
|
const entry = lockfile.servers[serverName];
|
|
3010
3907
|
if (!entry) {
|
|
3011
|
-
console.error(
|
|
3908
|
+
console.error(pc17.red(` Error: Server '${serverName}' is not installed.`));
|
|
3012
3909
|
process.exit(1);
|
|
3013
3910
|
}
|
|
3014
3911
|
const lockfileEnv = parseEnvFlags(entry.envVars);
|
|
@@ -3018,24 +3915,24 @@ var logs_default = defineCommand13({
|
|
|
3018
3915
|
...lockfileEnv,
|
|
3019
3916
|
...vaultEnv
|
|
3020
3917
|
};
|
|
3021
|
-
console.log(
|
|
3918
|
+
console.log(pc17.dim(` Streaming logs for ${pc17.cyan(serverName)}... (Ctrl+C to stop)
|
|
3022
3919
|
`));
|
|
3023
|
-
const child =
|
|
3920
|
+
const child = spawn4(entry.command, entry.args, {
|
|
3024
3921
|
env: finalEnv,
|
|
3025
3922
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3026
3923
|
});
|
|
3027
3924
|
child.stdout?.on("data", (chunk) => {
|
|
3028
|
-
process.stdout.write(
|
|
3925
|
+
process.stdout.write(pc17.dim("[stdout] ") + chunk.toString());
|
|
3029
3926
|
});
|
|
3030
3927
|
child.stderr?.on("data", (chunk) => {
|
|
3031
|
-
process.stderr.write(
|
|
3928
|
+
process.stderr.write(pc17.yellow("[stderr] ") + chunk.toString());
|
|
3032
3929
|
});
|
|
3033
3930
|
child.on("error", (err) => {
|
|
3034
|
-
console.error(
|
|
3931
|
+
console.error(pc17.red(` Failed to start '${serverName}': ${err.message}`));
|
|
3035
3932
|
process.exit(1);
|
|
3036
3933
|
});
|
|
3037
3934
|
child.on("close", (code) => {
|
|
3038
|
-
console.log(
|
|
3935
|
+
console.log(pc17.dim(`
|
|
3039
3936
|
Process exited with code ${code ?? 0}`));
|
|
3040
3937
|
process.exit(code ?? 0);
|
|
3041
3938
|
});
|
|
@@ -3048,22 +3945,315 @@ var logs_default = defineCommand13({
|
|
|
3048
3945
|
});
|
|
3049
3946
|
}
|
|
3050
3947
|
});
|
|
3051
|
-
async function loadVaultSecrets(serverName) {
|
|
3052
|
-
try {
|
|
3053
|
-
const entries = listSecrets(serverName);
|
|
3054
|
-
if (entries.length === 0 || entries[0].keys.length === 0) return {};
|
|
3055
|
-
const password = await getMasterPassword();
|
|
3056
|
-
return getSecretsForServer(serverName, password);
|
|
3057
|
-
} catch {
|
|
3058
|
-
return {};
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3948
|
+
async function loadVaultSecrets(serverName) {
|
|
3949
|
+
try {
|
|
3950
|
+
const entries = listSecrets(serverName);
|
|
3951
|
+
if (entries.length === 0 || entries[0].keys.length === 0) return {};
|
|
3952
|
+
const password = await getMasterPassword();
|
|
3953
|
+
return getSecretsForServer(serverName, password);
|
|
3954
|
+
} catch {
|
|
3955
|
+
return {};
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
// src/commands/notify.ts
|
|
3960
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
3961
|
+
import pc18 from "picocolors";
|
|
3962
|
+
|
|
3963
|
+
// src/core/notify-service.ts
|
|
3964
|
+
import { execSync as execSync2 } from "child_process";
|
|
3965
|
+
import fs14 from "fs";
|
|
3966
|
+
function readHooks(file) {
|
|
3967
|
+
const target = file ?? getNotifyFile();
|
|
3968
|
+
if (!fs14.existsSync(target)) return [];
|
|
3969
|
+
try {
|
|
3970
|
+
const parsed = JSON.parse(fs14.readFileSync(target, "utf-8"));
|
|
3971
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
3972
|
+
} catch {
|
|
3973
|
+
return [];
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
function writeHooks(hooks, file) {
|
|
3977
|
+
const target = file ?? getNotifyFile();
|
|
3978
|
+
const dir = target.substring(0, target.lastIndexOf("/"));
|
|
3979
|
+
if (dir && !fs14.existsSync(dir)) {
|
|
3980
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
3981
|
+
}
|
|
3982
|
+
fs14.writeFileSync(target, JSON.stringify(hooks, null, 2), "utf-8");
|
|
3983
|
+
}
|
|
3984
|
+
function addHook(event, type, target, file) {
|
|
3985
|
+
const hooks = readHooks(file);
|
|
3986
|
+
hooks.push({ event, type, target });
|
|
3987
|
+
writeHooks(hooks, file);
|
|
3988
|
+
}
|
|
3989
|
+
function removeHook(index, file) {
|
|
3990
|
+
const hooks = readHooks(file);
|
|
3991
|
+
if (index < 0 || index >= hooks.length) {
|
|
3992
|
+
throw new Error(`Hook index ${index} out of range (0\u2013${hooks.length - 1})`);
|
|
3993
|
+
}
|
|
3994
|
+
hooks.splice(index, 1);
|
|
3995
|
+
writeHooks(hooks, file);
|
|
3996
|
+
}
|
|
3997
|
+
function listHooks(file) {
|
|
3998
|
+
return readHooks(file);
|
|
3999
|
+
}
|
|
4000
|
+
async function fireEvent(event, payload, file) {
|
|
4001
|
+
const hooks = readHooks(file).filter((h) => h.event === event);
|
|
4002
|
+
if (hooks.length === 0) return;
|
|
4003
|
+
const body = JSON.stringify({ event, ...payload });
|
|
4004
|
+
await Promise.allSettled(
|
|
4005
|
+
hooks.map(async (hook) => {
|
|
4006
|
+
if (hook.type === "webhook") {
|
|
4007
|
+
await fireWebhook(hook.target, body);
|
|
4008
|
+
} else {
|
|
4009
|
+
fireShell(hook.target, event, payload);
|
|
4010
|
+
}
|
|
4011
|
+
})
|
|
4012
|
+
);
|
|
4013
|
+
}
|
|
4014
|
+
async function fireWebhook(url, body) {
|
|
4015
|
+
const response = await fetch(url, {
|
|
4016
|
+
method: "POST",
|
|
4017
|
+
headers: { "Content-Type": "application/json" },
|
|
4018
|
+
body
|
|
4019
|
+
});
|
|
4020
|
+
if (!response.ok) {
|
|
4021
|
+
throw new Error(`Webhook ${url} returned ${response.status}`);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
function fireShell(command, event, payload) {
|
|
4025
|
+
const env = {
|
|
4026
|
+
...process.env,
|
|
4027
|
+
MCPMAN_EVENT: event,
|
|
4028
|
+
MCPMAN_PAYLOAD: JSON.stringify(payload)
|
|
4029
|
+
};
|
|
4030
|
+
execSync2(command, { stdio: "inherit", env });
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
// src/commands/notify.ts
|
|
4034
|
+
var VALID_EVENTS = ["install", "remove", "update", "health-fail"];
|
|
4035
|
+
var addCmd3 = defineCommand19({
|
|
4036
|
+
meta: { name: "add", description: "Add a webhook or shell hook for an event" },
|
|
4037
|
+
args: {
|
|
4038
|
+
event: {
|
|
4039
|
+
type: "string",
|
|
4040
|
+
description: `Event to hook (${VALID_EVENTS.join(", ")})`,
|
|
4041
|
+
required: true
|
|
4042
|
+
},
|
|
4043
|
+
webhook: {
|
|
4044
|
+
type: "string",
|
|
4045
|
+
description: "Webhook URL (POST JSON payload)"
|
|
4046
|
+
},
|
|
4047
|
+
shell: {
|
|
4048
|
+
type: "string",
|
|
4049
|
+
description: "Shell command to run"
|
|
4050
|
+
}
|
|
4051
|
+
},
|
|
4052
|
+
run({ args }) {
|
|
4053
|
+
const event = args.event;
|
|
4054
|
+
if (!VALID_EVENTS.includes(event)) {
|
|
4055
|
+
console.error(`${pc18.red("\u2717")} Invalid event "${event}". Valid: ${VALID_EVENTS.join(", ")}`);
|
|
4056
|
+
process.exit(1);
|
|
4057
|
+
}
|
|
4058
|
+
if (!args.webhook && !args.shell) {
|
|
4059
|
+
console.error(`${pc18.red("\u2717")} Provide --webhook <url> or --shell <command>`);
|
|
4060
|
+
process.exit(1);
|
|
4061
|
+
}
|
|
4062
|
+
const type = args.webhook ? "webhook" : "shell";
|
|
4063
|
+
const target = args.webhook ?? args.shell;
|
|
4064
|
+
addHook(event, type, target);
|
|
4065
|
+
const hooks = listHooks();
|
|
4066
|
+
const idx = hooks.length - 1;
|
|
4067
|
+
console.log(
|
|
4068
|
+
`${pc18.green("\u2713")} Hook [${pc18.cyan(String(idx))}] added: ${pc18.bold(event)} \u2192 ${pc18.dim(type)}:${target}`
|
|
4069
|
+
);
|
|
4070
|
+
}
|
|
4071
|
+
});
|
|
4072
|
+
var removeCmd2 = defineCommand19({
|
|
4073
|
+
meta: { name: "remove", description: "Remove a hook by index" },
|
|
4074
|
+
args: {
|
|
4075
|
+
index: { type: "positional", description: "Hook index (from `notify list`)", required: true }
|
|
4076
|
+
},
|
|
4077
|
+
run({ args }) {
|
|
4078
|
+
const idx = Number(args.index);
|
|
4079
|
+
if (Number.isNaN(idx) || idx < 0) {
|
|
4080
|
+
console.error(`${pc18.red("\u2717")} Invalid index "${args.index}".`);
|
|
4081
|
+
process.exit(1);
|
|
4082
|
+
}
|
|
4083
|
+
try {
|
|
4084
|
+
removeHook(idx);
|
|
4085
|
+
console.log(`${pc18.green("\u2713")} Hook [${idx}] removed`);
|
|
4086
|
+
} catch (err) {
|
|
4087
|
+
console.error(`${pc18.red("\u2717")} ${String(err)}`);
|
|
4088
|
+
process.exit(1);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
});
|
|
4092
|
+
var listCmd4 = defineCommand19({
|
|
4093
|
+
meta: { name: "list", description: "List all configured hooks" },
|
|
4094
|
+
args: {},
|
|
4095
|
+
run() {
|
|
4096
|
+
const hooks = listHooks();
|
|
4097
|
+
if (hooks.length === 0) {
|
|
4098
|
+
console.log(
|
|
4099
|
+
pc18.dim(
|
|
4100
|
+
"\n No hooks configured. Use `mcpman notify add --event <event> --webhook <url>`.\n"
|
|
4101
|
+
)
|
|
4102
|
+
);
|
|
4103
|
+
return;
|
|
4104
|
+
}
|
|
4105
|
+
console.log(pc18.bold("\n mcpman notify hooks\n"));
|
|
4106
|
+
console.log(pc18.dim(` ${"\u2500".repeat(60)}`));
|
|
4107
|
+
hooks.forEach((h, i) => {
|
|
4108
|
+
const typeLabel = h.type === "webhook" ? pc18.blue("webhook") : pc18.magenta("shell");
|
|
4109
|
+
console.log(
|
|
4110
|
+
` ${pc18.dim(`[${i}]`)} ${pc18.bold(h.event.padEnd(12))} ${typeLabel} ${pc18.dim(h.target)}`
|
|
4111
|
+
);
|
|
4112
|
+
});
|
|
4113
|
+
console.log(pc18.dim(` ${"\u2500".repeat(60)}
|
|
4114
|
+
`));
|
|
4115
|
+
}
|
|
4116
|
+
});
|
|
4117
|
+
var testCmd = defineCommand19({
|
|
4118
|
+
meta: { name: "test", description: "Fire a test event to all matching hooks" },
|
|
4119
|
+
args: {
|
|
4120
|
+
event: {
|
|
4121
|
+
type: "positional",
|
|
4122
|
+
description: `Event to test (${VALID_EVENTS.join(", ")})`,
|
|
4123
|
+
required: true
|
|
4124
|
+
}
|
|
4125
|
+
},
|
|
4126
|
+
async run({ args }) {
|
|
4127
|
+
const event = args.event;
|
|
4128
|
+
if (!VALID_EVENTS.includes(event)) {
|
|
4129
|
+
console.error(`${pc18.red("\u2717")} Invalid event "${event}". Valid: ${VALID_EVENTS.join(", ")}`);
|
|
4130
|
+
process.exit(1);
|
|
4131
|
+
}
|
|
4132
|
+
const hooks = listHooks().filter((h) => h.event === event);
|
|
4133
|
+
if (hooks.length === 0) {
|
|
4134
|
+
console.log(pc18.dim(`
|
|
4135
|
+
No hooks configured for event "${event}".
|
|
4136
|
+
`));
|
|
4137
|
+
return;
|
|
4138
|
+
}
|
|
4139
|
+
console.log(pc18.cyan(`
|
|
4140
|
+
Firing test event: ${pc18.bold(event)} (${hooks.length} hook(s))...
|
|
4141
|
+
`));
|
|
4142
|
+
try {
|
|
4143
|
+
await fireEvent(event, { test: true, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4144
|
+
console.log(`${pc18.green("\u2713")} Test event fired
|
|
4145
|
+
`);
|
|
4146
|
+
} catch (err) {
|
|
4147
|
+
console.error(`${pc18.red("\u2717")} ${String(err)}`);
|
|
4148
|
+
process.exit(1);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
});
|
|
4152
|
+
var notify_default = defineCommand19({
|
|
4153
|
+
meta: {
|
|
4154
|
+
name: "notify",
|
|
4155
|
+
description: "Manage event webhooks and shell hooks"
|
|
4156
|
+
},
|
|
4157
|
+
subCommands: {
|
|
4158
|
+
add: addCmd3,
|
|
4159
|
+
remove: removeCmd2,
|
|
4160
|
+
list: listCmd4,
|
|
4161
|
+
test: testCmd
|
|
4162
|
+
}
|
|
4163
|
+
});
|
|
4164
|
+
|
|
4165
|
+
// src/commands/pin.ts
|
|
4166
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
4167
|
+
import pc19 from "picocolors";
|
|
4168
|
+
var pin_default = defineCommand20({
|
|
4169
|
+
meta: {
|
|
4170
|
+
name: "pin",
|
|
4171
|
+
description: "Pin a server to a specific version"
|
|
4172
|
+
},
|
|
4173
|
+
args: {
|
|
4174
|
+
server: {
|
|
4175
|
+
type: "positional",
|
|
4176
|
+
description: "Server name to pin/unpin",
|
|
4177
|
+
required: false
|
|
4178
|
+
},
|
|
4179
|
+
version: {
|
|
4180
|
+
type: "positional",
|
|
4181
|
+
description: "Version to pin to (defaults to currently installed version)",
|
|
4182
|
+
required: false
|
|
4183
|
+
},
|
|
4184
|
+
unpin: {
|
|
4185
|
+
type: "boolean",
|
|
4186
|
+
description: "Remove the pin for a server",
|
|
4187
|
+
default: false
|
|
4188
|
+
},
|
|
4189
|
+
list: {
|
|
4190
|
+
type: "boolean",
|
|
4191
|
+
description: "List all pinned servers",
|
|
4192
|
+
default: false
|
|
4193
|
+
}
|
|
4194
|
+
},
|
|
4195
|
+
run({ args }) {
|
|
4196
|
+
if (args.list) {
|
|
4197
|
+
const pins = listPins();
|
|
4198
|
+
if (pins.length === 0) {
|
|
4199
|
+
console.log(pc19.dim("No servers are pinned."));
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
console.log(`
|
|
4203
|
+
${pc19.bold("Pinned servers")}
|
|
4204
|
+
`);
|
|
4205
|
+
for (const { server, version: version2 } of pins) {
|
|
4206
|
+
console.log(` ${pc19.cyan(pc19.bold(server))} ${pc19.dim("@")}${pc19.green(version2)}`);
|
|
4207
|
+
}
|
|
4208
|
+
console.log("");
|
|
4209
|
+
return;
|
|
4210
|
+
}
|
|
4211
|
+
if (args.unpin) {
|
|
4212
|
+
if (!args.server) {
|
|
4213
|
+
console.error(`${pc19.red("\u2717")} Specify a server name to unpin.`);
|
|
4214
|
+
process.exit(1);
|
|
4215
|
+
}
|
|
4216
|
+
if (!isPinned(args.server)) {
|
|
4217
|
+
console.log(pc19.dim(`"${args.server}" is not pinned.`));
|
|
4218
|
+
return;
|
|
4219
|
+
}
|
|
4220
|
+
unpinServer(args.server);
|
|
4221
|
+
console.log(`${pc19.green("\u2713")} Unpinned ${pc19.cyan(args.server)}`);
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
if (!args.server) {
|
|
4225
|
+
console.error(`${pc19.red("\u2717")} Specify a server name to pin. Use --list to see pins.`);
|
|
4226
|
+
process.exit(1);
|
|
4227
|
+
}
|
|
4228
|
+
let version = args.version;
|
|
4229
|
+
if (!version) {
|
|
4230
|
+
const lockfile = readLockfile();
|
|
4231
|
+
version = lockfile.servers[args.server]?.version;
|
|
4232
|
+
if (!version) {
|
|
4233
|
+
console.error(
|
|
4234
|
+
`${pc19.red("\u2717")} "${args.server}" not found in lockfile. Specify a version explicitly.`
|
|
4235
|
+
);
|
|
4236
|
+
process.exit(1);
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
pinServer(args.server, version);
|
|
4240
|
+
const prev = getPinnedVersion(args.server);
|
|
4241
|
+
if (prev && prev !== version) {
|
|
4242
|
+
console.log(
|
|
4243
|
+
`${pc19.green("\u2713")} Re-pinned ${pc19.cyan(args.server)} ${pc19.dim(prev)} \u2192 ${pc19.green(version)}`
|
|
4244
|
+
);
|
|
4245
|
+
} else {
|
|
4246
|
+
console.log(`${pc19.green("\u2713")} Pinned ${pc19.cyan(args.server)} @ ${pc19.green(version)}`);
|
|
4247
|
+
}
|
|
4248
|
+
console.log(pc19.dim(" Update notifications will be suppressed for this server."));
|
|
4249
|
+
}
|
|
4250
|
+
});
|
|
3061
4251
|
|
|
3062
4252
|
// src/commands/plugin.ts
|
|
3063
|
-
import { defineCommand as
|
|
4253
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
3064
4254
|
import { createSpinner as createSpinner3 } from "nanospinner";
|
|
3065
|
-
import
|
|
3066
|
-
var addCommand =
|
|
4255
|
+
import pc20 from "picocolors";
|
|
4256
|
+
var addCommand = defineCommand21({
|
|
3067
4257
|
meta: { name: "add", description: "Install a plugin package" },
|
|
3068
4258
|
args: {
|
|
3069
4259
|
package: {
|
|
@@ -3081,23 +4271,23 @@ var addCommand = defineCommand14({
|
|
|
3081
4271
|
spinner5.stop();
|
|
3082
4272
|
if (loaded) {
|
|
3083
4273
|
console.log(
|
|
3084
|
-
`${
|
|
4274
|
+
`${pc20.green("\u2713")} Plugin ${pc20.bold(loaded.name)} installed (prefix: ${pc20.cyan(loaded.prefix)})`
|
|
3085
4275
|
);
|
|
3086
4276
|
} else {
|
|
3087
4277
|
console.log(
|
|
3088
|
-
`${
|
|
4278
|
+
`${pc20.yellow("\u26A0")} Package ${pc20.bold(pkg)} installed but does not export a valid mcpman plugin.`
|
|
3089
4279
|
);
|
|
3090
4280
|
}
|
|
3091
4281
|
} catch (err) {
|
|
3092
4282
|
spinner5.stop();
|
|
3093
4283
|
console.error(
|
|
3094
|
-
`${
|
|
4284
|
+
`${pc20.red("\u2717")} Failed to install plugin: ${err instanceof Error ? err.message : String(err)}`
|
|
3095
4285
|
);
|
|
3096
4286
|
process.exit(1);
|
|
3097
4287
|
}
|
|
3098
4288
|
}
|
|
3099
4289
|
});
|
|
3100
|
-
var removeCommand =
|
|
4290
|
+
var removeCommand = defineCommand21({
|
|
3101
4291
|
meta: { name: "remove", description: "Uninstall a plugin package" },
|
|
3102
4292
|
args: {
|
|
3103
4293
|
package: {
|
|
@@ -3110,46 +4300,46 @@ var removeCommand = defineCommand14({
|
|
|
3110
4300
|
const pkg = args.package;
|
|
3111
4301
|
const installed = listPluginPackages();
|
|
3112
4302
|
if (!installed.includes(pkg)) {
|
|
3113
|
-
console.log(
|
|
4303
|
+
console.log(pc20.dim(`Plugin "${pkg}" is not installed.`));
|
|
3114
4304
|
return;
|
|
3115
4305
|
}
|
|
3116
4306
|
try {
|
|
3117
4307
|
removePluginPackage(pkg);
|
|
3118
|
-
console.log(`${
|
|
4308
|
+
console.log(`${pc20.green("\u2713")} Plugin ${pc20.bold(pkg)} removed.`);
|
|
3119
4309
|
} catch (err) {
|
|
3120
4310
|
console.error(
|
|
3121
|
-
`${
|
|
4311
|
+
`${pc20.red("\u2717")} Failed to remove plugin: ${err instanceof Error ? err.message : String(err)}`
|
|
3122
4312
|
);
|
|
3123
4313
|
process.exit(1);
|
|
3124
4314
|
}
|
|
3125
4315
|
}
|
|
3126
4316
|
});
|
|
3127
|
-
var listCommand2 =
|
|
4317
|
+
var listCommand2 = defineCommand21({
|
|
3128
4318
|
meta: { name: "list", description: "List installed plugins" },
|
|
3129
4319
|
run() {
|
|
3130
4320
|
const packages = listPluginPackages();
|
|
3131
4321
|
if (packages.length === 0) {
|
|
3132
|
-
console.log(
|
|
4322
|
+
console.log(pc20.dim("No plugins installed. Use `mcpman plugin add <package>`."));
|
|
3133
4323
|
return;
|
|
3134
4324
|
}
|
|
3135
4325
|
console.log("");
|
|
3136
|
-
console.log(
|
|
4326
|
+
console.log(pc20.bold("Installed plugins:"));
|
|
3137
4327
|
console.log("");
|
|
3138
4328
|
for (const pkg of packages) {
|
|
3139
4329
|
const loaded = loadPlugin(pkg);
|
|
3140
4330
|
if (loaded) {
|
|
3141
4331
|
console.log(
|
|
3142
|
-
` ${
|
|
4332
|
+
` ${pc20.green("\u25CF")} ${pc20.bold(loaded.name)} prefix: ${pc20.cyan(loaded.prefix)} pkg: ${pc20.dim(pkg)}`
|
|
3143
4333
|
);
|
|
3144
4334
|
} else {
|
|
3145
|
-
console.log(` ${
|
|
4335
|
+
console.log(` ${pc20.yellow("\u25CF")} ${pc20.dim(pkg)} ${pc20.yellow("(failed to load)")}`);
|
|
3146
4336
|
}
|
|
3147
4337
|
}
|
|
3148
4338
|
console.log("");
|
|
3149
|
-
console.log(
|
|
4339
|
+
console.log(pc20.dim(` ${packages.length} plugin${packages.length !== 1 ? "s" : ""} installed`));
|
|
3150
4340
|
}
|
|
3151
4341
|
});
|
|
3152
|
-
var plugin_default =
|
|
4342
|
+
var plugin_default = defineCommand21({
|
|
3153
4343
|
meta: {
|
|
3154
4344
|
name: "plugin",
|
|
3155
4345
|
description: "Manage mcpman plugins for custom registries"
|
|
@@ -3162,22 +4352,22 @@ var plugin_default = defineCommand14({
|
|
|
3162
4352
|
});
|
|
3163
4353
|
|
|
3164
4354
|
// src/commands/profiles.ts
|
|
3165
|
-
import { defineCommand as
|
|
3166
|
-
import
|
|
4355
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
4356
|
+
import pc21 from "picocolors";
|
|
3167
4357
|
|
|
3168
4358
|
// src/core/profile-service.ts
|
|
3169
|
-
import
|
|
3170
|
-
import
|
|
4359
|
+
import fs15 from "fs";
|
|
4360
|
+
import path13 from "path";
|
|
3171
4361
|
function ensureDir(dir = getProfilesDir()) {
|
|
3172
|
-
|
|
4362
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
3173
4363
|
}
|
|
3174
4364
|
function profilePath(name, dir = getProfilesDir()) {
|
|
3175
|
-
return
|
|
4365
|
+
return path13.join(dir, `${name}.json`);
|
|
3176
4366
|
}
|
|
3177
4367
|
function createProfile(name, description = "", dir = getProfilesDir()) {
|
|
3178
4368
|
ensureDir(dir);
|
|
3179
4369
|
const filePath = profilePath(name, dir);
|
|
3180
|
-
if (
|
|
4370
|
+
if (fs15.existsSync(filePath)) {
|
|
3181
4371
|
throw new Error(`Profile '${name}' already exists. Delete it first or use a different name.`);
|
|
3182
4372
|
}
|
|
3183
4373
|
const lockfile = readLockfile();
|
|
@@ -3187,16 +4377,16 @@ function createProfile(name, description = "", dir = getProfilesDir()) {
|
|
|
3187
4377
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3188
4378
|
servers: lockfile.servers
|
|
3189
4379
|
};
|
|
3190
|
-
|
|
4380
|
+
fs15.writeFileSync(filePath, JSON.stringify(profile, null, 2), "utf-8");
|
|
3191
4381
|
return profile;
|
|
3192
4382
|
}
|
|
3193
4383
|
function listProfiles(dir = getProfilesDir()) {
|
|
3194
4384
|
ensureDir(dir);
|
|
3195
|
-
const files =
|
|
4385
|
+
const files = fs15.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
3196
4386
|
const profiles = [];
|
|
3197
4387
|
for (const file of files) {
|
|
3198
4388
|
try {
|
|
3199
|
-
const raw =
|
|
4389
|
+
const raw = fs15.readFileSync(path13.join(dir, file), "utf-8");
|
|
3200
4390
|
const data = JSON.parse(raw);
|
|
3201
4391
|
profiles.push(data);
|
|
3202
4392
|
} catch {
|
|
@@ -3206,9 +4396,9 @@ function listProfiles(dir = getProfilesDir()) {
|
|
|
3206
4396
|
}
|
|
3207
4397
|
function loadProfile(name, dir = getProfilesDir()) {
|
|
3208
4398
|
const filePath = profilePath(name, dir);
|
|
3209
|
-
if (!
|
|
4399
|
+
if (!fs15.existsSync(filePath)) return null;
|
|
3210
4400
|
try {
|
|
3211
|
-
const raw =
|
|
4401
|
+
const raw = fs15.readFileSync(filePath, "utf-8");
|
|
3212
4402
|
return JSON.parse(raw);
|
|
3213
4403
|
} catch {
|
|
3214
4404
|
return null;
|
|
@@ -3216,13 +4406,13 @@ function loadProfile(name, dir = getProfilesDir()) {
|
|
|
3216
4406
|
}
|
|
3217
4407
|
function deleteProfile(name, dir = getProfilesDir()) {
|
|
3218
4408
|
const filePath = profilePath(name, dir);
|
|
3219
|
-
if (!
|
|
3220
|
-
|
|
4409
|
+
if (!fs15.existsSync(filePath)) return false;
|
|
4410
|
+
fs15.unlinkSync(filePath);
|
|
3221
4411
|
return true;
|
|
3222
4412
|
}
|
|
3223
4413
|
|
|
3224
4414
|
// src/commands/profiles.ts
|
|
3225
|
-
var profiles_default =
|
|
4415
|
+
var profiles_default = defineCommand22({
|
|
3226
4416
|
meta: {
|
|
3227
4417
|
name: "profiles",
|
|
3228
4418
|
description: "Manage named server configuration profiles"
|
|
@@ -3251,16 +4441,16 @@ var profiles_default = defineCommand15({
|
|
|
3251
4441
|
case "create": {
|
|
3252
4442
|
if (!name) {
|
|
3253
4443
|
console.error(
|
|
3254
|
-
|
|
4444
|
+
pc21.red(" Error: Profile name required. Usage: mcpman profiles create <name>")
|
|
3255
4445
|
);
|
|
3256
4446
|
process.exit(1);
|
|
3257
4447
|
}
|
|
3258
4448
|
try {
|
|
3259
4449
|
const profile = createProfile(name, args.description ?? "");
|
|
3260
4450
|
const count = Object.keys(profile.servers).length;
|
|
3261
|
-
console.log(
|
|
4451
|
+
console.log(pc21.green(` \u2713 Profile '${name}' created with ${count} server(s).`));
|
|
3262
4452
|
} catch (err) {
|
|
3263
|
-
console.error(
|
|
4453
|
+
console.error(pc21.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3264
4454
|
process.exit(1);
|
|
3265
4455
|
}
|
|
3266
4456
|
break;
|
|
@@ -3268,38 +4458,38 @@ var profiles_default = defineCommand15({
|
|
|
3268
4458
|
case "switch": {
|
|
3269
4459
|
if (!name) {
|
|
3270
4460
|
console.error(
|
|
3271
|
-
|
|
4461
|
+
pc21.red(" Error: Profile name required. Usage: mcpman profiles switch <name>")
|
|
3272
4462
|
);
|
|
3273
4463
|
process.exit(1);
|
|
3274
4464
|
}
|
|
3275
4465
|
const profile = loadProfile(name);
|
|
3276
4466
|
if (!profile) {
|
|
3277
|
-
console.error(
|
|
4467
|
+
console.error(pc21.red(` Error: Profile '${name}' not found.`));
|
|
3278
4468
|
process.exit(1);
|
|
3279
4469
|
}
|
|
3280
4470
|
const lockData = { lockfileVersion: 1, servers: profile.servers };
|
|
3281
4471
|
writeLockfile(lockData);
|
|
3282
4472
|
const count = Object.keys(profile.servers).length;
|
|
3283
|
-
console.log(
|
|
3284
|
-
console.log(
|
|
4473
|
+
console.log(pc21.green(` \u2713 Switched to profile '${name}' (${count} servers).`));
|
|
4474
|
+
console.log(pc21.dim(" Run mcpman sync to apply to all clients."));
|
|
3285
4475
|
break;
|
|
3286
4476
|
}
|
|
3287
4477
|
case "list": {
|
|
3288
4478
|
const profiles = listProfiles();
|
|
3289
4479
|
if (profiles.length === 0) {
|
|
3290
4480
|
console.log(
|
|
3291
|
-
|
|
4481
|
+
pc21.dim(" No profiles saved. Create one with: mcpman profiles create <name>")
|
|
3292
4482
|
);
|
|
3293
4483
|
return;
|
|
3294
4484
|
}
|
|
3295
|
-
console.log(
|
|
4485
|
+
console.log(pc21.bold(`
|
|
3296
4486
|
Profiles (${profiles.length})
|
|
3297
4487
|
`));
|
|
3298
|
-
for (const
|
|
3299
|
-
const count = Object.keys(
|
|
3300
|
-
const desc =
|
|
4488
|
+
for (const p13 of profiles) {
|
|
4489
|
+
const count = Object.keys(p13.servers).length;
|
|
4490
|
+
const desc = p13.description ? pc21.dim(` \u2014 ${p13.description}`) : "";
|
|
3301
4491
|
console.log(
|
|
3302
|
-
` ${
|
|
4492
|
+
` ${pc21.cyan("\u25CF")} ${pc21.bold(p13.name)} ${pc21.dim(`${count} server(s)`)}${desc}`
|
|
3303
4493
|
);
|
|
3304
4494
|
}
|
|
3305
4495
|
console.log();
|
|
@@ -3308,22 +4498,22 @@ var profiles_default = defineCommand15({
|
|
|
3308
4498
|
case "delete": {
|
|
3309
4499
|
if (!name) {
|
|
3310
4500
|
console.error(
|
|
3311
|
-
|
|
4501
|
+
pc21.red(" Error: Profile name required. Usage: mcpman profiles delete <name>")
|
|
3312
4502
|
);
|
|
3313
4503
|
process.exit(1);
|
|
3314
4504
|
}
|
|
3315
4505
|
const deleted = deleteProfile(name);
|
|
3316
4506
|
if (deleted) {
|
|
3317
|
-
console.log(
|
|
4507
|
+
console.log(pc21.green(` \u2713 Profile '${name}' deleted.`));
|
|
3318
4508
|
} else {
|
|
3319
|
-
console.error(
|
|
4509
|
+
console.error(pc21.red(` Error: Profile '${name}' not found.`));
|
|
3320
4510
|
process.exit(1);
|
|
3321
4511
|
}
|
|
3322
4512
|
break;
|
|
3323
4513
|
}
|
|
3324
4514
|
default:
|
|
3325
4515
|
console.error(
|
|
3326
|
-
|
|
4516
|
+
pc21.red(` Unknown action '${action}'. Use: create, switch, list, or delete.`)
|
|
3327
4517
|
);
|
|
3328
4518
|
process.exit(1);
|
|
3329
4519
|
}
|
|
@@ -3331,8 +4521,8 @@ var profiles_default = defineCommand15({
|
|
|
3331
4521
|
});
|
|
3332
4522
|
|
|
3333
4523
|
// src/commands/registry.ts
|
|
3334
|
-
import { defineCommand as
|
|
3335
|
-
import
|
|
4524
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
4525
|
+
import pc22 from "picocolors";
|
|
3336
4526
|
|
|
3337
4527
|
// src/core/registry-manager.ts
|
|
3338
4528
|
var BUILTIN_REGISTRIES = [
|
|
@@ -3402,7 +4592,7 @@ function getDefaultRegistry(configPath) {
|
|
|
3402
4592
|
}
|
|
3403
4593
|
|
|
3404
4594
|
// src/commands/registry.ts
|
|
3405
|
-
var registry_default =
|
|
4595
|
+
var registry_default = defineCommand23({
|
|
3406
4596
|
meta: {
|
|
3407
4597
|
name: "registry",
|
|
3408
4598
|
description: "Manage custom registry URLs"
|
|
@@ -3432,67 +4622,67 @@ var registry_default = defineCommand16({
|
|
|
3432
4622
|
case "list": {
|
|
3433
4623
|
const registries = getRegistries();
|
|
3434
4624
|
const defaultName = getDefaultRegistry();
|
|
3435
|
-
console.log(
|
|
4625
|
+
console.log(pc22.bold("\n Registries\n"));
|
|
3436
4626
|
for (const r of registries) {
|
|
3437
4627
|
const isDefault = r.name === defaultName;
|
|
3438
|
-
const defaultTag = isDefault ?
|
|
3439
|
-
const builtinTag = r.builtin ?
|
|
4628
|
+
const defaultTag = isDefault ? pc22.green(" (default)") : "";
|
|
4629
|
+
const builtinTag = r.builtin ? pc22.dim(" [builtin]") : "";
|
|
3440
4630
|
console.log(
|
|
3441
|
-
` ${isDefault ?
|
|
4631
|
+
` ${isDefault ? pc22.green("\u25CF") : pc22.dim("\u25CB")} ${pc22.bold(r.name)}${defaultTag}${builtinTag}`
|
|
3442
4632
|
);
|
|
3443
|
-
console.log(` ${
|
|
4633
|
+
console.log(` ${pc22.dim(r.url)}`);
|
|
3444
4634
|
}
|
|
3445
4635
|
console.log();
|
|
3446
4636
|
break;
|
|
3447
4637
|
}
|
|
3448
4638
|
case "add": {
|
|
3449
4639
|
if (!name) {
|
|
3450
|
-
console.error(
|
|
4640
|
+
console.error(pc22.red(" Error: Usage: mcpman registry add <name> <url>"));
|
|
3451
4641
|
process.exit(1);
|
|
3452
4642
|
}
|
|
3453
4643
|
if (!url) {
|
|
3454
|
-
console.error(
|
|
4644
|
+
console.error(pc22.red(" Error: Usage: mcpman registry add <name> <url>"));
|
|
3455
4645
|
process.exit(1);
|
|
3456
4646
|
}
|
|
3457
4647
|
try {
|
|
3458
4648
|
addRegistry(name, url);
|
|
3459
|
-
console.log(
|
|
4649
|
+
console.log(pc22.green(` Added registry '${name}' \u2192 ${url}`));
|
|
3460
4650
|
} catch (err) {
|
|
3461
|
-
console.error(
|
|
4651
|
+
console.error(pc22.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3462
4652
|
process.exit(1);
|
|
3463
4653
|
}
|
|
3464
4654
|
break;
|
|
3465
4655
|
}
|
|
3466
4656
|
case "remove": {
|
|
3467
4657
|
if (!name) {
|
|
3468
|
-
console.error(
|
|
4658
|
+
console.error(pc22.red(" Error: Usage: mcpman registry remove <name>"));
|
|
3469
4659
|
process.exit(1);
|
|
3470
4660
|
}
|
|
3471
4661
|
try {
|
|
3472
4662
|
removeRegistry(name);
|
|
3473
|
-
console.log(
|
|
4663
|
+
console.log(pc22.green(` Removed registry '${name}'.`));
|
|
3474
4664
|
} catch (err) {
|
|
3475
|
-
console.error(
|
|
4665
|
+
console.error(pc22.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3476
4666
|
process.exit(1);
|
|
3477
4667
|
}
|
|
3478
4668
|
break;
|
|
3479
4669
|
}
|
|
3480
4670
|
case "set-default": {
|
|
3481
4671
|
if (!name) {
|
|
3482
|
-
console.error(
|
|
4672
|
+
console.error(pc22.red(" Error: Usage: mcpman registry set-default <name>"));
|
|
3483
4673
|
process.exit(1);
|
|
3484
4674
|
}
|
|
3485
4675
|
try {
|
|
3486
4676
|
setDefaultRegistry(name);
|
|
3487
|
-
console.log(
|
|
4677
|
+
console.log(pc22.green(` Default registry set to '${name}'.`));
|
|
3488
4678
|
} catch (err) {
|
|
3489
|
-
console.error(
|
|
4679
|
+
console.error(pc22.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
3490
4680
|
process.exit(1);
|
|
3491
4681
|
}
|
|
3492
4682
|
break;
|
|
3493
4683
|
}
|
|
3494
4684
|
default:
|
|
3495
|
-
console.error(
|
|
4685
|
+
console.error(pc22.red(` Unknown action '${action}'. Use: list, add, remove, set-default.`));
|
|
3496
4686
|
process.exit(1);
|
|
3497
4687
|
}
|
|
3498
4688
|
}
|
|
@@ -3500,18 +4690,18 @@ var registry_default = defineCommand16({
|
|
|
3500
4690
|
|
|
3501
4691
|
// src/commands/remove.ts
|
|
3502
4692
|
import * as p8 from "@clack/prompts";
|
|
3503
|
-
import { defineCommand as
|
|
3504
|
-
import
|
|
3505
|
-
var
|
|
4693
|
+
import { defineCommand as defineCommand24 } from "citty";
|
|
4694
|
+
import pc23 from "picocolors";
|
|
4695
|
+
var CLIENT_DISPLAY3 = {
|
|
3506
4696
|
"claude-desktop": "Claude",
|
|
3507
4697
|
cursor: "Cursor",
|
|
3508
4698
|
vscode: "VS Code",
|
|
3509
4699
|
windsurf: "Windsurf"
|
|
3510
4700
|
};
|
|
3511
4701
|
function clientDisplayName(type) {
|
|
3512
|
-
return
|
|
4702
|
+
return CLIENT_DISPLAY3[type] ?? type;
|
|
3513
4703
|
}
|
|
3514
|
-
var remove_default =
|
|
4704
|
+
var remove_default = defineCommand24({
|
|
3515
4705
|
meta: {
|
|
3516
4706
|
name: "remove",
|
|
3517
4707
|
description: "Remove an MCP server from one or more AI clients"
|
|
@@ -3538,7 +4728,7 @@ var remove_default = defineCommand17({
|
|
|
3538
4728
|
}
|
|
3539
4729
|
},
|
|
3540
4730
|
async run({ args }) {
|
|
3541
|
-
p8.intro(
|
|
4731
|
+
p8.intro(pc23.bold("mcpman remove"));
|
|
3542
4732
|
const serverName = args.server;
|
|
3543
4733
|
const servers = await getInstalledServers();
|
|
3544
4734
|
const match = servers.find((s) => s.name === serverName);
|
|
@@ -3548,7 +4738,7 @@ var remove_default = defineCommand17({
|
|
|
3548
4738
|
(s) => s.name.includes(serverName) || serverName.includes(s.name)
|
|
3549
4739
|
);
|
|
3550
4740
|
if (similar.length > 0) {
|
|
3551
|
-
p8.log.info(`Did you mean: ${similar.map((s) =>
|
|
4741
|
+
p8.log.info(`Did you mean: ${similar.map((s) => pc23.cyan(s.name)).join(", ")}?`);
|
|
3552
4742
|
}
|
|
3553
4743
|
p8.outro("Nothing to remove.");
|
|
3554
4744
|
return;
|
|
@@ -3583,7 +4773,7 @@ var remove_default = defineCommand17({
|
|
|
3583
4773
|
if (!args.yes) {
|
|
3584
4774
|
const clientNames = targetClients.map(clientDisplayName).join(", ");
|
|
3585
4775
|
const confirmed = await p8.confirm({
|
|
3586
|
-
message: `Remove ${
|
|
4776
|
+
message: `Remove ${pc23.cyan(serverName)} from ${pc23.yellow(clientNames)}?`
|
|
3587
4777
|
});
|
|
3588
4778
|
if (p8.isCancel(confirmed) || !confirmed) {
|
|
3589
4779
|
p8.outro("Cancelled.");
|
|
@@ -3608,18 +4798,222 @@ var remove_default = defineCommand17({
|
|
|
3608
4798
|
}
|
|
3609
4799
|
if (errors.length > 0) {
|
|
3610
4800
|
for (const e of errors) p8.log.error(e);
|
|
3611
|
-
p8.outro(
|
|
4801
|
+
p8.outro(pc23.red("Completed with errors."));
|
|
4802
|
+
process.exit(1);
|
|
4803
|
+
}
|
|
4804
|
+
p8.outro(pc23.green(`Removed "${serverName}" successfully.`));
|
|
4805
|
+
}
|
|
4806
|
+
});
|
|
4807
|
+
|
|
4808
|
+
// src/commands/replay.ts
|
|
4809
|
+
import { defineCommand as defineCommand25 } from "citty";
|
|
4810
|
+
import pc24 from "picocolors";
|
|
4811
|
+
|
|
4812
|
+
// src/core/history-service.ts
|
|
4813
|
+
import { execSync as execSync3 } from "child_process";
|
|
4814
|
+
import fs16 from "fs";
|
|
4815
|
+
function readHistory(file) {
|
|
4816
|
+
const target = file ?? getHistoryFile();
|
|
4817
|
+
if (!fs16.existsSync(target)) return [];
|
|
4818
|
+
try {
|
|
4819
|
+
const parsed = JSON.parse(fs16.readFileSync(target, "utf-8"));
|
|
4820
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
4821
|
+
} catch {
|
|
4822
|
+
return [];
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
function getHistory(file) {
|
|
4826
|
+
return readHistory(file);
|
|
4827
|
+
}
|
|
4828
|
+
function replayCommand(index, file) {
|
|
4829
|
+
const history = readHistory(file);
|
|
4830
|
+
if (history.length === 0) {
|
|
4831
|
+
throw new Error("History is empty");
|
|
4832
|
+
}
|
|
4833
|
+
const reversed = [...history].reverse();
|
|
4834
|
+
if (index < 0 || index >= reversed.length) {
|
|
4835
|
+
throw new Error(`Index ${index} out of range (0\u2013${reversed.length - 1})`);
|
|
4836
|
+
}
|
|
4837
|
+
const entry = reversed[index];
|
|
4838
|
+
const fullCommand = ["mcpman", entry.command, ...entry.args].filter(Boolean).join(" ");
|
|
4839
|
+
execSync3(fullCommand, { stdio: "inherit" });
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
// src/commands/replay.ts
|
|
4843
|
+
var replay_default = defineCommand25({
|
|
4844
|
+
meta: {
|
|
4845
|
+
name: "replay",
|
|
4846
|
+
description: "Replay a previous mcpman command from history"
|
|
4847
|
+
},
|
|
4848
|
+
args: {
|
|
4849
|
+
index: {
|
|
4850
|
+
type: "positional",
|
|
4851
|
+
description: "History index to replay (0 = most recent)",
|
|
4852
|
+
required: false
|
|
4853
|
+
},
|
|
4854
|
+
list: {
|
|
4855
|
+
type: "boolean",
|
|
4856
|
+
description: "Show last 20 history entries",
|
|
4857
|
+
default: false
|
|
4858
|
+
}
|
|
4859
|
+
},
|
|
4860
|
+
run({ args }) {
|
|
4861
|
+
const history = getHistory();
|
|
4862
|
+
if (args.list || args.index === void 0) {
|
|
4863
|
+
if (history.length === 0) {
|
|
4864
|
+
console.log(pc24.dim("\n No command history found.\n"));
|
|
4865
|
+
return;
|
|
4866
|
+
}
|
|
4867
|
+
const reversed2 = [...history].reverse().slice(0, 20);
|
|
4868
|
+
console.log(pc24.bold("\n mcpman history\n"));
|
|
4869
|
+
console.log(pc24.dim(` ${"\u2500".repeat(50)}`));
|
|
4870
|
+
reversed2.forEach((entry2, i) => {
|
|
4871
|
+
const cmdParts2 = [entry2.command, ...entry2.args].filter(Boolean).join(" ");
|
|
4872
|
+
const ts = new Date(entry2.timestamp).toLocaleString();
|
|
4873
|
+
console.log(` ${pc24.cyan(String(i).padStart(2))} ${pc24.bold(cmdParts2)} ${pc24.dim(ts)}`);
|
|
4874
|
+
});
|
|
4875
|
+
console.log(pc24.dim(` ${"\u2500".repeat(50)}
|
|
4876
|
+
`));
|
|
4877
|
+
if (!args.list) {
|
|
4878
|
+
console.log(pc24.dim(" Usage: mcpman replay <index> | mcpman replay --list\n"));
|
|
4879
|
+
}
|
|
4880
|
+
return;
|
|
4881
|
+
}
|
|
4882
|
+
const idx = Number(args.index);
|
|
4883
|
+
if (Number.isNaN(idx) || idx < 0) {
|
|
4884
|
+
console.error(pc24.red(`
|
|
4885
|
+
Invalid index "${args.index}". Must be a non-negative number.
|
|
4886
|
+
`));
|
|
4887
|
+
process.exit(1);
|
|
4888
|
+
}
|
|
4889
|
+
const reversed = [...history].reverse();
|
|
4890
|
+
if (idx >= reversed.length) {
|
|
4891
|
+
console.error(
|
|
4892
|
+
pc24.red(`
|
|
4893
|
+
Index ${idx} out of range. History has ${reversed.length} entries.
|
|
4894
|
+
`)
|
|
4895
|
+
);
|
|
4896
|
+
process.exit(1);
|
|
4897
|
+
}
|
|
4898
|
+
const entry = reversed[idx];
|
|
4899
|
+
const cmdParts = [entry.command, ...entry.args].filter(Boolean).join(" ");
|
|
4900
|
+
console.log(pc24.cyan(`
|
|
4901
|
+
Replaying: mcpman ${cmdParts}
|
|
4902
|
+
`));
|
|
4903
|
+
try {
|
|
4904
|
+
replayCommand(idx);
|
|
4905
|
+
} catch (err) {
|
|
4906
|
+
console.error(pc24.red(` ${String(err)}`));
|
|
4907
|
+
process.exit(1);
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
});
|
|
4911
|
+
|
|
4912
|
+
// src/commands/rollback.ts
|
|
4913
|
+
import * as p9 from "@clack/prompts";
|
|
4914
|
+
import { defineCommand as defineCommand26 } from "citty";
|
|
4915
|
+
import pc25 from "picocolors";
|
|
4916
|
+
var rollback_default = defineCommand26({
|
|
4917
|
+
meta: {
|
|
4918
|
+
name: "rollback",
|
|
4919
|
+
description: "Restore a previous lockfile snapshot"
|
|
4920
|
+
},
|
|
4921
|
+
args: {
|
|
4922
|
+
index: {
|
|
4923
|
+
type: "positional",
|
|
4924
|
+
description: "Snapshot index to restore (0 = most recent). Omit to use --list.",
|
|
4925
|
+
required: false
|
|
4926
|
+
},
|
|
4927
|
+
list: {
|
|
4928
|
+
type: "boolean",
|
|
4929
|
+
description: "List available snapshots",
|
|
4930
|
+
default: false
|
|
4931
|
+
},
|
|
4932
|
+
yes: {
|
|
4933
|
+
type: "boolean",
|
|
4934
|
+
description: "Skip confirmation prompt",
|
|
4935
|
+
default: false
|
|
4936
|
+
}
|
|
4937
|
+
},
|
|
4938
|
+
async run({ args }) {
|
|
4939
|
+
const snapshots = listSnapshots();
|
|
4940
|
+
if (args.list || args.index === void 0) {
|
|
4941
|
+
if (snapshots.length === 0) {
|
|
4942
|
+
console.log(
|
|
4943
|
+
pc25.dim("No snapshots available. Snapshots are created on each lockfile write.")
|
|
4944
|
+
);
|
|
4945
|
+
return;
|
|
4946
|
+
}
|
|
4947
|
+
console.log(
|
|
4948
|
+
`
|
|
4949
|
+
${pc25.bold("Lockfile snapshots")} ${pc25.dim(`(${snapshots.length} available, 0 = most recent)`)}
|
|
4950
|
+
`
|
|
4951
|
+
);
|
|
4952
|
+
for (const snap2 of snapshots) {
|
|
4953
|
+
const size = `${Math.ceil(snap2.sizeBytes / 1024)}KB`;
|
|
4954
|
+
const date2 = snap2.createdAt ? new Date(snap2.createdAt).toLocaleString() : "unknown";
|
|
4955
|
+
console.log(` ${pc25.cyan(`[${snap2.index}]`)} ${pc25.dim(date2)} ${pc25.dim(size)}`);
|
|
4956
|
+
}
|
|
4957
|
+
console.log("");
|
|
4958
|
+
if (args.index === void 0) return;
|
|
4959
|
+
}
|
|
4960
|
+
const idx = Number.parseInt(String(args.index), 10);
|
|
4961
|
+
if (Number.isNaN(idx) || idx < 0) {
|
|
4962
|
+
console.error(
|
|
4963
|
+
`${pc25.red("\u2717")} Invalid index "${args.index}". Must be a non-negative integer.`
|
|
4964
|
+
);
|
|
4965
|
+
process.exit(1);
|
|
4966
|
+
}
|
|
4967
|
+
const snap = snapshots[idx];
|
|
4968
|
+
if (!snap) {
|
|
4969
|
+
console.error(
|
|
4970
|
+
`${pc25.red("\u2717")} Snapshot [${idx}] does not exist. Use --list to see available snapshots.`
|
|
4971
|
+
);
|
|
4972
|
+
process.exit(1);
|
|
4973
|
+
}
|
|
4974
|
+
const content = readSnapshot(idx);
|
|
4975
|
+
if (!content) {
|
|
4976
|
+
console.error(`${pc25.red("\u2717")} Could not read snapshot [${idx}].`);
|
|
4977
|
+
process.exit(1);
|
|
4978
|
+
}
|
|
4979
|
+
const date = snap.createdAt ? new Date(snap.createdAt).toLocaleString() : "unknown";
|
|
4980
|
+
console.log(`
|
|
4981
|
+
${pc25.bold("Restoring snapshot")} ${pc25.cyan(`[${idx}]`)} ${pc25.dim(date)}
|
|
4982
|
+
`);
|
|
4983
|
+
try {
|
|
4984
|
+
const parsed = JSON.parse(content);
|
|
4985
|
+
const count = Object.keys(parsed.servers ?? {}).length;
|
|
4986
|
+
console.log(` ${pc25.dim(`Preview: ${count} server(s) in snapshot`)}
|
|
4987
|
+
`);
|
|
4988
|
+
} catch {
|
|
4989
|
+
}
|
|
4990
|
+
const lockfilePath = resolveLockfilePath();
|
|
4991
|
+
if (!args.yes) {
|
|
4992
|
+
const confirmed = await p9.confirm({
|
|
4993
|
+
message: `Restore snapshot [${idx}] to ${lockfilePath}?`,
|
|
4994
|
+
initialValue: false
|
|
4995
|
+
});
|
|
4996
|
+
if (p9.isCancel(confirmed) || !confirmed) {
|
|
4997
|
+
p9.cancel("Cancelled.");
|
|
4998
|
+
return;
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
const restored = restoreSnapshot(idx, lockfilePath);
|
|
5002
|
+
if (!restored) {
|
|
5003
|
+
console.error(`${pc25.red("\u2717")} Restore failed.`);
|
|
3612
5004
|
process.exit(1);
|
|
3613
5005
|
}
|
|
3614
|
-
|
|
5006
|
+
console.log(`
|
|
5007
|
+
${pc25.green("\u2713")} Lockfile restored from snapshot [${idx}].`);
|
|
5008
|
+
console.log(pc25.dim(` Written to: ${lockfilePath}`));
|
|
3615
5009
|
}
|
|
3616
5010
|
});
|
|
3617
5011
|
|
|
3618
5012
|
// src/commands/run.ts
|
|
3619
|
-
import { spawn as
|
|
3620
|
-
import { defineCommand as
|
|
3621
|
-
import
|
|
3622
|
-
var run_default =
|
|
5013
|
+
import { spawn as spawn5 } from "child_process";
|
|
5014
|
+
import { defineCommand as defineCommand27 } from "citty";
|
|
5015
|
+
import pc26 from "picocolors";
|
|
5016
|
+
var run_default = defineCommand27({
|
|
3623
5017
|
meta: {
|
|
3624
5018
|
name: "run",
|
|
3625
5019
|
description: "Run an installed MCP server with vault secrets injected"
|
|
@@ -3641,8 +5035,8 @@ var run_default = defineCommand18({
|
|
|
3641
5035
|
const lockfile = readLockfile();
|
|
3642
5036
|
const entry = lockfile.servers[serverName];
|
|
3643
5037
|
if (!entry) {
|
|
3644
|
-
console.error(
|
|
3645
|
-
console.error(
|
|
5038
|
+
console.error(pc26.red(` Error: Server '${serverName}' is not installed.`));
|
|
5039
|
+
console.error(pc26.dim(` Run ${pc26.cyan("mcpman install <server>")} to install it first.`));
|
|
3646
5040
|
process.exit(1);
|
|
3647
5041
|
}
|
|
3648
5042
|
const lockfileEnv = parseEnvFlags(entry.envVars);
|
|
@@ -3654,8 +5048,8 @@ var run_default = defineCommand18({
|
|
|
3654
5048
|
...vaultEnv,
|
|
3655
5049
|
...cliEnv
|
|
3656
5050
|
};
|
|
3657
|
-
console.log(
|
|
3658
|
-
const child =
|
|
5051
|
+
console.log(pc26.dim(` Running ${pc26.cyan(serverName)}...`));
|
|
5052
|
+
const child = spawn5(entry.command, entry.args, {
|
|
3659
5053
|
env: finalEnv,
|
|
3660
5054
|
stdio: "inherit"
|
|
3661
5055
|
});
|
|
@@ -3672,7 +5066,7 @@ var run_default = defineCommand18({
|
|
|
3672
5066
|
resolve();
|
|
3673
5067
|
});
|
|
3674
5068
|
child.on("error", (err) => {
|
|
3675
|
-
console.error(
|
|
5069
|
+
console.error(pc26.red(` Failed to start '${serverName}': ${err.message}`));
|
|
3676
5070
|
process.exit(1);
|
|
3677
5071
|
resolve();
|
|
3678
5072
|
});
|
|
@@ -3688,15 +5082,15 @@ async function loadVaultSecrets2(serverName) {
|
|
|
3688
5082
|
const password = await getMasterPassword();
|
|
3689
5083
|
return getSecretsForServer(serverName, password);
|
|
3690
5084
|
} catch {
|
|
3691
|
-
console.warn(
|
|
5085
|
+
console.warn(pc26.yellow(" Warning: Could not load vault secrets, continuing without them."));
|
|
3692
5086
|
return {};
|
|
3693
5087
|
}
|
|
3694
5088
|
}
|
|
3695
5089
|
|
|
3696
5090
|
// src/commands/search.ts
|
|
3697
|
-
import { defineCommand as
|
|
5091
|
+
import { defineCommand as defineCommand28 } from "citty";
|
|
3698
5092
|
import { createSpinner as createSpinner4 } from "nanospinner";
|
|
3699
|
-
import
|
|
5093
|
+
import pc27 from "picocolors";
|
|
3700
5094
|
|
|
3701
5095
|
// src/core/registry-search.ts
|
|
3702
5096
|
var SEARCH_TIMEOUT_MS = 1e4;
|
|
@@ -3769,10 +5163,10 @@ function pad2(s, width) {
|
|
|
3769
5163
|
function highlightMatch(name, query) {
|
|
3770
5164
|
const idx = name.toLowerCase().indexOf(query.toLowerCase());
|
|
3771
5165
|
if (idx === -1) return name;
|
|
3772
|
-
return name.slice(0, idx) +
|
|
5166
|
+
return name.slice(0, idx) + pc27.yellow(name.slice(idx, idx + query.length)) + name.slice(idx + query.length);
|
|
3773
5167
|
}
|
|
3774
5168
|
function formatDownloads(n) {
|
|
3775
|
-
if (!n) return
|
|
5169
|
+
if (!n) return pc27.dim("\u2014");
|
|
3776
5170
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
3777
5171
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
3778
5172
|
return String(n);
|
|
@@ -3783,9 +5177,9 @@ function printNpmResults(results, query) {
|
|
|
3783
5177
|
const dlWidth = 9;
|
|
3784
5178
|
const descMax = 50;
|
|
3785
5179
|
const header = ` ${pad2("NAME", nameWidth)} ${pad2("VERSION", verWidth)} ${pad2("DOWNLOADS", dlWidth)} DESCRIPTION`;
|
|
3786
|
-
console.log(
|
|
5180
|
+
console.log(pc27.dim(header));
|
|
3787
5181
|
console.log(
|
|
3788
|
-
|
|
5182
|
+
pc27.dim(
|
|
3789
5183
|
` ${"-".repeat(nameWidth)} ${"-".repeat(verWidth)} ${"-".repeat(dlWidth)} ${"-".repeat(descMax)}`
|
|
3790
5184
|
)
|
|
3791
5185
|
);
|
|
@@ -3793,8 +5187,8 @@ function printNpmResults(results, query) {
|
|
|
3793
5187
|
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
3794
5188
|
const ver = pad2(r.version, verWidth);
|
|
3795
5189
|
const dl = pad2(formatDownloads(r.downloads), dlWidth);
|
|
3796
|
-
const desc = truncate2(r.description ||
|
|
3797
|
-
console.log(` ${name} ${
|
|
5190
|
+
const desc = truncate2(r.description || pc27.dim("(no description)"), descMax);
|
|
5191
|
+
console.log(` ${name} ${pc27.dim(ver)} ${dl} ${desc}`);
|
|
3798
5192
|
}
|
|
3799
5193
|
}
|
|
3800
5194
|
function printSmitheryResults(results, query) {
|
|
@@ -3802,19 +5196,19 @@ function printSmitheryResults(results, query) {
|
|
|
3802
5196
|
const usesWidth = 8;
|
|
3803
5197
|
const descMax = 50;
|
|
3804
5198
|
const header = ` ${pad2("NAME", nameWidth)} ${pad2("USES", usesWidth)} DESCRIPTION`;
|
|
3805
|
-
console.log(
|
|
5199
|
+
console.log(pc27.dim(header));
|
|
3806
5200
|
console.log(
|
|
3807
|
-
|
|
5201
|
+
pc27.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(usesWidth)} ${"-".repeat(descMax)}`)
|
|
3808
5202
|
);
|
|
3809
5203
|
for (const r of results) {
|
|
3810
5204
|
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
3811
5205
|
const uses = pad2(formatDownloads(r.useCount), usesWidth);
|
|
3812
|
-
const badge = r.verified ?
|
|
3813
|
-
const desc = truncate2(r.description ||
|
|
5206
|
+
const badge = r.verified ? pc27.green(" \u2713") : "";
|
|
5207
|
+
const desc = truncate2(r.description || pc27.dim("(no description)"), descMax);
|
|
3814
5208
|
console.log(` ${name}${badge} ${uses} ${desc}`);
|
|
3815
5209
|
}
|
|
3816
5210
|
}
|
|
3817
|
-
var search_default =
|
|
5211
|
+
var search_default = defineCommand28({
|
|
3818
5212
|
meta: {
|
|
3819
5213
|
name: "search",
|
|
3820
5214
|
description: "Search for MCP servers on npm or Smithery registry"
|
|
@@ -3846,7 +5240,7 @@ var search_default = defineCommand19({
|
|
|
3846
5240
|
const registry = args.registry.toLowerCase();
|
|
3847
5241
|
const limit = Math.min(Math.max(1, Number.parseInt(args.limit, 10) || 20), 100);
|
|
3848
5242
|
if (registry !== "npm" && registry !== "smithery") {
|
|
3849
|
-
console.error(
|
|
5243
|
+
console.error(pc27.red(` Unknown registry "${registry}". Use "npm" or "smithery".`));
|
|
3850
5244
|
process.exit(1);
|
|
3851
5245
|
}
|
|
3852
5246
|
const spinner5 = createSpinner4(`Searching ${registry} for "${query}"...`).start();
|
|
@@ -3854,20 +5248,20 @@ var search_default = defineCommand19({
|
|
|
3854
5248
|
const results2 = await searchNpm(query, limit);
|
|
3855
5249
|
spinner5.stop();
|
|
3856
5250
|
if (results2.length === 0) {
|
|
3857
|
-
console.log(
|
|
5251
|
+
console.log(pc27.dim(`
|
|
3858
5252
|
No results found for "${query}" on npm.
|
|
3859
5253
|
`));
|
|
3860
5254
|
return;
|
|
3861
5255
|
}
|
|
3862
5256
|
console.log(
|
|
3863
|
-
|
|
5257
|
+
pc27.bold(
|
|
3864
5258
|
`
|
|
3865
5259
|
mcpman search \u2014 npm (${results2.length} result${results2.length !== 1 ? "s" : ""})
|
|
3866
5260
|
`
|
|
3867
5261
|
)
|
|
3868
5262
|
);
|
|
3869
5263
|
printNpmResults(results2, query);
|
|
3870
|
-
console.log(
|
|
5264
|
+
console.log(pc27.dim("\n Install with: mcpman install <name>\n"));
|
|
3871
5265
|
if (args.all) {
|
|
3872
5266
|
await printPluginResults(query, limit);
|
|
3873
5267
|
}
|
|
@@ -3876,20 +5270,20 @@ var search_default = defineCommand19({
|
|
|
3876
5270
|
const results = await searchSmithery(query, limit);
|
|
3877
5271
|
spinner5.stop();
|
|
3878
5272
|
if (results.length === 0) {
|
|
3879
|
-
console.log(
|
|
5273
|
+
console.log(pc27.dim(`
|
|
3880
5274
|
No results found for "${query}" on Smithery.
|
|
3881
5275
|
`));
|
|
3882
5276
|
return;
|
|
3883
5277
|
}
|
|
3884
5278
|
console.log(
|
|
3885
|
-
|
|
5279
|
+
pc27.bold(
|
|
3886
5280
|
`
|
|
3887
5281
|
mcpman search \u2014 Smithery (${results.length} result${results.length !== 1 ? "s" : ""})
|
|
3888
5282
|
`
|
|
3889
5283
|
)
|
|
3890
5284
|
);
|
|
3891
5285
|
printSmitheryResults(results, query);
|
|
3892
|
-
console.log(
|
|
5286
|
+
console.log(pc27.dim("\n Install with: mcpman install <name>\n"));
|
|
3893
5287
|
if (args.all) {
|
|
3894
5288
|
await printPluginResults(query, limit);
|
|
3895
5289
|
}
|
|
@@ -3899,7 +5293,7 @@ async function printPluginResults(query, limit) {
|
|
|
3899
5293
|
const pluginResults = await searchPlugins(query, limit);
|
|
3900
5294
|
if (pluginResults.length === 0) return;
|
|
3901
5295
|
console.log(
|
|
3902
|
-
|
|
5296
|
+
pc27.bold(
|
|
3903
5297
|
`
|
|
3904
5298
|
Plugins (${pluginResults.length} result${pluginResults.length !== 1 ? "s" : ""})
|
|
3905
5299
|
`
|
|
@@ -3909,22 +5303,22 @@ async function printPluginResults(query, limit) {
|
|
|
3909
5303
|
const srcWidth = Math.max(6, ...pluginResults.map((r) => r.source.length));
|
|
3910
5304
|
const descMax = 50;
|
|
3911
5305
|
const header = ` ${pad2("NAME", nameWidth)} ${pad2("SOURCE", srcWidth)} DESCRIPTION`;
|
|
3912
|
-
console.log(
|
|
5306
|
+
console.log(pc27.dim(header));
|
|
3913
5307
|
console.log(
|
|
3914
|
-
|
|
5308
|
+
pc27.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(srcWidth)} ${"-".repeat(descMax)}`)
|
|
3915
5309
|
);
|
|
3916
5310
|
for (const r of pluginResults) {
|
|
3917
5311
|
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
3918
5312
|
const src = pad2(r.source, srcWidth);
|
|
3919
|
-
const desc = truncate2(r.description ||
|
|
3920
|
-
console.log(` ${name} ${
|
|
5313
|
+
const desc = truncate2(r.description || pc27.dim("(no description)"), descMax);
|
|
5314
|
+
console.log(` ${name} ${pc27.dim(src)} ${desc}`);
|
|
3921
5315
|
}
|
|
3922
5316
|
}
|
|
3923
5317
|
|
|
3924
5318
|
// src/commands/secrets.ts
|
|
3925
|
-
import * as
|
|
3926
|
-
import { defineCommand as
|
|
3927
|
-
import
|
|
5319
|
+
import * as p10 from "@clack/prompts";
|
|
5320
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
5321
|
+
import pc28 from "picocolors";
|
|
3928
5322
|
function maskValue(value) {
|
|
3929
5323
|
if (value.length <= 8) return "***";
|
|
3930
5324
|
return `${value.slice(0, 4)}***${value.slice(-3)}`;
|
|
@@ -3934,7 +5328,7 @@ function parseKeyValue(input) {
|
|
|
3934
5328
|
if (idx <= 0) return null;
|
|
3935
5329
|
return { key: input.slice(0, idx), value: input.slice(idx + 1) };
|
|
3936
5330
|
}
|
|
3937
|
-
var setCommand2 =
|
|
5331
|
+
var setCommand2 = defineCommand29({
|
|
3938
5332
|
meta: { name: "set", description: "Store an encrypted secret for a server" },
|
|
3939
5333
|
args: {
|
|
3940
5334
|
server: {
|
|
@@ -3951,28 +5345,28 @@ var setCommand2 = defineCommand20({
|
|
|
3951
5345
|
async run({ args }) {
|
|
3952
5346
|
const parsed = parseKeyValue(args.keyvalue);
|
|
3953
5347
|
if (!parsed) {
|
|
3954
|
-
console.error(`${
|
|
5348
|
+
console.error(`${pc28.red("\u2717")} Invalid format. Expected KEY=VALUE`);
|
|
3955
5349
|
process.exit(1);
|
|
3956
5350
|
}
|
|
3957
|
-
|
|
5351
|
+
p10.intro(pc28.cyan("mcpman secrets set"));
|
|
3958
5352
|
const isNew = listSecrets(args.server).length === 0 || !listSecrets(args.server)[0]?.keys.includes(parsed.key);
|
|
3959
5353
|
const vaultPath = (await import("./vault-service-UTZAV6N6.js")).getVaultPath();
|
|
3960
5354
|
const vaultExists = (await import("fs")).existsSync(vaultPath);
|
|
3961
5355
|
const password = await getMasterPassword(!vaultExists && isNew);
|
|
3962
|
-
const spin =
|
|
5356
|
+
const spin = p10.spinner();
|
|
3963
5357
|
spin.start("Encrypting secret...");
|
|
3964
5358
|
try {
|
|
3965
5359
|
setSecret(args.server, parsed.key, parsed.value, password);
|
|
3966
|
-
spin.stop(`${
|
|
5360
|
+
spin.stop(`${pc28.green("\u2713")} Stored ${pc28.bold(parsed.key)} for ${pc28.cyan(args.server)}`);
|
|
3967
5361
|
} catch (err) {
|
|
3968
|
-
spin.stop(`${
|
|
3969
|
-
console.error(
|
|
5362
|
+
spin.stop(`${pc28.red("\u2717")} Failed to store secret`);
|
|
5363
|
+
console.error(pc28.dim(String(err)));
|
|
3970
5364
|
process.exit(1);
|
|
3971
5365
|
}
|
|
3972
|
-
|
|
5366
|
+
p10.outro(pc28.dim("Secret encrypted and saved to vault."));
|
|
3973
5367
|
}
|
|
3974
5368
|
});
|
|
3975
|
-
var listCommand3 =
|
|
5369
|
+
var listCommand3 = defineCommand29({
|
|
3976
5370
|
meta: { name: "list", description: "List secret keys stored in the vault" },
|
|
3977
5371
|
args: {
|
|
3978
5372
|
server: {
|
|
@@ -3984,75 +5378,270 @@ var listCommand3 = defineCommand20({
|
|
|
3984
5378
|
async run({ args }) {
|
|
3985
5379
|
const results = listSecrets(args.server || void 0);
|
|
3986
5380
|
if (results.length === 0) {
|
|
3987
|
-
const filter = args.server ? ` for ${
|
|
3988
|
-
console.log(
|
|
5381
|
+
const filter = args.server ? ` for ${pc28.cyan(args.server)}` : "";
|
|
5382
|
+
console.log(pc28.dim(`No secrets stored${filter}.`));
|
|
3989
5383
|
return;
|
|
3990
5384
|
}
|
|
3991
5385
|
console.log("");
|
|
3992
5386
|
for (const { server, keys } of results) {
|
|
3993
|
-
console.log(
|
|
5387
|
+
console.log(pc28.bold(pc28.cyan(server)));
|
|
3994
5388
|
for (const key of keys) {
|
|
3995
|
-
console.log(` ${
|
|
5389
|
+
console.log(` ${pc28.green("\u25CF")} ${pc28.bold(key)} ${pc28.dim(maskValue("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"))}`);
|
|
5390
|
+
}
|
|
5391
|
+
console.log("");
|
|
5392
|
+
}
|
|
5393
|
+
const total = results.reduce((n, r) => n + r.keys.length, 0);
|
|
5394
|
+
console.log(
|
|
5395
|
+
pc28.dim(
|
|
5396
|
+
` ${total} secret${total !== 1 ? "s" : ""} in ${results.length} server${results.length !== 1 ? "s" : ""}`
|
|
5397
|
+
)
|
|
5398
|
+
);
|
|
5399
|
+
}
|
|
5400
|
+
});
|
|
5401
|
+
var removeCommand2 = defineCommand29({
|
|
5402
|
+
meta: { name: "remove", description: "Delete a secret from the vault" },
|
|
5403
|
+
args: {
|
|
5404
|
+
server: {
|
|
5405
|
+
type: "positional",
|
|
5406
|
+
description: "Server name",
|
|
5407
|
+
required: true
|
|
5408
|
+
},
|
|
5409
|
+
key: {
|
|
5410
|
+
type: "positional",
|
|
5411
|
+
description: "Secret key to remove",
|
|
5412
|
+
required: true
|
|
5413
|
+
}
|
|
5414
|
+
},
|
|
5415
|
+
async run({ args }) {
|
|
5416
|
+
const confirmed = await p10.confirm({
|
|
5417
|
+
message: `Remove ${pc28.bold(args.key)} from ${pc28.cyan(args.server)}?`,
|
|
5418
|
+
initialValue: false
|
|
5419
|
+
});
|
|
5420
|
+
if (p10.isCancel(confirmed) || !confirmed) {
|
|
5421
|
+
p10.cancel("Cancelled.");
|
|
5422
|
+
return;
|
|
5423
|
+
}
|
|
5424
|
+
try {
|
|
5425
|
+
removeSecret(args.server, args.key);
|
|
5426
|
+
console.log(`${pc28.green("\u2713")} Removed ${pc28.bold(args.key)} from ${pc28.cyan(args.server)}`);
|
|
5427
|
+
} catch (err) {
|
|
5428
|
+
console.error(`${pc28.red("\u2717")} Failed to remove secret`);
|
|
5429
|
+
console.error(pc28.dim(String(err)));
|
|
5430
|
+
process.exit(1);
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
});
|
|
5434
|
+
var secrets_default = defineCommand29({
|
|
5435
|
+
meta: {
|
|
5436
|
+
name: "secrets",
|
|
5437
|
+
description: "Manage encrypted secrets for MCP servers"
|
|
5438
|
+
},
|
|
5439
|
+
subCommands: {
|
|
5440
|
+
set: setCommand2,
|
|
5441
|
+
list: listCommand3,
|
|
5442
|
+
remove: removeCommand2
|
|
5443
|
+
}
|
|
5444
|
+
});
|
|
5445
|
+
|
|
5446
|
+
// src/commands/status.ts
|
|
5447
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
5448
|
+
import { createSpinner as createSpinner5 } from "nanospinner";
|
|
5449
|
+
import pc29 from "picocolors";
|
|
5450
|
+
|
|
5451
|
+
// src/core/status-checker.ts
|
|
5452
|
+
import { execSync as execSync4 } from "child_process";
|
|
5453
|
+
import { spawn as spawn6 } from "child_process";
|
|
5454
|
+
var MCP_INIT_REQUEST2 = JSON.stringify({
|
|
5455
|
+
jsonrpc: "2.0",
|
|
5456
|
+
id: 1,
|
|
5457
|
+
method: "initialize",
|
|
5458
|
+
params: {
|
|
5459
|
+
protocolVersion: "2024-11-05",
|
|
5460
|
+
capabilities: {},
|
|
5461
|
+
clientInfo: { name: "mcpman-status", version: "0.9.0" }
|
|
5462
|
+
}
|
|
5463
|
+
});
|
|
5464
|
+
async function probeServer(name, entry, timeoutMs = 3e3) {
|
|
5465
|
+
const start = Date.now();
|
|
5466
|
+
return new Promise((resolve) => {
|
|
5467
|
+
let settled = false;
|
|
5468
|
+
const settle = (status) => {
|
|
5469
|
+
if (!settled) {
|
|
5470
|
+
settled = true;
|
|
5471
|
+
resolve(status);
|
|
5472
|
+
}
|
|
5473
|
+
};
|
|
5474
|
+
let child = null;
|
|
5475
|
+
const timer = setTimeout(() => {
|
|
5476
|
+
child?.kill();
|
|
5477
|
+
settle({
|
|
5478
|
+
name,
|
|
5479
|
+
alive: false,
|
|
5480
|
+
responseTimeMs: null,
|
|
5481
|
+
error: "timeout"
|
|
5482
|
+
});
|
|
5483
|
+
}, timeoutMs);
|
|
5484
|
+
try {
|
|
5485
|
+
child = spawn6(entry.command, entry.args ?? [], {
|
|
5486
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5487
|
+
env: { ...process.env }
|
|
5488
|
+
});
|
|
5489
|
+
} catch (err) {
|
|
5490
|
+
clearTimeout(timer);
|
|
5491
|
+
settle({
|
|
5492
|
+
name,
|
|
5493
|
+
alive: false,
|
|
5494
|
+
responseTimeMs: null,
|
|
5495
|
+
error: String(err)
|
|
5496
|
+
});
|
|
5497
|
+
return;
|
|
5498
|
+
}
|
|
5499
|
+
let stdout = "";
|
|
5500
|
+
child.stdout?.on("data", (chunk) => {
|
|
5501
|
+
stdout += chunk.toString();
|
|
5502
|
+
const lines = stdout.split("\n");
|
|
5503
|
+
for (const line of lines) {
|
|
5504
|
+
const trimmed = line.trim();
|
|
5505
|
+
if (!trimmed) continue;
|
|
5506
|
+
try {
|
|
5507
|
+
const parsed = JSON.parse(trimmed);
|
|
5508
|
+
if (parsed.jsonrpc === "2.0" && "result" in parsed) {
|
|
5509
|
+
clearTimeout(timer);
|
|
5510
|
+
child?.kill();
|
|
5511
|
+
settle({
|
|
5512
|
+
name,
|
|
5513
|
+
alive: true,
|
|
5514
|
+
responseTimeMs: Date.now() - start
|
|
5515
|
+
});
|
|
5516
|
+
return;
|
|
5517
|
+
}
|
|
5518
|
+
} catch {
|
|
5519
|
+
}
|
|
3996
5520
|
}
|
|
3997
|
-
|
|
5521
|
+
});
|
|
5522
|
+
child.on("error", (err) => {
|
|
5523
|
+
clearTimeout(timer);
|
|
5524
|
+
settle({
|
|
5525
|
+
name,
|
|
5526
|
+
alive: false,
|
|
5527
|
+
responseTimeMs: null,
|
|
5528
|
+
error: err.message
|
|
5529
|
+
});
|
|
5530
|
+
});
|
|
5531
|
+
child.on("exit", (code) => {
|
|
5532
|
+
if (!settled) {
|
|
5533
|
+
clearTimeout(timer);
|
|
5534
|
+
settle({
|
|
5535
|
+
name,
|
|
5536
|
+
alive: false,
|
|
5537
|
+
responseTimeMs: null,
|
|
5538
|
+
error: `exited with code ${code}`
|
|
5539
|
+
});
|
|
5540
|
+
}
|
|
5541
|
+
});
|
|
5542
|
+
try {
|
|
5543
|
+
child.stdin?.write(`${MCP_INIT_REQUEST2}
|
|
5544
|
+
`);
|
|
5545
|
+
} catch {
|
|
3998
5546
|
}
|
|
3999
|
-
|
|
5547
|
+
});
|
|
5548
|
+
}
|
|
5549
|
+
async function getServerStatuses(serverName) {
|
|
5550
|
+
const lockfile = readLockfile();
|
|
5551
|
+
const { servers } = lockfile;
|
|
5552
|
+
if (serverName) {
|
|
5553
|
+
const entry = servers[serverName];
|
|
5554
|
+
if (!entry) {
|
|
5555
|
+
return [{ name: serverName, alive: false, responseTimeMs: null, error: "not in lockfile" }];
|
|
5556
|
+
}
|
|
5557
|
+
return [await probeServer(serverName, entry)];
|
|
5558
|
+
}
|
|
5559
|
+
if (Object.keys(servers).length === 0) return [];
|
|
5560
|
+
return Promise.all(Object.entries(servers).map(([name, entry]) => probeServer(name, entry)));
|
|
5561
|
+
}
|
|
5562
|
+
|
|
5563
|
+
// src/commands/status.ts
|
|
5564
|
+
function pad3(s, width) {
|
|
5565
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
5566
|
+
}
|
|
5567
|
+
function formatStatus(s) {
|
|
5568
|
+
return s.alive ? pc29.green("alive") : pc29.red("dead");
|
|
5569
|
+
}
|
|
5570
|
+
function formatResponseTime(s) {
|
|
5571
|
+
if (!s.alive || s.responseTimeMs === null) return pc29.dim("\u2014");
|
|
5572
|
+
return pc29.cyan(`${s.responseTimeMs}ms`);
|
|
5573
|
+
}
|
|
5574
|
+
function printTable(statuses) {
|
|
5575
|
+
const nameW = Math.max(6, ...statuses.map((s) => s.name.length));
|
|
5576
|
+
const header = ` ${pad3("SERVER", nameW)} ${pad3("STATUS", 7)} ${pad3("RESPONSE", 10)} ERROR`;
|
|
5577
|
+
console.log(pc29.dim(header));
|
|
5578
|
+
console.log(
|
|
5579
|
+
pc29.dim(` ${"\u2500".repeat(nameW)} ${"\u2500".repeat(7)} ${"\u2500".repeat(10)} ${"\u2500".repeat(20)}`)
|
|
5580
|
+
);
|
|
5581
|
+
for (const s of statuses) {
|
|
5582
|
+
const errStr = s.error ? pc29.dim(s.error) : "";
|
|
4000
5583
|
console.log(
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
5584
|
+
` ${pad3(s.name, nameW)} ${pad3(
|
|
5585
|
+
formatStatus(s),
|
|
5586
|
+
7 + 10
|
|
5587
|
+
/* color codes */
|
|
5588
|
+
)} ${pad3(formatResponseTime(s), 10 + 10)} ${errStr}`
|
|
4004
5589
|
);
|
|
4005
5590
|
}
|
|
4006
|
-
}
|
|
4007
|
-
var
|
|
4008
|
-
meta: {
|
|
5591
|
+
}
|
|
5592
|
+
var status_default = defineCommand30({
|
|
5593
|
+
meta: {
|
|
5594
|
+
name: "status",
|
|
5595
|
+
description: "Show live process status for installed MCP servers"
|
|
5596
|
+
},
|
|
4009
5597
|
args: {
|
|
4010
5598
|
server: {
|
|
4011
|
-
type: "
|
|
4012
|
-
description: "
|
|
4013
|
-
required: true
|
|
5599
|
+
type: "string",
|
|
5600
|
+
description: "Check a specific server by name"
|
|
4014
5601
|
},
|
|
4015
|
-
|
|
4016
|
-
type: "
|
|
4017
|
-
description: "
|
|
4018
|
-
|
|
5602
|
+
json: {
|
|
5603
|
+
type: "boolean",
|
|
5604
|
+
description: "Output results as JSON",
|
|
5605
|
+
default: false
|
|
4019
5606
|
}
|
|
4020
5607
|
},
|
|
4021
5608
|
async run({ args }) {
|
|
4022
|
-
const
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
});
|
|
4026
|
-
if (p9.isCancel(confirmed) || !confirmed) {
|
|
4027
|
-
p9.cancel("Cancelled.");
|
|
4028
|
-
return;
|
|
4029
|
-
}
|
|
5609
|
+
const label = args.server ? `Probing ${args.server}...` : "Probing all servers...";
|
|
5610
|
+
const spinner5 = createSpinner5(label).start();
|
|
5611
|
+
let statuses;
|
|
4030
5612
|
try {
|
|
4031
|
-
|
|
4032
|
-
console.log(`${pc19.green("\u2713")} Removed ${pc19.bold(args.key)} from ${pc19.cyan(args.server)}`);
|
|
5613
|
+
statuses = await getServerStatuses(args.server);
|
|
4033
5614
|
} catch (err) {
|
|
4034
|
-
|
|
4035
|
-
console.error(
|
|
5615
|
+
spinner5.error({ text: "Status check failed" });
|
|
5616
|
+
console.error(pc29.red(String(err)));
|
|
4036
5617
|
process.exit(1);
|
|
4037
5618
|
}
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
5619
|
+
spinner5.success({ text: `Checked ${statuses.length} server(s)` });
|
|
5620
|
+
if (statuses.length === 0) {
|
|
5621
|
+
console.log(pc29.dim("\n No MCP servers installed.\n"));
|
|
5622
|
+
return;
|
|
5623
|
+
}
|
|
5624
|
+
if (args.json) {
|
|
5625
|
+
console.log(JSON.stringify(statuses, null, 2));
|
|
5626
|
+
return;
|
|
5627
|
+
}
|
|
5628
|
+
console.log(pc29.bold("\n mcpman status\n"));
|
|
5629
|
+
printTable(statuses);
|
|
5630
|
+
const alive = statuses.filter((s) => s.alive).length;
|
|
5631
|
+
const dead = statuses.length - alive;
|
|
5632
|
+
const parts = [];
|
|
5633
|
+
if (alive > 0) parts.push(pc29.green(`${alive} alive`));
|
|
5634
|
+
if (dead > 0) parts.push(pc29.red(`${dead} dead`));
|
|
5635
|
+
console.log(`
|
|
5636
|
+
${parts.join(" \xB7 ")}
|
|
5637
|
+
`);
|
|
4049
5638
|
}
|
|
4050
5639
|
});
|
|
4051
5640
|
|
|
4052
5641
|
// src/commands/sync.ts
|
|
4053
|
-
import * as
|
|
4054
|
-
import { defineCommand as
|
|
4055
|
-
import
|
|
5642
|
+
import * as p11 from "@clack/prompts";
|
|
5643
|
+
import { defineCommand as defineCommand31 } from "citty";
|
|
5644
|
+
import pc30 from "picocolors";
|
|
4056
5645
|
|
|
4057
5646
|
// src/core/config-diff.ts
|
|
4058
5647
|
function reconstructServerEntry(lockEntry) {
|
|
@@ -4197,14 +5786,14 @@ async function getClientConfigs() {
|
|
|
4197
5786
|
}
|
|
4198
5787
|
|
|
4199
5788
|
// src/commands/sync.ts
|
|
4200
|
-
var
|
|
4201
|
-
var
|
|
5789
|
+
var VALID_CLIENTS2 = ["claude-desktop", "cursor", "vscode", "windsurf"];
|
|
5790
|
+
var CLIENT_DISPLAY4 = {
|
|
4202
5791
|
"claude-desktop": "Claude Desktop",
|
|
4203
5792
|
cursor: "Cursor",
|
|
4204
5793
|
vscode: "VS Code",
|
|
4205
5794
|
windsurf: "Windsurf"
|
|
4206
5795
|
};
|
|
4207
|
-
var sync_default =
|
|
5796
|
+
var sync_default = defineCommand31({
|
|
4208
5797
|
meta: {
|
|
4209
5798
|
name: "sync",
|
|
4210
5799
|
description: "Sync MCP server configs across all detected AI clients"
|
|
@@ -4231,20 +5820,20 @@ var sync_default = defineCommand21({
|
|
|
4231
5820
|
}
|
|
4232
5821
|
},
|
|
4233
5822
|
async run({ args }) {
|
|
4234
|
-
|
|
5823
|
+
p11.intro(`${pc30.cyan("mcpman sync")}`);
|
|
4235
5824
|
const sourceClient = args.source;
|
|
4236
|
-
if (sourceClient && !
|
|
4237
|
-
|
|
4238
|
-
`Invalid --source "${sourceClient}". Must be one of: ${
|
|
5825
|
+
if (sourceClient && !VALID_CLIENTS2.includes(sourceClient)) {
|
|
5826
|
+
p11.log.error(
|
|
5827
|
+
`Invalid --source "${sourceClient}". Must be one of: ${VALID_CLIENTS2.join(", ")}`
|
|
4239
5828
|
);
|
|
4240
5829
|
process.exit(1);
|
|
4241
5830
|
}
|
|
4242
|
-
const spinner5 =
|
|
5831
|
+
const spinner5 = p11.spinner();
|
|
4243
5832
|
spinner5.start("Detecting clients and reading configs...");
|
|
4244
5833
|
const { configs, handlers } = await getClientConfigs();
|
|
4245
5834
|
spinner5.stop(`Found ${configs.size} client(s)`);
|
|
4246
5835
|
if (configs.size === 0) {
|
|
4247
|
-
|
|
5836
|
+
p11.log.warn(
|
|
4248
5837
|
"No AI clients detected. Install Claude Desktop, Cursor, VS Code, or Windsurf first."
|
|
4249
5838
|
);
|
|
4250
5839
|
process.exit(0);
|
|
@@ -4253,10 +5842,10 @@ var sync_default = defineCommand21({
|
|
|
4253
5842
|
let actions;
|
|
4254
5843
|
if (sourceClient) {
|
|
4255
5844
|
if (!configs.has(sourceClient)) {
|
|
4256
|
-
|
|
5845
|
+
p11.log.error(`Source client "${sourceClient}" is not detected or its config is unreadable.`);
|
|
4257
5846
|
process.exit(1);
|
|
4258
5847
|
}
|
|
4259
|
-
|
|
5848
|
+
p11.log.info(`Using ${CLIENT_DISPLAY4[sourceClient]} as source of truth`);
|
|
4260
5849
|
actions = computeDiffFromClient(sourceClient, configs, diffOptions);
|
|
4261
5850
|
} else {
|
|
4262
5851
|
const lockfile = readLockfile();
|
|
@@ -4267,32 +5856,32 @@ var sync_default = defineCommand21({
|
|
|
4267
5856
|
const extraCount = actions.filter((a) => a.action === "extra").length;
|
|
4268
5857
|
const removeCount = actions.filter((a) => a.action === "remove").length;
|
|
4269
5858
|
if (addCount === 0 && removeCount === 0 && extraCount === 0) {
|
|
4270
|
-
|
|
5859
|
+
p11.outro(pc30.green("All clients are in sync."));
|
|
4271
5860
|
process.exit(0);
|
|
4272
5861
|
}
|
|
4273
5862
|
const parts = [];
|
|
4274
|
-
if (addCount > 0) parts.push(
|
|
4275
|
-
if (removeCount > 0) parts.push(
|
|
4276
|
-
if (extraCount > 0) parts.push(
|
|
4277
|
-
|
|
5863
|
+
if (addCount > 0) parts.push(pc30.green(`${addCount} to add`));
|
|
5864
|
+
if (removeCount > 0) parts.push(pc30.red(`${removeCount} to remove`));
|
|
5865
|
+
if (extraCount > 0) parts.push(pc30.yellow(`${extraCount} extra (informational)`));
|
|
5866
|
+
p11.log.info(parts.join(" \xB7 "));
|
|
4278
5867
|
if (args["dry-run"]) {
|
|
4279
|
-
|
|
5868
|
+
p11.outro(pc30.dim("Dry run \u2014 no changes applied."));
|
|
4280
5869
|
process.exit(1);
|
|
4281
5870
|
}
|
|
4282
5871
|
if (addCount === 0 && removeCount === 0) {
|
|
4283
|
-
|
|
5872
|
+
p11.outro(pc30.dim("No additions needed. Extra servers left untouched."));
|
|
4284
5873
|
process.exit(1);
|
|
4285
5874
|
}
|
|
4286
5875
|
if (!args.yes) {
|
|
4287
5876
|
const actionParts = [];
|
|
4288
5877
|
if (addCount > 0) actionParts.push(`${addCount} addition(s)`);
|
|
4289
5878
|
if (removeCount > 0) actionParts.push(`${removeCount} removal(s)`);
|
|
4290
|
-
const confirmed = await
|
|
5879
|
+
const confirmed = await p11.confirm({
|
|
4291
5880
|
message: `Apply ${actionParts.join(" and ")} to client configs?`,
|
|
4292
5881
|
initialValue: true
|
|
4293
5882
|
});
|
|
4294
|
-
if (
|
|
4295
|
-
|
|
5883
|
+
if (p11.isCancel(confirmed) || !confirmed) {
|
|
5884
|
+
p11.outro(pc30.dim("Cancelled \u2014 no changes applied."));
|
|
4296
5885
|
process.exit(0);
|
|
4297
5886
|
}
|
|
4298
5887
|
}
|
|
@@ -4300,40 +5889,40 @@ var sync_default = defineCommand21({
|
|
|
4300
5889
|
const result = await applySyncActions(actions, handlers);
|
|
4301
5890
|
spinner5.stop("Done");
|
|
4302
5891
|
if (result.applied > 0) {
|
|
4303
|
-
|
|
5892
|
+
p11.log.success(`Added ${result.applied} server(s) to client configs.`);
|
|
4304
5893
|
}
|
|
4305
5894
|
if (result.removed > 0) {
|
|
4306
|
-
|
|
5895
|
+
p11.log.success(`Removed ${result.removed} server(s) from client configs.`);
|
|
4307
5896
|
}
|
|
4308
5897
|
if (result.failed > 0) {
|
|
4309
5898
|
for (const e of result.errors) {
|
|
4310
|
-
|
|
5899
|
+
p11.log.error(`Failed to sync "${e.server}" on ${e.client}: ${e.error}`);
|
|
4311
5900
|
}
|
|
4312
5901
|
}
|
|
4313
|
-
|
|
4314
|
-
result.failed === 0 ?
|
|
5902
|
+
p11.outro(
|
|
5903
|
+
result.failed === 0 ? pc30.green("Sync complete.") : pc30.yellow("Sync complete with errors.")
|
|
4315
5904
|
);
|
|
4316
5905
|
process.exit(result.failed > 0 ? 1 : 0);
|
|
4317
5906
|
}
|
|
4318
5907
|
});
|
|
4319
5908
|
function printDiffTable(actions) {
|
|
4320
5909
|
if (actions.length === 0) {
|
|
4321
|
-
|
|
5910
|
+
p11.log.info("No actions to display.");
|
|
4322
5911
|
return;
|
|
4323
5912
|
}
|
|
4324
5913
|
const nameWidth = Math.max(6, ...actions.map((a) => a.server.length));
|
|
4325
5914
|
const clientWidth = Math.max(
|
|
4326
5915
|
6,
|
|
4327
|
-
...actions.map((a) =>
|
|
5916
|
+
...actions.map((a) => CLIENT_DISPLAY4[a.client]?.length ?? a.client.length)
|
|
4328
5917
|
);
|
|
4329
|
-
const header = ` ${
|
|
4330
|
-
console.log(
|
|
4331
|
-
console.log(
|
|
5918
|
+
const header = ` ${pad4("SERVER", nameWidth)} ${pad4("CLIENT", clientWidth)} STATUS`;
|
|
5919
|
+
console.log(pc30.dim(header));
|
|
5920
|
+
console.log(pc30.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientWidth)} ------`));
|
|
4332
5921
|
for (const action of actions) {
|
|
4333
|
-
const clientDisplay =
|
|
5922
|
+
const clientDisplay = CLIENT_DISPLAY4[action.client] ?? action.client;
|
|
4334
5923
|
const [icon, statusText] = formatAction(action.action);
|
|
4335
5924
|
console.log(
|
|
4336
|
-
` ${
|
|
5925
|
+
` ${pad4(action.server, nameWidth)} ${pad4(clientDisplay, clientWidth)} ${icon} ${statusText}`
|
|
4337
5926
|
);
|
|
4338
5927
|
}
|
|
4339
5928
|
console.log("");
|
|
@@ -4341,25 +5930,193 @@ function printDiffTable(actions) {
|
|
|
4341
5930
|
function formatAction(action) {
|
|
4342
5931
|
switch (action) {
|
|
4343
5932
|
case "add":
|
|
4344
|
-
return [
|
|
5933
|
+
return [pc30.green("+"), pc30.green("missing \u2014 will add")];
|
|
4345
5934
|
case "extra":
|
|
4346
|
-
return [
|
|
5935
|
+
return [pc30.yellow("?"), pc30.yellow("extra (not in lockfile)")];
|
|
4347
5936
|
case "remove":
|
|
4348
|
-
return [
|
|
5937
|
+
return [pc30.red("\u2013"), pc30.red("extra \u2014 will remove")];
|
|
4349
5938
|
case "ok":
|
|
4350
|
-
return [
|
|
5939
|
+
return [pc30.dim("\xB7"), pc30.dim("in sync")];
|
|
4351
5940
|
}
|
|
4352
5941
|
}
|
|
4353
|
-
function
|
|
5942
|
+
function pad4(s, width) {
|
|
4354
5943
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
4355
5944
|
}
|
|
4356
5945
|
|
|
5946
|
+
// src/commands/template.ts
|
|
5947
|
+
import { defineCommand as defineCommand32 } from "citty";
|
|
5948
|
+
import pc31 from "picocolors";
|
|
5949
|
+
|
|
5950
|
+
// src/core/template-service.ts
|
|
5951
|
+
import fs17 from "fs";
|
|
5952
|
+
import path14 from "path";
|
|
5953
|
+
function templatePath(name, dir) {
|
|
5954
|
+
return path14.join(dir ?? getTemplatesDir(), `${name}.json`);
|
|
5955
|
+
}
|
|
5956
|
+
function ensureDir2(dir) {
|
|
5957
|
+
const target = dir ?? getTemplatesDir();
|
|
5958
|
+
if (!fs17.existsSync(target)) {
|
|
5959
|
+
fs17.mkdirSync(target, { recursive: true });
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
function saveTemplate(name, opts = {}) {
|
|
5963
|
+
const lockfile = readLockfile();
|
|
5964
|
+
const servers = Object.entries(lockfile.servers).map(([sName, entry]) => ({
|
|
5965
|
+
name: sName,
|
|
5966
|
+
source: entry.source,
|
|
5967
|
+
version: entry.version,
|
|
5968
|
+
args: entry.args
|
|
5969
|
+
}));
|
|
5970
|
+
const template = {
|
|
5971
|
+
name,
|
|
5972
|
+
description: opts.description ?? "",
|
|
5973
|
+
servers,
|
|
5974
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5975
|
+
};
|
|
5976
|
+
ensureDir2(opts.dir);
|
|
5977
|
+
fs17.writeFileSync(templatePath(name, opts.dir), JSON.stringify(template, null, 2), "utf-8");
|
|
5978
|
+
}
|
|
5979
|
+
function loadTemplate(name, dir) {
|
|
5980
|
+
const file = templatePath(name, dir);
|
|
5981
|
+
if (!fs17.existsSync(file)) return null;
|
|
5982
|
+
try {
|
|
5983
|
+
return JSON.parse(fs17.readFileSync(file, "utf-8"));
|
|
5984
|
+
} catch {
|
|
5985
|
+
return null;
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
function listTemplates(dir) {
|
|
5989
|
+
const target = dir ?? getTemplatesDir();
|
|
5990
|
+
if (!fs17.existsSync(target)) return [];
|
|
5991
|
+
return fs17.readdirSync(target).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, "")).sort();
|
|
5992
|
+
}
|
|
5993
|
+
function deleteTemplate(name, dir) {
|
|
5994
|
+
const file = templatePath(name, dir);
|
|
5995
|
+
if (fs17.existsSync(file)) {
|
|
5996
|
+
fs17.unlinkSync(file);
|
|
5997
|
+
}
|
|
5998
|
+
}
|
|
5999
|
+
function applyTemplate(name, dir) {
|
|
6000
|
+
const template = loadTemplate(name, dir);
|
|
6001
|
+
if (!template) {
|
|
6002
|
+
throw new Error(`Template "${name}" not found`);
|
|
6003
|
+
}
|
|
6004
|
+
return template.servers.map((s) => {
|
|
6005
|
+
const versionSuffix = s.version ? `@${s.version}` : "";
|
|
6006
|
+
return `mcpman install ${s.name}${versionSuffix}`;
|
|
6007
|
+
});
|
|
6008
|
+
}
|
|
6009
|
+
|
|
6010
|
+
// src/commands/template.ts
|
|
6011
|
+
var saveCmd = defineCommand32({
|
|
6012
|
+
meta: { name: "save", description: "Save current lockfile servers as a named template" },
|
|
6013
|
+
args: {
|
|
6014
|
+
name: { type: "positional", description: "Template name", required: true },
|
|
6015
|
+
description: {
|
|
6016
|
+
type: "string",
|
|
6017
|
+
description: "Optional description for the template"
|
|
6018
|
+
}
|
|
6019
|
+
},
|
|
6020
|
+
run({ args }) {
|
|
6021
|
+
try {
|
|
6022
|
+
saveTemplate(args.name, { description: args.description });
|
|
6023
|
+
const tmpl = loadTemplate(args.name);
|
|
6024
|
+
const count = tmpl?.servers.length ?? 0;
|
|
6025
|
+
console.log(
|
|
6026
|
+
`${pc31.green("\u2713")} Template ${pc31.cyan(pc31.bold(args.name))} saved (${count} server${count !== 1 ? "s" : ""})`
|
|
6027
|
+
);
|
|
6028
|
+
} catch (err) {
|
|
6029
|
+
console.error(`${pc31.red("\u2717")} ${String(err)}`);
|
|
6030
|
+
process.exit(1);
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
});
|
|
6034
|
+
var applyCmd = defineCommand32({
|
|
6035
|
+
meta: { name: "apply", description: "Print install commands for a template" },
|
|
6036
|
+
args: {
|
|
6037
|
+
name: { type: "positional", description: "Template name", required: true }
|
|
6038
|
+
},
|
|
6039
|
+
run({ args }) {
|
|
6040
|
+
let commands;
|
|
6041
|
+
try {
|
|
6042
|
+
commands = applyTemplate(args.name);
|
|
6043
|
+
} catch (err) {
|
|
6044
|
+
console.error(`${pc31.red("\u2717")} ${String(err)}`);
|
|
6045
|
+
process.exit(1);
|
|
6046
|
+
}
|
|
6047
|
+
if (commands.length === 0) {
|
|
6048
|
+
console.log(pc31.dim(`
|
|
6049
|
+
Template "${args.name}" has no servers.
|
|
6050
|
+
`));
|
|
6051
|
+
return;
|
|
6052
|
+
}
|
|
6053
|
+
console.log(pc31.bold(`
|
|
6054
|
+
Template: ${pc31.cyan(args.name)}
|
|
6055
|
+
`));
|
|
6056
|
+
console.log(pc31.dim(" Run the following commands to install all servers:\n"));
|
|
6057
|
+
for (const cmd of commands) {
|
|
6058
|
+
console.log(` ${pc31.green("$")} ${cmd}`);
|
|
6059
|
+
}
|
|
6060
|
+
console.log();
|
|
6061
|
+
}
|
|
6062
|
+
});
|
|
6063
|
+
var listCmd5 = defineCommand32({
|
|
6064
|
+
meta: { name: "list", description: "List all saved templates" },
|
|
6065
|
+
args: {},
|
|
6066
|
+
run() {
|
|
6067
|
+
const names = listTemplates();
|
|
6068
|
+
if (names.length === 0) {
|
|
6069
|
+
console.log(pc31.dim("\n No templates saved. Use `mcpman template save <name>`.\n"));
|
|
6070
|
+
return;
|
|
6071
|
+
}
|
|
6072
|
+
console.log(pc31.bold("\n mcpman templates\n"));
|
|
6073
|
+
console.log(pc31.dim(` ${"\u2500".repeat(50)}`));
|
|
6074
|
+
for (const name of names) {
|
|
6075
|
+
const tmpl = loadTemplate(name);
|
|
6076
|
+
const count = tmpl?.servers.length ?? 0;
|
|
6077
|
+
const desc = tmpl?.description ? pc31.dim(` \u2014 ${tmpl.description}`) : "";
|
|
6078
|
+
console.log(
|
|
6079
|
+
` ${pc31.cyan(pc31.bold(name.padEnd(20)))} ${pc31.dim(`${count} server${count !== 1 ? "s" : ""}`)}${desc}`
|
|
6080
|
+
);
|
|
6081
|
+
}
|
|
6082
|
+
console.log(pc31.dim(` ${"\u2500".repeat(50)}
|
|
6083
|
+
`));
|
|
6084
|
+
}
|
|
6085
|
+
});
|
|
6086
|
+
var deleteCmd2 = defineCommand32({
|
|
6087
|
+
meta: { name: "delete", description: "Delete a saved template" },
|
|
6088
|
+
args: {
|
|
6089
|
+
name: { type: "positional", description: "Template name", required: true }
|
|
6090
|
+
},
|
|
6091
|
+
run({ args }) {
|
|
6092
|
+
const existing = listTemplates();
|
|
6093
|
+
if (!existing.includes(args.name)) {
|
|
6094
|
+
console.error(`${pc31.red("\u2717")} Template "${args.name}" does not exist.`);
|
|
6095
|
+
process.exit(1);
|
|
6096
|
+
}
|
|
6097
|
+
deleteTemplate(args.name);
|
|
6098
|
+
console.log(`${pc31.green("\u2713")} Template ${pc31.cyan(args.name)} deleted`);
|
|
6099
|
+
}
|
|
6100
|
+
});
|
|
6101
|
+
var template_default = defineCommand32({
|
|
6102
|
+
meta: {
|
|
6103
|
+
name: "template",
|
|
6104
|
+
description: "Manage install templates"
|
|
6105
|
+
},
|
|
6106
|
+
subCommands: {
|
|
6107
|
+
save: saveCmd,
|
|
6108
|
+
apply: applyCmd,
|
|
6109
|
+
list: listCmd5,
|
|
6110
|
+
delete: deleteCmd2
|
|
6111
|
+
}
|
|
6112
|
+
});
|
|
6113
|
+
|
|
4357
6114
|
// src/commands/test-command.ts
|
|
4358
|
-
import { defineCommand as
|
|
4359
|
-
import
|
|
6115
|
+
import { defineCommand as defineCommand33 } from "citty";
|
|
6116
|
+
import pc32 from "picocolors";
|
|
4360
6117
|
|
|
4361
6118
|
// src/core/mcp-tester.ts
|
|
4362
|
-
import { spawn as
|
|
6119
|
+
import { spawn as spawn7 } from "child_process";
|
|
4363
6120
|
var TIMEOUT_MS = 1e4;
|
|
4364
6121
|
async function testMcpServer(serverName, command, args, env) {
|
|
4365
6122
|
const start = Date.now();
|
|
@@ -4369,7 +6126,7 @@ async function testMcpServer(serverName, command, args, env) {
|
|
|
4369
6126
|
let initOk = false;
|
|
4370
6127
|
let toolsOk = false;
|
|
4371
6128
|
let tools = [];
|
|
4372
|
-
const child =
|
|
6129
|
+
const child = spawn7(command, args, {
|
|
4373
6130
|
env: { ...process.env, ...env },
|
|
4374
6131
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4375
6132
|
});
|
|
@@ -4454,7 +6211,7 @@ async function testMcpServer(serverName, command, args, env) {
|
|
|
4454
6211
|
}
|
|
4455
6212
|
|
|
4456
6213
|
// src/commands/test-command.ts
|
|
4457
|
-
var test_command_default =
|
|
6214
|
+
var test_command_default = defineCommand33({
|
|
4458
6215
|
meta: {
|
|
4459
6216
|
name: "test",
|
|
4460
6217
|
description: "Test MCP server connectivity and capabilities"
|
|
@@ -4475,10 +6232,10 @@ var test_command_default = defineCommand22({
|
|
|
4475
6232
|
const lockfile = readLockfile();
|
|
4476
6233
|
const serverNames = args.all ? Object.keys(lockfile.servers) : args.server ? [args.server] : [];
|
|
4477
6234
|
if (serverNames.length === 0) {
|
|
4478
|
-
console.error(
|
|
6235
|
+
console.error(pc32.red(" Error: Specify a server name or use --all."));
|
|
4479
6236
|
process.exit(1);
|
|
4480
6237
|
}
|
|
4481
|
-
console.log(
|
|
6238
|
+
console.log(pc32.bold(`
|
|
4482
6239
|
mcpman test \u2014 ${serverNames.length} server(s)
|
|
4483
6240
|
`));
|
|
4484
6241
|
let passed = 0;
|
|
@@ -4486,7 +6243,7 @@ var test_command_default = defineCommand22({
|
|
|
4486
6243
|
for (const name of serverNames) {
|
|
4487
6244
|
const entry = lockfile.servers[name];
|
|
4488
6245
|
if (!entry) {
|
|
4489
|
-
console.log(` ${
|
|
6246
|
+
console.log(` ${pc32.red("\u2717")} ${pc32.bold(name)} \u2014 not installed`);
|
|
4490
6247
|
failed++;
|
|
4491
6248
|
continue;
|
|
4492
6249
|
}
|
|
@@ -4497,27 +6254,27 @@ var test_command_default = defineCommand22({
|
|
|
4497
6254
|
if (result.passed) {
|
|
4498
6255
|
passed++;
|
|
4499
6256
|
console.log(
|
|
4500
|
-
` ${
|
|
6257
|
+
` ${pc32.green("\u2713")} ${pc32.bold(name)} ${pc32.dim(`(${result.responseTimeMs}ms)`)}`
|
|
4501
6258
|
);
|
|
4502
6259
|
if (result.tools.length > 0) {
|
|
4503
|
-
console.log(
|
|
6260
|
+
console.log(pc32.dim(` Tools: ${result.tools.join(", ")}`));
|
|
4504
6261
|
}
|
|
4505
6262
|
} else {
|
|
4506
6263
|
failed++;
|
|
4507
|
-
console.log(` ${
|
|
6264
|
+
console.log(` ${pc32.red("\u2717")} ${pc32.bold(name)} ${pc32.dim(`(${result.responseTimeMs}ms)`)}`);
|
|
4508
6265
|
if (result.error) {
|
|
4509
|
-
console.log(` ${
|
|
6266
|
+
console.log(` ${pc32.red(result.error)}`);
|
|
4510
6267
|
}
|
|
4511
6268
|
console.log(
|
|
4512
|
-
` ${
|
|
6269
|
+
` ${pc32.dim("initialize:")} ${result.initializeOk ? pc32.green("ok") : pc32.red("fail")} ${pc32.dim("tools/list:")} ${result.toolsListOk ? pc32.green("ok") : pc32.red("fail")}`
|
|
4513
6270
|
);
|
|
4514
6271
|
}
|
|
4515
6272
|
}
|
|
4516
|
-
console.log(
|
|
6273
|
+
console.log(pc32.dim(`
|
|
4517
6274
|
${"\u2500".repeat(40)}`));
|
|
4518
6275
|
const parts = [];
|
|
4519
|
-
if (passed > 0) parts.push(
|
|
4520
|
-
if (failed > 0) parts.push(
|
|
6276
|
+
if (passed > 0) parts.push(pc32.green(`${passed} passed`));
|
|
6277
|
+
if (failed > 0) parts.push(pc32.red(`${failed} failed`));
|
|
4521
6278
|
console.log(` ${parts.join(", ")}
|
|
4522
6279
|
`);
|
|
4523
6280
|
if (failed > 0) process.exit(1);
|
|
@@ -4535,24 +6292,24 @@ async function loadVaultSecrets3(serverName) {
|
|
|
4535
6292
|
}
|
|
4536
6293
|
|
|
4537
6294
|
// src/commands/update.ts
|
|
4538
|
-
import * as
|
|
4539
|
-
import { defineCommand as
|
|
4540
|
-
import
|
|
6295
|
+
import * as p12 from "@clack/prompts";
|
|
6296
|
+
import { defineCommand as defineCommand34 } from "citty";
|
|
6297
|
+
import pc34 from "picocolors";
|
|
4541
6298
|
|
|
4542
6299
|
// src/core/update-notifier.ts
|
|
4543
|
-
import
|
|
6300
|
+
import fs18 from "fs";
|
|
4544
6301
|
import os3 from "os";
|
|
4545
|
-
import
|
|
4546
|
-
import
|
|
4547
|
-
var CACHE_FILE =
|
|
6302
|
+
import path15 from "path";
|
|
6303
|
+
import pc33 from "picocolors";
|
|
6304
|
+
var CACHE_FILE = path15.join(os3.homedir(), ".mcpman", ".update-check");
|
|
4548
6305
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4549
6306
|
function writeUpdateCache(data) {
|
|
4550
6307
|
try {
|
|
4551
|
-
const dir =
|
|
4552
|
-
if (!
|
|
6308
|
+
const dir = path15.dirname(CACHE_FILE);
|
|
6309
|
+
if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
|
|
4553
6310
|
const tmp = `${CACHE_FILE}.tmp`;
|
|
4554
|
-
|
|
4555
|
-
|
|
6311
|
+
fs18.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
6312
|
+
fs18.renameSync(tmp, CACHE_FILE);
|
|
4556
6313
|
} catch {
|
|
4557
6314
|
}
|
|
4558
6315
|
}
|
|
@@ -4560,13 +6317,13 @@ function writeUpdateCache(data) {
|
|
|
4560
6317
|
// src/commands/update.ts
|
|
4561
6318
|
async function loadClients3() {
|
|
4562
6319
|
try {
|
|
4563
|
-
const mod = await import("./client-detector-
|
|
6320
|
+
const mod = await import("./client-detector-O2HN4MUB.js");
|
|
4564
6321
|
return mod.getInstalledClients();
|
|
4565
6322
|
} catch {
|
|
4566
6323
|
return [];
|
|
4567
6324
|
}
|
|
4568
6325
|
}
|
|
4569
|
-
function
|
|
6326
|
+
function printTable2(updates) {
|
|
4570
6327
|
const NAME_W = 28;
|
|
4571
6328
|
const VER_W = 10;
|
|
4572
6329
|
const header = [
|
|
@@ -4575,19 +6332,19 @@ function printTable(updates) {
|
|
|
4575
6332
|
"LATEST".padEnd(VER_W),
|
|
4576
6333
|
"STATUS"
|
|
4577
6334
|
].join(" ");
|
|
4578
|
-
console.log(
|
|
6335
|
+
console.log(pc34.bold(`
|
|
4579
6336
|
${header}`));
|
|
4580
|
-
console.log(
|
|
6337
|
+
console.log(pc34.dim(` ${"\u2500".repeat(NAME_W + VER_W * 2 + 20)}`));
|
|
4581
6338
|
for (const u of updates) {
|
|
4582
6339
|
const nameCol = u.server.slice(0, NAME_W).padEnd(NAME_W);
|
|
4583
6340
|
const curCol = u.currentVersion.padEnd(VER_W);
|
|
4584
6341
|
const latCol = u.latestVersion.padEnd(VER_W);
|
|
4585
|
-
const statusCol = u.hasUpdate ?
|
|
6342
|
+
const statusCol = u.hasUpdate ? pc34.yellow(`Update available${u.updateType ? ` [${u.updateType}]` : ""}`) : pc34.green("Up to date");
|
|
4586
6343
|
console.log(` ${nameCol} ${curCol} ${latCol} ${statusCol}`);
|
|
4587
6344
|
}
|
|
4588
6345
|
console.log();
|
|
4589
6346
|
}
|
|
4590
|
-
var update_default =
|
|
6347
|
+
var update_default = defineCommand34({
|
|
4591
6348
|
meta: {
|
|
4592
6349
|
name: "update",
|
|
4593
6350
|
description: "Check for and apply updates to installed MCP servers"
|
|
@@ -4626,7 +6383,7 @@ var update_default = defineCommand23({
|
|
|
4626
6383
|
}
|
|
4627
6384
|
process.exit(1);
|
|
4628
6385
|
}
|
|
4629
|
-
const spinner5 =
|
|
6386
|
+
const spinner5 = p12.spinner();
|
|
4630
6387
|
spinner5.start("Checking versions...");
|
|
4631
6388
|
let updates;
|
|
4632
6389
|
try {
|
|
@@ -4645,53 +6402,53 @@ var update_default = defineCommand23({
|
|
|
4645
6402
|
console.log(JSON.stringify(updates, null, 2));
|
|
4646
6403
|
return;
|
|
4647
6404
|
}
|
|
4648
|
-
|
|
6405
|
+
printTable2(updates);
|
|
4649
6406
|
const outdated = updates.filter((u) => u.hasUpdate);
|
|
4650
6407
|
if (outdated.length === 0) {
|
|
4651
|
-
console.log(
|
|
6408
|
+
console.log(pc34.green(" All servers are up to date."));
|
|
4652
6409
|
return;
|
|
4653
6410
|
}
|
|
4654
6411
|
if (args.check) {
|
|
4655
6412
|
console.log(
|
|
4656
|
-
|
|
6413
|
+
pc34.yellow(` ${outdated.length} update(s) available. Run mcpman update to apply.`)
|
|
4657
6414
|
);
|
|
4658
6415
|
return;
|
|
4659
6416
|
}
|
|
4660
6417
|
if (!args.yes) {
|
|
4661
|
-
const confirmed = await
|
|
6418
|
+
const confirmed = await p12.confirm({
|
|
4662
6419
|
message: `Apply ${outdated.length} update(s)?`,
|
|
4663
6420
|
initialValue: true
|
|
4664
6421
|
});
|
|
4665
|
-
if (
|
|
4666
|
-
|
|
6422
|
+
if (p12.isCancel(confirmed) || !confirmed) {
|
|
6423
|
+
p12.outro("Cancelled.");
|
|
4667
6424
|
return;
|
|
4668
6425
|
}
|
|
4669
6426
|
}
|
|
4670
6427
|
const clients = await loadClients3();
|
|
4671
6428
|
let successCount = 0;
|
|
4672
6429
|
for (const update of outdated) {
|
|
4673
|
-
const s =
|
|
6430
|
+
const s = p12.spinner();
|
|
4674
6431
|
s.start(`Updating ${update.server}...`);
|
|
4675
6432
|
const result = await applyServerUpdate(update.server, servers[update.server], clients);
|
|
4676
6433
|
if (result.success) {
|
|
4677
|
-
s.stop(`${
|
|
6434
|
+
s.stop(`${pc34.green("\u2713")} ${update.server}: ${result.fromVersion} \u2192 ${result.toVersion}`);
|
|
4678
6435
|
successCount++;
|
|
4679
6436
|
} else {
|
|
4680
|
-
s.stop(`${
|
|
6437
|
+
s.stop(`${pc34.red("\u2717")} ${update.server}: ${result.error}`);
|
|
4681
6438
|
}
|
|
4682
6439
|
}
|
|
4683
6440
|
const freshLockfile = readLockfile(resolveLockfilePath());
|
|
4684
6441
|
const freshUpdates = await checkAllVersions(freshLockfile);
|
|
4685
6442
|
writeUpdateCache({ lastCheck: (/* @__PURE__ */ new Date()).toISOString(), updates: freshUpdates });
|
|
4686
|
-
|
|
6443
|
+
p12.outro(`${successCount} of ${outdated.length} server(s) updated.`);
|
|
4687
6444
|
}
|
|
4688
6445
|
});
|
|
4689
6446
|
|
|
4690
6447
|
// src/commands/upgrade.ts
|
|
4691
|
-
import { execSync as
|
|
4692
|
-
import { defineCommand as
|
|
4693
|
-
import
|
|
4694
|
-
var upgrade_default =
|
|
6448
|
+
import { execSync as execSync5 } from "child_process";
|
|
6449
|
+
import { defineCommand as defineCommand35 } from "citty";
|
|
6450
|
+
import pc35 from "picocolors";
|
|
6451
|
+
var upgrade_default = defineCommand35({
|
|
4695
6452
|
meta: {
|
|
4696
6453
|
name: "upgrade",
|
|
4697
6454
|
description: "Upgrade mcpman to the latest version"
|
|
@@ -4704,42 +6461,214 @@ var upgrade_default = defineCommand24({
|
|
|
4704
6461
|
}
|
|
4705
6462
|
},
|
|
4706
6463
|
async run({ args }) {
|
|
4707
|
-
console.log(
|
|
6464
|
+
console.log(pc35.dim(` Current version: ${APP_VERSION}`));
|
|
4708
6465
|
let latest;
|
|
4709
6466
|
try {
|
|
4710
|
-
latest =
|
|
6467
|
+
latest = execSync5("npm view mcpman version", { encoding: "utf-8", timeout: 15e3 }).trim();
|
|
4711
6468
|
} catch {
|
|
4712
|
-
console.error(
|
|
6469
|
+
console.error(pc35.red(" Error: Could not check latest version from npm."));
|
|
4713
6470
|
process.exit(1);
|
|
4714
6471
|
}
|
|
4715
6472
|
if (latest === APP_VERSION) {
|
|
4716
|
-
console.log(
|
|
6473
|
+
console.log(pc35.green(" \u2713 Already on the latest version."));
|
|
4717
6474
|
return;
|
|
4718
6475
|
}
|
|
4719
|
-
console.log(
|
|
6476
|
+
console.log(pc35.yellow(` Update available: ${APP_VERSION} \u2192 ${latest}`));
|
|
4720
6477
|
if (args.check) {
|
|
4721
|
-
console.log(
|
|
6478
|
+
console.log(pc35.dim(" Run mcpman upgrade to install."));
|
|
4722
6479
|
return;
|
|
4723
6480
|
}
|
|
4724
|
-
console.log(
|
|
6481
|
+
console.log(pc35.dim(" Installing..."));
|
|
4725
6482
|
try {
|
|
4726
|
-
|
|
4727
|
-
console.log(
|
|
6483
|
+
execSync5(`npm install -g mcpman@${latest}`, { stdio: "inherit", timeout: 6e4 });
|
|
6484
|
+
console.log(pc35.green(`
|
|
4728
6485
|
\u2713 Upgraded to mcpman@${latest}`));
|
|
4729
6486
|
} catch {
|
|
4730
|
-
console.error(
|
|
6487
|
+
console.error(pc35.red(" Error: Upgrade failed. Try manually: npm install -g mcpman@latest"));
|
|
6488
|
+
process.exit(1);
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
});
|
|
6492
|
+
|
|
6493
|
+
// src/commands/validate.ts
|
|
6494
|
+
import { defineCommand as defineCommand36 } from "citty";
|
|
6495
|
+
import pc36 from "picocolors";
|
|
6496
|
+
|
|
6497
|
+
// src/core/config-validator.ts
|
|
6498
|
+
import fs19 from "fs";
|
|
6499
|
+
var REQUIRED_LOCK_FIELDS = ["version", "source", "command", "args"];
|
|
6500
|
+
var VALID_SOURCES = ["npm", "smithery", "github", "local"];
|
|
6501
|
+
function validateLockfile(filePath) {
|
|
6502
|
+
const target = filePath ?? resolveLockfilePath();
|
|
6503
|
+
const errors = [];
|
|
6504
|
+
if (!fs19.existsSync(target)) {
|
|
6505
|
+
return { file: target, valid: false, errors: ["Lockfile not found"] };
|
|
6506
|
+
}
|
|
6507
|
+
let data;
|
|
6508
|
+
try {
|
|
6509
|
+
data = JSON.parse(fs19.readFileSync(target, "utf-8"));
|
|
6510
|
+
} catch {
|
|
6511
|
+
return { file: target, valid: false, errors: ["Invalid JSON"] };
|
|
6512
|
+
}
|
|
6513
|
+
if (typeof data !== "object" || data === null) {
|
|
6514
|
+
return { file: target, valid: false, errors: ["Root must be an object"] };
|
|
6515
|
+
}
|
|
6516
|
+
const obj = data;
|
|
6517
|
+
if (obj.lockfileVersion !== 1) {
|
|
6518
|
+
errors.push(`lockfileVersion must be 1, got ${JSON.stringify(obj.lockfileVersion)}`);
|
|
6519
|
+
}
|
|
6520
|
+
if (typeof obj.servers !== "object" || obj.servers === null || Array.isArray(obj.servers)) {
|
|
6521
|
+
errors.push("servers must be an object");
|
|
6522
|
+
return { file: target, valid: errors.length === 0, errors };
|
|
6523
|
+
}
|
|
6524
|
+
const servers = obj.servers;
|
|
6525
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
6526
|
+
if (typeof entry !== "object" || entry === null) {
|
|
6527
|
+
errors.push(`servers.${name}: must be an object`);
|
|
6528
|
+
continue;
|
|
6529
|
+
}
|
|
6530
|
+
const e = entry;
|
|
6531
|
+
for (const field of REQUIRED_LOCK_FIELDS) {
|
|
6532
|
+
if (!(field in e)) {
|
|
6533
|
+
errors.push(`servers.${name}: missing required field "${field}"`);
|
|
6534
|
+
}
|
|
6535
|
+
}
|
|
6536
|
+
if (e.source && !VALID_SOURCES.includes(e.source)) {
|
|
6537
|
+
errors.push(`servers.${name}: invalid source "${e.source}"`);
|
|
6538
|
+
}
|
|
6539
|
+
if (!Array.isArray(e.args)) {
|
|
6540
|
+
errors.push(`servers.${name}: args must be an array`);
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
return { file: target, valid: errors.length === 0, errors };
|
|
6544
|
+
}
|
|
6545
|
+
function validateClientConfig(clientName) {
|
|
6546
|
+
let filePath;
|
|
6547
|
+
try {
|
|
6548
|
+
filePath = resolveConfigPath(clientName);
|
|
6549
|
+
} catch {
|
|
6550
|
+
return { file: clientName, valid: false, errors: [`Unknown client: ${clientName}`] };
|
|
6551
|
+
}
|
|
6552
|
+
const errors = [];
|
|
6553
|
+
if (!fs19.existsSync(filePath)) {
|
|
6554
|
+
return { file: filePath, valid: false, errors: ["Config file not found"] };
|
|
6555
|
+
}
|
|
6556
|
+
let data;
|
|
6557
|
+
try {
|
|
6558
|
+
data = JSON.parse(fs19.readFileSync(filePath, "utf-8"));
|
|
6559
|
+
} catch {
|
|
6560
|
+
return { file: filePath, valid: false, errors: ["Invalid JSON"] };
|
|
6561
|
+
}
|
|
6562
|
+
if (typeof data !== "object" || data === null) {
|
|
6563
|
+
return { file: filePath, valid: false, errors: ["Root must be an object"] };
|
|
6564
|
+
}
|
|
6565
|
+
const obj = data;
|
|
6566
|
+
const servers = obj.mcpServers ?? obj["mcp.servers"];
|
|
6567
|
+
if (!servers) {
|
|
6568
|
+
return { file: filePath, valid: true, errors: [] };
|
|
6569
|
+
}
|
|
6570
|
+
if (typeof servers !== "object" || Array.isArray(servers)) {
|
|
6571
|
+
errors.push("mcpServers must be an object");
|
|
6572
|
+
return { file: filePath, valid: false, errors };
|
|
6573
|
+
}
|
|
6574
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
6575
|
+
if (typeof entry !== "object" || entry === null) {
|
|
6576
|
+
errors.push(`mcpServers.${name}: must be an object`);
|
|
6577
|
+
continue;
|
|
6578
|
+
}
|
|
6579
|
+
const e = entry;
|
|
6580
|
+
if (!("command" in e) || typeof e.command !== "string") {
|
|
6581
|
+
errors.push(`mcpServers.${name}: missing or invalid "command"`);
|
|
6582
|
+
}
|
|
6583
|
+
if (!("args" in e) || !Array.isArray(e.args)) {
|
|
6584
|
+
errors.push(`mcpServers.${name}: missing or invalid "args" (must be array)`);
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
return { file: filePath, valid: errors.length === 0, errors };
|
|
6588
|
+
}
|
|
6589
|
+
function validateAll(lockfilePath) {
|
|
6590
|
+
const results = [validateLockfile(lockfilePath)];
|
|
6591
|
+
const clients = ["claude-desktop", "cursor", "vscode", "windsurf"];
|
|
6592
|
+
for (const client of clients) {
|
|
6593
|
+
const r = validateClientConfig(client);
|
|
6594
|
+
if (!r.errors.includes("Config file not found")) {
|
|
6595
|
+
results.push(r);
|
|
6596
|
+
}
|
|
6597
|
+
}
|
|
6598
|
+
return results;
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6601
|
+
// src/commands/validate.ts
|
|
6602
|
+
function printResult(r) {
|
|
6603
|
+
const icon = r.valid ? pc36.green("\u2713") : pc36.red("\u2717");
|
|
6604
|
+
const status = r.valid ? pc36.green("PASS") : pc36.red("FAIL");
|
|
6605
|
+
console.log(` ${icon} ${pc36.bold(r.file)} ${status}`);
|
|
6606
|
+
if (!r.valid) {
|
|
6607
|
+
for (const err of r.errors) {
|
|
6608
|
+
console.log(` ${pc36.dim("\xB7")} ${pc36.yellow(err)}`);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
var validate_default = defineCommand36({
|
|
6613
|
+
meta: {
|
|
6614
|
+
name: "validate",
|
|
6615
|
+
description: "Validate lockfile and client configs against expected schema"
|
|
6616
|
+
},
|
|
6617
|
+
args: {
|
|
6618
|
+
client: {
|
|
6619
|
+
type: "string",
|
|
6620
|
+
description: "Validate a specific client config (claude-desktop, cursor, vscode, windsurf)"
|
|
6621
|
+
},
|
|
6622
|
+
json: {
|
|
6623
|
+
type: "boolean",
|
|
6624
|
+
description: "Output results as JSON",
|
|
6625
|
+
default: false
|
|
6626
|
+
}
|
|
6627
|
+
},
|
|
6628
|
+
run({ args }) {
|
|
6629
|
+
let results;
|
|
6630
|
+
if (args.client) {
|
|
6631
|
+
results = [validateClientConfig(args.client)];
|
|
6632
|
+
} else {
|
|
6633
|
+
results = validateAll();
|
|
6634
|
+
}
|
|
6635
|
+
if (args.json) {
|
|
6636
|
+
console.log(JSON.stringify(results, null, 2));
|
|
6637
|
+
const anyFail = results.some((r) => !r.valid);
|
|
6638
|
+
if (anyFail) process.exit(1);
|
|
6639
|
+
return;
|
|
6640
|
+
}
|
|
6641
|
+
console.log(pc36.bold("\n mcpman validate\n"));
|
|
6642
|
+
console.log(pc36.dim(` ${"\u2500".repeat(60)}`));
|
|
6643
|
+
for (const r of results) {
|
|
6644
|
+
printResult(r);
|
|
6645
|
+
}
|
|
6646
|
+
console.log(pc36.dim(`
|
|
6647
|
+
${"\u2500".repeat(60)}`));
|
|
6648
|
+
const passed = results.filter((r) => r.valid).length;
|
|
6649
|
+
const failed = results.filter((r) => !r.valid).length;
|
|
6650
|
+
if (failed === 0) {
|
|
6651
|
+
console.log(`
|
|
6652
|
+
${pc36.green("All files valid")} (${passed} checked)
|
|
6653
|
+
`);
|
|
6654
|
+
} else {
|
|
6655
|
+
console.log(
|
|
6656
|
+
`
|
|
6657
|
+
${pc36.red(`${failed} file(s) failed`)} ${pc36.dim("\xB7")} ${pc36.green(`${passed} passed`)}
|
|
6658
|
+
`
|
|
6659
|
+
);
|
|
4731
6660
|
process.exit(1);
|
|
4732
6661
|
}
|
|
4733
6662
|
}
|
|
4734
6663
|
});
|
|
4735
6664
|
|
|
4736
6665
|
// src/commands/watch.ts
|
|
4737
|
-
import { defineCommand as
|
|
4738
|
-
import
|
|
6666
|
+
import { defineCommand as defineCommand37 } from "citty";
|
|
6667
|
+
import pc37 from "picocolors";
|
|
4739
6668
|
|
|
4740
6669
|
// src/core/file-watcher-service.ts
|
|
4741
|
-
import { spawn as
|
|
4742
|
-
import
|
|
6670
|
+
import { spawn as spawn8 } from "child_process";
|
|
6671
|
+
import fs20 from "fs";
|
|
4743
6672
|
var IGNORE_PATTERNS = [
|
|
4744
6673
|
"node_modules",
|
|
4745
6674
|
".git",
|
|
@@ -4751,7 +6680,7 @@ var IGNORE_PATTERNS = [
|
|
|
4751
6680
|
".tox"
|
|
4752
6681
|
];
|
|
4753
6682
|
function shouldIgnore(filename) {
|
|
4754
|
-
return IGNORE_PATTERNS.some((
|
|
6683
|
+
return IGNORE_PATTERNS.some((p13) => filename.includes(p13));
|
|
4755
6684
|
}
|
|
4756
6685
|
function hasWatchedExtension(filename, extensions) {
|
|
4757
6686
|
return extensions.some((ext) => filename.endsWith(`.${ext}`));
|
|
@@ -4777,7 +6706,7 @@ var ServerWatcher = class {
|
|
|
4777
6706
|
`);
|
|
4778
6707
|
this.spawnChild();
|
|
4779
6708
|
try {
|
|
4780
|
-
this.watcher =
|
|
6709
|
+
this.watcher = fs20.watch(options.watchDir, { recursive: true }, (_event, filename) => {
|
|
4781
6710
|
if (!filename) return;
|
|
4782
6711
|
this.onFileChange(filename);
|
|
4783
6712
|
});
|
|
@@ -4812,7 +6741,7 @@ var ServerWatcher = class {
|
|
|
4812
6741
|
process.stdout.write("\x1Bc");
|
|
4813
6742
|
}
|
|
4814
6743
|
console.log(` [${timestamp()}] Starting ${serverName}...`);
|
|
4815
|
-
this.child =
|
|
6744
|
+
this.child = spawn8(command, args, { env, stdio: ["pipe", "pipe", "pipe"] });
|
|
4816
6745
|
this.child.stdout?.on("data", (data) => {
|
|
4817
6746
|
process.stdout.write(` [stdout] ${data.toString().trimEnd()}
|
|
4818
6747
|
`);
|
|
@@ -4876,7 +6805,7 @@ var ServerWatcher = class {
|
|
|
4876
6805
|
// src/commands/watch.ts
|
|
4877
6806
|
var DEFAULT_EXTENSIONS = ["ts", "js", "json", "py", "mjs", "cjs"];
|
|
4878
6807
|
var DEFAULT_DEBOUNCE_MS = 300;
|
|
4879
|
-
var watch_default =
|
|
6808
|
+
var watch_default = defineCommand37({
|
|
4880
6809
|
meta: {
|
|
4881
6810
|
name: "watch",
|
|
4882
6811
|
description: "Watch a local MCP server for file changes and auto-restart"
|
|
@@ -4915,8 +6844,8 @@ var watch_default = defineCommand25({
|
|
|
4915
6844
|
const lockfile = readLockfile();
|
|
4916
6845
|
const entry = lockfile.servers[serverName];
|
|
4917
6846
|
if (!entry) {
|
|
4918
|
-
console.error(
|
|
4919
|
-
console.error(
|
|
6847
|
+
console.error(pc37.red(` Error: Server '${serverName}' not found in lockfile.`));
|
|
6848
|
+
console.error(pc37.dim(` Run ${pc37.cyan("mcpman link .")} to register a local server.`));
|
|
4920
6849
|
process.exit(1);
|
|
4921
6850
|
}
|
|
4922
6851
|
let watchDir = args.dir;
|
|
@@ -4924,8 +6853,8 @@ var watch_default = defineCommand25({
|
|
|
4924
6853
|
if (entry.source === "local" && entry.resolved) {
|
|
4925
6854
|
watchDir = entry.resolved;
|
|
4926
6855
|
} else {
|
|
4927
|
-
console.error(
|
|
4928
|
-
console.error(
|
|
6856
|
+
console.error(pc37.red(` Error: Cannot determine watch directory for '${serverName}'.`));
|
|
6857
|
+
console.error(pc37.dim(" Use --dir to specify the directory to watch."));
|
|
4929
6858
|
process.exit(1);
|
|
4930
6859
|
}
|
|
4931
6860
|
}
|
|
@@ -4971,12 +6900,12 @@ async function loadVaultSecrets4(serverName) {
|
|
|
4971
6900
|
}
|
|
4972
6901
|
|
|
4973
6902
|
// src/commands/why.ts
|
|
4974
|
-
import { defineCommand as
|
|
4975
|
-
import
|
|
6903
|
+
import { defineCommand as defineCommand38 } from "citty";
|
|
6904
|
+
import pc38 from "picocolors";
|
|
4976
6905
|
|
|
4977
6906
|
// src/core/why-service.ts
|
|
4978
|
-
import
|
|
4979
|
-
import
|
|
6907
|
+
import fs21 from "fs";
|
|
6908
|
+
import path16 from "path";
|
|
4980
6909
|
var ALL_CLIENT_TYPES = ["claude-desktop", "cursor", "vscode", "windsurf"];
|
|
4981
6910
|
async function getServerProvenance(serverName, lockfilePath, profilesDir) {
|
|
4982
6911
|
const lockfile = readLockfile(lockfilePath);
|
|
@@ -5020,7 +6949,7 @@ async function buildClientStatuses(serverName, lockfileClients) {
|
|
|
5020
6949
|
}));
|
|
5021
6950
|
}
|
|
5022
6951
|
async function findOrphanedClients(serverName) {
|
|
5023
|
-
const { getInstalledClients: getInstalledClients2 } = await import("./client-detector-
|
|
6952
|
+
const { getInstalledClients: getInstalledClients2 } = await import("./client-detector-O2HN4MUB.js");
|
|
5024
6953
|
const handlers = await getInstalledClients2();
|
|
5025
6954
|
const results = [];
|
|
5026
6955
|
for (const handler of handlers) {
|
|
@@ -5036,16 +6965,16 @@ async function findOrphanedClients(serverName) {
|
|
|
5036
6965
|
}
|
|
5037
6966
|
function scanProfiles(serverName, profilesDir) {
|
|
5038
6967
|
const found = [];
|
|
5039
|
-
if (!
|
|
6968
|
+
if (!fs21.existsSync(profilesDir)) return found;
|
|
5040
6969
|
let files;
|
|
5041
6970
|
try {
|
|
5042
|
-
files =
|
|
6971
|
+
files = fs21.readdirSync(profilesDir).filter((f) => f.endsWith(".json"));
|
|
5043
6972
|
} catch {
|
|
5044
6973
|
return found;
|
|
5045
6974
|
}
|
|
5046
6975
|
for (const file of files) {
|
|
5047
6976
|
try {
|
|
5048
|
-
const raw =
|
|
6977
|
+
const raw = fs21.readFileSync(path16.join(profilesDir, file), "utf-8");
|
|
5049
6978
|
const profile = JSON.parse(raw);
|
|
5050
6979
|
if (serverName in (profile.servers ?? {})) {
|
|
5051
6980
|
found.push(profile.name ?? file.replace(".json", ""));
|
|
@@ -5074,8 +7003,8 @@ function formatWhyOutput(result) {
|
|
|
5074
7003
|
if (result.profiles.length > 0) {
|
|
5075
7004
|
lines.push("");
|
|
5076
7005
|
lines.push(" Profiles:");
|
|
5077
|
-
for (const
|
|
5078
|
-
lines.push(` ${
|
|
7006
|
+
for (const p13 of result.profiles) {
|
|
7007
|
+
lines.push(` ${p13}`);
|
|
5079
7008
|
}
|
|
5080
7009
|
}
|
|
5081
7010
|
if (result.envVars.length > 0) {
|
|
@@ -5089,7 +7018,7 @@ function formatWhyOutput(result) {
|
|
|
5089
7018
|
}
|
|
5090
7019
|
|
|
5091
7020
|
// src/commands/why.ts
|
|
5092
|
-
var why_default =
|
|
7021
|
+
var why_default = defineCommand38({
|
|
5093
7022
|
meta: {
|
|
5094
7023
|
name: "why",
|
|
5095
7024
|
description: "Show why a server is installed (provenance, clients, profiles)"
|
|
@@ -5111,15 +7040,15 @@ var why_default = defineCommand26({
|
|
|
5111
7040
|
const asJson = args.json;
|
|
5112
7041
|
const result = await getServerProvenance(serverName);
|
|
5113
7042
|
if (!result) {
|
|
5114
|
-
console.error(
|
|
5115
|
-
console.error(
|
|
7043
|
+
console.error(pc38.red(` Server '${serverName}' not found in lockfile or any client config.`));
|
|
7044
|
+
console.error(pc38.dim(` Run ${pc38.cyan("mcpman list")} to see installed servers.`));
|
|
5116
7045
|
process.exit(1);
|
|
5117
7046
|
}
|
|
5118
7047
|
if (result.orphaned) {
|
|
5119
|
-
console.log(
|
|
7048
|
+
console.log(pc38.yellow(`
|
|
5120
7049
|
Server '${serverName}' is orphaned:`));
|
|
5121
|
-
console.log(
|
|
5122
|
-
console.log(
|
|
7050
|
+
console.log(pc38.dim(" Found in client config(s) but not in lockfile."));
|
|
7051
|
+
console.log(pc38.dim(` Run ${pc38.cyan("mcpman sync --remove")} to clean up.
|
|
5123
7052
|
`));
|
|
5124
7053
|
const registeredClients = result.clients.filter((c) => c.registered).map((c) => c.type);
|
|
5125
7054
|
if (registeredClients.length > 0) {
|
|
@@ -5142,7 +7071,7 @@ process.on("SIGINT", () => {
|
|
|
5142
7071
|
console.log("\nAborted.");
|
|
5143
7072
|
process.exit(130);
|
|
5144
7073
|
});
|
|
5145
|
-
var main =
|
|
7074
|
+
var main = defineCommand39({
|
|
5146
7075
|
meta: {
|
|
5147
7076
|
name: APP_NAME,
|
|
5148
7077
|
version: APP_VERSION,
|
|
@@ -5174,7 +7103,19 @@ var main = defineCommand27({
|
|
|
5174
7103
|
watch: watch_default,
|
|
5175
7104
|
registry: registry_default,
|
|
5176
7105
|
completions: completions_default,
|
|
5177
|
-
why: why_default
|
|
7106
|
+
why: why_default,
|
|
7107
|
+
env: env_default,
|
|
7108
|
+
bench: bench_default,
|
|
7109
|
+
diff: diff_default,
|
|
7110
|
+
group: group_default,
|
|
7111
|
+
pin: pin_default,
|
|
7112
|
+
rollback: rollback_default,
|
|
7113
|
+
validate: validate_default,
|
|
7114
|
+
status: status_default,
|
|
7115
|
+
replay: replay_default,
|
|
7116
|
+
alias: alias_default,
|
|
7117
|
+
template: template_default,
|
|
7118
|
+
notify: notify_default
|
|
5178
7119
|
}
|
|
5179
7120
|
});
|
|
5180
7121
|
runMain(main);
|