@yawlabs/mcp 0.60.2 → 0.60.6

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,422 @@ import {
17
17
  signIn,
18
18
  signOut,
19
19
  userConfigDir
20
- } from "./chunk-C3WU6HAG.js";
20
+ } from "./chunk-I5625HJE.js";
21
+
22
+ // src/audit-cmd.ts
23
+ import { homedir as homedir3 } from "os";
24
+
25
+ // src/grades-cache.ts
26
+ import { readFile } from "fs/promises";
27
+ import { homedir } from "os";
28
+ import { join } from "path";
29
+
30
+ // src/jsonc.ts
31
+ function stripJsoncComments(src) {
32
+ let out = "";
33
+ let i = 0;
34
+ const len = src.length;
35
+ let inString = false;
36
+ let stringChar = "";
37
+ while (i < len) {
38
+ const c = src[i];
39
+ if (inString) {
40
+ out += c;
41
+ if (c === "\\" && i + 1 < len) {
42
+ out += src[i + 1];
43
+ i += 2;
44
+ continue;
45
+ }
46
+ if (c === stringChar) inString = false;
47
+ i++;
48
+ continue;
49
+ }
50
+ if (c === '"' || c === "'") {
51
+ inString = true;
52
+ stringChar = c;
53
+ out += c;
54
+ i++;
55
+ continue;
56
+ }
57
+ const next = src[i + 1];
58
+ if (c === "/" && next === "/") {
59
+ while (i < len && src[i] !== "\n") i++;
60
+ continue;
61
+ }
62
+ if (c === "/" && next === "*") {
63
+ i += 2;
64
+ while (i < len && !(src[i] === "*" && src[i + 1] === "/")) {
65
+ if (src[i] === "\n") out += "\n";
66
+ i++;
67
+ }
68
+ i += 2;
69
+ continue;
70
+ }
71
+ out += c;
72
+ i++;
73
+ }
74
+ return out;
75
+ }
76
+ function parseJsonc(src) {
77
+ const debommed = src.charCodeAt(0) === 65279 ? src.slice(1) : src;
78
+ const stripped = stripJsoncComments(debommed);
79
+ return JSON.parse(stripped);
80
+ }
81
+
82
+ // src/grades-cache.ts
83
+ var GRADES_FILENAME = "grades.json";
84
+ var GRADE_LETTERS = /* @__PURE__ */ new Set(["A", "B", "C", "D", "F"]);
85
+ function gradesCachePath(home = homedir()) {
86
+ return join(home, CONFIG_DIRNAME, GRADES_FILENAME);
87
+ }
88
+ function validateEntry(entry) {
89
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
90
+ const e = entry;
91
+ const grade = typeof e.grade === "string" ? e.grade.toUpperCase() : "";
92
+ if (!GRADE_LETTERS.has(grade)) return null;
93
+ const score = typeof e.score === "number" && Number.isFinite(e.score) ? e.score : null;
94
+ if (score === null) return null;
95
+ const gradedAt = typeof e.gradedAt === "string" && e.gradedAt.length > 0 ? e.gradedAt : "";
96
+ if (!gradedAt) return null;
97
+ return { grade, score, gradedAt };
98
+ }
99
+ async function readGradesCache(home = homedir()) {
100
+ const path3 = gradesCachePath(home);
101
+ let raw;
102
+ try {
103
+ raw = await readFile(path3, "utf8");
104
+ } catch {
105
+ return {};
106
+ }
107
+ let parsed;
108
+ try {
109
+ parsed = parseJsonc(raw);
110
+ } catch (err) {
111
+ log("warn", "grades.json is not valid JSON; ignoring", {
112
+ path: path3,
113
+ error: err instanceof Error ? err.message : String(err)
114
+ });
115
+ return {};
116
+ }
117
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
118
+ const out = {};
119
+ for (const [ns, entry] of Object.entries(parsed)) {
120
+ const validated = validateEntry(entry);
121
+ if (validated) out[ns] = validated;
122
+ }
123
+ return out;
124
+ }
125
+ async function writeGrade(namespace, grade, home = homedir()) {
126
+ const path3 = gradesCachePath(home);
127
+ const cache = await readGradesCache(home);
128
+ cache[namespace] = grade;
129
+ await atomicWriteFile(path3, `${JSON.stringify(cache, null, 2)}
130
+ `);
131
+ return path3;
132
+ }
133
+
134
+ // src/local-bundles.ts
135
+ import { createHash } from "crypto";
136
+ import { existsSync } from "fs";
137
+ import { readFile as readFile2 } from "fs/promises";
138
+ import { homedir as homedir2 } from "os";
139
+ import { join as join2 } from "path";
140
+ var BUNDLES_FILENAME = "bundles.json";
141
+ var CURRENT_BUNDLES_SCHEMA_VERSION = 1;
142
+ function localBundlesPath(configDir) {
143
+ return join2(configDir, BUNDLES_FILENAME);
144
+ }
145
+ var NAMESPACE_RE = /^[a-z][a-z0-9_]{0,29}$/;
146
+ function validateEntry2(entry, warnings) {
147
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
148
+ warnings.push("bundles.json: skipping non-object server entry");
149
+ return null;
150
+ }
151
+ const e = entry;
152
+ const namespace = typeof e.namespace === "string" ? e.namespace : "";
153
+ if (!namespace || !NAMESPACE_RE.test(namespace)) {
154
+ warnings.push(`bundles.json: skipping server with invalid namespace ${JSON.stringify(namespace)}`);
155
+ return null;
156
+ }
157
+ const name = typeof e.name === "string" && e.name.length > 0 ? e.name : namespace;
158
+ const type = e.type === "remote" ? "remote" : "local";
159
+ const transport = e.transport === "streamable-http" || e.transport === "sse" || e.transport === "stdio" ? e.transport : void 0;
160
+ const command = typeof e.command === "string" ? e.command : void 0;
161
+ const args = Array.isArray(e.args) ? e.args.filter((a) => typeof a === "string") : void 0;
162
+ const env = e.env && typeof e.env === "object" && !Array.isArray(e.env) ? Object.fromEntries(
163
+ Object.entries(e.env).filter(([, v]) => typeof v === "string")
164
+ ) : void 0;
165
+ const url = typeof e.url === "string" ? e.url : void 0;
166
+ const description = typeof e.description === "string" ? e.description : void 0;
167
+ const isActive = e.isActive !== false;
168
+ const id = typeof e.id === "string" && e.id.length > 0 ? e.id : `local-${namespace}`;
169
+ return {
170
+ id,
171
+ name,
172
+ namespace,
173
+ type,
174
+ transport,
175
+ command,
176
+ args,
177
+ env,
178
+ url,
179
+ isActive,
180
+ description
181
+ };
182
+ }
183
+ async function readBundlesAt(path3, warnings) {
184
+ let raw;
185
+ try {
186
+ raw = await readFile2(path3, "utf8");
187
+ } catch {
188
+ return { exists: false, file: null };
189
+ }
190
+ let parsed;
191
+ try {
192
+ parsed = parseJsonc(raw);
193
+ } catch (err) {
194
+ const msg = err instanceof Error ? err.message : String(err);
195
+ warnings.push(`${path3}: invalid JSON (${msg}) -- file ignored`);
196
+ log("warn", "bundles.json is not valid JSON; ignoring", { path: path3, error: msg });
197
+ return { exists: true, file: null };
198
+ }
199
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
200
+ warnings.push(`${path3}: root must be a JSON object -- file ignored`);
201
+ return { exists: true, file: null };
202
+ }
203
+ const obj = parsed;
204
+ const version = typeof obj.version === "number" ? obj.version : void 0;
205
+ if (version !== void 0 && version > CURRENT_BUNDLES_SCHEMA_VERSION) {
206
+ warnings.push(
207
+ `${path3}: schema version ${version} is newer than this yaw-mcp (${CURRENT_BUNDLES_SCHEMA_VERSION}); upgrade with \`npm i -g @yawlabs/mcp@latest\`. Loading best-effort.`
208
+ );
209
+ }
210
+ const rawServers = obj.servers;
211
+ if (!Array.isArray(rawServers)) {
212
+ warnings.push(`${path3}: 'servers' must be an array -- file ignored`);
213
+ return { exists: true, file: null };
214
+ }
215
+ return {
216
+ exists: true,
217
+ file: { version, servers: rawServers }
218
+ };
219
+ }
220
+ function hashContent(servers) {
221
+ const h = createHash("sha256");
222
+ h.update(JSON.stringify(servers));
223
+ return `local-${h.digest("hex").slice(0, 16)}`;
224
+ }
225
+ async function loadLocalBundles(opts = {}) {
226
+ const cwd = opts.cwd ?? process.cwd();
227
+ const home = opts.home ?? homedir2();
228
+ const warnings = [];
229
+ const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
230
+ const projectPath = projectDir ? localBundlesPath(projectDir) : null;
231
+ const globalPath = localBundlesPath(join2(home, CONFIG_DIRNAME));
232
+ const projectResult = projectPath ? await readBundlesAt(projectPath, warnings) : { exists: false, file: null };
233
+ let file;
234
+ let sourcePath;
235
+ if (projectResult.exists) {
236
+ file = projectResult.file;
237
+ sourcePath = projectPath;
238
+ } else {
239
+ const globalResult = await readBundlesAt(globalPath, warnings);
240
+ file = globalResult.file;
241
+ sourcePath = globalResult.exists ? globalPath : null;
242
+ }
243
+ if (!file) {
244
+ return { config: null, path: sourcePath, warnings };
245
+ }
246
+ const servers = [];
247
+ for (const raw of file.servers) {
248
+ const validated = validateEntry2(raw, warnings);
249
+ if (validated) servers.push(validated);
250
+ }
251
+ return {
252
+ config: {
253
+ servers,
254
+ configVersion: hashContent(servers)
255
+ },
256
+ path: sourcePath,
257
+ warnings
258
+ };
259
+ }
260
+ function deriveNamespace(name) {
261
+ let ns = name.toLowerCase().replace(/[^a-z0-9]+/g, "");
262
+ if (ns.length === 0) return "server";
263
+ if (!/^[a-z]/.test(ns)) ns = `s${ns}`;
264
+ if (ns.length > 30) ns = ns.slice(0, 30);
265
+ return ns;
266
+ }
267
+ async function readRawUserBundles(home) {
268
+ const path3 = localBundlesPath(userConfigDir(home));
269
+ if (!existsSync(path3)) {
270
+ return { version: CURRENT_BUNDLES_SCHEMA_VERSION, servers: [] };
271
+ }
272
+ const warnings = [];
273
+ const r = await readBundlesAt(path3, warnings);
274
+ if (!r.file) {
275
+ const detail = warnings.length > 0 ? ` (${warnings.join("; ")})` : "";
276
+ throw new Error(`${path3} is malformed${detail}; fix it by hand before adding servers.`);
277
+ }
278
+ return { version: r.file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION, servers: r.file.servers };
279
+ }
280
+ async function upsertUserBundle(entry, opts = {}) {
281
+ const home = opts.home ?? homedir2();
282
+ const path3 = localBundlesPath(userConfigDir(home));
283
+ const file = await readRawUserBundles(home);
284
+ const idx = file.servers.findIndex(
285
+ (s) => s?.namespace === entry.namespace || entry.name != null && s?.name === entry.name
286
+ );
287
+ const replaced = idx >= 0;
288
+ if (replaced) file.servers[idx] = entry;
289
+ else file.servers.push(entry);
290
+ file.version = file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION;
291
+ await atomicWriteFile(path3, `${JSON.stringify(file, null, 2)}
292
+ `);
293
+ return { path: path3, replaced };
294
+ }
295
+ async function removeUserBundle(namespace, opts = {}) {
296
+ const home = opts.home ?? homedir2();
297
+ const path3 = localBundlesPath(userConfigDir(home));
298
+ if (!existsSync(path3)) return { path: path3, removed: false };
299
+ const file = await readRawUserBundles(home);
300
+ const before = file.servers.length;
301
+ file.servers = file.servers.filter((s) => s?.namespace !== namespace);
302
+ if (file.servers.length === before) return { path: path3, removed: false };
303
+ file.version = file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION;
304
+ await atomicWriteFile(path3, `${JSON.stringify(file, null, 2)}
305
+ `);
306
+ return { path: path3, removed: true };
307
+ }
308
+ async function findShadowingProjectBundles(cwd, home = homedir2()) {
309
+ const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
310
+ if (!projectDir) return null;
311
+ const projectPath = localBundlesPath(projectDir);
312
+ return existsSync(projectPath) ? projectPath : null;
313
+ }
314
+
315
+ // src/audit-cmd.ts
316
+ var AUDIT_USAGE = `Usage: yaw-mcp audit <namespace> [--json]
317
+
318
+ Run the MCP compliance suite against a server configured in your local
319
+ bundles.json and cache its A-F grade in ~/.yaw-mcp/grades.json. The cached
320
+ grade then shows up in \`yaw-mcp servers\` and the Yaw Terminal MCP panel.
321
+
322
+ <namespace> The namespace of a stdio server in bundles.json (see
323
+ \`yaw-mcp list\`).
324
+ --json Emit machine-readable JSON instead of text.
325
+
326
+ To grade an arbitrary target (a URL, or a command not in bundles.json),
327
+ use \`yaw-mcp compliance <target>\` instead.`;
328
+ function parseAuditArgs(argv) {
329
+ let json = false;
330
+ let namespace;
331
+ for (const a of argv) {
332
+ if (a === "--json") {
333
+ json = true;
334
+ } else if (a === "--help" || a === "-h") {
335
+ return { ok: false, error: AUDIT_USAGE };
336
+ } else if (a.startsWith("-")) {
337
+ return { ok: false, error: `yaw-mcp audit: unknown argument "${a}"
338
+
339
+ ${AUDIT_USAGE}` };
340
+ } else if (namespace === void 0) {
341
+ namespace = a;
342
+ } else {
343
+ return { ok: false, error: `yaw-mcp audit: unexpected extra argument "${a}"
344
+
345
+ ${AUDIT_USAGE}` };
346
+ }
347
+ }
348
+ if (namespace === void 0) {
349
+ return { ok: false, error: `yaw-mcp audit: missing <namespace>.
350
+
351
+ ${AUDIT_USAGE}` };
352
+ }
353
+ return { ok: true, options: { namespace, json } };
354
+ }
355
+ function findServer(servers, namespace) {
356
+ return servers.find((s) => s.namespace === namespace);
357
+ }
358
+ async function defaultRunner(target) {
359
+ const { runComplianceSuite } = await import("@yawlabs/mcp-compliance");
360
+ const report = await runComplianceSuite({
361
+ type: "stdio",
362
+ command: target.command,
363
+ args: target.args,
364
+ env: target.env
365
+ });
366
+ return { grade: report.grade, score: report.score };
367
+ }
368
+ async function runAudit(opts = {}) {
369
+ const write = opts.out ?? ((s) => process.stdout.write(s));
370
+ const writeErr = opts.err ?? ((s) => process.stderr.write(s));
371
+ const lines = [];
372
+ const print = (s = "") => {
373
+ lines.push(s);
374
+ write(`${s}
375
+ `);
376
+ };
377
+ const printErr = (s) => {
378
+ lines.push(s);
379
+ writeErr(`${s}
380
+ `);
381
+ };
382
+ const namespace = opts.namespace;
383
+ if (!namespace) {
384
+ printErr("yaw-mcp audit: missing <namespace>.");
385
+ return { exitCode: 1, lines };
386
+ }
387
+ const home = opts.home ?? homedir3();
388
+ const { config, path: path3 } = await loadLocalBundles({ cwd: opts.cwd, home });
389
+ const servers = config?.servers ?? [];
390
+ const server = findServer(servers, namespace);
391
+ if (!server) {
392
+ const where = path3 ? ` (${path3})` : "";
393
+ printErr(
394
+ `yaw-mcp audit: no server named "${namespace}" in bundles.json${where}. Run \`yaw-mcp list\` to see configured servers.`
395
+ );
396
+ return { exitCode: 1, lines };
397
+ }
398
+ if (!server.command) {
399
+ if (server.url) {
400
+ printErr(
401
+ `yaw-mcp audit: "${namespace}" is a remote server (${server.url}). Audit grades stdio servers; run \`yaw-mcp compliance ${server.url}\` to grade a remote target.`
402
+ );
403
+ } else {
404
+ printErr(`yaw-mcp audit: "${namespace}" has no command to spawn -- it can't be audited as a stdio server.`);
405
+ }
406
+ return { exitCode: 2, lines };
407
+ }
408
+ const target = {
409
+ command: server.command,
410
+ args: server.args ?? [],
411
+ env: server.env
412
+ };
413
+ if (!opts.json) {
414
+ print(`Auditing "${namespace}" (${target.command}${target.args.length ? ` ${target.args.join(" ")}` : ""})...`);
415
+ }
416
+ const runner = opts.runner ?? defaultRunner;
417
+ let report;
418
+ try {
419
+ report = await runner(target);
420
+ } catch (err) {
421
+ const msg = err instanceof Error ? err.message : String(err);
422
+ log("error", "audit: compliance suite failed", { namespace, error: msg });
423
+ printErr(`yaw-mcp audit: compliance suite failed for "${namespace}": ${msg}`);
424
+ return { exitCode: 2, lines };
425
+ }
426
+ const gradedAt = (/* @__PURE__ */ new Date()).toISOString();
427
+ const cachePath = await writeGrade(namespace, { grade: report.grade, score: report.score, gradedAt }, home);
428
+ if (opts.json) {
429
+ print(JSON.stringify({ namespace, grade: report.grade, score: report.score, gradedAt, cache: cachePath }, null, 2));
430
+ return { exitCode: 0, lines };
431
+ }
432
+ print(`Grade: ${report.grade} (${report.score.toFixed(1)}%)`);
433
+ print(`Cached to ${cachePath}`);
434
+ return { exitCode: 0, lines };
435
+ }
21
436
 
