@yawlabs/mcp 0.62.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 = {
@@ -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;
@@ -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);
@@ -3122,6 +3241,11 @@ async function runTry(opts) {
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) {
@@ -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,8 +3645,9 @@ 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 };
@@ -3537,7 +3662,7 @@ async function runUpgrade(opts = {}) {
3537
3662
  if (runSpec) {
3538
3663
  print("Run `yaw-mcp upgrade --run` to upgrade in place, or run it yourself:");
3539
3664
  } else {
3540
- print("Run it yourself (--run can't safely automate this install method):");
3665
+ print("Manual upgrade required (--run can't safely automate this install method). Run it yourself:");
3541
3666
  }
3542
3667
  print("");
3543
3668
  if (installRoot) {
@@ -3547,7 +3672,9 @@ async function runUpgrade(opts = {}) {
3547
3672
  return { exitCode: 1, lines };
3548
3673
  }
3549
3674
  if (!runSpec) {
3550
- printErr(`yaw-mcp upgrade --run: a "${method}" install can't be upgraded automatically. Run it yourself:`);
3675
+ printErr(
3676
+ `yaw-mcp upgrade --run: a "${method}" install can't be upgraded automatically (manual upgrade required). Run it yourself:`
3677
+ );
3551
3678
  printErr("");
3552
3679
  printErr(` ${plan.command}`);
3553
3680
  return { exitCode: 2, lines };
@@ -3572,7 +3699,7 @@ async function runUpgrade(opts = {}) {
3572
3699
  return { exitCode: 3, lines };
3573
3700
  }
3574
3701
  function readCurrentVersion() {
3575
- return true ? "0.62.0" : "dev";
3702
+ return true ? "0.63.0" : "dev";
3576
3703
  }
3577
3704
 
3578
3705
  // src/usage-hints.ts
@@ -3634,7 +3761,7 @@ function selectFlakyNamespaces(entries, limit) {
3634
3761
  }
3635
3762
 
3636
3763
  // src/doctor-cmd.ts
3637
- var VERSION = true ? "0.62.0" : "dev";
3764
+ var VERSION = true ? "0.63.0" : "dev";
3638
3765
  function isPersistenceDisabled(env) {
3639
3766
  const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
3640
3767
  return raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
@@ -3814,6 +3941,31 @@ async function runDoctorJson(opts) {
3814
3941
  }
3815
3942
  }
3816
3943
  const shellShadows = scanShellHistoryForShadows({ home, env });
3944
+ const trialGc = await gcExpiredTrials({ home, env, postEvent: opts.postTryEvent, now: opts.now }).catch(() => ({
3945
+ cleared: 0,
3946
+ failed: 0
3947
+ }));
3948
+ const trialScan = await scanTrials({ home, now: opts.now });
3949
+ const trials = {
3950
+ cleared: trialGc.cleared,
3951
+ live: trialScan.live.map(({ marker, msUntilExpiry }) => ({
3952
+ slug: marker.slug,
3953
+ clientName: marker.clientName,
3954
+ clientPath: marker.clientPath,
3955
+ msUntilExpiry
3956
+ })),
3957
+ malformed: trialScan.malformed
3958
+ };
3959
+ const analyticsFailure = getLastAnalyticsFailure();
3960
+ const reportFailure = getLastReportFailure();
3961
+ const backgroundPosters = {
3962
+ analytics: analyticsFailure ? {
3963
+ statusCode: analyticsFailure.statusCode,
3964
+ url: analyticsFailure.url,
3965
+ at: new Date(analyticsFailure.at).toISOString()
3966
+ } : null,
3967
+ toolReport: reportFailure ? { statusCode: reportFailure.statusCode, url: reportFailure.url, at: new Date(reportFailure.at).toISOString() } : null
3968
+ };
3817
3969
  const skipCheck = (opts.skipRegistryCheck === true || Boolean(process.env.VITEST)) && !opts.registryFetch;
3818
3970
  const latest = skipCheck ? null : await fetchLatestVersion(opts.registryFetch);
3819
3971
  const effectiveVersion = opts.currentVersion ?? VERSION;
@@ -3846,6 +3998,8 @@ async function runDoctorJson(opts) {
3846
3998
  reliability,
3847
3999
  clients,
3848
4000
  shellShadows,
4001
+ trials,
4002
+ backgroundPosters,
3849
4003
  upgrade: { current: effectiveVersion, latest, stale },
3850
4004
  diagnosis: { exitCode, summary }
3851
4005
  };
@@ -4284,8 +4438,10 @@ import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
4284
4438
  import { homedir as homedir10 } from "os";
4285
4439
  import path3 from "path";
4286
4440
 
4287
- // src/foundry-corpus.ts
4288
- import { readFileSync as readFileSync2 } from "fs";
4441
+ // src/foundry.ts
4442
+ import { appendFile, mkdir as mkdir3, stat as stat4 } from "fs/promises";
4443
+ import { homedir as homedir9 } from "os";
4444
+ import path2 from "path";
4289
4445
 
4290
4446
  // src/relevance.ts
4291
4447
  var K1 = 1.2;
@@ -4404,7 +4560,64 @@ function rankServers(context, servers) {
4404
4560
  return results;
4405
4561
  }
4406
4562
 
4563
+ // src/foundry.ts
4564
+ var SECRET_PREFIXES = ["sk_", "sk-", "tok_", "ghp_", "gho_", "xox", "pk_", "akia"];
4565
+ function looksSensitive(token5) {
4566
+ for (const prefix of SECRET_PREFIXES) {
4567
+ if (token5.startsWith(prefix)) return true;
4568
+ }
4569
+ if (token5.length >= 16 && /^[0-9a-f]+$/.test(token5)) return true;
4570
+ if (token5.length >= 12 && /[a-z]/.test(token5) && /[0-9]/.test(token5)) return true;
4571
+ if (token5.length >= 16 && /^[a-z]+$/.test(token5)) return true;
4572
+ return false;
4573
+ }
4574
+ function redactIntent(intent) {
4575
+ const all = tokenize(intent);
4576
+ const tokens = [];
4577
+ let redactedCount = 0;
4578
+ for (const token5 of all) {
4579
+ if (looksSensitive(token5)) {
4580
+ redactedCount++;
4581
+ } else {
4582
+ tokens.push(token5);
4583
+ }
4584
+ }
4585
+ tokens.sort();
4586
+ return { tokens, redactedCount };
4587
+ }
4588
+ function isFoundryEnabled() {
4589
+ const raw = process.env.YAW_MCP_FOUNDRY;
4590
+ if (!raw) return false;
4591
+ const v = raw.trim().toLowerCase();
4592
+ return v === "1" || v === "true";
4593
+ }
4594
+ var MAX_FOUNDRY_BYTES = 5 * 1024 * 1024;
4595
+ var FOUNDRY_FILENAME = "foundry.jsonl";
4596
+ async function appendFoundryTrace(trace, home = homedir9()) {
4597
+ try {
4598
+ if (!isFoundryEnabled()) return;
4599
+ const dir = userConfigDir(home);
4600
+ const file = path2.join(dir, FOUNDRY_FILENAME);
4601
+ try {
4602
+ const info = await stat4(file);
4603
+ if (info.size >= MAX_FOUNDRY_BYTES) return;
4604
+ } catch {
4605
+ }
4606
+ const line = `${JSON.stringify({
4607
+ tokens: trace.tokens,
4608
+ candidates: trace.candidates,
4609
+ chosen: trace.chosen,
4610
+ redactedCount: trace.redactedCount
4611
+ })}
4612
+ `;
4613
+ await mkdir3(dir, { recursive: true });
4614
+ await appendFile(file, line, "utf8");
4615
+ } catch {
4616
+ }
4617
+ }
4618
+
4407
4619
  // src/foundry-corpus.ts
4620
+ import { readFileSync as readFileSync2 } from "fs";
4408
4621
  var FOUNDRY_CORPUS_VERSION = 1;
4409
4622
  var DEFAULT_CORPUS_CAP = 500;
4410
4623
  function parseTraceLines(text) {
@@ -4485,65 +4698,6 @@ function scoreCorpus(corpus) {
4485
4698
  };
4486
4699
  }
4487
4700
 
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
4701
  // src/foundry-cmd.ts
4548
4702
  var DEFAULT_OUT = path3.join("src", "tests", "fixtures", "foundry-corpus.json");
4549
4703
  var FOUNDRY_USAGE = `Usage: yaw-mcp foundry export [--out <path>] [--cap <n>] [--json]
@@ -4676,60 +4830,6 @@ async function runFoundryExport(opts) {
4676
4830
  return { exitCode: 0, lines };
4677
4831
  }
4678
4832
 
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
4833
  // src/local-add-cmd.ts
4734
4834
  import { homedir as homedir11 } from "os";
4735
4835
  var SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
@@ -4747,7 +4847,7 @@ var ADD_USAGE = `Usage: yaw-mcp add <slug> [flags]
4747
4847
  --json Emit the written entry as JSON (implies success on stdout).
4748
4848
  --catalog <url> Override the catalog URL (default the public catalog).`;
4749
4849
  function parseEnvFlag(v, bag) {
4750
- if (!v || !v.includes("=")) return "--env requires KEY=value";
4850
+ if (!v?.includes("=")) return "--env requires KEY=value";
4751
4851
  const eq = v.indexOf("=");
4752
4852
  const key = v.slice(0, eq);
4753
4853
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return `--env: invalid KEY "${key}"`;
@@ -4776,7 +4876,7 @@ function parseAddArgs(argv) {
4776
4876
  break;
4777
4877
  case "--catalog": {
4778
4878
  const v = next();
4779
- if (!v) return { ok: false, error: "--catalog requires a URL" };
4879
+ if (!v || v.startsWith("--")) return { ok: false, error: "--catalog requires a URL" };
4780
4880
  opts.catalogUrl = v;
4781
4881
  break;
4782
4882
  }
@@ -4828,7 +4928,7 @@ async function runAdd(opts) {
4828
4928
  }
4829
4929
  const namespace = deriveNamespace(server.name);
4830
4930
  const supplied = { ...env, ...opts.envOverrides ?? {} };
4831
- const missing = server.requiredEnvKeys.filter((k) => !supplied[k] || supplied[k] === "");
4931
+ const missing = server.requiredEnvKeys.filter((k) => (supplied[k] ?? "").trim() === "");
4832
4932
  if (missing.length > 0) {
4833
4933
  printErr(`yaw-mcp add: ${server.name} needs the following env var(s) before it can run:`);
4834
4934
  for (const k of missing) printErr(` - ${k}`);
@@ -4840,7 +4940,11 @@ async function runAdd(opts) {
4840
4940
  }
4841
4941
  const entryEnv = {};
4842
4942
  for (const k of server.requiredEnvKeys) entryEnv[k] = "";
4843
- for (const [k, v] of Object.entries(opts.envOverrides ?? {})) entryEnv[k] = v;
4943
+ for (const [k, v] of Object.entries(opts.envOverrides ?? {})) {
4944
+ const trimmed = v.trim();
4945
+ if (trimmed === "") continue;
4946
+ entryEnv[k] = trimmed;
4947
+ }
4844
4948
  const overrides = opts.envOverrides ?? {};
4845
4949
  const ambientOnlyRequired = server.requiredEnvKeys.filter(
4846
4950
  (k) => (!overrides[k] || overrides[k] === "") && env[k] != null && env[k] !== ""
@@ -4947,7 +5051,7 @@ async function runRemove(opts) {
4947
5051
  printErr(`yaw-mcp remove: ${e.message}`);
4948
5052
  return { exitCode: 1, written: [] };
4949
5053
  }
4950
- if (!res || !res.removed) {
5054
+ if (!res?.removed) {
4951
5055
  print(`yaw-mcp remove: no server matching "${opts.target}" in ${res?.path ?? "bundles.json"} (nothing to do).`);
4952
5056
  const shadow2 = await findShadowingProjectBundles(cwd, home).catch(() => null);
4953
5057
  if (shadow2) {
@@ -5037,9 +5141,11 @@ function parseLoginArgs(argv) {
5037
5141
  const a = argv[i];
5038
5142
  if (a === "--key") {
5039
5143
  const v = argv[++i];
5040
- if (!v) return { ok: false, error: `yaw-mcp login: --key requires a value
5144
+ if (!v || v.startsWith("-")) {
5145
+ return { ok: false, error: `yaw-mcp login: --key requires a value
5041
5146
 
5042
5147
  ${LOGIN_USAGE}` };
5148
+ }
5043
5149
  opts.key = v;
5044
5150
  } else if (a === "--json") {
5045
5151
  opts.json = true;
@@ -5142,7 +5248,7 @@ async function runLogout(opts = {}, io = {
5142
5248
  }
5143
5249
 
5144
5250
  // src/reset-learning-cmd.ts
5145
- import { unlink as unlink2 } from "fs/promises";
5251
+ import { readFile as readFile8, unlink as unlink2 } from "fs/promises";
5146
5252
  import { homedir as homedir12 } from "os";
5147
5253
  import { join as join9 } from "path";
5148
5254
  var RESET_LEARNING_USAGE = `Usage: yaw-mcp reset-learning
@@ -5154,16 +5260,15 @@ var RESET_LEARNING_USAGE = `Usage: yaw-mcp reset-learning
5154
5260
 
5155
5261
  -h, --help Show this help.`;
5156
5262
  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}"
5263
+ if (argv.length === 0) return { kind: "ok", options: {} };
5264
+ const first = argv[0];
5265
+ if (first === "-h" || first === "--help") return { kind: "help" };
5266
+ return {
5267
+ kind: "error",
5268
+ error: `yaw-mcp reset-learning: unknown argument "${first}"
5162
5269
 
5163
5270
  ${RESET_LEARNING_USAGE}`
5164
- };
5165
- }
5166
- return { kind: "ok", options: {} };
5271
+ };
5167
5272
  }
5168
5273
  async function runResetLearning(opts = {}) {
5169
5274
  const home = opts.home ?? homedir12();
@@ -5191,6 +5296,7 @@ async function runResetLearning(opts = {}) {
5191
5296
  const persisted = await loadState(filePath);
5192
5297
  const learningCount = Object.keys(persisted.learning).length;
5193
5298
  const packCount = persisted.packHistory.length;
5299
+ const parsedCleanly = await peekParsedCleanly(filePath);
5194
5300
  try {
5195
5301
  await unlink2(filePath);
5196
5302
  } catch (err) {
@@ -5203,12 +5309,33 @@ async function runResetLearning(opts = {}) {
5203
5309
  printErr(`yaw-mcp reset-learning: failed to remove ${filePath}: ${msg}`);
5204
5310
  return { exitCode: 1, lines, removed: false, path: filePath };
5205
5311
  }
5312
+ if (!parsedCleanly) {
5313
+ print("yaw-mcp reset-learning: cleared persisted state (contents unreadable).");
5314
+ print(` path: ${filePath}`);
5315
+ return { exitCode: 0, lines, removed: true, path: filePath };
5316
+ }
5206
5317
  print("yaw-mcp reset-learning: cleared persisted state.");
5207
5318
  print(` path: ${filePath}`);
5208
5319
  print(` learning entries removed: ${learningCount}`);
5209
5320
  print(` pack history entries removed: ${packCount}`);
5210
5321
  return { exitCode: 0, lines, removed: true, path: filePath };
5211
5322
  }
5323
+ async function peekParsedCleanly(filePath) {
5324
+ let raw;
5325
+ try {
5326
+ raw = await readFile8(filePath, "utf8");
5327
+ } catch (err) {
5328
+ if (isFileNotFound2(err)) return true;
5329
+ return false;
5330
+ }
5331
+ try {
5332
+ const parsed = JSON.parse(raw);
5333
+ if (!parsed || typeof parsed !== "object") return false;
5334
+ return parsed.version === STATE_SCHEMA_VERSION;
5335
+ } catch {
5336
+ return false;
5337
+ }
5338
+ }
5212
5339
  function isFileNotFound2(err) {
5213
5340
  return !!err && typeof err === "object" && "code" in err && err.code === "ENOENT";
5214
5341
  }
@@ -5219,7 +5346,7 @@ import { homedir as homedir14 } from "os";
5219
5346
 
5220
5347
  // src/secrets-vault.ts
5221
5348
  import { existsSync as existsSync5 } from "fs";
5222
- import { chmod as chmod3, readFile as readFile8 } from "fs/promises";
5349
+ import { chmod as chmod3, readFile as readFile9 } from "fs/promises";
5223
5350
  import { homedir as homedir13 } from "os";
5224
5351
  import { join as join10 } from "path";
5225
5352
 
@@ -5294,7 +5421,7 @@ async function loadVault(path5) {
5294
5421
  if (!existsSync5(path5)) return null;
5295
5422
  let raw;
5296
5423
  try {
5297
- raw = await readFile8(path5, "utf8");
5424
+ raw = await readFile9(path5, "utf8");
5298
5425
  } catch (err) {
5299
5426
  log("warn", "Failed to read vault", { path: path5, error: err instanceof Error ? err.message : String(err) });
5300
5427
  return null;
@@ -5330,7 +5457,7 @@ function isEncryptedEntry(v) {
5330
5457
  }
5331
5458
  async function saveVault(path5, vault) {
5332
5459
  await atomicWriteFile(path5, `${JSON.stringify(vault, null, 2)}
5333
- `);
5460
+ `, "utf8", 384);
5334
5461
  if (process.platform !== "win32") {
5335
5462
  try {
5336
5463
  await chmod3(path5, 384);
@@ -5443,6 +5570,10 @@ Actions:
5443
5570
  line, no echo). Override with --value <v> or
5444
5571
  --stdin (raw, multi-line) for scripting.
5445
5572
  get <name> Decrypt and print one secret value to stdout.
5573
+ NOTE: this prints the secret in CLEARTEXT (with
5574
+ or without --json). Redirect to a file or pipe
5575
+ to a consumer; avoid running it interactively so
5576
+ the value does not land in terminal scrollback.
5446
5577
  list Show vault entry names (values stay encrypted).
5447
5578
  remove <name> Delete an entry.
5448
5579
  lock Clear the in-process passphrase cache.
@@ -5487,9 +5618,14 @@ function parseSecretsArgs(argv) {
5487
5618
  }
5488
5619
  if (a === "--value") {
5489
5620
  const v = argv[++i];
5490
- if (v === void 0) return { ok: false, error: `yaw-mcp secrets: --value requires a value
5621
+ if (v === void 0 || v.startsWith("-")) {
5622
+ return {
5623
+ ok: false,
5624
+ error: `yaw-mcp secrets: --value requires a value (for a dash-leading value use --stdin)
5491
5625
 
5492
- ${SECRETS_USAGE}` };
5626
+ ${SECRETS_USAGE}`
5627
+ };
5628
+ }
5493
5629
  opts.value = v;
5494
5630
  continue;
5495
5631
  }
@@ -5564,7 +5700,7 @@ function readPassphraseFromTTY(stdin, stdout, prompt = "Vault passphrase: ") {
5564
5700
  stdin.setEncoding("utf8");
5565
5701
  const onData = (chunk) => {
5566
5702
  for (const ch of chunk) {
5567
- if (ch === "\n" || ch === "\r" || ch === "") {
5703
+ if (ch === "\n" || ch === "\r") {
5568
5704
  stdout.write("\n");
5569
5705
  stdin.removeListener("data", onData);
5570
5706
  try {
@@ -5575,6 +5711,17 @@ function readPassphraseFromTTY(stdin, stdout, prompt = "Vault passphrase: ") {
5575
5711
  resolve7(chunks.join(""));
5576
5712
  return;
5577
5713
  }
5714
+ if (ch === "") {
5715
+ stdout.write("\n");
5716
+ stdin.removeListener("data", onData);
5717
+ try {
5718
+ stdin.setRawMode?.(wasRaw);
5719
+ } catch {
5720
+ }
5721
+ stdin.pause();
5722
+ resolve7("");
5723
+ return;
5724
+ }
5578
5725
  if (ch === "") {
5579
5726
  stdout.write("\n");
5580
5727
  process.exit(130);
@@ -5693,6 +5840,14 @@ async function runSecrets(opts, io = {
5693
5840
  `);
5694
5841
  return { exitCode: 1 };
5695
5842
  }
5843
+ const outStream = opts.io?.stdout ?? process.stdout;
5844
+ if (outStream.isTTY === true) {
5845
+ const stderr = opts.io?.stderr ?? process.stderr;
5846
+ stderr.write(
5847
+ `yaw-mcp secrets: warning -- printing "${name}" in cleartext to your terminal; it will remain in scrollback.
5848
+ `
5849
+ );
5850
+ }
5696
5851
  if (opts.json) io.out(`${JSON.stringify({ ok: true, name, value })}
5697
5852
  `);
5698
5853
  else io.out(`${value}
@@ -5811,9 +5966,9 @@ async function runSecretsPull(opts, io) {
5811
5966
  const remote = await getResource(MCP_SECRETS_RESOURCE, { home, baseUrl: opts.baseUrl });
5812
5967
  const remoteEntries = remote.data?.entries;
5813
5968
  const remoteHasEntries = remoteEntries !== void 0 && remoteEntries !== null && typeof remoteEntries === "object" && Object.keys(remoteEntries).length > 0;
5814
- if (!remote.data || !remote.data.salt || !remoteHasEntries) {
5969
+ if (!remote.data?.salt || !remoteHasEntries) {
5815
5970
  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 })}
5971
+ if (opts.json) io.out(`${JSON.stringify({ ok: true, empty: true, message: msg })}
5817
5972
  `);
5818
5973
  else io.out(`${msg}
5819
5974
  `);
@@ -5864,7 +6019,7 @@ async function runSecretsPull(opts, io) {
5864
6019
  }
5865
6020
 
5866
6021
  // src/server.ts
5867
- import { readFile as readFile10 } from "fs/promises";
6022
+ import { readFile as readFile11 } from "fs/promises";
5868
6023
  import { homedir as homedir15 } from "os";
5869
6024
  import { isAbsolute as isAbsolute2, relative, resolve as resolve6 } from "path";
5870
6025
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -5930,7 +6085,7 @@ function defaultSpawn2(cmd, args) {
5930
6085
  async function maybeAutoUpgrade(deps = {}) {
5931
6086
  const optOut = process.env.YAW_MCP_AUTO_UPGRADE;
5932
6087
  if (optOut === "0" || optOut?.toLowerCase() === "false") return;
5933
- const current = deps.currentVersion ?? (true ? "0.62.0" : "dev");
6088
+ const current = deps.currentVersion ?? (true ? "0.63.0" : "dev");
5934
6089
  if (current === "dev") return;
5935
6090
  const method = (deps.isSeaImpl ? await deps.isSeaImpl() : await detectSea()) ? "binary" : detectInstallMethod(deps.argvPath ?? process.argv[1]);
5936
6091
  const latest = await (deps.fetchLatestImpl ?? fetchLatestVersion2)();
@@ -6365,14 +6520,76 @@ function stepBindingKey(step, index) {
6365
6520
  return typeof step.id === "string" && step.id.length > 0 ? step.id : String(index);
6366
6521
  }
6367
6522
 
6523
+ // src/fuzzy.ts
6524
+ function levenshtein(a, b) {
6525
+ if (a === b) return 0;
6526
+ const aLen = a.length;
6527
+ const bLen = b.length;
6528
+ if (aLen === 0) return bLen;
6529
+ if (bLen === 0) return aLen;
6530
+ let prev = new Array(bLen + 1);
6531
+ let curr = new Array(bLen + 1);
6532
+ for (let j = 0; j <= bLen; j++) prev[j] = j;
6533
+ for (let i = 1; i <= aLen; i++) {
6534
+ curr[0] = i;
6535
+ for (let j = 1; j <= bLen; j++) {
6536
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
6537
+ curr[j] = Math.min(
6538
+ curr[j - 1] + 1,
6539
+ // insertion
6540
+ prev[j] + 1,
6541
+ // deletion
6542
+ prev[j - 1] + cost
6543
+ // substitution
6544
+ );
6545
+ }
6546
+ [prev, curr] = [curr, prev];
6547
+ }
6548
+ return prev[bLen];
6549
+ }
6550
+ function closestNames(query, candidates, limit) {
6551
+ if (limit <= 0) return [];
6552
+ const q = query.toLowerCase();
6553
+ const scored = [];
6554
+ for (const c of candidates) {
6555
+ if (c === query) continue;
6556
+ const lc = c.toLowerCase();
6557
+ let score = null;
6558
+ if (lc === q) {
6559
+ score = 0;
6560
+ } else if (lc.startsWith(q) || q.startsWith(lc)) {
6561
+ score = 1;
6562
+ } else if (
6563
+ // Substring containment is only a credible "typo" signal when the
6564
+ // query is long enough to be specific AND the shorter string covers
6565
+ // at least half the longer one. Without these gates a 1-2 char query
6566
+ // ("ls", "set") substring-matches long commands ("list", "set-active",
6567
+ // "secrets") and surfaces misleading suggestions the header calls
6568
+ // conservative. The Levenshtein tier still catches genuine short typos.
6569
+ q.length >= 3 && (lc.includes(q) || q.includes(lc)) && Math.min(q.length, lc.length) * 2 >= Math.max(q.length, lc.length)
6570
+ ) {
6571
+ score = 2;
6572
+ } else {
6573
+ const d = levenshtein(q, lc);
6574
+ if (d <= 2) score = 2 + d;
6575
+ }
6576
+ if (score !== null) scored.push({ name: c, score });
6577
+ }
6578
+ scored.sort((a, b) => {
6579
+ if (a.score !== b.score) return a.score - b.score;
6580
+ return a.name.localeCompare(b.name);
6581
+ });
6582
+ return scored.slice(0, limit).map((s) => s.name);
6583
+ }
6584
+
6368
6585
  // src/guide.ts
6369
- import { readFile as readFile9 } from "fs/promises";
6586
+ import { readFile as readFile10 } from "fs/promises";
6370
6587
  var GUIDE_READ_TIMEOUT_MS = 1e3;
6371
6588
  async function readGuide(path5, scope) {
6372
6589
  let raw;
6373
6590
  try {
6374
6591
  raw = await Promise.race([
6375
- readFile9(path5, "utf8"),
6592
+ readFile10(path5, "utf8"),
6376
6593
  new Promise(
6377
6594
  (_, reject) => setTimeout(() => reject(new Error("guide read timeout")), GUIDE_READ_TIMEOUT_MS)
6378
6595
  )
@@ -7187,7 +7404,7 @@ var PackDetector = class {
7187
7404
  loadSnapshot(snapshot) {
7188
7405
  const clean = [];
7189
7406
  for (const c of snapshot) {
7190
- if (!c || !c.namespace || !c.toolName) continue;
7407
+ if (!c?.namespace || !c.toolName) continue;
7191
7408
  clean.push({ namespace: c.namespace, toolName: c.toolName, at: c.at });
7192
7409
  }
7193
7410
  if (clean.length > this.maxHistory) {
@@ -7374,7 +7591,7 @@ async function routeResourceRead(uri, resourceRoutes, activeConnections, builtin
7374
7591
  return { contents: [{ uri, text: `Unknown resource: ${uri}` }] };
7375
7592
  }
7376
7593
  const connection = activeConnections.get(route.namespace);
7377
- if (!connection || connection.status !== "connected") {
7594
+ if (connection?.status !== "connected") {
7378
7595
  return { contents: [{ uri, text: `Server "${route.namespace}" is not connected.` }] };
7379
7596
  }
7380
7597
  try {
@@ -7392,7 +7609,7 @@ async function routePromptGet(name, args, promptRoutes, activeConnections) {
7392
7609
  return { messages: [{ role: "user", content: { type: "text", text: `Unknown prompt: ${name}` } }] };
7393
7610
  }
7394
7611
  const connection = activeConnections.get(route.namespace);
7395
- if (!connection || connection.status !== "connected") {
7612
+ if (connection?.status !== "connected") {
7396
7613
  return {
7397
7614
  messages: [{ role: "user", content: { type: "text", text: `Server "${route.namespace}" is not connected.` } }]
7398
7615
  };
@@ -7420,7 +7637,7 @@ async function routeToolCall(toolName, args, toolRoutes, activeConnections) {
7420
7637
  };
7421
7638
  }
7422
7639
  const connection = activeConnections.get(route.namespace);
7423
- if (!connection || connection.status !== "connected") {
7640
+ if (connection?.status !== "connected") {
7424
7641
  return {
7425
7642
  content: [
7426
7643
  {
@@ -7731,10 +7948,50 @@ async function callLegacyRerank(payload) {
7731
7948
  }
7732
7949
  }
7733
7950
  async function readTeamCookie() {
7734
- const teamSync = await import("./team-sync-B4R6FLKR.js");
7951
+ const teamSync = await import("./team-sync-OONB72BJ.js");
7735
7952
  return teamSync.getCachedCookie();
7736
7953
  }
7737
7954
 
7955
+ // src/reward.ts
7956
+ var ERROR_SHAPED_CATEGORIES = /* @__PURE__ */ new Set([
7957
+ "validation_error",
7958
+ "timeout",
7959
+ "unauthorized",
7960
+ "unknown_tool",
7961
+ "connection_lost",
7962
+ "rate_limited",
7963
+ "not_found"
7964
+ ]);
7965
+ function firstTextBlock(result) {
7966
+ const content = result.content;
7967
+ if (!content || content.length === 0) return void 0;
7968
+ for (const block of content) {
7969
+ if (typeof block.text === "string" && block.text.trim().length > 0) return block.text;
7970
+ }
7971
+ return void 0;
7972
+ }
7973
+ function isEmptyBody(result) {
7974
+ const content = result.content;
7975
+ if (!content || content.length === 0) return true;
7976
+ for (const block of content) {
7977
+ if (typeof block.text === "string" && block.text.trim().length > 0) {
7978
+ return false;
7979
+ }
7980
+ }
7981
+ return true;
7982
+ }
7983
+ function computeOutcomeReward(result) {
7984
+ if (result.isError === true) return 0;
7985
+ const text = firstTextBlock(result);
7986
+ if (text !== void 0) {
7987
+ if (ERROR_SHAPED_CATEGORIES.has(classifyError(text))) {
7988
+ return 0.2;
7989
+ }
7990
+ }
7991
+ if (isEmptyBody(result)) return 0.3;
7992
+ return 1;
7993
+ }
7994
+
7738
7995
  // src/reward-grader.ts
7739
7996
  function isRewardGraderEnabled() {
7740
7997
  const raw = process.env.YAW_MCP_REWARD_GRADER;
@@ -7836,46 +8093,6 @@ function extractText(content) {
7836
8093
  return "";
7837
8094
  }
7838
8095
 
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
8096
  // src/runtime-detect.ts
7880
8097
  import { spawn as spawn4 } from "child_process";
7881
8098
  import { request as request8 } from "undici";
@@ -8194,7 +8411,9 @@ async function bestOfNViaSampling(server, intent, candidates, n) {
8194
8411
  }
8195
8412
  if (votes.size === 0) return null;
8196
8413
  const order = /* @__PURE__ */ new Map();
8197
- candidates.forEach((c, i) => order.set(c.namespace, i));
8414
+ candidates.forEach((c, i) => {
8415
+ order.set(c.namespace, i);
8416
+ });
8198
8417
  let winner = null;
8199
8418
  let bestVotes = -1;
8200
8419
  let bestRank = Number.POSITIVE_INFINITY;
@@ -8557,7 +8776,7 @@ function categorizeSpawnError(err) {
8557
8776
  }
8558
8777
  async function connectToUpstream(config, onDisconnect, onListChanged) {
8559
8778
  const client = new Client(
8560
- { name: "yaw-mcp", version: true ? "0.62.0" : "dev" },
8779
+ { name: "yaw-mcp", version: true ? "0.63.0" : "dev" },
8561
8780
  { capabilities: {} }
8562
8781
  );
8563
8782
  let transport;
@@ -8884,7 +9103,7 @@ var ConnectServer = class _ConnectServer {
8884
9103
  this.apiUrl = apiUrl5;
8885
9104
  this.token = token5;
8886
9105
  this.server = new Server(
8887
- { name: "yaw-mcp", version: true ? "0.62.0" : "dev" },
9106
+ { name: "yaw-mcp", version: true ? "0.63.0" : "dev" },
8888
9107
  {
8889
9108
  capabilities: {
8890
9109
  tools: { listChanged: true },
@@ -10484,7 +10703,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
10484
10703
  let changed = false;
10485
10704
  for (const [namespace, connection] of this.connections) {
10486
10705
  const newServerConfig = newServersByNs.get(namespace);
10487
- if (!newServerConfig || !newServerConfig.isActive) {
10706
+ if (!newServerConfig?.isActive) {
10488
10707
  log("info", "Server removed or disabled in config, deactivating", { namespace });
10489
10708
  await disconnectFromUpstream(connection);
10490
10709
  this.connections.delete(namespace);
@@ -10582,7 +10801,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
10582
10801
  isError: true
10583
10802
  };
10584
10803
  }
10585
- const raw = await readFile10(resolved, "utf-8");
10804
+ const raw = await readFile11(resolved, "utf-8");
10586
10805
  const parsed = JSON.parse(raw);
10587
10806
  if (!parsed.mcpServers || typeof parsed.mcpServers !== "object" || Array.isArray(parsed.mcpServers)) {
10588
10807
  return {
@@ -11386,7 +11605,7 @@ import { homedir as homedir16 } from "os";
11386
11605
 
11387
11606
  // src/sync-state.ts
11388
11607
  import { existsSync as existsSync7 } from "fs";
11389
- import { mkdir as mkdir4, readFile as readFile11 } from "fs/promises";
11608
+ import { mkdir as mkdir4, readFile as readFile12 } from "fs/promises";
11390
11609
  import { dirname as dirname2, join as join11 } from "path";
11391
11610
  var SYNC_STATE_FILENAME = "sync-state.json";
11392
11611
  function syncStatePath(home) {
@@ -11396,7 +11615,7 @@ async function readSyncState(home) {
11396
11615
  const path5 = syncStatePath(home);
11397
11616
  if (!existsSync7(path5)) return {};
11398
11617
  try {
11399
- const raw = await readFile11(path5, "utf8");
11618
+ const raw = await readFile12(path5, "utf8");
11400
11619
  const parsed = JSON.parse(raw);
11401
11620
  if (!parsed || typeof parsed !== "object") return {};
11402
11621
  return parsed;
@@ -11644,7 +11863,11 @@ function formatPlain(events, opts, orderId, total) {
11644
11863
  lines.push(` ${c.client.padEnd(24)} ${c.total} calls`);
11645
11864
  }
11646
11865
  lines.push("");
11647
- lines.push("Recent events (newest first):");
11866
+ if (events.length > renderedCount) {
11867
+ lines.push("Recent events (newest first, capped at --limit; By-server / By-AI-client above span the full window):");
11868
+ } else {
11869
+ lines.push("Recent events (newest first):");
11870
+ }
11648
11871
  const recent = events.slice(-Math.min(events.length, opts.limit ?? 50)).reverse();
11649
11872
  for (const e of recent) {
11650
11873
  const when = new Date(e.ts).toISOString().replace("T", " ").slice(0, 19);
@@ -11715,9 +11938,45 @@ async function runStats(opts, io = {
11715
11938
  }
11716
11939
  }
11717
11940
 
11941
+ // src/subcommands.ts
11942
+ var FLAG_ALIASES = ["--help", "-h", "--version", "-V"];
11943
+ var KNOWN_SUBCOMMANDS = [
11944
+ "compliance",
11945
+ "audit",
11946
+ "foundry",
11947
+ "install",
11948
+ "add",
11949
+ "remove",
11950
+ "list",
11951
+ "doctor",
11952
+ "reset-learning",
11953
+ "servers",
11954
+ "bundles",
11955
+ "completion",
11956
+ "upgrade",
11957
+ "try",
11958
+ "try-cleanup",
11959
+ "login",
11960
+ "logout",
11961
+ "sync",
11962
+ "stats",
11963
+ "secrets",
11964
+ "set-active",
11965
+ "help",
11966
+ ...FLAG_ALIASES
11967
+ ];
11968
+ function suggestSubcommand(input, limit = 3) {
11969
+ const visible = KNOWN_SUBCOMMANDS.filter((s) => !s.startsWith("-"));
11970
+ return closestNames(input, visible, limit);
11971
+ }
11972
+ function suggestFlag(input, limit = 2) {
11973
+ if (input.length <= 2) return [];
11974
+ return closestNames(input, FLAG_ALIASES, limit);
11975
+ }
11976
+
11718
11977
  // src/sync-cmd.ts
11719
11978
  import { existsSync as existsSync8 } from "fs";
11720
- import { mkdir as mkdir5, readFile as readFile12 } from "fs/promises";
11979
+ import { mkdir as mkdir5, readFile as readFile13 } from "fs/promises";
11721
11980
  import { homedir as homedir18 } from "os";
11722
11981
  import { dirname as dirname3, join as join12 } from "path";
11723
11982
  var SYNC_USAGE = `Usage: yaw-mcp sync <push|pull|status> [--json]
@@ -11771,7 +12030,7 @@ function bundlesPath(home) {
11771
12030
  async function readLocalBundles(home) {
11772
12031
  const path5 = bundlesPath(home);
11773
12032
  if (!existsSync8(path5)) return { version: 1, servers: [] };
11774
- const raw = await readFile12(path5, "utf8");
12033
+ const raw = await readFile13(path5, "utf8");
11775
12034
  let parsed;
11776
12035
  try {
11777
12036
  parsed = JSON.parse(raw);
@@ -11989,45 +12248,30 @@ function handleSyncError(err, opts, io) {
11989
12248
  }
11990
12249
 
11991
12250
  // 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
- ];
12251
+ function dispatch(cmd, p) {
12252
+ p.then((r) => process.exit(typeof r === "number" ? r : r.exitCode)).catch((err) => {
12253
+ const msg = err instanceof Error ? err.message : String(err);
12254
+ process.stderr.write(`yaw-mcp ${cmd}: ${msg}
12255
+ `);
12256
+ process.exit(1);
12257
+ });
12258
+ }
12020
12259
  var subcommand = process.argv[2];
12021
12260
  if (subcommand === "compliance") {
12022
- runComplianceCommand(process.argv.slice(3)).then((code) => process.exit(code));
12261
+ dispatch("compliance", runComplianceCommand(process.argv.slice(3)));
12023
12262
  } else if (subcommand === "audit") {
12024
12263
  const parsed = parseAuditArgs(process.argv.slice(3));
12025
12264
  if (!parsed.ok) {
12265
+ if (parsed.help) {
12266
+ process.stdout.write(`${parsed.error}
12267
+ `);
12268
+ process.exit(0);
12269
+ }
12026
12270
  process.stderr.write(`${parsed.error}
12027
12271
  `);
12028
12272
  process.exit(2);
12029
12273
  }
12030
- runAudit(parsed.options).then((r) => process.exit(r.exitCode));
12274
+ dispatch("audit", runAudit(parsed.options));
12031
12275
  } else if (subcommand === "foundry") {
12032
12276
  const parsed = parseFoundryArgs(process.argv.slice(3));
12033
12277
  if (!parsed.ok) {
@@ -12036,7 +12280,7 @@ if (subcommand === "compliance") {
12036
12280
  `);
12037
12281
  process.exit(isHelp ? 0 : 2);
12038
12282
  }
12039
- runFoundryExport(parsed.options).then((r) => process.exit(r.exitCode));
12283
+ dispatch("foundry", runFoundryExport(parsed.options));
12040
12284
  } else if (subcommand === "install") {
12041
12285
  const parsed = parseInstallArgs(process.argv.slice(3));
12042
12286
  if (!parsed.ok) {
@@ -12050,23 +12294,28 @@ if (subcommand === "compliance") {
12050
12294
  process.exit(2);
12051
12295
  }
12052
12296
  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));
12297
+ dispatch("install", runInstall({ ...parsed.options, claudeConfigDir }));
12054
12298
  } else if (subcommand === "doctor") {
12055
12299
  const doctorArgs = process.argv.slice(3);
12056
12300
  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")) {
12301
+ const isHelpArg = (a) => a === "--help" || a === "-h";
12302
+ const doctorUnknowns = doctorArgs.filter((a) => a !== "--json" && !isHelpArg(a));
12303
+ const firstHelpIdx = doctorArgs.findIndex(isHelpArg);
12304
+ const firstUnknownIdx = doctorArgs.findIndex((a) => a !== "--json" && !isHelpArg(a));
12305
+ const helpWins = firstHelpIdx !== -1 && (firstUnknownIdx === -1 || firstHelpIdx < firstUnknownIdx);
12306
+ if (helpWins) {
12059
12307
  process.stdout.write(
12060
12308
  "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
12309
  );
12062
12310
  process.exit(0);
12063
12311
  }
12064
- if (doctorUnknown) {
12065
- process.stderr.write(`yaw-mcp doctor: unknown argument "${doctorUnknown}"
12312
+ if (doctorUnknowns.length > 0) {
12313
+ const quoted = doctorUnknowns.map((a) => `"${a}"`).join(", ");
12314
+ process.stderr.write(`yaw-mcp doctor: unknown argument${doctorUnknowns.length > 1 ? "s" : ""} ${quoted}
12066
12315
  `);
12067
12316
  process.exit(2);
12068
12317
  }
12069
- runDoctor({ json: doctorJson }).then((r) => process.exit(r.exitCode));
12318
+ dispatch("doctor", runDoctor({ json: doctorJson }));
12070
12319
  } else if (subcommand === "reset-learning") {
12071
12320
  const parsed = parseResetLearningArgs(process.argv.slice(3));
12072
12321
  if (parsed.kind === "help") {
@@ -12079,7 +12328,7 @@ if (subcommand === "compliance") {
12079
12328
  `);
12080
12329
  process.exit(2);
12081
12330
  }
12082
- runResetLearning().then((r) => process.exit(r.exitCode));
12331
+ dispatch("reset-learning", runResetLearning());
12083
12332
  } else if (subcommand === "servers") {
12084
12333
  const parsed = parseServersArgs(process.argv.slice(3));
12085
12334
  if (!parsed.ok) {
@@ -12092,7 +12341,7 @@ if (subcommand === "compliance") {
12092
12341
  `);
12093
12342
  process.exit(2);
12094
12343
  }
12095
- runServersCommand(parsed.options).then((r) => process.exit(r.exitCode));
12344
+ dispatch("servers", runServersCommand(parsed.options));
12096
12345
  } else if (subcommand === "bundles") {
12097
12346
  const parsed = parseBundlesArgs(process.argv.slice(3));
12098
12347
  if (!parsed.ok) {
@@ -12105,7 +12354,7 @@ if (subcommand === "compliance") {
12105
12354
  `);
12106
12355
  process.exit(2);
12107
12356
  }
12108
- runBundlesCommand(parsed.options).then((r) => process.exit(r.exitCode));
12357
+ dispatch("bundles", runBundlesCommand(parsed.options));
12109
12358
  } else if (subcommand === "completion") {
12110
12359
  const parsed = parseCompletionArgs(process.argv.slice(3));
12111
12360
  if (!parsed.ok) {
@@ -12118,7 +12367,7 @@ if (subcommand === "compliance") {
12118
12367
  `);
12119
12368
  process.exit(2);
12120
12369
  }
12121
- runCompletion(parsed.options).then((r) => process.exit(r.exitCode));
12370
+ dispatch("completion", runCompletion(parsed.options));
12122
12371
  } else if (subcommand === "upgrade") {
12123
12372
  const parsed = parseUpgradeArgs(process.argv.slice(3));
12124
12373
  if (!parsed.ok) {
@@ -12131,7 +12380,7 @@ if (subcommand === "compliance") {
12131
12380
  `);
12132
12381
  process.exit(2);
12133
12382
  }
12134
- runUpgrade(parsed.options).then((r) => process.exit(r.exitCode));
12383
+ dispatch("upgrade", runUpgrade(parsed.options));
12135
12384
  } else if (subcommand === "try") {
12136
12385
  const parsed = parseTryArgs(process.argv.slice(3));
12137
12386
  if (!parsed.ok) {
@@ -12144,7 +12393,7 @@ if (subcommand === "compliance") {
12144
12393
  `);
12145
12394
  process.exit(2);
12146
12395
  }
12147
- runTry(parsed.options).then((r) => process.exit(r.exitCode));
12396
+ dispatch("try", runTry(parsed.options));
12148
12397
  } else if (subcommand === "try-cleanup") {
12149
12398
  const parsed = parseTryCleanupArgs(process.argv.slice(3));
12150
12399
  if (!parsed.ok) {
@@ -12157,7 +12406,7 @@ if (subcommand === "compliance") {
12157
12406
  `);
12158
12407
  process.exit(2);
12159
12408
  }
12160
- runTryCleanup(parsed.options).then((r) => process.exit(r.exitCode));
12409
+ dispatch("try-cleanup", runTryCleanup(parsed.options));
12161
12410
  } else if (subcommand === "add") {
12162
12411
  const parsed = parseAddArgs(process.argv.slice(3));
12163
12412
  if (!parsed.ok) {
@@ -12170,7 +12419,7 @@ if (subcommand === "compliance") {
12170
12419
  `);
12171
12420
  process.exit(2);
12172
12421
  }
12173
- runAdd(parsed.options).then((r) => process.exit(r.exitCode));
12422
+ dispatch("add", runAdd(parsed.options));
12174
12423
  } else if (subcommand === "remove") {
12175
12424
  const parsed = parseRemoveArgs(process.argv.slice(3));
12176
12425
  if (!parsed.ok) {
@@ -12183,7 +12432,7 @@ if (subcommand === "compliance") {
12183
12432
  `);
12184
12433
  process.exit(2);
12185
12434
  }
12186
- runRemove(parsed.options).then((r) => process.exit(r.exitCode));
12435
+ dispatch("remove", runRemove(parsed.options));
12187
12436
  } else if (subcommand === "list") {
12188
12437
  const parsed = parseListArgs(process.argv.slice(3));
12189
12438
  if (!parsed.ok) {
@@ -12196,7 +12445,7 @@ if (subcommand === "compliance") {
12196
12445
  `);
12197
12446
  process.exit(2);
12198
12447
  }
12199
- runList(parsed.options).then((r) => process.exit(r.exitCode));
12448
+ dispatch("list", runList(parsed.options));
12200
12449
  } else if (subcommand === "login") {
12201
12450
  const parsed = parseLoginArgs(process.argv.slice(3));
12202
12451
  if (!parsed.ok) {
@@ -12209,7 +12458,7 @@ if (subcommand === "compliance") {
12209
12458
  `);
12210
12459
  process.exit(2);
12211
12460
  }
12212
- runLogin(parsed.options).then((r) => process.exit(r.exitCode));
12461
+ dispatch("login", runLogin(parsed.options));
12213
12462
  } else if (subcommand === "logout") {
12214
12463
  const parsed = parseLogoutArgs(process.argv.slice(3));
12215
12464
  if (!parsed.ok) {
@@ -12222,7 +12471,7 @@ if (subcommand === "compliance") {
12222
12471
  `);
12223
12472
  process.exit(2);
12224
12473
  }
12225
- runLogout(parsed.options).then((r) => process.exit(r.exitCode));
12474
+ dispatch("logout", runLogout(parsed.options));
12226
12475
  } else if (subcommand === "sync") {
12227
12476
  const parsed = parseSyncArgs(process.argv.slice(3));
12228
12477
  if (!parsed.ok) {
@@ -12235,7 +12484,7 @@ if (subcommand === "compliance") {
12235
12484
  `);
12236
12485
  process.exit(2);
12237
12486
  }
12238
- runSync(parsed.options).then((r) => process.exit(r.exitCode));
12487
+ dispatch("sync", runSync(parsed.options));
12239
12488
  } else if (subcommand === "stats") {
12240
12489
  const parsed = parseStatsArgs(process.argv.slice(3));
12241
12490
  if (!parsed.ok) {
@@ -12248,7 +12497,7 @@ if (subcommand === "compliance") {
12248
12497
  `);
12249
12498
  process.exit(2);
12250
12499
  }
12251
- runStats(parsed.options).then((r) => process.exit(r.exitCode));
12500
+ dispatch("stats", runStats(parsed.options));
12252
12501
  } else if (subcommand === "secrets") {
12253
12502
  const parsed = parseSecretsArgs(process.argv.slice(3));
12254
12503
  if (!parsed.ok) {
@@ -12261,7 +12510,7 @@ if (subcommand === "compliance") {
12261
12510
  `);
12262
12511
  process.exit(2);
12263
12512
  }
12264
- runSecrets(parsed.options).then((r) => process.exit(r.exitCode));
12513
+ dispatch("secrets", runSecrets(parsed.options));
12265
12514
  } else if (subcommand === "set-active") {
12266
12515
  const parsed = parseSetActiveArgs(process.argv.slice(3));
12267
12516
  if (!parsed.ok) {
@@ -12274,7 +12523,7 @@ if (subcommand === "compliance") {
12274
12523
  `);
12275
12524
  process.exit(2);
12276
12525
  }
12277
- runSetActive(parsed.options).then((r) => process.exit(r.exitCode));
12526
+ dispatch("set-active", runSetActive(parsed.options));
12278
12527
  } else if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
12279
12528
  process.stdout.write(`
12280
12529
  yaw-mcp \u2014 one install, every MCP server, managed from the cloud.
@@ -12391,16 +12640,20 @@ if (subcommand === "compliance") {
12391
12640
  `);
12392
12641
  process.exit(0);
12393
12642
  } else if (subcommand === "--version" || subcommand === "-V") {
12394
- process.stdout.write(`yaw-mcp ${true ? "0.62.0" : "dev"}
12643
+ process.stdout.write(`yaw-mcp ${true ? "0.63.0" : "dev"}
12395
12644
  `);
12396
12645
  process.exit(0);
12397
12646
  } else if (subcommand && !subcommand.startsWith("-")) {
12398
- const visible = KNOWN_SUBCOMMANDS.filter((s) => !s.startsWith("-") && s !== "help");
12399
- const suggestions = closestNames(subcommand, visible, 3);
12647
+ const suggestions = suggestSubcommand(subcommand);
12400
12648
  const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : " Run `yaw-mcp --help` for the list of subcommands.";
12401
12649
  process.stderr.write(`yaw-mcp: unknown subcommand "${subcommand}".${hint}
12402
12650
  `);
12403
12651
  process.exit(2);
12652
+ } else if (subcommand && suggestFlag(subcommand).length > 0) {
12653
+ const suggestions = suggestFlag(subcommand);
12654
+ process.stderr.write(`yaw-mcp: unknown flag "${subcommand}". Did you mean: ${suggestions.join(", ")}?
12655
+ `);
12656
+ process.exit(2);
12404
12657
  } else {
12405
12658
  runServer();
12406
12659
  }