@yawlabs/mcp 0.60.3 → 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 = [
@@ -88,70 +503,18 @@ function topPartialBundles(installedNamespaces, limit) {
88
503
  return partial.slice().sort((a, b) => {
89
504
  if (a.missing.length !== b.missing.length) return a.missing.length - b.missing.length;
90
505
  if (a.have.length !== b.have.length) return b.have.length - a.have.length;
91
- return a.bundle.id.localeCompare(b.bundle.id);
92
- }).slice(0, limit);
93
- }
94
-
95
- // 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);
506
+ return a.bundle.id.localeCompare(b.bundle.id);
507
+ }).slice(0, limit);
150
508
  }
151
509
 
510
+ // src/config-loader.ts
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";
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)) {
@@ -2821,6 +3184,7 @@ async function gcExpiredTrials(opts) {
2821
3184
  // src/upgrade-cmd.ts
2822
3185
  import { spawn as spawn2 } from "child_process";
2823
3186
  import { realpathSync } from "fs";
3187
+ var BINARY_DOWNLOAD_URL = "https://github.com/YawLabs/mcp/releases/latest";
2824
3188
  var UPGRADE_USAGE = `Usage: yaw-mcp upgrade [--run] [--json]
2825
3189
 
2826
3190
  Show (or execute) the command to upgrade @yawlabs/mcp to the latest version.
@@ -2933,6 +3297,9 @@ function buildUpgradePlan(input) {
2933
3297
  case "dev-checkout":
2934
3298
  command = "git pull && npm run build";
2935
3299
  break;
3300
+ case "binary":
3301
+ command = null;
3302
+ break;
2936
3303
  default:
2937
3304
  command = "npm install -g @yawlabs/mcp@latest";
2938
3305
  break;
@@ -2978,6 +3345,17 @@ async function defaultSpawn(cmd, args, cwd) {
2978
3345
  child.on("error", () => resolve5(1));
2979
3346
  });
2980
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
+ }
2981
3359
  async function runUpgrade(opts = {}) {
2982
3360
  const write = opts.out ?? ((s) => process.stdout.write(s));
2983
3361
  const writeErr = opts.err ?? ((s) => process.stderr.write(s));
@@ -2995,7 +3373,8 @@ async function runUpgrade(opts = {}) {
2995
3373
  const fetcher = opts.fetchLatest ?? defaultFetchLatest;
2996
3374
  const current = opts.currentVersion ?? readCurrentVersion();
2997
3375
  const argvPath = opts.argvPath ?? process.argv[1];
2998
- const method = await refineInstallMethod(detectInstallMethod(argvPath), argvPath, opts.npmPrefix);
3376
+ const sea = opts.isSea ? opts.isSea() : await detectSea();
3377
+ const method = sea ? "binary" : await refineInstallMethod(detectInstallMethod(argvPath), argvPath, opts.npmPrefix);
2999
3378
  let latest;
3000
3379
  try {
3001
3380
  latest = await fetcher();
@@ -3015,6 +3394,9 @@ async function runUpgrade(opts = {}) {
3015
3394
  print(` ${plan.command}`);
3016
3395
  } else if (method === "bundled-app") {
3017
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}`);
3018
3400
  } else {
3019
3401
  print("Your install uses `npx -y` \u2014 just restart the MCP client when you're back online.");
3020
3402
  }