22
437
  // src/bundles.ts
23
438
  var CURATED_BUNDLES = [
@@ -93,65 +508,13 @@ function topPartialBundles(installedNamespaces, limit) {
93
508
  }
94
509
 
95
510
  // src/config-loader.ts
96
- import { readFile, stat as stat2 } from "fs/promises";
97
- import { homedir } from "os";
98
- import { join as join2, resolve } from "path";
99
-
100
- // src/jsonc.ts
101
- function stripJsoncComments(src) {
102
- let out = "";
103
- let i = 0;
104
- const len = src.length;
105
- let inString = false;
106
- let stringChar = "";
107
- while (i < len) {
108
- const c = src[i];
109
- if (inString) {
110
- out += c;
111
- if (c === "\\" && i + 1 < len) {
112
- out += src[i + 1];
113
- i += 2;
114
- continue;
115
- }
116
- if (c === stringChar) inString = false;
117
- i++;
118
- continue;
119
- }
120
- if (c === '"' || c === "'") {
121
- inString = true;
122
- stringChar = c;
123
- out += c;
124
- i++;
125
- continue;
126
- }
127
- const next = src[i + 1];
128
- if (c === "/" && next === "/") {
129
- while (i < len && src[i] !== "\n") i++;
130
- continue;
131
- }
132
- if (c === "/" && next === "*") {
133
- i += 2;
134
- while (i < len && !(src[i] === "*" && src[i + 1] === "/")) {
135
- if (src[i] === "\n") out += "\n";
136
- i++;
137
- }
138
- i += 2;
139
- continue;
140
- }
141
- out += c;
142
- i++;
143
- }
144
- return out;
145
- }
146
- function parseJsonc(src) {
147
- const debommed = src.charCodeAt(0) === 65279 ? src.slice(1) : src;
148
- const stripped = stripJsoncComments(debommed);
149
- return JSON.parse(stripped);
150
- }
511
+ import { readFile as readFile3, stat as stat2 } from "fs/promises";
512
+ import { homedir as homedir4 } from "os";
513
+ import { join as join4, resolve } from "path";
151
514
 
152
515
  // src/migrate.ts
153
516
  import { mkdir, rename, stat } from "fs/promises";
154
- import { dirname, join } from "path";
517
+ import { dirname, join as join3 } from "path";
155
518
  var LEGACY_GLOBAL_FILENAME = ".yaw-mcp.json";
156
519
  var LEGACY_PROJECT_FILENAME = ".yaw-mcp.json";
157
520
  var LEGACY_LOCAL_FILENAME = ".yaw-mcp.local.json";
@@ -195,17 +558,17 @@ async function migrateFile(legacy, target, scope) {
195
558
  }
196
559
  async function migrateLegacyConfigPaths(opts) {
197
560
  const { cwd, home } = opts;
198
- const legacyGlobal = join(home, LEGACY_GLOBAL_FILENAME);
199
- const newGlobal = join(userConfigDir(home), NEW_CONFIG_FILENAME);
561
+ const legacyGlobal = join3(home, LEGACY_GLOBAL_FILENAME);
562
+ const newGlobal = join3(userConfigDir(home), NEW_CONFIG_FILENAME);
200
563
  await migrateFile(legacyGlobal, newGlobal, "global");
201
564
  const legacyProjectRoot = await findLegacyProjectRoot(cwd, home);
202
565
  if (legacyProjectRoot) {
203
- const newDir = join(legacyProjectRoot, CONFIG_DIRNAME);
204
- const legacyLocal = join(legacyProjectRoot, LEGACY_LOCAL_FILENAME);
205
- const newLocal = join(newDir, NEW_LOCAL_FILENAME);
566
+ const newDir = join3(legacyProjectRoot, CONFIG_DIRNAME);
567
+ const legacyLocal = join3(legacyProjectRoot, LEGACY_LOCAL_FILENAME);
568
+ const newLocal = join3(newDir, NEW_LOCAL_FILENAME);
206
569
  await migrateFile(legacyLocal, newLocal, "local");
207
- const legacyProject = join(legacyProjectRoot, LEGACY_PROJECT_FILENAME);
208
- const newProject = join(newDir, NEW_CONFIG_FILENAME);
570
+ const legacyProject = join3(legacyProjectRoot, LEGACY_PROJECT_FILENAME);
571
+ const newProject = join3(newDir, NEW_CONFIG_FILENAME);
209
572
  await migrateFile(legacyProject, newProject, "project");
210
573
  }
211
574
  }
@@ -216,8 +579,8 @@ async function findLegacyProjectRoot(cwd, home) {
216
579
  let prev = "";
217
580
  while (dir !== prev) {
218
581
  if (dir === homeResolved) return null;
219
- const legacyProject = join(dir, LEGACY_PROJECT_FILENAME);
220
- const legacyLocal = join(dir, LEGACY_LOCAL_FILENAME);
582
+ const legacyProject = join3(dir, LEGACY_PROJECT_FILENAME);
583
+ const legacyLocal = join3(dir, LEGACY_LOCAL_FILENAME);
221
584
  if (await exists(legacyProject) || await exists(legacyLocal)) return dir;
222
585
  prev = dir;
223
586
  dir = dirname5(dir);
@@ -233,7 +596,7 @@ var DEFAULT_API_BASE = "https://yaw.sh/mcp";
233
596
  async function readConfigAt(path3, scope, warnings) {
234
597
  let raw;
235
598
  try {
236
- raw = await readFile(path3, "utf8");
599
+ raw = await readFile3(path3, "utf8");
237
600
  } catch {
238
601
  return null;
239
602
  }
@@ -304,7 +667,7 @@ function unionBlocked(files) {
304
667
  }
305
668
  async function loadYawMcpConfig(opts = {}) {
306
669
  const cwd = resolve(opts.cwd ?? process.cwd());
307
- const home = resolve(opts.home ?? homedir());
670
+ const home = resolve(opts.home ?? homedir4());
308
671
  const env = opts.env ?? process.env;
309
672
  const warnings = [];
310
673
  const loadedFiles = [];
@@ -316,9 +679,9 @@ async function loadYawMcpConfig(opts = {}) {
316
679
  return null;
317
680
  });
318
681
  const globalDir = userConfigDir(home);
319
- const localPath = projectConfigDir ? join2(projectConfigDir, LOCAL_CONFIG_FILENAME) : null;
320
- const projectPath = projectConfigDir ? join2(projectConfigDir, CONFIG_FILENAME) : null;
321
- const globalPath = join2(globalDir, CONFIG_FILENAME);
682
+ const localPath = projectConfigDir ? join4(projectConfigDir, LOCAL_CONFIG_FILENAME) : null;
683
+ const projectPath = projectConfigDir ? join4(projectConfigDir, CONFIG_FILENAME) : null;
684
+ const globalPath = join4(globalDir, CONFIG_FILENAME);
322
685
  const local = localPath ? await readConfigAt(localPath, "local", warnings) : null;
323
686
  if (local) loadedFiles.push(local);
324
687
  const projectIsGlobal = projectConfigDir !== null && projectConfigDir === globalDir;
@@ -995,10 +1358,10 @@ Publish failed: ${err?.message ?? String(err)}
995
1358
  }
996
1359
 
997
1360
  // src/doctor-cmd.ts
998
- import { existsSync as existsSync3, readFileSync, statSync } from "fs";
999
- import { readFile as readFile5 } from "fs/promises";
1000
- import { homedir as homedir5 } from "os";
1001
- import { join as join6 } from "path";
1361
+ import { existsSync as existsSync4, readFileSync, statSync } from "fs";
1362
+ import { readFile as readFile7 } from "fs/promises";
1363
+ import { homedir as homedir8 } from "os";
1364
+ import { join as join8 } from "path";
1002
1365
 
1003
1366
  // src/analytics.ts
1004
1367
  import { request as request3 } from "undici";
@@ -1305,8 +1668,8 @@ function formatShadowLine(server) {
1305
1668
  }
1306
1669
 
1307
1670
  // src/install-targets.ts
1308
- import { homedir as homedir2 } from "os";
1309
- import { join as join3 } from "path";
1671
+ import { homedir as homedir5 } from "os";
1672
+ import { join as join5 } from "path";
1310
1673
  var CURRENT_OS = process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux";
1311
1674
  var INSTALL_TARGETS = [
1312
1675
  {
@@ -1387,8 +1750,8 @@ var INSTALL_TARGETS = [
1387
1750
  }
1388
1751
  ];
1389
1752
  function resolveInstallPath(opts) {
1390
- const home = opts.home ?? homedir2();
1391
- const appData = opts.appData ?? process.env.APPDATA ?? join3(home, "AppData", "Roaming");
1753
+ const home = opts.home ?? homedir5();
1754
+ const appData = opts.appData ?? process.env.APPDATA ?? join5(home, "AppData", "Roaming");
1392
1755
  const { clientId, scope, os, projectDir, claudeConfigDir } = opts;
1393
1756
  const target = INSTALL_TARGETS.find((t) => t.clientId === clientId);
1394
1757
  if (!target) throw new Error(`Unknown client: ${clientId}`);
@@ -1415,25 +1778,25 @@ function pathFor(client, scope, os, base) {
1415
1778
  if (client === "claude-code") {
1416
1779
  if (scope === "user") {
1417
1780
  if (claudeConfigDir) {
1418
- const absolute = join3(claudeConfigDir, ".claude.json");
1781
+ const absolute = join5(claudeConfigDir, ".claude.json");
1419
1782
  return { absolute, display: absolute, containerPath: ["mcpServers"] };
1420
1783
  }
1421
1784
  const display = os === "windows" ? "%USERPROFILE%\\.claude.json" : "~/.claude.json";
1422
- return { absolute: join3(home, ".claude.json"), display, containerPath: ["mcpServers"] };
1785
+ return { absolute: join5(home, ".claude.json"), display, containerPath: ["mcpServers"] };
1423
1786
  }
1424
1787
  if (scope === "project") {
1425
1788
  return {
1426
- absolute: join3(projectDir, ".mcp.json"),
1789
+ absolute: join5(projectDir, ".mcp.json"),
1427
1790
  display: joinPath("<project folder>", ".mcp.json"),
1428
1791
  containerPath: ["mcpServers"]
1429
1792
  };
1430
1793
  }
1431
1794
  if (claudeConfigDir) {
1432
- const absolute = join3(claudeConfigDir, ".claude.json");
1795
+ const absolute = join5(claudeConfigDir, ".claude.json");
1433
1796
  return { absolute, display: absolute, containerPath: ["projects", projectDir, "mcpServers"] };
1434
1797
  }
1435
1798
  return {
1436
- absolute: join3(home, ".claude.json"),
1799
+ absolute: join5(home, ".claude.json"),
1437
1800
  display: os === "windows" ? "%USERPROFILE%\\.claude.json" : "~/.claude.json",
1438
1801
  containerPath: ["projects", projectDir, "mcpServers"]
1439
1802
  };
@@ -1441,14 +1804,14 @@ function pathFor(client, scope, os, base) {
1441
1804
  if (client === "claude-desktop") {
1442
1805
  if (os === "macos") {
1443
1806
  return {
1444
- absolute: join3(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
1807
+ absolute: join5(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
1445
1808
  display: "~/Library/Application Support/Claude/claude_desktop_config.json",
1446
1809
  containerPath: ["mcpServers"]
1447
1810
  };
1448
1811
  }
1449
1812
  if (os === "windows") {
1450
1813
  return {
1451
- absolute: join3(appData, "Claude", "claude_desktop_config.json"),
1814
+ absolute: join5(appData, "Claude", "claude_desktop_config.json"),
1452
1815
  display: "%APPDATA%\\Claude\\claude_desktop_config.json",
1453
1816
  containerPath: ["mcpServers"]
1454
1817
  };
@@ -1458,17 +1821,17 @@ function pathFor(client, scope, os, base) {
1458
1821
  if (client === "cursor") {
1459
1822
  if (scope === "user") {
1460
1823
  const display = os === "windows" ? "%USERPROFILE%\\.cursor\\mcp.json" : "~/.cursor/mcp.json";
1461
- return { absolute: join3(home, ".cursor", "mcp.json"), display, containerPath: ["mcpServers"] };
1824
+ return { absolute: join5(home, ".cursor", "mcp.json"), display, containerPath: ["mcpServers"] };
1462
1825
  }
1463
1826
  return {
1464
- absolute: join3(projectDir, ".cursor", "mcp.json"),
1827
+ absolute: join5(projectDir, ".cursor", "mcp.json"),
1465
1828
  display: joinPath("<project folder>", ".cursor", "mcp.json"),
1466
1829
  containerPath: ["mcpServers"]
1467
1830
  };
1468
1831
  }
1469
1832
  if (client === "vscode") {
1470
1833
  return {
1471
- absolute: join3(projectDir, ".vscode", "mcp.json"),
1834
+ absolute: join5(projectDir, ".vscode", "mcp.json"),
1472
1835
  display: joinPath("<project folder>", ".vscode", "mcp.json"),
1473
1836
  containerPath: ["servers"]
1474
1837
  };
@@ -1501,14 +1864,14 @@ var CLAUDE_CODE_ALLOW_PATTERN = "mcp__mcp__*";
1501
1864
  function resolveClaudeCodeSettingsPath(scope, opts) {
1502
1865
  const { home, projectDir, claudeConfigDir } = opts;
1503
1866
  const cfgDir = claudeConfigDir && claudeConfigDir.length > 0 ? claudeConfigDir : null;
1504
- if (scope === "user") return cfgDir ? join3(cfgDir, "settings.json") : join3(home, ".claude", "settings.json");
1505
- if (scope === "project" && projectDir) return join3(projectDir, ".claude", "settings.json");
1506
- if (scope === "local" && projectDir) return join3(projectDir, ".claude", "settings.local.json");
1867
+ if (scope === "user") return cfgDir ? join5(cfgDir, "settings.json") : join5(home, ".claude", "settings.json");
1868
+ if (scope === "project" && projectDir) return join5(projectDir, ".claude", "settings.json");
1869
+ if (scope === "local" && projectDir) return join5(projectDir, ".claude", "settings.local.json");
1507
1870
  return null;
1508
1871
  }
1509
1872
 
1510
1873
  // src/persistence.ts
1511
- import { readFile as readFile2 } from "fs/promises";
1874
+ import { readFile as readFile4 } from "fs/promises";
1512
1875
  import path from "path";
1513
1876
  var STATE_SCHEMA_VERSION = 1;
1514
1877
  var STATE_FILENAME = "state.json";
@@ -1520,7 +1883,7 @@ function emptyState() {
1520
1883
  }
1521
1884
  async function loadState(filePath = statePath()) {
1522
1885
  try {
1523
- const raw = await readFile2(filePath, "utf8");
1886
+ const raw = await readFile4(filePath, "utf8");
1524
1887
  const parsed = JSON.parse(raw);
1525
1888
  if (!parsed || typeof parsed !== "object") return emptyState();
1526
1889
  if (parsed.version !== STATE_SCHEMA_VERSION) return emptyState();
@@ -1630,11 +1993,11 @@ async function reportTools(serverId, tools) {
1630
1993
  }
1631
1994
 
1632
1995
  // src/try-cmd.ts
1633
- import { createHash } from "crypto";
1634
- import { existsSync as existsSync2 } from "fs";
1635
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile4, readdir, unlink } from "fs/promises";
1636
- import { homedir as homedir4, hostname, userInfo } from "os";
1637
- import { join as join5, resolve as resolve3 } from "path";
1996
+ import { createHash as createHash2 } from "crypto";
1997
+ import { existsSync as existsSync3 } from "fs";
1998
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile6, readdir, unlink } from "fs/promises";
1999
+ import { homedir as homedir7, hostname, userInfo } from "os";
2000
+ import { join as join7, resolve as resolve3 } from "path";
1638
2001
  import { request as request5 } from "undici";
1639
2002
 
1640
2003
  // src/catalog.ts
@@ -1733,10 +2096,10 @@ async function resolveCatalogSlug(slug, opts = {}) {
1733
2096
  }
1734
2097
 
1735
2098
  // src/install-cmd.ts
1736
- import { existsSync } from "fs";
1737
- import { chmod, readFile as readFile3 } from "fs/promises";
1738
- import { homedir as homedir3 } from "os";
1739
- import { join as join4, resolve as resolve2 } from "path";
2099
+ import { existsSync as existsSync2 } from "fs";
2100
+ import { chmod, readFile as readFile5 } from "fs/promises";
2101
+ import { homedir as homedir6 } from "os";
2102
+ import { join as join6, resolve as resolve2 } from "path";
1740
2103
  import { createInterface } from "readline/promises";
1741
2104
  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)";
1742
2105
  async function runInstall(opts) {
@@ -1821,10 +2184,10 @@ ${USAGE}`);
1821
2184
  let existing = {};
1822
2185
  let existingHasEntry = false;
1823
2186
  let legacyEntry = null;
1824
- if (existsSync(resolved.absolute)) {
2187
+ if (existsSync2(resolved.absolute)) {
1825
2188
  let raw;
1826
2189
  try {
1827
- raw = await readFile3(resolved.absolute, "utf8");
2190
+ raw = await readFile5(resolved.absolute, "utf8");
1828
2191
  } catch (e) {
1829
2192
  err(`yaw-mcp install: cannot read ${resolved.absolute}: ${e.message}`);
1830
2193
  return { written: [], wouldWrite: [], messages, exitCode: 1 };
@@ -1881,8 +2244,8 @@ ${USAGE}`);
1881
2244
  const clientJson = `${JSON.stringify(merged, null, 2)}
1882
2245
  `;
1883
2246
  const writeYawMcpConfig = !opts.skipYawMcpConfig && token5 !== null;
1884
- const home = opts.home ?? homedir3();
1885
- const yawMcpConfigPath = join4(home, CONFIG_DIRNAME, CONFIG_FILENAME);
2247
+ const home = opts.home ?? homedir6();
2248
+ const yawMcpConfigPath = join6(home, CONFIG_DIRNAME, CONFIG_FILENAME);
1886
2249
  const yawMcpConfigComposed = writeYawMcpConfig ? await composeYawMcpConfig(yawMcpConfigPath, token5) : { json: "" };
1887
2250
  if ("backupPath" in yawMcpConfigComposed && yawMcpConfigComposed.backupPath) {
1888
2251
  log2(
@@ -1972,9 +2335,9 @@ async function prepareClaudeCodeSettingsPatch(opts) {
1972
2335
  });
1973
2336
  if (!path3) return null;
1974
2337
  let existing = {};
1975
- if (existsSync(path3)) {
2338
+ if (existsSync2(path3)) {
1976
2339
  try {
1977
- const raw = await readFile3(path3, "utf8");
2340
+ const raw = await readFile5(path3, "utf8");
1978
2341
  if (raw.trim().length > 0) {
1979
2342
  const parsed = parseJsonc(raw);
1980
2343
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
@@ -2079,10 +2442,10 @@ function removeFromClientConfig(existing, containerPath, entryName) {
2079
2442
  async function composeYawMcpConfig(path3, token5) {
2080
2443
  let existing = {};
2081
2444
  let backupPath;
2082
- if (existsSync(path3)) {
2445
+ if (existsSync2(path3)) {
2083
2446
  let raw = "";
2084
2447
  try {
2085
- raw = await readFile3(path3, "utf8");
2448
+ raw = await readFile5(path3, "utf8");
2086
2449
  } catch {
2087
2450
  raw = "";
2088
2451
  }
@@ -2193,7 +2556,7 @@ ${USAGE}` };
2193
2556
  return { ok: true, options: opts };
2194
2557
  }
2195
2558
  async function runInstallList(opts, log2) {
2196
- const home = opts.home ?? homedir3();
2559
+ const home = opts.home ?? homedir6();
2197
2560
  const cwd = opts.cwd ?? process.cwd();
2198
2561
  const os = opts.os ?? CURRENT_OS;
2199
2562
  const probes = await probeClientsAsync({ home, os, cwd, claudeConfigDir: opts.claudeConfigDir });
@@ -2440,17 +2803,17 @@ function parseDurationMs(s) {
2440
2803
  const factor = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
2441
2804
  return n * factor;
2442
2805
  }
2443
- function trialsDir(home = homedir4()) {
2444
- return join5(home, CONFIG_DIRNAME, TRIALS_DIRNAME);
2806
+ function trialsDir(home = homedir7()) {
2807
+ return join7(home, CONFIG_DIRNAME, TRIALS_DIRNAME);
2445
2808
  }
2446
- function trialMarkerPath(slug, home = homedir4()) {
2447
- return join5(trialsDir(home), `${slug}.json`);
2809
+ function trialMarkerPath(slug, home = homedir7()) {
2810
+ return join7(trialsDir(home), `${slug}.json`);
2448
2811
  }
2449
- function anonIdPath(home = homedir4()) {
2450
- return join5(trialsDir(home), ANON_FILENAME);
2812
+ function anonIdPath(home = homedir7()) {
2813
+ return join7(trialsDir(home), ANON_FILENAME);
2451
2814
  }
2452
2815
  function computeAnonId() {
2453
- const h = createHash("sha256");
2816
+ const h = createHash2("sha256");
2454
2817
  h.update(hostname());
2455
2818
  try {
2456
2819
  h.update(userInfo().username);
@@ -2458,11 +2821,11 @@ function computeAnonId() {
2458
2821
  }
2459
2822
  return h.digest("hex").slice(0, 16);
2460
2823
  }
2461
- async function loadOrCreateAnonId(home = homedir4()) {
2824
+ async function loadOrCreateAnonId(home = homedir7()) {
2462
2825
  const path3 = anonIdPath(home);
2463
- if (existsSync2(path3)) {
2826
+ if (existsSync3(path3)) {
2464
2827
  try {
2465
- const raw = (await readFile4(path3, "utf8")).trim();
2828
+ const raw = (await readFile6(path3, "utf8")).trim();
2466
2829
  if (/^[0-9a-f]{16}$/.test(raw)) return raw;
2467
2830
  } catch {
2468
2831
  }
@@ -2542,7 +2905,7 @@ async function runTry(opts) {
2542
2905
  return { exitCode: 2, written: [] };
2543
2906
  }
2544
2907
  const env = opts.env ?? process.env;
2545
- const home = opts.home ?? homedir4();
2908
+ const home = opts.home ?? homedir7();
2546
2909
  const cwd = opts.cwd ?? process.cwd();
2547
2910
  const os = opts.os ?? CURRENT_OS;
2548
2911
  const now = opts.now ? opts.now() : Date.now();
@@ -2609,9 +2972,9 @@ async function runTry(opts) {
2609
2972
  createdAt: now
2610
2973
  };
2611
2974
  let existing = {};
2612
- if (existsSync2(resolved.absolute)) {
2975
+ if (existsSync3(resolved.absolute)) {
2613
2976
  try {
2614
- const raw = await readFile4(resolved.absolute, "utf8");
2977
+ const raw = await readFile6(resolved.absolute, "utf8");
2615
2978
  if (raw.trim().length > 0) {
2616
2979
  const parsed = parseJsonc(raw);
2617
2980
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
@@ -2683,16 +3046,16 @@ async function runTryCleanup(opts) {
2683
3046
  return { exitCode: 2, written: [] };
2684
3047
  }
2685
3048
  const env = opts.env ?? process.env;
2686
- const home = opts.home ?? homedir4();
3049
+ const home = opts.home ?? homedir7();
2687
3050
  const baseUrl = opts.baseUrl ?? env.YAW_MCP_BASE_URL ?? DEFAULT_BASE_URL;
2688
3051
  const markerPath = trialMarkerPath(slug, home);
2689
- if (!existsSync2(markerPath)) {
3052
+ if (!existsSync3(markerPath)) {
2690
3053
  print(`yaw-mcp try-cleanup: no trial marker for "${slug}" (nothing to do).`);
2691
3054
  return { exitCode: 0, written: [] };
2692
3055
  }
2693
3056
  let marker;
2694
3057
  try {
2695
- const raw = await readFile4(markerPath, "utf8");
3058
+ const raw = await readFile6(markerPath, "utf8");
2696
3059
  const parsed = JSON.parse(raw);
2697
3060
  if (!parsed || typeof parsed !== "object" || typeof parsed.entryName !== "string") {
2698
3061
  throw new Error("marker is missing required fields");
@@ -2702,9 +3065,9 @@ async function runTryCleanup(opts) {
2702
3065
  printErr(`yaw-mcp try-cleanup: marker at ${markerPath} is unreadable (${e.message}).`);
2703
3066
  return { exitCode: 1, written: [] };
2704
3067
  }
2705
- if (existsSync2(marker.clientPath)) {
3068
+ if (existsSync3(marker.clientPath)) {
2706
3069
  try {
2707
- const raw = await readFile4(marker.clientPath, "utf8");
3070
+ const raw = await readFile6(marker.clientPath, "utf8");
2708
3071
  if (raw.trim().length > 0) {
2709
3072
  const parsed = parseJsonc(raw);
2710
3073
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
@@ -2746,11 +3109,11 @@ function formatTtl(ms) {
2746
3109
  return `${Math.round(clamped / 864e5)}d`;
2747
3110
  }
2748
3111
  async function scanTrials(opts = {}) {
2749
- const home = opts.home ?? homedir4();
3112
+ const home = opts.home ?? homedir7();
2750
3113
  const now = opts.now ? opts.now() : Date.now();
2751
3114
  const dir = trialsDir(home);
2752
3115
  const result = { live: [], expired: [], malformed: [] };
2753
- if (!existsSync2(dir)) return result;
3116
+ if (!existsSync3(dir)) return result;
2754
3117
  let entries;
2755
3118
  try {
2756
3119
  entries = await readdir(dir);
@@ -2759,9 +3122,9 @@ async function scanTrials(opts = {}) {
2759
3122
  }
2760
3123
  for (const filename of entries) {
2761
3124
  if (!filename.endsWith(".json")) continue;
2762
- const path3 = join5(dir, filename);
3125
+ const path3 = join7(dir, filename);
2763
3126
  try {
2764
- const raw = await readFile4(path3, "utf8");
3127
+ const raw = await readFile6(path3, "utf8");
2765
3128
  const parsed = JSON.parse(raw);
2766
3129
  if (!parsed || typeof parsed !== "object" || typeof parsed.slug !== "string" || typeof parsed.expiresAt !== "number" || typeof parsed.clientPath !== "string" || !Array.isArray(parsed.containerPath) || typeof parsed.entryName !== "string") {
2767
3130
  result.malformed.push(path3);
@@ -2779,7 +3142,7 @@ async function scanTrials(opts = {}) {
2779
3142
  return result;
2780
3143
  }
2781
3144
  async function gcExpiredTrials(opts) {
2782
- const home = opts.home ?? homedir4();
3145
+ const home = opts.home ?? homedir7();
2783
3146
  const env = opts.env ?? process.env;
2784
3147
  const baseUrl = opts.baseUrl ?? env.YAW_MCP_BASE_URL ?? DEFAULT_BASE_URL;
2785
3148
  const postEvent = opts.postEvent ?? defaultPostEvent;
@@ -2790,8 +3153,8 @@ async function gcExpiredTrials(opts) {
2790
3153
  let failed = 0;
2791
3154
  for (const { marker } of scan.expired) {
2792
3155
  try {
2793
- if (existsSync2(marker.clientPath)) {
2794
- const raw = await readFile4(marker.clientPath, "utf8");
3156
+ if (existsSync3(marker.clientPath)) {
3157
+ const raw = await readFile6(marker.clientPath, "utf8");
2795
3158
  if (raw.trim().length > 0) {
2796
3159
  const parsed = parseJsonc(raw);
2797
3160
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
@@ -2820,6 +3183,8 @@ async function gcExpiredTrials(opts) {
2820
3183
 
2821
3184
  // src/upgrade-cmd.ts
2822
3185
  import { spawn as spawn2 } from "child_process";
3186
+ import { realpathSync } from "fs";
3187
+ var BINARY_DOWNLOAD_URL = "https://github.com/YawLabs/mcp/releases/latest";
2823
3188
  var UPGRADE_USAGE = `Usage: yaw-mcp upgrade [--run] [--json]
2824
3189
 
2825
3190
  Show (or execute) the command to upgrade @yawlabs/mcp to the latest version.
@@ -2860,6 +3225,52 @@ function localInstallRoot(argvPath) {
2860
3225
  const idx = argvPath.replace(/\\/g, "/").indexOf("/node_modules/");
2861
3226
  return idx > 0 ? argvPath.slice(0, idx) : null;
2862
3227
  }
3228
+ async function defaultNpmPrefix() {
3229
+ if (process.env.VITEST) return null;
3230
+ return new Promise((resolve5) => {
3231
+ const child = spawn2("npm", ["prefix", "-g"], {
3232
+ shell: process.platform === "win32",
3233
+ stdio: ["ignore", "pipe", "ignore"]
3234
+ });
3235
+ let out = "";
3236
+ const timer = setTimeout(() => {
3237
+ child.kill();
3238
+ resolve5(null);
3239
+ }, 3e3);
3240
+ child.stdout?.on("data", (d) => {
3241
+ out += String(d);
3242
+ });
3243
+ child.on("close", (code) => {
3244
+ clearTimeout(timer);
3245
+ resolve5(code === 0 && out.trim() ? out.trim() : null);
3246
+ });
3247
+ child.on("error", () => {
3248
+ clearTimeout(timer);
3249
+ resolve5(null);
3250
+ });
3251
+ });
3252
+ }
3253
+ function comparablePath(p) {
3254
+ let real = p;
3255
+ try {
3256
+ real = realpathSync(p);
3257
+ } catch {
3258
+ }
3259
+ const normalized = real.replace(/\\/g, "/");
3260
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
3261
+ }
3262
+ async function refineInstallMethod(method, argvPath, npmPrefix = defaultNpmPrefix) {
3263
+ if (method !== "local-node-modules" && method !== "unknown") return method;
3264
+ if (!argvPath) return method;
3265
+ const prefix = await npmPrefix();
3266
+ if (!prefix) return method;
3267
+ const entry = comparablePath(argvPath);
3268
+ const pfx = comparablePath(prefix);
3269
+ if (entry.startsWith(`${pfx}/node_modules/`) || entry.startsWith(`${pfx}/lib/node_modules/`)) {
3270
+ return "global-npm";
3271
+ }
3272
+ return method;
3273
+ }
2863
3274
  function buildUpgradePlan(input) {
2864
3275
  const { current, latest, method } = input;
2865
3276
  const stale = latest !== null && current !== "dev" && compareSemverLocal(current, latest) < 0;
@@ -2886,6 +3297,9 @@ function buildUpgradePlan(input) {
2886
3297
  case "dev-checkout":
2887
3298
  command = "git pull && npm run build";
2888
3299
  break;
3300
+ case "binary":
3301
+ command = null;
3302
+ break;
2889
3303
  default:
2890
3304
  command = "npm install -g @yawlabs/mcp@latest";
2891
3305
  break;
@@ -2931,6 +3345,17 @@ async function defaultSpawn(cmd, args, cwd) {
2931
3345
  child.on("error", () => resolve5(1));
2932
3346
  });
2933
3347
  }
3348
+ async function detectSea() {
3349
+ if (process.env.ELECTRON_RUN_AS_NODE) return false;
3350
+ const exe = process.execPath.replace(/\\/g, "/").split("/").pop()?.toLowerCase() ?? "";
3351
+ if (exe === "node" || exe === "node.exe") return false;
3352
+ try {
3353
+ const sea = await import("sea");
3354
+ return typeof sea.isSea === "function" && sea.isSea() === true;
3355
+ } catch {
3356
+ return false;
3357
+ }
3358
+ }
2934
3359
  async function runUpgrade(opts = {}) {
2935
3360
  const write = opts.out ?? ((s) => process.stdout.write(s));
2936
3361
  const writeErr = opts.err ?? ((s) => process.stderr.write(s));
@@ -2948,7 +3373,8 @@ async function runUpgrade(opts = {}) {
2948
3373
  const fetcher = opts.fetchLatest ?? defaultFetchLatest;
2949
3374
  const current = opts.currentVersion ?? readCurrentVersion();
2950
3375
  const argvPath = opts.argvPath ?? process.argv[1];
2951
- const method = detectInstallMethod(argvPath);
3376
+ const sea = opts.isSea ? opts.isSea() : await detectSea();
3377
+ const method = sea ? "binary" : await refineInstallMethod(detectInstallMethod(argvPath), argvPath, opts.npmPrefix);
2952
3378
  let latest;
2953
3379
  try {
2954
3380
  latest = await fetcher();
@@ -2968,6 +3394,9 @@ async function runUpgrade(opts = {}) {
2968
3394
  print(` ${plan.command}`);
2969
3395
  } else if (method === "bundled-app") {
2970
3396
  print("This copy of yaw-mcp ships inside Yaw Terminal and updates with the app \u2014 nothing to run.");
3397
+ } else if (method === "binary") {
3398
+ print("yaw-mcp is a standalone binary \u2014 download the latest build and replace");
3399
+ print(`this executable: ${BINARY_DOWNLOAD_URL}`);
2971
3400
  } else {
2972
3401
  print("Your install uses `npx -y` \u2014 just restart the MCP client when you're back online.");
2973
3402
  }
@@ -2991,6 +3420,13 @@ async function runUpgrade(opts = {}) {
2991
3420
  print("there is nothing to run here. Update Yaw Terminal to get the new version.");
2992
3421
  return { exitCode: 0, lines };
2993
3422
  }
3423
+ if (method === "binary") {
3424
+ print("yaw-mcp is running as a standalone binary \u2014 there's no package manager");
3425
+ print("to upgrade it. Download the latest build and replace this executable:");
3426
+ print("");
3427
+ print(` ${BINARY_DOWNLOAD_URL}`);
3428
+ return { exitCode: opts.run ? 2 : 1, lines };
3429
+ }
2994
3430
  if (!plan.command) {
2995
3431
  print("No upgrade command available for this install method.");
2996
3432
  return { exitCode: 0, lines };
@@ -3033,7 +3469,7 @@ async function runUpgrade(opts = {}) {
3033
3469
  return { exitCode: 3, lines };
3034
3470
  }
3035
3471
  function readCurrentVersion() {
3036
- return true ? "0.60.2" : "dev";
3472
+ return true ? "0.60.6" : "dev";
3037
3473
  }
3038
3474
 
3039
3475
  // src/usage-hints.ts
@@ -3095,7 +3531,7 @@ function selectFlakyNamespaces(entries, limit) {
3095
3531
  }
3096
3532
 
3097
3533
  // src/doctor-cmd.ts
3098
- var VERSION = true ? "0.60.2" : "dev";
3534
+ var VERSION = true ? "0.60.6" : "dev";
3099
3535
  async function runDoctor(opts = {}) {
3100
3536
  if (opts.json) return runDoctorJson(opts);
3101
3537
  const lines = [];
@@ -3106,7 +3542,7 @@ async function runDoctor(opts = {}) {
3106
3542
  `);
3107
3543
  };
3108
3544
  const cwd = opts.cwd ?? process.cwd();
3109
- const home = opts.home ?? homedir5();
3545
+ const home = opts.home ?? homedir8();
3110
3546
  const os = opts.os ?? CURRENT_OS;
3111
3547
  const env = opts.env ?? process.env;
3112
3548
  print(`yaw-mcp doctor \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`);
@@ -3167,7 +3603,7 @@ async function runDoctor(opts = {}) {
3167
3603
  const latest = skipCheck ? null : await fetchLatestVersion(opts.registryFetch);
3168
3604
  const staleHint = latest && VERSION !== "dev" && compareSemver(VERSION, latest) < 0 ? latest : null;
3169
3605
  if (staleHint) {
3170
- const method = detectInstallMethod(process.argv[1]);
3606
+ const method = await detectSea() ? "binary" : await refineInstallMethod(detectInstallMethod(process.argv[1]), process.argv[1]);
3171
3607
  print("UPGRADE AVAILABLE");
3172
3608
  if (method === "bundled-app") {
3173
3609
  print(` Running ${VERSION}; npm latest is ${staleHint}. This copy ships inside`);
@@ -3175,6 +3611,10 @@ async function runDoctor(opts = {}) {
3175
3611
  } else if (method === "npx") {
3176
3612
  print(` Running ${VERSION}; npm latest is ${staleHint}. npx fetches the latest`);
3177
3613
  print(" on each spawn \u2014 restart your MCP client to pick it up.");
3614
+ } else if (method === "binary") {
3615
+ print(` Running ${VERSION}; npm latest is ${staleHint}. This is a standalone`);
3616
+ print(" binary \u2014 download the latest build and replace the executable:");
3617
+ print(` ${BINARY_DOWNLOAD_URL}`);
3178
3618
  } else if (method === "global-npm" || method === "pnpm-global" || method === "bun-global" || method === "local-node-modules") {
3179
3619
  print(` Running ${VERSION}; npm latest is ${staleHint}. To upgrade in place:`);
3180
3620
  print("");
@@ -3208,7 +3648,7 @@ async function runDoctorJson(opts) {
3208
3648
  const lines = [];
3209
3649
  const write = opts.out ?? ((s) => process.stdout.write(s));
3210
3650
  const cwd = opts.cwd ?? process.cwd();
3211
- const home = opts.home ?? homedir5();
3651
+ const home = opts.home ?? homedir8();
3212
3652
  const os = opts.os ?? CURRENT_OS;
3213
3653
  const env = opts.env ?? process.env;
3214
3654
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -3231,7 +3671,7 @@ async function runDoctorJson(opts) {
3231
3671
  const persistRaw = env.YAW_MCP_DISABLE_PERSISTENCE;
3232
3672
  const persistDisabled = persistRaw !== void 0 && persistRaw !== "" && (persistRaw === "1" || persistRaw.toLowerCase() === "true");
3233
3673
  const state = persistDisabled ? { disabled: true, path: null, savedAt: null, learningEntries: null, packHistoryEntries: null } : await (async () => {
3234
- const filePath = join6(userConfigDir(home), STATE_FILENAME);
3674
+ const filePath = join8(userConfigDir(home), STATE_FILENAME);
3235
3675
  const persisted = await loadState(filePath);
3236
3676
  const fresh = persisted.savedAt === 0;
3237
3677
  return {
@@ -3244,7 +3684,7 @@ async function runDoctorJson(opts) {
3244
3684
  })();
3245
3685
  const reliability = [];
3246
3686
  if (!persistDisabled) {
3247
- const filePath = join6(userConfigDir(home), STATE_FILENAME);
3687
+ const filePath = join8(userConfigDir(home), STATE_FILENAME);
3248
3688
  const persisted = await loadState(filePath);
3249
3689
  if (persisted.savedAt !== 0) {
3250
3690
  const entries = Object.entries(persisted.learning).map(([namespace, usage]) => ({ namespace, usage }));
@@ -3329,7 +3769,7 @@ async function renderStateSection(opts) {
3329
3769
  print("");
3330
3770
  return;
3331
3771
  }
3332
- const filePath = join6(userConfigDir(home), STATE_FILENAME);
3772
+ const filePath = join8(userConfigDir(home), STATE_FILENAME);
3333
3773
  print(` path: ${filePath}`);
3334
3774
  const peek = await peekStateFile(filePath);
3335
3775
  if (peek.kind === "malformed") {
@@ -3363,7 +3803,7 @@ async function renderStateSection(opts) {
3363
3803
  async function peekStateFile(filePath) {
3364
3804
  let raw;
3365
3805
  try {
3366
- raw = await readFile5(filePath, "utf8");
3806
+ raw = await readFile7(filePath, "utf8");
3367
3807
  } catch (err) {
3368
3808
  if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
3369
3809
  return { kind: "missing" };
@@ -3388,7 +3828,7 @@ async function renderReliabilitySection(opts) {
3388
3828
  const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
3389
3829
  const disabled = raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
3390
3830
  if (disabled) return;
3391
- const filePath = join6(userConfigDir(home), STATE_FILENAME);
3831
+ const filePath = join8(userConfigDir(home), STATE_FILENAME);
3392
3832
  const persisted = await loadState(filePath);
3393
3833
  if (persisted.savedAt === 0) return;
3394
3834
  const entries = Object.entries(persisted.learning).map(([namespace, usage]) => ({ namespace, usage }));
@@ -3494,7 +3934,7 @@ function probeClients(opts) {
3494
3934
  } catch {
3495
3935
  continue;
3496
3936
  }
3497
- const exists3 = existsSync3(resolved.absolute);
3937
+ const exists3 = existsSync4(resolved.absolute);
3498
3938
  let hasMcpEntry = false;
3499
3939
  let hasLegacyEntry = false;
3500
3940
  let legacyEntryName = null;
@@ -3571,14 +4011,14 @@ async function probeClientsAsync(opts) {
3571
4011
  projectDir: scope.requiresProjectDir ? opts.cwd : void 0,
3572
4012
  claudeConfigDir: opts.claudeConfigDir
3573
4013
  });
3574
- const exists3 = existsSync3(resolved.absolute);
4014
+ const exists3 = existsSync4(resolved.absolute);
3575
4015
  let hasMcpEntry = false;
3576
4016
  let hasLegacyEntry = false;
3577
4017
  let legacyEntryName = null;
3578
4018
  let malformed = false;
3579
4019
  if (exists3) {
3580
4020
  try {
3581
- const raw = await readFile5(resolved.absolute, "utf8");
4021
+ const raw = await readFile7(resolved.absolute, "utf8");
3582
4022
  if (raw.trim().length > 0) {
3583
4023
  const parsed = parseJsonc(raw);
3584
4024
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
@@ -3660,9 +4100,9 @@ function scanShellHistoryForShadows(opts) {
3660
4100
  }
3661
4101
  function shellHistorySources(opts) {
3662
4102
  const sources = [];
3663
- sources.push({ path: join6(opts.home, ".bash_history"), extractCommand: (l) => l.trim() || null });
4103
+ sources.push({ path: join8(opts.home, ".bash_history"), extractCommand: (l) => l.trim() || null });
3664
4104
  sources.push({
3665
- path: join6(opts.home, ".zsh_history"),
4105
+ path: join8(opts.home, ".zsh_history"),
3666
4106
  // Zsh extended-history lines look like `: 1700000000:0;npm audit`.
3667
4107
  // Strip the metadata prefix so we get just the command.
3668
4108
  extractCommand: (l) => {
@@ -3678,7 +4118,7 @@ function shellHistorySources(opts) {
3678
4118
  const appData = opts.env.APPDATA;
3679
4119
  if (appData) {
3680
4120
  sources.push({
3681
- path: join6(appData, "Microsoft", "Windows", "PowerShell", "PSReadLine", "ConsoleHost_history.txt"),
4121
+ path: join8(appData, "Microsoft", "Windows", "PowerShell", "PSReadLine", "ConsoleHost_history.txt"),
3682
4122
  extractCommand: (l) => l.trim() || null
3683
4123
  });
3684
4124
  }
@@ -3786,190 +4226,7 @@ function closestNames(query, candidates, limit) {
3786
4226
  }
3787
4227
 
3788
4228
  // src/local-add-cmd.ts
3789
- import { homedir as homedir7 } from "os";
3790
-
3791
- // src/local-bundles.ts
3792
- import { createHash as createHash2 } from "crypto";
3793
- import { existsSync as existsSync4 } from "fs";
3794
- import { readFile as readFile6 } from "fs/promises";
3795
- import { homedir as homedir6 } from "os";
3796
- import { join as join7 } from "path";
3797
- var BUNDLES_FILENAME = "bundles.json";
3798
- var CURRENT_BUNDLES_SCHEMA_VERSION = 1;
3799
- function localBundlesPath(configDir) {
3800
- return join7(configDir, BUNDLES_FILENAME);
3801
- }
3802
- var NAMESPACE_RE = /^[a-z][a-z0-9_]{0,29}$/;
3803
- function validateEntry(entry, warnings) {
3804
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3805
- warnings.push("bundles.json: skipping non-object server entry");
3806
- return null;
3807
- }
3808
- const e = entry;
3809
- const namespace = typeof e.namespace === "string" ? e.namespace : "";
3810
- if (!namespace || !NAMESPACE_RE.test(namespace)) {
3811
- warnings.push(`bundles.json: skipping server with invalid namespace ${JSON.stringify(namespace)}`);
3812
- return null;
3813
- }
3814
- const name = typeof e.name === "string" && e.name.length > 0 ? e.name : namespace;
3815
- const type = e.type === "remote" ? "remote" : "local";
3816
- const transport = e.transport === "streamable-http" || e.transport === "sse" || e.transport === "stdio" ? e.transport : void 0;
3817
- const command = typeof e.command === "string" ? e.command : void 0;
3818
- const args = Array.isArray(e.args) ? e.args.filter((a) => typeof a === "string") : void 0;
3819
- const env = e.env && typeof e.env === "object" && !Array.isArray(e.env) ? Object.fromEntries(
3820
- Object.entries(e.env).filter(([, v]) => typeof v === "string")
3821
- ) : void 0;
3822
- const url = typeof e.url === "string" ? e.url : void 0;
3823
- const description = typeof e.description === "string" ? e.description : void 0;
3824
- const isActive = e.isActive !== false;
3825
- const id = typeof e.id === "string" && e.id.length > 0 ? e.id : `local-${namespace}`;
3826
- return {
3827
- id,
3828
- name,
3829
- namespace,
3830
- type,
3831
- transport,
3832
- command,
3833
- args,
3834
- env,
3835
- url,
3836
- isActive,
3837
- description
3838
- };
3839
- }
3840
- async function readBundlesAt(path3, warnings) {
3841
- let raw;
3842
- try {
3843
- raw = await readFile6(path3, "utf8");
3844
- } catch {
3845
- return { exists: false, file: null };
3846
- }
3847
- let parsed;
3848
- try {
3849
- parsed = parseJsonc(raw);
3850
- } catch (err) {
3851
- const msg = err instanceof Error ? err.message : String(err);
3852
- warnings.push(`${path3}: invalid JSON (${msg}) -- file ignored`);
3853
- log("warn", "bundles.json is not valid JSON; ignoring", { path: path3, error: msg });
3854
- return { exists: true, file: null };
3855
- }
3856
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3857
- warnings.push(`${path3}: root must be a JSON object -- file ignored`);
3858
- return { exists: true, file: null };
3859
- }
3860
- const obj = parsed;
3861
- const version = typeof obj.version === "number" ? obj.version : void 0;
3862
- if (version !== void 0 && version > CURRENT_BUNDLES_SCHEMA_VERSION) {
3863
- warnings.push(
3864
- `${path3}: schema version ${version} is newer than this yaw-mcp (${CURRENT_BUNDLES_SCHEMA_VERSION}); upgrade with \`npm i -g @yawlabs/mcp@latest\`. Loading best-effort.`
3865
- );
3866
- }
3867
- const rawServers = obj.servers;
3868
- if (!Array.isArray(rawServers)) {
3869
- warnings.push(`${path3}: 'servers' must be an array -- file ignored`);
3870
- return { exists: true, file: null };
3871
- }
3872
- return {
3873
- exists: true,
3874
- file: { version, servers: rawServers }
3875
- };
3876
- }
3877
- function hashContent(servers) {
3878
- const h = createHash2("sha256");
3879
- h.update(JSON.stringify(servers));
3880
- return `local-${h.digest("hex").slice(0, 16)}`;
3881
- }
3882
- async function loadLocalBundles(opts = {}) {
3883
- const cwd = opts.cwd ?? process.cwd();
3884
- const home = opts.home ?? homedir6();
3885
- const warnings = [];
3886
- const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
3887
- const projectPath = projectDir ? localBundlesPath(projectDir) : null;
3888
- const globalPath = localBundlesPath(join7(home, CONFIG_DIRNAME));
3889
- const projectResult = projectPath ? await readBundlesAt(projectPath, warnings) : { exists: false, file: null };
3890
- let file;
3891
- let sourcePath;
3892
- if (projectResult.exists) {
3893
- file = projectResult.file;
3894
- sourcePath = projectPath;
3895
- } else {
3896
- const globalResult = await readBundlesAt(globalPath, warnings);
3897
- file = globalResult.file;
3898
- sourcePath = globalResult.exists ? globalPath : null;
3899
- }
3900
- if (!file) {
3901
- return { config: null, path: sourcePath, warnings };
3902
- }
3903
- const servers = [];
3904
- for (const raw of file.servers) {
3905
- const validated = validateEntry(raw, warnings);
3906
- if (validated) servers.push(validated);
3907
- }
3908
- return {
3909
- config: {
3910
- servers,
3911
- configVersion: hashContent(servers)
3912
- },
3913
- path: sourcePath,
3914
- warnings
3915
- };
3916
- }
3917
- function deriveNamespace(name) {
3918
- let ns = name.toLowerCase().replace(/[^a-z0-9]+/g, "");
3919
- if (ns.length === 0) return "server";
3920
- if (!/^[a-z]/.test(ns)) ns = `s${ns}`;
3921
- if (ns.length > 30) ns = ns.slice(0, 30);
3922
- return ns;
3923
- }
3924
- async function readRawUserBundles(home) {
3925
- const path3 = localBundlesPath(userConfigDir(home));
3926
- if (!existsSync4(path3)) {
3927
- return { version: CURRENT_BUNDLES_SCHEMA_VERSION, servers: [] };
3928
- }
3929
- const warnings = [];
3930
- const r = await readBundlesAt(path3, warnings);
3931
- if (!r.file) {
3932
- const detail = warnings.length > 0 ? ` (${warnings.join("; ")})` : "";
3933
- throw new Error(`${path3} is malformed${detail}; fix it by hand before adding servers.`);
3934
- }
3935
- return { version: r.file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION, servers: r.file.servers };
3936
- }
3937
- async function upsertUserBundle(entry, opts = {}) {
3938
- const home = opts.home ?? homedir6();
3939
- const path3 = localBundlesPath(userConfigDir(home));
3940
- const file = await readRawUserBundles(home);
3941
- const idx = file.servers.findIndex(
3942
- (s) => s?.namespace === entry.namespace || entry.name != null && s?.name === entry.name
3943
- );
3944
- const replaced = idx >= 0;
3945
- if (replaced) file.servers[idx] = entry;
3946
- else file.servers.push(entry);
3947
- file.version = file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION;
3948
- await atomicWriteFile(path3, `${JSON.stringify(file, null, 2)}
3949
- `);
3950
- return { path: path3, replaced };
3951
- }
3952
- async function removeUserBundle(namespace, opts = {}) {
3953
- const home = opts.home ?? homedir6();
3954
- const path3 = localBundlesPath(userConfigDir(home));
3955
- if (!existsSync4(path3)) return { path: path3, removed: false };
3956
- const file = await readRawUserBundles(home);
3957
- const before = file.servers.length;
3958
- file.servers = file.servers.filter((s) => s?.namespace !== namespace);
3959
- if (file.servers.length === before) return { path: path3, removed: false };
3960
- file.version = file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION;
3961
- await atomicWriteFile(path3, `${JSON.stringify(file, null, 2)}
3962
- `);
3963
- return { path: path3, removed: true };
3964
- }
3965
- async function findShadowingProjectBundles(cwd, home = homedir6()) {
3966
- const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
3967
- if (!projectDir) return null;
3968
- const projectPath = localBundlesPath(projectDir);
3969
- return existsSync4(projectPath) ? projectPath : null;
3970
- }
3971
-
3972
- // src/local-add-cmd.ts
4229
+ import { homedir as homedir9 } from "os";
3973
4230
  var SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
3974
4231
  var ADD_USAGE = `Usage: yaw-mcp add <slug> [flags]
3975
4232
 
@@ -4052,7 +4309,7 @@ async function runAdd(opts) {
4052
4309
  return { exitCode: 2, written: [] };
4053
4310
  }
4054
4311
  const env = opts.env ?? process.env;
4055
- const home = opts.home ?? homedir7();
4312
+ const home = opts.home ?? homedir9();
4056
4313
  const cwd = opts.cwd ?? process.cwd();
4057
4314
  let server;
4058
4315
  try {
@@ -4158,7 +4415,7 @@ async function runRemove(opts) {
4158
4415
  printErr(`yaw-mcp remove: "${opts.target}" isn't a valid slug or namespace.`);
4159
4416
  return { exitCode: 2, written: [] };
4160
4417
  }
4161
- const home = opts.home ?? homedir7();
4418
+ const home = opts.home ?? homedir9();
4162
4419
  const cwd = opts.cwd ?? process.cwd();
4163
4420
  const derived = deriveNamespace(opts.target);
4164
4421
  const candidates = derived === opts.target ? [opts.target] : [opts.target, derived];
@@ -4216,7 +4473,7 @@ async function runList(opts) {
4216
4473
  const out = opts.out ?? ((s) => process.stdout.write(s));
4217
4474
  const print = (s = "") => out(`${s}
4218
4475
  `);
4219
- const home = opts.home ?? homedir7();
4476
+ const home = opts.home ?? homedir9();
4220
4477
  const cwd = opts.cwd ?? process.cwd();
4221
4478
  const loaded = await loadLocalBundles({ home, cwd });
4222
4479
  const servers = loaded.config?.servers ?? [];
@@ -4364,8 +4621,8 @@ async function runLogout(opts = {}, io = {
4364
4621
 
4365
4622
  // src/reset-learning-cmd.ts
4366
4623
  import { unlink as unlink2 } from "fs/promises";
4367
- import { homedir as homedir8 } from "os";
4368
- import { join as join8 } from "path";
4624
+ import { homedir as homedir10 } from "os";
4625
+ import { join as join9 } from "path";
4369
4626
  var RESET_LEARNING_USAGE = `Usage: yaw-mcp reset-learning
4370
4627
 
4371
4628
  Delete ~/.yaw-mcp/state.json so cross-session learning starts fresh.
@@ -4387,7 +4644,7 @@ ${RESET_LEARNING_USAGE}`
4387
4644
  return { kind: "ok", options: {} };
4388
4645
  }
4389
4646
  async function runResetLearning(opts = {}) {
4390
- const home = opts.home ?? homedir8();
4647
+ const home = opts.home ?? homedir10();
4391
4648
  const env = opts.env ?? process.env;
4392
4649
  const write = opts.out ?? ((s) => process.stdout.write(s));
4393
4650
  const writeErr = opts.err ?? ((s) => process.stderr.write(s));
@@ -4402,7 +4659,7 @@ async function runResetLearning(opts = {}) {
4402
4659
  writeErr(`${s}
4403
4660
  `);
4404
4661
  };
4405
- const filePath = join8(userConfigDir(home), STATE_FILENAME);
4662
+ const filePath = join9(userConfigDir(home), STATE_FILENAME);
4406
4663
  const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
4407
4664
  const disabled = raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
4408
4665
  if (disabled) {
@@ -4437,14 +4694,14 @@ function isFileNotFound2(err) {
4437
4694
  // src/secrets-cmd.ts
4438
4695
  import { existsSync as existsSync6 } from "fs";
4439
4696
  import { mkdir as mkdir3 } from "fs/promises";
4440
- import { homedir as homedir10 } from "os";
4697
+ import { homedir as homedir12 } from "os";
4441
4698
  import { dirname as dirname3 } from "path";
4442
4699
 
4443
4700
  // src/secrets-vault.ts
4444
4701
  import { existsSync as existsSync5 } from "fs";
4445
- import { chmod as chmod3, readFile as readFile7 } from "fs/promises";
4446
- import { homedir as homedir9 } from "os";
4447
- import { dirname as dirname2, join as join9 } from "path";
4702
+ import { chmod as chmod3, readFile as readFile8 } from "fs/promises";
4703
+ import { homedir as homedir11 } from "os";
4704
+ import { dirname as dirname2, join as join10 } from "path";
4448
4705
 
4449
4706
  // src/secrets-crypto.ts
4450
4707
  import { createCipheriv, createDecipheriv, randomBytes, scrypt as scryptCb } from "crypto";
@@ -4502,8 +4759,8 @@ function decryptEntry(entry, key) {
4502
4759
  // src/secrets-vault.ts
4503
4760
  var SECRETS_FILENAME = "secrets.json";
4504
4761
  var SECRETS_SCHEMA_VERSION = 1;
4505
- function vaultPath(home = homedir9()) {
4506
- return join9(home, CONFIG_DIRNAME, SECRETS_FILENAME);
4762
+ function vaultPath(home = homedir11()) {
4763
+ return join10(home, CONFIG_DIRNAME, SECRETS_FILENAME);
4507
4764
  }
4508
4765
  function emptyVault() {
4509
4766
  return {
@@ -4516,7 +4773,7 @@ async function loadVault(path3) {
4516
4773
  if (!existsSync5(path3)) return null;
4517
4774
  let raw;
4518
4775
  try {
4519
- raw = await readFile7(path3, "utf8");
4776
+ raw = await readFile8(path3, "utf8");
4520
4777
  } catch (err) {
4521
4778
  log("warn", "Failed to read vault", { path: path3, error: err instanceof Error ? err.message : String(err) });
4522
4779
  return null;
@@ -4776,7 +5033,7 @@ async function runSecrets(opts, io = {
4776
5033
  out: (s) => process.stdout.write(s),
4777
5034
  err: (s) => process.stderr.write(s)
4778
5035
  }) {
4779
- const home = opts.home ?? homedir10();
5036
+ const home = opts.home ?? homedir12();
4780
5037
  const path3 = vaultPath(home);
4781
5038
  if (opts.action === "lock") {
4782
5039
  lock();
@@ -4904,7 +5161,7 @@ async function runSecrets(opts, io = {
4904
5161
  }
4905
5162
  var MCP_SECRETS_RESOURCE = "mcp_secrets";
4906
5163
  async function runSecretsPush(opts, io) {
4907
- const home = opts.home ?? homedir10();
5164
+ const home = opts.home ?? homedir12();
4908
5165
  const path3 = vaultPath(home);
4909
5166
  const session = await getSession({ home, baseUrl: opts.baseUrl });
4910
5167
  if (!session) {
@@ -4967,7 +5224,7 @@ async function runSecretsPush(opts, io) {
4967
5224
  }
4968
5225
  }
4969
5226
  async function runSecretsPull(opts, io) {
4970
- const home = opts.home ?? homedir10();
5227
+ const home = opts.home ?? homedir12();
4971
5228
  const path3 = vaultPath(home);
4972
5229
  const session = await getSession({ home, baseUrl: opts.baseUrl });
4973
5230
  if (!session) {
@@ -5022,8 +5279,8 @@ async function runSecretsPull(opts, io) {
5022
5279
  }
5023
5280
 
5024
5281
  // src/server.ts
5025
- import { readFile as readFile9 } from "fs/promises";
5026
- import { homedir as homedir11 } from "os";
5282
+ import { readFile as readFile10 } from "fs/promises";
5283
+ import { homedir as homedir13 } from "os";
5027
5284
  import { isAbsolute, relative, resolve as resolve4 } from "path";
5028
5285
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5029
5286
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -5083,22 +5340,31 @@ function defaultSpawn2(cmd, args) {
5083
5340
  async function maybeAutoUpgrade(deps = {}) {
5084
5341
  const optOut = process.env.YAW_MCP_AUTO_UPGRADE;
5085
5342
  if (optOut === "0" || optOut?.toLowerCase() === "false") return;
5086
- const current = deps.currentVersion ?? (true ? "0.60.2" : "dev");
5343
+ const current = deps.currentVersion ?? (true ? "0.60.6" : "dev");
5087
5344
  if (current === "dev") return;
5088
- const method = detectInstallMethod(deps.argvPath ?? process.argv[1]);
5345
+ const method = (deps.isSeaImpl ? await deps.isSeaImpl() : await detectSea()) ? "binary" : detectInstallMethod(deps.argvPath ?? process.argv[1]);
5089
5346
  const latest = await (deps.fetchLatestImpl ?? fetchLatestVersion2)();
5090
5347
  if (latest === null) return;
5091
5348
  const plan = buildUpgradePlan({ current, latest, method });
5092
5349
  if (!plan.stale) return;
5093
- if (method === "global-npm") {
5094
- log("info", "yaw-mcp is out of date; upgrading the global install in the background", { current, latest });
5095
- (deps.spawnImpl ?? defaultSpawn2)("npm", ["install", "-g", "@yawlabs/mcp@latest"]);
5350
+ const globalSpec = 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"] } : null;
5351
+ if (globalSpec) {
5352
+ log("info", "yaw-mcp is out of date; upgrading the global install in the background", {
5353
+ current,
5354
+ latest,
5355
+ tool: globalSpec.cmd
5356
+ });
5357
+ (deps.spawnImpl ?? defaultSpawn2)(globalSpec.cmd, globalSpec.args);
5096
5358
  return;
5097
5359
  }
5098
5360
  if (method === "bundled-app") {
5099
5361
  log("info", "yaw-mcp (bundled with Yaw Terminal) is behind npm; it updates with the app", { current, latest });
5100
5362
  return;
5101
5363
  }
5364
+ if (method === "binary") {
5365
+ log("info", "yaw-mcp (standalone binary) is behind npm; download the latest build to update", { current, latest });
5366
+ return;
5367
+ }
5102
5368
  log("info", "yaw-mcp is out of date; restart your MCP client to pick up the latest version", {
5103
5369
  current,
5104
5370
  latest,
@@ -5458,13 +5724,13 @@ function stepBindingKey(step, index) {
5458
5724
  }
5459
5725
 
5460
5726
  // src/guide.ts
5461
- import { readFile as readFile8 } from "fs/promises";
5727
+ import { readFile as readFile9 } from "fs/promises";
5462
5728
  var GUIDE_READ_TIMEOUT_MS = 1e3;
5463
5729
  async function readGuide(path3, scope) {
5464
5730
  let raw;
5465
5731
  try {
5466
5732
  raw = await Promise.race([
5467
- readFile8(path3, "utf8"),
5733
+ readFile9(path3, "utf8"),
5468
5734
  new Promise(
5469
5735
  (_, reject) => setTimeout(() => reject(new Error("guide read timeout")), GUIDE_READ_TIMEOUT_MS)
5470
5736
  )
@@ -6799,12 +7065,12 @@ async function callLegacyRerank(payload) {
6799
7065
  }
6800
7066
  }
6801
7067
  async function readTeamCookie() {
6802
- const teamSync = await import("./team-sync-4JF5LBRB.js");
7068
+ const teamSync = await import("./team-sync-5356FJP6.js");
6803
7069
  const session = await teamSync.getSession();
6804
7070
  if (!session) return null;
6805
- const { readFile: readFile11 } = await import("fs/promises");
7071
+ const { readFile: readFile12 } = await import("fs/promises");
6806
7072
  try {
6807
- const raw = await readFile11(teamSync.sessionStatePath(), "utf8");
7073
+ const raw = await readFile12(teamSync.sessionStatePath(), "utf8");
6808
7074
  const parsed = JSON.parse(raw);
6809
7075
  return typeof parsed.cookie === "string" && parsed.cookie ? parsed.cookie : null;
6810
7076
  } catch {
@@ -7356,7 +7622,7 @@ function categorizeSpawnError(err) {
7356
7622
  }
7357
7623
  async function connectToUpstream(config, onDisconnect, onListChanged) {
7358
7624
  const client = new Client(
7359
- { name: "yaw-mcp", version: true ? "0.60.2" : "dev" },
7625
+ { name: "yaw-mcp", version: true ? "0.60.6" : "dev" },
7360
7626
  { capabilities: {} }
7361
7627
  );
7362
7628
  let transport;
@@ -7665,7 +7931,7 @@ var ConnectServer = class _ConnectServer {
7665
7931
  this.apiUrl = apiUrl5;
7666
7932
  this.token = token5;
7667
7933
  this.server = new Server(
7668
- { name: "yaw-mcp", version: true ? "0.60.2" : "dev" },
7934
+ { name: "yaw-mcp", version: true ? "0.60.6" : "dev" },
7669
7935
  {
7670
7936
  capabilities: {
7671
7937
  tools: { listChanged: true },
@@ -9196,7 +9462,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
9196
9462
  }
9197
9463
  const ALLOWED_FILENAMES = ["claude_desktop_config.json", "mcp.json", "settings.json", "mcp_config.json"];
9198
9464
  try {
9199
- const resolved = filepath.startsWith("~/") || filepath.startsWith("~\\") ? resolve4(homedir11(), filepath.slice(2)) : resolve4(filepath);
9465
+ const resolved = filepath.startsWith("~/") || filepath.startsWith("~\\") ? resolve4(homedir13(), filepath.slice(2)) : resolve4(filepath);
9200
9466
  const resolvedBasename = resolved.split(/[/\\]/).pop() || "";
9201
9467
  if (!ALLOWED_FILENAMES.includes(resolvedBasename)) {
9202
9468
  return {
@@ -9213,7 +9479,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
9213
9479
  const rel = relative(base, p);
9214
9480
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
9215
9481
  };
9216
- if (!isUnder(homedir11(), resolved) && !isUnder(process.cwd(), resolved)) {
9482
+ if (!isUnder(homedir13(), resolved) && !isUnder(process.cwd(), resolved)) {
9217
9483
  return {
9218
9484
  content: [
9219
9485
  { type: "text", text: "Import path must be under your home directory or the current working directory." }
@@ -9221,7 +9487,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
9221
9487
  isError: true
9222
9488
  };
9223
9489
  }
9224
- const raw = await readFile9(resolved, "utf-8");
9490
+ const raw = await readFile10(resolved, "utf-8");
9225
9491
  const parsed = JSON.parse(raw);
9226
9492
  if (!parsed.mcpServers || typeof parsed.mcpServers !== "object" || Array.isArray(parsed.mcpServers)) {
9227
9493
  return {
@@ -9895,15 +10161,24 @@ async function runServersCommand(opts = {}) {
9895
10161
  ...backend,
9896
10162
  servers: backend.servers.filter((s) => s.namespace.toLowerCase().includes(opts.filter.toLowerCase()))
9897
10163
  } : backend;
10164
+ const gradesReader = opts.gradesReader ?? readGradesCache;
10165
+ const grades = await gradesReader(opts.home).catch(() => ({}));
10166
+ const merged = {
10167
+ ...filtered,
10168
+ servers: filtered.servers.map((s) => {
10169
+ const cached = grades[s.namespace];
10170
+ return cached ? { ...s, complianceGrade: cached.grade } : s;
10171
+ })
10172
+ };
9898
10173
  if (opts.json) {
9899
- print(JSON.stringify(filtered, null, 2));
10174
+ print(JSON.stringify(merged, null, 2));
9900
10175
  return { exitCode: 0, lines };
9901
10176
  }
9902
10177
  if (opts.filter && filtered.servers.length === 0) {
9903
10178
  print(`No servers match "${opts.filter}". Run \`yaw-mcp servers\` to see the full list.`);
9904
10179
  return { exitCode: 0, lines };
9905
10180
  }
9906
- renderTable(filtered, print);
10181
+ renderTable(merged, print);
9907
10182
  return { exitCode: 0, lines };
9908
10183
  }
9909
10184
  function renderTable(cfg, print) {
@@ -9947,7 +10222,7 @@ function truncateVersion(v) {
9947
10222
  }
9948
10223
 
9949
10224
  // src/stats-cmd.ts
9950
- import { homedir as homedir12 } from "os";
10225
+ import { homedir as homedir14 } from "os";
9951
10226
  var STATS_USAGE = `Usage: yaw-mcp stats [--json] [--limit N] [--days N]
9952
10227
 
9953
10228
  Print a digest of recent AI tool calls recorded against your Yaw
@@ -10062,7 +10337,7 @@ async function runStats(opts, io = {
10062
10337
  out: (s) => process.stdout.write(s),
10063
10338
  err: (s) => process.stderr.write(s)
10064
10339
  }) {
10065
- const home = opts.home ?? homedir12();
10340
+ const home = opts.home ?? homedir14();
10066
10341
  const session = await getSession({ home, baseUrl: opts.baseUrl });
10067
10342
  if (!session) {
10068
10343
  const msg = "Not signed in. Yaw MCP analytics requires a Yaw Team account.\n - Yaw Team: $15/seat/mo or $150/seat/yr -- https://yaw.sh/mcp\nSign in with: yaw-mcp login --key <license-key>";
@@ -10120,9 +10395,9 @@ async function runStats(opts, io = {
10120
10395
 
10121
10396
  // src/sync-cmd.ts
10122
10397
  import { existsSync as existsSync7 } from "fs";
10123
- import { mkdir as mkdir4, readFile as readFile10 } from "fs/promises";
10124
- import { homedir as homedir13 } from "os";
10125
- import { dirname as dirname4, join as join10 } from "path";
10398
+ import { mkdir as mkdir4, readFile as readFile11 } from "fs/promises";
10399
+ import { homedir as homedir15 } from "os";
10400
+ import { dirname as dirname4, join as join11 } from "path";
10126
10401
  var SYNC_USAGE = `Usage: yaw-mcp sync <push|pull|status> [--json]
10127
10402
 
10128
10403
  Replicate ~/.yaw-mcp/bundles.json across machines via your Yaw
@@ -10165,12 +10440,12 @@ ${SYNC_USAGE}` };
10165
10440
  return { ok: true, options: opts };
10166
10441
  }
10167
10442
  function bundlesPath(home) {
10168
- return join10(home, CONFIG_DIRNAME, BUNDLES_FILENAME2);
10443
+ return join11(home, CONFIG_DIRNAME, BUNDLES_FILENAME2);
10169
10444
  }
10170
10445
  async function readLocalBundles(home) {
10171
10446
  const path3 = bundlesPath(home);
10172
10447
  if (!existsSync7(path3)) return { version: 1, servers: [] };
10173
- const raw = await readFile10(path3, "utf8");
10448
+ const raw = await readFile11(path3, "utf8");
10174
10449
  const parsed = JSON.parse(raw);
10175
10450
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.servers)) {
10176
10451
  throw new Error(`${path3}: malformed -- expected { servers: [...] }`);
@@ -10207,7 +10482,7 @@ async function runSync(opts, io = {
10207
10482
  out: (s) => process.stdout.write(s),
10208
10483
  err: (s) => process.stderr.write(s)
10209
10484
  }) {
10210
- const home = opts.home ?? homedir13();
10485
+ const home = opts.home ?? homedir15();
10211
10486
  const session = await getSession({ home, baseUrl: opts.baseUrl });
10212
10487
  if (!session) {
10213
10488
  const msg = "Not signed in. Run `yaw-mcp login --key <license-key>` first.";
@@ -10349,6 +10624,7 @@ function handleSyncError(err, opts, io) {
10349
10624
  // src/index.ts
10350
10625
  var KNOWN_SUBCOMMANDS = [
10351
10626
  "compliance",
10627
+ "audit",
10352
10628
  "install",
10353
10629
  "add",
10354
10630
  "remove",
@@ -10375,6 +10651,14 @@ var KNOWN_SUBCOMMANDS = [
10375
10651
  var subcommand = process.argv[2];
10376
10652
  if (subcommand === "compliance") {
10377
10653
  runComplianceCommand(process.argv.slice(3)).then((code) => process.exit(code));
10654
+ } else if (subcommand === "audit") {
10655
+ const parsed = parseAuditArgs(process.argv.slice(3));
10656
+ if (!parsed.ok) {
10657
+ process.stderr.write(`${parsed.error}
10658
+ `);
10659
+ process.exit(2);
10660
+ }
10661
+ runAudit(parsed.options).then((r) => process.exit(r.exitCode));
10378
10662
  } else if (subcommand === "install") {
10379
10663
  const parsed = parseInstallArgs(process.argv.slice(3));
10380
10664
  if (!parsed.ok) {
@@ -10589,6 +10873,10 @@ if (subcommand === "compliance") {
10589
10873
  compliance <target> Run the 88-test compliance suite against an MCP
10590
10874
  server. --publish posts the report to
10591
10875
  yaw.sh/mcp and prints the public URL.
10876
+ audit <namespace> Run the compliance suite against a stdio server
10877
+ from your bundles.json and cache its A-F grade in
10878
+ ~/.yaw-mcp/grades.json (shown in \`servers\` + the
10879
+ Yaw Terminal MCP panel).
10592
10880
  help, --help, -h Show this help.
10593
10881
  --version, -V Print yaw-mcp version.
10594
10882
 
@@ -10636,7 +10924,7 @@ if (subcommand === "compliance") {
10636
10924
  `);
10637
10925
  process.exit(0);
10638
10926
  } else if (subcommand === "--version" || subcommand === "-V") {
10639
- process.stdout.write(`yaw-mcp ${true ? "0.60.2" : "dev"}
10927
+ process.stdout.write(`yaw-mcp ${true ? "0.60.6" : "dev"}
10640
10928
  `);
10641
10929
  process.exit(0);
10642
10930
  } else if (subcommand && !subcommand.startsWith("-")) {