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/LICENSE +21 -0
- package/dist/add-ZRNIX6DM.js +44 -0
- package/dist/chunk-EHS56XXY.js +163 -0
- package/dist/chunk-FBMQ3AC4.js +339 -0
- package/dist/chunk-I4WS4MMI.js +92 -0
- package/dist/index.js +613 -0
- package/dist/mcp-NZIEMQOM.js +58 -0
- package/dist/mcp-server.js +129 -0
- package/dist/prompt-DCFFOQ7K.js +96 -0
- package/package.json +46 -0
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
|
+
};
|