mcpman 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{trust-scorer-LYC6KZCD.js → chunk-RGKHLY5G.js} +1 -0
- package/dist/{chunk-QY22QTBR.js → chunk-RMMEBP2J.js} +7 -0
- package/dist/{client-detector-SUIJSIYM.js → client-detector-UAP2EYZA.js} +1 -1
- package/dist/index.cjs +791 -219
- package/dist/index.js +763 -203
- package/dist/trust-scorer-G74WK25Q.js +7 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
computeTrustScore
|
|
4
|
+
} from "./chunk-RGKHLY5G.js";
|
|
5
|
+
import {
|
|
6
|
+
getConfigPath,
|
|
3
7
|
getInstalledClients
|
|
4
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-RMMEBP2J.js";
|
|
5
9
|
import {
|
|
6
10
|
getMasterPassword,
|
|
7
11
|
getSecretsForServer,
|
|
@@ -11,7 +15,7 @@ import {
|
|
|
11
15
|
} from "./chunk-6X6Q6UZC.js";
|
|
12
16
|
|
|
13
17
|
// src/index.ts
|
|
14
|
-
import { defineCommand as
|
|
18
|
+
import { defineCommand as defineCommand14, runMain } from "citty";
|
|
15
19
|
|
|
16
20
|
// src/commands/audit.ts
|
|
17
21
|
import { defineCommand } from "citty";
|
|
@@ -193,8 +197,8 @@ async function scanServer(name, entry) {
|
|
|
193
197
|
fetchNpmMetadata(name),
|
|
194
198
|
fetchVulnerabilities(name, entry.version)
|
|
195
199
|
]);
|
|
196
|
-
const { computeTrustScore } = await import("./trust-scorer-
|
|
197
|
-
const { score, riskLevel } =
|
|
200
|
+
const { computeTrustScore: computeTrustScore2 } = await import("./trust-scorer-G74WK25Q.js");
|
|
201
|
+
const { score, riskLevel } = computeTrustScore2(metadata, vulnerabilities);
|
|
198
202
|
const report = {
|
|
199
203
|
server: name,
|
|
200
204
|
source: "npm",
|
|
@@ -211,11 +215,11 @@ async function scanAllServers(servers, concurrency = 3) {
|
|
|
211
215
|
const results = [];
|
|
212
216
|
const executing = /* @__PURE__ */ new Set();
|
|
213
217
|
for (const [name, entry] of entries) {
|
|
214
|
-
const
|
|
218
|
+
const p11 = scanServer(name, entry).then((r) => {
|
|
215
219
|
results.push(r);
|
|
216
|
-
executing.delete(
|
|
220
|
+
executing.delete(p11);
|
|
217
221
|
});
|
|
218
|
-
executing.add(
|
|
222
|
+
executing.add(p11);
|
|
219
223
|
if (executing.size >= concurrency) await Promise.race(executing);
|
|
220
224
|
}
|
|
221
225
|
await Promise.all(executing);
|
|
@@ -329,11 +333,11 @@ async function checkAllVersions(lockfile) {
|
|
|
329
333
|
const results = [];
|
|
330
334
|
const executing = /* @__PURE__ */ new Set();
|
|
331
335
|
for (const [name, entry] of entries) {
|
|
332
|
-
const
|
|
336
|
+
const p11 = checkVersion(name, entry).then((r) => {
|
|
333
337
|
results.push(r);
|
|
334
|
-
executing.delete(
|
|
338
|
+
executing.delete(p11);
|
|
335
339
|
});
|
|
336
|
-
executing.add(
|
|
340
|
+
executing.add(p11);
|
|
337
341
|
if (executing.size >= 5) {
|
|
338
342
|
await Promise.race(executing);
|
|
339
343
|
}
|
|
@@ -671,7 +675,7 @@ var audit_default = defineCommand({
|
|
|
671
675
|
});
|
|
672
676
|
async function loadClients() {
|
|
673
677
|
try {
|
|
674
|
-
const mod = await import("./client-detector-
|
|
678
|
+
const mod = await import("./client-detector-UAP2EYZA.js");
|
|
675
679
|
return mod.getInstalledClients();
|
|
676
680
|
} catch {
|
|
677
681
|
return [];
|
|
@@ -770,9 +774,156 @@ async function runAuditFix(reports, servers, skipConfirm) {
|
|
|
770
774
|
`);
|
|
771
775
|
}
|
|
772
776
|
|
|
773
|
-
// src/commands/
|
|
777
|
+
// src/commands/config.ts
|
|
774
778
|
import { defineCommand as defineCommand2 } from "citty";
|
|
775
779
|
import pc2 from "picocolors";
|
|
780
|
+
import * as p2 from "@clack/prompts";
|
|
781
|
+
|
|
782
|
+
// src/core/config-service.ts
|
|
783
|
+
import fs3 from "fs";
|
|
784
|
+
import path3 from "path";
|
|
785
|
+
var VALID_KEYS = /* @__PURE__ */ new Set([
|
|
786
|
+
"defaultClient",
|
|
787
|
+
"updateCheckInterval",
|
|
788
|
+
"preferredRegistry",
|
|
789
|
+
"vaultTimeout"
|
|
790
|
+
]);
|
|
791
|
+
function readConfig(configPath = getConfigPath()) {
|
|
792
|
+
try {
|
|
793
|
+
const raw = fs3.readFileSync(configPath, "utf-8");
|
|
794
|
+
const parsed = JSON.parse(raw);
|
|
795
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
796
|
+
return {};
|
|
797
|
+
}
|
|
798
|
+
return parsed;
|
|
799
|
+
} catch {
|
|
800
|
+
return {};
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
function writeConfig(data, configPath = getConfigPath()) {
|
|
804
|
+
const dir = path3.dirname(configPath);
|
|
805
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
806
|
+
const tmp = `${configPath}.tmp`;
|
|
807
|
+
fs3.writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
808
|
+
fs3.renameSync(tmp, configPath);
|
|
809
|
+
}
|
|
810
|
+
function getConfigValue(key, configPath = getConfigPath()) {
|
|
811
|
+
const data = readConfig(configPath);
|
|
812
|
+
if (!VALID_KEYS.has(key)) return void 0;
|
|
813
|
+
return data[key];
|
|
814
|
+
}
|
|
815
|
+
function setConfigValue(key, value, configPath = getConfigPath()) {
|
|
816
|
+
const data = readConfig(configPath);
|
|
817
|
+
if (!VALID_KEYS.has(key)) {
|
|
818
|
+
throw new Error(`Unknown config key: "${key}". Valid keys: ${[...VALID_KEYS].join(", ")}`);
|
|
819
|
+
}
|
|
820
|
+
data[key] = value;
|
|
821
|
+
writeConfig(data, configPath);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/commands/config.ts
|
|
825
|
+
function coerceValue(raw) {
|
|
826
|
+
if (raw === "true") return true;
|
|
827
|
+
if (raw === "false") return false;
|
|
828
|
+
const num = Number(raw);
|
|
829
|
+
if (!Number.isNaN(num) && raw.trim() !== "") return num;
|
|
830
|
+
return raw;
|
|
831
|
+
}
|
|
832
|
+
var setCommand = defineCommand2({
|
|
833
|
+
meta: { name: "set", description: "Set a config value" },
|
|
834
|
+
args: {
|
|
835
|
+
key: {
|
|
836
|
+
type: "positional",
|
|
837
|
+
description: "Config key (e.g. defaultClient)",
|
|
838
|
+
required: true
|
|
839
|
+
},
|
|
840
|
+
value: {
|
|
841
|
+
type: "positional",
|
|
842
|
+
description: "Value to set",
|
|
843
|
+
required: true
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
run({ args }) {
|
|
847
|
+
try {
|
|
848
|
+
const coerced = coerceValue(args.value);
|
|
849
|
+
setConfigValue(args.key, coerced);
|
|
850
|
+
console.log(
|
|
851
|
+
`${pc2.green("\u2713")} Set ${pc2.bold(args.key)} = ${pc2.cyan(String(coerced))}`
|
|
852
|
+
);
|
|
853
|
+
} catch (err) {
|
|
854
|
+
console.error(`${pc2.red("\u2717")} ${String(err)}`);
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
var getCommand = defineCommand2({
|
|
860
|
+
meta: { name: "get", description: "Get a config value" },
|
|
861
|
+
args: {
|
|
862
|
+
key: {
|
|
863
|
+
type: "positional",
|
|
864
|
+
description: "Config key to read",
|
|
865
|
+
required: true
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
run({ args }) {
|
|
869
|
+
const val = getConfigValue(args.key);
|
|
870
|
+
if (val === void 0) {
|
|
871
|
+
console.log(pc2.dim(`${args.key}: (not set)`));
|
|
872
|
+
} else {
|
|
873
|
+
console.log(`${pc2.bold(args.key)}: ${pc2.cyan(String(val))}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
var listCommand = defineCommand2({
|
|
878
|
+
meta: { name: "list", description: "List all config values" },
|
|
879
|
+
run() {
|
|
880
|
+
const data = readConfig();
|
|
881
|
+
const entries = Object.entries(data);
|
|
882
|
+
if (entries.length === 0) {
|
|
883
|
+
console.log(pc2.dim("No config values set. Use `mcpman config set <key> <value>`."));
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
console.log("");
|
|
887
|
+
console.log(pc2.bold("mcpman config:"));
|
|
888
|
+
console.log("");
|
|
889
|
+
for (const [key, val] of entries) {
|
|
890
|
+
console.log(` ${pc2.green("\u25CF")} ${pc2.bold(key)} ${pc2.cyan(String(val))}`);
|
|
891
|
+
}
|
|
892
|
+
console.log("");
|
|
893
|
+
console.log(pc2.dim(` ${entries.length} key${entries.length !== 1 ? "s" : ""} configured`));
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
var resetCommand = defineCommand2({
|
|
897
|
+
meta: { name: "reset", description: "Reset config to defaults (removes config file)" },
|
|
898
|
+
async run() {
|
|
899
|
+
const confirmed = await p2.confirm({
|
|
900
|
+
message: "Reset all config values to defaults?",
|
|
901
|
+
initialValue: false
|
|
902
|
+
});
|
|
903
|
+
if (p2.isCancel(confirmed) || !confirmed) {
|
|
904
|
+
p2.cancel("Cancelled.");
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
writeConfig({});
|
|
908
|
+
console.log(`${pc2.green("\u2713")} Config reset to defaults.`);
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
var config_default = defineCommand2({
|
|
912
|
+
meta: {
|
|
913
|
+
name: "config",
|
|
914
|
+
description: "Manage mcpman CLI configuration"
|
|
915
|
+
},
|
|
916
|
+
subCommands: {
|
|
917
|
+
set: setCommand,
|
|
918
|
+
get: getCommand,
|
|
919
|
+
list: listCommand,
|
|
920
|
+
reset: resetCommand
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// src/commands/doctor.ts
|
|
925
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
926
|
+
import pc3 from "picocolors";
|
|
776
927
|
|
|
777
928
|
// src/core/server-inventory.ts
|
|
778
929
|
async function getInstalledServers(clientFilter) {
|
|
@@ -1036,12 +1187,12 @@ async function quickHealthProbe(config, timeoutMs = 3e3) {
|
|
|
1036
1187
|
|
|
1037
1188
|
// src/commands/doctor.ts
|
|
1038
1189
|
var CHECK_ICON = {
|
|
1039
|
-
pass:
|
|
1040
|
-
fail:
|
|
1041
|
-
skip:
|
|
1042
|
-
warn:
|
|
1190
|
+
pass: pc3.green("\u2713"),
|
|
1191
|
+
fail: pc3.red("\u2717"),
|
|
1192
|
+
skip: pc3.dim("-"),
|
|
1193
|
+
warn: pc3.yellow("\u26A0")
|
|
1043
1194
|
};
|
|
1044
|
-
var doctor_default =
|
|
1195
|
+
var doctor_default = defineCommand3({
|
|
1045
1196
|
meta: {
|
|
1046
1197
|
name: "doctor",
|
|
1047
1198
|
description: "Check MCP server health and configuration"
|
|
@@ -1054,10 +1205,10 @@ var doctor_default = defineCommand2({
|
|
|
1054
1205
|
}
|
|
1055
1206
|
},
|
|
1056
1207
|
async run({ args }) {
|
|
1057
|
-
console.log(
|
|
1208
|
+
console.log(pc3.bold("\n mcpman doctor\n"));
|
|
1058
1209
|
const servers = await getInstalledServers();
|
|
1059
1210
|
if (servers.length === 0) {
|
|
1060
|
-
console.log(
|
|
1211
|
+
console.log(pc3.dim(" No MCP servers installed. Run mcpman install <server> to get started."));
|
|
1061
1212
|
return;
|
|
1062
1213
|
}
|
|
1063
1214
|
const tasks = servers.map((s) => () => checkServerHealth(s.name, s.config));
|
|
@@ -1069,14 +1220,14 @@ var doctor_default = defineCommand2({
|
|
|
1069
1220
|
if (result.status === "healthy") passed++;
|
|
1070
1221
|
else failed++;
|
|
1071
1222
|
}
|
|
1072
|
-
console.log(
|
|
1223
|
+
console.log(pc3.dim(" " + "\u2500".repeat(50)));
|
|
1073
1224
|
const parts = [];
|
|
1074
|
-
if (passed > 0) parts.push(
|
|
1075
|
-
if (failed > 0) parts.push(
|
|
1225
|
+
if (passed > 0) parts.push(pc3.green(`${passed} healthy`));
|
|
1226
|
+
if (failed > 0) parts.push(pc3.red(`${failed} unhealthy`));
|
|
1076
1227
|
console.log(` Summary: ${parts.join(", ")}`);
|
|
1077
1228
|
if (failed > 0) {
|
|
1078
1229
|
if (!args.fix) {
|
|
1079
|
-
console.log(
|
|
1230
|
+
console.log(pc3.dim(` Run ${pc3.cyan("mcpman doctor --fix")} for fix suggestions.
|
|
1080
1231
|
`));
|
|
1081
1232
|
}
|
|
1082
1233
|
process.exit(1);
|
|
@@ -1085,13 +1236,13 @@ var doctor_default = defineCommand2({
|
|
|
1085
1236
|
}
|
|
1086
1237
|
});
|
|
1087
1238
|
function printServerResult(result, showFix) {
|
|
1088
|
-
const icon = result.status === "healthy" ?
|
|
1089
|
-
console.log(` ${icon} ${
|
|
1239
|
+
const icon = result.status === "healthy" ? pc3.green("\u25CF") : pc3.red("\u25CF");
|
|
1240
|
+
console.log(` ${icon} ${pc3.bold(result.serverName)}`);
|
|
1090
1241
|
for (const check of result.checks) {
|
|
1091
1242
|
const checkIcon = check.skipped ? CHECK_ICON.skip : check.passed ? CHECK_ICON.pass : CHECK_ICON.fail;
|
|
1092
1243
|
console.log(` ${checkIcon} ${check.name}: ${check.message}`);
|
|
1093
1244
|
if (showFix && !check.passed && !check.skipped && check.fix) {
|
|
1094
|
-
console.log(` ${
|
|
1245
|
+
console.log(` ${pc3.yellow("\u2192")} Fix: ${pc3.cyan(check.fix)}`);
|
|
1095
1246
|
}
|
|
1096
1247
|
}
|
|
1097
1248
|
console.log();
|
|
@@ -1100,11 +1251,11 @@ async function runParallel(tasks, concurrency) {
|
|
|
1100
1251
|
const results = [];
|
|
1101
1252
|
const executing = /* @__PURE__ */ new Set();
|
|
1102
1253
|
for (const task of tasks) {
|
|
1103
|
-
const
|
|
1254
|
+
const p11 = task().then((r) => {
|
|
1104
1255
|
results.push(r);
|
|
1105
|
-
executing.delete(
|
|
1256
|
+
executing.delete(p11);
|
|
1106
1257
|
});
|
|
1107
|
-
executing.add(
|
|
1258
|
+
executing.add(p11);
|
|
1108
1259
|
if (executing.size >= concurrency) {
|
|
1109
1260
|
await Promise.race(executing);
|
|
1110
1261
|
}
|
|
@@ -1113,11 +1264,173 @@ async function runParallel(tasks, concurrency) {
|
|
|
1113
1264
|
return results;
|
|
1114
1265
|
}
|
|
1115
1266
|
|
|
1267
|
+
// src/commands/info.ts
|
|
1268
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
1269
|
+
import pc4 from "picocolors";
|
|
1270
|
+
import { createSpinner as createSpinner2 } from "nanospinner";
|
|
1271
|
+
|
|
1272
|
+
// src/core/package-info.ts
|
|
1273
|
+
async function buildInfo(name, entry, source = "npm") {
|
|
1274
|
+
const resolvedSource = entry?.source ?? source;
|
|
1275
|
+
let weeklyDownloads = 0;
|
|
1276
|
+
let maintainerCount = 0;
|
|
1277
|
+
let packageAge = 0;
|
|
1278
|
+
let lastPublish = "";
|
|
1279
|
+
let deprecated = false;
|
|
1280
|
+
let trustScore = null;
|
|
1281
|
+
let riskLevel = "UNKNOWN";
|
|
1282
|
+
if (resolvedSource === "npm") {
|
|
1283
|
+
const metadata = await fetchNpmMetadata(name);
|
|
1284
|
+
if (!metadata && !entry) {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
if (metadata) {
|
|
1288
|
+
weeklyDownloads = metadata.weeklyDownloads;
|
|
1289
|
+
maintainerCount = metadata.maintainerCount;
|
|
1290
|
+
packageAge = metadata.packageAge;
|
|
1291
|
+
lastPublish = metadata.lastPublish;
|
|
1292
|
+
deprecated = metadata.deprecated;
|
|
1293
|
+
const scored = computeTrustScore(metadata, []);
|
|
1294
|
+
trustScore = scored.score;
|
|
1295
|
+
riskLevel = scored.riskLevel;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
name,
|
|
1300
|
+
version: entry?.version ?? "unknown",
|
|
1301
|
+
description: "",
|
|
1302
|
+
source: resolvedSource,
|
|
1303
|
+
runtime: entry?.runtime ?? "node",
|
|
1304
|
+
envVars: entry?.envVars ?? [],
|
|
1305
|
+
weeklyDownloads,
|
|
1306
|
+
maintainerCount,
|
|
1307
|
+
packageAge,
|
|
1308
|
+
lastPublish,
|
|
1309
|
+
deprecated,
|
|
1310
|
+
trustScore,
|
|
1311
|
+
riskLevel,
|
|
1312
|
+
installedClients: entry?.clients ?? [],
|
|
1313
|
+
isInstalled: entry !== null
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
async function getPackageInfo(serverName) {
|
|
1317
|
+
const lockfile = readLockfile();
|
|
1318
|
+
const entry = lockfile.servers[serverName] ?? null;
|
|
1319
|
+
return buildInfo(serverName, entry);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/commands/info.ts
|
|
1323
|
+
function colorRisk2(score, riskLevel) {
|
|
1324
|
+
const label = score !== null ? `${score}/100 (${riskLevel})` : riskLevel;
|
|
1325
|
+
if (riskLevel === "LOW") return pc4.green(label);
|
|
1326
|
+
if (riskLevel === "MEDIUM") return pc4.yellow(label);
|
|
1327
|
+
if (riskLevel === "HIGH") return pc4.red(label);
|
|
1328
|
+
if (riskLevel === "CRITICAL") return pc4.bold(pc4.red(label));
|
|
1329
|
+
return pc4.dim(label);
|
|
1330
|
+
}
|
|
1331
|
+
function formatDaysAgo(isoDate) {
|
|
1332
|
+
if (!isoDate) return "unknown";
|
|
1333
|
+
const days = Math.floor((Date.now() - new Date(isoDate).getTime()) / 864e5);
|
|
1334
|
+
if (days === 0) return "today";
|
|
1335
|
+
if (days === 1) return "1 day ago";
|
|
1336
|
+
return `${days} days ago`;
|
|
1337
|
+
}
|
|
1338
|
+
function printInfo(info2) {
|
|
1339
|
+
const installedBadge = info2.isInstalled ? pc4.green(" [installed]") : pc4.dim(" [not installed]");
|
|
1340
|
+
console.log();
|
|
1341
|
+
console.log(pc4.bold(` ${info2.name}@${info2.version}`) + installedBadge);
|
|
1342
|
+
console.log(pc4.dim(" " + "\u2500".repeat(60)));
|
|
1343
|
+
console.log(` ${pc4.dim("Source:")} ${info2.source}`);
|
|
1344
|
+
console.log(` ${pc4.dim("Runtime:")} ${info2.runtime}`);
|
|
1345
|
+
if (info2.description) {
|
|
1346
|
+
console.log(` ${pc4.dim("Description:")} ${info2.description}`);
|
|
1347
|
+
}
|
|
1348
|
+
if (info2.deprecated) {
|
|
1349
|
+
console.log(` ${pc4.red("[DEPRECATED]")} This package is deprecated`);
|
|
1350
|
+
}
|
|
1351
|
+
console.log();
|
|
1352
|
+
console.log(` ${pc4.bold("Trust & Security")}`);
|
|
1353
|
+
console.log(` ${pc4.dim("Trust score:")} ${colorRisk2(info2.trustScore, info2.riskLevel)}`);
|
|
1354
|
+
if (info2.source === "npm") {
|
|
1355
|
+
console.log(
|
|
1356
|
+
` ${pc4.dim("Downloads:")} ${info2.weeklyDownloads.toLocaleString()}/week ${pc4.dim("|")} ${pc4.dim("Age:")} ${info2.packageAge}d ${pc4.dim("|")} ${pc4.dim("Maintainers:")} ${info2.maintainerCount}`
|
|
1357
|
+
);
|
|
1358
|
+
if (info2.lastPublish) {
|
|
1359
|
+
console.log(` ${pc4.dim("Last publish:")} ${formatDaysAgo(info2.lastPublish)}`);
|
|
1360
|
+
}
|
|
1361
|
+
} else {
|
|
1362
|
+
console.log(pc4.dim(" (Trust data available for npm packages only)"));
|
|
1363
|
+
}
|
|
1364
|
+
console.log();
|
|
1365
|
+
console.log(` ${pc4.bold("Environment Variables")}`);
|
|
1366
|
+
if (info2.envVars.length > 0) {
|
|
1367
|
+
for (const env of info2.envVars) {
|
|
1368
|
+
console.log(` ${pc4.cyan("\u2022")} ${env}`);
|
|
1369
|
+
}
|
|
1370
|
+
} else {
|
|
1371
|
+
console.log(pc4.dim(" none required"));
|
|
1372
|
+
}
|
|
1373
|
+
console.log();
|
|
1374
|
+
console.log(` ${pc4.bold("Installed Clients")}`);
|
|
1375
|
+
if (info2.installedClients.length > 0) {
|
|
1376
|
+
for (const client of info2.installedClients) {
|
|
1377
|
+
console.log(` ${pc4.green("\u2713")} ${client}`);
|
|
1378
|
+
}
|
|
1379
|
+
} else {
|
|
1380
|
+
console.log(pc4.dim(" Not installed in any client"));
|
|
1381
|
+
}
|
|
1382
|
+
console.log();
|
|
1383
|
+
console.log(pc4.dim(" " + "\u2500".repeat(60)));
|
|
1384
|
+
console.log();
|
|
1385
|
+
}
|
|
1386
|
+
var info_default = defineCommand4({
|
|
1387
|
+
meta: {
|
|
1388
|
+
name: "info",
|
|
1389
|
+
description: "Show detailed metadata for an MCP server (installed or from registry)"
|
|
1390
|
+
},
|
|
1391
|
+
args: {
|
|
1392
|
+
server: {
|
|
1393
|
+
type: "positional",
|
|
1394
|
+
description: "Server name (e.g. @modelcontextprotocol/server-filesystem)",
|
|
1395
|
+
required: true
|
|
1396
|
+
},
|
|
1397
|
+
json: {
|
|
1398
|
+
type: "boolean",
|
|
1399
|
+
description: "Output results as JSON",
|
|
1400
|
+
default: false
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
async run({ args }) {
|
|
1404
|
+
const spinner5 = createSpinner2(`Fetching info for ${args.server}...`).start();
|
|
1405
|
+
let info2;
|
|
1406
|
+
try {
|
|
1407
|
+
info2 = await getPackageInfo(args.server);
|
|
1408
|
+
} catch (err) {
|
|
1409
|
+
spinner5.error({ text: "Failed to fetch package info" });
|
|
1410
|
+
console.error(pc4.red(String(err)));
|
|
1411
|
+
process.exit(1);
|
|
1412
|
+
}
|
|
1413
|
+
if (!info2) {
|
|
1414
|
+
spinner5.error({ text: `Package not found: ${args.server}` });
|
|
1415
|
+
console.log(pc4.dim(`
|
|
1416
|
+
"${args.server}" was not found in the npm registry or your lockfile.
|
|
1417
|
+
`));
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
spinner5.success({ text: `Found ${args.server}` });
|
|
1421
|
+
if (args.json) {
|
|
1422
|
+
console.log(JSON.stringify(info2, null, 2));
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
printInfo(info2);
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1116
1429
|
// src/commands/init.ts
|
|
1117
|
-
import { defineCommand as
|
|
1118
|
-
import * as
|
|
1119
|
-
import
|
|
1120
|
-
var init_default =
|
|
1430
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
1431
|
+
import * as p3 from "@clack/prompts";
|
|
1432
|
+
import path4 from "path";
|
|
1433
|
+
var init_default = defineCommand5({
|
|
1121
1434
|
meta: {
|
|
1122
1435
|
name: "init",
|
|
1123
1436
|
description: "Initialize mcpman.lock in the current project"
|
|
@@ -1132,27 +1445,27 @@ var init_default = defineCommand3({
|
|
|
1132
1445
|
},
|
|
1133
1446
|
async run({ args }) {
|
|
1134
1447
|
const nonInteractive = args.yes || !process.stdout.isTTY;
|
|
1135
|
-
|
|
1136
|
-
const targetPath =
|
|
1448
|
+
p3.intro("mcpman init");
|
|
1449
|
+
const targetPath = path4.join(process.cwd(), LOCKFILE_NAME);
|
|
1137
1450
|
const existing = findLockfile();
|
|
1138
1451
|
if (existing) {
|
|
1139
1452
|
if (nonInteractive) {
|
|
1140
|
-
|
|
1453
|
+
p3.log.warn(`Lockfile already exists: ${existing} \u2014 overwriting (non-interactive).`);
|
|
1141
1454
|
} else {
|
|
1142
|
-
|
|
1143
|
-
const overwrite = await
|
|
1144
|
-
if (
|
|
1145
|
-
|
|
1455
|
+
p3.log.warn(`Lockfile already exists: ${existing}`);
|
|
1456
|
+
const overwrite = await p3.confirm({ message: "Overwrite?" });
|
|
1457
|
+
if (p3.isCancel(overwrite) || !overwrite) {
|
|
1458
|
+
p3.outro("Cancelled.");
|
|
1146
1459
|
return;
|
|
1147
1460
|
}
|
|
1148
1461
|
}
|
|
1149
1462
|
}
|
|
1150
1463
|
let clients = [];
|
|
1151
1464
|
try {
|
|
1152
|
-
const mod = await import("./client-detector-
|
|
1465
|
+
const mod = await import("./client-detector-UAP2EYZA.js");
|
|
1153
1466
|
clients = await mod.getInstalledClients();
|
|
1154
1467
|
} catch {
|
|
1155
|
-
|
|
1468
|
+
p3.log.warn("Could not detect AI clients \u2014 creating empty lockfile.");
|
|
1156
1469
|
}
|
|
1157
1470
|
const clientServers = [];
|
|
1158
1471
|
for (const client of clients) {
|
|
@@ -1166,26 +1479,26 @@ var init_default = defineCommand3({
|
|
|
1166
1479
|
}
|
|
1167
1480
|
createEmptyLockfile(targetPath);
|
|
1168
1481
|
if (clientServers.length === 0) {
|
|
1169
|
-
|
|
1170
|
-
|
|
1482
|
+
p3.log.info("No existing servers found in any client config.");
|
|
1483
|
+
p3.outro(`Created ${LOCKFILE_NAME} \u2014 add it to version control!`);
|
|
1171
1484
|
return;
|
|
1172
1485
|
}
|
|
1173
1486
|
let selected;
|
|
1174
1487
|
if (nonInteractive) {
|
|
1175
1488
|
selected = clientServers.map((cs) => cs.client.type);
|
|
1176
|
-
|
|
1489
|
+
p3.log.info(`Non-interactive mode: importing all ${clientServers.length} client(s).`);
|
|
1177
1490
|
} else {
|
|
1178
1491
|
const options = clientServers.map((cs) => ({
|
|
1179
1492
|
value: cs.client.type,
|
|
1180
1493
|
label: `${cs.client.displayName} (${Object.keys(cs.servers).length} servers)`
|
|
1181
1494
|
}));
|
|
1182
|
-
const toImport = await
|
|
1495
|
+
const toImport = await p3.multiselect({
|
|
1183
1496
|
message: "Import existing servers into lockfile?",
|
|
1184
1497
|
options,
|
|
1185
1498
|
required: false
|
|
1186
1499
|
});
|
|
1187
|
-
if (
|
|
1188
|
-
|
|
1500
|
+
if (p3.isCancel(toImport)) {
|
|
1501
|
+
p3.outro(`Created empty ${LOCKFILE_NAME}`);
|
|
1189
1502
|
return;
|
|
1190
1503
|
}
|
|
1191
1504
|
selected = toImport;
|
|
@@ -1211,20 +1524,20 @@ var init_default = defineCommand3({
|
|
|
1211
1524
|
importCount++;
|
|
1212
1525
|
}
|
|
1213
1526
|
}
|
|
1214
|
-
|
|
1527
|
+
p3.outro(
|
|
1215
1528
|
`Created ${LOCKFILE_NAME} with ${importCount} server(s) \u2014 commit to version control!`
|
|
1216
1529
|
);
|
|
1217
1530
|
}
|
|
1218
1531
|
});
|
|
1219
1532
|
|
|
1220
1533
|
// src/commands/install.ts
|
|
1221
|
-
import { defineCommand as
|
|
1534
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
1222
1535
|
|
|
1223
1536
|
// src/core/installer.ts
|
|
1224
|
-
import * as
|
|
1537
|
+
import * as p5 from "@clack/prompts";
|
|
1225
1538
|
|
|
1226
1539
|
// src/core/installer-vault-helpers.ts
|
|
1227
|
-
import * as
|
|
1540
|
+
import * as p4 from "@clack/prompts";
|
|
1228
1541
|
async function tryLoadVaultSecrets(serverName) {
|
|
1229
1542
|
try {
|
|
1230
1543
|
const entries = listSecrets(serverName);
|
|
@@ -1241,67 +1554,67 @@ async function offerVaultSave(serverName, newVars, yes) {
|
|
|
1241
1554
|
if (Object.keys(newVars).length === 0) return;
|
|
1242
1555
|
if (yes) return;
|
|
1243
1556
|
try {
|
|
1244
|
-
const save = await
|
|
1557
|
+
const save = await p4.confirm({
|
|
1245
1558
|
message: `Save ${Object.keys(newVars).length} env var(s) to encrypted vault for future installs?`
|
|
1246
1559
|
});
|
|
1247
|
-
if (
|
|
1560
|
+
if (p4.isCancel(save) || !save) return;
|
|
1248
1561
|
const password = await getMasterPassword();
|
|
1249
1562
|
for (const [key, value] of Object.entries(newVars)) {
|
|
1250
1563
|
setSecret(serverName, key, value, password);
|
|
1251
1564
|
}
|
|
1252
|
-
|
|
1565
|
+
p4.log.success(`Credentials saved to vault for '${serverName}'`);
|
|
1253
1566
|
} catch (err) {
|
|
1254
|
-
|
|
1567
|
+
p4.log.warn(`Could not save to vault: ${err instanceof Error ? err.message : String(err)}`);
|
|
1255
1568
|
}
|
|
1256
1569
|
}
|
|
1257
1570
|
|
|
1258
1571
|
// src/core/installer.ts
|
|
1259
1572
|
async function loadClients2() {
|
|
1260
1573
|
try {
|
|
1261
|
-
const mod = await import("./client-detector-
|
|
1574
|
+
const mod = await import("./client-detector-UAP2EYZA.js");
|
|
1262
1575
|
return mod.getInstalledClients();
|
|
1263
1576
|
} catch {
|
|
1264
1577
|
return [];
|
|
1265
1578
|
}
|
|
1266
1579
|
}
|
|
1267
1580
|
async function installServer(input, options = {}) {
|
|
1268
|
-
|
|
1269
|
-
const spinner5 =
|
|
1581
|
+
p5.intro("mcpman install");
|
|
1582
|
+
const spinner5 = p5.spinner();
|
|
1270
1583
|
spinner5.start("Resolving server...");
|
|
1271
1584
|
let metadata;
|
|
1272
1585
|
try {
|
|
1273
1586
|
metadata = await resolveServer(input);
|
|
1274
1587
|
} catch (err) {
|
|
1275
1588
|
spinner5.stop("Resolution failed");
|
|
1276
|
-
|
|
1589
|
+
p5.log.error(err instanceof Error ? err.message : String(err));
|
|
1277
1590
|
process.exit(1);
|
|
1278
1591
|
}
|
|
1279
1592
|
spinner5.stop(`Found: ${metadata.name}@${metadata.version}`);
|
|
1280
1593
|
const clients = await loadClients2();
|
|
1281
1594
|
if (clients.length === 0) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1595
|
+
p5.log.warn("No supported AI clients detected on this machine.");
|
|
1596
|
+
p5.log.info("Supported: Claude Desktop, Cursor, VS Code, Windsurf");
|
|
1284
1597
|
process.exit(1);
|
|
1285
1598
|
}
|
|
1286
1599
|
let selectedClients;
|
|
1287
1600
|
if (options.client) {
|
|
1288
1601
|
const found = clients.find((c) => c.type === options.client || c.displayName.toLowerCase() === options.client?.toLowerCase());
|
|
1289
1602
|
if (!found) {
|
|
1290
|
-
|
|
1291
|
-
|
|
1603
|
+
p5.log.error(`Client '${options.client}' not found or not installed.`);
|
|
1604
|
+
p5.log.info(`Available: ${clients.map((c) => c.type).join(", ")}`);
|
|
1292
1605
|
process.exit(1);
|
|
1293
1606
|
}
|
|
1294
1607
|
selectedClients = [found];
|
|
1295
1608
|
} else if (options.yes || clients.length === 1) {
|
|
1296
1609
|
selectedClients = clients;
|
|
1297
1610
|
} else {
|
|
1298
|
-
const chosen = await
|
|
1611
|
+
const chosen = await p5.multiselect({
|
|
1299
1612
|
message: "Install to which client(s)?",
|
|
1300
1613
|
options: clients.map((c) => ({ value: c.type, label: c.displayName })),
|
|
1301
1614
|
required: true
|
|
1302
1615
|
});
|
|
1303
|
-
if (
|
|
1304
|
-
|
|
1616
|
+
if (p5.isCancel(chosen)) {
|
|
1617
|
+
p5.outro("Cancelled.");
|
|
1305
1618
|
process.exit(0);
|
|
1306
1619
|
}
|
|
1307
1620
|
selectedClients = clients.filter((c) => chosen.includes(c.type));
|
|
@@ -1316,13 +1629,13 @@ async function installServer(input, options = {}) {
|
|
|
1316
1629
|
collectedEnv[envVar.name] = envVar.default;
|
|
1317
1630
|
continue;
|
|
1318
1631
|
}
|
|
1319
|
-
const val = await
|
|
1632
|
+
const val = await p5.text({
|
|
1320
1633
|
message: `${envVar.name}${envVar.description ? ` \u2014 ${envVar.description}` : ""}`,
|
|
1321
1634
|
placeholder: envVar.default ?? "",
|
|
1322
1635
|
validate: (v) => envVar.required && !v ? "Required" : void 0
|
|
1323
1636
|
});
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1637
|
+
if (p5.isCancel(val)) {
|
|
1638
|
+
p5.outro("Cancelled.");
|
|
1326
1639
|
process.exit(0);
|
|
1327
1640
|
}
|
|
1328
1641
|
collectedEnv[envVar.name] = val;
|
|
@@ -1341,7 +1654,7 @@ async function installServer(input, options = {}) {
|
|
|
1341
1654
|
clientTypes.push(client.type);
|
|
1342
1655
|
} catch (err) {
|
|
1343
1656
|
spinner5.stop("Partial failure");
|
|
1344
|
-
|
|
1657
|
+
p5.log.warn(`Failed to write to ${client.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1345
1658
|
}
|
|
1346
1659
|
}
|
|
1347
1660
|
spinner5.stop("Config written");
|
|
@@ -1360,13 +1673,13 @@ async function installServer(input, options = {}) {
|
|
|
1360
1673
|
clients: clientTypes
|
|
1361
1674
|
});
|
|
1362
1675
|
const lockPath = findLockfile() ?? "mcpman.lock (global)";
|
|
1363
|
-
|
|
1676
|
+
p5.log.success(`Lockfile updated: ${lockPath}`);
|
|
1364
1677
|
await offerVaultSave(metadata.name, newlyEnteredVars, options.yes ?? false);
|
|
1365
|
-
|
|
1678
|
+
p5.outro(`${metadata.name}@${metadata.version} installed to ${clientTypes.join(", ")}`);
|
|
1366
1679
|
}
|
|
1367
1680
|
|
|
1368
1681
|
// src/utils/logger.ts
|
|
1369
|
-
import
|
|
1682
|
+
import pc5 from "picocolors";
|
|
1370
1683
|
var noColor = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
1371
1684
|
var isVerbose = process.argv.includes("--verbose");
|
|
1372
1685
|
var isJson = process.argv.includes("--json");
|
|
@@ -1375,19 +1688,19 @@ function colorize(fn, text2) {
|
|
|
1375
1688
|
}
|
|
1376
1689
|
function info(message) {
|
|
1377
1690
|
if (isJson) return;
|
|
1378
|
-
console.log(`${colorize(
|
|
1691
|
+
console.log(`${colorize(pc5.cyan, "i")} ${message}`);
|
|
1379
1692
|
}
|
|
1380
1693
|
function error(message) {
|
|
1381
1694
|
if (isJson) return;
|
|
1382
|
-
console.error(`${colorize(
|
|
1695
|
+
console.error(`${colorize(pc5.red, "\u2717")} ${message}`);
|
|
1383
1696
|
}
|
|
1384
1697
|
function json(data) {
|
|
1385
1698
|
console.log(JSON.stringify(data, null, 2));
|
|
1386
1699
|
}
|
|
1387
1700
|
|
|
1388
1701
|
// src/commands/install.ts
|
|
1389
|
-
import * as
|
|
1390
|
-
var install_default =
|
|
1702
|
+
import * as p6 from "@clack/prompts";
|
|
1703
|
+
var install_default = defineCommand6({
|
|
1391
1704
|
meta: {
|
|
1392
1705
|
name: "install",
|
|
1393
1706
|
description: "Install an MCP server into one or more AI clients"
|
|
@@ -1436,8 +1749,8 @@ async function restoreFromLockfile() {
|
|
|
1436
1749
|
info("Lockfile is empty \u2014 nothing to restore.");
|
|
1437
1750
|
return;
|
|
1438
1751
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1752
|
+
p6.intro(`mcpman install (restore from ${lockPath})`);
|
|
1753
|
+
p6.log.info(`Restoring ${entries.length} server(s)...`);
|
|
1441
1754
|
for (const [name, entry] of entries) {
|
|
1442
1755
|
const input = entry.source === "smithery" ? `smithery:${name}` : entry.source === "github" ? entry.resolved : name;
|
|
1443
1756
|
await installServer(input, {
|
|
@@ -1445,18 +1758,18 @@ async function restoreFromLockfile() {
|
|
|
1445
1758
|
yes: true
|
|
1446
1759
|
});
|
|
1447
1760
|
}
|
|
1448
|
-
|
|
1761
|
+
p6.outro("Restore complete.");
|
|
1449
1762
|
}
|
|
1450
1763
|
|
|
1451
1764
|
// src/commands/list.ts
|
|
1452
|
-
import { defineCommand as
|
|
1453
|
-
import
|
|
1765
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
1766
|
+
import pc6 from "picocolors";
|
|
1454
1767
|
var STATUS_ICON = {
|
|
1455
|
-
healthy:
|
|
1456
|
-
unhealthy:
|
|
1457
|
-
unknown:
|
|
1768
|
+
healthy: pc6.green("\u25CF"),
|
|
1769
|
+
unhealthy: pc6.red("\u25CF"),
|
|
1770
|
+
unknown: pc6.dim("\u25CB")
|
|
1458
1771
|
};
|
|
1459
|
-
var list_default =
|
|
1772
|
+
var list_default = defineCommand7({
|
|
1460
1773
|
meta: {
|
|
1461
1774
|
name: "list",
|
|
1462
1775
|
description: "List installed MCP servers"
|
|
@@ -1476,7 +1789,7 @@ var list_default = defineCommand5({
|
|
|
1476
1789
|
const servers = await getInstalledServers(args.client);
|
|
1477
1790
|
if (servers.length === 0) {
|
|
1478
1791
|
const filter = args.client ? ` for client "${args.client}"` : "";
|
|
1479
|
-
console.log(
|
|
1792
|
+
console.log(pc6.dim(`No MCP servers installed${filter}. Run ${pc6.cyan("mcpman install <server>")} to get started.`));
|
|
1480
1793
|
return;
|
|
1481
1794
|
}
|
|
1482
1795
|
const withStatus = await Promise.all(
|
|
@@ -1500,8 +1813,8 @@ var list_default = defineCommand5({
|
|
|
1500
1813
|
const nameWidth = Math.max(4, ...withStatus.map((s) => s.name.length));
|
|
1501
1814
|
const clientsWidth = Math.max(7, ...withStatus.map((s) => formatClients(s.clients).length));
|
|
1502
1815
|
const header = ` ${pad("NAME", nameWidth)} ${pad("CLIENT(S)", clientsWidth)} ${pad("COMMAND", 20)} STATUS`;
|
|
1503
|
-
console.log(
|
|
1504
|
-
console.log(
|
|
1816
|
+
console.log(pc6.dim(header));
|
|
1817
|
+
console.log(pc6.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`));
|
|
1505
1818
|
for (const s of withStatus) {
|
|
1506
1819
|
const icon = STATUS_ICON[s.status];
|
|
1507
1820
|
const clientsStr = formatClients(s.clients);
|
|
@@ -1509,7 +1822,7 @@ var list_default = defineCommand5({
|
|
|
1509
1822
|
console.log(` ${pad(s.name, nameWidth)} ${pad(clientsStr, clientsWidth)} ${pad(cmdStr, 20)} ${icon} ${s.status}`);
|
|
1510
1823
|
}
|
|
1511
1824
|
const clientSet = new Set(withStatus.flatMap((s) => s.clients));
|
|
1512
|
-
console.log(
|
|
1825
|
+
console.log(pc6.dim(`
|
|
1513
1826
|
${withStatus.length} server${withStatus.length !== 1 ? "s" : ""} \xB7 ${clientSet.size} client${clientSet.size !== 1 ? "s" : ""}`));
|
|
1514
1827
|
}
|
|
1515
1828
|
});
|
|
@@ -1530,9 +1843,9 @@ function formatClients(clients) {
|
|
|
1530
1843
|
}
|
|
1531
1844
|
|
|
1532
1845
|
// src/commands/remove.ts
|
|
1533
|
-
import { defineCommand as
|
|
1534
|
-
import * as
|
|
1535
|
-
import
|
|
1846
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
1847
|
+
import * as p7 from "@clack/prompts";
|
|
1848
|
+
import pc7 from "picocolors";
|
|
1536
1849
|
var CLIENT_DISPLAY2 = {
|
|
1537
1850
|
"claude-desktop": "Claude",
|
|
1538
1851
|
cursor: "Cursor",
|
|
@@ -1542,7 +1855,7 @@ var CLIENT_DISPLAY2 = {
|
|
|
1542
1855
|
function clientDisplayName(type) {
|
|
1543
1856
|
return CLIENT_DISPLAY2[type] ?? type;
|
|
1544
1857
|
}
|
|
1545
|
-
var remove_default =
|
|
1858
|
+
var remove_default = defineCommand8({
|
|
1546
1859
|
meta: {
|
|
1547
1860
|
name: "remove",
|
|
1548
1861
|
description: "Remove an MCP server from one or more AI clients"
|
|
@@ -1569,17 +1882,17 @@ var remove_default = defineCommand6({
|
|
|
1569
1882
|
}
|
|
1570
1883
|
},
|
|
1571
1884
|
async run({ args }) {
|
|
1572
|
-
|
|
1885
|
+
p7.intro(pc7.bold("mcpman remove"));
|
|
1573
1886
|
const serverName = args.server;
|
|
1574
1887
|
const servers = await getInstalledServers();
|
|
1575
1888
|
const match = servers.find((s) => s.name === serverName);
|
|
1576
1889
|
if (!match) {
|
|
1577
|
-
|
|
1890
|
+
p7.log.warn(`Server "${serverName}" is not installed.`);
|
|
1578
1891
|
const similar = servers.filter((s) => s.name.includes(serverName) || serverName.includes(s.name));
|
|
1579
1892
|
if (similar.length > 0) {
|
|
1580
|
-
|
|
1893
|
+
p7.log.info(`Did you mean: ${similar.map((s) => pc7.cyan(s.name)).join(", ")}?`);
|
|
1581
1894
|
}
|
|
1582
|
-
|
|
1895
|
+
p7.outro("Nothing to remove.");
|
|
1583
1896
|
return;
|
|
1584
1897
|
}
|
|
1585
1898
|
let targetClients;
|
|
@@ -1587,15 +1900,15 @@ var remove_default = defineCommand6({
|
|
|
1587
1900
|
targetClients = match.clients;
|
|
1588
1901
|
} else if (args.client) {
|
|
1589
1902
|
if (!match.clients.includes(args.client)) {
|
|
1590
|
-
|
|
1591
|
-
|
|
1903
|
+
p7.log.warn(`Server "${serverName}" is not installed in client "${args.client}".`);
|
|
1904
|
+
p7.outro("Nothing to remove.");
|
|
1592
1905
|
return;
|
|
1593
1906
|
}
|
|
1594
1907
|
targetClients = [args.client];
|
|
1595
1908
|
} else if (match.clients.length === 1) {
|
|
1596
1909
|
targetClients = match.clients;
|
|
1597
1910
|
} else {
|
|
1598
|
-
const selected = await
|
|
1911
|
+
const selected = await p7.multiselect({
|
|
1599
1912
|
message: `Remove "${serverName}" from which clients?`,
|
|
1600
1913
|
options: match.clients.map((c) => ({
|
|
1601
1914
|
value: c,
|
|
@@ -1603,19 +1916,19 @@ var remove_default = defineCommand6({
|
|
|
1603
1916
|
})),
|
|
1604
1917
|
required: true
|
|
1605
1918
|
});
|
|
1606
|
-
if (
|
|
1607
|
-
|
|
1919
|
+
if (p7.isCancel(selected)) {
|
|
1920
|
+
p7.outro("Cancelled.");
|
|
1608
1921
|
process.exit(0);
|
|
1609
1922
|
}
|
|
1610
1923
|
targetClients = selected;
|
|
1611
1924
|
}
|
|
1612
1925
|
if (!args.yes) {
|
|
1613
1926
|
const clientNames = targetClients.map(clientDisplayName).join(", ");
|
|
1614
|
-
const confirmed = await
|
|
1615
|
-
message: `Remove ${
|
|
1927
|
+
const confirmed = await p7.confirm({
|
|
1928
|
+
message: `Remove ${pc7.cyan(serverName)} from ${pc7.yellow(clientNames)}?`
|
|
1616
1929
|
});
|
|
1617
|
-
if (
|
|
1618
|
-
|
|
1930
|
+
if (p7.isCancel(confirmed) || !confirmed) {
|
|
1931
|
+
p7.outro("Cancelled.");
|
|
1619
1932
|
return;
|
|
1620
1933
|
}
|
|
1621
1934
|
}
|
|
@@ -1629,25 +1942,268 @@ var remove_default = defineCommand6({
|
|
|
1629
1942
|
}
|
|
1630
1943
|
try {
|
|
1631
1944
|
await handler.removeServer(serverName);
|
|
1632
|
-
|
|
1945
|
+
p7.log.success(`Removed from ${clientDisplayName(clientType)}`);
|
|
1633
1946
|
} catch (err) {
|
|
1634
1947
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1635
1948
|
errors.push(`${clientDisplayName(clientType)}: ${msg}`);
|
|
1636
1949
|
}
|
|
1637
1950
|
}
|
|
1638
1951
|
if (errors.length > 0) {
|
|
1639
|
-
for (const e of errors)
|
|
1640
|
-
|
|
1952
|
+
for (const e of errors) p7.log.error(e);
|
|
1953
|
+
p7.outro(pc7.red("Completed with errors."));
|
|
1954
|
+
process.exit(1);
|
|
1955
|
+
}
|
|
1956
|
+
p7.outro(pc7.green(`Removed "${serverName}" successfully.`));
|
|
1957
|
+
}
|
|
1958
|
+
});
|
|
1959
|
+
|
|
1960
|
+
// src/commands/run.ts
|
|
1961
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
1962
|
+
import { spawn as spawn2 } from "child_process";
|
|
1963
|
+
import pc8 from "picocolors";
|
|
1964
|
+
var run_default = defineCommand9({
|
|
1965
|
+
meta: {
|
|
1966
|
+
name: "run",
|
|
1967
|
+
description: "Run an installed MCP server with vault secrets injected"
|
|
1968
|
+
},
|
|
1969
|
+
args: {
|
|
1970
|
+
server: {
|
|
1971
|
+
type: "positional",
|
|
1972
|
+
description: "Server name to run (as installed in lockfile)",
|
|
1973
|
+
required: true
|
|
1974
|
+
},
|
|
1975
|
+
env: {
|
|
1976
|
+
type: "string",
|
|
1977
|
+
description: "Override env var KEY=VAL (repeatable)",
|
|
1978
|
+
alias: "e"
|
|
1979
|
+
}
|
|
1980
|
+
},
|
|
1981
|
+
async run({ args }) {
|
|
1982
|
+
const serverName = args.server;
|
|
1983
|
+
const lockfile = readLockfile();
|
|
1984
|
+
const entry = lockfile.servers[serverName];
|
|
1985
|
+
if (!entry) {
|
|
1986
|
+
console.error(pc8.red(` Error: Server '${serverName}' is not installed.`));
|
|
1987
|
+
console.error(pc8.dim(` Run ${pc8.cyan("mcpman install <server>")} to install it first.`));
|
|
1988
|
+
process.exit(1);
|
|
1989
|
+
}
|
|
1990
|
+
const lockfileEnv = parseEnvFlags(entry.envVars);
|
|
1991
|
+
const vaultEnv = await loadVaultSecrets(serverName);
|
|
1992
|
+
const cliEnv = parseEnvFlags(args.env);
|
|
1993
|
+
const finalEnv = {
|
|
1994
|
+
...process.env,
|
|
1995
|
+
...lockfileEnv,
|
|
1996
|
+
...vaultEnv,
|
|
1997
|
+
...cliEnv
|
|
1998
|
+
};
|
|
1999
|
+
console.log(pc8.dim(` Running ${pc8.cyan(serverName)}...`));
|
|
2000
|
+
const child = spawn2(entry.command, entry.args, {
|
|
2001
|
+
env: finalEnv,
|
|
2002
|
+
stdio: "inherit"
|
|
2003
|
+
});
|
|
2004
|
+
const forwardSignal = (signal) => {
|
|
2005
|
+
if (!child.killed) {
|
|
2006
|
+
child.kill(signal);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
|
2010
|
+
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
|
2011
|
+
await new Promise((resolve) => {
|
|
2012
|
+
child.on("close", (code) => {
|
|
2013
|
+
process.exit(code ?? 0);
|
|
2014
|
+
resolve();
|
|
2015
|
+
});
|
|
2016
|
+
child.on("error", (err) => {
|
|
2017
|
+
console.error(pc8.red(` Failed to start '${serverName}': ${err.message}`));
|
|
2018
|
+
process.exit(1);
|
|
2019
|
+
resolve();
|
|
2020
|
+
});
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
});
|
|
2024
|
+
async function loadVaultSecrets(serverName) {
|
|
2025
|
+
try {
|
|
2026
|
+
const entries = listSecrets(serverName);
|
|
2027
|
+
if (entries.length === 0 || entries[0].keys.length === 0) {
|
|
2028
|
+
return {};
|
|
2029
|
+
}
|
|
2030
|
+
const password = await getMasterPassword();
|
|
2031
|
+
return getSecretsForServer(serverName, password);
|
|
2032
|
+
} catch {
|
|
2033
|
+
console.warn(pc8.yellow(" Warning: Could not load vault secrets, continuing without them."));
|
|
2034
|
+
return {};
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// src/commands/search.ts
|
|
2039
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
2040
|
+
import pc9 from "picocolors";
|
|
2041
|
+
import { createSpinner as createSpinner3 } from "nanospinner";
|
|
2042
|
+
|
|
2043
|
+
// src/core/registry-search.ts
|
|
2044
|
+
var SEARCH_TIMEOUT_MS = 1e4;
|
|
2045
|
+
async function searchNpm(query, limit = 20) {
|
|
2046
|
+
const cap = Math.min(limit, 100);
|
|
2047
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=mcp+${encodeURIComponent(query)}&size=${cap}`;
|
|
2048
|
+
try {
|
|
2049
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS) });
|
|
2050
|
+
if (!res.ok) return [];
|
|
2051
|
+
const data = await res.json();
|
|
2052
|
+
const objects = Array.isArray(data["objects"]) ? data["objects"] : [];
|
|
2053
|
+
return objects.map((obj) => {
|
|
2054
|
+
const pkg = obj["package"] ?? {};
|
|
2055
|
+
const dl = obj["downloads"];
|
|
2056
|
+
return {
|
|
2057
|
+
name: typeof pkg["name"] === "string" ? pkg["name"] : "",
|
|
2058
|
+
description: typeof pkg["description"] === "string" ? pkg["description"] : "",
|
|
2059
|
+
version: typeof pkg["version"] === "string" ? pkg["version"] : "",
|
|
2060
|
+
date: typeof pkg["date"] === "string" ? pkg["date"] : "",
|
|
2061
|
+
downloads: typeof dl?.["weekly"] === "number" ? dl["weekly"] : 0,
|
|
2062
|
+
keywords: Array.isArray(pkg["keywords"]) ? pkg["keywords"] : []
|
|
2063
|
+
};
|
|
2064
|
+
}).filter((r) => r.name !== "");
|
|
2065
|
+
} catch {
|
|
2066
|
+
return [];
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
async function searchSmithery(query, limit = 20) {
|
|
2070
|
+
const cap = Math.min(limit, 100);
|
|
2071
|
+
const url = `https://api.smithery.ai/v1/servers?q=${encodeURIComponent(query)}&limit=${cap}`;
|
|
2072
|
+
try {
|
|
2073
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS) });
|
|
2074
|
+
if (!res.ok) return [];
|
|
2075
|
+
const data = await res.json();
|
|
2076
|
+
const servers = Array.isArray(data["servers"]) ? data["servers"] : [];
|
|
2077
|
+
return servers.map((s) => ({
|
|
2078
|
+
name: typeof s["name"] === "string" ? s["name"] : "",
|
|
2079
|
+
description: typeof s["description"] === "string" ? s["description"] : "",
|
|
2080
|
+
version: typeof s["version"] === "string" ? s["version"] : "latest",
|
|
2081
|
+
runtime: typeof s["runtime"] === "string" ? s["runtime"] : "node"
|
|
2082
|
+
})).filter((r) => r.name !== "");
|
|
2083
|
+
} catch {
|
|
2084
|
+
return [];
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// src/commands/search.ts
|
|
2089
|
+
function truncate2(s, max) {
|
|
2090
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
2091
|
+
}
|
|
2092
|
+
function pad2(s, width) {
|
|
2093
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
2094
|
+
}
|
|
2095
|
+
function highlightMatch(name, query) {
|
|
2096
|
+
const idx = name.toLowerCase().indexOf(query.toLowerCase());
|
|
2097
|
+
if (idx === -1) return name;
|
|
2098
|
+
return name.slice(0, idx) + pc9.yellow(name.slice(idx, idx + query.length)) + name.slice(idx + query.length);
|
|
2099
|
+
}
|
|
2100
|
+
function formatDownloads(n) {
|
|
2101
|
+
if (!n) return pc9.dim("\u2014");
|
|
2102
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2103
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
2104
|
+
return String(n);
|
|
2105
|
+
}
|
|
2106
|
+
function printNpmResults(results, query) {
|
|
2107
|
+
const nameWidth = Math.max(4, ...results.map((r) => r.name.length), 20);
|
|
2108
|
+
const verWidth = Math.max(7, ...results.map((r) => r.version.length));
|
|
2109
|
+
const dlWidth = 9;
|
|
2110
|
+
const descMax = 50;
|
|
2111
|
+
const header = ` ${pad2("NAME", nameWidth)} ${pad2("VERSION", verWidth)} ${pad2("DOWNLOADS", dlWidth)} DESCRIPTION`;
|
|
2112
|
+
console.log(pc9.dim(header));
|
|
2113
|
+
console.log(pc9.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(verWidth)} ${"-".repeat(dlWidth)} ${"-".repeat(descMax)}`));
|
|
2114
|
+
for (const r of results) {
|
|
2115
|
+
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
2116
|
+
const ver = pad2(r.version, verWidth);
|
|
2117
|
+
const dl = pad2(formatDownloads(r.downloads), dlWidth);
|
|
2118
|
+
const desc = truncate2(r.description || pc9.dim("(no description)"), descMax);
|
|
2119
|
+
console.log(` ${name} ${pc9.dim(ver)} ${dl} ${desc}`);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
function printSmitheryResults(results, query) {
|
|
2123
|
+
const nameWidth = Math.max(4, ...results.map((r) => r.name.length), 20);
|
|
2124
|
+
const verWidth = Math.max(7, ...results.map((r) => r.version.length));
|
|
2125
|
+
const descMax = 50;
|
|
2126
|
+
const header = ` ${pad2("NAME", nameWidth)} ${pad2("VERSION", verWidth)} DESCRIPTION`;
|
|
2127
|
+
console.log(pc9.dim(header));
|
|
2128
|
+
console.log(pc9.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(verWidth)} ${"-".repeat(descMax)}`));
|
|
2129
|
+
for (const r of results) {
|
|
2130
|
+
const name = highlightMatch(pad2(r.name, nameWidth), query);
|
|
2131
|
+
const ver = pad2(r.version, verWidth);
|
|
2132
|
+
const desc = truncate2(r.description || pc9.dim("(no description)"), descMax);
|
|
2133
|
+
console.log(` ${name} ${pc9.dim(ver)} ${desc}`);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
var search_default = defineCommand10({
|
|
2137
|
+
meta: {
|
|
2138
|
+
name: "search",
|
|
2139
|
+
description: "Search for MCP servers on npm or Smithery registry"
|
|
2140
|
+
},
|
|
2141
|
+
args: {
|
|
2142
|
+
query: {
|
|
2143
|
+
type: "positional",
|
|
2144
|
+
description: "Search query",
|
|
2145
|
+
required: true
|
|
2146
|
+
},
|
|
2147
|
+
registry: {
|
|
2148
|
+
type: "string",
|
|
2149
|
+
description: "Registry to search: npm or smithery (default: npm)",
|
|
2150
|
+
default: "npm"
|
|
2151
|
+
},
|
|
2152
|
+
limit: {
|
|
2153
|
+
type: "string",
|
|
2154
|
+
description: "Maximum number of results (default: 20, max: 100)",
|
|
2155
|
+
default: "20"
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
async run({ args }) {
|
|
2159
|
+
const query = args.query;
|
|
2160
|
+
const registry = args.registry.toLowerCase();
|
|
2161
|
+
const limit = Math.min(Math.max(1, Number.parseInt(args.limit, 10) || 20), 100);
|
|
2162
|
+
if (registry !== "npm" && registry !== "smithery") {
|
|
2163
|
+
console.error(pc9.red(` Unknown registry "${registry}". Use "npm" or "smithery".`));
|
|
1641
2164
|
process.exit(1);
|
|
1642
2165
|
}
|
|
1643
|
-
|
|
2166
|
+
const spinner5 = createSpinner3(`Searching ${registry} for "${query}"...`).start();
|
|
2167
|
+
if (registry === "npm") {
|
|
2168
|
+
const results2 = await searchNpm(query, limit);
|
|
2169
|
+
spinner5.stop();
|
|
2170
|
+
if (results2.length === 0) {
|
|
2171
|
+
console.log(pc9.dim(`
|
|
2172
|
+
No results found for "${query}" on npm.
|
|
2173
|
+
`));
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
console.log(pc9.bold(`
|
|
2177
|
+
mcpman search \u2014 npm (${results2.length} result${results2.length !== 1 ? "s" : ""})
|
|
2178
|
+
`));
|
|
2179
|
+
printNpmResults(results2, query);
|
|
2180
|
+
console.log(pc9.dim(`
|
|
2181
|
+
Install with: mcpman install <name>
|
|
2182
|
+
`));
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
const results = await searchSmithery(query, limit);
|
|
2186
|
+
spinner5.stop();
|
|
2187
|
+
if (results.length === 0) {
|
|
2188
|
+
console.log(pc9.dim(`
|
|
2189
|
+
No results found for "${query}" on Smithery.
|
|
2190
|
+
`));
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
console.log(pc9.bold(`
|
|
2194
|
+
mcpman search \u2014 Smithery (${results.length} result${results.length !== 1 ? "s" : ""})
|
|
2195
|
+
`));
|
|
2196
|
+
printSmitheryResults(results, query);
|
|
2197
|
+
console.log(pc9.dim(`
|
|
2198
|
+
Install with: mcpman install <name>
|
|
2199
|
+
`));
|
|
1644
2200
|
}
|
|
1645
2201
|
});
|
|
1646
2202
|
|
|
1647
2203
|
// src/commands/secrets.ts
|
|
1648
|
-
import { defineCommand as
|
|
1649
|
-
import
|
|
1650
|
-
import * as
|
|
2204
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
2205
|
+
import pc10 from "picocolors";
|
|
2206
|
+
import * as p8 from "@clack/prompts";
|
|
1651
2207
|
function maskValue(value) {
|
|
1652
2208
|
if (value.length <= 8) return "***";
|
|
1653
2209
|
return `${value.slice(0, 4)}***${value.slice(-3)}`;
|
|
@@ -1657,7 +2213,7 @@ function parseKeyValue(input) {
|
|
|
1657
2213
|
if (idx <= 0) return null;
|
|
1658
2214
|
return { key: input.slice(0, idx), value: input.slice(idx + 1) };
|
|
1659
2215
|
}
|
|
1660
|
-
var
|
|
2216
|
+
var setCommand2 = defineCommand11({
|
|
1661
2217
|
meta: { name: "set", description: "Store an encrypted secret for a server" },
|
|
1662
2218
|
args: {
|
|
1663
2219
|
server: {
|
|
@@ -1674,30 +2230,30 @@ var setCommand = defineCommand7({
|
|
|
1674
2230
|
async run({ args }) {
|
|
1675
2231
|
const parsed = parseKeyValue(args.keyvalue);
|
|
1676
2232
|
if (!parsed) {
|
|
1677
|
-
console.error(
|
|
2233
|
+
console.error(pc10.red("\u2717") + " Invalid format. Expected KEY=VALUE");
|
|
1678
2234
|
process.exit(1);
|
|
1679
2235
|
}
|
|
1680
|
-
|
|
2236
|
+
p8.intro(pc10.cyan("mcpman secrets set"));
|
|
1681
2237
|
const isNew = listSecrets(args.server).length === 0 || !listSecrets(args.server)[0]?.keys.includes(parsed.key);
|
|
1682
2238
|
const vaultPath = (await import("./vault-service-UTZAV6N6.js")).getVaultPath();
|
|
1683
2239
|
const vaultExists = (await import("fs")).existsSync(vaultPath);
|
|
1684
2240
|
const password = await getMasterPassword(!vaultExists && isNew);
|
|
1685
|
-
const spin =
|
|
2241
|
+
const spin = p8.spinner();
|
|
1686
2242
|
spin.start("Encrypting secret...");
|
|
1687
2243
|
try {
|
|
1688
2244
|
setSecret(args.server, parsed.key, parsed.value, password);
|
|
1689
2245
|
spin.stop(
|
|
1690
|
-
`${
|
|
2246
|
+
`${pc10.green("\u2713")} Stored ${pc10.bold(parsed.key)} for ${pc10.cyan(args.server)}`
|
|
1691
2247
|
);
|
|
1692
2248
|
} catch (err) {
|
|
1693
|
-
spin.stop(
|
|
1694
|
-
console.error(
|
|
2249
|
+
spin.stop(pc10.red("\u2717") + " Failed to store secret");
|
|
2250
|
+
console.error(pc10.dim(String(err)));
|
|
1695
2251
|
process.exit(1);
|
|
1696
2252
|
}
|
|
1697
|
-
|
|
2253
|
+
p8.outro(pc10.dim("Secret encrypted and saved to vault."));
|
|
1698
2254
|
}
|
|
1699
2255
|
});
|
|
1700
|
-
var
|
|
2256
|
+
var listCommand2 = defineCommand11({
|
|
1701
2257
|
meta: { name: "list", description: "List secret keys stored in the vault" },
|
|
1702
2258
|
args: {
|
|
1703
2259
|
server: {
|
|
@@ -1709,23 +2265,23 @@ var listCommand = defineCommand7({
|
|
|
1709
2265
|
async run({ args }) {
|
|
1710
2266
|
const results = listSecrets(args.server || void 0);
|
|
1711
2267
|
if (results.length === 0) {
|
|
1712
|
-
const filter = args.server ? ` for ${
|
|
1713
|
-
console.log(
|
|
2268
|
+
const filter = args.server ? ` for ${pc10.cyan(args.server)}` : "";
|
|
2269
|
+
console.log(pc10.dim(`No secrets stored${filter}.`));
|
|
1714
2270
|
return;
|
|
1715
2271
|
}
|
|
1716
2272
|
console.log("");
|
|
1717
2273
|
for (const { server, keys } of results) {
|
|
1718
|
-
console.log(
|
|
2274
|
+
console.log(pc10.bold(pc10.cyan(server)));
|
|
1719
2275
|
for (const key of keys) {
|
|
1720
|
-
console.log(` ${
|
|
2276
|
+
console.log(` ${pc10.green("\u25CF")} ${pc10.bold(key)} ${pc10.dim(maskValue("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"))}`);
|
|
1721
2277
|
}
|
|
1722
2278
|
console.log("");
|
|
1723
2279
|
}
|
|
1724
2280
|
const total = results.reduce((n, r) => n + r.keys.length, 0);
|
|
1725
|
-
console.log(
|
|
2281
|
+
console.log(pc10.dim(` ${total} secret${total !== 1 ? "s" : ""} in ${results.length} server${results.length !== 1 ? "s" : ""}`));
|
|
1726
2282
|
}
|
|
1727
2283
|
});
|
|
1728
|
-
var removeCommand =
|
|
2284
|
+
var removeCommand = defineCommand11({
|
|
1729
2285
|
meta: { name: "remove", description: "Delete a secret from the vault" },
|
|
1730
2286
|
args: {
|
|
1731
2287
|
server: {
|
|
@@ -1740,40 +2296,40 @@ var removeCommand = defineCommand7({
|
|
|
1740
2296
|
}
|
|
1741
2297
|
},
|
|
1742
2298
|
async run({ args }) {
|
|
1743
|
-
const confirmed = await
|
|
1744
|
-
message: `Remove ${
|
|
2299
|
+
const confirmed = await p8.confirm({
|
|
2300
|
+
message: `Remove ${pc10.bold(args.key)} from ${pc10.cyan(args.server)}?`,
|
|
1745
2301
|
initialValue: false
|
|
1746
2302
|
});
|
|
1747
|
-
if (
|
|
1748
|
-
|
|
2303
|
+
if (p8.isCancel(confirmed) || !confirmed) {
|
|
2304
|
+
p8.cancel("Cancelled.");
|
|
1749
2305
|
return;
|
|
1750
2306
|
}
|
|
1751
2307
|
try {
|
|
1752
2308
|
removeSecret(args.server, args.key);
|
|
1753
|
-
console.log(`${
|
|
2309
|
+
console.log(`${pc10.green("\u2713")} Removed ${pc10.bold(args.key)} from ${pc10.cyan(args.server)}`);
|
|
1754
2310
|
} catch (err) {
|
|
1755
|
-
console.error(
|
|
1756
|
-
console.error(
|
|
2311
|
+
console.error(pc10.red("\u2717") + " Failed to remove secret");
|
|
2312
|
+
console.error(pc10.dim(String(err)));
|
|
1757
2313
|
process.exit(1);
|
|
1758
2314
|
}
|
|
1759
2315
|
}
|
|
1760
2316
|
});
|
|
1761
|
-
var secrets_default =
|
|
2317
|
+
var secrets_default = defineCommand11({
|
|
1762
2318
|
meta: {
|
|
1763
2319
|
name: "secrets",
|
|
1764
2320
|
description: "Manage encrypted secrets for MCP servers"
|
|
1765
2321
|
},
|
|
1766
2322
|
subCommands: {
|
|
1767
|
-
set:
|
|
1768
|
-
list:
|
|
2323
|
+
set: setCommand2,
|
|
2324
|
+
list: listCommand2,
|
|
1769
2325
|
remove: removeCommand
|
|
1770
2326
|
}
|
|
1771
2327
|
});
|
|
1772
2328
|
|
|
1773
2329
|
// src/commands/sync.ts
|
|
1774
|
-
import { defineCommand as
|
|
1775
|
-
import * as
|
|
1776
|
-
import
|
|
2330
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
2331
|
+
import * as p9 from "@clack/prompts";
|
|
2332
|
+
import pc11 from "picocolors";
|
|
1777
2333
|
|
|
1778
2334
|
// src/core/config-diff.ts
|
|
1779
2335
|
function reconstructServerEntry(lockEntry) {
|
|
@@ -1923,7 +2479,7 @@ var CLIENT_DISPLAY3 = {
|
|
|
1923
2479
|
vscode: "VS Code",
|
|
1924
2480
|
windsurf: "Windsurf"
|
|
1925
2481
|
};
|
|
1926
|
-
var sync_default =
|
|
2482
|
+
var sync_default = defineCommand12({
|
|
1927
2483
|
meta: {
|
|
1928
2484
|
name: "sync",
|
|
1929
2485
|
description: "Sync MCP server configs across all detected AI clients"
|
|
@@ -1950,28 +2506,28 @@ var sync_default = defineCommand8({
|
|
|
1950
2506
|
}
|
|
1951
2507
|
},
|
|
1952
2508
|
async run({ args }) {
|
|
1953
|
-
|
|
2509
|
+
p9.intro(`${pc11.cyan("mcpman sync")}`);
|
|
1954
2510
|
const sourceClient = args.source;
|
|
1955
2511
|
if (sourceClient && !VALID_CLIENTS.includes(sourceClient)) {
|
|
1956
|
-
|
|
2512
|
+
p9.log.error(`Invalid --source "${sourceClient}". Must be one of: ${VALID_CLIENTS.join(", ")}`);
|
|
1957
2513
|
process.exit(1);
|
|
1958
2514
|
}
|
|
1959
|
-
const spinner5 =
|
|
2515
|
+
const spinner5 = p9.spinner();
|
|
1960
2516
|
spinner5.start("Detecting clients and reading configs...");
|
|
1961
2517
|
const { configs, handlers } = await getClientConfigs();
|
|
1962
2518
|
spinner5.stop(`Found ${configs.size} client(s)`);
|
|
1963
2519
|
if (configs.size === 0) {
|
|
1964
|
-
|
|
2520
|
+
p9.log.warn("No AI clients detected. Install Claude Desktop, Cursor, VS Code, or Windsurf first.");
|
|
1965
2521
|
process.exit(0);
|
|
1966
2522
|
}
|
|
1967
2523
|
const diffOptions = { remove: args.remove };
|
|
1968
2524
|
let actions;
|
|
1969
2525
|
if (sourceClient) {
|
|
1970
2526
|
if (!configs.has(sourceClient)) {
|
|
1971
|
-
|
|
2527
|
+
p9.log.error(`Source client "${sourceClient}" is not detected or its config is unreadable.`);
|
|
1972
2528
|
process.exit(1);
|
|
1973
2529
|
}
|
|
1974
|
-
|
|
2530
|
+
p9.log.info(`Using ${CLIENT_DISPLAY3[sourceClient]} as source of truth`);
|
|
1975
2531
|
actions = computeDiffFromClient(sourceClient, configs, diffOptions);
|
|
1976
2532
|
} else {
|
|
1977
2533
|
const lockfile = readLockfile();
|
|
@@ -1982,32 +2538,32 @@ var sync_default = defineCommand8({
|
|
|
1982
2538
|
const extraCount = actions.filter((a) => a.action === "extra").length;
|
|
1983
2539
|
const removeCount = actions.filter((a) => a.action === "remove").length;
|
|
1984
2540
|
if (addCount === 0 && removeCount === 0 && extraCount === 0) {
|
|
1985
|
-
|
|
2541
|
+
p9.outro(pc11.green("All clients are in sync."));
|
|
1986
2542
|
process.exit(0);
|
|
1987
2543
|
}
|
|
1988
2544
|
const parts = [];
|
|
1989
|
-
if (addCount > 0) parts.push(
|
|
1990
|
-
if (removeCount > 0) parts.push(
|
|
1991
|
-
if (extraCount > 0) parts.push(
|
|
1992
|
-
|
|
2545
|
+
if (addCount > 0) parts.push(pc11.green(`${addCount} to add`));
|
|
2546
|
+
if (removeCount > 0) parts.push(pc11.red(`${removeCount} to remove`));
|
|
2547
|
+
if (extraCount > 0) parts.push(pc11.yellow(`${extraCount} extra (informational)`));
|
|
2548
|
+
p9.log.info(parts.join(" \xB7 "));
|
|
1993
2549
|
if (args["dry-run"]) {
|
|
1994
|
-
|
|
2550
|
+
p9.outro(pc11.dim("Dry run \u2014 no changes applied."));
|
|
1995
2551
|
process.exit(1);
|
|
1996
2552
|
}
|
|
1997
2553
|
if (addCount === 0 && removeCount === 0) {
|
|
1998
|
-
|
|
2554
|
+
p9.outro(pc11.dim("No additions needed. Extra servers left untouched."));
|
|
1999
2555
|
process.exit(1);
|
|
2000
2556
|
}
|
|
2001
2557
|
if (!args.yes) {
|
|
2002
2558
|
const actionParts = [];
|
|
2003
2559
|
if (addCount > 0) actionParts.push(`${addCount} addition(s)`);
|
|
2004
2560
|
if (removeCount > 0) actionParts.push(`${removeCount} removal(s)`);
|
|
2005
|
-
const confirmed = await
|
|
2561
|
+
const confirmed = await p9.confirm({
|
|
2006
2562
|
message: `Apply ${actionParts.join(" and ")} to client configs?`,
|
|
2007
2563
|
initialValue: true
|
|
2008
2564
|
});
|
|
2009
|
-
if (
|
|
2010
|
-
|
|
2565
|
+
if (p9.isCancel(confirmed) || !confirmed) {
|
|
2566
|
+
p9.outro(pc11.dim("Cancelled \u2014 no changes applied."));
|
|
2011
2567
|
process.exit(0);
|
|
2012
2568
|
}
|
|
2013
2569
|
}
|
|
@@ -2015,72 +2571,72 @@ var sync_default = defineCommand8({
|
|
|
2015
2571
|
const result = await applySyncActions(actions, handlers);
|
|
2016
2572
|
spinner5.stop("Done");
|
|
2017
2573
|
if (result.applied > 0) {
|
|
2018
|
-
|
|
2574
|
+
p9.log.success(`Added ${result.applied} server(s) to client configs.`);
|
|
2019
2575
|
}
|
|
2020
2576
|
if (result.removed > 0) {
|
|
2021
|
-
|
|
2577
|
+
p9.log.success(`Removed ${result.removed} server(s) from client configs.`);
|
|
2022
2578
|
}
|
|
2023
2579
|
if (result.failed > 0) {
|
|
2024
2580
|
for (const e of result.errors) {
|
|
2025
|
-
|
|
2581
|
+
p9.log.error(`Failed to sync "${e.server}" on ${e.client}: ${e.error}`);
|
|
2026
2582
|
}
|
|
2027
2583
|
}
|
|
2028
|
-
|
|
2584
|
+
p9.outro(result.failed === 0 ? pc11.green("Sync complete.") : pc11.yellow("Sync complete with errors."));
|
|
2029
2585
|
process.exit(result.failed > 0 ? 1 : 0);
|
|
2030
2586
|
}
|
|
2031
2587
|
});
|
|
2032
2588
|
function printDiffTable(actions) {
|
|
2033
2589
|
if (actions.length === 0) {
|
|
2034
|
-
|
|
2590
|
+
p9.log.info("No actions to display.");
|
|
2035
2591
|
return;
|
|
2036
2592
|
}
|
|
2037
2593
|
const nameWidth = Math.max(6, ...actions.map((a) => a.server.length));
|
|
2038
2594
|
const clientWidth = Math.max(6, ...actions.map((a) => CLIENT_DISPLAY3[a.client]?.length ?? a.client.length));
|
|
2039
|
-
const header = ` ${
|
|
2040
|
-
console.log(
|
|
2041
|
-
console.log(
|
|
2595
|
+
const header = ` ${pad3("SERVER", nameWidth)} ${pad3("CLIENT", clientWidth)} STATUS`;
|
|
2596
|
+
console.log(pc11.dim(header));
|
|
2597
|
+
console.log(pc11.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientWidth)} ------`));
|
|
2042
2598
|
for (const action of actions) {
|
|
2043
2599
|
const clientDisplay = CLIENT_DISPLAY3[action.client] ?? action.client;
|
|
2044
2600
|
const [icon, statusText] = formatAction(action.action);
|
|
2045
|
-
console.log(` ${
|
|
2601
|
+
console.log(` ${pad3(action.server, nameWidth)} ${pad3(clientDisplay, clientWidth)} ${icon} ${statusText}`);
|
|
2046
2602
|
}
|
|
2047
2603
|
console.log("");
|
|
2048
2604
|
}
|
|
2049
2605
|
function formatAction(action) {
|
|
2050
2606
|
switch (action) {
|
|
2051
2607
|
case "add":
|
|
2052
|
-
return [
|
|
2608
|
+
return [pc11.green("+"), pc11.green("missing \u2014 will add")];
|
|
2053
2609
|
case "extra":
|
|
2054
|
-
return [
|
|
2610
|
+
return [pc11.yellow("?"), pc11.yellow("extra (not in lockfile)")];
|
|
2055
2611
|
case "remove":
|
|
2056
|
-
return [
|
|
2612
|
+
return [pc11.red("\u2013"), pc11.red("extra \u2014 will remove")];
|
|
2057
2613
|
case "ok":
|
|
2058
|
-
return [
|
|
2614
|
+
return [pc11.dim("\xB7"), pc11.dim("in sync")];
|
|
2059
2615
|
}
|
|
2060
2616
|
}
|
|
2061
|
-
function
|
|
2617
|
+
function pad3(s, width) {
|
|
2062
2618
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
2063
2619
|
}
|
|
2064
2620
|
|
|
2065
2621
|
// src/commands/update.ts
|
|
2066
|
-
import { defineCommand as
|
|
2067
|
-
import * as
|
|
2068
|
-
import
|
|
2622
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
2623
|
+
import * as p10 from "@clack/prompts";
|
|
2624
|
+
import pc13 from "picocolors";
|
|
2069
2625
|
|
|
2070
2626
|
// src/core/update-notifier.ts
|
|
2071
|
-
import
|
|
2072
|
-
import
|
|
2627
|
+
import fs4 from "fs";
|
|
2628
|
+
import path5 from "path";
|
|
2073
2629
|
import os3 from "os";
|
|
2074
|
-
import
|
|
2075
|
-
var CACHE_FILE =
|
|
2630
|
+
import pc12 from "picocolors";
|
|
2631
|
+
var CACHE_FILE = path5.join(os3.homedir(), ".mcpman", ".update-check");
|
|
2076
2632
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2077
2633
|
function writeUpdateCache(data) {
|
|
2078
2634
|
try {
|
|
2079
|
-
const dir =
|
|
2080
|
-
if (!
|
|
2635
|
+
const dir = path5.dirname(CACHE_FILE);
|
|
2636
|
+
if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
|
|
2081
2637
|
const tmp = `${CACHE_FILE}.tmp`;
|
|
2082
|
-
|
|
2083
|
-
|
|
2638
|
+
fs4.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
2639
|
+
fs4.renameSync(tmp, CACHE_FILE);
|
|
2084
2640
|
} catch {
|
|
2085
2641
|
}
|
|
2086
2642
|
}
|
|
@@ -2088,7 +2644,7 @@ function writeUpdateCache(data) {
|
|
|
2088
2644
|
// src/commands/update.ts
|
|
2089
2645
|
async function loadClients3() {
|
|
2090
2646
|
try {
|
|
2091
|
-
const mod = await import("./client-detector-
|
|
2647
|
+
const mod = await import("./client-detector-UAP2EYZA.js");
|
|
2092
2648
|
return mod.getInstalledClients();
|
|
2093
2649
|
} catch {
|
|
2094
2650
|
return [];
|
|
@@ -2103,19 +2659,19 @@ function printTable(updates) {
|
|
|
2103
2659
|
"LATEST".padEnd(VER_W),
|
|
2104
2660
|
"STATUS"
|
|
2105
2661
|
].join(" ");
|
|
2106
|
-
console.log(
|
|
2662
|
+
console.log(pc13.bold(`
|
|
2107
2663
|
${header}`));
|
|
2108
|
-
console.log(
|
|
2664
|
+
console.log(pc13.dim(` ${"\u2500".repeat(NAME_W + VER_W * 2 + 20)}`));
|
|
2109
2665
|
for (const u of updates) {
|
|
2110
2666
|
const nameCol = u.server.slice(0, NAME_W).padEnd(NAME_W);
|
|
2111
2667
|
const curCol = u.currentVersion.padEnd(VER_W);
|
|
2112
2668
|
const latCol = u.latestVersion.padEnd(VER_W);
|
|
2113
|
-
const statusCol = u.hasUpdate ?
|
|
2669
|
+
const statusCol = u.hasUpdate ? pc13.yellow(`Update available${u.updateType ? ` [${u.updateType}]` : ""}`) : pc13.green("Up to date");
|
|
2114
2670
|
console.log(` ${nameCol} ${curCol} ${latCol} ${statusCol}`);
|
|
2115
2671
|
}
|
|
2116
2672
|
console.log();
|
|
2117
2673
|
}
|
|
2118
|
-
var update_default =
|
|
2674
|
+
var update_default = defineCommand13({
|
|
2119
2675
|
meta: {
|
|
2120
2676
|
name: "update",
|
|
2121
2677
|
description: "Check for and apply updates to installed MCP servers"
|
|
@@ -2154,7 +2710,7 @@ var update_default = defineCommand9({
|
|
|
2154
2710
|
}
|
|
2155
2711
|
process.exit(1);
|
|
2156
2712
|
}
|
|
2157
|
-
const spinner5 =
|
|
2713
|
+
const spinner5 = p10.spinner();
|
|
2158
2714
|
spinner5.start("Checking versions...");
|
|
2159
2715
|
let updates;
|
|
2160
2716
|
try {
|
|
@@ -2176,27 +2732,27 @@ var update_default = defineCommand9({
|
|
|
2176
2732
|
printTable(updates);
|
|
2177
2733
|
const outdated = updates.filter((u) => u.hasUpdate);
|
|
2178
2734
|
if (outdated.length === 0) {
|
|
2179
|
-
console.log(
|
|
2735
|
+
console.log(pc13.green(" All servers are up to date."));
|
|
2180
2736
|
return;
|
|
2181
2737
|
}
|
|
2182
2738
|
if (args.check) {
|
|
2183
|
-
console.log(
|
|
2739
|
+
console.log(pc13.yellow(` ${outdated.length} update(s) available. Run mcpman update to apply.`));
|
|
2184
2740
|
return;
|
|
2185
2741
|
}
|
|
2186
2742
|
if (!args.yes) {
|
|
2187
|
-
const confirmed = await
|
|
2743
|
+
const confirmed = await p10.confirm({
|
|
2188
2744
|
message: `Apply ${outdated.length} update(s)?`,
|
|
2189
2745
|
initialValue: true
|
|
2190
2746
|
});
|
|
2191
|
-
if (
|
|
2192
|
-
|
|
2747
|
+
if (p10.isCancel(confirmed) || !confirmed) {
|
|
2748
|
+
p10.outro("Cancelled.");
|
|
2193
2749
|
return;
|
|
2194
2750
|
}
|
|
2195
2751
|
}
|
|
2196
2752
|
const clients = await loadClients3();
|
|
2197
2753
|
let successCount = 0;
|
|
2198
2754
|
for (const update of outdated) {
|
|
2199
|
-
const s =
|
|
2755
|
+
const s = p10.spinner();
|
|
2200
2756
|
s.start(`Updating ${update.server}...`);
|
|
2201
2757
|
const result = await applyServerUpdate(
|
|
2202
2758
|
update.server,
|
|
@@ -2204,22 +2760,22 @@ var update_default = defineCommand9({
|
|
|
2204
2760
|
clients
|
|
2205
2761
|
);
|
|
2206
2762
|
if (result.success) {
|
|
2207
|
-
s.stop(`${
|
|
2763
|
+
s.stop(`${pc13.green("\u2713")} ${update.server}: ${result.fromVersion} \u2192 ${result.toVersion}`);
|
|
2208
2764
|
successCount++;
|
|
2209
2765
|
} else {
|
|
2210
|
-
s.stop(`${
|
|
2766
|
+
s.stop(`${pc13.red("\u2717")} ${update.server}: ${result.error}`);
|
|
2211
2767
|
}
|
|
2212
2768
|
}
|
|
2213
2769
|
const freshLockfile = readLockfile(resolveLockfilePath());
|
|
2214
2770
|
const freshUpdates = await checkAllVersions(freshLockfile);
|
|
2215
2771
|
writeUpdateCache({ lastCheck: (/* @__PURE__ */ new Date()).toISOString(), updates: freshUpdates });
|
|
2216
|
-
|
|
2772
|
+
p10.outro(`${successCount} of ${outdated.length} server(s) updated.`);
|
|
2217
2773
|
}
|
|
2218
2774
|
});
|
|
2219
2775
|
|
|
2220
2776
|
// src/utils/constants.ts
|
|
2221
2777
|
var APP_NAME = "mcpman";
|
|
2222
|
-
var APP_VERSION = "0.
|
|
2778
|
+
var APP_VERSION = "0.4.0";
|
|
2223
2779
|
var APP_DESCRIPTION = "The package manager for MCP servers";
|
|
2224
2780
|
|
|
2225
2781
|
// src/index.ts
|
|
@@ -2227,7 +2783,7 @@ process.on("SIGINT", () => {
|
|
|
2227
2783
|
console.log("\nAborted.");
|
|
2228
2784
|
process.exit(130);
|
|
2229
2785
|
});
|
|
2230
|
-
var main =
|
|
2786
|
+
var main = defineCommand14({
|
|
2231
2787
|
meta: {
|
|
2232
2788
|
name: APP_NAME,
|
|
2233
2789
|
version: APP_VERSION,
|
|
@@ -2242,7 +2798,11 @@ var main = defineCommand10({
|
|
|
2242
2798
|
secrets: secrets_default,
|
|
2243
2799
|
sync: sync_default,
|
|
2244
2800
|
audit: audit_default,
|
|
2245
|
-
update: update_default
|
|
2801
|
+
update: update_default,
|
|
2802
|
+
config: config_default,
|
|
2803
|
+
search: search_default,
|
|
2804
|
+
info: info_default,
|
|
2805
|
+
run: run_default
|
|
2246
2806
|
}
|
|
2247
2807
|
});
|
|
2248
2808
|
runMain(main);
|