@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-
|
|
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 =
|
|
199
|
-
const newGlobal =
|
|
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 =
|
|
204
|
-
const legacyLocal =
|
|
205
|
-
const newLocal =
|
|
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 =
|
|
208
|
-
const newProject =
|
|
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 =
|
|
220
|
-
const legacyLocal =
|
|
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
|
|
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 ??
|
|
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 ?
|
|
320
|
-
const projectPath = projectConfigDir ?
|
|
321
|
-
const globalPath =
|
|
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
|
|
999
|
-
import { readFile as
|
|
1000
|
-
import { homedir as
|
|
1001
|
-
import { join as
|
|
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
|
|
1309
|
-
import { join as
|
|
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 ??
|
|
1391
|
-
const appData = opts.appData ?? process.env.APPDATA ??
|
|
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 =
|
|
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:
|
|
1785
|
+
return { absolute: join5(home, ".claude.json"), display, containerPath: ["mcpServers"] };
|
|
1423
1786
|
}
|
|
1424
1787
|
if (scope === "project") {
|
|
1425
1788
|
return {
|
|
1426
|
-
absolute:
|
|
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 =
|
|
1795
|
+
const absolute = join5(claudeConfigDir, ".claude.json");
|
|
1433
1796
|
return { absolute, display: absolute, containerPath: ["projects", projectDir, "mcpServers"] };
|
|
1434
1797
|
}
|
|
1435
1798
|
return {
|
|
1436
|
-
absolute:
|
|
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:
|
|
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:
|
|
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:
|
|
1824
|
+
return { absolute: join5(home, ".cursor", "mcp.json"), display, containerPath: ["mcpServers"] };
|
|
1462
1825
|
}
|
|
1463
1826
|
return {
|
|
1464
|
-
absolute:
|
|
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:
|
|
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 ?
|
|
1505
|
-
if (scope === "project" && projectDir) return
|
|
1506
|
-
if (scope === "local" && projectDir) return
|
|
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
|
|
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
|
|
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
|
|
1635
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile as
|
|
1636
|
-
import { homedir as
|
|
1637
|
-
import { join as
|
|
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
|
|
1738
|
-
import { homedir as
|
|
1739
|
-
import { join as
|
|
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 (
|
|
2187
|
+
if (existsSync2(resolved.absolute)) {
|
|
1825
2188
|
let raw;
|
|
1826
2189
|
try {
|
|
1827
|
-
raw = await
|
|
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 ??
|
|
1885
|
-
const yawMcpConfigPath =
|
|
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 (
|
|
2338
|
+
if (existsSync2(path3)) {
|
|
1976
2339
|
try {
|
|
1977
|
-
const raw = await
|
|
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 (
|
|
2445
|
+
if (existsSync2(path3)) {
|
|
2083
2446
|
let raw = "";
|
|
2084
2447
|
try {
|
|
2085
|
-
raw = await
|
|
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 ??
|
|
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 =
|
|
2444
|
-
return
|
|
2806
|
+
function trialsDir(home = homedir7()) {
|
|
2807
|
+
return join7(home, CONFIG_DIRNAME, TRIALS_DIRNAME);
|
|
2445
2808
|
}
|
|
2446
|
-
function trialMarkerPath(slug, home =
|
|
2447
|
-
return
|
|
2809
|
+
function trialMarkerPath(slug, home = homedir7()) {
|
|
2810
|
+
return join7(trialsDir(home), `${slug}.json`);
|
|
2448
2811
|
}
|
|
2449
|
-
function anonIdPath(home =
|
|
2450
|
-
return
|
|
2812
|
+
function anonIdPath(home = homedir7()) {
|
|
2813
|
+
return join7(trialsDir(home), ANON_FILENAME);
|
|
2451
2814
|
}
|
|
2452
2815
|
function computeAnonId() {
|
|
2453
|
-
const h =
|
|
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 =
|
|
2824
|
+
async function loadOrCreateAnonId(home = homedir7()) {
|
|
2462
2825
|
const path3 = anonIdPath(home);
|
|
2463
|
-
if (
|
|
2826
|
+
if (existsSync3(path3)) {
|
|
2464
2827
|
try {
|
|
2465
|
-
const raw = (await
|
|
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 ??
|
|
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 (
|
|
2975
|
+
if (existsSync3(resolved.absolute)) {
|
|
2613
2976
|
try {
|
|
2614
|
-
const raw = await
|
|
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 ??
|
|
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 (!
|
|
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
|
|
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 (
|
|
3068
|
+
if (existsSync3(marker.clientPath)) {
|
|
2706
3069
|
try {
|
|
2707
|
-
const raw = await
|
|
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 ??
|
|
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 (!
|
|
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 =
|
|
3125
|
+
const path3 = join7(dir, filename);
|
|
2763
3126
|
try {
|
|
2764
|
-
const raw = await
|
|
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 ??
|
|
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 (
|
|
2794
|
-
const raw = await
|
|
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
|
|
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.
|
|
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.
|
|
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 ??
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
4103
|
+
sources.push({ path: join8(opts.home, ".bash_history"), extractCommand: (l) => l.trim() || null });
|
|
3711
4104
|
sources.push({
|
|
3712
|
-
path:
|
|
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:
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
|
4415
|
-
import { join as
|
|
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 ??
|
|
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 =
|
|
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
|
|
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
|
|
4493
|
-
import { homedir as
|
|
4494
|
-
import { dirname as dirname2, join as
|
|
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 =
|
|
4553
|
-
return
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
|
5073
|
-
import { homedir as
|
|
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.
|
|
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
|
|
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
|
-
|
|
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-
|
|
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:
|
|
7071
|
+
const { readFile: readFile12 } = await import("fs/promises");
|
|
6858
7072
|
try {
|
|
6859
|
-
const raw = await
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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 ??
|
|
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
|
|
10176
|
-
import { homedir as
|
|
10177
|
-
import { dirname as dirname4, join as
|
|
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
|
|
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
|
|
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 ??
|
|
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.
|
|
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("-")) {
|