@yawlabs/mcp 0.62.0 → 0.63.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
signIn,
|
|
18
18
|
signOut,
|
|
19
19
|
userConfigDir
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-BTL5M3GN.js";
|
|
21
21
|
|
|
22
22
|
// src/audit-cmd.ts
|
|
23
23
|
import { homedir as homedir3 } from "os";
|
|
@@ -401,7 +401,7 @@ function parseAuditArgs(argv) {
|
|
|
401
401
|
if (a === "--json") {
|
|
402
402
|
json = true;
|
|
403
403
|
} else if (a === "--help" || a === "-h") {
|
|
404
|
-
return { ok: false, error: AUDIT_USAGE };
|
|
404
|
+
return { ok: false, error: AUDIT_USAGE, help: true };
|
|
405
405
|
} else if (a.startsWith("-")) {
|
|
406
406
|
return { ok: false, error: `yaw-mcp audit: unknown argument "${a}"
|
|
407
407
|
|
|
@@ -451,7 +451,7 @@ async function runAudit(opts = {}) {
|
|
|
451
451
|
const namespace = opts.namespace;
|
|
452
452
|
if (!namespace) {
|
|
453
453
|
printErr("yaw-mcp audit: missing <namespace>.");
|
|
454
|
-
return { exitCode:
|
|
454
|
+
return { exitCode: 2, lines };
|
|
455
455
|
}
|
|
456
456
|
const home = opts.home ?? homedir3();
|
|
457
457
|
const { config, path: path5 } = await loadLocalBundles({ cwd: opts.cwd, home });
|
|
@@ -576,6 +576,9 @@ function topPartialBundles(installedNamespaces, limit) {
|
|
|
576
576
|
}).slice(0, limit);
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
+
// src/config.ts
|
|
580
|
+
import { request } from "undici";
|
|
581
|
+
|
|
579
582
|
// src/config-loader.ts
|
|
580
583
|
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
581
584
|
import { homedir as homedir4 } from "os";
|
|
@@ -841,7 +844,6 @@ function profileAllows(profile, namespace) {
|
|
|
841
844
|
}
|
|
842
845
|
|
|
843
846
|
// src/config.ts
|
|
844
|
-
import { request } from "undici";
|
|
845
847
|
async function fetchConfig(apiUrl5, token5, currentVersion) {
|
|
846
848
|
const url = `${apiUrl5.replace(/\/$/, "")}/api/connect/config`;
|
|
847
849
|
const headers = {
|
|
@@ -1134,14 +1136,14 @@ var SUBCOMMAND_SPEC = [
|
|
|
1134
1136
|
name: "sync",
|
|
1135
1137
|
description: "Sync bundles across machines",
|
|
1136
1138
|
positional: ["push", "pull", "status"],
|
|
1137
|
-
flags: ["--
|
|
1139
|
+
flags: ["--dry-run", "--json", "--help"]
|
|
1138
1140
|
},
|
|
1139
1141
|
{ name: "stats", description: "Show usage statistics", flags: ["--limit", "--days", "--json", "--help"] },
|
|
1140
1142
|
{
|
|
1141
1143
|
name: "secrets",
|
|
1142
1144
|
description: "Manage stored secrets",
|
|
1143
1145
|
positional: ["set", "get", "list", "remove", "lock", "push", "pull"],
|
|
1144
|
-
flags: ["--
|
|
1146
|
+
flags: ["--force", "--value", "--stdin", "--json", "--help"]
|
|
1145
1147
|
},
|
|
1146
1148
|
{
|
|
1147
1149
|
name: "set-active",
|
|
@@ -1152,6 +1154,12 @@ var SUBCOMMAND_SPEC = [
|
|
|
1152
1154
|
// Other.
|
|
1153
1155
|
{ name: "audit", description: "Run a full-pass audit of loaded servers", flags: ["--json", "--help"] },
|
|
1154
1156
|
{ name: "compliance", description: "Run the compliance suite against a server", flags: ["--publish", "--help"] },
|
|
1157
|
+
{
|
|
1158
|
+
name: "foundry",
|
|
1159
|
+
description: "Export the opt-in dispatch-trace corpus",
|
|
1160
|
+
positional: ["export"],
|
|
1161
|
+
flags: ["--out", "--cap", "--json", "--help"]
|
|
1162
|
+
},
|
|
1155
1163
|
{ name: "help", description: "Show usage", flags: [] }
|
|
1156
1164
|
];
|
|
1157
1165
|
function parseCompletionArgs(argv) {
|
|
@@ -1226,7 +1234,7 @@ ${posClause}
|
|
|
1226
1234
|
# Install: save this to ~/.local/share/bash-completion/completions/yaw-mcp
|
|
1227
1235
|
# or source it from your .bashrc.
|
|
1228
1236
|
_yaw-mcp() {
|
|
1229
|
-
local cur
|
|
1237
|
+
local cur cword
|
|
1230
1238
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
1231
1239
|
cword=$COMP_CWORD
|
|
1232
1240
|
|
|
@@ -1337,14 +1345,17 @@ ${caseBranches}
|
|
|
1337
1345
|
// src/compliance-cmd.ts
|
|
1338
1346
|
import { spawn } from "child_process";
|
|
1339
1347
|
import { request as request2 } from "undici";
|
|
1348
|
+
var COMPLIANCE_USAGE = '\n Usage: yaw-mcp compliance <target> [extraArgs...] [--publish]\n\n Examples:\n yaw-mcp compliance "npx -y @modelcontextprotocol/server-filesystem /tmp"\n yaw-mcp compliance https://example.com/mcp --publish\n\n';
|
|
1340
1349
|
async function runComplianceCommand(argv) {
|
|
1350
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
1351
|
+
process.stdout.write(COMPLIANCE_USAGE);
|
|
1352
|
+
return 0;
|
|
1353
|
+
}
|
|
1341
1354
|
const publish = argv.includes("--publish");
|
|
1342
1355
|
const args = argv.filter((a) => a !== "--publish");
|
|
1343
1356
|
if (args.length === 0) {
|
|
1344
|
-
process.stderr.write(
|
|
1345
|
-
|
|
1346
|
-
);
|
|
1347
|
-
return 1;
|
|
1357
|
+
process.stderr.write(COMPLIANCE_USAGE);
|
|
1358
|
+
return 2;
|
|
1348
1359
|
}
|
|
1349
1360
|
const apiUrl5 = process.env.YAW_MCP_URL ?? "https://yaw.sh/mcp";
|
|
1350
1361
|
const report = await runTest(args);
|
|
@@ -1372,6 +1383,8 @@ Delete token (save this): ${result.deleteToken}
|
|
|
1372
1383
|
}
|
|
1373
1384
|
return 0;
|
|
1374
1385
|
}
|
|
1386
|
+
var MAX_STDOUT_BYTES = 16 * 1024 * 1024;
|
|
1387
|
+
var CHILD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1375
1388
|
function runTest(args) {
|
|
1376
1389
|
return new Promise((resolve7) => {
|
|
1377
1390
|
const child = spawn("npx", ["-y", "@yawlabs/mcp-compliance", "test", "--format", "json", ...args], {
|
|
@@ -1379,16 +1392,48 @@ function runTest(args) {
|
|
|
1379
1392
|
shell: process.platform === "win32"
|
|
1380
1393
|
});
|
|
1381
1394
|
let stdout = "";
|
|
1395
|
+
let stdoutBytes = 0;
|
|
1396
|
+
let settled = false;
|
|
1397
|
+
const timer = setTimeout(() => {
|
|
1398
|
+
if (settled) return;
|
|
1399
|
+
settled = true;
|
|
1400
|
+
child.kill();
|
|
1401
|
+
process.stderr.write(`
|
|
1402
|
+
mcp-compliance timed out after ${CHILD_TIMEOUT_MS / 1e3}s; killed.
|
|
1403
|
+
`);
|
|
1404
|
+
resolve7(null);
|
|
1405
|
+
}, CHILD_TIMEOUT_MS);
|
|
1406
|
+
timer.unref?.();
|
|
1382
1407
|
child.stdout.on("data", (chunk) => {
|
|
1408
|
+
if (settled) return;
|
|
1409
|
+
stdoutBytes += chunk.length;
|
|
1410
|
+
if (stdoutBytes > MAX_STDOUT_BYTES) {
|
|
1411
|
+
settled = true;
|
|
1412
|
+
clearTimeout(timer);
|
|
1413
|
+
child.kill();
|
|
1414
|
+
process.stderr.write(
|
|
1415
|
+
`
|
|
1416
|
+
mcp-compliance produced more than ${MAX_STDOUT_BYTES / (1024 * 1024)} MB of output; killed.
|
|
1417
|
+
`
|
|
1418
|
+
);
|
|
1419
|
+
resolve7(null);
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1383
1422
|
stdout += chunk.toString();
|
|
1384
1423
|
});
|
|
1385
1424
|
child.on("error", (err) => {
|
|
1425
|
+
if (settled) return;
|
|
1426
|
+
settled = true;
|
|
1427
|
+
clearTimeout(timer);
|
|
1386
1428
|
process.stderr.write(`
|
|
1387
1429
|
Failed to launch mcp-compliance: ${err.message}
|
|
1388
1430
|
`);
|
|
1389
1431
|
resolve7(null);
|
|
1390
1432
|
});
|
|
1391
1433
|
child.on("close", (code) => {
|
|
1434
|
+
if (settled) return;
|
|
1435
|
+
settled = true;
|
|
1436
|
+
clearTimeout(timer);
|
|
1392
1437
|
try {
|
|
1393
1438
|
const parsed = JSON.parse(stdout);
|
|
1394
1439
|
if (!parsed.grade || !parsed.summary) {
|
|
@@ -1417,12 +1462,36 @@ Target: ${url}
|
|
|
1417
1462
|
`
|
|
1418
1463
|
);
|
|
1419
1464
|
}
|
|
1465
|
+
function projectForPublish(report) {
|
|
1466
|
+
const tests = Array.isArray(report.tests) ? report.tests : [];
|
|
1467
|
+
return {
|
|
1468
|
+
grade: report.grade,
|
|
1469
|
+
score: report.score,
|
|
1470
|
+
url: report.url,
|
|
1471
|
+
summary: {
|
|
1472
|
+
total: report.summary.total,
|
|
1473
|
+
passed: report.summary.passed,
|
|
1474
|
+
failed: report.summary.failed,
|
|
1475
|
+
required: report.summary.required,
|
|
1476
|
+
requiredPassed: report.summary.requiredPassed
|
|
1477
|
+
},
|
|
1478
|
+
tests: tests.map((t) => {
|
|
1479
|
+
const test = t ?? {};
|
|
1480
|
+
const projected = {};
|
|
1481
|
+
if (typeof test.name === "string") projected.name = test.name;
|
|
1482
|
+
if (typeof test.status === "string") projected.status = test.status;
|
|
1483
|
+
if (typeof test.required === "boolean") projected.required = test.required;
|
|
1484
|
+
if (typeof test.message === "string") projected.message = test.message;
|
|
1485
|
+
return projected;
|
|
1486
|
+
})
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1420
1489
|
async function publishReport(apiUrl5, report) {
|
|
1421
1490
|
try {
|
|
1422
1491
|
const res = await request2(`${apiUrl5.replace(/\/$/, "")}/api/compliance/ext`, {
|
|
1423
1492
|
method: "POST",
|
|
1424
1493
|
headers: { "Content-Type": "application/json" },
|
|
1425
|
-
body: JSON.stringify(report)
|
|
1494
|
+
body: JSON.stringify(projectForPublish(report))
|
|
1426
1495
|
});
|
|
1427
1496
|
if (res.statusCode !== 200) {
|
|
1428
1497
|
const body = await res.body.text().catch(() => "");
|
|
@@ -1720,7 +1789,7 @@ function resolveShadowedClis(server) {
|
|
|
1720
1789
|
if (cache.length < 3) return [];
|
|
1721
1790
|
const prefixes = /* @__PURE__ */ new Set();
|
|
1722
1791
|
for (const t of cache) {
|
|
1723
|
-
const first = t.name.split(/[_
|
|
1792
|
+
const first = t.name.split(/[_.-]/)[0];
|
|
1724
1793
|
if (first) prefixes.add(first.toLowerCase());
|
|
1725
1794
|
}
|
|
1726
1795
|
if (prefixes.size !== 1) return [];
|
|
@@ -2083,7 +2152,7 @@ async function reportTools(serverId, tools) {
|
|
|
2083
2152
|
// src/try-cmd.ts
|
|
2084
2153
|
import { createHash as createHash2 } from "crypto";
|
|
2085
2154
|
import { existsSync as existsSync3 } from "fs";
|
|
2086
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile6,
|
|
2155
|
+
import { chmod as chmod2, mkdir as mkdir2, readdir, readFile as readFile6, unlink } from "fs/promises";
|
|
2087
2156
|
import { homedir as homedir7, hostname, userInfo } from "os";
|
|
2088
2157
|
import { join as join7, resolve as resolve5 } from "path";
|
|
2089
2158
|
import { request as request5 } from "undici";
|
|
@@ -2189,7 +2258,8 @@ import { chmod, readFile as readFile5 } from "fs/promises";
|
|
|
2189
2258
|
import { homedir as homedir6 } from "os";
|
|
2190
2259
|
import { join as join6, resolve as resolve4 } from "path";
|
|
2191
2260
|
import { createInterface } from "readline/promises";
|
|
2192
|
-
|
|
2261
|
+
import { Writable } from "stream";
|
|
2262
|
+
var USAGE = "Usage: yaw-mcp install <claude-code|claude-desktop|cursor|vscode> [--scope user|project|local]\n [--token <mcp_pat_\u2026>] [--project-dir <path>] [--os macos|linux|windows]\n [--force | --skip] [--dry-run] [--no-yaw-mcp-config]\n yaw-mcp install --list (detect clients; no writes)\n yaw-mcp install --all [--token <mcp_pat_\u2026>] (install into every detected client)\n\n Note: --token puts the PAT on the command line, where it is visible in shell\n history and the process table (ps/Task Manager) -- avoid it on shared machines.\n Prefer seeding ~/.yaw-mcp/config.json once (install reads the token from there),\n or set the token via your account before installing.";
|
|
2193
2263
|
async function runInstall(opts) {
|
|
2194
2264
|
const stdout = opts.io?.stdout ?? process.stdout;
|
|
2195
2265
|
const stderr = opts.io?.stderr ?? process.stderr;
|
|
@@ -2306,7 +2376,7 @@ ${USAGE}`);
|
|
|
2306
2376
|
}
|
|
2307
2377
|
if (existingHasEntry) {
|
|
2308
2378
|
let decision;
|
|
2309
|
-
if (opts.force) decision = "overwrite";
|
|
2379
|
+
if (opts.force || opts.dryRun) decision = "overwrite";
|
|
2310
2380
|
else if (opts.skip) decision = "skip";
|
|
2311
2381
|
else if (opts.promptAnswer) decision = opts.promptAnswer;
|
|
2312
2382
|
else if (opts.io?.isTTY ?? (Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY))) {
|
|
@@ -2351,7 +2421,7 @@ ${USAGE}`);
|
|
|
2351
2421
|
if (opts.dryRun) {
|
|
2352
2422
|
log2("\n--- dry run: would write the following ---");
|
|
2353
2423
|
if (writeYawMcpConfig) log2(`# ${yawMcpConfigPath}
|
|
2354
|
-
${yawMcpConfigJson}`);
|
|
2424
|
+
${redactConfigToken(yawMcpConfigJson)}`);
|
|
2355
2425
|
log2(`
|
|
2356
2426
|
# ${resolved.absolute}
|
|
2357
2427
|
${clientJson}`);
|
|
@@ -2371,7 +2441,7 @@ ${settingsPatch.nextJson}`);
|
|
|
2371
2441
|
const written = [];
|
|
2372
2442
|
if (writeYawMcpConfig) {
|
|
2373
2443
|
try {
|
|
2374
|
-
await atomicWriteFile(yawMcpConfigPath, yawMcpConfigJson);
|
|
2444
|
+
await atomicWriteFile(yawMcpConfigPath, yawMcpConfigJson, "utf8", 384);
|
|
2375
2445
|
if (process.platform !== "win32") {
|
|
2376
2446
|
try {
|
|
2377
2447
|
await chmod(yawMcpConfigPath, 384);
|
|
@@ -2527,6 +2597,21 @@ function removeFromClientConfig(existing, containerPath, entryName) {
|
|
|
2527
2597
|
parent[leafKey] = container;
|
|
2528
2598
|
return out;
|
|
2529
2599
|
}
|
|
2600
|
+
async function writeBackup(path5, raw) {
|
|
2601
|
+
const candidate = `${path5}.bak-${Date.now()}`;
|
|
2602
|
+
try {
|
|
2603
|
+
await atomicWriteFile(candidate, raw, "utf8", 384);
|
|
2604
|
+
if (process.platform !== "win32") {
|
|
2605
|
+
try {
|
|
2606
|
+
await chmod(candidate, 384);
|
|
2607
|
+
} catch {
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
return candidate;
|
|
2611
|
+
} catch {
|
|
2612
|
+
return void 0;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2530
2615
|
async function composeYawMcpConfig(path5, token5) {
|
|
2531
2616
|
let existing = {};
|
|
2532
2617
|
let backupPath;
|
|
@@ -2543,20 +2628,10 @@ async function composeYawMcpConfig(path5, token5) {
|
|
|
2543
2628
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
2544
2629
|
existing = parsed;
|
|
2545
2630
|
} else {
|
|
2546
|
-
|
|
2547
|
-
try {
|
|
2548
|
-
await atomicWriteFile(candidate, raw);
|
|
2549
|
-
backupPath = candidate;
|
|
2550
|
-
} catch {
|
|
2551
|
-
}
|
|
2631
|
+
backupPath = await writeBackup(path5, raw);
|
|
2552
2632
|
}
|
|
2553
2633
|
} catch {
|
|
2554
|
-
|
|
2555
|
-
try {
|
|
2556
|
-
await atomicWriteFile(candidate, raw);
|
|
2557
|
-
backupPath = candidate;
|
|
2558
|
-
} catch {
|
|
2559
|
-
}
|
|
2634
|
+
backupPath = await writeBackup(path5, raw);
|
|
2560
2635
|
}
|
|
2561
2636
|
}
|
|
2562
2637
|
}
|
|
@@ -2566,6 +2641,9 @@ async function composeYawMcpConfig(path5, token5) {
|
|
|
2566
2641
|
return { json: `${JSON.stringify(next, null, 2)}
|
|
2567
2642
|
`, backupPath };
|
|
2568
2643
|
}
|
|
2644
|
+
function redactConfigToken(json) {
|
|
2645
|
+
return json.replace(/("token"\s*:\s*)"(?:[^"\\]|\\.)*"/g, '$1"mcp_pat_***"');
|
|
2646
|
+
}
|
|
2569
2647
|
function parseInstallArgs(argv) {
|
|
2570
2648
|
if (argv.length === 0) return { ok: false, error: USAGE };
|
|
2571
2649
|
const positional = [];
|
|
@@ -2680,7 +2758,7 @@ async function runInstallList(opts, log2) {
|
|
|
2680
2758
|
}
|
|
2681
2759
|
log2("");
|
|
2682
2760
|
log2("Install into a specific client: `yaw-mcp install <client> [--scope user|project|local]`");
|
|
2683
|
-
log2("Install into every available user
|
|
2761
|
+
log2("Install into every available client (user scope where supported): `yaw-mcp install --all`");
|
|
2684
2762
|
return { written: [], wouldWrite: [], messages: [], exitCode: 0 };
|
|
2685
2763
|
}
|
|
2686
2764
|
function statusFor(p) {
|
|
@@ -2737,15 +2815,35 @@ async function runInstallAll(opts, log2, err) {
|
|
|
2737
2815
|
const aggregateMessages = [];
|
|
2738
2816
|
let failed = 0;
|
|
2739
2817
|
let succeeded = 0;
|
|
2818
|
+
const collisionClients = [];
|
|
2819
|
+
const realStderr = opts.io?.stderr ?? process.stderr;
|
|
2820
|
+
const isCollisionRefusal = (s) => s.includes(`already has a "${ENTRY_NAME}" entry and stdin is not a TTY`);
|
|
2740
2821
|
for (const plan of plans) {
|
|
2741
2822
|
log2(`\u2500\u2500 ${plan.clientId} (${plan.scope}) \u2500\u2500`);
|
|
2823
|
+
let sawCollision = false;
|
|
2824
|
+
const subStderr = new Writable({
|
|
2825
|
+
write(chunk, _enc, cb) {
|
|
2826
|
+
const text = chunk.toString();
|
|
2827
|
+
if (isCollisionRefusal(text)) sawCollision = true;
|
|
2828
|
+
else realStderr.write(text);
|
|
2829
|
+
cb();
|
|
2830
|
+
}
|
|
2831
|
+
});
|
|
2832
|
+
const baseIo = opts.io ?? {
|
|
2833
|
+
stdin: process.stdin,
|
|
2834
|
+
stdout: process.stdout,
|
|
2835
|
+
stderr: process.stderr,
|
|
2836
|
+
isTTY: Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY)
|
|
2837
|
+
};
|
|
2742
2838
|
const result = await runInstall({
|
|
2743
2839
|
...opts,
|
|
2744
2840
|
listOnly: false,
|
|
2745
2841
|
all: false,
|
|
2746
2842
|
clientId: plan.clientId,
|
|
2747
|
-
scope: plan.scope
|
|
2843
|
+
scope: plan.scope,
|
|
2844
|
+
io: { ...baseIo, stderr: subStderr }
|
|
2748
2845
|
});
|
|
2846
|
+
if (sawCollision) collisionClients.push(plan.clientId);
|
|
2749
2847
|
aggregateWritten.push(...result.written);
|
|
2750
2848
|
aggregateWouldWrite.push(...result.wouldWrite);
|
|
2751
2849
|
aggregateMessages.push(...result.messages);
|
|
@@ -2753,6 +2851,12 @@ async function runInstallAll(opts, log2, err) {
|
|
|
2753
2851
|
else failed += 1;
|
|
2754
2852
|
log2("");
|
|
2755
2853
|
}
|
|
2854
|
+
if (collisionClients.length > 0) {
|
|
2855
|
+
err(
|
|
2856
|
+
`yaw-mcp install --all: ${collisionClients.length} client${collisionClients.length === 1 ? "" : "s"} already have a "${ENTRY_NAME}" entry (${collisionClients.join(", ")}) and stdin is not a TTY.
|
|
2857
|
+
Re-run \`yaw-mcp install --all --force\` to overwrite them, or \`--skip\` to leave them untouched.`
|
|
2858
|
+
);
|
|
2859
|
+
}
|
|
2756
2860
|
const totalPlanned = plans.length;
|
|
2757
2861
|
if (failed === 0) {
|
|
2758
2862
|
log2(`Done: ${succeeded}/${totalPlanned} clients installed successfully.`);
|
|
@@ -2829,7 +2933,7 @@ function parseTryArgs(argv) {
|
|
|
2829
2933
|
}
|
|
2830
2934
|
case "--env": {
|
|
2831
2935
|
const v = next();
|
|
2832
|
-
if (!v
|
|
2936
|
+
if (!v?.includes("=")) return { ok: false, error: "--env requires KEY=value" };
|
|
2833
2937
|
const eq = v.indexOf("=");
|
|
2834
2938
|
const key = v.slice(0, eq);
|
|
2835
2939
|
const val = v.slice(eq + 1);
|
|
@@ -2844,7 +2948,7 @@ function parseTryArgs(argv) {
|
|
|
2844
2948
|
break;
|
|
2845
2949
|
case "--base": {
|
|
2846
2950
|
const v = next();
|
|
2847
|
-
if (!v) return { ok: false, error: "--base requires a URL" };
|
|
2951
|
+
if (!v || v.startsWith("--")) return { ok: false, error: "--base requires a URL" };
|
|
2848
2952
|
opts.baseUrl = v;
|
|
2849
2953
|
break;
|
|
2850
2954
|
}
|
|
@@ -2853,6 +2957,8 @@ function parseTryArgs(argv) {
|
|
|
2853
2957
|
return { ok: false, error: TRY_USAGE, help: true };
|
|
2854
2958
|
default:
|
|
2855
2959
|
if (a.startsWith("--")) return { ok: false, error: `Unknown flag: ${a}
|
|
2960
|
+
${TRY_USAGE}` };
|
|
2961
|
+
if (a === "-") return { ok: false, error: `Invalid argument "-".
|
|
2856
2962
|
${TRY_USAGE}` };
|
|
2857
2963
|
positional.push(a);
|
|
2858
2964
|
}
|
|
@@ -2879,6 +2985,8 @@ function parseTryCleanupArgs(argv) {
|
|
|
2879
2985
|
continue;
|
|
2880
2986
|
}
|
|
2881
2987
|
if (a.startsWith("--")) return { ok: false, error: `Unknown flag: ${a}
|
|
2988
|
+
${TRY_CLEANUP_USAGE}` };
|
|
2989
|
+
if (a === "-") return { ok: false, error: `Invalid argument "-".
|
|
2882
2990
|
${TRY_CLEANUP_USAGE}` };
|
|
2883
2991
|
positional.push(a);
|
|
2884
2992
|
}
|
|
@@ -3045,6 +3153,10 @@ async function runTry(opts) {
|
|
|
3045
3153
|
for (const [k, v] of Object.entries(opts.envOverrides ?? {})) {
|
|
3046
3154
|
if (!(k in trialEnv)) trialEnv[k] = v;
|
|
3047
3155
|
}
|
|
3156
|
+
const overrides = opts.envOverrides ?? {};
|
|
3157
|
+
const ambientOnlyRequired = (server.requiredEnvVars ?? []).filter(
|
|
3158
|
+
(k) => (!overrides[k] || overrides[k] === "") && (supplied[k] ?? "").trim() !== ""
|
|
3159
|
+
);
|
|
3048
3160
|
const entry = buildLaunchEntry({
|
|
3049
3161
|
os,
|
|
3050
3162
|
upstream: {
|
|
@@ -3066,8 +3178,9 @@ async function runTry(opts) {
|
|
|
3066
3178
|
entryName,
|
|
3067
3179
|
createdAt: now
|
|
3068
3180
|
};
|
|
3181
|
+
const clientPreExisted = existsSync3(resolved.absolute);
|
|
3069
3182
|
let existing = {};
|
|
3070
|
-
if (
|
|
3183
|
+
if (clientPreExisted) {
|
|
3071
3184
|
try {
|
|
3072
3185
|
const raw = await readFile6(resolved.absolute, "utf8");
|
|
3073
3186
|
if (raw.trim().length > 0) {
|
|
@@ -3110,6 +3223,12 @@ async function runTry(opts) {
|
|
|
3110
3223
|
try {
|
|
3111
3224
|
await atomicWriteFile(resolved.absolute, clientJson);
|
|
3112
3225
|
written.push(resolved.absolute);
|
|
3226
|
+
if (!clientPreExisted && entry.env && Object.keys(entry.env).length > 0 && process.platform !== "win32") {
|
|
3227
|
+
try {
|
|
3228
|
+
await chmod2(resolved.absolute, 384);
|
|
3229
|
+
} catch {
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3113
3232
|
} catch (e) {
|
|
3114
3233
|
printErr(`yaw-mcp try: failed to write ${resolved.absolute}: ${e.message}`);
|
|
3115
3234
|
await unlink(trialMarkerPath(slug, home)).catch(() => void 0);
|
|
@@ -3117,11 +3236,16 @@ async function runTry(opts) {
|
|
|
3117
3236
|
}
|
|
3118
3237
|
const anonId = await loadOrCreateAnonId(home);
|
|
3119
3238
|
const postEvent = opts.postEvent ?? defaultPostEvent;
|
|
3120
|
-
postEvent(baseUrl, { slug, action: "try", anonId }).catch(() => void 0);
|
|
3239
|
+
await postEvent(baseUrl, { slug, action: "try", anonId }).catch(() => void 0);
|
|
3121
3240
|
const ttlPretty = formatTtl(ttlMs);
|
|
3122
3241
|
print(`Trial wired: ${server.name} via yaw-mcp-try-${slug} -> ${resolved.absolute}`);
|
|
3123
3242
|
print(`Expires in ${ttlPretty}; remove sooner with: yaw-mcp try-cleanup ${slug}`);
|
|
3124
3243
|
print(`Liking it? Sign up at ${baseUrl}/signup to keep ${server.name} on every machine.`);
|
|
3244
|
+
if (ambientOnlyRequired.length > 0) {
|
|
3245
|
+
printErr(
|
|
3246
|
+
`Note: ${ambientOnlyRequired.join(", ")} ${ambientOnlyRequired.length === 1 ? "was" : "were"} read from your shell env and written into the trial entry at ${resolved.absolute}. Remove the trial with: yaw-mcp try-cleanup ${slug}`
|
|
3247
|
+
);
|
|
3248
|
+
}
|
|
3125
3249
|
return { exitCode: 0, written, marker };
|
|
3126
3250
|
}
|
|
3127
3251
|
async function runTryCleanup(opts) {
|
|
@@ -3194,7 +3318,7 @@ async function runTryCleanup(opts) {
|
|
|
3194
3318
|
}
|
|
3195
3319
|
const anonId = await loadOrCreateAnonId(home);
|
|
3196
3320
|
const postEvent = opts.postEvent ?? defaultPostEvent;
|
|
3197
|
-
postEvent(baseUrl, { slug, action: "cleanup", anonId }).catch(() => void 0);
|
|
3321
|
+
await postEvent(baseUrl, { slug, action: "cleanup", anonId }).catch(() => void 0);
|
|
3198
3322
|
print(`Trial for "${slug}" cleaned up.`);
|
|
3199
3323
|
return { exitCode: 0, written };
|
|
3200
3324
|
}
|
|
@@ -3268,7 +3392,7 @@ async function gcExpiredTrials(opts) {
|
|
|
3268
3392
|
}
|
|
3269
3393
|
}
|
|
3270
3394
|
await unlink(trialMarkerPath(marker.slug, home));
|
|
3271
|
-
postEvent(baseUrl, { slug: marker.slug, action: "expiry-gc", anonId }).catch(() => void 0);
|
|
3395
|
+
await postEvent(baseUrl, { slug: marker.slug, action: "expiry-gc", anonId }).catch(() => void 0);
|
|
3272
3396
|
cleared++;
|
|
3273
3397
|
} catch (e) {
|
|
3274
3398
|
log("debug", "trial gc failed", { slug: marker.slug, error: e.message });
|
|
@@ -3308,7 +3432,7 @@ ${UPGRADE_USAGE}` };
|
|
|
3308
3432
|
function detectInstallMethod(argvPath) {
|
|
3309
3433
|
if (!argvPath) return "unknown";
|
|
3310
3434
|
const normalized = argvPath.replace(/\\/g, "/");
|
|
3311
|
-
if (/\/_npx\//.test(normalized)) return "npx";
|
|
3435
|
+
if (/\/_npx\/[0-9a-f]+\/node_modules\/@yawlabs\/mcp\//.test(normalized)) return "npx";
|
|
3312
3436
|
if (/\/app\.asar\.unpacked\//.test(normalized)) return "bundled-app";
|
|
3313
3437
|
if (/\/npm\/node_modules\/@yawlabs\/mcp\//.test(normalized)) return "global-npm";
|
|
3314
3438
|
if (/\/lib\/node_modules\/@yawlabs\/mcp\//.test(normalized)) return "global-npm";
|
|
@@ -3521,23 +3645,20 @@ async function runUpgrade(opts = {}) {
|
|
|
3521
3645
|
return { exitCode: 0, lines };
|
|
3522
3646
|
}
|
|
3523
3647
|
if (method === "binary") {
|
|
3524
|
-
print("yaw-mcp is running as a standalone binary \u2014
|
|
3525
|
-
print("
|
|
3648
|
+
print("yaw-mcp is running as a standalone binary \u2014 manual upgrade required.");
|
|
3649
|
+
print("There's no package manager to upgrade it, and `--run` can't automate");
|
|
3650
|
+
print("this: download the latest build and replace this executable:");
|
|
3526
3651
|
print("");
|
|
3527
3652
|
print(` ${BINARY_DOWNLOAD_URL}`);
|
|
3528
3653
|
return { exitCode: opts.run ? 2 : 1, lines };
|
|
3529
3654
|
}
|
|
3530
|
-
if (!plan.command) {
|
|
3531
|
-
print("No upgrade command available for this install method.");
|
|
3532
|
-
return { exitCode: 0, lines };
|
|
3533
|
-
}
|
|
3534
3655
|
const installRoot = method === "local-node-modules" ? localInstallRoot(argvPath) : null;
|
|
3535
3656
|
const runSpec = method === "global-npm" ? { cmd: "npm", args: ["install", "-g", "@yawlabs/mcp@latest"] } : method === "pnpm-global" ? { cmd: "pnpm", args: ["add", "-g", "@yawlabs/mcp@latest"] } : method === "bun-global" ? { cmd: "bun", args: ["add", "-g", "@yawlabs/mcp@latest"] } : method === "local-node-modules" && installRoot !== null ? { cmd: "npm", args: ["install", "@yawlabs/mcp@latest"], cwd: installRoot } : null;
|
|
3536
3657
|
if (!opts.run) {
|
|
3537
3658
|
if (runSpec) {
|
|
3538
3659
|
print("Run `yaw-mcp upgrade --run` to upgrade in place, or run it yourself:");
|
|
3539
3660
|
} else {
|
|
3540
|
-
print("
|
|
3661
|
+
print("Manual upgrade required (--run can't safely automate this install method). Run it yourself:");
|
|
3541
3662
|
}
|
|
3542
3663
|
print("");
|
|
3543
3664
|
if (installRoot) {
|
|
@@ -3547,7 +3668,9 @@ async function runUpgrade(opts = {}) {
|
|
|
3547
3668
|
return { exitCode: 1, lines };
|
|
3548
3669
|
}
|
|
3549
3670
|
if (!runSpec) {
|
|
3550
|
-
printErr(
|
|
3671
|
+
printErr(
|
|
3672
|
+
`yaw-mcp upgrade --run: a "${method}" install can't be upgraded automatically (manual upgrade required). Run it yourself:`
|
|
3673
|
+
);
|
|
3551
3674
|
printErr("");
|
|
3552
3675
|
printErr(` ${plan.command}`);
|
|
3553
3676
|
return { exitCode: 2, lines };
|
|
@@ -3572,7 +3695,7 @@ async function runUpgrade(opts = {}) {
|
|
|
3572
3695
|
return { exitCode: 3, lines };
|
|
3573
3696
|
}
|
|
3574
3697
|
function readCurrentVersion() {
|
|
3575
|
-
return true ? "0.
|
|
3698
|
+
return true ? "0.63.1" : "dev";
|
|
3576
3699
|
}
|
|
3577
3700
|
|
|
3578
3701
|
// src/usage-hints.ts
|
|
@@ -3634,7 +3757,7 @@ function selectFlakyNamespaces(entries, limit) {
|
|
|
3634
3757
|
}
|
|
3635
3758
|
|
|
3636
3759
|
// src/doctor-cmd.ts
|
|
3637
|
-
var VERSION = true ? "0.
|
|
3760
|
+
var VERSION = true ? "0.63.1" : "dev";
|
|
3638
3761
|
function isPersistenceDisabled(env) {
|
|
3639
3762
|
const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
|
|
3640
3763
|
return raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
|
|
@@ -3677,11 +3800,13 @@ async function runDoctor(opts = {}) {
|
|
|
3677
3800
|
renderEnvSection({ env, print });
|
|
3678
3801
|
const persistenceDisabled = isPersistenceDisabled(env);
|
|
3679
3802
|
const stateFilePath = join8(userConfigDir(home), STATE_FILENAME);
|
|
3680
|
-
const
|
|
3681
|
-
await
|
|
3803
|
+
const statePeek = persistenceDisabled ? null : await peekStateFile(stateFilePath);
|
|
3804
|
+
const persistedState = statePeek?.kind === "ok" ? await loadState(stateFilePath) : null;
|
|
3805
|
+
renderStateSection({
|
|
3682
3806
|
filePath: stateFilePath,
|
|
3683
3807
|
disabled: persistenceDisabled,
|
|
3684
3808
|
persisted: persistedState,
|
|
3809
|
+
peek: statePeek,
|
|
3685
3810
|
print
|
|
3686
3811
|
});
|
|
3687
3812
|
renderReliabilitySection({ disabled: persistenceDisabled, persisted: persistedState, print });
|
|
@@ -3814,6 +3939,31 @@ async function runDoctorJson(opts) {
|
|
|
3814
3939
|
}
|
|
3815
3940
|
}
|
|
3816
3941
|
const shellShadows = scanShellHistoryForShadows({ home, env });
|
|
3942
|
+
const trialGc = await gcExpiredTrials({ home, env, postEvent: opts.postTryEvent, now: opts.now }).catch(() => ({
|
|
3943
|
+
cleared: 0,
|
|
3944
|
+
failed: 0
|
|
3945
|
+
}));
|
|
3946
|
+
const trialScan = await scanTrials({ home, now: opts.now });
|
|
3947
|
+
const trials = {
|
|
3948
|
+
cleared: trialGc.cleared,
|
|
3949
|
+
live: trialScan.live.map(({ marker, msUntilExpiry }) => ({
|
|
3950
|
+
slug: marker.slug,
|
|
3951
|
+
clientName: marker.clientName,
|
|
3952
|
+
clientPath: marker.clientPath,
|
|
3953
|
+
msUntilExpiry
|
|
3954
|
+
})),
|
|
3955
|
+
malformed: trialScan.malformed
|
|
3956
|
+
};
|
|
3957
|
+
const analyticsFailure = getLastAnalyticsFailure();
|
|
3958
|
+
const reportFailure = getLastReportFailure();
|
|
3959
|
+
const backgroundPosters = {
|
|
3960
|
+
analytics: analyticsFailure ? {
|
|
3961
|
+
statusCode: analyticsFailure.statusCode,
|
|
3962
|
+
url: analyticsFailure.url,
|
|
3963
|
+
at: new Date(analyticsFailure.at).toISOString()
|
|
3964
|
+
} : null,
|
|
3965
|
+
toolReport: reportFailure ? { statusCode: reportFailure.statusCode, url: reportFailure.url, at: new Date(reportFailure.at).toISOString() } : null
|
|
3966
|
+
};
|
|
3817
3967
|
const skipCheck = (opts.skipRegistryCheck === true || Boolean(process.env.VITEST)) && !opts.registryFetch;
|
|
3818
3968
|
const latest = skipCheck ? null : await fetchLatestVersion(opts.registryFetch);
|
|
3819
3969
|
const effectiveVersion = opts.currentVersion ?? VERSION;
|
|
@@ -3846,6 +3996,8 @@ async function runDoctorJson(opts) {
|
|
|
3846
3996
|
reliability,
|
|
3847
3997
|
clients,
|
|
3848
3998
|
shellShadows,
|
|
3999
|
+
trials,
|
|
4000
|
+
backgroundPosters,
|
|
3849
4001
|
upgrade: { current: effectiveVersion, latest, stale },
|
|
3850
4002
|
diagnosis: { exitCode, summary }
|
|
3851
4003
|
};
|
|
@@ -3874,16 +4026,15 @@ function renderEnvSection(opts) {
|
|
|
3874
4026
|
}
|
|
3875
4027
|
print("");
|
|
3876
4028
|
}
|
|
3877
|
-
|
|
3878
|
-
const { filePath, disabled, persisted, print } = opts;
|
|
4029
|
+
function renderStateSection(opts) {
|
|
4030
|
+
const { filePath, disabled, persisted, peek, print } = opts;
|
|
3879
4031
|
print("STATE");
|
|
3880
|
-
if (disabled) {
|
|
3881
|
-
print(" status: disabled via YAW_MCP_DISABLE_PERSISTENCE");
|
|
4032
|
+
if (disabled || !peek) {
|
|
4033
|
+
if (disabled) print(" status: disabled via YAW_MCP_DISABLE_PERSISTENCE");
|
|
3882
4034
|
print("");
|
|
3883
4035
|
return;
|
|
3884
4036
|
}
|
|
3885
4037
|
print(` path: ${filePath}`);
|
|
3886
|
-
const peek = await peekStateFile(filePath);
|
|
3887
4038
|
if (peek.kind === "malformed") {
|
|
3888
4039
|
print(" status: corrupt -- file exists but JSON is unparseable");
|
|
3889
4040
|
print(` fix: \`yaw-mcp reset-learning\` to clear, or open ${filePath} and fix by hand`);
|
|
@@ -4042,29 +4193,14 @@ function probeClients(opts) {
|
|
|
4042
4193
|
continue;
|
|
4043
4194
|
}
|
|
4044
4195
|
const exists3 = existsSync4(resolved.absolute);
|
|
4045
|
-
let
|
|
4046
|
-
let hasLegacyEntry = false;
|
|
4047
|
-
let legacyEntryName = null;
|
|
4048
|
-
let malformed = false;
|
|
4196
|
+
let classified = { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: false };
|
|
4049
4197
|
if (exists3) {
|
|
4050
4198
|
try {
|
|
4051
4199
|
statSync(resolved.absolute);
|
|
4052
4200
|
const raw = readFileSync(resolved.absolute, "utf8");
|
|
4053
|
-
|
|
4054
|
-
const parsed = parseJsonc(raw);
|
|
4055
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
4056
|
-
const container = walkContainer(parsed, resolved.containerPath);
|
|
4057
|
-
if (container) {
|
|
4058
|
-
hasMcpEntry = ENTRY_NAME in container;
|
|
4059
|
-
legacyEntryName = findLegacyEntry(container);
|
|
4060
|
-
hasLegacyEntry = legacyEntryName !== null;
|
|
4061
|
-
}
|
|
4062
|
-
} else {
|
|
4063
|
-
malformed = true;
|
|
4064
|
-
}
|
|
4065
|
-
}
|
|
4201
|
+
classified = classifyProbeContent(raw, resolved.containerPath);
|
|
4066
4202
|
} catch {
|
|
4067
|
-
|
|
4203
|
+
classified = { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: true };
|
|
4068
4204
|
}
|
|
4069
4205
|
}
|
|
4070
4206
|
out.push({
|
|
@@ -4072,10 +4208,7 @@ function probeClients(opts) {
|
|
|
4072
4208
|
scope: scope.scope,
|
|
4073
4209
|
path: resolved.absolute,
|
|
4074
4210
|
exists: exists3,
|
|
4075
|
-
|
|
4076
|
-
hasLegacyEntry,
|
|
4077
|
-
legacyEntryName,
|
|
4078
|
-
malformed,
|
|
4211
|
+
...classified,
|
|
4079
4212
|
unavailable: false
|
|
4080
4213
|
});
|
|
4081
4214
|
}
|
|
@@ -4091,6 +4224,30 @@ function walkContainer(root, path5) {
|
|
|
4091
4224
|
if (typeof cur !== "object" || cur === null || Array.isArray(cur)) return null;
|
|
4092
4225
|
return cur;
|
|
4093
4226
|
}
|
|
4227
|
+
function classifyProbeContent(raw, containerPath) {
|
|
4228
|
+
if (raw.trim().length === 0) {
|
|
4229
|
+
return { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: false };
|
|
4230
|
+
}
|
|
4231
|
+
try {
|
|
4232
|
+
const parsed = parseJsonc(raw);
|
|
4233
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
4234
|
+
return { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: true };
|
|
4235
|
+
}
|
|
4236
|
+
const container = walkContainer(parsed, containerPath);
|
|
4237
|
+
if (!container) {
|
|
4238
|
+
return { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: false };
|
|
4239
|
+
}
|
|
4240
|
+
const legacyEntryName = findLegacyEntry(container);
|
|
4241
|
+
return {
|
|
4242
|
+
hasMcpEntry: ENTRY_NAME in container,
|
|
4243
|
+
hasLegacyEntry: legacyEntryName !== null,
|
|
4244
|
+
legacyEntryName,
|
|
4245
|
+
malformed: false
|
|
4246
|
+
};
|
|
4247
|
+
} catch {
|
|
4248
|
+
return { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: true };
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4094
4251
|
async function probeClientsAsync(opts) {
|
|
4095
4252
|
const result = [];
|
|
4096
4253
|
for (const target of INSTALL_TARGETS) {
|
|
@@ -4110,38 +4267,28 @@ async function probeClientsAsync(opts) {
|
|
|
4110
4267
|
continue;
|
|
4111
4268
|
}
|
|
4112
4269
|
for (const scope of target.scopes) {
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4270
|
+
let resolved;
|
|
4271
|
+
try {
|
|
4272
|
+
resolved = resolveInstallPath({
|
|
4273
|
+
clientId: target.clientId,
|
|
4274
|
+
scope: scope.scope,
|
|
4275
|
+
os: opts.os,
|
|
4276
|
+
home: opts.home,
|
|
4277
|
+
projectDir: scope.requiresProjectDir ? opts.cwd : void 0,
|
|
4278
|
+
claudeConfigDir: opts.claudeConfigDir
|
|
4279
|
+
});
|
|
4280
|
+
} catch {
|
|
4281
|
+
continue;
|
|
4282
|
+
}
|
|
4121
4283
|
const exists3 = existsSync4(resolved.absolute);
|
|
4122
|
-
let
|
|
4123
|
-
let hasLegacyEntry = false;
|
|
4124
|
-
let legacyEntryName = null;
|
|
4125
|
-
let malformed = false;
|
|
4284
|
+
let classified = { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: false };
|
|
4126
4285
|
if (exists3) {
|
|
4127
4286
|
try {
|
|
4128
4287
|
await stat3(resolved.absolute);
|
|
4129
4288
|
const raw = await readFile7(resolved.absolute, "utf8");
|
|
4130
|
-
|
|
4131
|
-
const parsed = parseJsonc(raw);
|
|
4132
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
4133
|
-
const container = walkContainer(parsed, resolved.containerPath);
|
|
4134
|
-
if (container) {
|
|
4135
|
-
hasMcpEntry = ENTRY_NAME in container;
|
|
4136
|
-
legacyEntryName = findLegacyEntry(container);
|
|
4137
|
-
hasLegacyEntry = legacyEntryName !== null;
|
|
4138
|
-
}
|
|
4139
|
-
} else {
|
|
4140
|
-
malformed = true;
|
|
4141
|
-
}
|
|
4142
|
-
}
|
|
4289
|
+
classified = classifyProbeContent(raw, resolved.containerPath);
|
|
4143
4290
|
} catch {
|
|
4144
|
-
|
|
4291
|
+
classified = { hasMcpEntry: false, hasLegacyEntry: false, legacyEntryName: null, malformed: true };
|
|
4145
4292
|
}
|
|
4146
4293
|
}
|
|
4147
4294
|
result.push({
|
|
@@ -4149,10 +4296,7 @@ async function probeClientsAsync(opts) {
|
|
|
4149
4296
|
scope: scope.scope,
|
|
4150
4297
|
path: resolved.absolute,
|
|
4151
4298
|
exists: exists3,
|
|
4152
|
-
|
|
4153
|
-
hasLegacyEntry,
|
|
4154
|
-
legacyEntryName,
|
|
4155
|
-
malformed,
|
|
4299
|
+
...classified,
|
|
4156
4300
|
unavailable: false
|
|
4157
4301
|
});
|
|
4158
4302
|
}
|
|
@@ -4284,8 +4428,10 @@ import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
|
4284
4428
|
import { homedir as homedir10 } from "os";
|
|
4285
4429
|
import path3 from "path";
|
|
4286
4430
|
|
|
4287
|
-
// src/foundry
|
|
4288
|
-
import {
|
|
4431
|
+
// src/foundry.ts
|
|
4432
|
+
import { appendFile, mkdir as mkdir3, stat as stat4 } from "fs/promises";
|
|
4433
|
+
import { homedir as homedir9 } from "os";
|
|
4434
|
+
import path2 from "path";
|
|
4289
4435
|
|
|
4290
4436
|
// src/relevance.ts
|
|
4291
4437
|
var K1 = 1.2;
|
|
@@ -4404,7 +4550,64 @@ function rankServers(context, servers) {
|
|
|
4404
4550
|
return results;
|
|
4405
4551
|
}
|
|
4406
4552
|
|
|
4553
|
+
// src/foundry.ts
|
|
4554
|
+
var SECRET_PREFIXES = ["sk_", "sk-", "tok_", "ghp_", "gho_", "xox", "pk_", "akia"];
|
|
4555
|
+
function looksSensitive(token5) {
|
|
4556
|
+
for (const prefix of SECRET_PREFIXES) {
|
|
4557
|
+
if (token5.startsWith(prefix)) return true;
|
|
4558
|
+
}
|
|
4559
|
+
if (token5.length >= 16 && /^[0-9a-f]+$/.test(token5)) return true;
|
|
4560
|
+
if (token5.length >= 12 && /[a-z]/.test(token5) && /[0-9]/.test(token5)) return true;
|
|
4561
|
+
if (token5.length >= 16 && /^[a-z]+$/.test(token5)) return true;
|
|
4562
|
+
return false;
|
|
4563
|
+
}
|
|
4564
|
+
function redactIntent(intent) {
|
|
4565
|
+
const all = tokenize(intent);
|
|
4566
|
+
const tokens = [];
|
|
4567
|
+
let redactedCount = 0;
|
|
4568
|
+
for (const token5 of all) {
|
|
4569
|
+
if (looksSensitive(token5)) {
|
|
4570
|
+
redactedCount++;
|
|
4571
|
+
} else {
|
|
4572
|
+
tokens.push(token5);
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
tokens.sort();
|
|
4576
|
+
return { tokens, redactedCount };
|
|
4577
|
+
}
|
|
4578
|
+
function isFoundryEnabled() {
|
|
4579
|
+
const raw = process.env.YAW_MCP_FOUNDRY;
|
|
4580
|
+
if (!raw) return false;
|
|
4581
|
+
const v = raw.trim().toLowerCase();
|
|
4582
|
+
return v === "1" || v === "true";
|
|
4583
|
+
}
|
|
4584
|
+
var MAX_FOUNDRY_BYTES = 5 * 1024 * 1024;
|
|
4585
|
+
var FOUNDRY_FILENAME = "foundry.jsonl";
|
|
4586
|
+
async function appendFoundryTrace(trace, home = homedir9()) {
|
|
4587
|
+
try {
|
|
4588
|
+
if (!isFoundryEnabled()) return;
|
|
4589
|
+
const dir = userConfigDir(home);
|
|
4590
|
+
const file = path2.join(dir, FOUNDRY_FILENAME);
|
|
4591
|
+
try {
|
|
4592
|
+
const info = await stat4(file);
|
|
4593
|
+
if (info.size >= MAX_FOUNDRY_BYTES) return;
|
|
4594
|
+
} catch {
|
|
4595
|
+
}
|
|
4596
|
+
const line = `${JSON.stringify({
|
|
4597
|
+
tokens: trace.tokens,
|
|
4598
|
+
candidates: trace.candidates,
|
|
4599
|
+
chosen: trace.chosen,
|
|
4600
|
+
redactedCount: trace.redactedCount
|
|
4601
|
+
})}
|
|
4602
|
+
`;
|
|
4603
|
+
await mkdir3(dir, { recursive: true });
|
|
4604
|
+
await appendFile(file, line, "utf8");
|
|
4605
|
+
} catch {
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
|
|
4407
4609
|
// src/foundry-corpus.ts
|
|
4610
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
4408
4611
|
var FOUNDRY_CORPUS_VERSION = 1;
|
|
4409
4612
|
var DEFAULT_CORPUS_CAP = 500;
|
|
4410
4613
|
function parseTraceLines(text) {
|
|
@@ -4485,65 +4688,6 @@ function scoreCorpus(corpus) {
|
|
|
4485
4688
|
};
|
|
4486
4689
|
}
|
|
4487
4690
|
|
|
4488
|
-
// src/foundry.ts
|
|
4489
|
-
import { appendFile, mkdir as mkdir3, stat as stat4 } from "fs/promises";
|
|
4490
|
-
import { homedir as homedir9 } from "os";
|
|
4491
|
-
import path2 from "path";
|
|
4492
|
-
var SECRET_PREFIXES = ["sk_", "sk-", "tok_", "ghp_", "gho_", "xox", "pk_", "akia"];
|
|
4493
|
-
function looksSensitive(token5) {
|
|
4494
|
-
for (const prefix of SECRET_PREFIXES) {
|
|
4495
|
-
if (token5.startsWith(prefix)) return true;
|
|
4496
|
-
}
|
|
4497
|
-
if (token5.length >= 16 && /^[0-9a-f]+$/.test(token5)) return true;
|
|
4498
|
-
if (token5.length >= 12 && /[a-z]/.test(token5) && /[0-9]/.test(token5)) return true;
|
|
4499
|
-
if (token5.length >= 16 && /^[a-z]+$/.test(token5)) return true;
|
|
4500
|
-
return false;
|
|
4501
|
-
}
|
|
4502
|
-
function redactIntent(intent) {
|
|
4503
|
-
const all = tokenize(intent);
|
|
4504
|
-
const tokens = [];
|
|
4505
|
-
let redactedCount = 0;
|
|
4506
|
-
for (const token5 of all) {
|
|
4507
|
-
if (looksSensitive(token5)) {
|
|
4508
|
-
redactedCount++;
|
|
4509
|
-
} else {
|
|
4510
|
-
tokens.push(token5);
|
|
4511
|
-
}
|
|
4512
|
-
}
|
|
4513
|
-
tokens.sort();
|
|
4514
|
-
return { tokens, redactedCount };
|
|
4515
|
-
}
|
|
4516
|
-
function isFoundryEnabled() {
|
|
4517
|
-
const raw = process.env.YAW_MCP_FOUNDRY;
|
|
4518
|
-
if (!raw) return false;
|
|
4519
|
-
const v = raw.trim().toLowerCase();
|
|
4520
|
-
return v === "1" || v === "true";
|
|
4521
|
-
}
|
|
4522
|
-
var MAX_FOUNDRY_BYTES = 5 * 1024 * 1024;
|
|
4523
|
-
var FOUNDRY_FILENAME = "foundry.jsonl";
|
|
4524
|
-
async function appendFoundryTrace(trace, home = homedir9()) {
|
|
4525
|
-
try {
|
|
4526
|
-
if (!isFoundryEnabled()) return;
|
|
4527
|
-
const dir = userConfigDir(home);
|
|
4528
|
-
const file = path2.join(dir, FOUNDRY_FILENAME);
|
|
4529
|
-
try {
|
|
4530
|
-
const info = await stat4(file);
|
|
4531
|
-
if (info.size >= MAX_FOUNDRY_BYTES) return;
|
|
4532
|
-
} catch {
|
|
4533
|
-
}
|
|
4534
|
-
const line = `${JSON.stringify({
|
|
4535
|
-
tokens: trace.tokens,
|
|
4536
|
-
candidates: trace.candidates,
|
|
4537
|
-
chosen: trace.chosen,
|
|
4538
|
-
redactedCount: trace.redactedCount
|
|
4539
|
-
})}
|
|
4540
|
-
`;
|
|
4541
|
-
await mkdir3(dir, { recursive: true });
|
|
4542
|
-
await appendFile(file, line, "utf8");
|
|
4543
|
-
} catch {
|
|
4544
|
-
}
|
|
4545
|
-
}
|
|
4546
|
-
|
|
4547
4691
|
// src/foundry-cmd.ts
|
|
4548
4692
|
var DEFAULT_OUT = path3.join("src", "tests", "fixtures", "foundry-corpus.json");
|
|
4549
4693
|
var FOUNDRY_USAGE = `Usage: yaw-mcp foundry export [--out <path>] [--cap <n>] [--json]
|
|
@@ -4676,60 +4820,6 @@ async function runFoundryExport(opts) {
|
|
|
4676
4820
|
return { exitCode: 0, lines };
|
|
4677
4821
|
}
|
|
4678
4822
|
|
|
4679
|
-
// src/fuzzy.ts
|
|
4680
|
-
function levenshtein(a, b) {
|
|
4681
|
-
if (a === b) return 0;
|
|
4682
|
-
const aLen = a.length;
|
|
4683
|
-
const bLen = b.length;
|
|
4684
|
-
if (aLen === 0) return bLen;
|
|
4685
|
-
if (bLen === 0) return aLen;
|
|
4686
|
-
let prev = new Array(bLen + 1);
|
|
4687
|
-
let curr = new Array(bLen + 1);
|
|
4688
|
-
for (let j = 0; j <= bLen; j++) prev[j] = j;
|
|
4689
|
-
for (let i = 1; i <= aLen; i++) {
|
|
4690
|
-
curr[0] = i;
|
|
4691
|
-
for (let j = 1; j <= bLen; j++) {
|
|
4692
|
-
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
4693
|
-
curr[j] = Math.min(
|
|
4694
|
-
curr[j - 1] + 1,
|
|
4695
|
-
// insertion
|
|
4696
|
-
prev[j] + 1,
|
|
4697
|
-
// deletion
|
|
4698
|
-
prev[j - 1] + cost
|
|
4699
|
-
// substitution
|
|
4700
|
-
);
|
|
4701
|
-
}
|
|
4702
|
-
[prev, curr] = [curr, prev];
|
|
4703
|
-
}
|
|
4704
|
-
return prev[bLen];
|
|
4705
|
-
}
|
|
4706
|
-
function closestNames(query, candidates, limit) {
|
|
4707
|
-
if (limit <= 0) return [];
|
|
4708
|
-
const q = query.toLowerCase();
|
|
4709
|
-
const scored = [];
|
|
4710
|
-
for (const c of candidates) {
|
|
4711
|
-
if (c === query) continue;
|
|
4712
|
-
const lc = c.toLowerCase();
|
|
4713
|
-
let score = null;
|
|
4714
|
-
if (lc === q) {
|
|
4715
|
-
score = 0;
|
|
4716
|
-
} else if (lc.startsWith(q) || q.startsWith(lc)) {
|
|
4717
|
-
score = 1;
|
|
4718
|
-
} else if (lc.includes(q) || q.includes(lc)) {
|
|
4719
|
-
score = 2;
|
|
4720
|
-
} else {
|
|
4721
|
-
const d = levenshtein(q, lc);
|
|
4722
|
-
if (d <= 2) score = 2 + d;
|
|
4723
|
-
}
|
|
4724
|
-
if (score !== null) scored.push({ name: c, score });
|
|
4725
|
-
}
|
|
4726
|
-
scored.sort((a, b) => {
|
|
4727
|
-
if (a.score !== b.score) return a.score - b.score;
|
|
4728
|
-
return a.name.localeCompare(b.name);
|
|
4729
|
-
});
|
|
4730
|
-
return scored.slice(0, limit).map((s) => s.name);
|
|
4731
|
-
}
|
|
4732
|
-
|
|
4733
4823
|
// src/local-add-cmd.ts
|
|
4734
4824
|
import { homedir as homedir11 } from "os";
|
|
4735
4825
|
var SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
@@ -4747,7 +4837,7 @@ var ADD_USAGE = `Usage: yaw-mcp add <slug> [flags]
|
|
|
4747
4837
|
--json Emit the written entry as JSON (implies success on stdout).
|
|
4748
4838
|
--catalog <url> Override the catalog URL (default the public catalog).`;
|
|
4749
4839
|
function parseEnvFlag(v, bag) {
|
|
4750
|
-
if (!v
|
|
4840
|
+
if (!v?.includes("=")) return "--env requires KEY=value";
|
|
4751
4841
|
const eq = v.indexOf("=");
|
|
4752
4842
|
const key = v.slice(0, eq);
|
|
4753
4843
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return `--env: invalid KEY "${key}"`;
|
|
@@ -4776,7 +4866,7 @@ function parseAddArgs(argv) {
|
|
|
4776
4866
|
break;
|
|
4777
4867
|
case "--catalog": {
|
|
4778
4868
|
const v = next();
|
|
4779
|
-
if (!v) return { ok: false, error: "--catalog requires a URL" };
|
|
4869
|
+
if (!v || v.startsWith("--")) return { ok: false, error: "--catalog requires a URL" };
|
|
4780
4870
|
opts.catalogUrl = v;
|
|
4781
4871
|
break;
|
|
4782
4872
|
}
|
|
@@ -4828,7 +4918,7 @@ async function runAdd(opts) {
|
|
|
4828
4918
|
}
|
|
4829
4919
|
const namespace = deriveNamespace(server.name);
|
|
4830
4920
|
const supplied = { ...env, ...opts.envOverrides ?? {} };
|
|
4831
|
-
const missing = server.requiredEnvKeys.filter((k) =>
|
|
4921
|
+
const missing = server.requiredEnvKeys.filter((k) => (supplied[k] ?? "").trim() === "");
|
|
4832
4922
|
if (missing.length > 0) {
|
|
4833
4923
|
printErr(`yaw-mcp add: ${server.name} needs the following env var(s) before it can run:`);
|
|
4834
4924
|
for (const k of missing) printErr(` - ${k}`);
|
|
@@ -4840,7 +4930,11 @@ async function runAdd(opts) {
|
|
|
4840
4930
|
}
|
|
4841
4931
|
const entryEnv = {};
|
|
4842
4932
|
for (const k of server.requiredEnvKeys) entryEnv[k] = "";
|
|
4843
|
-
for (const [k, v] of Object.entries(opts.envOverrides ?? {}))
|
|
4933
|
+
for (const [k, v] of Object.entries(opts.envOverrides ?? {})) {
|
|
4934
|
+
const trimmed = v.trim();
|
|
4935
|
+
if (trimmed === "") continue;
|
|
4936
|
+
entryEnv[k] = trimmed;
|
|
4937
|
+
}
|
|
4844
4938
|
const overrides = opts.envOverrides ?? {};
|
|
4845
4939
|
const ambientOnlyRequired = server.requiredEnvKeys.filter(
|
|
4846
4940
|
(k) => (!overrides[k] || overrides[k] === "") && env[k] != null && env[k] !== ""
|
|
@@ -4947,7 +5041,7 @@ async function runRemove(opts) {
|
|
|
4947
5041
|
printErr(`yaw-mcp remove: ${e.message}`);
|
|
4948
5042
|
return { exitCode: 1, written: [] };
|
|
4949
5043
|
}
|
|
4950
|
-
if (!res
|
|
5044
|
+
if (!res?.removed) {
|
|
4951
5045
|
print(`yaw-mcp remove: no server matching "${opts.target}" in ${res?.path ?? "bundles.json"} (nothing to do).`);
|
|
4952
5046
|
const shadow2 = await findShadowingProjectBundles(cwd, home).catch(() => null);
|
|
4953
5047
|
if (shadow2) {
|
|
@@ -5037,9 +5131,11 @@ function parseLoginArgs(argv) {
|
|
|
5037
5131
|
const a = argv[i];
|
|
5038
5132
|
if (a === "--key") {
|
|
5039
5133
|
const v = argv[++i];
|
|
5040
|
-
if (!v
|
|
5134
|
+
if (!v || v.startsWith("-")) {
|
|
5135
|
+
return { ok: false, error: `yaw-mcp login: --key requires a value
|
|
5041
5136
|
|
|
5042
5137
|
${LOGIN_USAGE}` };
|
|
5138
|
+
}
|
|
5043
5139
|
opts.key = v;
|
|
5044
5140
|
} else if (a === "--json") {
|
|
5045
5141
|
opts.json = true;
|
|
@@ -5142,7 +5238,7 @@ async function runLogout(opts = {}, io = {
|
|
|
5142
5238
|
}
|
|
5143
5239
|
|
|
5144
5240
|
// src/reset-learning-cmd.ts
|
|
5145
|
-
import { unlink as unlink2 } from "fs/promises";
|
|
5241
|
+
import { readFile as readFile8, unlink as unlink2 } from "fs/promises";
|
|
5146
5242
|
import { homedir as homedir12 } from "os";
|
|
5147
5243
|
import { join as join9 } from "path";
|
|
5148
5244
|
var RESET_LEARNING_USAGE = `Usage: yaw-mcp reset-learning
|
|
@@ -5154,16 +5250,15 @@ var RESET_LEARNING_USAGE = `Usage: yaw-mcp reset-learning
|
|
|
5154
5250
|
|
|
5155
5251
|
-h, --help Show this help.`;
|
|
5156
5252
|
function parseResetLearningArgs(argv) {
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5253
|
+
if (argv.length === 0) return { kind: "ok", options: {} };
|
|
5254
|
+
const first = argv[0];
|
|
5255
|
+
if (first === "-h" || first === "--help") return { kind: "help" };
|
|
5256
|
+
return {
|
|
5257
|
+
kind: "error",
|
|
5258
|
+
error: `yaw-mcp reset-learning: unknown argument "${first}"
|
|
5162
5259
|
|
|
5163
5260
|
${RESET_LEARNING_USAGE}`
|
|
5164
|
-
|
|
5165
|
-
}
|
|
5166
|
-
return { kind: "ok", options: {} };
|
|
5261
|
+
};
|
|
5167
5262
|
}
|
|
5168
5263
|
async function runResetLearning(opts = {}) {
|
|
5169
5264
|
const home = opts.home ?? homedir12();
|
|
@@ -5191,6 +5286,7 @@ async function runResetLearning(opts = {}) {
|
|
|
5191
5286
|
const persisted = await loadState(filePath);
|
|
5192
5287
|
const learningCount = Object.keys(persisted.learning).length;
|
|
5193
5288
|
const packCount = persisted.packHistory.length;
|
|
5289
|
+
const parsedCleanly = await peekParsedCleanly(filePath);
|
|
5194
5290
|
try {
|
|
5195
5291
|
await unlink2(filePath);
|
|
5196
5292
|
} catch (err) {
|
|
@@ -5203,12 +5299,33 @@ async function runResetLearning(opts = {}) {
|
|
|
5203
5299
|
printErr(`yaw-mcp reset-learning: failed to remove ${filePath}: ${msg}`);
|
|
5204
5300
|
return { exitCode: 1, lines, removed: false, path: filePath };
|
|
5205
5301
|
}
|
|
5302
|
+
if (!parsedCleanly) {
|
|
5303
|
+
print("yaw-mcp reset-learning: cleared persisted state (contents unreadable).");
|
|
5304
|
+
print(` path: ${filePath}`);
|
|
5305
|
+
return { exitCode: 0, lines, removed: true, path: filePath };
|
|
5306
|
+
}
|
|
5206
5307
|
print("yaw-mcp reset-learning: cleared persisted state.");
|
|
5207
5308
|
print(` path: ${filePath}`);
|
|
5208
5309
|
print(` learning entries removed: ${learningCount}`);
|
|
5209
5310
|
print(` pack history entries removed: ${packCount}`);
|
|
5210
5311
|
return { exitCode: 0, lines, removed: true, path: filePath };
|
|
5211
5312
|
}
|
|
5313
|
+
async function peekParsedCleanly(filePath) {
|
|
5314
|
+
let raw;
|
|
5315
|
+
try {
|
|
5316
|
+
raw = await readFile8(filePath, "utf8");
|
|
5317
|
+
} catch (err) {
|
|
5318
|
+
if (isFileNotFound2(err)) return true;
|
|
5319
|
+
return false;
|
|
5320
|
+
}
|
|
5321
|
+
try {
|
|
5322
|
+
const parsed = JSON.parse(raw);
|
|
5323
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
5324
|
+
return parsed.version === STATE_SCHEMA_VERSION;
|
|
5325
|
+
} catch {
|
|
5326
|
+
return false;
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5212
5329
|
function isFileNotFound2(err) {
|
|
5213
5330
|
return !!err && typeof err === "object" && "code" in err && err.code === "ENOENT";
|
|
5214
5331
|
}
|
|
@@ -5219,7 +5336,7 @@ import { homedir as homedir14 } from "os";
|
|
|
5219
5336
|
|
|
5220
5337
|
// src/secrets-vault.ts
|
|
5221
5338
|
import { existsSync as existsSync5 } from "fs";
|
|
5222
|
-
import { chmod as chmod3, readFile as
|
|
5339
|
+
import { chmod as chmod3, readFile as readFile9 } from "fs/promises";
|
|
5223
5340
|
import { homedir as homedir13 } from "os";
|
|
5224
5341
|
import { join as join10 } from "path";
|
|
5225
5342
|
|
|
@@ -5294,7 +5411,7 @@ async function loadVault(path5) {
|
|
|
5294
5411
|
if (!existsSync5(path5)) return null;
|
|
5295
5412
|
let raw;
|
|
5296
5413
|
try {
|
|
5297
|
-
raw = await
|
|
5414
|
+
raw = await readFile9(path5, "utf8");
|
|
5298
5415
|
} catch (err) {
|
|
5299
5416
|
log("warn", "Failed to read vault", { path: path5, error: err instanceof Error ? err.message : String(err) });
|
|
5300
5417
|
return null;
|
|
@@ -5330,7 +5447,7 @@ function isEncryptedEntry(v) {
|
|
|
5330
5447
|
}
|
|
5331
5448
|
async function saveVault(path5, vault) {
|
|
5332
5449
|
await atomicWriteFile(path5, `${JSON.stringify(vault, null, 2)}
|
|
5333
|
-
|
|
5450
|
+
`, "utf8", 384);
|
|
5334
5451
|
if (process.platform !== "win32") {
|
|
5335
5452
|
try {
|
|
5336
5453
|
await chmod3(path5, 384);
|
|
@@ -5443,6 +5560,10 @@ Actions:
|
|
|
5443
5560
|
line, no echo). Override with --value <v> or
|
|
5444
5561
|
--stdin (raw, multi-line) for scripting.
|
|
5445
5562
|
get <name> Decrypt and print one secret value to stdout.
|
|
5563
|
+
NOTE: this prints the secret in CLEARTEXT (with
|
|
5564
|
+
or without --json). Redirect to a file or pipe
|
|
5565
|
+
to a consumer; avoid running it interactively so
|
|
5566
|
+
the value does not land in terminal scrollback.
|
|
5446
5567
|
list Show vault entry names (values stay encrypted).
|
|
5447
5568
|
remove <name> Delete an entry.
|
|
5448
5569
|
lock Clear the in-process passphrase cache.
|
|
@@ -5487,9 +5608,14 @@ function parseSecretsArgs(argv) {
|
|
|
5487
5608
|
}
|
|
5488
5609
|
if (a === "--value") {
|
|
5489
5610
|
const v = argv[++i];
|
|
5490
|
-
if (v === void 0
|
|
5611
|
+
if (v === void 0 || v.startsWith("-")) {
|
|
5612
|
+
return {
|
|
5613
|
+
ok: false,
|
|
5614
|
+
error: `yaw-mcp secrets: --value requires a value (for a dash-leading value use --stdin)
|
|
5491
5615
|
|
|
5492
|
-
${SECRETS_USAGE}`
|
|
5616
|
+
${SECRETS_USAGE}`
|
|
5617
|
+
};
|
|
5618
|
+
}
|
|
5493
5619
|
opts.value = v;
|
|
5494
5620
|
continue;
|
|
5495
5621
|
}
|
|
@@ -5564,7 +5690,7 @@ function readPassphraseFromTTY(stdin, stdout, prompt = "Vault passphrase: ") {
|
|
|
5564
5690
|
stdin.setEncoding("utf8");
|
|
5565
5691
|
const onData = (chunk) => {
|
|
5566
5692
|
for (const ch of chunk) {
|
|
5567
|
-
if (ch === "\n" || ch === "\r"
|
|
5693
|
+
if (ch === "\n" || ch === "\r") {
|
|
5568
5694
|
stdout.write("\n");
|
|
5569
5695
|
stdin.removeListener("data", onData);
|
|
5570
5696
|
try {
|
|
@@ -5575,6 +5701,17 @@ function readPassphraseFromTTY(stdin, stdout, prompt = "Vault passphrase: ") {
|
|
|
5575
5701
|
resolve7(chunks.join(""));
|
|
5576
5702
|
return;
|
|
5577
5703
|
}
|
|
5704
|
+
if (ch === "") {
|
|
5705
|
+
stdout.write("\n");
|
|
5706
|
+
stdin.removeListener("data", onData);
|
|
5707
|
+
try {
|
|
5708
|
+
stdin.setRawMode?.(wasRaw);
|
|
5709
|
+
} catch {
|
|
5710
|
+
}
|
|
5711
|
+
stdin.pause();
|
|
5712
|
+
resolve7("");
|
|
5713
|
+
return;
|
|
5714
|
+
}
|
|
5578
5715
|
if (ch === "") {
|
|
5579
5716
|
stdout.write("\n");
|
|
5580
5717
|
process.exit(130);
|
|
@@ -5589,11 +5726,11 @@ function readPassphraseFromTTY(stdin, stdout, prompt = "Vault passphrase: ") {
|
|
|
5589
5726
|
stdin.on("data", onData);
|
|
5590
5727
|
});
|
|
5591
5728
|
}
|
|
5592
|
-
async function readStdinValue(io) {
|
|
5729
|
+
async function readStdinValue(io, forceRaw) {
|
|
5593
5730
|
const stdin = io?.stdin ?? process.stdin;
|
|
5594
5731
|
const stdout = io?.stdout ?? process.stdout;
|
|
5595
5732
|
const isTTY = stdin.isTTY === true;
|
|
5596
|
-
if (isTTY) {
|
|
5733
|
+
if (isTTY && !forceRaw) {
|
|
5597
5734
|
stdout.write("Secret value: ");
|
|
5598
5735
|
return readPassphraseFromTTY(stdin, stdout);
|
|
5599
5736
|
}
|
|
@@ -5638,6 +5775,18 @@ async function runSecrets(opts, io = {
|
|
|
5638
5775
|
}
|
|
5639
5776
|
return { exitCode: 0 };
|
|
5640
5777
|
}
|
|
5778
|
+
if (opts.action === "get" || opts.action === "remove") {
|
|
5779
|
+
const existingVault = await loadVault(path5);
|
|
5780
|
+
if (!existingVault || !(opts.name in existingVault.entries)) {
|
|
5781
|
+
const name = opts.name;
|
|
5782
|
+
const msg = `No secret named "${name}" in the vault.`;
|
|
5783
|
+
if (opts.json) io.err(`${JSON.stringify({ ok: false, error: msg })}
|
|
5784
|
+
`);
|
|
5785
|
+
else io.err(`yaw-mcp secrets: ${msg}
|
|
5786
|
+
`);
|
|
5787
|
+
return { exitCode: 1 };
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5641
5790
|
let vault = await loadVault(path5) ?? newVault();
|
|
5642
5791
|
const isFresh = !existsSync6(path5);
|
|
5643
5792
|
const passphrase = await resolvePassphrase(opts);
|
|
@@ -5664,7 +5813,7 @@ async function runSecrets(opts, io = {
|
|
|
5664
5813
|
const name = opts.name;
|
|
5665
5814
|
let value;
|
|
5666
5815
|
if (opts.value !== void 0) value = opts.value;
|
|
5667
|
-
else value = await readStdinValue(opts.io);
|
|
5816
|
+
else value = await readStdinValue(opts.io, opts.fromStdin);
|
|
5668
5817
|
if (!value) {
|
|
5669
5818
|
const msg = "Secret value cannot be empty.";
|
|
5670
5819
|
if (opts.json) io.err(`${JSON.stringify({ ok: false, error: msg })}
|
|
@@ -5693,6 +5842,14 @@ async function runSecrets(opts, io = {
|
|
|
5693
5842
|
`);
|
|
5694
5843
|
return { exitCode: 1 };
|
|
5695
5844
|
}
|
|
5845
|
+
const outStream = opts.io?.stdout ?? process.stdout;
|
|
5846
|
+
if (outStream.isTTY === true) {
|
|
5847
|
+
const stderr = opts.io?.stderr ?? process.stderr;
|
|
5848
|
+
stderr.write(
|
|
5849
|
+
`yaw-mcp secrets: warning -- printing "${name}" in cleartext to your terminal; it will remain in scrollback.
|
|
5850
|
+
`
|
|
5851
|
+
);
|
|
5852
|
+
}
|
|
5696
5853
|
if (opts.json) io.out(`${JSON.stringify({ ok: true, name, value })}
|
|
5697
5854
|
`);
|
|
5698
5855
|
else io.out(`${value}
|
|
@@ -5811,9 +5968,9 @@ async function runSecretsPull(opts, io) {
|
|
|
5811
5968
|
const remote = await getResource(MCP_SECRETS_RESOURCE, { home, baseUrl: opts.baseUrl });
|
|
5812
5969
|
const remoteEntries = remote.data?.entries;
|
|
5813
5970
|
const remoteHasEntries = remoteEntries !== void 0 && remoteEntries !== null && typeof remoteEntries === "object" && Object.keys(remoteEntries).length > 0;
|
|
5814
|
-
if (!remote.data
|
|
5971
|
+
if (!remote.data?.salt || !remoteHasEntries) {
|
|
5815
5972
|
const msg = "Remote mcp_secrets is empty. Push from this machine to seed it.";
|
|
5816
|
-
if (opts.json) io.out(`${JSON.stringify({ ok: true, empty: true })}
|
|
5973
|
+
if (opts.json) io.out(`${JSON.stringify({ ok: true, empty: true, message: msg })}
|
|
5817
5974
|
`);
|
|
5818
5975
|
else io.out(`${msg}
|
|
5819
5976
|
`);
|
|
@@ -5864,7 +6021,7 @@ async function runSecretsPull(opts, io) {
|
|
|
5864
6021
|
}
|
|
5865
6022
|
|
|
5866
6023
|
// src/server.ts
|
|
5867
|
-
import { readFile as
|
|
6024
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
5868
6025
|
import { homedir as homedir15 } from "os";
|
|
5869
6026
|
import { isAbsolute as isAbsolute2, relative, resolve as resolve6 } from "path";
|
|
5870
6027
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -5930,7 +6087,7 @@ function defaultSpawn2(cmd, args) {
|
|
|
5930
6087
|
async function maybeAutoUpgrade(deps = {}) {
|
|
5931
6088
|
const optOut = process.env.YAW_MCP_AUTO_UPGRADE;
|
|
5932
6089
|
if (optOut === "0" || optOut?.toLowerCase() === "false") return;
|
|
5933
|
-
const current = deps.currentVersion ?? (true ? "0.
|
|
6090
|
+
const current = deps.currentVersion ?? (true ? "0.63.1" : "dev");
|
|
5934
6091
|
if (current === "dev") return;
|
|
5935
6092
|
const method = (deps.isSeaImpl ? await deps.isSeaImpl() : await detectSea()) ? "binary" : detectInstallMethod(deps.argvPath ?? process.argv[1]);
|
|
5936
6093
|
const latest = await (deps.fetchLatestImpl ?? fetchLatestVersion2)();
|
|
@@ -6365,14 +6522,76 @@ function stepBindingKey(step, index) {
|
|
|
6365
6522
|
return typeof step.id === "string" && step.id.length > 0 ? step.id : String(index);
|
|
6366
6523
|
}
|
|
6367
6524
|
|
|
6525
|
+
// src/fuzzy.ts
|
|
6526
|
+
function levenshtein(a, b) {
|
|
6527
|
+
if (a === b) return 0;
|
|
6528
|
+
const aLen = a.length;
|
|
6529
|
+
const bLen = b.length;
|
|
6530
|
+
if (aLen === 0) return bLen;
|
|
6531
|
+
if (bLen === 0) return aLen;
|
|
6532
|
+
let prev = new Array(bLen + 1);
|
|
6533
|
+
let curr = new Array(bLen + 1);
|
|
6534
|
+
for (let j = 0; j <= bLen; j++) prev[j] = j;
|
|
6535
|
+
for (let i = 1; i <= aLen; i++) {
|
|
6536
|
+
curr[0] = i;
|
|
6537
|
+
for (let j = 1; j <= bLen; j++) {
|
|
6538
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
6539
|
+
curr[j] = Math.min(
|
|
6540
|
+
curr[j - 1] + 1,
|
|
6541
|
+
// insertion
|
|
6542
|
+
prev[j] + 1,
|
|
6543
|
+
// deletion
|
|
6544
|
+
prev[j - 1] + cost
|
|
6545
|
+
// substitution
|
|
6546
|
+
);
|
|
6547
|
+
}
|
|
6548
|
+
[prev, curr] = [curr, prev];
|
|
6549
|
+
}
|
|
6550
|
+
return prev[bLen];
|
|
6551
|
+
}
|
|
6552
|
+
function closestNames(query, candidates, limit) {
|
|
6553
|
+
if (limit <= 0) return [];
|
|
6554
|
+
const q = query.toLowerCase();
|
|
6555
|
+
const scored = [];
|
|
6556
|
+
for (const c of candidates) {
|
|
6557
|
+
if (c === query) continue;
|
|
6558
|
+
const lc = c.toLowerCase();
|
|
6559
|
+
let score = null;
|
|
6560
|
+
if (lc === q) {
|
|
6561
|
+
score = 0;
|
|
6562
|
+
} else if (lc.startsWith(q) || q.startsWith(lc)) {
|
|
6563
|
+
score = 1;
|
|
6564
|
+
} else if (
|
|
6565
|
+
// Substring containment is only a credible "typo" signal when the
|
|
6566
|
+
// query is long enough to be specific AND the shorter string covers
|
|
6567
|
+
// at least half the longer one. Without these gates a 1-2 char query
|
|
6568
|
+
// ("ls", "set") substring-matches long commands ("list", "set-active",
|
|
6569
|
+
// "secrets") and surfaces misleading suggestions the header calls
|
|
6570
|
+
// conservative. The Levenshtein tier still catches genuine short typos.
|
|
6571
|
+
q.length >= 3 && (lc.includes(q) || q.includes(lc)) && Math.min(q.length, lc.length) * 2 >= Math.max(q.length, lc.length)
|
|
6572
|
+
) {
|
|
6573
|
+
score = 2;
|
|
6574
|
+
} else {
|
|
6575
|
+
const d = levenshtein(q, lc);
|
|
6576
|
+
if (d <= 2) score = 2 + d;
|
|
6577
|
+
}
|
|
6578
|
+
if (score !== null) scored.push({ name: c, score });
|
|
6579
|
+
}
|
|
6580
|
+
scored.sort((a, b) => {
|
|
6581
|
+
if (a.score !== b.score) return a.score - b.score;
|
|
6582
|
+
return a.name.localeCompare(b.name);
|
|
6583
|
+
});
|
|
6584
|
+
return scored.slice(0, limit).map((s) => s.name);
|
|
6585
|
+
}
|
|
6586
|
+
|
|
6368
6587
|
// src/guide.ts
|
|
6369
|
-
import { readFile as
|
|
6588
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
6370
6589
|
var GUIDE_READ_TIMEOUT_MS = 1e3;
|
|
6371
6590
|
async function readGuide(path5, scope) {
|
|
6372
6591
|
let raw;
|
|
6373
6592
|
try {
|
|
6374
6593
|
raw = await Promise.race([
|
|
6375
|
-
|
|
6594
|
+
readFile10(path5, "utf8"),
|
|
6376
6595
|
new Promise(
|
|
6377
6596
|
(_, reject) => setTimeout(() => reject(new Error("guide read timeout")), GUIDE_READ_TIMEOUT_MS)
|
|
6378
6597
|
)
|
|
@@ -7187,7 +7406,7 @@ var PackDetector = class {
|
|
|
7187
7406
|
loadSnapshot(snapshot) {
|
|
7188
7407
|
const clean = [];
|
|
7189
7408
|
for (const c of snapshot) {
|
|
7190
|
-
if (!c
|
|
7409
|
+
if (!c?.namespace || !c.toolName) continue;
|
|
7191
7410
|
clean.push({ namespace: c.namespace, toolName: c.toolName, at: c.at });
|
|
7192
7411
|
}
|
|
7193
7412
|
if (clean.length > this.maxHistory) {
|
|
@@ -7374,7 +7593,7 @@ async function routeResourceRead(uri, resourceRoutes, activeConnections, builtin
|
|
|
7374
7593
|
return { contents: [{ uri, text: `Unknown resource: ${uri}` }] };
|
|
7375
7594
|
}
|
|
7376
7595
|
const connection = activeConnections.get(route.namespace);
|
|
7377
|
-
if (
|
|
7596
|
+
if (connection?.status !== "connected") {
|
|
7378
7597
|
return { contents: [{ uri, text: `Server "${route.namespace}" is not connected.` }] };
|
|
7379
7598
|
}
|
|
7380
7599
|
try {
|
|
@@ -7392,7 +7611,7 @@ async function routePromptGet(name, args, promptRoutes, activeConnections) {
|
|
|
7392
7611
|
return { messages: [{ role: "user", content: { type: "text", text: `Unknown prompt: ${name}` } }] };
|
|
7393
7612
|
}
|
|
7394
7613
|
const connection = activeConnections.get(route.namespace);
|
|
7395
|
-
if (
|
|
7614
|
+
if (connection?.status !== "connected") {
|
|
7396
7615
|
return {
|
|
7397
7616
|
messages: [{ role: "user", content: { type: "text", text: `Server "${route.namespace}" is not connected.` } }]
|
|
7398
7617
|
};
|
|
@@ -7420,7 +7639,7 @@ async function routeToolCall(toolName, args, toolRoutes, activeConnections) {
|
|
|
7420
7639
|
};
|
|
7421
7640
|
}
|
|
7422
7641
|
const connection = activeConnections.get(route.namespace);
|
|
7423
|
-
if (
|
|
7642
|
+
if (connection?.status !== "connected") {
|
|
7424
7643
|
return {
|
|
7425
7644
|
content: [
|
|
7426
7645
|
{
|
|
@@ -7731,10 +7950,50 @@ async function callLegacyRerank(payload) {
|
|
|
7731
7950
|
}
|
|
7732
7951
|
}
|
|
7733
7952
|
async function readTeamCookie() {
|
|
7734
|
-
const teamSync = await import("./team-sync-
|
|
7953
|
+
const teamSync = await import("./team-sync-OONB72BJ.js");
|
|
7735
7954
|
return teamSync.getCachedCookie();
|
|
7736
7955
|
}
|
|
7737
7956
|
|
|
7957
|
+
// src/reward.ts
|
|
7958
|
+
var ERROR_SHAPED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
7959
|
+
"validation_error",
|
|
7960
|
+
"timeout",
|
|
7961
|
+
"unauthorized",
|
|
7962
|
+
"unknown_tool",
|
|
7963
|
+
"connection_lost",
|
|
7964
|
+
"rate_limited",
|
|
7965
|
+
"not_found"
|
|
7966
|
+
]);
|
|
7967
|
+
function firstTextBlock(result) {
|
|
7968
|
+
const content = result.content;
|
|
7969
|
+
if (!content || content.length === 0) return void 0;
|
|
7970
|
+
for (const block of content) {
|
|
7971
|
+
if (typeof block.text === "string" && block.text.trim().length > 0) return block.text;
|
|
7972
|
+
}
|
|
7973
|
+
return void 0;
|
|
7974
|
+
}
|
|
7975
|
+
function isEmptyBody(result) {
|
|
7976
|
+
const content = result.content;
|
|
7977
|
+
if (!content || content.length === 0) return true;
|
|
7978
|
+
for (const block of content) {
|
|
7979
|
+
if (typeof block.text === "string" && block.text.trim().length > 0) {
|
|
7980
|
+
return false;
|
|
7981
|
+
}
|
|
7982
|
+
}
|
|
7983
|
+
return true;
|
|
7984
|
+
}
|
|
7985
|
+
function computeOutcomeReward(result) {
|
|
7986
|
+
if (result.isError === true) return 0;
|
|
7987
|
+
const text = firstTextBlock(result);
|
|
7988
|
+
if (text !== void 0) {
|
|
7989
|
+
if (ERROR_SHAPED_CATEGORIES.has(classifyError(text))) {
|
|
7990
|
+
return 0.2;
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
if (isEmptyBody(result)) return 0.3;
|
|
7994
|
+
return 1;
|
|
7995
|
+
}
|
|
7996
|
+
|
|
7738
7997
|
// src/reward-grader.ts
|
|
7739
7998
|
function isRewardGraderEnabled() {
|
|
7740
7999
|
const raw = process.env.YAW_MCP_REWARD_GRADER;
|
|
@@ -7836,46 +8095,6 @@ function extractText(content) {
|
|
|
7836
8095
|
return "";
|
|
7837
8096
|
}
|
|
7838
8097
|
|
|
7839
|
-
// src/reward.ts
|
|
7840
|
-
var ERROR_SHAPED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
7841
|
-
"validation_error",
|
|
7842
|
-
"timeout",
|
|
7843
|
-
"unauthorized",
|
|
7844
|
-
"unknown_tool",
|
|
7845
|
-
"connection_lost",
|
|
7846
|
-
"rate_limited",
|
|
7847
|
-
"not_found"
|
|
7848
|
-
]);
|
|
7849
|
-
function firstTextBlock(result) {
|
|
7850
|
-
const content = result.content;
|
|
7851
|
-
if (!content || content.length === 0) return void 0;
|
|
7852
|
-
for (const block of content) {
|
|
7853
|
-
if (typeof block.text === "string" && block.text.trim().length > 0) return block.text;
|
|
7854
|
-
}
|
|
7855
|
-
return void 0;
|
|
7856
|
-
}
|
|
7857
|
-
function isEmptyBody(result) {
|
|
7858
|
-
const content = result.content;
|
|
7859
|
-
if (!content || content.length === 0) return true;
|
|
7860
|
-
for (const block of content) {
|
|
7861
|
-
if (typeof block.text === "string" && block.text.trim().length > 0) {
|
|
7862
|
-
return false;
|
|
7863
|
-
}
|
|
7864
|
-
}
|
|
7865
|
-
return true;
|
|
7866
|
-
}
|
|
7867
|
-
function computeOutcomeReward(result) {
|
|
7868
|
-
if (result.isError === true) return 0;
|
|
7869
|
-
const text = firstTextBlock(result);
|
|
7870
|
-
if (text !== void 0) {
|
|
7871
|
-
if (ERROR_SHAPED_CATEGORIES.has(classifyError(text))) {
|
|
7872
|
-
return 0.2;
|
|
7873
|
-
}
|
|
7874
|
-
}
|
|
7875
|
-
if (isEmptyBody(result)) return 0.3;
|
|
7876
|
-
return 1;
|
|
7877
|
-
}
|
|
7878
|
-
|
|
7879
8098
|
// src/runtime-detect.ts
|
|
7880
8099
|
import { spawn as spawn4 } from "child_process";
|
|
7881
8100
|
import { request as request8 } from "undici";
|
|
@@ -8194,7 +8413,9 @@ async function bestOfNViaSampling(server, intent, candidates, n) {
|
|
|
8194
8413
|
}
|
|
8195
8414
|
if (votes.size === 0) return null;
|
|
8196
8415
|
const order = /* @__PURE__ */ new Map();
|
|
8197
|
-
candidates.forEach((c, i) =>
|
|
8416
|
+
candidates.forEach((c, i) => {
|
|
8417
|
+
order.set(c.namespace, i);
|
|
8418
|
+
});
|
|
8198
8419
|
let winner = null;
|
|
8199
8420
|
let bestVotes = -1;
|
|
8200
8421
|
let bestRank = Number.POSITIVE_INFINITY;
|
|
@@ -8557,7 +8778,7 @@ function categorizeSpawnError(err) {
|
|
|
8557
8778
|
}
|
|
8558
8779
|
async function connectToUpstream(config, onDisconnect, onListChanged) {
|
|
8559
8780
|
const client = new Client(
|
|
8560
|
-
{ name: "yaw-mcp", version: true ? "0.
|
|
8781
|
+
{ name: "yaw-mcp", version: true ? "0.63.1" : "dev" },
|
|
8561
8782
|
{ capabilities: {} }
|
|
8562
8783
|
);
|
|
8563
8784
|
let transport;
|
|
@@ -8884,7 +9105,7 @@ var ConnectServer = class _ConnectServer {
|
|
|
8884
9105
|
this.apiUrl = apiUrl5;
|
|
8885
9106
|
this.token = token5;
|
|
8886
9107
|
this.server = new Server(
|
|
8887
|
-
{ name: "yaw-mcp", version: true ? "0.
|
|
9108
|
+
{ name: "yaw-mcp", version: true ? "0.63.1" : "dev" },
|
|
8888
9109
|
{
|
|
8889
9110
|
capabilities: {
|
|
8890
9111
|
tools: { listChanged: true },
|
|
@@ -10484,7 +10705,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
|
|
|
10484
10705
|
let changed = false;
|
|
10485
10706
|
for (const [namespace, connection] of this.connections) {
|
|
10486
10707
|
const newServerConfig = newServersByNs.get(namespace);
|
|
10487
|
-
if (!newServerConfig
|
|
10708
|
+
if (!newServerConfig?.isActive) {
|
|
10488
10709
|
log("info", "Server removed or disabled in config, deactivating", { namespace });
|
|
10489
10710
|
await disconnectFromUpstream(connection);
|
|
10490
10711
|
this.connections.delete(namespace);
|
|
@@ -10582,7 +10803,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
|
|
|
10582
10803
|
isError: true
|
|
10583
10804
|
};
|
|
10584
10805
|
}
|
|
10585
|
-
const raw = await
|
|
10806
|
+
const raw = await readFile11(resolved, "utf-8");
|
|
10586
10807
|
const parsed = JSON.parse(raw);
|
|
10587
10808
|
if (!parsed.mcpServers || typeof parsed.mcpServers !== "object" || Array.isArray(parsed.mcpServers)) {
|
|
10588
10809
|
return {
|
|
@@ -11386,7 +11607,7 @@ import { homedir as homedir16 } from "os";
|
|
|
11386
11607
|
|
|
11387
11608
|
// src/sync-state.ts
|
|
11388
11609
|
import { existsSync as existsSync7 } from "fs";
|
|
11389
|
-
import { mkdir as mkdir4, readFile as
|
|
11610
|
+
import { mkdir as mkdir4, readFile as readFile12 } from "fs/promises";
|
|
11390
11611
|
import { dirname as dirname2, join as join11 } from "path";
|
|
11391
11612
|
var SYNC_STATE_FILENAME = "sync-state.json";
|
|
11392
11613
|
function syncStatePath(home) {
|
|
@@ -11396,7 +11617,7 @@ async function readSyncState(home) {
|
|
|
11396
11617
|
const path5 = syncStatePath(home);
|
|
11397
11618
|
if (!existsSync7(path5)) return {};
|
|
11398
11619
|
try {
|
|
11399
|
-
const raw = await
|
|
11620
|
+
const raw = await readFile12(path5, "utf8");
|
|
11400
11621
|
const parsed = JSON.parse(raw);
|
|
11401
11622
|
if (!parsed || typeof parsed !== "object") return {};
|
|
11402
11623
|
return parsed;
|
|
@@ -11644,7 +11865,11 @@ function formatPlain(events, opts, orderId, total) {
|
|
|
11644
11865
|
lines.push(` ${c.client.padEnd(24)} ${c.total} calls`);
|
|
11645
11866
|
}
|
|
11646
11867
|
lines.push("");
|
|
11647
|
-
|
|
11868
|
+
if (events.length > renderedCount) {
|
|
11869
|
+
lines.push("Recent events (newest first, capped at --limit; By-server / By-AI-client above span the full window):");
|
|
11870
|
+
} else {
|
|
11871
|
+
lines.push("Recent events (newest first):");
|
|
11872
|
+
}
|
|
11648
11873
|
const recent = events.slice(-Math.min(events.length, opts.limit ?? 50)).reverse();
|
|
11649
11874
|
for (const e of recent) {
|
|
11650
11875
|
const when = new Date(e.ts).toISOString().replace("T", " ").slice(0, 19);
|
|
@@ -11715,9 +11940,45 @@ async function runStats(opts, io = {
|
|
|
11715
11940
|
}
|
|
11716
11941
|
}
|
|
11717
11942
|
|
|
11943
|
+
// src/subcommands.ts
|
|
11944
|
+
var FLAG_ALIASES = ["--help", "-h", "--version", "-V"];
|
|
11945
|
+
var KNOWN_SUBCOMMANDS = [
|
|
11946
|
+
"compliance",
|
|
11947
|
+
"audit",
|
|
11948
|
+
"foundry",
|
|
11949
|
+
"install",
|
|
11950
|
+
"add",
|
|
11951
|
+
"remove",
|
|
11952
|
+
"list",
|
|
11953
|
+
"doctor",
|
|
11954
|
+
"reset-learning",
|
|
11955
|
+
"servers",
|
|
11956
|
+
"bundles",
|
|
11957
|
+
"completion",
|
|
11958
|
+
"upgrade",
|
|
11959
|
+
"try",
|
|
11960
|
+
"try-cleanup",
|
|
11961
|
+
"login",
|
|
11962
|
+
"logout",
|
|
11963
|
+
"sync",
|
|
11964
|
+
"stats",
|
|
11965
|
+
"secrets",
|
|
11966
|
+
"set-active",
|
|
11967
|
+
"help",
|
|
11968
|
+
...FLAG_ALIASES
|
|
11969
|
+
];
|
|
11970
|
+
function suggestSubcommand(input, limit = 3) {
|
|
11971
|
+
const visible = KNOWN_SUBCOMMANDS.filter((s) => !s.startsWith("-"));
|
|
11972
|
+
return closestNames(input, visible, limit);
|
|
11973
|
+
}
|
|
11974
|
+
function suggestFlag(input, limit = 2) {
|
|
11975
|
+
if (input.length <= 2) return [];
|
|
11976
|
+
return closestNames(input, FLAG_ALIASES, limit);
|
|
11977
|
+
}
|
|
11978
|
+
|
|
11718
11979
|
// src/sync-cmd.ts
|
|
11719
11980
|
import { existsSync as existsSync8 } from "fs";
|
|
11720
|
-
import { mkdir as mkdir5, readFile as
|
|
11981
|
+
import { mkdir as mkdir5, readFile as readFile13 } from "fs/promises";
|
|
11721
11982
|
import { homedir as homedir18 } from "os";
|
|
11722
11983
|
import { dirname as dirname3, join as join12 } from "path";
|
|
11723
11984
|
var SYNC_USAGE = `Usage: yaw-mcp sync <push|pull|status> [--json]
|
|
@@ -11771,7 +12032,7 @@ function bundlesPath(home) {
|
|
|
11771
12032
|
async function readLocalBundles(home) {
|
|
11772
12033
|
const path5 = bundlesPath(home);
|
|
11773
12034
|
if (!existsSync8(path5)) return { version: 1, servers: [] };
|
|
11774
|
-
const raw = await
|
|
12035
|
+
const raw = await readFile13(path5, "utf8");
|
|
11775
12036
|
let parsed;
|
|
11776
12037
|
try {
|
|
11777
12038
|
parsed = JSON.parse(raw);
|
|
@@ -11932,13 +12193,28 @@ async function syncPull(opts, io, home) {
|
|
|
11932
12193
|
}
|
|
11933
12194
|
async function syncPush(opts, io, home) {
|
|
11934
12195
|
const local = await readLocalBundles(home);
|
|
12196
|
+
const stripped = local.servers.map(stripEnvValues);
|
|
12197
|
+
if (opts.dryRun) {
|
|
12198
|
+
if (opts.json) {
|
|
12199
|
+
io.out(
|
|
12200
|
+
`${JSON.stringify({ ok: true, dryRun: true, serverCount: stripped.length }, null, 2)}
|
|
12201
|
+
`
|
|
12202
|
+
);
|
|
12203
|
+
} else {
|
|
12204
|
+
io.out(
|
|
12205
|
+
`[dry-run] would push ${stripped.length} server${stripped.length === 1 ? "" : "s"} (env values stripped); nothing sent.
|
|
12206
|
+
`
|
|
12207
|
+
);
|
|
12208
|
+
}
|
|
12209
|
+
return { exitCode: 0 };
|
|
12210
|
+
}
|
|
11935
12211
|
const remote = await getResource(MCP_BUNDLES_RESOURCE, {
|
|
11936
12212
|
home: opts.home,
|
|
11937
12213
|
baseUrl: opts.baseUrl
|
|
11938
12214
|
});
|
|
11939
12215
|
const remoteServers = remote.data?.servers ?? [];
|
|
11940
|
-
const
|
|
11941
|
-
const payload = { version: 1, servers:
|
|
12216
|
+
const merged = mergeRemoteActive(stripped, remoteServers);
|
|
12217
|
+
const payload = { version: 1, servers: merged };
|
|
11942
12218
|
const syncState = await readSyncState(home);
|
|
11943
12219
|
const lastPulled = syncState.mcp_bundles?.lastPulledVersion;
|
|
11944
12220
|
const pushVersion = lastPulled ?? remote.version;
|
|
@@ -11948,10 +12224,10 @@ async function syncPush(opts, io, home) {
|
|
|
11948
12224
|
});
|
|
11949
12225
|
await writeSyncState(home, { mcp_bundles: { lastPulledVersion: res.version } });
|
|
11950
12226
|
if (opts.json) {
|
|
11951
|
-
io.out(`${JSON.stringify({ ok: true, serverCount:
|
|
12227
|
+
io.out(`${JSON.stringify({ ok: true, serverCount: merged.length, newVersion: res.version }, null, 2)}
|
|
11952
12228
|
`);
|
|
11953
12229
|
} else {
|
|
11954
|
-
io.out(`Pushed ${
|
|
12230
|
+
io.out(`Pushed ${merged.length} server${merged.length === 1 ? "" : "s"} -> mcp_bundles v${res.version}.
|
|
11955
12231
|
`);
|
|
11956
12232
|
io.out("Env values stripped before upload; use `yaw-mcp secrets push` to sync secrets across machines.\n");
|
|
11957
12233
|
}
|
|
@@ -11989,45 +12265,30 @@ function handleSyncError(err, opts, io) {
|
|
|
11989
12265
|
}
|
|
11990
12266
|
|
|
11991
12267
|
// src/index.ts
|
|
11992
|
-
|
|
11993
|
-
"
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
"doctor",
|
|
12001
|
-
"reset-learning",
|
|
12002
|
-
"servers",
|
|
12003
|
-
"bundles",
|
|
12004
|
-
"completion",
|
|
12005
|
-
"upgrade",
|
|
12006
|
-
"try",
|
|
12007
|
-
"try-cleanup",
|
|
12008
|
-
"login",
|
|
12009
|
-
"logout",
|
|
12010
|
-
"sync",
|
|
12011
|
-
"stats",
|
|
12012
|
-
"secrets",
|
|
12013
|
-
"set-active",
|
|
12014
|
-
"help",
|
|
12015
|
-
"--help",
|
|
12016
|
-
"-h",
|
|
12017
|
-
"--version",
|
|
12018
|
-
"-V"
|
|
12019
|
-
];
|
|
12268
|
+
function dispatch(cmd, p) {
|
|
12269
|
+
p.then((r) => process.exit(typeof r === "number" ? r : r.exitCode)).catch((err) => {
|
|
12270
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12271
|
+
process.stderr.write(`yaw-mcp ${cmd}: ${msg}
|
|
12272
|
+
`);
|
|
12273
|
+
process.exit(1);
|
|
12274
|
+
});
|
|
12275
|
+
}
|
|
12020
12276
|
var subcommand = process.argv[2];
|
|
12021
12277
|
if (subcommand === "compliance") {
|
|
12022
|
-
runComplianceCommand(process.argv.slice(3))
|
|
12278
|
+
dispatch("compliance", runComplianceCommand(process.argv.slice(3)));
|
|
12023
12279
|
} else if (subcommand === "audit") {
|
|
12024
12280
|
const parsed = parseAuditArgs(process.argv.slice(3));
|
|
12025
12281
|
if (!parsed.ok) {
|
|
12282
|
+
if (parsed.help) {
|
|
12283
|
+
process.stdout.write(`${parsed.error}
|
|
12284
|
+
`);
|
|
12285
|
+
process.exit(0);
|
|
12286
|
+
}
|
|
12026
12287
|
process.stderr.write(`${parsed.error}
|
|
12027
12288
|
`);
|
|
12028
12289
|
process.exit(2);
|
|
12029
12290
|
}
|
|
12030
|
-
runAudit(parsed.options)
|
|
12291
|
+
dispatch("audit", runAudit(parsed.options));
|
|
12031
12292
|
} else if (subcommand === "foundry") {
|
|
12032
12293
|
const parsed = parseFoundryArgs(process.argv.slice(3));
|
|
12033
12294
|
if (!parsed.ok) {
|
|
@@ -12036,7 +12297,7 @@ if (subcommand === "compliance") {
|
|
|
12036
12297
|
`);
|
|
12037
12298
|
process.exit(isHelp ? 0 : 2);
|
|
12038
12299
|
}
|
|
12039
|
-
runFoundryExport(parsed.options)
|
|
12300
|
+
dispatch("foundry", runFoundryExport(parsed.options));
|
|
12040
12301
|
} else if (subcommand === "install") {
|
|
12041
12302
|
const parsed = parseInstallArgs(process.argv.slice(3));
|
|
12042
12303
|
if (!parsed.ok) {
|
|
@@ -12050,23 +12311,28 @@ if (subcommand === "compliance") {
|
|
|
12050
12311
|
process.exit(2);
|
|
12051
12312
|
}
|
|
12052
12313
|
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR && process.env.CLAUDE_CONFIG_DIR.length > 0 ? process.env.CLAUDE_CONFIG_DIR : void 0;
|
|
12053
|
-
runInstall({ ...parsed.options, claudeConfigDir })
|
|
12314
|
+
dispatch("install", runInstall({ ...parsed.options, claudeConfigDir }));
|
|
12054
12315
|
} else if (subcommand === "doctor") {
|
|
12055
12316
|
const doctorArgs = process.argv.slice(3);
|
|
12056
12317
|
const doctorJson = doctorArgs.includes("--json");
|
|
12057
|
-
const
|
|
12058
|
-
|
|
12318
|
+
const isHelpArg = (a) => a === "--help" || a === "-h";
|
|
12319
|
+
const doctorUnknowns = doctorArgs.filter((a) => a !== "--json" && !isHelpArg(a));
|
|
12320
|
+
const firstHelpIdx = doctorArgs.findIndex(isHelpArg);
|
|
12321
|
+
const firstUnknownIdx = doctorArgs.findIndex((a) => a !== "--json" && !isHelpArg(a));
|
|
12322
|
+
const helpWins = firstHelpIdx !== -1 && (firstUnknownIdx === -1 || firstHelpIdx < firstUnknownIdx);
|
|
12323
|
+
if (helpWins) {
|
|
12059
12324
|
process.stdout.write(
|
|
12060
12325
|
"Usage: yaw-mcp doctor [--json]\n\n Print a diagnostic of your yaw-mcp setup.\n\n --json Emit machine-readable JSON instead of text.\n"
|
|
12061
12326
|
);
|
|
12062
12327
|
process.exit(0);
|
|
12063
12328
|
}
|
|
12064
|
-
if (
|
|
12065
|
-
|
|
12329
|
+
if (doctorUnknowns.length > 0) {
|
|
12330
|
+
const quoted = doctorUnknowns.map((a) => `"${a}"`).join(", ");
|
|
12331
|
+
process.stderr.write(`yaw-mcp doctor: unknown argument${doctorUnknowns.length > 1 ? "s" : ""} ${quoted}
|
|
12066
12332
|
`);
|
|
12067
12333
|
process.exit(2);
|
|
12068
12334
|
}
|
|
12069
|
-
runDoctor({ json: doctorJson })
|
|
12335
|
+
dispatch("doctor", runDoctor({ json: doctorJson }));
|
|
12070
12336
|
} else if (subcommand === "reset-learning") {
|
|
12071
12337
|
const parsed = parseResetLearningArgs(process.argv.slice(3));
|
|
12072
12338
|
if (parsed.kind === "help") {
|
|
@@ -12079,7 +12345,7 @@ if (subcommand === "compliance") {
|
|
|
12079
12345
|
`);
|
|
12080
12346
|
process.exit(2);
|
|
12081
12347
|
}
|
|
12082
|
-
|
|
12348
|
+
dispatch("reset-learning", runResetLearning());
|
|
12083
12349
|
} else if (subcommand === "servers") {
|
|
12084
12350
|
const parsed = parseServersArgs(process.argv.slice(3));
|
|
12085
12351
|
if (!parsed.ok) {
|
|
@@ -12092,7 +12358,7 @@ if (subcommand === "compliance") {
|
|
|
12092
12358
|
`);
|
|
12093
12359
|
process.exit(2);
|
|
12094
12360
|
}
|
|
12095
|
-
runServersCommand(parsed.options)
|
|
12361
|
+
dispatch("servers", runServersCommand(parsed.options));
|
|
12096
12362
|
} else if (subcommand === "bundles") {
|
|
12097
12363
|
const parsed = parseBundlesArgs(process.argv.slice(3));
|
|
12098
12364
|
if (!parsed.ok) {
|
|
@@ -12105,7 +12371,7 @@ if (subcommand === "compliance") {
|
|
|
12105
12371
|
`);
|
|
12106
12372
|
process.exit(2);
|
|
12107
12373
|
}
|
|
12108
|
-
runBundlesCommand(parsed.options)
|
|
12374
|
+
dispatch("bundles", runBundlesCommand(parsed.options));
|
|
12109
12375
|
} else if (subcommand === "completion") {
|
|
12110
12376
|
const parsed = parseCompletionArgs(process.argv.slice(3));
|
|
12111
12377
|
if (!parsed.ok) {
|
|
@@ -12118,7 +12384,7 @@ if (subcommand === "compliance") {
|
|
|
12118
12384
|
`);
|
|
12119
12385
|
process.exit(2);
|
|
12120
12386
|
}
|
|
12121
|
-
runCompletion(parsed.options)
|
|
12387
|
+
dispatch("completion", runCompletion(parsed.options));
|
|
12122
12388
|
} else if (subcommand === "upgrade") {
|
|
12123
12389
|
const parsed = parseUpgradeArgs(process.argv.slice(3));
|
|
12124
12390
|
if (!parsed.ok) {
|
|
@@ -12131,7 +12397,7 @@ if (subcommand === "compliance") {
|
|
|
12131
12397
|
`);
|
|
12132
12398
|
process.exit(2);
|
|
12133
12399
|
}
|
|
12134
|
-
runUpgrade(parsed.options)
|
|
12400
|
+
dispatch("upgrade", runUpgrade(parsed.options));
|
|
12135
12401
|
} else if (subcommand === "try") {
|
|
12136
12402
|
const parsed = parseTryArgs(process.argv.slice(3));
|
|
12137
12403
|
if (!parsed.ok) {
|
|
@@ -12144,7 +12410,7 @@ if (subcommand === "compliance") {
|
|
|
12144
12410
|
`);
|
|
12145
12411
|
process.exit(2);
|
|
12146
12412
|
}
|
|
12147
|
-
runTry(parsed.options)
|
|
12413
|
+
dispatch("try", runTry(parsed.options));
|
|
12148
12414
|
} else if (subcommand === "try-cleanup") {
|
|
12149
12415
|
const parsed = parseTryCleanupArgs(process.argv.slice(3));
|
|
12150
12416
|
if (!parsed.ok) {
|
|
@@ -12157,7 +12423,7 @@ if (subcommand === "compliance") {
|
|
|
12157
12423
|
`);
|
|
12158
12424
|
process.exit(2);
|
|
12159
12425
|
}
|
|
12160
|
-
runTryCleanup(parsed.options)
|
|
12426
|
+
dispatch("try-cleanup", runTryCleanup(parsed.options));
|
|
12161
12427
|
} else if (subcommand === "add") {
|
|
12162
12428
|
const parsed = parseAddArgs(process.argv.slice(3));
|
|
12163
12429
|
if (!parsed.ok) {
|
|
@@ -12170,7 +12436,7 @@ if (subcommand === "compliance") {
|
|
|
12170
12436
|
`);
|
|
12171
12437
|
process.exit(2);
|
|
12172
12438
|
}
|
|
12173
|
-
runAdd(parsed.options)
|
|
12439
|
+
dispatch("add", runAdd(parsed.options));
|
|
12174
12440
|
} else if (subcommand === "remove") {
|
|
12175
12441
|
const parsed = parseRemoveArgs(process.argv.slice(3));
|
|
12176
12442
|
if (!parsed.ok) {
|
|
@@ -12183,7 +12449,7 @@ if (subcommand === "compliance") {
|
|
|
12183
12449
|
`);
|
|
12184
12450
|
process.exit(2);
|
|
12185
12451
|
}
|
|
12186
|
-
runRemove(parsed.options)
|
|
12452
|
+
dispatch("remove", runRemove(parsed.options));
|
|
12187
12453
|
} else if (subcommand === "list") {
|
|
12188
12454
|
const parsed = parseListArgs(process.argv.slice(3));
|
|
12189
12455
|
if (!parsed.ok) {
|
|
@@ -12196,7 +12462,7 @@ if (subcommand === "compliance") {
|
|
|
12196
12462
|
`);
|
|
12197
12463
|
process.exit(2);
|
|
12198
12464
|
}
|
|
12199
|
-
runList(parsed.options)
|
|
12465
|
+
dispatch("list", runList(parsed.options));
|
|
12200
12466
|
} else if (subcommand === "login") {
|
|
12201
12467
|
const parsed = parseLoginArgs(process.argv.slice(3));
|
|
12202
12468
|
if (!parsed.ok) {
|
|
@@ -12209,7 +12475,7 @@ if (subcommand === "compliance") {
|
|
|
12209
12475
|
`);
|
|
12210
12476
|
process.exit(2);
|
|
12211
12477
|
}
|
|
12212
|
-
runLogin(parsed.options)
|
|
12478
|
+
dispatch("login", runLogin(parsed.options));
|
|
12213
12479
|
} else if (subcommand === "logout") {
|
|
12214
12480
|
const parsed = parseLogoutArgs(process.argv.slice(3));
|
|
12215
12481
|
if (!parsed.ok) {
|
|
@@ -12222,7 +12488,7 @@ if (subcommand === "compliance") {
|
|
|
12222
12488
|
`);
|
|
12223
12489
|
process.exit(2);
|
|
12224
12490
|
}
|
|
12225
|
-
runLogout(parsed.options)
|
|
12491
|
+
dispatch("logout", runLogout(parsed.options));
|
|
12226
12492
|
} else if (subcommand === "sync") {
|
|
12227
12493
|
const parsed = parseSyncArgs(process.argv.slice(3));
|
|
12228
12494
|
if (!parsed.ok) {
|
|
@@ -12235,7 +12501,7 @@ if (subcommand === "compliance") {
|
|
|
12235
12501
|
`);
|
|
12236
12502
|
process.exit(2);
|
|
12237
12503
|
}
|
|
12238
|
-
runSync(parsed.options)
|
|
12504
|
+
dispatch("sync", runSync(parsed.options));
|
|
12239
12505
|
} else if (subcommand === "stats") {
|
|
12240
12506
|
const parsed = parseStatsArgs(process.argv.slice(3));
|
|
12241
12507
|
if (!parsed.ok) {
|
|
@@ -12248,7 +12514,7 @@ if (subcommand === "compliance") {
|
|
|
12248
12514
|
`);
|
|
12249
12515
|
process.exit(2);
|
|
12250
12516
|
}
|
|
12251
|
-
runStats(parsed.options)
|
|
12517
|
+
dispatch("stats", runStats(parsed.options));
|
|
12252
12518
|
} else if (subcommand === "secrets") {
|
|
12253
12519
|
const parsed = parseSecretsArgs(process.argv.slice(3));
|
|
12254
12520
|
if (!parsed.ok) {
|
|
@@ -12261,7 +12527,7 @@ if (subcommand === "compliance") {
|
|
|
12261
12527
|
`);
|
|
12262
12528
|
process.exit(2);
|
|
12263
12529
|
}
|
|
12264
|
-
runSecrets(parsed.options)
|
|
12530
|
+
dispatch("secrets", runSecrets(parsed.options));
|
|
12265
12531
|
} else if (subcommand === "set-active") {
|
|
12266
12532
|
const parsed = parseSetActiveArgs(process.argv.slice(3));
|
|
12267
12533
|
if (!parsed.ok) {
|
|
@@ -12274,7 +12540,7 @@ if (subcommand === "compliance") {
|
|
|
12274
12540
|
`);
|
|
12275
12541
|
process.exit(2);
|
|
12276
12542
|
}
|
|
12277
|
-
runSetActive(parsed.options)
|
|
12543
|
+
dispatch("set-active", runSetActive(parsed.options));
|
|
12278
12544
|
} else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
12279
12545
|
process.stdout.write(`
|
|
12280
12546
|
yaw-mcp \u2014 one install, every MCP server, managed from the cloud.
|
|
@@ -12365,9 +12631,9 @@ if (subcommand === "compliance") {
|
|
|
12365
12631
|
gate (default: a clearly-winning server is
|
|
12366
12632
|
activated in the same call).
|
|
12367
12633
|
YAW_MCP_AUTO_UPGRADE Set to \`0\` to disable the background
|
|
12368
|
-
self-upgrade check at
|
|
12369
|
-
|
|
12370
|
-
|
|
12634
|
+
self-upgrade check at server startup (default:
|
|
12635
|
+
stale global-npm installs are upgraded in the
|
|
12636
|
+
background).
|
|
12371
12637
|
YAW_MCP_PRUNE_RESPONSES Set to \`0\` to disable response pruning.
|
|
12372
12638
|
YAW_MCP_DISABLE_PERSISTENCE Disable cross-session learning state.
|
|
12373
12639
|
YAW_MCP_CATALOG_URL Override the catalog \`add\`/\`try\` resolve slugs
|
|
@@ -12391,16 +12657,20 @@ if (subcommand === "compliance") {
|
|
|
12391
12657
|
`);
|
|
12392
12658
|
process.exit(0);
|
|
12393
12659
|
} else if (subcommand === "--version" || subcommand === "-V") {
|
|
12394
|
-
process.stdout.write(`yaw-mcp ${true ? "0.
|
|
12660
|
+
process.stdout.write(`yaw-mcp ${true ? "0.63.1" : "dev"}
|
|
12395
12661
|
`);
|
|
12396
12662
|
process.exit(0);
|
|
12397
12663
|
} else if (subcommand && !subcommand.startsWith("-")) {
|
|
12398
|
-
const
|
|
12399
|
-
const suggestions = closestNames(subcommand, visible, 3);
|
|
12664
|
+
const suggestions = suggestSubcommand(subcommand);
|
|
12400
12665
|
const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : " Run `yaw-mcp --help` for the list of subcommands.";
|
|
12401
12666
|
process.stderr.write(`yaw-mcp: unknown subcommand "${subcommand}".${hint}
|
|
12402
12667
|
`);
|
|
12403
12668
|
process.exit(2);
|
|
12669
|
+
} else if (subcommand && suggestFlag(subcommand).length > 0) {
|
|
12670
|
+
const suggestions = suggestFlag(subcommand);
|
|
12671
|
+
process.stderr.write(`yaw-mcp: unknown flag "${subcommand}". Did you mean: ${suggestions.join(", ")}?
|
|
12672
|
+
`);
|
|
12673
|
+
process.exit(2);
|
|
12404
12674
|
} else {
|
|
12405
12675
|
runServer();
|
|
12406
12676
|
}
|