@@ -3038,6 +3420,13 @@ async function runUpgrade(opts = {}) {
3038
3420
  print("there is nothing to run here. Update Yaw Terminal to get the new version.");
3039
3421
  return { exitCode: 0, lines };
3040
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
+ }
3041
3430
  if (!plan.command) {
3042
3431
  print("No upgrade command available for this install method.");
3043
3432
  return { exitCode: 0, lines };
@@ -3080,7 +3469,7 @@ async function runUpgrade(opts = {}) {
3080
3469
  return { exitCode: 3, lines };
3081
3470
  }
3082
3471
  function readCurrentVersion() {
3083
- return true ? "0.60.3" : "dev";
3472
+ return true ? "0.60.6" : "dev";
3084
3473
  }
3085
3474
 
3086
3475
  // src/usage-hints.ts
@@ -3142,7 +3531,7 @@ function selectFlakyNamespaces(entries, limit) {
3142
3531
  }
3143
3532
 
3144
3533
  // src/doctor-cmd.ts
3145
- var VERSION = true ? "0.60.3" : "dev";
3534
+ var VERSION = true ? "0.60.6" : "dev";
3146
3535
  async function runDoctor(opts = {}) {
3147
3536
  if (opts.json) return runDoctorJson(opts);
3148
3537
  const lines = [];
@@ -3153,7 +3542,7 @@ async function runDoctor(opts = {}) {
3153
3542
  `);
3154
3543
  };
3155
3544
  const cwd = opts.cwd ?? process.cwd();
3156
- const home = opts.home ?? homedir5();
3545
+ const home = opts.home ?? homedir8();
3157
3546
  const os = opts.os ?? CURRENT_OS;
3158
3547
  const env = opts.env ?? process.env;
3159
3548
  print(`yaw-mcp doctor \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`);
@@ -3214,7 +3603,7 @@ async function runDoctor(opts = {}) {
3214
3603
  const latest = skipCheck ? null : await fetchLatestVersion(opts.registryFetch);
3215
3604
  const staleHint = latest && VERSION !== "dev" && compareSemver(VERSION, latest) < 0 ? latest : null;
3216
3605
  if (staleHint) {
3217
- const method = await refineInstallMethod(detectInstallMethod(process.argv[1]), process.argv[1]);
3606
+ const method = await detectSea() ? "binary" : await refineInstallMethod(detectInstallMethod(process.argv[1]), process.argv[1]);
3218
3607
  print("UPGRADE AVAILABLE");
3219
3608
  if (method === "bundled-app") {
3220
3609
  print(` Running ${VERSION}; npm latest is ${staleHint}. This copy ships inside`);
@@ -3222,6 +3611,10 @@ async function runDoctor(opts = {}) {
3222
3611
  } else if (method === "npx") {
3223
3612
  print(` Running ${VERSION}; npm latest is ${staleHint}. npx fetches the latest`);
3224
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}`);
3225
3618
  } else if (method === "global-npm" || method === "pnpm-global" || method === "bun-global" || method === "local-node-modules") {
3226
3619
  print(` Running ${VERSION}; npm latest is ${staleHint}. To upgrade in place:`);
3227
3620
  print("");
@@ -3255,7 +3648,7 @@ async function runDoctorJson(opts) {
3255
3648
  const lines = [];
3256
3649
  const write = opts.out ?? ((s) => process.stdout.write(s));
3257
3650
  const cwd = opts.cwd ?? process.cwd();
3258
- const home = opts.home ?? homedir5();
3651
+ const home = opts.home ?? homedir8();
3259
3652
  const os = opts.os ?? CURRENT_OS;
3260
3653
  const env = opts.env ?? process.env;
3261
3654
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -3278,7 +3671,7 @@ async function runDoctorJson(opts) {
3278
3671
  const persistRaw = env.YAW_MCP_DISABLE_PERSISTENCE;
3279
3672
  const persistDisabled = persistRaw !== void 0 && persistRaw !== "" && (persistRaw === "1" || persistRaw.toLowerCase() === "true");
3280
3673
  const state = persistDisabled ? { disabled: true, path: null, savedAt: null, learningEntries: null, packHistoryEntries: null } : await (async () => {
3281
- const filePath = join6(userConfigDir(home), STATE_FILENAME);
3674
+ const filePath = join8(userConfigDir(home), STATE_FILENAME);
3282
3675
  const persisted = await loadState(filePath);
3283
3676
  const fresh = persisted.savedAt === 0;
3284
3677
  return {
@@ -3291,7 +3684,7 @@ async function runDoctorJson(opts) {
3291
3684
  })();
3292
3685
  const reliability = [];
3293
3686
  if (!persistDisabled) {
3294
- const filePath = join6(userConfigDir(home), STATE_FILENAME);
3687
+ const filePath = join8(userConfigDir(home), STATE_FILENAME);
3295
3688
  const persisted = await loadState(filePath);
3296
3689
  if (persisted.savedAt !== 0) {
3297
3690
  const entries = Object.entries(persisted.learning).map(([namespace, usage]) => ({ namespace, usage }));
@@ -3376,7 +3769,7 @@ async function renderStateSection(opts) {
3376
3769
  print("");
3377
3770
  return;
3378
3771
  }
3379
- const filePath = join6(userConfigDir(home), STATE_FILENAME);
3772
+ const filePath = join8(userConfigDir(home), STATE_FILENAME);
3380
3773
  print(` path: ${filePath}`);
3381
3774
  const peek = await peekStateFile(filePath);
3382
3775
  if (peek.kind === "malformed") {
@@ -3410,7 +3803,7 @@ async function renderStateSection(opts) {
3410
3803
  async function peekStateFile(filePath) {
3411
3804
  let raw;
3412
3805
  try {
3413
- raw = await readFile5(filePath, "utf8");
3806
+ raw = await readFile7(filePath, "utf8");
3414
3807
  } catch (err) {
3415
3808
  if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
3416
3809
  return { kind: "missing" };
@@ -3435,7 +3828,7 @@ async function renderReliabilitySection(opts) {
3435
3828
  const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
3436
3829
  const disabled = raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
3437
3830
  if (disabled) return;
3438
- const filePath = join6(userConfigDir(home), STATE_FILENAME);
3831
+ const filePath = join8(userConfigDir(home), STATE_FILENAME);
3439
3832
  const persisted = await loadState(filePath);
3440
3833
  if (persisted.savedAt === 0) return;
3441
3834
  const entries = Object.entries(persisted.learning).map(([namespace, usage]) => ({ namespace, usage }));
@@ -3541,7 +3934,7 @@ function probeClients(opts) {
3541
3934
  } catch {
3542
3935
  continue;
3543
3936
  }
3544
- const exists3 = existsSync3(resolved.absolute);
3937
+ const exists3 = existsSync4(resolved.absolute);
3545
3938
  let hasMcpEntry = false;
3546
3939
  let hasLegacyEntry = false;
3547
3940
  let legacyEntryName = null;
@@ -3618,14 +4011,14 @@ async function probeClientsAsync(opts) {
3618
4011
  projectDir: scope.requiresProjectDir ? opts.cwd : void 0,
3619
4012
  claudeConfigDir: opts.claudeConfigDir
3620
4013
  });
3621
- const exists3 = existsSync3(resolved.absolute);
4014
+ const exists3 = existsSync4(resolved.absolute);
3622
4015
  let hasMcpEntry = false;
3623
4016
  let hasLegacyEntry = false;
3624
4017
  let legacyEntryName = null;
3625
4018
  let malformed = false;
3626
4019
  if (exists3) {
3627
4020
  try {
3628
- const raw = await readFile5(resolved.absolute, "utf8");
4021
+ const raw = await readFile7(resolved.absolute, "utf8");
3629
4022
  if (raw.trim().length > 0) {
3630
4023
  const parsed = parseJsonc(raw);
3631
4024
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
@@ -3707,9 +4100,9 @@ function scanShellHistoryForShadows(opts) {
3707
4100
  }
3708
4101
  function shellHistorySources(opts) {
3709
4102
  const sources = [];
3710
- 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 });
3711
4104
  sources.push({
3712
- path: join6(opts.home, ".zsh_history"),
4105
+ path: join8(opts.home, ".zsh_history"),
3713
4106
  // Zsh extended-history lines look like `: 1700000000:0;npm audit`.
3714
4107
  // Strip the metadata prefix so we get just the command.
3715
4108
  extractCommand: (l) => {
@@ -3725,7 +4118,7 @@ function shellHistorySources(opts) {
3725
4118
  const appData = opts.env.APPDATA;
3726
4119
  if (appData) {
3727
4120
  sources.push({
3728
- path: join6(appData, "Microsoft", "Windows", "PowerShell", "PSReadLine", "ConsoleHost_history.txt"),
4121
+ path: join8(appData, "Microsoft", "Windows", "PowerShell", "PSReadLine", "ConsoleHost_history.txt"),
3729
4122
  extractCommand: (l) => l.trim() || null
3730
4123
  });
3731
4124
  }
@@ -3833,190 +4226,7 @@ function closestNames(query, candidates, limit) {
3833
4226
  }
3834
4227
 
3835
4228
  // src/local-add-cmd.ts
3836
- import { homedir as homedir7 } from "os";
3837
-
3838
- // src/local-bundles.ts
3839
- import { createHash as createHash2 } from "crypto";
3840
- import { existsSync as existsSync4 } from "fs";
3841
- import { readFile as readFile6 } from "fs/promises";
3842
- import { homedir as homedir6 } from "os";
3843
- import { join as join7 } from "path";
3844
- var BUNDLES_FILENAME = "bundles.json";
3845
- var CURRENT_BUNDLES_SCHEMA_VERSION = 1;
3846
- function localBundlesPath(configDir) {
3847
- return join7(configDir, BUNDLES_FILENAME);
3848
- }
3849
- var NAMESPACE_RE = /^[a-z][a-z0-9_]{0,29}$/;
3850
- function validateEntry(entry, warnings) {
3851
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3852
- warnings.push("bundles.json: skipping non-object server entry");
3853
- return null;
3854
- }
3855
- const e = entry;
3856
- const namespace = typeof e.namespace === "string" ? e.namespace : "";
3857
- if (!namespace || !NAMESPACE_RE.test(namespace)) {
3858
- warnings.push(`bundles.json: skipping server with invalid namespace ${JSON.stringify(namespace)}`);
3859
- return null;
3860
- }
3861
- const name = typeof e.name === "string" && e.name.length > 0 ? e.name : namespace;
3862
- const type = e.type === "remote" ? "remote" : "local";
3863
- const transport = e.transport === "streamable-http" || e.transport === "sse" || e.transport === "stdio" ? e.transport : void 0;
3864
- const command = typeof e.command === "string" ? e.command : void 0;
3865
- const args = Array.isArray(e.args) ? e.args.filter((a) => typeof a === "string") : void 0;
3866
- const env = e.env && typeof e.env === "object" && !Array.isArray(e.env) ? Object.fromEntries(
3867
- Object.entries(e.env).filter(([, v]) => typeof v === "string")
3868
- ) : void 0;
3869
- const url = typeof e.url === "string" ? e.url : void 0;
3870
- const description = typeof e.description === "string" ? e.description : void 0;
3871
- const isActive = e.isActive !== false;
3872
- const id = typeof e.id === "string" && e.id.length > 0 ? e.id : `local-${namespace}`;
3873
- return {
3874
- id,
3875
- name,
3876
- namespace,
3877
- type,
3878
- transport,
3879
- command,
3880
- args,
3881
- env,
3882
- url,
3883
- isActive,
3884
- description
3885
- };
3886
- }
3887
- async function readBundlesAt(path3, warnings) {
3888
- let raw;
3889
- try {
3890
- raw = await readFile6(path3, "utf8");
3891
- } catch {
3892
- return { exists: false, file: null };
3893
- }
3894
- let parsed;
3895
- try {
3896
- parsed = parseJsonc(raw);
3897
- } catch (err) {
3898
- const msg = err instanceof Error ? err.message : String(err);
3899
- warnings.push(`${path3}: invalid JSON (${msg}) -- file ignored`);
3900
- log("warn", "bundles.json is not valid JSON; ignoring", { path: path3, error: msg });
3901
- return { exists: true, file: null };
3902
- }
3903
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3904
- warnings.push(`${path3}: root must be a JSON object -- file ignored`);
3905
- return { exists: true, file: null };
3906
- }
3907
- const obj = parsed;
3908
- const version = typeof obj.version === "number" ? obj.version : void 0;
3909
- if (version !== void 0 && version > CURRENT_BUNDLES_SCHEMA_VERSION) {
3910
- warnings.push(
3911
- `${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.`
3912
- );
3913
- }
3914
- const rawServers = obj.servers;
3915
- if (!Array.isArray(rawServers)) {
3916
- warnings.push(`${path3}: 'servers' must be an array -- file ignored`);
3917
- return { exists: true, file: null };
3918
- }
3919
- return {
3920
- exists: true,
3921
- file: { version, servers: rawServers }
3922
- };
3923
- }
3924
- function hashContent(servers) {
3925
- const h = createHash2("sha256");
3926
- h.update(JSON.stringify(servers));
3927
- return `local-${h.digest("hex").slice(0, 16)}`;
3928
- }
3929
- async function loadLocalBundles(opts = {}) {
3930
- const cwd = opts.cwd ?? process.cwd();
3931
- const home = opts.home ?? homedir6();
3932
- const warnings = [];
3933
- const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
3934
- const projectPath = projectDir ? localBundlesPath(projectDir) : null;
3935
- const globalPath = localBundlesPath(join7(home, CONFIG_DIRNAME));
3936
- const projectResult = projectPath ? await readBundlesAt(projectPath, warnings) : { exists: false, file: null };
3937
- let file;
3938
- let sourcePath;
3939
- if (projectResult.exists) {
3940
- file = projectResult.file;
3941
- sourcePath = projectPath;
3942
- } else {
3943
- const globalResult = await readBundlesAt(globalPath, warnings);
3944
- file = globalResult.file;
3945
- sourcePath = globalResult.exists ? globalPath : null;
3946
- }
3947
- if (!file) {
3948
- return { config: null, path: sourcePath, warnings };
3949
- }
3950
- const servers = [];
3951
- for (const raw of file.servers) {
3952
- const validated = validateEntry(raw, warnings);
3953
- if (validated) servers.push(validated);
3954
- }
3955
- return {
3956
- config: {
3957
- servers,
3958
- configVersion: hashContent(servers)
3959
- },
3960
- path: sourcePath,
3961
- warnings
3962
- };
3963
- }
3964
- function deriveNamespace(name) {
3965
- let ns = name.toLowerCase().replace(/[^a-z0-9]+/g, "");
3966
- if (ns.length === 0) return "server";
3967
- if (!/^[a-z]/.test(ns)) ns = `s${ns}`;
3968
- if (ns.length > 30) ns = ns.slice(0, 30);
3969
- return ns;
3970
- }
3971
- async function readRawUserBundles(home) {
3972
- const path3 = localBundlesPath(userConfigDir(home));
3973
- if (!existsSync4(path3)) {
3974
- return { version: CURRENT_BUNDLES_SCHEMA_VERSION, servers: [] };
3975
- }
3976
- const warnings = [];
3977
- const r = await readBundlesAt(path3, warnings);
3978
- if (!r.file) {
3979
- const detail = warnings.length > 0 ? ` (${warnings.join("; ")})` : "";
3980
- throw new Error(`${path3} is malformed${detail}; fix it by hand before adding servers.`);
3981
- }
3982
- return { version: r.file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION, servers: r.file.servers };
3983
- }
3984
- async function upsertUserBundle(entry, opts = {}) {
3985
- const home = opts.home ?? homedir6();
3986
- const path3 = localBundlesPath(userConfigDir(home));
3987
- const file = await readRawUserBundles(home);
3988
- const idx = file.servers.findIndex(
3989
- (s) => s?.namespace === entry.namespace || entry.name != null && s?.name === entry.name
3990
- );
3991
- const replaced = idx >= 0;
3992
- if (replaced) file.servers[idx] = entry;
3993
- else file.servers.push(entry);
3994
- file.version = file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION;
3995
- await atomicWriteFile(path3, `${JSON.stringify(file, null, 2)}
3996
- `);
3997
- return { path: path3, replaced };
3998
- }
3999
- async function removeUserBundle(namespace, opts = {}) {
4000
- const home = opts.home ?? homedir6();
4001
- const path3 = localBundlesPath(userConfigDir(home));
4002
- if (!existsSync4(path3)) return { path: path3, removed: false };
4003
- const file = await readRawUserBundles(home);
4004
- const before = file.servers.length;
4005
- file.servers = file.servers.filter((s) => s?.namespace !== namespace);
4006
- if (file.servers.length === before) return { path: path3, removed: false };
4007
- file.version = file.version ?? CURRENT_BUNDLES_SCHEMA_VERSION;
4008
- await atomicWriteFile(path3, `${JSON.stringify(file, null, 2)}
4009
- `);
4010
- return { path: path3, removed: true };
4011
- }
4012
- async function findShadowingProjectBundles(cwd, home = homedir6()) {
4013
- const projectDir = await findProjectConfigDir(cwd, home).catch(() => null);
4014
- if (!projectDir) return null;
4015
- const projectPath = localBundlesPath(projectDir);
4016
- return existsSync4(projectPath) ? projectPath : null;
4017
- }
4018
-
4019
- // src/local-add-cmd.ts
4229
+ import { homedir as homedir9 } from "os";
4020
4230
  var SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
4021
4231
  var ADD_USAGE = `Usage: yaw-mcp add <slug> [flags]
4022
4232
 
@@ -4099,7 +4309,7 @@ async function runAdd(opts) {
4099
4309
  return { exitCode: 2, written: [] };
4100
4310
  }
4101
4311
  const env = opts.env ?? process.env;
4102
- const home = opts.home ?? homedir7();
4312
+ const home = opts.home ?? homedir9();
4103
4313
  const cwd = opts.cwd ?? process.cwd();
4104
4314
  let server;
4105
4315
  try {
@@ -4205,7 +4415,7 @@ async function runRemove(opts) {
4205
4415
  printErr(`yaw-mcp remove: "${opts.target}" isn't a valid slug or namespace.`);
4206
4416
  return { exitCode: 2, written: [] };
4207
4417
  }
4208
- const home = opts.home ?? homedir7();
4418
+ const home = opts.home ?? homedir9();
4209
4419
  const cwd = opts.cwd ?? process.cwd();
4210
4420
  const derived = deriveNamespace(opts.target);
4211
4421
  const candidates = derived === opts.target ? [opts.target] : [opts.target, derived];
@@ -4263,7 +4473,7 @@ async function runList(opts) {
4263
4473
  const out = opts.out ?? ((s) => process.stdout.write(s));
4264
4474
  const print = (s = "") => out(`${s}
4265
4475
  `);
4266
- const home = opts.home ?? homedir7();
4476
+ const home = opts.home ?? homedir9();
4267
4477
  const cwd = opts.cwd ?? process.cwd();
4268
4478
  const loaded = await loadLocalBundles({ home, cwd });
4269
4479
  const servers = loaded.config?.servers ?? [];
@@ -4411,8 +4621,8 @@ async function runLogout(opts = {}, io = {
4411
4621
 
4412
4622
  // src/reset-learning-cmd.ts
4413
4623
  import { unlink as unlink2 } from "fs/promises";
4414
- import { homedir as homedir8 } from "os";
4415
- import { join as join8 } from "path";
4624
+ import { homedir as homedir10 } from "os";
4625
+ import { join as join9 } from "path";
4416
4626
  var RESET_LEARNING_USAGE = `Usage: yaw-mcp reset-learning
4417
4627
 
4418
4628
  Delete ~/.yaw-mcp/state.json so cross-session learning starts fresh.
@@ -4434,7 +4644,7 @@ ${RESET_LEARNING_USAGE}`
4434
4644
  return { kind: "ok", options: {} };
4435
4645
  }
4436
4646
  async function runResetLearning(opts = {}) {
4437
- const home = opts.home ?? homedir8();
4647
+ const home = opts.home ?? homedir10();
4438
4648
  const env = opts.env ?? process.env;
4439
4649
  const write = opts.out ?? ((s) => process.stdout.write(s));
4440
4650
  const writeErr = opts.err ?? ((s) => process.stderr.write(s));
@@ -4449,7 +4659,7 @@ async function runResetLearning(opts = {}) {
4449
4659
  writeErr(`${s}
4450
4660
  `);
4451
4661
  };
4452
- const filePath = join8(userConfigDir(home), STATE_FILENAME);
4662
+ const filePath = join9(userConfigDir(home), STATE_FILENAME);
4453
4663
  const raw = env.YAW_MCP_DISABLE_PERSISTENCE;
4454
4664
  const disabled = raw !== void 0 && raw !== "" && (raw === "1" || raw.toLowerCase() === "true");
4455
4665
  if (disabled) {
@@ -4484,14 +4694,14 @@ function isFileNotFound2(err) {
4484
4694
  // src/secrets-cmd.ts
4485
4695
  import { existsSync as existsSync6 } from "fs";
4486
4696
  import { mkdir as mkdir3 } from "fs/promises";
4487
- import { homedir as homedir10 } from "os";
4697
+ import { homedir as homedir12 } from "os";
4488
4698
  import { dirname as dirname3 } from "path";
4489
4699
 
4490
4700
  // src/secrets-vault.ts
4491
4701
  import { existsSync as existsSync5 } from "fs";
4492
- import { chmod as chmod3, readFile as readFile7 } from "fs/promises";
4493
- import { homedir as homedir9 } from "os";
4494
- 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";
4495
4705
 
4496
4706
  // src/secrets-crypto.ts
4497
4707
  import { createCipheriv, createDecipheriv, randomBytes, scrypt as scryptCb } from "crypto";
@@ -4549,8 +4759,8 @@ function decryptEntry(entry, key) {
4549
4759
  // src/secrets-vault.ts
4550
4760
  var SECRETS_FILENAME = "secrets.json";
4551
4761
  var SECRETS_SCHEMA_VERSION = 1;
4552
- function vaultPath(home = homedir9()) {
4553
- return join9(home, CONFIG_DIRNAME, SECRETS_FILENAME);
4762
+ function vaultPath(home = homedir11()) {
4763
+ return join10(home, CONFIG_DIRNAME, SECRETS_FILENAME);
4554
4764
  }
4555
4765
  function emptyVault() {
4556
4766
  return {
@@ -4563,7 +4773,7 @@ async function loadVault(path3) {
4563
4773
  if (!existsSync5(path3)) return null;
4564
4774
  let raw;
4565
4775
  try {
4566
- raw = await readFile7(path3, "utf8");
4776
+ raw = await readFile8(path3, "utf8");
4567
4777
  } catch (err) {
4568
4778
  log("warn", "Failed to read vault", { path: path3, error: err instanceof Error ? err.message : String(err) });
4569
4779
  return null;
@@ -4823,7 +5033,7 @@ async function runSecrets(opts, io = {
4823
5033
  out: (s) => process.stdout.write(s),
4824
5034
  err: (s) => process.stderr.write(s)
4825
5035
  }) {
4826
- const home = opts.home ?? homedir10();
5036
+ const home = opts.home ?? homedir12();
4827
5037
  const path3 = vaultPath(home);
4828
5038
  if (opts.action === "lock") {
4829
5039
  lock();
@@ -4951,7 +5161,7 @@ async function runSecrets(opts, io = {
4951
5161
  }
4952
5162
  var MCP_SECRETS_RESOURCE = "mcp_secrets";
4953
5163
  async function runSecretsPush(opts, io) {
4954
- const home = opts.home ?? homedir10();
5164
+ const home = opts.home ?? homedir12();
4955
5165
  const path3 = vaultPath(home);
4956
5166
  const session = await getSession({ home, baseUrl: opts.baseUrl });
4957
5167
  if (!session) {
@@ -5014,7 +5224,7 @@ async function runSecretsPush(opts, io) {
5014
5224
  }
5015
5225
  }
5016
5226
  async function runSecretsPull(opts, io) {
5017
- const home = opts.home ?? homedir10();
5227
+ const home = opts.home ?? homedir12();
5018
5228
  const path3 = vaultPath(home);
5019
5229
  const session = await getSession({ home, baseUrl: opts.baseUrl });
5020
5230
  if (!session) {
@@ -5069,8 +5279,8 @@ async function runSecretsPull(opts, io) {
5069
5279
  }
5070
5280
 
5071
5281
  // src/server.ts
5072
- import { readFile as readFile9 } from "fs/promises";
5073
- import { homedir as homedir11 } from "os";
5282
+ import { readFile as readFile10 } from "fs/promises";
5283
+ import { homedir as homedir13 } from "os";
5074
5284
  import { isAbsolute, relative, resolve as resolve4 } from "path";
5075
5285
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5076
5286
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -5130,9 +5340,9 @@ function defaultSpawn2(cmd, args) {
5130
5340
  async function maybeAutoUpgrade(deps = {}) {
5131
5341
  const optOut = process.env.YAW_MCP_AUTO_UPGRADE;
5132
5342
  if (optOut === "0" || optOut?.toLowerCase() === "false") return;
5133
- const current = deps.currentVersion ?? (true ? "0.60.3" : "dev");
5343
+ const current = deps.currentVersion ?? (true ? "0.60.6" : "dev");
5134
5344
  if (current === "dev") return;
5135
- 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]);
5136
5346
  const latest = await (deps.fetchLatestImpl ?? fetchLatestVersion2)();
5137
5347
  if (latest === null) return;
5138
5348
  const plan = buildUpgradePlan({ current, latest, method });
@@ -5151,6 +5361,10 @@ async function maybeAutoUpgrade(deps = {}) {
5151
5361
  log("info", "yaw-mcp (bundled with Yaw Terminal) is behind npm; it updates with the app", { current, latest });
5152
5362
  return;
5153
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
+ }
5154
5368
  log("info", "yaw-mcp is out of date; restart your MCP client to pick up the latest version", {
5155
5369
  current,
5156
5370
  latest,
@@ -5510,13 +5724,13 @@ function stepBindingKey(step, index) {
5510
5724
  }
5511
5725
 
5512
5726
  // src/guide.ts
5513
- import { readFile as readFile8 } from "fs/promises";
5727
+ import { readFile as readFile9 } from "fs/promises";
5514
5728
  var GUIDE_READ_TIMEOUT_MS = 1e3;
5515
5729
  async function readGuide(path3, scope) {
5516
5730
  let raw;
5517
5731
  try {
5518
5732
  raw = await Promise.race([
5519
- readFile8(path3, "utf8"),
5733
+ readFile9(path3, "utf8"),
5520
5734
  new Promise(
5521
5735
  (_, reject) => setTimeout(() => reject(new Error("guide read timeout")), GUIDE_READ_TIMEOUT_MS)
5522
5736
  )
@@ -6851,12 +7065,12 @@ async function callLegacyRerank(payload) {
6851
7065
  }
6852
7066
  }
6853
7067
  async function readTeamCookie() {
6854
- const teamSync = await import("./team-sync-4JF5LBRB.js");
7068
+ const teamSync = await import("./team-sync-5356FJP6.js");
6855
7069
  const session = await teamSync.getSession();
6856
7070
  if (!session) return null;
6857
- const { readFile: readFile11 } = await import("fs/promises");
7071
+ const { readFile: readFile12 } = await import("fs/promises");
6858
7072
  try {
6859
- const raw = await readFile11(teamSync.sessionStatePath(), "utf8");
7073
+ const raw = await readFile12(teamSync.sessionStatePath(), "utf8");
6860
7074
  const parsed = JSON.parse(raw);
6861
7075
  return typeof parsed.cookie === "string" && parsed.cookie ? parsed.cookie : null;
6862
7076
  } catch {
@@ -7408,7 +7622,7 @@ function categorizeSpawnError(err) {
7408
7622
  }
7409
7623
  async function connectToUpstream(config, onDisconnect, onListChanged) {
7410
7624
  const client = new Client(
7411
- { name: "yaw-mcp", version: true ? "0.60.3" : "dev" },
7625
+ { name: "yaw-mcp", version: true ? "0.60.6" : "dev" },
7412
7626
  { capabilities: {} }
7413
7627
  );
7414
7628
  let transport;
@@ -7717,7 +7931,7 @@ var ConnectServer = class _ConnectServer {
7717
7931
  this.apiUrl = apiUrl5;
7718
7932
  this.token = token5;
7719
7933
  this.server = new Server(
7720
- { name: "yaw-mcp", version: true ? "0.60.3" : "dev" },
7934
+ { name: "yaw-mcp", version: true ? "0.60.6" : "dev" },
7721
7935
  {
7722
7936
  capabilities: {
7723
7937
  tools: { listChanged: true },
@@ -9248,7 +9462,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
9248
9462
  }
9249
9463
  const ALLOWED_FILENAMES = ["claude_desktop_config.json", "mcp.json", "settings.json", "mcp_config.json"];
9250
9464
  try {
9251
- 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);
9252
9466
  const resolvedBasename = resolved.split(/[/\\]/).pop() || "";
9253
9467
  if (!ALLOWED_FILENAMES.includes(resolvedBasename)) {
9254
9468
  return {
@@ -9265,7 +9479,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
9265
9479
  const rel = relative(base, p);
9266
9480
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
9267
9481
  };
9268
- if (!isUnder(homedir11(), resolved) && !isUnder(process.cwd(), resolved)) {
9482
+ if (!isUnder(homedir13(), resolved) && !isUnder(process.cwd(), resolved)) {
9269
9483
  return {
9270
9484
  content: [
9271
9485
  { type: "text", text: "Import path must be under your home directory or the current working directory." }
@@ -9273,7 +9487,7 @@ ${activeCount} loaded in this session, ${totalTools} tools in context${tokenSumm
9273
9487
  isError: true
9274
9488
  };
9275
9489
  }
9276
- const raw = await readFile9(resolved, "utf-8");
9490
+ const raw = await readFile10(resolved, "utf-8");
9277
9491
  const parsed = JSON.parse(raw);
9278
9492
  if (!parsed.mcpServers || typeof parsed.mcpServers !== "object" || Array.isArray(parsed.mcpServers)) {
9279
9493
  return {
@@ -9947,15 +10161,24 @@ async function runServersCommand(opts = {}) {
9947
10161
  ...backend,
9948
10162
  servers: backend.servers.filter((s) => s.namespace.toLowerCase().includes(opts.filter.toLowerCase()))
9949
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
+ };
9950
10173
  if (opts.json) {
9951
- print(JSON.stringify(filtered, null, 2));
10174
+ print(JSON.stringify(merged, null, 2));
9952
10175
  return { exitCode: 0, lines };
9953
10176
  }
9954
10177
  if (opts.filter && filtered.servers.length === 0) {
9955
10178
  print(`No servers match "${opts.filter}". Run \`yaw-mcp servers\` to see the full list.`);
9956
10179
  return { exitCode: 0, lines };
9957
10180
  }
9958
- renderTable(filtered, print);
10181
+ renderTable(merged, print);
9959
10182
  return { exitCode: 0, lines };
9960
10183
  }
9961
10184
  function renderTable(cfg, print) {
@@ -9999,7 +10222,7 @@ function truncateVersion(v) {
9999
10222
  }
10000
10223
 
10001
10224
  // src/stats-cmd.ts
10002
- import { homedir as homedir12 } from "os";
10225
+ import { homedir as homedir14 } from "os";
10003
10226
  var STATS_USAGE = `Usage: yaw-mcp stats [--json] [--limit N] [--days N]
10004
10227
 
10005
10228
  Print a digest of recent AI tool calls recorded against your Yaw
@@ -10114,7 +10337,7 @@ async function runStats(opts, io = {
10114
10337
  out: (s) => process.stdout.write(s),
10115
10338
  err: (s) => process.stderr.write(s)
10116
10339
  }) {
10117
- const home = opts.home ?? homedir12();
10340
+ const home = opts.home ?? homedir14();
10118
10341
  const session = await getSession({ home, baseUrl: opts.baseUrl });
10119
10342
  if (!session) {
10120
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>";
@@ -10172,9 +10395,9 @@ async function runStats(opts, io = {
10172
10395
 
10173
10396
  // src/sync-cmd.ts
10174
10397
  import { existsSync as existsSync7 } from "fs";
10175
- import { mkdir as mkdir4, readFile as readFile10 } from "fs/promises";
10176
- import { homedir as homedir13 } from "os";
10177
- 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";
10178
10401
  var SYNC_USAGE = `Usage: yaw-mcp sync <push|pull|status> [--json]
10179
10402
 
10180
10403
  Replicate ~/.yaw-mcp/bundles.json across machines via your Yaw
@@ -10217,12 +10440,12 @@ ${SYNC_USAGE}` };
10217
10440
  return { ok: true, options: opts };
10218
10441
  }
10219
10442
  function bundlesPath(home) {
10220
- return join10(home, CONFIG_DIRNAME, BUNDLES_FILENAME2);
10443
+ return join11(home, CONFIG_DIRNAME, BUNDLES_FILENAME2);
10221
10444
  }
10222
10445
  async function readLocalBundles(home) {
10223
10446
  const path3 = bundlesPath(home);
10224
10447
  if (!existsSync7(path3)) return { version: 1, servers: [] };
10225
- const raw = await readFile10(path3, "utf8");
10448
+ const raw = await readFile11(path3, "utf8");
10226
10449
  const parsed = JSON.parse(raw);
10227
10450
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.servers)) {
10228
10451
  throw new Error(`${path3}: malformed -- expected { servers: [...] }`);
@@ -10259,7 +10482,7 @@ async function runSync(opts, io = {
10259
10482
  out: (s) => process.stdout.write(s),
10260
10483
  err: (s) => process.stderr.write(s)
10261
10484
  }) {
10262
- const home = opts.home ?? homedir13();
10485
+ const home = opts.home ?? homedir15();
10263
10486
  const session = await getSession({ home, baseUrl: opts.baseUrl });
10264
10487
  if (!session) {
10265
10488
  const msg = "Not signed in. Run `yaw-mcp login --key <license-key>` first.";
@@ -10401,6 +10624,7 @@ function handleSyncError(err, opts, io) {
10401
10624
  // src/index.ts
10402
10625
  var KNOWN_SUBCOMMANDS = [
10403
10626
  "compliance",
10627
+ "audit",
10404
10628
  "install",
10405
10629
  "add",
10406
10630
  "remove",
@@ -10427,6 +10651,14 @@ var KNOWN_SUBCOMMANDS = [
10427
10651
  var subcommand = process.argv[2];
10428
10652
  if (subcommand === "compliance") {
10429
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));
10430
10662
  } else if (subcommand === "install") {
10431
10663
  const parsed = parseInstallArgs(process.argv.slice(3));
10432
10664
  if (!parsed.ok) {
@@ -10641,6 +10873,10 @@ if (subcommand === "compliance") {
10641
10873
  compliance <target> Run the 88-test compliance suite against an MCP
10642
10874
  server. --publish posts the report to
10643
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).
10644
10880
  help, --help, -h Show this help.
10645
10881
  --version, -V Print yaw-mcp version.
10646
10882
 
@@ -10688,7 +10924,7 @@ if (subcommand === "compliance") {
10688
10924
  `);
10689
10925
  process.exit(0);
10690
10926
  } else if (subcommand === "--version" || subcommand === "-V") {
10691
- process.stdout.write(`yaw-mcp ${true ? "0.60.3" : "dev"}
10927
+ process.stdout.write(`yaw-mcp ${true ? "0.60.6" : "dev"}
10692
10928
  `);
10693
10929
  process.exit(0);
10694
10930
  } else if (subcommand && !subcommand.startsWith("-")) {