@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-WORQOSXT.js";
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: 1, lines };
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: ["--key", "--json", "--help"]
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: ["--key", "--value", "--stdin", "--json", "--help"]
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 prev words cword
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
- '\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'
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(/[_.\-]/)[0];
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, readdir, unlink } from "fs/promises";
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
- 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)";
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
- const candidate = `${path5}.bak-${Date.now()}`;
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
- const candidate = `${path5}.bak-${Date.now()}`;
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-scope client: `yaw-mcp install --all`");
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 || !v.includes("=")) return { ok: false, error: "--env requires KEY=value" };
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 (existsSync3(resolved.absolute)) {
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 there's no package manager");
3525
- print("to upgrade it. Download the latest build and replace this executable:");
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("Run it yourself (--run can't safely automate this install method):");
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(`yaw-mcp upgrade --run: a "${method}" install can't be upgraded automatically. Run it yourself:`);
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.62.0" : "dev";
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.62.0" : "dev";
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 persistedState = persistenceDisabled ? null : await loadState(stateFilePath);
3681
- await renderStateSection({
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
- async function renderStateSection(opts) {
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 hasMcpEntry = false;
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
- if (raw.trim().length > 0) {
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
- malformed = true;
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
- hasMcpEntry,
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
- const resolved = resolveInstallPath({
4114
- clientId: target.clientId,
4115
- scope: scope.scope,
4116
- os: opts.os,
4117
- home: opts.home,
4118
- projectDir: scope.requiresProjectDir ? opts.cwd : void 0,
4119
- claudeConfigDir: opts.claudeConfigDir
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 hasMcpEntry = false;
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
- if (raw.trim().length > 0) {
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
- malformed = true;
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
- hasMcpEntry,
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-corpus.ts
4288
- import { readFileSync as readFileSync2 } from "fs";
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 || !v.includes("=")) return "--env requires KEY=value";
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) => !supplied[k] || supplied[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 ?? {})) entryEnv[k] = v;
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 || !res.removed) {
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) return { ok: false, error: `yaw-mcp login: --key requires a value
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
- for (const arg of argv) {
5158
- if (arg === "-h" || arg === "--help") return { kind: "help" };
5159
- return {
5160
- kind: "error",
5161
- error: `yaw-mcp reset-learning: unknown argument "${arg}"
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 readFile8 } from "fs/promises";
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 readFile8(path5, "utf8");
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) return { ok: false, error: `yaw-mcp secrets: --value requires a value
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" || ch === "") {
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 || !remote.data.salt || !remoteHasEntries) {
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 readFile10 } from "fs/promises";
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.62.0" : "dev");
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 readFile9 } from "fs/promises";
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
- readFile9(path5, "utf8"),
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 || !c.namespace || !c.toolName) continue;
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 (!connection || connection.status !== "connected") {
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 (!connection || connection.status !== "connected") {
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 (!connection || connection.status !== "connected") {
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-B4R6FLKR.js");
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) => order.set(c.namespace, 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.62.0" : "dev" },
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.62.0" : "dev" },
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 || !newServerConfig.isActive) {
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 readFile10(resolved, "utf-8");
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 readFile11 } from "fs/promises";
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 readFile11(path5, "utf8");
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
- lines.push("Recent events (newest first):");
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 readFile12 } from "fs/promises";
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 readFile12(path5, "utf8");
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 stripped = mergeRemoteActive(local.servers.map(stripEnvValues), remoteServers);
11941
- const payload = { version: 1, servers: stripped };
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: stripped.length, newVersion: res.version }, null, 2)}
12227
+ io.out(`${JSON.stringify({ ok: true, serverCount: merged.length, newVersion: res.version }, null, 2)}
11952
12228
  `);
11953
12229
  } else {
11954
- io.out(`Pushed ${stripped.length} server${stripped.length === 1 ? "" : "s"} -> mcp_bundles v${res.version}.
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
- var KNOWN_SUBCOMMANDS = [
11993
- "compliance",
11994
- "audit",
11995
- "foundry",
11996
- "install",
11997
- "add",
11998
- "remove",
11999
- "list",
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)).then((code) => process.exit(code));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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 }).then((r) => process.exit(r.exitCode));
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 doctorUnknown = doctorArgs.find((a) => a !== "--json" && a !== "--help" && a !== "-h");
12058
- if (doctorArgs.includes("--help") || doctorArgs.includes("-h")) {
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 (doctorUnknown) {
12065
- process.stderr.write(`yaw-mcp doctor: unknown argument "${doctorUnknown}"
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 }).then((r) => process.exit(r.exitCode));
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
- runResetLearning().then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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).then((r) => process.exit(r.exitCode));
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 \`yaw-mcp serve\` startup
12369
- (default: stale global-npm installs are
12370
- upgraded in the background).
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.62.0" : "dev"}
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 visible = KNOWN_SUBCOMMANDS.filter((s) => !s.startsWith("-") && s !== "help");
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
  }