godmode 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,613 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ execute
4
+ } from "./chunk-I4WS4MMI.js";
5
+ import {
6
+ listApis,
7
+ loadManifest,
8
+ removeApi,
9
+ updateApi,
10
+ validateGraphQLFlags
11
+ } from "./chunk-FBMQ3AC4.js";
12
+ import {
13
+ executeMcpTool,
14
+ validateMcpFlags
15
+ } from "./chunk-EHS56XXY.js";
16
+
17
+ // src/env.ts
18
+ import { readFileSync } from "fs";
19
+ import { resolve } from "path";
20
+ function loadEnv() {
21
+ let text;
22
+ try {
23
+ text = readFileSync(resolve(process.cwd(), ".env"), "utf-8");
24
+ } catch {
25
+ return;
26
+ }
27
+ for (const line of text.split("\n")) {
28
+ const trimmed = line.trim();
29
+ if (!trimmed || trimmed.startsWith("#")) continue;
30
+ const eqIdx = trimmed.indexOf("=");
31
+ if (eqIdx < 0) continue;
32
+ const key = trimmed.slice(0, eqIdx).trim();
33
+ const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
34
+ if (key && !(key in process.env)) process.env[key] = val;
35
+ }
36
+ }
37
+
38
+ // src/match.ts
39
+ function matchRoute(manifest, userSegments, method) {
40
+ if (userSegments.length > 0) {
41
+ const ver = manifest.versions.find((v) => v.name === userSegments[0]);
42
+ if (ver) {
43
+ const match = findMatch(manifest.routes, userSegments.slice(1), method, ver.name);
44
+ if (match) return match;
45
+ }
46
+ }
47
+ return findMatch(manifest.routes, userSegments, method);
48
+ }
49
+ function findMatch(routes, userSegments, method, version) {
50
+ let bestMatch = null;
51
+ let bestScore = -1;
52
+ for (const route of routes) {
53
+ if (route.method !== method) continue;
54
+ if (route.segments.length !== userSegments.length) continue;
55
+ if (version !== void 0 && route.version !== version) continue;
56
+ let score = 0;
57
+ const params = {};
58
+ let matches = true;
59
+ for (let i = 0; i < route.segments.length; i++) {
60
+ const seg = route.segments[i];
61
+ const userSeg = userSegments[i];
62
+ if (seg.isParam) {
63
+ params[seg.value] = userSeg;
64
+ } else if (seg.value === userSeg) {
65
+ score += 1;
66
+ } else {
67
+ matches = false;
68
+ break;
69
+ }
70
+ }
71
+ if (matches && (score > bestScore || score === bestScore && isLaterVersion(route, bestMatch?.route))) {
72
+ bestScore = score;
73
+ bestMatch = { route, params };
74
+ }
75
+ }
76
+ return bestMatch;
77
+ }
78
+ function isLaterVersion(a, b) {
79
+ if (!b) return true;
80
+ return a.version > b.version;
81
+ }
82
+ function suggestRoutes(manifest, userSegments) {
83
+ if (!userSegments.length) return [];
84
+ return manifest.routes.filter((route) => {
85
+ if (route.segments.length < userSegments.length) return false;
86
+ const firstSeg = route.segments[0];
87
+ return !firstSeg.isParam && firstSeg.value === userSegments[0];
88
+ });
89
+ }
90
+
91
+ // src/args.ts
92
+ function parseArgs(args) {
93
+ const segments = [];
94
+ const headers = {};
95
+ const query = {};
96
+ const body = {};
97
+ let method = "get";
98
+ let token;
99
+ let filter;
100
+ let methodFilter;
101
+ let all = false;
102
+ let verbose = false;
103
+ let dryRun = false;
104
+ let help = false;
105
+ for (let i = 0; i < args.length; i++) {
106
+ const arg = args[i];
107
+ switch (arg) {
108
+ // HTTP methods
109
+ case "-g":
110
+ case "--get":
111
+ method = "get";
112
+ break;
113
+ case "-po":
114
+ case "--post":
115
+ method = "post";
116
+ break;
117
+ case "-pu":
118
+ case "--put":
119
+ method = "put";
120
+ break;
121
+ case "-pa":
122
+ case "--patch":
123
+ method = "patch";
124
+ break;
125
+ case "-d":
126
+ case "--delete":
127
+ method = "delete";
128
+ break;
129
+ case "--head":
130
+ method = "head";
131
+ break;
132
+ // Options
133
+ case "-H":
134
+ case "--header": {
135
+ const val = args[++i];
136
+ const idx = val.indexOf(":");
137
+ if (idx > 0) headers[val.slice(0, idx).trim()] = val.slice(idx + 1).trim();
138
+ break;
139
+ }
140
+ case "--token":
141
+ token = args[++i];
142
+ break;
143
+ case "--filter":
144
+ filter = args[++i];
145
+ break;
146
+ case "--method":
147
+ methodFilter = args[++i];
148
+ break;
149
+ case "--all":
150
+ all = true;
151
+ break;
152
+ case "-v":
153
+ case "--verbose":
154
+ verbose = true;
155
+ break;
156
+ case "--dry-run":
157
+ dryRun = true;
158
+ break;
159
+ case "-h":
160
+ case "--help":
161
+ help = true;
162
+ break;
163
+ default:
164
+ if (arg.startsWith("-")) {
165
+ process.stderr.write(`Unknown flag: ${arg}
166
+ `);
167
+ process.exit(1);
168
+ }
169
+ const eqeq = arg.indexOf("==");
170
+ const eq = arg.indexOf("=");
171
+ if (eqeq > 0) {
172
+ query[arg.slice(0, eqeq)] = arg.slice(eqeq + 2);
173
+ } else if (eq > 0) {
174
+ body[arg.slice(0, eq)] = arg.slice(eq + 1);
175
+ } else {
176
+ segments.push(arg);
177
+ }
178
+ }
179
+ }
180
+ if (Object.keys(body).length && method === "get") method = "post";
181
+ return { segments, method, headers, query, body, token, filter, methodFilter, all, verbose, dryRun, help };
182
+ }
183
+ async function readStdin() {
184
+ if (process.stdin.isTTY) return void 0;
185
+ const chunks = [];
186
+ for await (const chunk of process.stdin) chunks.push(chunk);
187
+ const text = Buffer.concat(chunks).toString("utf-8").trim();
188
+ return text || void 0;
189
+ }
190
+
191
+ // src/help.ts
192
+ import fuzzysort from "fuzzysort";
193
+ function buildTrie(routes) {
194
+ const root = { name: "", isParam: false, methods: [], children: [] };
195
+ for (const route of routes) {
196
+ let node = root;
197
+ for (const seg of route.segments) {
198
+ let child = node.children.find((c) => c.name === seg.value && c.isParam === seg.isParam);
199
+ if (!child) {
200
+ child = { name: seg.value, isParam: seg.isParam, methods: [], children: [] };
201
+ node.children.push(child);
202
+ }
203
+ node = child;
204
+ }
205
+ node.methods.push({ method: route.method, summary: route.summary });
206
+ }
207
+ return root;
208
+ }
209
+ function navigateTrie(root, segments) {
210
+ let node = root;
211
+ const fullPath = [];
212
+ const params = [];
213
+ for (const seg of segments) {
214
+ const staticChild = node.children.find((c) => !c.isParam && c.name === seg);
215
+ if (staticChild) {
216
+ fullPath.push(seg);
217
+ node = staticChild;
218
+ continue;
219
+ }
220
+ const paramChild = node.children.find((c) => c.isParam);
221
+ if (paramChild) {
222
+ const nested = paramChild.children.find((c) => !c.isParam && c.name === seg);
223
+ if (nested) {
224
+ fullPath.push(`<${paramChild.name}>`, seg);
225
+ params.push({ name: paramChild.name, provided: false });
226
+ node = nested;
227
+ continue;
228
+ }
229
+ fullPath.push(seg);
230
+ params.push({ name: paramChild.name, provided: true, value: seg });
231
+ node = paramChild;
232
+ continue;
233
+ }
234
+ return null;
235
+ }
236
+ return { node, fullPath, params };
237
+ }
238
+ function getChildren(node) {
239
+ const statics = node.children.filter((c) => !c.isParam);
240
+ const params = node.children.filter((c) => c.isParam);
241
+ const result = [...statics];
242
+ for (const p of params) result.push(...p.children);
243
+ result.sort((a, b) => a.name.localeCompare(b.name));
244
+ return result;
245
+ }
246
+ var ACTION_ORDER = ["list", "create", "get", "update", "delete"];
247
+ var METHOD_LABEL = {
248
+ get: "\x1B[1;38;5;34mGET\x1B[0m",
249
+ post: "\x1B[1;38;5;25mPOST\x1B[0m",
250
+ put: "\x1B[1;38;5;172mPUT\x1B[0m",
251
+ patch: "\x1B[1;38;5;30mPATCH\x1B[0m",
252
+ delete: "\x1B[1;38;5;160mDELETE\x1B[0m"
253
+ };
254
+ var DIM = "\x1B[2m";
255
+ var ITALIC = "\x1B[3m";
256
+ var RESET = "\x1B[0m";
257
+ function methodToAction(method, isResource) {
258
+ switch (method) {
259
+ case "get":
260
+ return isResource ? "get" : "list";
261
+ case "post":
262
+ return isResource ? "update" : "create";
263
+ case "put":
264
+ case "patch":
265
+ return "update";
266
+ case "delete":
267
+ return "delete";
268
+ default:
269
+ return method;
270
+ }
271
+ }
272
+ function getNodeActions(node) {
273
+ const actions = /* @__PURE__ */ new Set();
274
+ const paramChild = node.children.find((c) => c.isParam);
275
+ const isLeaf = !paramChild;
276
+ for (const ep of node.methods) actions.add(methodToAction(ep.method, isLeaf));
277
+ if (paramChild) {
278
+ for (const ep of paramChild.methods) actions.add(methodToAction(ep.method, true));
279
+ }
280
+ return [...actions].sort((a, b) => ACTION_ORDER.indexOf(a) - ACTION_ORDER.indexOf(b));
281
+ }
282
+ function getParamName(node) {
283
+ return node.children.find((c) => c.isParam)?.name || null;
284
+ }
285
+ function methodLabels(node) {
286
+ const methods = /* @__PURE__ */ new Set();
287
+ for (const ep of node.methods) methods.add(ep.method);
288
+ const paramChild = node.children.find((c) => c.isParam);
289
+ if (paramChild) for (const ep of paramChild.methods) methods.add(ep.method);
290
+ const order = ["get", "post", "put", "patch", "delete"];
291
+ return order.filter((m) => methods.has(m)).map((m) => METHOD_LABEL[m]).join(" ");
292
+ }
293
+ function showHelp() {
294
+ console.log(`\u0262\u1D0F\u1D05\u1D0D\u1D0F\u1D05\u1D07
295
+ \x1B[2mbetter than mcp\x1B[0m
296
+
297
+ Usage:
298
+ godmode <api> <resource> [id] [flags]
299
+ godmode <api> /path [flags]
300
+
301
+ Setup:
302
+ create Create own custom API entrypoint
303
+ add <name|file> Add API as CLI command from <name>.yaml config
304
+ update <name> Re-fetch OpenAPI spec and rebuild routes
305
+ remove <name> Unregister an API
306
+ list Show all registered APIs
307
+ mcp <name> Serve registered API as MCP server (stdio)
308
+ ... Run \x1B[2mgodmode add --help\x1B[0m for config format
309
+
310
+ Navigation:
311
+ <api> --help Show resources, auth, and usage
312
+ <api> <resource> --help Show operations and sub-resources
313
+
314
+ Methods:
315
+ -g, --get GET (default)
316
+ -po, --post POST
317
+ -pu, --put PUT
318
+ -pa, --patch PATCH
319
+ -d, --delete DELETE
320
+
321
+ Data (httpie-style):
322
+ key=value Body field (JSON, implies POST)
323
+ key==value Query param (URL)
324
+
325
+ Options:
326
+ -H <key:value> Add header
327
+ --token <tok> Auth token (overrides config)
328
+ --dry-run Preview request without sending
329
+ -v, --verbose Show full request/response
330
+
331
+ Use "godmode <api> --help" for API-specific usage.`);
332
+ }
333
+ function showApiHelp(manifest, apiName, path, filter, methodFilter, all) {
334
+ const root = buildTrie(manifest.routes);
335
+ const nav = navigateTrie(root, path);
336
+ if (!nav) {
337
+ console.log("No matching resource.");
338
+ return;
339
+ }
340
+ const { node, fullPath } = nav;
341
+ const actions = getNodeActions(node);
342
+ const param = getParamName(node);
343
+ const children = getChildren(node).filter((c) => !c.isParam);
344
+ const childNames = [...new Set(children.map((c) => c.name))];
345
+ const resourceName = fullPath.join(" ");
346
+ if (!path.length) {
347
+ const name = manifest.config.name || apiName;
348
+ const ver = manifest.specVersion ? ` \x1B[2mv${manifest.specVersion}\x1B[0m` : "";
349
+ const desc = manifest.description ? `
350
+ \x1B[2m${manifest.description}\x1B[0m` : "";
351
+ console.log(`${name}${ver}${desc}
352
+ `);
353
+ console.log(`Usage:
354
+ godmode ${apiName} <resource> [id] [flags]
355
+ `);
356
+ const auth = manifest.config.auth;
357
+ if (auth?.env) {
358
+ const ok = !!process.env[auth.env];
359
+ const status = ok ? "\x1B[32m(token provided)\x1B[0m" : "\x1B[31m(missing token)\x1B[0m";
360
+ console.log(`${auth.env} ${status}
361
+ `);
362
+ }
363
+ } else {
364
+ const paramHint = getParamName(node);
365
+ const idRef = paramHint ? ` [${paramHint}]` : "";
366
+ console.log(`Usage:
367
+ godmode ${apiName} ${resourceName}${idRef} [flags]
368
+ `);
369
+ const auth = manifest.config.auth;
370
+ if (auth?.env) {
371
+ const ok = !!process.env[auth.env];
372
+ const status = ok ? "\x1B[32m(token provided)\x1B[0m" : "\x1B[31m(missing token)\x1B[0m";
373
+ console.log(`${auth.env} ${status}`);
374
+ }
375
+ for (const p of nav.params) {
376
+ const status = p.provided ? `\x1B[32m(${p.value})\x1B[0m` : "\x1B[31m(missing parameter)\x1B[0m";
377
+ console.log(`<${p.name}> ${status}`);
378
+ }
379
+ console.log("");
380
+ const mcpTools = manifest.config._mcpTools;
381
+ const mcpTool = mcpTools?.find((t) => t.name === path[path.length - 1]);
382
+ if (mcpTool?.inputSchema?.properties) {
383
+ const props = mcpTool.inputSchema.properties;
384
+ const required = new Set(mcpTool.inputSchema.required || []);
385
+ const toolName = mcpTool.name;
386
+ if (mcpTool.description) {
387
+ const first = mcpTool.description.split("\n")[0];
388
+ console.log(`${ITALIC}${first}${RESET}
389
+ `);
390
+ }
391
+ console.log("Parameters:");
392
+ for (const [name, schema] of Object.entries(props)) {
393
+ const req = required.has(name) ? `\x1B[1;38;5;160m[REQUIRED]${RESET} ` : "";
394
+ const desc = schema.description ? `${DIM}${ITALIC}${schema.description}${RESET}` : "";
395
+ console.log(` ${name.padEnd(28)}${req}${desc}`);
396
+ }
397
+ console.log("");
398
+ console.log(`Example:
399
+ godmode ${apiName} ${toolName} ${[...required].map((r) => `${r}=...`).join(" ")}`);
400
+ console.log("");
401
+ } else {
402
+ const order = ["get", "post", "put", "patch", "delete"];
403
+ const pc = node.children.find((c) => c.isParam);
404
+ const rows = [];
405
+ for (const ep of node.methods) {
406
+ rows.push({ method: ep.method, summary: ep.summary, needsId: false });
407
+ }
408
+ if (pc) {
409
+ for (const ep of pc.methods) {
410
+ rows.push({ method: ep.method, summary: ep.summary, needsId: true });
411
+ }
412
+ }
413
+ if (rows.length) {
414
+ const lastSeg = path[path.length - 1] || resourceName;
415
+ const base = lastSeg;
416
+ const lines = [];
417
+ for (const m of order) {
418
+ for (const r of rows.filter((r2) => r2.method === m)) {
419
+ const id = r.needsId ? ` <${pc.name}>` : "";
420
+ let suffix = "";
421
+ if (m === "post" || m === "put" || m === "patch") suffix = " key=val";
422
+ if (m === "delete") suffix = " -d";
423
+ const needsFlag = !r.needsId && m !== "get" && m !== "post" || r.needsId && m === "post";
424
+ const flag = needsFlag ? ` -${m === "post" ? "po" : m === "put" ? "pu" : m === "patch" ? "pa" : ""}` : "";
425
+ const cmd = `${base}${id}${suffix}${flag}`.trim();
426
+ lines.push({ cmd, label: METHOD_LABEL[m] || m, desc: r.summary || "" });
427
+ }
428
+ }
429
+ const maxCmd = Math.max(...lines.map((l) => l.cmd.length));
430
+ for (const l of lines) {
431
+ const desc = l.desc ? ` ${ITALIC}${l.desc}${RESET}` : "";
432
+ console.log(` ${l.cmd.padEnd(maxCmd + 2)}${l.label}${desc}`);
433
+ }
434
+ console.log("");
435
+ }
436
+ }
437
+ }
438
+ if (childNames.length) {
439
+ const ALL_METHODS = ["get", "post", "put", "patch", "delete"];
440
+ const mf = methodFilter ? fuzzysort.go(methodFilter, ALL_METHODS)[0]?.target : void 0;
441
+ if (methodFilter && !mf) {
442
+ console.log(`
443
+ No method matching "${methodFilter}".`);
444
+ return;
445
+ }
446
+ let candidates = mf ? childNames.filter((n) => {
447
+ const child = children.find((c) => c.name === n);
448
+ if (!child) return false;
449
+ const methods = /* @__PURE__ */ new Set();
450
+ for (const ep of child.methods) methods.add(ep.method);
451
+ const pc = child.children.find((c) => c.isParam);
452
+ if (pc) for (const ep of pc.methods) methods.add(ep.method);
453
+ return methods.has(mf);
454
+ }) : childNames;
455
+ const filtered = filter ? fuzzysort.go(filter, candidates).map((r) => r.target) : candidates;
456
+ const prefix = `godmode ${apiName}${resourceName ? " " + resourceName : ""}`;
457
+ const label = filter || mf ? `Resources${filter ? ` matching "${filter}"` : ""}${mf ? ` with ${mf.toUpperCase()}${methodFilter !== mf ? ` (matched "${methodFilter}")` : ""}` : ""}:` : "Resources:";
458
+ console.log(`
459
+ ${label}`);
460
+ if (!filtered.length) {
461
+ console.log(" No matches.");
462
+ } else {
463
+ const limit = filter || mf || all ? filtered.length : 5;
464
+ const shown = filtered.slice(0, limit);
465
+ const more = filtered.length - shown.length;
466
+ for (const n of shown) {
467
+ const child = children.find((c) => c.name === n);
468
+ const labels = child ? methodLabels(child) : "";
469
+ const subCount = child ? getChildren(child).filter((c) => !c.isParam).length : 0;
470
+ const sub = subCount ? ` ${DIM}(${subCount} sub)${RESET}` : "";
471
+ const rawDesc = manifest.resourceDescriptions[n] || "";
472
+ const truncated = rawDesc.length > 60 ? rawDesc.slice(0, 57) + "..." : rawDesc;
473
+ const descStr = truncated ? ` ${ITALIC}${truncated}${RESET}` : "";
474
+ console.log(` ${n.padEnd(28)}${labels}${sub}${descStr}`);
475
+ }
476
+ if (more) {
477
+ console.log(` \x1B[2m...${more} more \u2192 use --all, --filter or --method\x1B[0m`);
478
+ }
479
+ }
480
+ console.log(`
481
+ Use "${prefix} <resource> --help" for more.`);
482
+ }
483
+ }
484
+
485
+ // src/index.ts
486
+ loadEnv();
487
+ async function main() {
488
+ const args = process.argv.slice(2);
489
+ if (!args.length || args.length === 1 && (args[0] === "-h" || args[0] === "--help")) {
490
+ showHelp();
491
+ return;
492
+ }
493
+ const cmd = args[0];
494
+ if (cmd === "create") {
495
+ const { configWizard } = await import("./prompt-DCFFOQ7K.js");
496
+ await configWizard();
497
+ return;
498
+ }
499
+ if (cmd === "add") {
500
+ const { runAdd } = await import("./add-ZRNIX6DM.js");
501
+ await runAdd(args.slice(1));
502
+ return;
503
+ }
504
+ if (cmd === "update") {
505
+ if (!args[1]) {
506
+ console.error("Usage: godmode update <name>");
507
+ process.exit(1);
508
+ }
509
+ await updateApi(args[1]);
510
+ return;
511
+ }
512
+ if (cmd === "remove") {
513
+ if (!args[1]) {
514
+ console.error("Usage: godmode remove <name>");
515
+ process.exit(1);
516
+ }
517
+ await removeApi(args[1]);
518
+ return;
519
+ }
520
+ if (cmd === "list") {
521
+ await listApis();
522
+ return;
523
+ }
524
+ if (cmd === "mcp") {
525
+ const { runMcp } = await import("./mcp-NZIEMQOM.js");
526
+ await runMcp(args.slice(1));
527
+ return;
528
+ }
529
+ const apiName = cmd;
530
+ const parsed = parseArgs(args.slice(1));
531
+ const manifest = await loadManifest(apiName);
532
+ if (parsed.help || !parsed.segments.length) {
533
+ showApiHelp(manifest, apiName, parsed.segments, parsed.filter, parsed.methodFilter, parsed.all);
534
+ return;
535
+ }
536
+ if (manifest.config.type === "graphql") {
537
+ const err = validateGraphQLFlags(parsed.method, parsed.query, parsed.body, apiName);
538
+ if (err) {
539
+ process.stderr.write(err + "\n");
540
+ process.exit(1);
541
+ }
542
+ }
543
+ if (manifest.config.type === "mcp") {
544
+ const err = validateMcpFlags(parsed.method, parsed.query);
545
+ if (err) {
546
+ process.stderr.write(err + "\n");
547
+ process.exit(1);
548
+ }
549
+ const result = await executeMcpTool(manifest.config, parsed.segments[0], parsed.body, {
550
+ verbose: parsed.verbose,
551
+ dryRun: parsed.dryRun
552
+ });
553
+ if (result) process.stdout.write(result + "\n");
554
+ return;
555
+ }
556
+ const query = parsed.query;
557
+ const hasBody = Object.keys(parsed.body).length > 0;
558
+ let body = hasBody ? JSON.stringify(parsed.body) : void 0;
559
+ if (!body && ["post", "put", "patch"].includes(parsed.method)) body = await readStdin();
560
+ if (parsed.segments[0]?.startsWith("/")) {
561
+ const rawPath = parsed.segments[0];
562
+ const syntheticRoute = { path: rawPath, method: parsed.method, summary: "", version: "", segments: [] };
563
+ await execute(manifest, { route: syntheticRoute, params: {} }, {
564
+ headers: parsed.headers,
565
+ query,
566
+ body,
567
+ token: parsed.token,
568
+ verbose: parsed.verbose,
569
+ dryRun: parsed.dryRun
570
+ });
571
+ return;
572
+ }
573
+ const match = matchRoute(manifest, parsed.segments, parsed.method);
574
+ if (!match) {
575
+ process.stderr.write(`No ${parsed.method.toUpperCase()} route matching: ${parsed.segments.join(" ")}
576
+ `);
577
+ for (const m of ["get", "post", "put", "patch", "delete"]) {
578
+ if (m === parsed.method) continue;
579
+ const alt = matchRoute(manifest, parsed.segments, m);
580
+ if (alt) {
581
+ const flag = m === "delete" ? "-d" : `--${m}`;
582
+ process.stderr.write(` try: godmode ${apiName} ${parsed.segments.join(" ")} ${flag}
583
+ `);
584
+ }
585
+ }
586
+ const similar = suggestRoutes(manifest, parsed.segments).slice(0, 5);
587
+ if (similar.length) {
588
+ process.stderr.write("\nSimilar:\n");
589
+ const seen = /* @__PURE__ */ new Set();
590
+ for (const r of similar) {
591
+ const p = r.segments.map((s) => s.isParam ? `{${s.value}}` : s.value).join(" ");
592
+ if (seen.has(p)) continue;
593
+ seen.add(p);
594
+ process.stderr.write(` ${p}
595
+ `);
596
+ }
597
+ }
598
+ process.exit(1);
599
+ }
600
+ await execute(manifest, match, {
601
+ headers: parsed.headers,
602
+ query,
603
+ body,
604
+ token: parsed.token,
605
+ verbose: parsed.verbose,
606
+ dryRun: parsed.dryRun
607
+ });
608
+ }
609
+ main().catch((err) => {
610
+ process.stderr.write(`${err.message || err}
611
+ `);
612
+ process.exit(1);
613
+ });
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GODMODE_HOME,
4
+ loadManifest
5
+ } from "./chunk-FBMQ3AC4.js";
6
+ import "./chunk-EHS56XXY.js";
7
+
8
+ // src/commands/mcp.ts
9
+ import { resolve } from "path";
10
+ import { readFile } from "fs/promises";
11
+ import { spawn } from "child_process";
12
+ async function runMcp(args) {
13
+ const name = args[0];
14
+ if (!name || name === "--help" || name === "-h") {
15
+ console.log(`Serve a registered API as an MCP server over stdio.
16
+
17
+ Usage:
18
+ godmode mcp <name> [options]
19
+
20
+ Options:
21
+ --filter <text> Fuzzy-filter routes by resource name
22
+ --method <method> Filter by HTTP method (get, post, etc.)
23
+
24
+ For package-based adapters, spawns the adapter's MCP server.
25
+ For spec-based adapters, serves routes as MCP tools.`);
26
+ process.exit(name ? 0 : 1);
27
+ }
28
+ let filter;
29
+ let method;
30
+ for (let i = 1; i < args.length; i++) {
31
+ if (args[i] === "--filter" && args[i + 1]) filter = args[++i];
32
+ else if (args[i] === "--method" && args[i + 1]) method = args[++i];
33
+ }
34
+ const pkgName = name.startsWith("@") ? name : `@godmode-cli/${name}`;
35
+ const pkgDir = resolve(GODMODE_HOME, "node_modules", pkgName);
36
+ const mcpConfigPath = resolve(pkgDir, ".mcp.json");
37
+ try {
38
+ const mcpConfig = JSON.parse(await readFile(mcpConfigPath, "utf-8"));
39
+ const serverKey = Object.keys(mcpConfig.mcpServers)[0];
40
+ const server = mcpConfig.mcpServers[serverKey];
41
+ if (server.type === "stdio" && server.command) {
42
+ const child = spawn(server.command, server.args || [], {
43
+ stdio: "inherit",
44
+ cwd: pkgDir,
45
+ env: { ...process.env, ...server.env }
46
+ });
47
+ child.on("exit", (code) => process.exit(code ?? 0));
48
+ return;
49
+ }
50
+ } catch {
51
+ }
52
+ const manifest = await loadManifest(name);
53
+ const { serveMcp } = await import("./mcp-server.js");
54
+ await serveMcp(manifest, { filter, method });
55
+ }
56
+ export {
57
+ runMcp
58
+ };