mcpman 0.1.1 → 0.3.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 +68 -2
- package/dist/chunk-6X6Q6UZC.js +141 -0
- package/dist/index.cjs +1769 -356
- package/dist/index.js +1479 -308
- package/dist/trust-scorer-LYC6KZCD.js +77 -0
- package/dist/vault-service-UTZAV6N6.js +29 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,90 @@ var init_cjs_shims = __esm({
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
// src/core/trust-scorer.ts
|
|
41
|
+
var trust_scorer_exports = {};
|
|
42
|
+
__export(trust_scorer_exports, {
|
|
43
|
+
computeTrustScore: () => computeTrustScore
|
|
44
|
+
});
|
|
45
|
+
function vulnScore(vulns) {
|
|
46
|
+
let score = 100;
|
|
47
|
+
for (const v of vulns) {
|
|
48
|
+
if (v.severity === "critical") score -= 25;
|
|
49
|
+
else if (v.severity === "high") score -= 15;
|
|
50
|
+
else if (v.severity === "moderate") score -= 10;
|
|
51
|
+
else score -= 5;
|
|
52
|
+
}
|
|
53
|
+
return Math.max(0, score);
|
|
54
|
+
}
|
|
55
|
+
function downloadScore(weeklyDownloads) {
|
|
56
|
+
if (weeklyDownloads <= 0) return 0;
|
|
57
|
+
if (weeklyDownloads >= 1e6) return 100;
|
|
58
|
+
if (weeklyDownloads >= 1e5) return 80;
|
|
59
|
+
if (weeklyDownloads >= 1e4) return 60;
|
|
60
|
+
if (weeklyDownloads >= 1e3) return 40;
|
|
61
|
+
if (weeklyDownloads >= 100) return 20;
|
|
62
|
+
return 10;
|
|
63
|
+
}
|
|
64
|
+
function ageScore(packageAge) {
|
|
65
|
+
if (packageAge <= 0) return 0;
|
|
66
|
+
if (packageAge >= 730) return 100;
|
|
67
|
+
if (packageAge >= 365) return 80;
|
|
68
|
+
if (packageAge >= 180) return 60;
|
|
69
|
+
if (packageAge >= 30) return 30;
|
|
70
|
+
return 10;
|
|
71
|
+
}
|
|
72
|
+
function publishScore(lastPublish) {
|
|
73
|
+
const daysSince = Math.floor((Date.now() - new Date(lastPublish).getTime()) / 864e5);
|
|
74
|
+
if (daysSince <= 30) return 100;
|
|
75
|
+
if (daysSince <= 90) return 80;
|
|
76
|
+
if (daysSince <= 180) return 60;
|
|
77
|
+
if (daysSince <= 365) return 40;
|
|
78
|
+
return 20;
|
|
79
|
+
}
|
|
80
|
+
function maintainerScore(count, deprecated) {
|
|
81
|
+
let score = 0;
|
|
82
|
+
if (count >= 3) score = 90;
|
|
83
|
+
else if (count === 2) score = 70;
|
|
84
|
+
else score = 50;
|
|
85
|
+
if (!deprecated) score += 10;
|
|
86
|
+
return Math.min(100, score);
|
|
87
|
+
}
|
|
88
|
+
function toRiskLevel(score) {
|
|
89
|
+
if (score >= 80) return "LOW";
|
|
90
|
+
if (score >= 50) return "MEDIUM";
|
|
91
|
+
if (score >= 20) return "HIGH";
|
|
92
|
+
return "CRITICAL";
|
|
93
|
+
}
|
|
94
|
+
function computeTrustScore(metadata, vulns) {
|
|
95
|
+
if (!metadata) {
|
|
96
|
+
const vScore = vulnScore(vulns);
|
|
97
|
+
const score2 = Math.round(vScore * WEIGHT_VULNS * 100) / 100;
|
|
98
|
+
return { score: Math.round(score2), riskLevel: toRiskLevel(score2) };
|
|
99
|
+
}
|
|
100
|
+
const scores = {
|
|
101
|
+
vulns: vulnScore(vulns),
|
|
102
|
+
downloads: downloadScore(metadata.weeklyDownloads),
|
|
103
|
+
age: ageScore(metadata.packageAge),
|
|
104
|
+
publish: publishScore(metadata.lastPublish),
|
|
105
|
+
maintainers: maintainerScore(metadata.maintainerCount, metadata.deprecated)
|
|
106
|
+
};
|
|
107
|
+
const weighted = scores.vulns * WEIGHT_VULNS + scores.downloads * WEIGHT_DOWNLOADS + scores.age * WEIGHT_AGE + scores.publish * WEIGHT_PUBLISH_FREQ + scores.maintainers * WEIGHT_MAINTAINERS;
|
|
108
|
+
const score = Math.min(100, Math.max(0, Math.round(weighted)));
|
|
109
|
+
return { score, riskLevel: toRiskLevel(score) };
|
|
110
|
+
}
|
|
111
|
+
var WEIGHT_VULNS, WEIGHT_DOWNLOADS, WEIGHT_AGE, WEIGHT_PUBLISH_FREQ, WEIGHT_MAINTAINERS;
|
|
112
|
+
var init_trust_scorer = __esm({
|
|
113
|
+
"src/core/trust-scorer.ts"() {
|
|
114
|
+
"use strict";
|
|
115
|
+
init_cjs_shims();
|
|
116
|
+
WEIGHT_VULNS = 0.3;
|
|
117
|
+
WEIGHT_DOWNLOADS = 0.2;
|
|
118
|
+
WEIGHT_AGE = 0.15;
|
|
119
|
+
WEIGHT_PUBLISH_FREQ = 0.15;
|
|
120
|
+
WEIGHT_MAINTAINERS = 0.2;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
40
124
|
// src/clients/types.ts
|
|
41
125
|
var ConfigParseError, ConfigWriteError;
|
|
42
126
|
var init_types = __esm({
|
|
@@ -64,43 +148,43 @@ var init_types = __esm({
|
|
|
64
148
|
async function atomicWrite(filePath, content) {
|
|
65
149
|
const tmpPath = `${filePath}.tmp`;
|
|
66
150
|
try {
|
|
67
|
-
await
|
|
68
|
-
await
|
|
69
|
-
await
|
|
151
|
+
await import_node_fs3.default.promises.mkdir(import_node_path3.default.dirname(filePath), { recursive: true });
|
|
152
|
+
await import_node_fs3.default.promises.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
|
|
153
|
+
await import_node_fs3.default.promises.rename(tmpPath, filePath);
|
|
70
154
|
} catch (err) {
|
|
71
155
|
try {
|
|
72
|
-
await
|
|
156
|
+
await import_node_fs3.default.promises.unlink(tmpPath);
|
|
73
157
|
} catch {
|
|
74
158
|
}
|
|
75
159
|
throw err;
|
|
76
160
|
}
|
|
77
161
|
}
|
|
78
|
-
async function pathExists(
|
|
162
|
+
async function pathExists(p11) {
|
|
79
163
|
try {
|
|
80
|
-
await
|
|
164
|
+
await import_node_fs3.default.promises.access(p11);
|
|
81
165
|
return true;
|
|
82
166
|
} catch {
|
|
83
167
|
return false;
|
|
84
168
|
}
|
|
85
169
|
}
|
|
86
|
-
var
|
|
170
|
+
var import_node_fs3, import_node_path3, BaseClientHandler;
|
|
87
171
|
var init_base_client_handler = __esm({
|
|
88
172
|
"src/clients/base-client-handler.ts"() {
|
|
89
173
|
"use strict";
|
|
90
174
|
init_cjs_shims();
|
|
91
|
-
|
|
92
|
-
|
|
175
|
+
import_node_fs3 = __toESM(require("fs"), 1);
|
|
176
|
+
import_node_path3 = __toESM(require("path"), 1);
|
|
93
177
|
init_types();
|
|
94
178
|
BaseClientHandler = class {
|
|
95
179
|
async isInstalled() {
|
|
96
|
-
const dir =
|
|
180
|
+
const dir = import_node_path3.default.dirname(this.getConfigPath());
|
|
97
181
|
return pathExists(dir);
|
|
98
182
|
}
|
|
99
183
|
/** Read raw JSON from disk, return empty object if file missing */
|
|
100
184
|
async readRaw() {
|
|
101
185
|
const configPath = this.getConfigPath();
|
|
102
186
|
try {
|
|
103
|
-
const raw = await
|
|
187
|
+
const raw = await import_node_fs3.default.promises.readFile(configPath, "utf-8");
|
|
104
188
|
return JSON.parse(raw);
|
|
105
189
|
} catch (err) {
|
|
106
190
|
if (err.code === "ENOENT") {
|
|
@@ -151,26 +235,26 @@ var init_base_client_handler = __esm({
|
|
|
151
235
|
|
|
152
236
|
// src/utils/paths.ts
|
|
153
237
|
function getHomedir() {
|
|
154
|
-
return
|
|
238
|
+
return import_node_os3.default.homedir();
|
|
155
239
|
}
|
|
156
240
|
function getAppDataDir() {
|
|
157
241
|
const home = getHomedir();
|
|
158
242
|
if (process.platform === "darwin") {
|
|
159
|
-
return
|
|
243
|
+
return import_node_path4.default.join(home, "Library", "Application Support");
|
|
160
244
|
}
|
|
161
245
|
if (process.platform === "win32") {
|
|
162
|
-
return process.env.APPDATA ??
|
|
246
|
+
return process.env.APPDATA ?? import_node_path4.default.join(home, "AppData", "Roaming");
|
|
163
247
|
}
|
|
164
|
-
return process.env.XDG_CONFIG_HOME ??
|
|
248
|
+
return process.env.XDG_CONFIG_HOME ?? import_node_path4.default.join(home, ".config");
|
|
165
249
|
}
|
|
166
250
|
function resolveConfigPath(client) {
|
|
167
251
|
const appData = getAppDataDir();
|
|
168
252
|
const home = getHomedir();
|
|
169
253
|
switch (client) {
|
|
170
254
|
case "claude-desktop":
|
|
171
|
-
return
|
|
255
|
+
return import_node_path4.default.join(appData, "Claude", "claude_desktop_config.json");
|
|
172
256
|
case "cursor":
|
|
173
|
-
return
|
|
257
|
+
return import_node_path4.default.join(
|
|
174
258
|
appData,
|
|
175
259
|
"Cursor",
|
|
176
260
|
"User",
|
|
@@ -179,7 +263,7 @@ function resolveConfigPath(client) {
|
|
|
179
263
|
"mcp.json"
|
|
180
264
|
);
|
|
181
265
|
case "windsurf":
|
|
182
|
-
return
|
|
266
|
+
return import_node_path4.default.join(
|
|
183
267
|
appData,
|
|
184
268
|
"Windsurf",
|
|
185
269
|
"User",
|
|
@@ -189,21 +273,21 @@ function resolveConfigPath(client) {
|
|
|
189
273
|
);
|
|
190
274
|
case "vscode":
|
|
191
275
|
if (process.platform === "darwin") {
|
|
192
|
-
return
|
|
276
|
+
return import_node_path4.default.join(appData, "Code", "User", "settings.json");
|
|
193
277
|
}
|
|
194
278
|
if (process.platform === "win32") {
|
|
195
|
-
return
|
|
279
|
+
return import_node_path4.default.join(appData, "Code", "User", "settings.json");
|
|
196
280
|
}
|
|
197
|
-
return
|
|
281
|
+
return import_node_path4.default.join(home, ".config", "Code", "User", "settings.json");
|
|
198
282
|
}
|
|
199
283
|
}
|
|
200
|
-
var
|
|
284
|
+
var import_node_os3, import_node_path4;
|
|
201
285
|
var init_paths = __esm({
|
|
202
286
|
"src/utils/paths.ts"() {
|
|
203
287
|
"use strict";
|
|
204
288
|
init_cjs_shims();
|
|
205
|
-
|
|
206
|
-
|
|
289
|
+
import_node_os3 = __toESM(require("os"), 1);
|
|
290
|
+
import_node_path4 = __toESM(require("path"), 1);
|
|
207
291
|
}
|
|
208
292
|
});
|
|
209
293
|
|
|
@@ -313,32 +397,947 @@ function getClient(type) {
|
|
|
313
397
|
return new WindsurfHandler();
|
|
314
398
|
}
|
|
315
399
|
}
|
|
316
|
-
async function getInstalledClients() {
|
|
317
|
-
const all = getAllClientTypes().map(getClient);
|
|
318
|
-
const results = await Promise.all(
|
|
319
|
-
all.map(async (handler) => ({ handler, installed: await handler.isInstalled() }))
|
|
400
|
+
async function getInstalledClients() {
|
|
401
|
+
const all = getAllClientTypes().map(getClient);
|
|
402
|
+
const results = await Promise.all(
|
|
403
|
+
all.map(async (handler) => ({ handler, installed: await handler.isInstalled() }))
|
|
404
|
+
);
|
|
405
|
+
return results.filter((r) => r.installed).map((r) => r.handler);
|
|
406
|
+
}
|
|
407
|
+
var init_client_detector = __esm({
|
|
408
|
+
"src/clients/client-detector.ts"() {
|
|
409
|
+
"use strict";
|
|
410
|
+
init_cjs_shims();
|
|
411
|
+
init_claude_desktop();
|
|
412
|
+
init_cursor();
|
|
413
|
+
init_vscode();
|
|
414
|
+
init_windsurf();
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// src/core/vault-service.ts
|
|
419
|
+
var vault_service_exports = {};
|
|
420
|
+
__export(vault_service_exports, {
|
|
421
|
+
clearPasswordCache: () => clearPasswordCache,
|
|
422
|
+
decrypt: () => decrypt,
|
|
423
|
+
encrypt: () => encrypt,
|
|
424
|
+
getMasterPassword: () => getMasterPassword,
|
|
425
|
+
getSecret: () => getSecret,
|
|
426
|
+
getSecretsForServer: () => getSecretsForServer,
|
|
427
|
+
getVaultPath: () => getVaultPath,
|
|
428
|
+
listSecrets: () => listSecrets,
|
|
429
|
+
readVault: () => readVault,
|
|
430
|
+
removeSecret: () => removeSecret,
|
|
431
|
+
setSecret: () => setSecret,
|
|
432
|
+
writeVault: () => writeVault
|
|
433
|
+
});
|
|
434
|
+
function getVaultPath() {
|
|
435
|
+
return import_node_path6.default.join(import_node_os4.default.homedir(), ".mcpman", "vault.enc");
|
|
436
|
+
}
|
|
437
|
+
function readVault(vaultPath = getVaultPath()) {
|
|
438
|
+
const empty = { version: 1, servers: {} };
|
|
439
|
+
try {
|
|
440
|
+
const raw = import_node_fs4.default.readFileSync(vaultPath, "utf-8");
|
|
441
|
+
const parsed = JSON.parse(raw);
|
|
442
|
+
if (parsed.version !== 1 || typeof parsed.servers !== "object") return empty;
|
|
443
|
+
return parsed;
|
|
444
|
+
} catch {
|
|
445
|
+
return empty;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function writeVault(data, vaultPath = getVaultPath()) {
|
|
449
|
+
const dir = import_node_path6.default.dirname(vaultPath);
|
|
450
|
+
import_node_fs4.default.mkdirSync(dir, { recursive: true });
|
|
451
|
+
const tmp = `${vaultPath}.tmp`;
|
|
452
|
+
import_node_fs4.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
453
|
+
if (process.platform !== "win32") {
|
|
454
|
+
import_node_fs4.default.chmodSync(tmp, 384);
|
|
455
|
+
}
|
|
456
|
+
import_node_fs4.default.renameSync(tmp, vaultPath);
|
|
457
|
+
if (process.platform !== "win32") {
|
|
458
|
+
import_node_fs4.default.chmodSync(vaultPath, 384);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function encrypt(value, password2) {
|
|
462
|
+
const salt = import_node_crypto2.default.randomBytes(16);
|
|
463
|
+
const key = import_node_crypto2.default.pbkdf2Sync(password2, salt, 1e5, 32, "sha256");
|
|
464
|
+
const iv = import_node_crypto2.default.randomBytes(16);
|
|
465
|
+
const cipher = import_node_crypto2.default.createCipheriv("aes-256-cbc", key, iv);
|
|
466
|
+
const encrypted = Buffer.concat([cipher.update(value, "utf-8"), cipher.final()]);
|
|
467
|
+
return {
|
|
468
|
+
salt: salt.toString("hex"),
|
|
469
|
+
iv: iv.toString("hex"),
|
|
470
|
+
data: encrypted.toString("hex")
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function decrypt(entry, password2) {
|
|
474
|
+
const salt = Buffer.from(entry.salt, "hex");
|
|
475
|
+
const key = import_node_crypto2.default.pbkdf2Sync(password2, salt, 1e5, 32, "sha256");
|
|
476
|
+
const iv = Buffer.from(entry.iv, "hex");
|
|
477
|
+
const decipher = import_node_crypto2.default.createDecipheriv("aes-256-cbc", key, iv);
|
|
478
|
+
const decrypted = Buffer.concat([
|
|
479
|
+
decipher.update(Buffer.from(entry.data, "hex")),
|
|
480
|
+
decipher.final()
|
|
481
|
+
// throws ERR_OSSL_BAD_DECRYPT on wrong password
|
|
482
|
+
]);
|
|
483
|
+
return decrypted.toString("utf-8");
|
|
484
|
+
}
|
|
485
|
+
async function getMasterPassword(confirm8 = false) {
|
|
486
|
+
if (_cachedPassword) return _cachedPassword;
|
|
487
|
+
const password2 = await p3.password({
|
|
488
|
+
message: "Enter vault master password:",
|
|
489
|
+
validate: (v) => v.length < 8 ? "Password must be at least 8 characters" : void 0
|
|
490
|
+
});
|
|
491
|
+
if (p3.isCancel(password2)) {
|
|
492
|
+
p3.cancel("Vault access cancelled.");
|
|
493
|
+
process.exit(0);
|
|
494
|
+
}
|
|
495
|
+
if (confirm8) {
|
|
496
|
+
const confirm22 = await p3.password({ message: "Confirm master password:" });
|
|
497
|
+
if (p3.isCancel(confirm22) || confirm22 !== password2) {
|
|
498
|
+
p3.cancel("Passwords do not match.");
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
_cachedPassword = password2;
|
|
503
|
+
return _cachedPassword;
|
|
504
|
+
}
|
|
505
|
+
function clearPasswordCache() {
|
|
506
|
+
_cachedPassword = null;
|
|
507
|
+
}
|
|
508
|
+
function setSecret(server, key, value, password2, vaultPath = getVaultPath()) {
|
|
509
|
+
const vault = readVault(vaultPath);
|
|
510
|
+
if (!vault.servers[server]) vault.servers[server] = {};
|
|
511
|
+
vault.servers[server][key] = encrypt(value, password2);
|
|
512
|
+
writeVault(vault, vaultPath);
|
|
513
|
+
}
|
|
514
|
+
function getSecret(server, key, password2, vaultPath = getVaultPath()) {
|
|
515
|
+
const vault = readVault(vaultPath);
|
|
516
|
+
const entry = vault.servers[server]?.[key];
|
|
517
|
+
if (!entry) return null;
|
|
518
|
+
return decrypt(entry, password2);
|
|
519
|
+
}
|
|
520
|
+
function getSecretsForServer(server, password2, vaultPath = getVaultPath()) {
|
|
521
|
+
const vault = readVault(vaultPath);
|
|
522
|
+
const entries = vault.servers[server];
|
|
523
|
+
if (!entries) return {};
|
|
524
|
+
const result = {};
|
|
525
|
+
for (const [k, entry] of Object.entries(entries)) {
|
|
526
|
+
result[k] = decrypt(entry, password2);
|
|
527
|
+
}
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
function removeSecret(server, key, vaultPath = getVaultPath()) {
|
|
531
|
+
const vault = readVault(vaultPath);
|
|
532
|
+
if (vault.servers[server]) {
|
|
533
|
+
delete vault.servers[server][key];
|
|
534
|
+
if (Object.keys(vault.servers[server]).length === 0) {
|
|
535
|
+
delete vault.servers[server];
|
|
536
|
+
}
|
|
537
|
+
writeVault(vault, vaultPath);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function listSecrets(server, vaultPath = getVaultPath()) {
|
|
541
|
+
const vault = readVault(vaultPath);
|
|
542
|
+
const entries = server ? vault.servers[server] ? { [server]: vault.servers[server] } : {} : vault.servers;
|
|
543
|
+
return Object.entries(entries).map(([srv, keys]) => ({
|
|
544
|
+
server: srv,
|
|
545
|
+
keys: Object.keys(keys)
|
|
546
|
+
}));
|
|
547
|
+
}
|
|
548
|
+
var import_node_crypto2, import_node_fs4, import_node_os4, import_node_path6, p3, _cachedPassword;
|
|
549
|
+
var init_vault_service = __esm({
|
|
550
|
+
"src/core/vault-service.ts"() {
|
|
551
|
+
"use strict";
|
|
552
|
+
init_cjs_shims();
|
|
553
|
+
import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
554
|
+
import_node_fs4 = __toESM(require("fs"), 1);
|
|
555
|
+
import_node_os4 = __toESM(require("os"), 1);
|
|
556
|
+
import_node_path6 = __toESM(require("path"), 1);
|
|
557
|
+
p3 = __toESM(require("@clack/prompts"), 1);
|
|
558
|
+
_cachedPassword = null;
|
|
559
|
+
process.on("exit", () => {
|
|
560
|
+
_cachedPassword = null;
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// src/index.ts
|
|
566
|
+
init_cjs_shims();
|
|
567
|
+
var import_citty10 = require("citty");
|
|
568
|
+
|
|
569
|
+
// src/commands/audit.ts
|
|
570
|
+
init_cjs_shims();
|
|
571
|
+
var import_citty = require("citty");
|
|
572
|
+
var p = __toESM(require("@clack/prompts"), 1);
|
|
573
|
+
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
574
|
+
var import_nanospinner = require("nanospinner");
|
|
575
|
+
|
|
576
|
+
// src/core/lockfile.ts
|
|
577
|
+
init_cjs_shims();
|
|
578
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
579
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
580
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
581
|
+
var LOCKFILE_NAME = "mcpman.lock";
|
|
582
|
+
function findLockfile() {
|
|
583
|
+
let dir = process.cwd();
|
|
584
|
+
while (true) {
|
|
585
|
+
const candidate = import_node_path.default.join(dir, LOCKFILE_NAME);
|
|
586
|
+
if (import_node_fs.default.existsSync(candidate)) return candidate;
|
|
587
|
+
const parent = import_node_path.default.dirname(dir);
|
|
588
|
+
if (parent === dir) break;
|
|
589
|
+
dir = parent;
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
function getGlobalLockfilePath() {
|
|
594
|
+
return import_node_path.default.join(import_node_os.default.homedir(), ".mcpman", LOCKFILE_NAME);
|
|
595
|
+
}
|
|
596
|
+
function resolveLockfilePath() {
|
|
597
|
+
return findLockfile() ?? getGlobalLockfilePath();
|
|
598
|
+
}
|
|
599
|
+
function readLockfile(filePath) {
|
|
600
|
+
const target = filePath ?? resolveLockfilePath();
|
|
601
|
+
if (!import_node_fs.default.existsSync(target)) {
|
|
602
|
+
return { lockfileVersion: 1, servers: {} };
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
const raw = import_node_fs.default.readFileSync(target, "utf-8");
|
|
606
|
+
return JSON.parse(raw);
|
|
607
|
+
} catch {
|
|
608
|
+
return { lockfileVersion: 1, servers: {} };
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function serialize(data) {
|
|
612
|
+
const sorted = {
|
|
613
|
+
lockfileVersion: data.lockfileVersion,
|
|
614
|
+
servers: Object.fromEntries(
|
|
615
|
+
Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
|
|
616
|
+
)
|
|
617
|
+
};
|
|
618
|
+
return JSON.stringify(sorted, null, 2) + "\n";
|
|
619
|
+
}
|
|
620
|
+
function writeLockfile(data, filePath) {
|
|
621
|
+
const target = filePath ?? resolveLockfilePath();
|
|
622
|
+
const dir = import_node_path.default.dirname(target);
|
|
623
|
+
if (!import_node_fs.default.existsSync(dir)) {
|
|
624
|
+
import_node_fs.default.mkdirSync(dir, { recursive: true });
|
|
625
|
+
}
|
|
626
|
+
const tmp = `${target}.tmp`;
|
|
627
|
+
import_node_fs.default.writeFileSync(tmp, serialize(data), "utf-8");
|
|
628
|
+
import_node_fs.default.renameSync(tmp, target);
|
|
629
|
+
}
|
|
630
|
+
function addEntry(name, entry, filePath) {
|
|
631
|
+
const data = readLockfile(filePath);
|
|
632
|
+
data.servers[name] = entry;
|
|
633
|
+
writeLockfile(data, filePath);
|
|
634
|
+
}
|
|
635
|
+
function createEmptyLockfile(filePath) {
|
|
636
|
+
writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/core/security-scanner.ts
|
|
640
|
+
init_cjs_shims();
|
|
641
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
642
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
643
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
644
|
+
var CACHE_PATH = import_node_path2.default.join(import_node_os2.default.homedir(), ".mcpman", ".audit-cache.json");
|
|
645
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
646
|
+
function readCache() {
|
|
647
|
+
try {
|
|
648
|
+
if (!import_node_fs2.default.existsSync(CACHE_PATH)) return {};
|
|
649
|
+
return JSON.parse(import_node_fs2.default.readFileSync(CACHE_PATH, "utf-8"));
|
|
650
|
+
} catch {
|
|
651
|
+
return {};
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function writeCache(cache) {
|
|
655
|
+
const dir = import_node_path2.default.dirname(CACHE_PATH);
|
|
656
|
+
if (!import_node_fs2.default.existsSync(dir)) import_node_fs2.default.mkdirSync(dir, { recursive: true });
|
|
657
|
+
const tmp = `${CACHE_PATH}.tmp`;
|
|
658
|
+
import_node_fs2.default.writeFileSync(tmp, JSON.stringify(cache, null, 2), "utf-8");
|
|
659
|
+
import_node_fs2.default.renameSync(tmp, CACHE_PATH);
|
|
660
|
+
}
|
|
661
|
+
function getCachedReport(name, version) {
|
|
662
|
+
const cache = readCache();
|
|
663
|
+
const key = `${name}@${version}`;
|
|
664
|
+
const entry = cache[key];
|
|
665
|
+
if (!entry) return null;
|
|
666
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
|
|
667
|
+
return entry.report;
|
|
668
|
+
}
|
|
669
|
+
function cacheReport(name, version, report) {
|
|
670
|
+
const cache = readCache();
|
|
671
|
+
cache[`${name}@${version}`] = { report, timestamp: Date.now() };
|
|
672
|
+
writeCache(cache);
|
|
673
|
+
}
|
|
674
|
+
async function fetchNpmMetadata(packageName) {
|
|
675
|
+
const timeout = 1e4;
|
|
676
|
+
const signal = AbortSignal.timeout(timeout);
|
|
677
|
+
try {
|
|
678
|
+
const [regRes, dlRes] = await Promise.all([
|
|
679
|
+
fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, { signal }),
|
|
680
|
+
fetch(`https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`, {
|
|
681
|
+
signal: AbortSignal.timeout(timeout)
|
|
682
|
+
})
|
|
683
|
+
]);
|
|
684
|
+
if (!regRes.ok) return null;
|
|
685
|
+
const reg = await regRes.json();
|
|
686
|
+
const time = reg["time"] ?? {};
|
|
687
|
+
const created = time["created"] ? new Date(time["created"]) : null;
|
|
688
|
+
const modified = time["modified"] ? new Date(time["modified"]) : null;
|
|
689
|
+
const packageAge = created ? Math.floor((Date.now() - created.getTime()) / 864e5) : 0;
|
|
690
|
+
const maintainers = Array.isArray(reg["maintainers"]) ? reg["maintainers"] : [];
|
|
691
|
+
const latestVersion = reg["dist-tags"]?.["latest"] ?? "";
|
|
692
|
+
const versionData = reg["versions"]?.[latestVersion];
|
|
693
|
+
const deprecated = typeof versionData?.["deprecated"] === "string";
|
|
694
|
+
let weeklyDownloads = 0;
|
|
695
|
+
if (dlRes.ok) {
|
|
696
|
+
const dl = await dlRes.json();
|
|
697
|
+
weeklyDownloads = typeof dl["downloads"] === "number" ? dl["downloads"] : 0;
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
weeklyDownloads,
|
|
701
|
+
lastPublish: modified?.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
702
|
+
packageAge,
|
|
703
|
+
maintainerCount: maintainers.length,
|
|
704
|
+
deprecated
|
|
705
|
+
};
|
|
706
|
+
} catch {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async function fetchVulnerabilities(packageName, version) {
|
|
711
|
+
try {
|
|
712
|
+
const res = await fetch("https://api.osv.dev/v1/query", {
|
|
713
|
+
method: "POST",
|
|
714
|
+
headers: { "Content-Type": "application/json" },
|
|
715
|
+
body: JSON.stringify({ package: { name: packageName, ecosystem: "npm" }, version }),
|
|
716
|
+
signal: AbortSignal.timeout(1e4)
|
|
717
|
+
});
|
|
718
|
+
if (!res.ok) return [];
|
|
719
|
+
const data = await res.json();
|
|
720
|
+
const vulns = Array.isArray(data["vulns"]) ? data["vulns"] : [];
|
|
721
|
+
return vulns.map((v) => {
|
|
722
|
+
const severity = v["database_specific"]?.["severity"];
|
|
723
|
+
const sev = typeof severity === "string" ? severity.toLowerCase() : "moderate";
|
|
724
|
+
const refs = Array.isArray(v["references"]) ? v["references"] : [];
|
|
725
|
+
return {
|
|
726
|
+
severity: ["low", "moderate", "high", "critical"].includes(sev) ? sev : "moderate",
|
|
727
|
+
title: typeof v["summary"] === "string" ? v["summary"] : typeof v["id"] === "string" ? v["id"] : "Unknown vulnerability",
|
|
728
|
+
url: typeof refs[0]?.["url"] === "string" ? refs[0]["url"] : void 0
|
|
729
|
+
};
|
|
730
|
+
});
|
|
731
|
+
} catch {
|
|
732
|
+
return [];
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
async function scanServer(name, entry) {
|
|
736
|
+
if (entry.source !== "npm") {
|
|
737
|
+
return {
|
|
738
|
+
server: name,
|
|
739
|
+
source: entry.source,
|
|
740
|
+
score: null,
|
|
741
|
+
riskLevel: "UNKNOWN",
|
|
742
|
+
vulnerabilities: [],
|
|
743
|
+
metadata: null
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
const cached = getCachedReport(name, entry.version);
|
|
747
|
+
if (cached) return cached;
|
|
748
|
+
const [metadata, vulnerabilities] = await Promise.all([
|
|
749
|
+
fetchNpmMetadata(name),
|
|
750
|
+
fetchVulnerabilities(name, entry.version)
|
|
751
|
+
]);
|
|
752
|
+
const { computeTrustScore: computeTrustScore2 } = await Promise.resolve().then(() => (init_trust_scorer(), trust_scorer_exports));
|
|
753
|
+
const { score, riskLevel } = computeTrustScore2(metadata, vulnerabilities);
|
|
754
|
+
const report = {
|
|
755
|
+
server: name,
|
|
756
|
+
source: "npm",
|
|
757
|
+
score,
|
|
758
|
+
riskLevel,
|
|
759
|
+
vulnerabilities,
|
|
760
|
+
metadata
|
|
761
|
+
};
|
|
762
|
+
cacheReport(name, entry.version, report);
|
|
763
|
+
return report;
|
|
764
|
+
}
|
|
765
|
+
async function scanAllServers(servers, concurrency = 3) {
|
|
766
|
+
const entries = Object.entries(servers);
|
|
767
|
+
const results = [];
|
|
768
|
+
const executing = /* @__PURE__ */ new Set();
|
|
769
|
+
for (const [name, entry] of entries) {
|
|
770
|
+
const p11 = scanServer(name, entry).then((r) => {
|
|
771
|
+
results.push(r);
|
|
772
|
+
executing.delete(p11);
|
|
773
|
+
});
|
|
774
|
+
executing.add(p11);
|
|
775
|
+
if (executing.size >= concurrency) await Promise.race(executing);
|
|
776
|
+
}
|
|
777
|
+
await Promise.all(executing);
|
|
778
|
+
return results;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/core/version-checker.ts
|
|
782
|
+
init_cjs_shims();
|
|
783
|
+
function compareVersions(a, b) {
|
|
784
|
+
const aParts = a.replace(/^v/, "").split(".").map(Number);
|
|
785
|
+
const bParts = b.replace(/^v/, "").split(".").map(Number);
|
|
786
|
+
const len = Math.max(aParts.length, bParts.length);
|
|
787
|
+
for (let i = 0; i < len; i++) {
|
|
788
|
+
const aN = aParts[i] ?? 0;
|
|
789
|
+
const bN = bParts[i] ?? 0;
|
|
790
|
+
if (Number.isNaN(aN) || Number.isNaN(bN)) return 0;
|
|
791
|
+
if (aN < bN) return -1;
|
|
792
|
+
if (aN > bN) return 1;
|
|
793
|
+
}
|
|
794
|
+
return 0;
|
|
795
|
+
}
|
|
796
|
+
function detectUpdateType(current, latest) {
|
|
797
|
+
const cParts = current.replace(/^v/, "").split(".").map(Number);
|
|
798
|
+
const lParts = latest.replace(/^v/, "").split(".").map(Number);
|
|
799
|
+
if ((lParts[0] ?? 0) > (cParts[0] ?? 0)) return "major";
|
|
800
|
+
if ((lParts[1] ?? 0) > (cParts[1] ?? 0)) return "minor";
|
|
801
|
+
return "patch";
|
|
802
|
+
}
|
|
803
|
+
async function fetchNpmLatest(packageName) {
|
|
804
|
+
try {
|
|
805
|
+
const res = await fetch(
|
|
806
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
807
|
+
{
|
|
808
|
+
headers: { Accept: "application/json" },
|
|
809
|
+
signal: AbortSignal.timeout(8e3)
|
|
810
|
+
}
|
|
811
|
+
);
|
|
812
|
+
if (!res.ok) return null;
|
|
813
|
+
const data = await res.json();
|
|
814
|
+
return typeof data.version === "string" ? data.version : null;
|
|
815
|
+
} catch {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
async function fetchSmitheryLatest(name) {
|
|
820
|
+
try {
|
|
821
|
+
const res = await fetch(
|
|
822
|
+
`https://registry.smithery.ai/servers/${encodeURIComponent(name)}`,
|
|
823
|
+
{
|
|
824
|
+
headers: { Accept: "application/json" },
|
|
825
|
+
signal: AbortSignal.timeout(8e3)
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
if (!res.ok) return null;
|
|
829
|
+
const data = await res.json();
|
|
830
|
+
return typeof data.version === "string" ? data.version : null;
|
|
831
|
+
} catch {
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
async function fetchGithubLatest(resolved) {
|
|
836
|
+
const match = resolved.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
837
|
+
if (!match) return null;
|
|
838
|
+
const [, owner, repo] = match;
|
|
839
|
+
try {
|
|
840
|
+
const res = await fetch(
|
|
841
|
+
`https://api.github.com/repos/${owner}/${repo}/releases/latest`,
|
|
842
|
+
{
|
|
843
|
+
headers: { Accept: "application/json" },
|
|
844
|
+
signal: AbortSignal.timeout(8e3)
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
if (!res.ok) return null;
|
|
848
|
+
const data = await res.json();
|
|
849
|
+
return typeof data.tag_name === "string" ? data.tag_name.replace(/^v/, "") : null;
|
|
850
|
+
} catch {
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
async function checkVersion(name, lockEntry) {
|
|
855
|
+
const current = lockEntry.version;
|
|
856
|
+
let latest = null;
|
|
857
|
+
if (lockEntry.source === "npm") {
|
|
858
|
+
latest = await fetchNpmLatest(name);
|
|
859
|
+
} else if (lockEntry.source === "smithery") {
|
|
860
|
+
latest = await fetchSmitheryLatest(name);
|
|
861
|
+
} else if (lockEntry.source === "github") {
|
|
862
|
+
latest = await fetchGithubLatest(lockEntry.resolved);
|
|
863
|
+
}
|
|
864
|
+
if (!latest || latest === current) {
|
|
865
|
+
return {
|
|
866
|
+
server: name,
|
|
867
|
+
source: lockEntry.source,
|
|
868
|
+
currentVersion: current,
|
|
869
|
+
latestVersion: latest ?? current,
|
|
870
|
+
hasUpdate: false
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
const hasUpdate = compareVersions(current, latest) === -1;
|
|
874
|
+
return {
|
|
875
|
+
server: name,
|
|
876
|
+
source: lockEntry.source,
|
|
877
|
+
currentVersion: current,
|
|
878
|
+
latestVersion: latest,
|
|
879
|
+
hasUpdate,
|
|
880
|
+
updateType: hasUpdate ? detectUpdateType(current, latest) : void 0
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
async function checkAllVersions(lockfile) {
|
|
884
|
+
const entries = Object.entries(lockfile.servers);
|
|
885
|
+
if (entries.length === 0) return [];
|
|
886
|
+
const results = [];
|
|
887
|
+
const executing = /* @__PURE__ */ new Set();
|
|
888
|
+
for (const [name, entry] of entries) {
|
|
889
|
+
const p11 = checkVersion(name, entry).then((r) => {
|
|
890
|
+
results.push(r);
|
|
891
|
+
executing.delete(p11);
|
|
892
|
+
});
|
|
893
|
+
executing.add(p11);
|
|
894
|
+
if (executing.size >= 5) {
|
|
895
|
+
await Promise.race(executing);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
await Promise.all(executing);
|
|
899
|
+
return results;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/core/server-updater.ts
|
|
903
|
+
init_cjs_shims();
|
|
904
|
+
|
|
905
|
+
// src/core/server-resolver.ts
|
|
906
|
+
init_cjs_shims();
|
|
907
|
+
|
|
908
|
+
// src/core/registry.ts
|
|
909
|
+
init_cjs_shims();
|
|
910
|
+
var import_node_crypto = require("crypto");
|
|
911
|
+
function computeIntegrity(resolvedUrl) {
|
|
912
|
+
const hash = (0, import_node_crypto.createHash)("sha512").update(resolvedUrl).digest("base64");
|
|
913
|
+
return `sha512-${hash}`;
|
|
914
|
+
}
|
|
915
|
+
async function resolveFromSmithery(name) {
|
|
916
|
+
const url = `https://registry.smithery.ai/servers/${encodeURIComponent(name)}`;
|
|
917
|
+
let data;
|
|
918
|
+
try {
|
|
919
|
+
const res = await fetch(url, {
|
|
920
|
+
headers: { Accept: "application/json" },
|
|
921
|
+
signal: AbortSignal.timeout(8e3)
|
|
922
|
+
});
|
|
923
|
+
if (res.status === 404) {
|
|
924
|
+
throw new Error(`Server '${name}' not found on Smithery registry`);
|
|
925
|
+
}
|
|
926
|
+
if (!res.ok) {
|
|
927
|
+
throw new Error(`Smithery API error: ${res.status}`);
|
|
928
|
+
}
|
|
929
|
+
data = await res.json();
|
|
930
|
+
} catch (err) {
|
|
931
|
+
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
932
|
+
throw new Error(
|
|
933
|
+
`Cannot reach Smithery registry: ${err instanceof Error ? err.message : String(err)}`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
const version = typeof data.version === "string" ? data.version : "latest";
|
|
937
|
+
const command = typeof data.command === "string" ? data.command : "npx";
|
|
938
|
+
const args = Array.isArray(data.args) ? data.args : ["-y", `${name}@${version}`];
|
|
939
|
+
const envVars = Array.isArray(data.envVars) ? data.envVars : [];
|
|
940
|
+
const resolved = typeof data.resolved === "string" ? data.resolved : `smithery:${name}@${version}`;
|
|
941
|
+
return {
|
|
942
|
+
name,
|
|
943
|
+
version,
|
|
944
|
+
description: typeof data.description === "string" ? data.description : "",
|
|
945
|
+
runtime: "node",
|
|
946
|
+
command,
|
|
947
|
+
args,
|
|
948
|
+
envVars,
|
|
949
|
+
resolved
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
async function resolveFromNpm(packageName) {
|
|
953
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
954
|
+
let data;
|
|
955
|
+
try {
|
|
956
|
+
const res = await fetch(url, {
|
|
957
|
+
headers: { Accept: "application/json" },
|
|
958
|
+
signal: AbortSignal.timeout(8e3)
|
|
959
|
+
});
|
|
960
|
+
if (res.status === 404) {
|
|
961
|
+
throw new Error(`Package '${packageName}' not found on npm`);
|
|
962
|
+
}
|
|
963
|
+
if (!res.ok) {
|
|
964
|
+
throw new Error(`npm registry error: ${res.status}`);
|
|
965
|
+
}
|
|
966
|
+
data = await res.json();
|
|
967
|
+
} catch (err) {
|
|
968
|
+
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
969
|
+
throw new Error(
|
|
970
|
+
`Cannot reach npm registry: ${err instanceof Error ? err.message : String(err)}`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
const version = typeof data.version === "string" ? data.version : "latest";
|
|
974
|
+
const resolved = `https://registry.npmjs.org/${packageName}/-/${packageName.replace(/^@[^/]+\//, "")}-${version}.tgz`;
|
|
975
|
+
const mcpField = data.mcp && typeof data.mcp === "object" ? data.mcp : null;
|
|
976
|
+
const envVars = mcpField?.envVars ? mcpField.envVars : [];
|
|
977
|
+
return {
|
|
978
|
+
name: packageName,
|
|
979
|
+
version,
|
|
980
|
+
description: typeof data.description === "string" ? data.description : "",
|
|
981
|
+
runtime: "node",
|
|
982
|
+
command: "npx",
|
|
983
|
+
args: ["-y", `${packageName}@${version}`],
|
|
984
|
+
envVars,
|
|
985
|
+
resolved
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
async function resolveFromGitHub(githubUrl) {
|
|
989
|
+
const match = githubUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
990
|
+
if (!match) {
|
|
991
|
+
throw new Error(`Invalid GitHub URL: ${githubUrl}`);
|
|
992
|
+
}
|
|
993
|
+
const [, owner, repo] = match;
|
|
994
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/package.json`;
|
|
995
|
+
let pkgData = {};
|
|
996
|
+
try {
|
|
997
|
+
const res = await fetch(rawUrl, { signal: AbortSignal.timeout(8e3) });
|
|
998
|
+
if (res.ok) {
|
|
999
|
+
pkgData = await res.json();
|
|
1000
|
+
}
|
|
1001
|
+
} catch {
|
|
1002
|
+
}
|
|
1003
|
+
const version = typeof pkgData.version === "string" ? pkgData.version : "main";
|
|
1004
|
+
const name = typeof pkgData.name === "string" ? pkgData.name : `${owner}/${repo}`;
|
|
1005
|
+
return {
|
|
1006
|
+
name,
|
|
1007
|
+
version,
|
|
1008
|
+
description: typeof pkgData.description === "string" ? pkgData.description : "",
|
|
1009
|
+
runtime: "node",
|
|
1010
|
+
command: "npx",
|
|
1011
|
+
args: ["-y", githubUrl],
|
|
1012
|
+
envVars: [],
|
|
1013
|
+
resolved: githubUrl
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/core/server-resolver.ts
|
|
1018
|
+
function detectSource(input) {
|
|
1019
|
+
if (input.startsWith("smithery:")) {
|
|
1020
|
+
return { type: "smithery", input: input.slice(9) };
|
|
1021
|
+
}
|
|
1022
|
+
if (input.startsWith("https://github.com/") || input.startsWith("github.com/")) {
|
|
1023
|
+
return { type: "github", input };
|
|
1024
|
+
}
|
|
1025
|
+
return { type: "npm", input };
|
|
1026
|
+
}
|
|
1027
|
+
function parseEnvFlags(envFlags) {
|
|
1028
|
+
if (!envFlags) return {};
|
|
1029
|
+
const flags = Array.isArray(envFlags) ? envFlags : [envFlags];
|
|
1030
|
+
const result = {};
|
|
1031
|
+
for (const flag of flags) {
|
|
1032
|
+
const idx = flag.indexOf("=");
|
|
1033
|
+
if (idx > 0) {
|
|
1034
|
+
result[flag.slice(0, idx)] = flag.slice(idx + 1);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return result;
|
|
1038
|
+
}
|
|
1039
|
+
async function resolveServer(input) {
|
|
1040
|
+
const source = detectSource(input);
|
|
1041
|
+
switch (source.type) {
|
|
1042
|
+
case "smithery":
|
|
1043
|
+
return resolveFromSmithery(source.input);
|
|
1044
|
+
case "github":
|
|
1045
|
+
return resolveFromGitHub(source.input);
|
|
1046
|
+
case "npm":
|
|
1047
|
+
return resolveFromNpm(source.input);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/core/server-updater.ts
|
|
1052
|
+
async function applyServerUpdate(serverName, lockEntry, clients) {
|
|
1053
|
+
const fromVersion = lockEntry.version;
|
|
1054
|
+
const input = lockEntry.source === "smithery" ? `smithery:${serverName}` : lockEntry.source === "github" ? lockEntry.resolved : serverName;
|
|
1055
|
+
try {
|
|
1056
|
+
const metadata = await resolveServer(input);
|
|
1057
|
+
const integrity = computeIntegrity(metadata.resolved);
|
|
1058
|
+
addEntry(serverName, {
|
|
1059
|
+
...lockEntry,
|
|
1060
|
+
version: metadata.version,
|
|
1061
|
+
resolved: metadata.resolved,
|
|
1062
|
+
integrity,
|
|
1063
|
+
command: metadata.command,
|
|
1064
|
+
args: metadata.args,
|
|
1065
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1066
|
+
});
|
|
1067
|
+
const targetClients = clients.filter(
|
|
1068
|
+
(c) => lockEntry.clients.includes(c.type)
|
|
1069
|
+
);
|
|
1070
|
+
for (const client of targetClients) {
|
|
1071
|
+
try {
|
|
1072
|
+
await client.addServer(serverName, {
|
|
1073
|
+
command: metadata.command,
|
|
1074
|
+
args: metadata.args
|
|
1075
|
+
});
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return {
|
|
1080
|
+
server: serverName,
|
|
1081
|
+
success: true,
|
|
1082
|
+
fromVersion,
|
|
1083
|
+
toVersion: metadata.version
|
|
1084
|
+
};
|
|
1085
|
+
} catch (err) {
|
|
1086
|
+
return {
|
|
1087
|
+
server: serverName,
|
|
1088
|
+
success: false,
|
|
1089
|
+
fromVersion,
|
|
1090
|
+
toVersion: fromVersion,
|
|
1091
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/commands/audit.ts
|
|
1097
|
+
function colorRisk(level, score) {
|
|
1098
|
+
const label = score !== null ? `${score}/100 (${level})` : level;
|
|
1099
|
+
if (level === "LOW") return import_picocolors.default.green(label);
|
|
1100
|
+
if (level === "MEDIUM") return import_picocolors.default.yellow(label);
|
|
1101
|
+
if (level === "HIGH") return import_picocolors.default.red(label);
|
|
1102
|
+
if (level === "CRITICAL") return import_picocolors.default.bold(import_picocolors.default.red(label));
|
|
1103
|
+
return import_picocolors.default.dim(label);
|
|
1104
|
+
}
|
|
1105
|
+
function daysAgo(isoDate) {
|
|
1106
|
+
const days = Math.floor((Date.now() - new Date(isoDate).getTime()) / 864e5);
|
|
1107
|
+
if (days === 0) return "today";
|
|
1108
|
+
if (days === 1) return "1 day ago";
|
|
1109
|
+
return `${days} days ago`;
|
|
1110
|
+
}
|
|
1111
|
+
function countVulns(vulns) {
|
|
1112
|
+
const c = { critical: 0, high: 0, moderate: 0, low: 0 };
|
|
1113
|
+
for (const v of vulns) c[v.severity]++;
|
|
1114
|
+
if (vulns.length === 0) return import_picocolors.default.green("none");
|
|
1115
|
+
const parts = [];
|
|
1116
|
+
if (c.critical) parts.push(import_picocolors.default.bold(import_picocolors.default.red(`${c.critical} critical`)));
|
|
1117
|
+
if (c.high) parts.push(import_picocolors.default.red(`${c.high} high`));
|
|
1118
|
+
if (c.moderate) parts.push(import_picocolors.default.yellow(`${c.moderate} moderate`));
|
|
1119
|
+
if (c.low) parts.push(import_picocolors.default.dim(`${c.low} low`));
|
|
1120
|
+
return parts.join(", ");
|
|
1121
|
+
}
|
|
1122
|
+
function printReport(report) {
|
|
1123
|
+
const riskColored = colorRisk(report.riskLevel, report.score);
|
|
1124
|
+
const icon = report.riskLevel === "LOW" ? import_picocolors.default.green("\u25CF") : report.riskLevel === "MEDIUM" ? import_picocolors.default.yellow("\u25CF") : report.riskLevel === "UNKNOWN" ? import_picocolors.default.dim("\u25CB") : import_picocolors.default.red("\u25CF");
|
|
1125
|
+
console.log(` ${icon} ${import_picocolors.default.bold(report.server)} Score: ${riskColored}`);
|
|
1126
|
+
if (report.source !== "npm") {
|
|
1127
|
+
console.log(` ${import_picocolors.default.dim("Non-npm source \u2014 security data unavailable")}`);
|
|
1128
|
+
console.log();
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
if (report.metadata) {
|
|
1132
|
+
const { weeklyDownloads, packageAge, lastPublish, maintainerCount, deprecated } = report.metadata;
|
|
1133
|
+
const dlStr = weeklyDownloads.toLocaleString();
|
|
1134
|
+
console.log(
|
|
1135
|
+
` ${import_picocolors.default.dim("Downloads:")} ${dlStr}/week ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Age:")} ${packageAge}d ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Last publish:")} ${daysAgo(lastPublish)} ${import_picocolors.default.dim("|")} ${import_picocolors.default.dim("Maintainers:")} ${maintainerCount}` + (deprecated ? import_picocolors.default.red(" [DEPRECATED]") : "")
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
console.log(` ${import_picocolors.default.dim("Vulnerabilities:")} ${countVulns(report.vulnerabilities)}`);
|
|
1139
|
+
if (report.vulnerabilities.length > 0) {
|
|
1140
|
+
for (const v of report.vulnerabilities) {
|
|
1141
|
+
const sevColor = v.severity === "critical" || v.severity === "high" ? import_picocolors.default.red : import_picocolors.default.yellow;
|
|
1142
|
+
const url = v.url ? import_picocolors.default.dim(` ${v.url}`) : "";
|
|
1143
|
+
console.log(` ${sevColor("\u25B8")} [${v.severity}] ${v.title}${url}`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
console.log();
|
|
1147
|
+
}
|
|
1148
|
+
var audit_default = (0, import_citty.defineCommand)({
|
|
1149
|
+
meta: {
|
|
1150
|
+
name: "audit",
|
|
1151
|
+
description: "Scan installed MCP servers for security vulnerabilities and trust scores"
|
|
1152
|
+
},
|
|
1153
|
+
args: {
|
|
1154
|
+
server: {
|
|
1155
|
+
type: "positional",
|
|
1156
|
+
description: "Specific server to audit (omit to audit all)",
|
|
1157
|
+
required: false
|
|
1158
|
+
},
|
|
1159
|
+
json: {
|
|
1160
|
+
type: "boolean",
|
|
1161
|
+
description: "Output results as JSON",
|
|
1162
|
+
default: false
|
|
1163
|
+
},
|
|
1164
|
+
fix: {
|
|
1165
|
+
type: "boolean",
|
|
1166
|
+
description: "Apply updates to fix vulnerable packages",
|
|
1167
|
+
default: false
|
|
1168
|
+
},
|
|
1169
|
+
yes: {
|
|
1170
|
+
type: "boolean",
|
|
1171
|
+
description: "Skip confirmation prompt (use with --fix)",
|
|
1172
|
+
default: false
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
async run({ args }) {
|
|
1176
|
+
const lockfile = readLockfile();
|
|
1177
|
+
const { servers } = lockfile;
|
|
1178
|
+
if (Object.keys(servers).length === 0) {
|
|
1179
|
+
console.log(import_picocolors.default.dim("\n No MCP servers installed. Run mcpman install <server> to get started.\n"));
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const targets = {};
|
|
1183
|
+
if (args.server) {
|
|
1184
|
+
if (!servers[args.server]) {
|
|
1185
|
+
console.error(import_picocolors.default.red(`
|
|
1186
|
+
Server "${args.server}" not found in lockfile.
|
|
1187
|
+
`));
|
|
1188
|
+
process.exit(1);
|
|
1189
|
+
}
|
|
1190
|
+
targets[args.server] = servers[args.server];
|
|
1191
|
+
} else {
|
|
1192
|
+
Object.assign(targets, servers);
|
|
1193
|
+
}
|
|
1194
|
+
const spinner5 = (0, import_nanospinner.createSpinner)(`Scanning ${Object.keys(targets).length} server(s)...`).start();
|
|
1195
|
+
let reports;
|
|
1196
|
+
try {
|
|
1197
|
+
reports = args.server ? [await scanServer(args.server, targets[args.server])] : await scanAllServers(targets);
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
spinner5.error({ text: "Scan failed" });
|
|
1200
|
+
console.error(import_picocolors.default.red(String(err)));
|
|
1201
|
+
process.exit(1);
|
|
1202
|
+
}
|
|
1203
|
+
spinner5.success({ text: `Scanned ${reports.length} server(s)` });
|
|
1204
|
+
if (args.json) {
|
|
1205
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
console.log(import_picocolors.default.bold("\n mcpman audit\n"));
|
|
1209
|
+
console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
|
|
1210
|
+
for (const report of reports) {
|
|
1211
|
+
printReport(report);
|
|
1212
|
+
}
|
|
1213
|
+
console.log(import_picocolors.default.dim(" " + "\u2500".repeat(60)));
|
|
1214
|
+
const withIssues = reports.filter(
|
|
1215
|
+
(r) => r.riskLevel !== "LOW" && r.riskLevel !== "UNKNOWN"
|
|
1216
|
+
);
|
|
1217
|
+
const npmReports = reports.filter((r) => r.source === "npm");
|
|
1218
|
+
const parts = [];
|
|
1219
|
+
parts.push(`${reports.length} server(s) scanned`);
|
|
1220
|
+
if (npmReports.length < reports.length) {
|
|
1221
|
+
parts.push(import_picocolors.default.dim(`${reports.length - npmReports.length} non-npm (unverified)`));
|
|
1222
|
+
}
|
|
1223
|
+
if (withIssues.length > 0) {
|
|
1224
|
+
parts.push(import_picocolors.default.yellow(`${withIssues.length} with issues`));
|
|
1225
|
+
} else {
|
|
1226
|
+
parts.push(import_picocolors.default.green("all clear"));
|
|
1227
|
+
}
|
|
1228
|
+
console.log(`
|
|
1229
|
+
Summary: ${parts.join(" | ")}
|
|
1230
|
+
`);
|
|
1231
|
+
if (args.fix) {
|
|
1232
|
+
await runAuditFix(reports, lockfile.servers, args.yes);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
async function loadClients() {
|
|
1237
|
+
try {
|
|
1238
|
+
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
1239
|
+
return mod.getInstalledClients();
|
|
1240
|
+
} catch {
|
|
1241
|
+
return [];
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
async function runAuditFix(reports, servers, skipConfirm) {
|
|
1245
|
+
const npmWithVulns = reports.filter(
|
|
1246
|
+
(r) => r.vulnerabilities.length > 0 && r.source === "npm"
|
|
320
1247
|
);
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
"
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
init_windsurf();
|
|
1248
|
+
const nonNpmWithVulns = reports.filter(
|
|
1249
|
+
(r) => r.vulnerabilities.length > 0 && r.source !== "npm"
|
|
1250
|
+
);
|
|
1251
|
+
if (nonNpmWithVulns.length > 0) {
|
|
1252
|
+
console.log(import_picocolors.default.yellow(" Non-npm servers require manual update:"));
|
|
1253
|
+
for (const r of nonNpmWithVulns) {
|
|
1254
|
+
console.log(` ${import_picocolors.default.dim("\u2192")} ${r.server} (${r.source})`);
|
|
1255
|
+
}
|
|
1256
|
+
console.log();
|
|
331
1257
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
1258
|
+
if (npmWithVulns.length === 0) {
|
|
1259
|
+
console.log(import_picocolors.default.green(" No fixable vulnerabilities found.\n"));
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const versionSpinner = (0, import_nanospinner.createSpinner)("Checking for available updates...").start();
|
|
1263
|
+
const versionChecks = await Promise.all(
|
|
1264
|
+
npmWithVulns.map((r) => checkVersion(r.server, servers[r.server]))
|
|
1265
|
+
);
|
|
1266
|
+
versionSpinner.success({ text: "Version check complete" });
|
|
1267
|
+
const updatable = versionChecks.filter((u) => u.hasUpdate);
|
|
1268
|
+
if (updatable.length === 0) {
|
|
1269
|
+
console.log(import_picocolors.default.yellow(
|
|
1270
|
+
" Vulnerable servers have no newer versions available yet.\n Allow time for registry to publish fixes.\n"
|
|
1271
|
+
));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
console.log(import_picocolors.default.bold(`
|
|
1275
|
+
${updatable.length} server(s) can be updated to fix vulnerabilities:
|
|
1276
|
+
`));
|
|
1277
|
+
for (const u of updatable) {
|
|
1278
|
+
console.log(` ${import_picocolors.default.cyan("\u2192")} ${u.server} ${import_picocolors.default.dim(u.currentVersion)} \u2192 ${import_picocolors.default.green(u.latestVersion)}`);
|
|
1279
|
+
}
|
|
1280
|
+
console.log();
|
|
1281
|
+
if (!skipConfirm) {
|
|
1282
|
+
const confirmed = await p.confirm({
|
|
1283
|
+
message: `Update ${updatable.length} vulnerable server(s)?`,
|
|
1284
|
+
initialValue: true
|
|
1285
|
+
});
|
|
1286
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
1287
|
+
p.outro("Cancelled.");
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const clients = await loadClients();
|
|
1292
|
+
let successCount = 0;
|
|
1293
|
+
const results = [];
|
|
1294
|
+
for (const u of updatable) {
|
|
1295
|
+
const s = (0, import_nanospinner.createSpinner)(`Updating ${u.server}...`).start();
|
|
1296
|
+
const result = await applyServerUpdate(u.server, servers[u.server], clients);
|
|
1297
|
+
if (result.success) {
|
|
1298
|
+
s.success({ text: `${import_picocolors.default.green("\u2713")} ${u.server}: ${result.fromVersion} \u2192 ${result.toVersion}` });
|
|
1299
|
+
successCount++;
|
|
1300
|
+
} else {
|
|
1301
|
+
s.error({ text: `${import_picocolors.default.red("\u2717")} ${u.server}: ${result.error}` });
|
|
1302
|
+
}
|
|
1303
|
+
results.push({
|
|
1304
|
+
server: u.server,
|
|
1305
|
+
from: result.fromVersion,
|
|
1306
|
+
to: result.toVersion,
|
|
1307
|
+
ok: result.success,
|
|
1308
|
+
error: result.error
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
console.log();
|
|
1312
|
+
if (successCount > 0) {
|
|
1313
|
+
const updatedNames = results.filter((r) => r.ok).map((r) => r.server);
|
|
1314
|
+
const freshLockfile = readLockfile();
|
|
1315
|
+
const rescanSpinner = (0, import_nanospinner.createSpinner)("Re-scanning updated servers...").start();
|
|
1316
|
+
const afterReports = await Promise.all(
|
|
1317
|
+
updatedNames.map((name) => scanServer(name, freshLockfile.servers[name]))
|
|
1318
|
+
);
|
|
1319
|
+
rescanSpinner.success({ text: "Re-scan complete" });
|
|
1320
|
+
console.log(import_picocolors.default.bold("\n Before / After:\n"));
|
|
1321
|
+
for (const after of afterReports) {
|
|
1322
|
+
const before = reports.find((r) => r.server === after.server);
|
|
1323
|
+
const beforeVulns = before?.vulnerabilities.length ?? 0;
|
|
1324
|
+
const afterVulns = after.vulnerabilities.length;
|
|
1325
|
+
const improved = afterVulns < beforeVulns ? import_picocolors.default.green("improved") : import_picocolors.default.yellow("unchanged");
|
|
1326
|
+
console.log(
|
|
1327
|
+
` ${import_picocolors.default.bold(after.server)} vulns: ${beforeVulns} \u2192 ${afterVulns} [${improved}]`
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
console.log();
|
|
1331
|
+
}
|
|
1332
|
+
console.log(`
|
|
1333
|
+
${successCount} of ${updatable.length} server(s) updated.
|
|
1334
|
+
`);
|
|
1335
|
+
}
|
|
337
1336
|
|
|
338
1337
|
// src/commands/doctor.ts
|
|
339
1338
|
init_cjs_shims();
|
|
340
|
-
var
|
|
341
|
-
var
|
|
1339
|
+
var import_citty2 = require("citty");
|
|
1340
|
+
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
342
1341
|
|
|
343
1342
|
// src/core/server-inventory.ts
|
|
344
1343
|
init_cjs_shims();
|
|
@@ -609,12 +1608,12 @@ async function quickHealthProbe(config, timeoutMs = 3e3) {
|
|
|
609
1608
|
|
|
610
1609
|
// src/commands/doctor.ts
|
|
611
1610
|
var CHECK_ICON = {
|
|
612
|
-
pass:
|
|
613
|
-
fail:
|
|
614
|
-
skip:
|
|
615
|
-
warn:
|
|
1611
|
+
pass: import_picocolors2.default.green("\u2713"),
|
|
1612
|
+
fail: import_picocolors2.default.red("\u2717"),
|
|
1613
|
+
skip: import_picocolors2.default.dim("-"),
|
|
1614
|
+
warn: import_picocolors2.default.yellow("\u26A0")
|
|
616
1615
|
};
|
|
617
|
-
var doctor_default = (0,
|
|
1616
|
+
var doctor_default = (0, import_citty2.defineCommand)({
|
|
618
1617
|
meta: {
|
|
619
1618
|
name: "doctor",
|
|
620
1619
|
description: "Check MCP server health and configuration"
|
|
@@ -627,10 +1626,10 @@ var doctor_default = (0, import_citty.defineCommand)({
|
|
|
627
1626
|
}
|
|
628
1627
|
},
|
|
629
1628
|
async run({ args }) {
|
|
630
|
-
console.log(
|
|
1629
|
+
console.log(import_picocolors2.default.bold("\n mcpman doctor\n"));
|
|
631
1630
|
const servers = await getInstalledServers();
|
|
632
1631
|
if (servers.length === 0) {
|
|
633
|
-
console.log(
|
|
1632
|
+
console.log(import_picocolors2.default.dim(" No MCP servers installed. Run mcpman install <server> to get started."));
|
|
634
1633
|
return;
|
|
635
1634
|
}
|
|
636
1635
|
const tasks = servers.map((s) => () => checkServerHealth(s.name, s.config));
|
|
@@ -642,14 +1641,14 @@ var doctor_default = (0, import_citty.defineCommand)({
|
|
|
642
1641
|
if (result.status === "healthy") passed++;
|
|
643
1642
|
else failed++;
|
|
644
1643
|
}
|
|
645
|
-
console.log(
|
|
1644
|
+
console.log(import_picocolors2.default.dim(" " + "\u2500".repeat(50)));
|
|
646
1645
|
const parts = [];
|
|
647
|
-
if (passed > 0) parts.push(
|
|
648
|
-
if (failed > 0) parts.push(
|
|
1646
|
+
if (passed > 0) parts.push(import_picocolors2.default.green(`${passed} healthy`));
|
|
1647
|
+
if (failed > 0) parts.push(import_picocolors2.default.red(`${failed} unhealthy`));
|
|
649
1648
|
console.log(` Summary: ${parts.join(", ")}`);
|
|
650
1649
|
if (failed > 0) {
|
|
651
1650
|
if (!args.fix) {
|
|
652
|
-
console.log(
|
|
1651
|
+
console.log(import_picocolors2.default.dim(` Run ${import_picocolors2.default.cyan("mcpman doctor --fix")} for fix suggestions.
|
|
653
1652
|
`));
|
|
654
1653
|
}
|
|
655
1654
|
process.exit(1);
|
|
@@ -658,13 +1657,13 @@ var doctor_default = (0, import_citty.defineCommand)({
|
|
|
658
1657
|
}
|
|
659
1658
|
});
|
|
660
1659
|
function printServerResult(result, showFix) {
|
|
661
|
-
const icon = result.status === "healthy" ?
|
|
662
|
-
console.log(` ${icon} ${
|
|
1660
|
+
const icon = result.status === "healthy" ? import_picocolors2.default.green("\u25CF") : import_picocolors2.default.red("\u25CF");
|
|
1661
|
+
console.log(` ${icon} ${import_picocolors2.default.bold(result.serverName)}`);
|
|
663
1662
|
for (const check of result.checks) {
|
|
664
1663
|
const checkIcon = check.skipped ? CHECK_ICON.skip : check.passed ? CHECK_ICON.pass : CHECK_ICON.fail;
|
|
665
1664
|
console.log(` ${checkIcon} ${check.name}: ${check.message}`);
|
|
666
1665
|
if (showFix && !check.passed && !check.skipped && check.fix) {
|
|
667
|
-
console.log(` ${
|
|
1666
|
+
console.log(` ${import_picocolors2.default.yellow("\u2192")} Fix: ${import_picocolors2.default.cyan(check.fix)}`);
|
|
668
1667
|
}
|
|
669
1668
|
}
|
|
670
1669
|
console.log();
|
|
@@ -673,11 +1672,11 @@ async function runParallel(tasks, concurrency) {
|
|
|
673
1672
|
const results = [];
|
|
674
1673
|
const executing = /* @__PURE__ */ new Set();
|
|
675
1674
|
for (const task of tasks) {
|
|
676
|
-
const
|
|
1675
|
+
const p11 = task().then((r) => {
|
|
677
1676
|
results.push(r);
|
|
678
|
-
executing.delete(
|
|
1677
|
+
executing.delete(p11);
|
|
679
1678
|
});
|
|
680
|
-
executing.add(
|
|
1679
|
+
executing.add(p11);
|
|
681
1680
|
if (executing.size >= concurrency) {
|
|
682
1681
|
await Promise.race(executing);
|
|
683
1682
|
}
|
|
@@ -688,184 +1687,10 @@ async function runParallel(tasks, concurrency) {
|
|
|
688
1687
|
|
|
689
1688
|
// src/commands/init.ts
|
|
690
1689
|
init_cjs_shims();
|
|
691
|
-
var
|
|
692
|
-
var
|
|
693
|
-
var
|
|
694
|
-
|
|
695
|
-
// src/core/lockfile.ts
|
|
696
|
-
init_cjs_shims();
|
|
697
|
-
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
698
|
-
var import_node_path3 = __toESM(require("path"), 1);
|
|
699
|
-
var import_node_os2 = __toESM(require("os"), 1);
|
|
700
|
-
var LOCKFILE_NAME = "mcpman.lock";
|
|
701
|
-
function findLockfile() {
|
|
702
|
-
let dir = process.cwd();
|
|
703
|
-
while (true) {
|
|
704
|
-
const candidate = import_node_path3.default.join(dir, LOCKFILE_NAME);
|
|
705
|
-
if (import_node_fs2.default.existsSync(candidate)) return candidate;
|
|
706
|
-
const parent = import_node_path3.default.dirname(dir);
|
|
707
|
-
if (parent === dir) break;
|
|
708
|
-
dir = parent;
|
|
709
|
-
}
|
|
710
|
-
return null;
|
|
711
|
-
}
|
|
712
|
-
function getGlobalLockfilePath() {
|
|
713
|
-
return import_node_path3.default.join(import_node_os2.default.homedir(), ".mcpman", LOCKFILE_NAME);
|
|
714
|
-
}
|
|
715
|
-
function resolveLockfilePath() {
|
|
716
|
-
return findLockfile() ?? getGlobalLockfilePath();
|
|
717
|
-
}
|
|
718
|
-
function readLockfile(filePath) {
|
|
719
|
-
const target = filePath ?? resolveLockfilePath();
|
|
720
|
-
if (!import_node_fs2.default.existsSync(target)) {
|
|
721
|
-
return { lockfileVersion: 1, servers: {} };
|
|
722
|
-
}
|
|
723
|
-
try {
|
|
724
|
-
const raw = import_node_fs2.default.readFileSync(target, "utf-8");
|
|
725
|
-
return JSON.parse(raw);
|
|
726
|
-
} catch {
|
|
727
|
-
return { lockfileVersion: 1, servers: {} };
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
function serialize(data) {
|
|
731
|
-
const sorted = {
|
|
732
|
-
lockfileVersion: data.lockfileVersion,
|
|
733
|
-
servers: Object.fromEntries(
|
|
734
|
-
Object.entries(data.servers).sort(([a], [b]) => a.localeCompare(b))
|
|
735
|
-
)
|
|
736
|
-
};
|
|
737
|
-
return JSON.stringify(sorted, null, 2) + "\n";
|
|
738
|
-
}
|
|
739
|
-
function writeLockfile(data, filePath) {
|
|
740
|
-
const target = filePath ?? resolveLockfilePath();
|
|
741
|
-
const dir = import_node_path3.default.dirname(target);
|
|
742
|
-
if (!import_node_fs2.default.existsSync(dir)) {
|
|
743
|
-
import_node_fs2.default.mkdirSync(dir, { recursive: true });
|
|
744
|
-
}
|
|
745
|
-
const tmp = `${target}.tmp`;
|
|
746
|
-
import_node_fs2.default.writeFileSync(tmp, serialize(data), "utf-8");
|
|
747
|
-
import_node_fs2.default.renameSync(tmp, target);
|
|
748
|
-
}
|
|
749
|
-
function addEntry(name, entry, filePath) {
|
|
750
|
-
const data = readLockfile(filePath);
|
|
751
|
-
data.servers[name] = entry;
|
|
752
|
-
writeLockfile(data, filePath);
|
|
753
|
-
}
|
|
754
|
-
function createEmptyLockfile(filePath) {
|
|
755
|
-
writeLockfile({ lockfileVersion: 1, servers: {} }, filePath);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// src/core/registry.ts
|
|
759
|
-
init_cjs_shims();
|
|
760
|
-
var import_node_crypto = require("crypto");
|
|
761
|
-
function computeIntegrity(resolvedUrl) {
|
|
762
|
-
const hash = (0, import_node_crypto.createHash)("sha512").update(resolvedUrl).digest("base64");
|
|
763
|
-
return `sha512-${hash}`;
|
|
764
|
-
}
|
|
765
|
-
async function resolveFromSmithery(name) {
|
|
766
|
-
const url = `https://registry.smithery.ai/servers/${encodeURIComponent(name)}`;
|
|
767
|
-
let data;
|
|
768
|
-
try {
|
|
769
|
-
const res = await fetch(url, {
|
|
770
|
-
headers: { Accept: "application/json" },
|
|
771
|
-
signal: AbortSignal.timeout(8e3)
|
|
772
|
-
});
|
|
773
|
-
if (res.status === 404) {
|
|
774
|
-
throw new Error(`Server '${name}' not found on Smithery registry`);
|
|
775
|
-
}
|
|
776
|
-
if (!res.ok) {
|
|
777
|
-
throw new Error(`Smithery API error: ${res.status}`);
|
|
778
|
-
}
|
|
779
|
-
data = await res.json();
|
|
780
|
-
} catch (err) {
|
|
781
|
-
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
782
|
-
throw new Error(
|
|
783
|
-
`Cannot reach Smithery registry: ${err instanceof Error ? err.message : String(err)}`
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
const version = typeof data.version === "string" ? data.version : "latest";
|
|
787
|
-
const command = typeof data.command === "string" ? data.command : "npx";
|
|
788
|
-
const args = Array.isArray(data.args) ? data.args : ["-y", `${name}@${version}`];
|
|
789
|
-
const envVars = Array.isArray(data.envVars) ? data.envVars : [];
|
|
790
|
-
const resolved = typeof data.resolved === "string" ? data.resolved : `smithery:${name}@${version}`;
|
|
791
|
-
return {
|
|
792
|
-
name,
|
|
793
|
-
version,
|
|
794
|
-
description: typeof data.description === "string" ? data.description : "",
|
|
795
|
-
runtime: "node",
|
|
796
|
-
command,
|
|
797
|
-
args,
|
|
798
|
-
envVars,
|
|
799
|
-
resolved
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
async function resolveFromNpm(packageName) {
|
|
803
|
-
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
804
|
-
let data;
|
|
805
|
-
try {
|
|
806
|
-
const res = await fetch(url, {
|
|
807
|
-
headers: { Accept: "application/json" },
|
|
808
|
-
signal: AbortSignal.timeout(8e3)
|
|
809
|
-
});
|
|
810
|
-
if (res.status === 404) {
|
|
811
|
-
throw new Error(`Package '${packageName}' not found on npm`);
|
|
812
|
-
}
|
|
813
|
-
if (!res.ok) {
|
|
814
|
-
throw new Error(`npm registry error: ${res.status}`);
|
|
815
|
-
}
|
|
816
|
-
data = await res.json();
|
|
817
|
-
} catch (err) {
|
|
818
|
-
if (err instanceof Error && err.message.includes("not found")) throw err;
|
|
819
|
-
throw new Error(
|
|
820
|
-
`Cannot reach npm registry: ${err instanceof Error ? err.message : String(err)}`
|
|
821
|
-
);
|
|
822
|
-
}
|
|
823
|
-
const version = typeof data.version === "string" ? data.version : "latest";
|
|
824
|
-
const resolved = `https://registry.npmjs.org/${packageName}/-/${packageName.replace(/^@[^/]+\//, "")}-${version}.tgz`;
|
|
825
|
-
const mcpField = data.mcp && typeof data.mcp === "object" ? data.mcp : null;
|
|
826
|
-
const envVars = mcpField?.envVars ? mcpField.envVars : [];
|
|
827
|
-
return {
|
|
828
|
-
name: packageName,
|
|
829
|
-
version,
|
|
830
|
-
description: typeof data.description === "string" ? data.description : "",
|
|
831
|
-
runtime: "node",
|
|
832
|
-
command: "npx",
|
|
833
|
-
args: ["-y", `${packageName}@${version}`],
|
|
834
|
-
envVars,
|
|
835
|
-
resolved
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
async function resolveFromGitHub(githubUrl) {
|
|
839
|
-
const match = githubUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
840
|
-
if (!match) {
|
|
841
|
-
throw new Error(`Invalid GitHub URL: ${githubUrl}`);
|
|
842
|
-
}
|
|
843
|
-
const [, owner, repo] = match;
|
|
844
|
-
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/package.json`;
|
|
845
|
-
let pkgData = {};
|
|
846
|
-
try {
|
|
847
|
-
const res = await fetch(rawUrl, { signal: AbortSignal.timeout(8e3) });
|
|
848
|
-
if (res.ok) {
|
|
849
|
-
pkgData = await res.json();
|
|
850
|
-
}
|
|
851
|
-
} catch {
|
|
852
|
-
}
|
|
853
|
-
const version = typeof pkgData.version === "string" ? pkgData.version : "main";
|
|
854
|
-
const name = typeof pkgData.name === "string" ? pkgData.name : `${owner}/${repo}`;
|
|
855
|
-
return {
|
|
856
|
-
name,
|
|
857
|
-
version,
|
|
858
|
-
description: typeof pkgData.description === "string" ? pkgData.description : "",
|
|
859
|
-
runtime: "node",
|
|
860
|
-
command: "npx",
|
|
861
|
-
args: ["-y", githubUrl],
|
|
862
|
-
envVars: [],
|
|
863
|
-
resolved: githubUrl
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// src/commands/init.ts
|
|
868
|
-
var init_default = (0, import_citty2.defineCommand)({
|
|
1690
|
+
var import_citty3 = require("citty");
|
|
1691
|
+
var p2 = __toESM(require("@clack/prompts"), 1);
|
|
1692
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1693
|
+
var init_default = (0, import_citty3.defineCommand)({
|
|
869
1694
|
meta: {
|
|
870
1695
|
name: "init",
|
|
871
1696
|
description: "Initialize mcpman.lock in the current project"
|
|
@@ -880,17 +1705,17 @@ var init_default = (0, import_citty2.defineCommand)({
|
|
|
880
1705
|
},
|
|
881
1706
|
async run({ args }) {
|
|
882
1707
|
const nonInteractive = args.yes || !process.stdout.isTTY;
|
|
883
|
-
|
|
884
|
-
const targetPath =
|
|
1708
|
+
p2.intro("mcpman init");
|
|
1709
|
+
const targetPath = import_node_path5.default.join(process.cwd(), LOCKFILE_NAME);
|
|
885
1710
|
const existing = findLockfile();
|
|
886
1711
|
if (existing) {
|
|
887
1712
|
if (nonInteractive) {
|
|
888
|
-
|
|
1713
|
+
p2.log.warn(`Lockfile already exists: ${existing} \u2014 overwriting (non-interactive).`);
|
|
889
1714
|
} else {
|
|
890
|
-
|
|
891
|
-
const overwrite = await
|
|
892
|
-
if (
|
|
893
|
-
|
|
1715
|
+
p2.log.warn(`Lockfile already exists: ${existing}`);
|
|
1716
|
+
const overwrite = await p2.confirm({ message: "Overwrite?" });
|
|
1717
|
+
if (p2.isCancel(overwrite) || !overwrite) {
|
|
1718
|
+
p2.outro("Cancelled.");
|
|
894
1719
|
return;
|
|
895
1720
|
}
|
|
896
1721
|
}
|
|
@@ -900,7 +1725,7 @@ var init_default = (0, import_citty2.defineCommand)({
|
|
|
900
1725
|
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
901
1726
|
clients = await mod.getInstalledClients();
|
|
902
1727
|
} catch {
|
|
903
|
-
|
|
1728
|
+
p2.log.warn("Could not detect AI clients \u2014 creating empty lockfile.");
|
|
904
1729
|
}
|
|
905
1730
|
const clientServers = [];
|
|
906
1731
|
for (const client of clients) {
|
|
@@ -914,26 +1739,26 @@ var init_default = (0, import_citty2.defineCommand)({
|
|
|
914
1739
|
}
|
|
915
1740
|
createEmptyLockfile(targetPath);
|
|
916
1741
|
if (clientServers.length === 0) {
|
|
917
|
-
|
|
918
|
-
|
|
1742
|
+
p2.log.info("No existing servers found in any client config.");
|
|
1743
|
+
p2.outro(`Created ${LOCKFILE_NAME} \u2014 add it to version control!`);
|
|
919
1744
|
return;
|
|
920
1745
|
}
|
|
921
1746
|
let selected;
|
|
922
1747
|
if (nonInteractive) {
|
|
923
1748
|
selected = clientServers.map((cs) => cs.client.type);
|
|
924
|
-
|
|
1749
|
+
p2.log.info(`Non-interactive mode: importing all ${clientServers.length} client(s).`);
|
|
925
1750
|
} else {
|
|
926
1751
|
const options = clientServers.map((cs) => ({
|
|
927
1752
|
value: cs.client.type,
|
|
928
1753
|
label: `${cs.client.displayName} (${Object.keys(cs.servers).length} servers)`
|
|
929
1754
|
}));
|
|
930
|
-
const toImport = await
|
|
1755
|
+
const toImport = await p2.multiselect({
|
|
931
1756
|
message: "Import existing servers into lockfile?",
|
|
932
1757
|
options,
|
|
933
1758
|
required: false
|
|
934
1759
|
});
|
|
935
|
-
if (
|
|
936
|
-
|
|
1760
|
+
if (p2.isCancel(toImport)) {
|
|
1761
|
+
p2.outro(`Created empty ${LOCKFILE_NAME}`);
|
|
937
1762
|
return;
|
|
938
1763
|
}
|
|
939
1764
|
selected = toImport;
|
|
@@ -959,7 +1784,7 @@ var init_default = (0, import_citty2.defineCommand)({
|
|
|
959
1784
|
importCount++;
|
|
960
1785
|
}
|
|
961
1786
|
}
|
|
962
|
-
|
|
1787
|
+
p2.outro(
|
|
963
1788
|
`Created ${LOCKFILE_NAME} with ${importCount} server(s) \u2014 commit to version control!`
|
|
964
1789
|
);
|
|
965
1790
|
}
|
|
@@ -967,49 +1792,48 @@ var init_default = (0, import_citty2.defineCommand)({
|
|
|
967
1792
|
|
|
968
1793
|
// src/commands/install.ts
|
|
969
1794
|
init_cjs_shims();
|
|
970
|
-
var
|
|
1795
|
+
var import_citty4 = require("citty");
|
|
971
1796
|
|
|
972
1797
|
// src/core/installer.ts
|
|
973
1798
|
init_cjs_shims();
|
|
974
|
-
var
|
|
1799
|
+
var p5 = __toESM(require("@clack/prompts"), 1);
|
|
975
1800
|
|
|
976
|
-
// src/core/
|
|
1801
|
+
// src/core/installer-vault-helpers.ts
|
|
977
1802
|
init_cjs_shims();
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
return resolveFromNpm(source.input);
|
|
1803
|
+
var p4 = __toESM(require("@clack/prompts"), 1);
|
|
1804
|
+
init_vault_service();
|
|
1805
|
+
async function tryLoadVaultSecrets(serverName) {
|
|
1806
|
+
try {
|
|
1807
|
+
const entries = listSecrets(serverName);
|
|
1808
|
+
if (entries.length === 0 || entries[0].keys.length === 0) {
|
|
1809
|
+
return {};
|
|
1810
|
+
}
|
|
1811
|
+
const password2 = await getMasterPassword();
|
|
1812
|
+
return getSecretsForServer(serverName, password2);
|
|
1813
|
+
} catch {
|
|
1814
|
+
return {};
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
async function offerVaultSave(serverName, newVars, yes) {
|
|
1818
|
+
if (Object.keys(newVars).length === 0) return;
|
|
1819
|
+
if (yes) return;
|
|
1820
|
+
try {
|
|
1821
|
+
const save = await p4.confirm({
|
|
1822
|
+
message: `Save ${Object.keys(newVars).length} env var(s) to encrypted vault for future installs?`
|
|
1823
|
+
});
|
|
1824
|
+
if (p4.isCancel(save) || !save) return;
|
|
1825
|
+
const password2 = await getMasterPassword();
|
|
1826
|
+
for (const [key, value] of Object.entries(newVars)) {
|
|
1827
|
+
setSecret(serverName, key, value, password2);
|
|
1828
|
+
}
|
|
1829
|
+
p4.log.success(`Credentials saved to vault for '${serverName}'`);
|
|
1830
|
+
} catch (err) {
|
|
1831
|
+
p4.log.warn(`Could not save to vault: ${err instanceof Error ? err.message : String(err)}`);
|
|
1008
1832
|
}
|
|
1009
1833
|
}
|
|
1010
1834
|
|
|
1011
1835
|
// src/core/installer.ts
|
|
1012
|
-
async function
|
|
1836
|
+
async function loadClients2() {
|
|
1013
1837
|
try {
|
|
1014
1838
|
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
1015
1839
|
return mod.getInstalledClients();
|
|
@@ -1018,83 +1842,86 @@ async function loadClients() {
|
|
|
1018
1842
|
}
|
|
1019
1843
|
}
|
|
1020
1844
|
async function installServer(input, options = {}) {
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1845
|
+
p5.intro("mcpman install");
|
|
1846
|
+
const spinner5 = p5.spinner();
|
|
1847
|
+
spinner5.start("Resolving server...");
|
|
1024
1848
|
let metadata;
|
|
1025
1849
|
try {
|
|
1026
1850
|
metadata = await resolveServer(input);
|
|
1027
1851
|
} catch (err) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1852
|
+
spinner5.stop("Resolution failed");
|
|
1853
|
+
p5.log.error(err instanceof Error ? err.message : String(err));
|
|
1030
1854
|
process.exit(1);
|
|
1031
1855
|
}
|
|
1032
|
-
|
|
1033
|
-
const clients = await
|
|
1856
|
+
spinner5.stop(`Found: ${metadata.name}@${metadata.version}`);
|
|
1857
|
+
const clients = await loadClients2();
|
|
1034
1858
|
if (clients.length === 0) {
|
|
1035
|
-
|
|
1036
|
-
|
|
1859
|
+
p5.log.warn("No supported AI clients detected on this machine.");
|
|
1860
|
+
p5.log.info("Supported: Claude Desktop, Cursor, VS Code, Windsurf");
|
|
1037
1861
|
process.exit(1);
|
|
1038
1862
|
}
|
|
1039
1863
|
let selectedClients;
|
|
1040
1864
|
if (options.client) {
|
|
1041
1865
|
const found = clients.find((c) => c.type === options.client || c.displayName.toLowerCase() === options.client?.toLowerCase());
|
|
1042
1866
|
if (!found) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1867
|
+
p5.log.error(`Client '${options.client}' not found or not installed.`);
|
|
1868
|
+
p5.log.info(`Available: ${clients.map((c) => c.type).join(", ")}`);
|
|
1045
1869
|
process.exit(1);
|
|
1046
1870
|
}
|
|
1047
1871
|
selectedClients = [found];
|
|
1048
1872
|
} else if (options.yes || clients.length === 1) {
|
|
1049
1873
|
selectedClients = clients;
|
|
1050
1874
|
} else {
|
|
1051
|
-
const chosen = await
|
|
1875
|
+
const chosen = await p5.multiselect({
|
|
1052
1876
|
message: "Install to which client(s)?",
|
|
1053
1877
|
options: clients.map((c) => ({ value: c.type, label: c.displayName })),
|
|
1054
1878
|
required: true
|
|
1055
1879
|
});
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
1880
|
+
if (p5.isCancel(chosen)) {
|
|
1881
|
+
p5.outro("Cancelled.");
|
|
1058
1882
|
process.exit(0);
|
|
1059
1883
|
}
|
|
1060
1884
|
selectedClients = clients.filter((c) => chosen.includes(c.type));
|
|
1061
1885
|
}
|
|
1062
1886
|
const providedEnv = parseEnvFlags(options.env);
|
|
1063
|
-
const
|
|
1887
|
+
const vaultEnv = await tryLoadVaultSecrets(metadata.name);
|
|
1888
|
+
const collectedEnv = { ...vaultEnv, ...providedEnv };
|
|
1889
|
+
const newlyEnteredVars = {};
|
|
1064
1890
|
const requiredVars = metadata.envVars.filter((e) => e.required && !(e.name in collectedEnv));
|
|
1065
1891
|
for (const envVar of requiredVars) {
|
|
1066
1892
|
if (options.yes && envVar.default) {
|
|
1067
1893
|
collectedEnv[envVar.name] = envVar.default;
|
|
1068
1894
|
continue;
|
|
1069
1895
|
}
|
|
1070
|
-
const val = await
|
|
1896
|
+
const val = await p5.text({
|
|
1071
1897
|
message: `${envVar.name}${envVar.description ? ` \u2014 ${envVar.description}` : ""}`,
|
|
1072
1898
|
placeholder: envVar.default ?? "",
|
|
1073
1899
|
validate: (v) => envVar.required && !v ? "Required" : void 0
|
|
1074
1900
|
});
|
|
1075
|
-
if (
|
|
1076
|
-
|
|
1901
|
+
if (p5.isCancel(val)) {
|
|
1902
|
+
p5.outro("Cancelled.");
|
|
1077
1903
|
process.exit(0);
|
|
1078
1904
|
}
|
|
1079
1905
|
collectedEnv[envVar.name] = val;
|
|
1906
|
+
newlyEnteredVars[envVar.name] = val;
|
|
1080
1907
|
}
|
|
1081
1908
|
const entry = {
|
|
1082
1909
|
command: metadata.command,
|
|
1083
1910
|
args: metadata.args,
|
|
1084
1911
|
...Object.keys(collectedEnv).length > 0 ? { env: collectedEnv } : {}
|
|
1085
1912
|
};
|
|
1086
|
-
|
|
1913
|
+
spinner5.start("Writing config...");
|
|
1087
1914
|
const clientTypes = [];
|
|
1088
1915
|
for (const client of selectedClients) {
|
|
1089
1916
|
try {
|
|
1090
1917
|
await client.addServer(metadata.name, entry);
|
|
1091
1918
|
clientTypes.push(client.type);
|
|
1092
1919
|
} catch (err) {
|
|
1093
|
-
|
|
1094
|
-
|
|
1920
|
+
spinner5.stop("Partial failure");
|
|
1921
|
+
p5.log.warn(`Failed to write to ${client.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1095
1922
|
}
|
|
1096
1923
|
}
|
|
1097
|
-
|
|
1924
|
+
spinner5.stop("Config written");
|
|
1098
1925
|
const source = detectSource(input);
|
|
1099
1926
|
const integrity = computeIntegrity(metadata.resolved);
|
|
1100
1927
|
addEntry(metadata.name, {
|
|
@@ -1110,13 +1937,14 @@ async function installServer(input, options = {}) {
|
|
|
1110
1937
|
clients: clientTypes
|
|
1111
1938
|
});
|
|
1112
1939
|
const lockPath = findLockfile() ?? "mcpman.lock (global)";
|
|
1113
|
-
|
|
1114
|
-
|
|
1940
|
+
p5.log.success(`Lockfile updated: ${lockPath}`);
|
|
1941
|
+
await offerVaultSave(metadata.name, newlyEnteredVars, options.yes ?? false);
|
|
1942
|
+
p5.outro(`${metadata.name}@${metadata.version} installed to ${clientTypes.join(", ")}`);
|
|
1115
1943
|
}
|
|
1116
1944
|
|
|
1117
1945
|
// src/utils/logger.ts
|
|
1118
1946
|
init_cjs_shims();
|
|
1119
|
-
var
|
|
1947
|
+
var import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
1120
1948
|
var noColor = process.env.NO_COLOR !== void 0 || process.argv.includes("--no-color");
|
|
1121
1949
|
var isVerbose = process.argv.includes("--verbose");
|
|
1122
1950
|
var isJson = process.argv.includes("--json");
|
|
@@ -1125,19 +1953,19 @@ function colorize(fn, text2) {
|
|
|
1125
1953
|
}
|
|
1126
1954
|
function info(message) {
|
|
1127
1955
|
if (isJson) return;
|
|
1128
|
-
console.log(`${colorize(
|
|
1956
|
+
console.log(`${colorize(import_picocolors3.default.cyan, "i")} ${message}`);
|
|
1129
1957
|
}
|
|
1130
1958
|
function error(message) {
|
|
1131
1959
|
if (isJson) return;
|
|
1132
|
-
console.error(`${colorize(
|
|
1960
|
+
console.error(`${colorize(import_picocolors3.default.red, "\u2717")} ${message}`);
|
|
1133
1961
|
}
|
|
1134
1962
|
function json(data) {
|
|
1135
1963
|
console.log(JSON.stringify(data, null, 2));
|
|
1136
1964
|
}
|
|
1137
1965
|
|
|
1138
1966
|
// src/commands/install.ts
|
|
1139
|
-
var
|
|
1140
|
-
var install_default = (0,
|
|
1967
|
+
var p6 = __toESM(require("@clack/prompts"), 1);
|
|
1968
|
+
var install_default = (0, import_citty4.defineCommand)({
|
|
1141
1969
|
meta: {
|
|
1142
1970
|
name: "install",
|
|
1143
1971
|
description: "Install an MCP server into one or more AI clients"
|
|
@@ -1186,8 +2014,8 @@ async function restoreFromLockfile() {
|
|
|
1186
2014
|
info("Lockfile is empty \u2014 nothing to restore.");
|
|
1187
2015
|
return;
|
|
1188
2016
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
2017
|
+
p6.intro(`mcpman install (restore from ${lockPath})`);
|
|
2018
|
+
p6.log.info(`Restoring ${entries.length} server(s)...`);
|
|
1191
2019
|
for (const [name, entry] of entries) {
|
|
1192
2020
|
const input = entry.source === "smithery" ? `smithery:${name}` : entry.source === "github" ? entry.resolved : name;
|
|
1193
2021
|
await installServer(input, {
|
|
@@ -1195,19 +2023,19 @@ async function restoreFromLockfile() {
|
|
|
1195
2023
|
yes: true
|
|
1196
2024
|
});
|
|
1197
2025
|
}
|
|
1198
|
-
|
|
2026
|
+
p6.outro("Restore complete.");
|
|
1199
2027
|
}
|
|
1200
2028
|
|
|
1201
2029
|
// src/commands/list.ts
|
|
1202
2030
|
init_cjs_shims();
|
|
1203
|
-
var
|
|
1204
|
-
var
|
|
2031
|
+
var import_citty5 = require("citty");
|
|
2032
|
+
var import_picocolors4 = __toESM(require("picocolors"), 1);
|
|
1205
2033
|
var STATUS_ICON = {
|
|
1206
|
-
healthy:
|
|
1207
|
-
unhealthy:
|
|
1208
|
-
unknown:
|
|
2034
|
+
healthy: import_picocolors4.default.green("\u25CF"),
|
|
2035
|
+
unhealthy: import_picocolors4.default.red("\u25CF"),
|
|
2036
|
+
unknown: import_picocolors4.default.dim("\u25CB")
|
|
1209
2037
|
};
|
|
1210
|
-
var list_default = (0,
|
|
2038
|
+
var list_default = (0, import_citty5.defineCommand)({
|
|
1211
2039
|
meta: {
|
|
1212
2040
|
name: "list",
|
|
1213
2041
|
description: "List installed MCP servers"
|
|
@@ -1227,7 +2055,7 @@ var list_default = (0, import_citty4.defineCommand)({
|
|
|
1227
2055
|
const servers = await getInstalledServers(args.client);
|
|
1228
2056
|
if (servers.length === 0) {
|
|
1229
2057
|
const filter = args.client ? ` for client "${args.client}"` : "";
|
|
1230
|
-
console.log(
|
|
2058
|
+
console.log(import_picocolors4.default.dim(`No MCP servers installed${filter}. Run ${import_picocolors4.default.cyan("mcpman install <server>")} to get started.`));
|
|
1231
2059
|
return;
|
|
1232
2060
|
}
|
|
1233
2061
|
const withStatus = await Promise.all(
|
|
@@ -1251,8 +2079,8 @@ var list_default = (0, import_citty4.defineCommand)({
|
|
|
1251
2079
|
const nameWidth = Math.max(4, ...withStatus.map((s) => s.name.length));
|
|
1252
2080
|
const clientsWidth = Math.max(7, ...withStatus.map((s) => formatClients(s.clients).length));
|
|
1253
2081
|
const header = ` ${pad("NAME", nameWidth)} ${pad("CLIENT(S)", clientsWidth)} ${pad("COMMAND", 20)} STATUS`;
|
|
1254
|
-
console.log(
|
|
1255
|
-
console.log(
|
|
2082
|
+
console.log(import_picocolors4.default.dim(header));
|
|
2083
|
+
console.log(import_picocolors4.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientsWidth)} ${"-".repeat(20)} ------`));
|
|
1256
2084
|
for (const s of withStatus) {
|
|
1257
2085
|
const icon = STATUS_ICON[s.status];
|
|
1258
2086
|
const clientsStr = formatClients(s.clients);
|
|
@@ -1260,7 +2088,7 @@ var list_default = (0, import_citty4.defineCommand)({
|
|
|
1260
2088
|
console.log(` ${pad(s.name, nameWidth)} ${pad(clientsStr, clientsWidth)} ${pad(cmdStr, 20)} ${icon} ${s.status}`);
|
|
1261
2089
|
}
|
|
1262
2090
|
const clientSet = new Set(withStatus.flatMap((s) => s.clients));
|
|
1263
|
-
console.log(
|
|
2091
|
+
console.log(import_picocolors4.default.dim(`
|
|
1264
2092
|
${withStatus.length} server${withStatus.length !== 1 ? "s" : ""} \xB7 ${clientSet.size} client${clientSet.size !== 1 ? "s" : ""}`));
|
|
1265
2093
|
}
|
|
1266
2094
|
});
|
|
@@ -1282,9 +2110,9 @@ function formatClients(clients) {
|
|
|
1282
2110
|
|
|
1283
2111
|
// src/commands/remove.ts
|
|
1284
2112
|
init_cjs_shims();
|
|
1285
|
-
var
|
|
1286
|
-
var
|
|
1287
|
-
var
|
|
2113
|
+
var import_citty6 = require("citty");
|
|
2114
|
+
var p7 = __toESM(require("@clack/prompts"), 1);
|
|
2115
|
+
var import_picocolors5 = __toESM(require("picocolors"), 1);
|
|
1288
2116
|
init_client_detector();
|
|
1289
2117
|
var CLIENT_DISPLAY2 = {
|
|
1290
2118
|
"claude-desktop": "Claude",
|
|
@@ -1295,7 +2123,7 @@ var CLIENT_DISPLAY2 = {
|
|
|
1295
2123
|
function clientDisplayName(type) {
|
|
1296
2124
|
return CLIENT_DISPLAY2[type] ?? type;
|
|
1297
2125
|
}
|
|
1298
|
-
var remove_default = (0,
|
|
2126
|
+
var remove_default = (0, import_citty6.defineCommand)({
|
|
1299
2127
|
meta: {
|
|
1300
2128
|
name: "remove",
|
|
1301
2129
|
description: "Remove an MCP server from one or more AI clients"
|
|
@@ -1322,17 +2150,17 @@ var remove_default = (0, import_citty5.defineCommand)({
|
|
|
1322
2150
|
}
|
|
1323
2151
|
},
|
|
1324
2152
|
async run({ args }) {
|
|
1325
|
-
|
|
2153
|
+
p7.intro(import_picocolors5.default.bold("mcpman remove"));
|
|
1326
2154
|
const serverName = args.server;
|
|
1327
2155
|
const servers = await getInstalledServers();
|
|
1328
2156
|
const match = servers.find((s) => s.name === serverName);
|
|
1329
2157
|
if (!match) {
|
|
1330
|
-
|
|
2158
|
+
p7.log.warn(`Server "${serverName}" is not installed.`);
|
|
1331
2159
|
const similar = servers.filter((s) => s.name.includes(serverName) || serverName.includes(s.name));
|
|
1332
2160
|
if (similar.length > 0) {
|
|
1333
|
-
|
|
2161
|
+
p7.log.info(`Did you mean: ${similar.map((s) => import_picocolors5.default.cyan(s.name)).join(", ")}?`);
|
|
1334
2162
|
}
|
|
1335
|
-
|
|
2163
|
+
p7.outro("Nothing to remove.");
|
|
1336
2164
|
return;
|
|
1337
2165
|
}
|
|
1338
2166
|
let targetClients;
|
|
@@ -1340,15 +2168,15 @@ var remove_default = (0, import_citty5.defineCommand)({
|
|
|
1340
2168
|
targetClients = match.clients;
|
|
1341
2169
|
} else if (args.client) {
|
|
1342
2170
|
if (!match.clients.includes(args.client)) {
|
|
1343
|
-
|
|
1344
|
-
|
|
2171
|
+
p7.log.warn(`Server "${serverName}" is not installed in client "${args.client}".`);
|
|
2172
|
+
p7.outro("Nothing to remove.");
|
|
1345
2173
|
return;
|
|
1346
2174
|
}
|
|
1347
2175
|
targetClients = [args.client];
|
|
1348
2176
|
} else if (match.clients.length === 1) {
|
|
1349
2177
|
targetClients = match.clients;
|
|
1350
2178
|
} else {
|
|
1351
|
-
const selected = await
|
|
2179
|
+
const selected = await p7.multiselect({
|
|
1352
2180
|
message: `Remove "${serverName}" from which clients?`,
|
|
1353
2181
|
options: match.clients.map((c) => ({
|
|
1354
2182
|
value: c,
|
|
@@ -1356,19 +2184,19 @@ var remove_default = (0, import_citty5.defineCommand)({
|
|
|
1356
2184
|
})),
|
|
1357
2185
|
required: true
|
|
1358
2186
|
});
|
|
1359
|
-
if (
|
|
1360
|
-
|
|
2187
|
+
if (p7.isCancel(selected)) {
|
|
2188
|
+
p7.outro("Cancelled.");
|
|
1361
2189
|
process.exit(0);
|
|
1362
2190
|
}
|
|
1363
2191
|
targetClients = selected;
|
|
1364
2192
|
}
|
|
1365
2193
|
if (!args.yes) {
|
|
1366
2194
|
const clientNames = targetClients.map(clientDisplayName).join(", ");
|
|
1367
|
-
const confirmed = await
|
|
1368
|
-
message: `Remove ${
|
|
2195
|
+
const confirmed = await p7.confirm({
|
|
2196
|
+
message: `Remove ${import_picocolors5.default.cyan(serverName)} from ${import_picocolors5.default.yellow(clientNames)}?`
|
|
1369
2197
|
});
|
|
1370
|
-
if (
|
|
1371
|
-
|
|
2198
|
+
if (p7.isCancel(confirmed) || !confirmed) {
|
|
2199
|
+
p7.outro("Cancelled.");
|
|
1372
2200
|
return;
|
|
1373
2201
|
}
|
|
1374
2202
|
}
|
|
@@ -1382,25 +2210,606 @@ var remove_default = (0, import_citty5.defineCommand)({
|
|
|
1382
2210
|
}
|
|
1383
2211
|
try {
|
|
1384
2212
|
await handler.removeServer(serverName);
|
|
1385
|
-
|
|
2213
|
+
p7.log.success(`Removed from ${clientDisplayName(clientType)}`);
|
|
1386
2214
|
} catch (err) {
|
|
1387
2215
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1388
2216
|
errors.push(`${clientDisplayName(clientType)}: ${msg}`);
|
|
1389
2217
|
}
|
|
1390
2218
|
}
|
|
1391
2219
|
if (errors.length > 0) {
|
|
1392
|
-
for (const e of errors)
|
|
1393
|
-
|
|
2220
|
+
for (const e of errors) p7.log.error(e);
|
|
2221
|
+
p7.outro(import_picocolors5.default.red("Completed with errors."));
|
|
2222
|
+
process.exit(1);
|
|
2223
|
+
}
|
|
2224
|
+
p7.outro(import_picocolors5.default.green(`Removed "${serverName}" successfully.`));
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2227
|
+
|
|
2228
|
+
// src/commands/secrets.ts
|
|
2229
|
+
init_cjs_shims();
|
|
2230
|
+
var import_citty7 = require("citty");
|
|
2231
|
+
var import_picocolors6 = __toESM(require("picocolors"), 1);
|
|
2232
|
+
var p8 = __toESM(require("@clack/prompts"), 1);
|
|
2233
|
+
init_vault_service();
|
|
2234
|
+
function maskValue(value) {
|
|
2235
|
+
if (value.length <= 8) return "***";
|
|
2236
|
+
return `${value.slice(0, 4)}***${value.slice(-3)}`;
|
|
2237
|
+
}
|
|
2238
|
+
function parseKeyValue(input) {
|
|
2239
|
+
const idx = input.indexOf("=");
|
|
2240
|
+
if (idx <= 0) return null;
|
|
2241
|
+
return { key: input.slice(0, idx), value: input.slice(idx + 1) };
|
|
2242
|
+
}
|
|
2243
|
+
var setCommand = (0, import_citty7.defineCommand)({
|
|
2244
|
+
meta: { name: "set", description: "Store an encrypted secret for a server" },
|
|
2245
|
+
args: {
|
|
2246
|
+
server: {
|
|
2247
|
+
type: "positional",
|
|
2248
|
+
description: "Server name (e.g. @modelcontextprotocol/server-github)",
|
|
2249
|
+
required: true
|
|
2250
|
+
},
|
|
2251
|
+
keyvalue: {
|
|
2252
|
+
type: "positional",
|
|
2253
|
+
description: "KEY=VALUE pair to store",
|
|
2254
|
+
required: true
|
|
2255
|
+
}
|
|
2256
|
+
},
|
|
2257
|
+
async run({ args }) {
|
|
2258
|
+
const parsed = parseKeyValue(args.keyvalue);
|
|
2259
|
+
if (!parsed) {
|
|
2260
|
+
console.error(import_picocolors6.default.red("\u2717") + " Invalid format. Expected KEY=VALUE");
|
|
2261
|
+
process.exit(1);
|
|
2262
|
+
}
|
|
2263
|
+
p8.intro(import_picocolors6.default.cyan("mcpman secrets set"));
|
|
2264
|
+
const isNew = listSecrets(args.server).length === 0 || !listSecrets(args.server)[0]?.keys.includes(parsed.key);
|
|
2265
|
+
const vaultPath = (await Promise.resolve().then(() => (init_vault_service(), vault_service_exports))).getVaultPath();
|
|
2266
|
+
const vaultExists = (await import("fs")).existsSync(vaultPath);
|
|
2267
|
+
const password2 = await getMasterPassword(!vaultExists && isNew);
|
|
2268
|
+
const spin = p8.spinner();
|
|
2269
|
+
spin.start("Encrypting secret...");
|
|
2270
|
+
try {
|
|
2271
|
+
setSecret(args.server, parsed.key, parsed.value, password2);
|
|
2272
|
+
spin.stop(
|
|
2273
|
+
`${import_picocolors6.default.green("\u2713")} Stored ${import_picocolors6.default.bold(parsed.key)} for ${import_picocolors6.default.cyan(args.server)}`
|
|
2274
|
+
);
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
spin.stop(import_picocolors6.default.red("\u2717") + " Failed to store secret");
|
|
2277
|
+
console.error(import_picocolors6.default.dim(String(err)));
|
|
2278
|
+
process.exit(1);
|
|
2279
|
+
}
|
|
2280
|
+
p8.outro(import_picocolors6.default.dim("Secret encrypted and saved to vault."));
|
|
2281
|
+
}
|
|
2282
|
+
});
|
|
2283
|
+
var listCommand = (0, import_citty7.defineCommand)({
|
|
2284
|
+
meta: { name: "list", description: "List secret keys stored in the vault" },
|
|
2285
|
+
args: {
|
|
2286
|
+
server: {
|
|
2287
|
+
type: "positional",
|
|
2288
|
+
description: "Filter by server name (optional)",
|
|
2289
|
+
required: false
|
|
2290
|
+
}
|
|
2291
|
+
},
|
|
2292
|
+
async run({ args }) {
|
|
2293
|
+
const results = listSecrets(args.server || void 0);
|
|
2294
|
+
if (results.length === 0) {
|
|
2295
|
+
const filter = args.server ? ` for ${import_picocolors6.default.cyan(args.server)}` : "";
|
|
2296
|
+
console.log(import_picocolors6.default.dim(`No secrets stored${filter}.`));
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
console.log("");
|
|
2300
|
+
for (const { server, keys } of results) {
|
|
2301
|
+
console.log(import_picocolors6.default.bold(import_picocolors6.default.cyan(server)));
|
|
2302
|
+
for (const key of keys) {
|
|
2303
|
+
console.log(` ${import_picocolors6.default.green("\u25CF")} ${import_picocolors6.default.bold(key)} ${import_picocolors6.default.dim(maskValue("\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"))}`);
|
|
2304
|
+
}
|
|
2305
|
+
console.log("");
|
|
2306
|
+
}
|
|
2307
|
+
const total = results.reduce((n, r) => n + r.keys.length, 0);
|
|
2308
|
+
console.log(import_picocolors6.default.dim(` ${total} secret${total !== 1 ? "s" : ""} in ${results.length} server${results.length !== 1 ? "s" : ""}`));
|
|
2309
|
+
}
|
|
2310
|
+
});
|
|
2311
|
+
var removeCommand = (0, import_citty7.defineCommand)({
|
|
2312
|
+
meta: { name: "remove", description: "Delete a secret from the vault" },
|
|
2313
|
+
args: {
|
|
2314
|
+
server: {
|
|
2315
|
+
type: "positional",
|
|
2316
|
+
description: "Server name",
|
|
2317
|
+
required: true
|
|
2318
|
+
},
|
|
2319
|
+
key: {
|
|
2320
|
+
type: "positional",
|
|
2321
|
+
description: "Secret key to remove",
|
|
2322
|
+
required: true
|
|
2323
|
+
}
|
|
2324
|
+
},
|
|
2325
|
+
async run({ args }) {
|
|
2326
|
+
const confirmed = await p8.confirm({
|
|
2327
|
+
message: `Remove ${import_picocolors6.default.bold(args.key)} from ${import_picocolors6.default.cyan(args.server)}?`,
|
|
2328
|
+
initialValue: false
|
|
2329
|
+
});
|
|
2330
|
+
if (p8.isCancel(confirmed) || !confirmed) {
|
|
2331
|
+
p8.cancel("Cancelled.");
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
try {
|
|
2335
|
+
removeSecret(args.server, args.key);
|
|
2336
|
+
console.log(`${import_picocolors6.default.green("\u2713")} Removed ${import_picocolors6.default.bold(args.key)} from ${import_picocolors6.default.cyan(args.server)}`);
|
|
2337
|
+
} catch (err) {
|
|
2338
|
+
console.error(import_picocolors6.default.red("\u2717") + " Failed to remove secret");
|
|
2339
|
+
console.error(import_picocolors6.default.dim(String(err)));
|
|
2340
|
+
process.exit(1);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
var secrets_default = (0, import_citty7.defineCommand)({
|
|
2345
|
+
meta: {
|
|
2346
|
+
name: "secrets",
|
|
2347
|
+
description: "Manage encrypted secrets for MCP servers"
|
|
2348
|
+
},
|
|
2349
|
+
subCommands: {
|
|
2350
|
+
set: setCommand,
|
|
2351
|
+
list: listCommand,
|
|
2352
|
+
remove: removeCommand
|
|
2353
|
+
}
|
|
2354
|
+
});
|
|
2355
|
+
|
|
2356
|
+
// src/commands/sync.ts
|
|
2357
|
+
init_cjs_shims();
|
|
2358
|
+
var import_citty8 = require("citty");
|
|
2359
|
+
var p9 = __toESM(require("@clack/prompts"), 1);
|
|
2360
|
+
var import_picocolors7 = __toESM(require("picocolors"), 1);
|
|
2361
|
+
|
|
2362
|
+
// src/core/config-diff.ts
|
|
2363
|
+
init_cjs_shims();
|
|
2364
|
+
function reconstructServerEntry(lockEntry) {
|
|
2365
|
+
const entry = {
|
|
2366
|
+
command: lockEntry.command
|
|
2367
|
+
};
|
|
2368
|
+
if (lockEntry.args && lockEntry.args.length > 0) {
|
|
2369
|
+
entry.args = lockEntry.args;
|
|
2370
|
+
}
|
|
2371
|
+
if (lockEntry.envVars && lockEntry.envVars.length > 0) {
|
|
2372
|
+
entry.env = Object.fromEntries(lockEntry.envVars.map((k) => [k, ""]));
|
|
2373
|
+
}
|
|
2374
|
+
return entry;
|
|
2375
|
+
}
|
|
2376
|
+
function computeDiff(lockfile, clientConfigs, options = {}) {
|
|
2377
|
+
const actions = [];
|
|
2378
|
+
for (const [server, lockEntry] of Object.entries(lockfile.servers)) {
|
|
2379
|
+
for (const client of lockEntry.clients) {
|
|
2380
|
+
const config = clientConfigs.get(client);
|
|
2381
|
+
if (!config) continue;
|
|
2382
|
+
if (server in config.servers) {
|
|
2383
|
+
actions.push({ server, client, action: "ok" });
|
|
2384
|
+
} else {
|
|
2385
|
+
actions.push({
|
|
2386
|
+
server,
|
|
2387
|
+
client,
|
|
2388
|
+
action: "add",
|
|
2389
|
+
entry: reconstructServerEntry(lockEntry)
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
const extraAction = options.remove ? "remove" : "extra";
|
|
2395
|
+
for (const [client, config] of clientConfigs) {
|
|
2396
|
+
for (const server of Object.keys(config.servers)) {
|
|
2397
|
+
if (!(server in lockfile.servers)) {
|
|
2398
|
+
actions.push({ server, client, action: extraAction });
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
return actions;
|
|
2403
|
+
}
|
|
2404
|
+
function computeDiffFromClient(sourceClient, clientConfigs, options = {}) {
|
|
2405
|
+
const actions = [];
|
|
2406
|
+
const sourceConfig = clientConfigs.get(sourceClient);
|
|
2407
|
+
if (!sourceConfig) return [];
|
|
2408
|
+
const extraAction = options.remove ? "remove" : "extra";
|
|
2409
|
+
for (const [client, config] of clientConfigs) {
|
|
2410
|
+
if (client === sourceClient) continue;
|
|
2411
|
+
for (const [server, entry] of Object.entries(sourceConfig.servers)) {
|
|
2412
|
+
if (server in config.servers) {
|
|
2413
|
+
actions.push({ server, client, action: "ok" });
|
|
2414
|
+
} else {
|
|
2415
|
+
actions.push({ server, client, action: "add", entry });
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
for (const server of Object.keys(config.servers)) {
|
|
2419
|
+
if (!(server in sourceConfig.servers)) {
|
|
2420
|
+
actions.push({ server, client, action: extraAction });
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return actions;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// src/core/sync-engine.ts
|
|
2428
|
+
init_cjs_shims();
|
|
2429
|
+
init_client_detector();
|
|
2430
|
+
async function applySyncActions(actions, clients) {
|
|
2431
|
+
const result = { applied: 0, removed: 0, failed: 0, errors: [] };
|
|
2432
|
+
const addActions = actions.filter((a) => a.action === "add" && a.entry);
|
|
2433
|
+
for (const action of addActions) {
|
|
2434
|
+
const handler = clients.get(action.client);
|
|
2435
|
+
if (!handler || !action.entry) {
|
|
2436
|
+
result.failed++;
|
|
2437
|
+
result.errors.push({
|
|
2438
|
+
server: action.server,
|
|
2439
|
+
client: action.client,
|
|
2440
|
+
error: "No handler available for client"
|
|
2441
|
+
});
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
try {
|
|
2445
|
+
await handler.addServer(action.server, action.entry);
|
|
2446
|
+
result.applied++;
|
|
2447
|
+
} catch (err) {
|
|
2448
|
+
result.failed++;
|
|
2449
|
+
result.errors.push({
|
|
2450
|
+
server: action.server,
|
|
2451
|
+
client: action.client,
|
|
2452
|
+
error: String(err)
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
const removeActions = actions.filter((a) => a.action === "remove");
|
|
2457
|
+
for (const action of removeActions) {
|
|
2458
|
+
const handler = clients.get(action.client);
|
|
2459
|
+
if (!handler) {
|
|
2460
|
+
result.failed++;
|
|
2461
|
+
result.errors.push({
|
|
2462
|
+
server: action.server,
|
|
2463
|
+
client: action.client,
|
|
2464
|
+
error: "No handler available for client"
|
|
2465
|
+
});
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
try {
|
|
2469
|
+
await handler.removeServer(action.server);
|
|
2470
|
+
result.removed++;
|
|
2471
|
+
} catch (err) {
|
|
2472
|
+
result.failed++;
|
|
2473
|
+
result.errors.push({
|
|
2474
|
+
server: action.server,
|
|
2475
|
+
client: action.client,
|
|
2476
|
+
error: String(err)
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
return result;
|
|
2481
|
+
}
|
|
2482
|
+
async function getClientConfigs() {
|
|
2483
|
+
const configs = /* @__PURE__ */ new Map();
|
|
2484
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
2485
|
+
let installedClients;
|
|
2486
|
+
try {
|
|
2487
|
+
installedClients = await getInstalledClients();
|
|
2488
|
+
} catch {
|
|
2489
|
+
return { configs, handlers };
|
|
2490
|
+
}
|
|
2491
|
+
await Promise.all(
|
|
2492
|
+
installedClients.map(async (handler) => {
|
|
2493
|
+
try {
|
|
2494
|
+
const config = await handler.readConfig();
|
|
2495
|
+
configs.set(handler.type, config);
|
|
2496
|
+
handlers.set(handler.type, handler);
|
|
2497
|
+
} catch (err) {
|
|
2498
|
+
console.warn(`[mcpman] Warning: could not read config for ${handler.displayName}: ${String(err)}`);
|
|
2499
|
+
}
|
|
2500
|
+
})
|
|
2501
|
+
);
|
|
2502
|
+
return { configs, handlers };
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// src/commands/sync.ts
|
|
2506
|
+
var VALID_CLIENTS = ["claude-desktop", "cursor", "vscode", "windsurf"];
|
|
2507
|
+
var CLIENT_DISPLAY3 = {
|
|
2508
|
+
"claude-desktop": "Claude Desktop",
|
|
2509
|
+
cursor: "Cursor",
|
|
2510
|
+
vscode: "VS Code",
|
|
2511
|
+
windsurf: "Windsurf"
|
|
2512
|
+
};
|
|
2513
|
+
var sync_default = (0, import_citty8.defineCommand)({
|
|
2514
|
+
meta: {
|
|
2515
|
+
name: "sync",
|
|
2516
|
+
description: "Sync MCP server configs across all detected AI clients"
|
|
2517
|
+
},
|
|
2518
|
+
args: {
|
|
2519
|
+
"dry-run": {
|
|
2520
|
+
type: "boolean",
|
|
2521
|
+
description: "Preview changes without applying them",
|
|
2522
|
+
default: false
|
|
2523
|
+
},
|
|
2524
|
+
remove: {
|
|
2525
|
+
type: "boolean",
|
|
2526
|
+
description: "Remove extra servers not in lockfile",
|
|
2527
|
+
default: false
|
|
2528
|
+
},
|
|
2529
|
+
source: {
|
|
2530
|
+
type: "string",
|
|
2531
|
+
description: "Use a specific client as source of truth (claude-desktop, cursor, vscode, windsurf)"
|
|
2532
|
+
},
|
|
2533
|
+
yes: {
|
|
2534
|
+
type: "boolean",
|
|
2535
|
+
description: "Skip confirmation prompt",
|
|
2536
|
+
default: false
|
|
2537
|
+
}
|
|
2538
|
+
},
|
|
2539
|
+
async run({ args }) {
|
|
2540
|
+
p9.intro(`${import_picocolors7.default.cyan("mcpman sync")}`);
|
|
2541
|
+
const sourceClient = args.source;
|
|
2542
|
+
if (sourceClient && !VALID_CLIENTS.includes(sourceClient)) {
|
|
2543
|
+
p9.log.error(`Invalid --source "${sourceClient}". Must be one of: ${VALID_CLIENTS.join(", ")}`);
|
|
2544
|
+
process.exit(1);
|
|
2545
|
+
}
|
|
2546
|
+
const spinner5 = p9.spinner();
|
|
2547
|
+
spinner5.start("Detecting clients and reading configs...");
|
|
2548
|
+
const { configs, handlers } = await getClientConfigs();
|
|
2549
|
+
spinner5.stop(`Found ${configs.size} client(s)`);
|
|
2550
|
+
if (configs.size === 0) {
|
|
2551
|
+
p9.log.warn("No AI clients detected. Install Claude Desktop, Cursor, VS Code, or Windsurf first.");
|
|
2552
|
+
process.exit(0);
|
|
2553
|
+
}
|
|
2554
|
+
const diffOptions = { remove: args.remove };
|
|
2555
|
+
let actions;
|
|
2556
|
+
if (sourceClient) {
|
|
2557
|
+
if (!configs.has(sourceClient)) {
|
|
2558
|
+
p9.log.error(`Source client "${sourceClient}" is not detected or its config is unreadable.`);
|
|
2559
|
+
process.exit(1);
|
|
2560
|
+
}
|
|
2561
|
+
p9.log.info(`Using ${CLIENT_DISPLAY3[sourceClient]} as source of truth`);
|
|
2562
|
+
actions = computeDiffFromClient(sourceClient, configs, diffOptions);
|
|
2563
|
+
} else {
|
|
2564
|
+
const lockfile = readLockfile();
|
|
2565
|
+
actions = computeDiff(lockfile, configs, diffOptions);
|
|
2566
|
+
}
|
|
2567
|
+
printDiffTable(actions);
|
|
2568
|
+
const addCount = actions.filter((a) => a.action === "add").length;
|
|
2569
|
+
const extraCount = actions.filter((a) => a.action === "extra").length;
|
|
2570
|
+
const removeCount = actions.filter((a) => a.action === "remove").length;
|
|
2571
|
+
if (addCount === 0 && removeCount === 0 && extraCount === 0) {
|
|
2572
|
+
p9.outro(import_picocolors7.default.green("All clients are in sync."));
|
|
2573
|
+
process.exit(0);
|
|
2574
|
+
}
|
|
2575
|
+
const parts = [];
|
|
2576
|
+
if (addCount > 0) parts.push(import_picocolors7.default.green(`${addCount} to add`));
|
|
2577
|
+
if (removeCount > 0) parts.push(import_picocolors7.default.red(`${removeCount} to remove`));
|
|
2578
|
+
if (extraCount > 0) parts.push(import_picocolors7.default.yellow(`${extraCount} extra (informational)`));
|
|
2579
|
+
p9.log.info(parts.join(" \xB7 "));
|
|
2580
|
+
if (args["dry-run"]) {
|
|
2581
|
+
p9.outro(import_picocolors7.default.dim("Dry run \u2014 no changes applied."));
|
|
2582
|
+
process.exit(1);
|
|
2583
|
+
}
|
|
2584
|
+
if (addCount === 0 && removeCount === 0) {
|
|
2585
|
+
p9.outro(import_picocolors7.default.dim("No additions needed. Extra servers left untouched."));
|
|
2586
|
+
process.exit(1);
|
|
2587
|
+
}
|
|
2588
|
+
if (!args.yes) {
|
|
2589
|
+
const actionParts = [];
|
|
2590
|
+
if (addCount > 0) actionParts.push(`${addCount} addition(s)`);
|
|
2591
|
+
if (removeCount > 0) actionParts.push(`${removeCount} removal(s)`);
|
|
2592
|
+
const confirmed = await p9.confirm({
|
|
2593
|
+
message: `Apply ${actionParts.join(" and ")} to client configs?`,
|
|
2594
|
+
initialValue: true
|
|
2595
|
+
});
|
|
2596
|
+
if (p9.isCancel(confirmed) || !confirmed) {
|
|
2597
|
+
p9.outro(import_picocolors7.default.dim("Cancelled \u2014 no changes applied."));
|
|
2598
|
+
process.exit(0);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
spinner5.start("Applying sync changes...");
|
|
2602
|
+
const result = await applySyncActions(actions, handlers);
|
|
2603
|
+
spinner5.stop("Done");
|
|
2604
|
+
if (result.applied > 0) {
|
|
2605
|
+
p9.log.success(`Added ${result.applied} server(s) to client configs.`);
|
|
2606
|
+
}
|
|
2607
|
+
if (result.removed > 0) {
|
|
2608
|
+
p9.log.success(`Removed ${result.removed} server(s) from client configs.`);
|
|
2609
|
+
}
|
|
2610
|
+
if (result.failed > 0) {
|
|
2611
|
+
for (const e of result.errors) {
|
|
2612
|
+
p9.log.error(`Failed to sync "${e.server}" on ${e.client}: ${e.error}`);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
p9.outro(result.failed === 0 ? import_picocolors7.default.green("Sync complete.") : import_picocolors7.default.yellow("Sync complete with errors."));
|
|
2616
|
+
process.exit(result.failed > 0 ? 1 : 0);
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
function printDiffTable(actions) {
|
|
2620
|
+
if (actions.length === 0) {
|
|
2621
|
+
p9.log.info("No actions to display.");
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
const nameWidth = Math.max(6, ...actions.map((a) => a.server.length));
|
|
2625
|
+
const clientWidth = Math.max(6, ...actions.map((a) => CLIENT_DISPLAY3[a.client]?.length ?? a.client.length));
|
|
2626
|
+
const header = ` ${pad2("SERVER", nameWidth)} ${pad2("CLIENT", clientWidth)} STATUS`;
|
|
2627
|
+
console.log(import_picocolors7.default.dim(header));
|
|
2628
|
+
console.log(import_picocolors7.default.dim(` ${"-".repeat(nameWidth)} ${"-".repeat(clientWidth)} ------`));
|
|
2629
|
+
for (const action of actions) {
|
|
2630
|
+
const clientDisplay = CLIENT_DISPLAY3[action.client] ?? action.client;
|
|
2631
|
+
const [icon, statusText] = formatAction(action.action);
|
|
2632
|
+
console.log(` ${pad2(action.server, nameWidth)} ${pad2(clientDisplay, clientWidth)} ${icon} ${statusText}`);
|
|
2633
|
+
}
|
|
2634
|
+
console.log("");
|
|
2635
|
+
}
|
|
2636
|
+
function formatAction(action) {
|
|
2637
|
+
switch (action) {
|
|
2638
|
+
case "add":
|
|
2639
|
+
return [import_picocolors7.default.green("+"), import_picocolors7.default.green("missing \u2014 will add")];
|
|
2640
|
+
case "extra":
|
|
2641
|
+
return [import_picocolors7.default.yellow("?"), import_picocolors7.default.yellow("extra (not in lockfile)")];
|
|
2642
|
+
case "remove":
|
|
2643
|
+
return [import_picocolors7.default.red("\u2013"), import_picocolors7.default.red("extra \u2014 will remove")];
|
|
2644
|
+
case "ok":
|
|
2645
|
+
return [import_picocolors7.default.dim("\xB7"), import_picocolors7.default.dim("in sync")];
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
function pad2(s, width) {
|
|
2649
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
// src/commands/update.ts
|
|
2653
|
+
init_cjs_shims();
|
|
2654
|
+
var import_citty9 = require("citty");
|
|
2655
|
+
var p10 = __toESM(require("@clack/prompts"), 1);
|
|
2656
|
+
var import_picocolors9 = __toESM(require("picocolors"), 1);
|
|
2657
|
+
|
|
2658
|
+
// src/core/update-notifier.ts
|
|
2659
|
+
init_cjs_shims();
|
|
2660
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
2661
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2662
|
+
var import_node_os5 = __toESM(require("os"), 1);
|
|
2663
|
+
var import_picocolors8 = __toESM(require("picocolors"), 1);
|
|
2664
|
+
var CACHE_FILE = import_node_path7.default.join(import_node_os5.default.homedir(), ".mcpman", ".update-check");
|
|
2665
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2666
|
+
function writeUpdateCache(data) {
|
|
2667
|
+
try {
|
|
2668
|
+
const dir = import_node_path7.default.dirname(CACHE_FILE);
|
|
2669
|
+
if (!import_node_fs5.default.existsSync(dir)) import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
2670
|
+
const tmp = `${CACHE_FILE}.tmp`;
|
|
2671
|
+
import_node_fs5.default.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
2672
|
+
import_node_fs5.default.renameSync(tmp, CACHE_FILE);
|
|
2673
|
+
} catch {
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// src/commands/update.ts
|
|
2678
|
+
async function loadClients3() {
|
|
2679
|
+
try {
|
|
2680
|
+
const mod = await Promise.resolve().then(() => (init_client_detector(), client_detector_exports));
|
|
2681
|
+
return mod.getInstalledClients();
|
|
2682
|
+
} catch {
|
|
2683
|
+
return [];
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
function printTable(updates) {
|
|
2687
|
+
const NAME_W = 28;
|
|
2688
|
+
const VER_W = 10;
|
|
2689
|
+
const header = [
|
|
2690
|
+
"NAME".padEnd(NAME_W),
|
|
2691
|
+
"CURRENT".padEnd(VER_W),
|
|
2692
|
+
"LATEST".padEnd(VER_W),
|
|
2693
|
+
"STATUS"
|
|
2694
|
+
].join(" ");
|
|
2695
|
+
console.log(import_picocolors9.default.bold(`
|
|
2696
|
+
${header}`));
|
|
2697
|
+
console.log(import_picocolors9.default.dim(` ${"\u2500".repeat(NAME_W + VER_W * 2 + 20)}`));
|
|
2698
|
+
for (const u of updates) {
|
|
2699
|
+
const nameCol = u.server.slice(0, NAME_W).padEnd(NAME_W);
|
|
2700
|
+
const curCol = u.currentVersion.padEnd(VER_W);
|
|
2701
|
+
const latCol = u.latestVersion.padEnd(VER_W);
|
|
2702
|
+
const statusCol = u.hasUpdate ? import_picocolors9.default.yellow(`Update available${u.updateType ? ` [${u.updateType}]` : ""}`) : import_picocolors9.default.green("Up to date");
|
|
2703
|
+
console.log(` ${nameCol} ${curCol} ${latCol} ${statusCol}`);
|
|
2704
|
+
}
|
|
2705
|
+
console.log();
|
|
2706
|
+
}
|
|
2707
|
+
var update_default = (0, import_citty9.defineCommand)({
|
|
2708
|
+
meta: {
|
|
2709
|
+
name: "update",
|
|
2710
|
+
description: "Check for and apply updates to installed MCP servers"
|
|
2711
|
+
},
|
|
2712
|
+
args: {
|
|
2713
|
+
server: {
|
|
2714
|
+
type: "positional",
|
|
2715
|
+
description: "Server name to update (omit to update all)",
|
|
2716
|
+
required: false
|
|
2717
|
+
},
|
|
2718
|
+
check: {
|
|
2719
|
+
type: "boolean",
|
|
2720
|
+
description: "Check only \u2014 do not apply updates",
|
|
2721
|
+
default: false
|
|
2722
|
+
},
|
|
2723
|
+
yes: {
|
|
2724
|
+
type: "boolean",
|
|
2725
|
+
description: "Skip confirmation prompt",
|
|
2726
|
+
default: false
|
|
2727
|
+
},
|
|
2728
|
+
json: {
|
|
2729
|
+
type: "boolean",
|
|
2730
|
+
description: "Output results as JSON",
|
|
2731
|
+
default: false
|
|
2732
|
+
}
|
|
2733
|
+
},
|
|
2734
|
+
async run({ args }) {
|
|
2735
|
+
const lockfile = readLockfile();
|
|
2736
|
+
const servers = lockfile.servers;
|
|
2737
|
+
const targetEntries = args.server ? Object.entries(servers).filter(([name]) => name === args.server) : Object.entries(servers);
|
|
2738
|
+
if (targetEntries.length === 0) {
|
|
2739
|
+
if (args.server) {
|
|
2740
|
+
console.error(`Server '${args.server}' not found in lockfile.`);
|
|
2741
|
+
} else {
|
|
2742
|
+
console.log("No servers installed. Run mcpman install <server> first.");
|
|
2743
|
+
}
|
|
2744
|
+
process.exit(1);
|
|
2745
|
+
}
|
|
2746
|
+
const spinner5 = p10.spinner();
|
|
2747
|
+
spinner5.start("Checking versions...");
|
|
2748
|
+
let updates;
|
|
2749
|
+
try {
|
|
2750
|
+
const partialLock = {
|
|
2751
|
+
lockfileVersion: 1,
|
|
2752
|
+
servers: Object.fromEntries(targetEntries)
|
|
2753
|
+
};
|
|
2754
|
+
updates = await checkAllVersions(partialLock);
|
|
2755
|
+
} catch (err) {
|
|
2756
|
+
spinner5.stop("Version check failed");
|
|
2757
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1394
2758
|
process.exit(1);
|
|
1395
2759
|
}
|
|
1396
|
-
|
|
2760
|
+
spinner5.stop(`Checked ${updates.length} server(s)`);
|
|
2761
|
+
if (args.json) {
|
|
2762
|
+
console.log(JSON.stringify(updates, null, 2));
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
printTable(updates);
|
|
2766
|
+
const outdated = updates.filter((u) => u.hasUpdate);
|
|
2767
|
+
if (outdated.length === 0) {
|
|
2768
|
+
console.log(import_picocolors9.default.green(" All servers are up to date."));
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
if (args.check) {
|
|
2772
|
+
console.log(import_picocolors9.default.yellow(` ${outdated.length} update(s) available. Run mcpman update to apply.`));
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2775
|
+
if (!args.yes) {
|
|
2776
|
+
const confirmed = await p10.confirm({
|
|
2777
|
+
message: `Apply ${outdated.length} update(s)?`,
|
|
2778
|
+
initialValue: true
|
|
2779
|
+
});
|
|
2780
|
+
if (p10.isCancel(confirmed) || !confirmed) {
|
|
2781
|
+
p10.outro("Cancelled.");
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
const clients = await loadClients3();
|
|
2786
|
+
let successCount = 0;
|
|
2787
|
+
for (const update of outdated) {
|
|
2788
|
+
const s = p10.spinner();
|
|
2789
|
+
s.start(`Updating ${update.server}...`);
|
|
2790
|
+
const result = await applyServerUpdate(
|
|
2791
|
+
update.server,
|
|
2792
|
+
servers[update.server],
|
|
2793
|
+
clients
|
|
2794
|
+
);
|
|
2795
|
+
if (result.success) {
|
|
2796
|
+
s.stop(`${import_picocolors9.default.green("\u2713")} ${update.server}: ${result.fromVersion} \u2192 ${result.toVersion}`);
|
|
2797
|
+
successCount++;
|
|
2798
|
+
} else {
|
|
2799
|
+
s.stop(`${import_picocolors9.default.red("\u2717")} ${update.server}: ${result.error}`);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
const freshLockfile = readLockfile(resolveLockfilePath());
|
|
2803
|
+
const freshUpdates = await checkAllVersions(freshLockfile);
|
|
2804
|
+
writeUpdateCache({ lastCheck: (/* @__PURE__ */ new Date()).toISOString(), updates: freshUpdates });
|
|
2805
|
+
p10.outro(`${successCount} of ${outdated.length} server(s) updated.`);
|
|
1397
2806
|
}
|
|
1398
2807
|
});
|
|
1399
2808
|
|
|
1400
2809
|
// src/utils/constants.ts
|
|
1401
2810
|
init_cjs_shims();
|
|
1402
2811
|
var APP_NAME = "mcpman";
|
|
1403
|
-
var APP_VERSION = "0.
|
|
2812
|
+
var APP_VERSION = "0.3.0";
|
|
1404
2813
|
var APP_DESCRIPTION = "The package manager for MCP servers";
|
|
1405
2814
|
|
|
1406
2815
|
// src/index.ts
|
|
@@ -1408,7 +2817,7 @@ process.on("SIGINT", () => {
|
|
|
1408
2817
|
console.log("\nAborted.");
|
|
1409
2818
|
process.exit(130);
|
|
1410
2819
|
});
|
|
1411
|
-
var main = (0,
|
|
2820
|
+
var main = (0, import_citty10.defineCommand)({
|
|
1412
2821
|
meta: {
|
|
1413
2822
|
name: APP_NAME,
|
|
1414
2823
|
version: APP_VERSION,
|
|
@@ -1419,7 +2828,11 @@ var main = (0, import_citty6.defineCommand)({
|
|
|
1419
2828
|
list: list_default,
|
|
1420
2829
|
remove: remove_default,
|
|
1421
2830
|
doctor: doctor_default,
|
|
1422
|
-
init: init_default
|
|
2831
|
+
init: init_default,
|
|
2832
|
+
secrets: secrets_default,
|
|
2833
|
+
sync: sync_default,
|
|
2834
|
+
audit: audit_default,
|
|
2835
|
+
update: update_default
|
|
1423
2836
|
}
|
|
1424
2837
|
});
|
|
1425
|
-
(0,
|
|
2838
|
+
(0, import_citty10.runMain)(main);
|