godmode 0.0.2 → 0.0.3
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/{add-AJERMPEA.js → add-D74TIN3J.js} +12 -11
- package/dist/{chunk-EXWYJU2I.js → chunk-3AQ2CZKC.js} +5 -4
- package/dist/chunk-4EU5JX6C.js +160 -0
- package/dist/{chunk-GJCJO7YT.js → chunk-AWGLXW3B.js} +24 -11
- package/dist/{chunk-2J2VDCQ4.js → chunk-KHFZI7IA.js} +380 -138
- package/dist/chunk-SMQBMG24.js +102 -0
- package/dist/chunk-XFFBFBN6.js +16 -0
- package/dist/chunk-YDXTIN53.js +212 -0
- package/dist/chunk-ZTPILA7B.js +403 -0
- package/dist/dist-I5CEQ4AC.js +779 -0
- package/dist/ext-K4XEAZGS.js +102 -0
- package/dist/index.js +262 -1540
- package/dist/permissions-E4G66OUZ.js +125 -0
- package/dist/{prompt-SWOWWAOH.js → prompt-YPAZ5433.js} +12 -6
- package/dist/{server-4YVOAJJG.js → server-L3L4TKK3.js} +19 -2
- package/dist/settings-3FF5WWEY.js +17 -0
- package/package.json +10 -9
package/dist/index.js
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
showApiHelp,
|
|
4
|
+
showExtensionOverview,
|
|
5
|
+
showExtensionVersion,
|
|
6
|
+
showHelp,
|
|
7
|
+
showVersion
|
|
8
|
+
} from "./chunk-ZTPILA7B.js";
|
|
9
|
+
import {
|
|
10
|
+
checkPermission,
|
|
11
|
+
matchRoute,
|
|
12
|
+
resourceFromRawPath,
|
|
13
|
+
resourceFromSegments,
|
|
14
|
+
resourceFromTool,
|
|
15
|
+
suggestRoutes,
|
|
16
|
+
suggestedAllowRule
|
|
17
|
+
} from "./chunk-4EU5JX6C.js";
|
|
18
|
+
import {
|
|
19
|
+
EXIT_CODES
|
|
20
|
+
} from "./chunk-XFFBFBN6.js";
|
|
21
|
+
import {
|
|
22
|
+
warnSettingsErrors
|
|
23
|
+
} from "./chunk-SMQBMG24.js";
|
|
5
24
|
import {
|
|
6
|
-
|
|
25
|
+
BUILTINS,
|
|
7
26
|
GODMODE_HOME,
|
|
8
|
-
|
|
27
|
+
findInstalledManifestSync,
|
|
9
28
|
loadManifest,
|
|
10
29
|
loadMultiManifest,
|
|
11
|
-
removeApi,
|
|
12
|
-
updateApi,
|
|
13
30
|
validateGraphQLFlags
|
|
14
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-KHFZI7IA.js";
|
|
32
|
+
import {
|
|
33
|
+
execute
|
|
34
|
+
} from "./chunk-AWGLXW3B.js";
|
|
15
35
|
import {
|
|
16
36
|
executeMcpTool,
|
|
17
37
|
validateMcpFlags
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
22
|
-
import { resolve as resolve5 } from "path";
|
|
38
|
+
} from "./chunk-3AQ2CZKC.js";
|
|
39
|
+
import "./chunk-YDXTIN53.js";
|
|
23
40
|
|
|
24
41
|
// src/env.ts
|
|
25
42
|
import { readFileSync } from "fs";
|
|
@@ -42,108 +59,6 @@ function loadEnv() {
|
|
|
42
59
|
}
|
|
43
60
|
}
|
|
44
61
|
|
|
45
|
-
// ../../interfaces/api/src/match.ts
|
|
46
|
-
function matchRoute(manifest, userSegments, method) {
|
|
47
|
-
if (userSegments.length > 0) {
|
|
48
|
-
const ver = manifest.versions.find((v) => v.name === userSegments[0]);
|
|
49
|
-
if (ver) {
|
|
50
|
-
const match = findMatch(manifest.routes, userSegments.slice(1), method, ver.name);
|
|
51
|
-
if (match) return match;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return findMatch(manifest.routes, userSegments, method);
|
|
55
|
-
}
|
|
56
|
-
function findMatch(routes, userSegments, method, version) {
|
|
57
|
-
let bestMatch = null;
|
|
58
|
-
let bestScore = -1;
|
|
59
|
-
for (const route of routes) {
|
|
60
|
-
if (route.method !== method) continue;
|
|
61
|
-
if (route.segments.length !== userSegments.length) continue;
|
|
62
|
-
if (version !== void 0 && route.version !== version) continue;
|
|
63
|
-
let score = 0;
|
|
64
|
-
const params = {};
|
|
65
|
-
let matches = true;
|
|
66
|
-
for (let i = 0; i < route.segments.length; i++) {
|
|
67
|
-
const seg = route.segments[i];
|
|
68
|
-
const userSeg = userSegments[i];
|
|
69
|
-
if (seg.isParam) {
|
|
70
|
-
params[seg.value] = userSeg;
|
|
71
|
-
} else if (seg.value === userSeg) {
|
|
72
|
-
score += 1;
|
|
73
|
-
} else {
|
|
74
|
-
matches = false;
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (matches && (score > bestScore || score === bestScore && isLaterVersion(route, bestMatch?.route))) {
|
|
79
|
-
bestScore = score;
|
|
80
|
-
bestMatch = { route, params };
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return bestMatch;
|
|
84
|
-
}
|
|
85
|
-
function isLaterVersion(a, b) {
|
|
86
|
-
if (!b) return true;
|
|
87
|
-
return a.version > b.version;
|
|
88
|
-
}
|
|
89
|
-
function suggestRoutes(manifest, userSegments) {
|
|
90
|
-
if (!userSegments.length) return [];
|
|
91
|
-
return manifest.routes.filter((route) => {
|
|
92
|
-
if (route.segments.length < userSegments.length) return false;
|
|
93
|
-
const firstSeg = route.segments[0];
|
|
94
|
-
return !firstSeg.isParam && firstSeg.value === userSegments[0];
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ../../interfaces/mcp/src/command.ts
|
|
99
|
-
import { resolve as resolve2 } from "path";
|
|
100
|
-
import { readFile } from "fs/promises";
|
|
101
|
-
import { spawn } from "child_process";
|
|
102
|
-
async function runMcp(rt, args) {
|
|
103
|
-
const name = args[0];
|
|
104
|
-
if (!name || name === "--help" || name === "-h") {
|
|
105
|
-
console.log(`Serve a registered API as an MCP server over stdio.
|
|
106
|
-
|
|
107
|
-
Usage:
|
|
108
|
-
godmode mcp <name> [options]
|
|
109
|
-
|
|
110
|
-
Options:
|
|
111
|
-
--filter <text> Fuzzy-filter routes by resource name
|
|
112
|
-
--method <method> Filter by HTTP method (get, post, etc.)
|
|
113
|
-
|
|
114
|
-
For package-based extensions, spawns the extension's MCP server.
|
|
115
|
-
For spec-based extensions, serves routes as MCP tools.`);
|
|
116
|
-
process.exit(name ? 0 : 1);
|
|
117
|
-
}
|
|
118
|
-
let filter;
|
|
119
|
-
let method;
|
|
120
|
-
for (let i = 1; i < args.length; i++) {
|
|
121
|
-
if (args[i] === "--filter" && args[i + 1]) filter = args[++i];
|
|
122
|
-
else if (args[i] === "--method" && args[i + 1]) method = args[++i];
|
|
123
|
-
}
|
|
124
|
-
const pkgName = name.startsWith("@") ? name : `@godmode-cli/${name}`;
|
|
125
|
-
const pkgDir = resolve2(rt.godmodeHome, "node_modules", pkgName);
|
|
126
|
-
const mcpConfigPath = resolve2(pkgDir, ".mcp.json");
|
|
127
|
-
try {
|
|
128
|
-
const mcpConfig = JSON.parse(await readFile(mcpConfigPath, "utf-8"));
|
|
129
|
-
const serverKey = Object.keys(mcpConfig.mcpServers)[0];
|
|
130
|
-
const server = mcpConfig.mcpServers[serverKey];
|
|
131
|
-
if (server.type === "stdio" && server.command) {
|
|
132
|
-
const child = spawn(server.command, server.args || [], {
|
|
133
|
-
stdio: "inherit",
|
|
134
|
-
cwd: pkgDir,
|
|
135
|
-
env: { ...process.env, ...server.env }
|
|
136
|
-
});
|
|
137
|
-
child.on("exit", (code) => process.exit(code ?? 0));
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
} catch {
|
|
141
|
-
}
|
|
142
|
-
const manifest = await rt.loadManifest(name);
|
|
143
|
-
const { serveMcp } = await import("./server-4YVOAJJG.js");
|
|
144
|
-
await serveMcp(manifest, { filter, method });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
62
|
// src/args.ts
|
|
148
63
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
149
64
|
function parseArgs(args) {
|
|
@@ -156,7 +71,7 @@ function parseArgs(args) {
|
|
|
156
71
|
let filter;
|
|
157
72
|
let methodFilter;
|
|
158
73
|
let all = false;
|
|
159
|
-
const
|
|
74
|
+
const debug = process.env.GODMODE_DEBUG === "1";
|
|
160
75
|
const dryRun = process.env.GODMODE_DRY_RUN === "1";
|
|
161
76
|
let help = false;
|
|
162
77
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -208,7 +123,7 @@ function parseArgs(args) {
|
|
|
208
123
|
}
|
|
209
124
|
}
|
|
210
125
|
}
|
|
211
|
-
return { segments, method, explicitMethod, headers, query, body, filter, methodFilter, all,
|
|
126
|
+
return { segments, method, explicitMethod, headers, query, body, filter, methodFilter, all, debug, dryRun, help };
|
|
212
127
|
}
|
|
213
128
|
async function readStdin() {
|
|
214
129
|
if (process.stdin.isTTY) return void 0;
|
|
@@ -218,1266 +133,258 @@ async function readStdin() {
|
|
|
218
133
|
return text || void 0;
|
|
219
134
|
}
|
|
220
135
|
|
|
221
|
-
// src/
|
|
222
|
-
import
|
|
136
|
+
// ../../interfaces/mcp/src/command.ts
|
|
137
|
+
import { resolve as resolve2 } from "path";
|
|
138
|
+
import { readFile } from "fs/promises";
|
|
139
|
+
import { spawn } from "child_process";
|
|
140
|
+
async function runMcp(rt, args) {
|
|
141
|
+
const name = args[0];
|
|
142
|
+
if (!name || name === "--help" || name === "-h") {
|
|
143
|
+
console.log(`Serve a registered API as an MCP server over stdio.
|
|
223
144
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
var DIM = USE_COLOR ? "\x1B[2m" : "";
|
|
227
|
-
var ITALIC = USE_COLOR ? "\x1B[3m" : "";
|
|
228
|
-
var RESET = USE_COLOR ? "\x1B[0m" : "";
|
|
229
|
-
var RED = USE_COLOR ? "\x1B[31m" : "";
|
|
230
|
-
function visibleLength(s) {
|
|
231
|
-
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
232
|
-
}
|
|
233
|
-
function wrapText(text, width) {
|
|
234
|
-
if (text.length <= width) return [text];
|
|
235
|
-
const out = [];
|
|
236
|
-
let line = "";
|
|
237
|
-
for (const word of text.split(/\s+/)) {
|
|
238
|
-
if (!line) {
|
|
239
|
-
line = word;
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (line.length + 1 + word.length <= width) {
|
|
243
|
-
line += " " + word;
|
|
244
|
-
} else {
|
|
245
|
-
out.push(line);
|
|
246
|
-
line = word;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if (line) out.push(line);
|
|
250
|
-
return out;
|
|
251
|
-
}
|
|
252
|
-
function renderSections(sections, maxLineWidth = 80) {
|
|
253
|
-
const INDENT = " ";
|
|
254
|
-
const GAP = " ";
|
|
255
|
-
const flattened = sections.map((s) => {
|
|
256
|
-
if (!s.rows.length) return { title: s.title, rows: [] };
|
|
257
|
-
const cols = Math.max(...s.rows.map((r) => r.length));
|
|
258
|
-
const widths = Array(cols).fill(0);
|
|
259
|
-
for (const row of s.rows) {
|
|
260
|
-
for (let i = 0; i < cols; i++) {
|
|
261
|
-
widths[i] = Math.max(widths[i], visibleLength(row[i] ?? ""));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const rows = s.rows.map((row) => {
|
|
265
|
-
const left = row.slice(0, cols - 1).map((cell, i) => (cell ?? "") + " ".repeat(widths[i] - visibleLength(cell ?? ""))).join(GAP).replace(/\s+$/, "");
|
|
266
|
-
const desc = row[cols - 1] ?? "";
|
|
267
|
-
return [left, desc];
|
|
268
|
-
});
|
|
269
|
-
return { title: s.title, rows };
|
|
270
|
-
});
|
|
271
|
-
const allRows = flattened.flatMap((s) => s.rows);
|
|
272
|
-
if (!allRows.length) return;
|
|
273
|
-
const rowsWithDesc = allRows.filter((r) => r[1].length > 0);
|
|
274
|
-
const widthCandidates = (rowsWithDesc.length ? rowsWithDesc : allRows).map((r) => visibleLength(r[0]));
|
|
275
|
-
const globalLeft = Math.max(...widthCandidates);
|
|
276
|
-
const descBudget = Math.max(20, maxLineWidth - INDENT.length - globalLeft - GAP.length);
|
|
277
|
-
for (const s of flattened) {
|
|
278
|
-
if (!s.rows.length) continue;
|
|
279
|
-
if (s.title) {
|
|
280
|
-
console.log("");
|
|
281
|
-
console.log(s.title);
|
|
282
|
-
}
|
|
283
|
-
for (const [left, desc] of s.rows) {
|
|
284
|
-
const pad = " ".repeat(Math.max(0, globalLeft - visibleLength(left)));
|
|
285
|
-
const plain = desc.replace(/\x1b\[[0-9;]*m/g, "");
|
|
286
|
-
if (!plain) {
|
|
287
|
-
console.log(`${INDENT}${left}${pad}`.trimEnd());
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
if (plain.length <= descBudget) {
|
|
291
|
-
console.log(`${INDENT}${left}${pad}${GAP}${desc}`);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
const italic = /^\x1b\[3m/.test(desc);
|
|
295
|
-
const wrap = (s2) => italic ? `${ITALIC}${s2}${RESET}` : s2;
|
|
296
|
-
const lines = wrapText(plain, Math.max(10, descBudget));
|
|
297
|
-
console.log(`${INDENT}${left}${pad}${GAP}${wrap(lines[0])}`);
|
|
298
|
-
for (let i = 1; i < lines.length; i++) {
|
|
299
|
-
console.log(`${INDENT}${" ".repeat(Math.max(0, globalLeft))}${GAP}${wrap(lines[i])}`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function authMissingLabel(type) {
|
|
305
|
-
switch (type) {
|
|
306
|
-
case "api-key":
|
|
307
|
-
return "missing api-key";
|
|
308
|
-
case "basic":
|
|
309
|
-
return "missing basic auth credentials";
|
|
310
|
-
case "bearer":
|
|
311
|
-
default:
|
|
312
|
-
return "missing bearer token";
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
var HelpPage = class {
|
|
316
|
-
title() {
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
|
-
tagline() {
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
authNote() {
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
usage() {
|
|
326
|
-
return [];
|
|
327
|
-
}
|
|
328
|
-
sections() {
|
|
329
|
-
return [];
|
|
330
|
-
}
|
|
331
|
-
footer() {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
toJSON() {
|
|
335
|
-
return {
|
|
336
|
-
title: this.title(),
|
|
337
|
-
tagline: this.tagline(),
|
|
338
|
-
authNote: this.authNote(),
|
|
339
|
-
usage: this.usage(),
|
|
340
|
-
sections: this.sections(),
|
|
341
|
-
footer: this.footer()
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
render() {
|
|
345
|
-
const d = this.toJSON();
|
|
346
|
-
let wrote = false;
|
|
347
|
-
if (d.title) {
|
|
348
|
-
console.log(d.title);
|
|
349
|
-
wrote = true;
|
|
350
|
-
}
|
|
351
|
-
if (d.tagline) {
|
|
352
|
-
if (wrote) console.log("");
|
|
353
|
-
console.log(d.tagline);
|
|
354
|
-
wrote = true;
|
|
355
|
-
}
|
|
356
|
-
if (d.authNote && !d.authNote.present) {
|
|
357
|
-
const arrow = USE_COLOR ? `${RED}-->${RESET}` : "-->";
|
|
358
|
-
const label = authMissingLabel(d.authNote.authType);
|
|
359
|
-
const body = USE_COLOR ? `${RED}${label}${RESET}` : label;
|
|
360
|
-
if (wrote) console.log("");
|
|
361
|
-
console.log(`${arrow} ${d.authNote.env}: ${body}`);
|
|
362
|
-
wrote = true;
|
|
363
|
-
}
|
|
364
|
-
if (d.usage.length) {
|
|
365
|
-
if (wrote) console.log("");
|
|
366
|
-
for (let i = 0; i < d.usage.length; i++) {
|
|
367
|
-
const prefix = i === 0 ? "Usage:" : " or:";
|
|
368
|
-
console.log(`${prefix} ${d.usage[i]}`);
|
|
369
|
-
}
|
|
370
|
-
wrote = true;
|
|
371
|
-
}
|
|
372
|
-
renderSections(d.sections);
|
|
373
|
-
if (d.footer) {
|
|
374
|
-
if (d.footer.extras?.length) {
|
|
375
|
-
console.log("");
|
|
376
|
-
for (const line of d.footer.extras) console.log(line);
|
|
377
|
-
}
|
|
378
|
-
if (d.footer.reportBugs || d.footer.homepage) {
|
|
379
|
-
console.log("");
|
|
380
|
-
if (d.footer.reportBugs) console.log(`Report bugs to <${d.footer.reportBugs}>.`);
|
|
381
|
-
if (d.footer.homepage) console.log(`Godmode home page: <${d.footer.homepage}>.`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
};
|
|
145
|
+
Usage:
|
|
146
|
+
godmode mcp <name> [options]
|
|
386
147
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if (!child) {
|
|
395
|
-
child = { name: seg.value, isParam: seg.isParam, methods: [], children: [] };
|
|
396
|
-
node.children.push(child);
|
|
397
|
-
}
|
|
398
|
-
node = child;
|
|
399
|
-
}
|
|
400
|
-
node.methods.push({ method: route.method, summary: route.summary });
|
|
401
|
-
}
|
|
402
|
-
return root;
|
|
403
|
-
}
|
|
404
|
-
function navigateTrie(root, segments) {
|
|
405
|
-
let node = root;
|
|
406
|
-
const fullPath = [];
|
|
407
|
-
const params = [];
|
|
408
|
-
for (const seg of segments) {
|
|
409
|
-
const staticChild = node.children.find((c) => !c.isParam && c.name === seg);
|
|
410
|
-
if (staticChild) {
|
|
411
|
-
fullPath.push(seg);
|
|
412
|
-
node = staticChild;
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
const paramChild = node.children.find((c) => c.isParam);
|
|
416
|
-
if (paramChild) {
|
|
417
|
-
const nested = paramChild.children.find((c) => !c.isParam && c.name === seg);
|
|
418
|
-
if (nested) {
|
|
419
|
-
fullPath.push(`<${paramChild.name}>`, seg);
|
|
420
|
-
params.push({ name: paramChild.name, provided: false });
|
|
421
|
-
node = nested;
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
fullPath.push(seg);
|
|
425
|
-
params.push({ name: paramChild.name, provided: true, value: seg });
|
|
426
|
-
node = paramChild;
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
return { node, fullPath, params };
|
|
432
|
-
}
|
|
433
|
-
function getChildren(node) {
|
|
434
|
-
const statics = node.children.filter((c) => !c.isParam);
|
|
435
|
-
const params = node.children.filter((c) => c.isParam);
|
|
436
|
-
const result = [...statics];
|
|
437
|
-
for (const p of params) result.push(...p.children);
|
|
438
|
-
result.sort((a, b) => a.name.localeCompare(b.name));
|
|
439
|
-
return result;
|
|
440
|
-
}
|
|
441
|
-
var METHOD_LABEL = USE_COLOR ? {
|
|
442
|
-
get: "\x1B[1;38;5;34mGET\x1B[0m",
|
|
443
|
-
post: "\x1B[1;38;5;25mPOST\x1B[0m",
|
|
444
|
-
put: "\x1B[1;38;5;172mPUT\x1B[0m",
|
|
445
|
-
patch: "\x1B[1;38;5;30mPATCH\x1B[0m",
|
|
446
|
-
delete: "\x1B[1;38;5;160mDELETE\x1B[0m"
|
|
447
|
-
} : { get: "GET", post: "POST", put: "PUT", patch: "PATCH", delete: "DELETE" };
|
|
448
|
-
var INTERFACE_LABEL = {
|
|
449
|
-
api: "API",
|
|
450
|
-
graphql: "GraphQL",
|
|
451
|
-
mcp: "MCP",
|
|
452
|
-
skill: "Skill"
|
|
453
|
-
};
|
|
454
|
-
var titleCase = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
455
|
-
var ROOT_OPTION_ROWS = [
|
|
456
|
-
["-v, --version", "output version information and exit"]
|
|
457
|
-
];
|
|
458
|
-
var INTERFACE_OPTION_ROWS = [
|
|
459
|
-
["-H, --header <key:value>", "add a request header"],
|
|
460
|
-
["-A, --all", "list all resources"],
|
|
461
|
-
["-F, --filter <text>", "fuzzy-filter resources by name"],
|
|
462
|
-
["-X, --method <verb>", "filter resources by HTTP method"],
|
|
463
|
-
["-h, --help", "show help for this subcommand"],
|
|
464
|
-
["-v, --version", "show extension spec versions"]
|
|
465
|
-
];
|
|
466
|
-
function getParamName(node) {
|
|
467
|
-
return node.children.find((c) => c.isParam)?.name || null;
|
|
468
|
-
}
|
|
469
|
-
function methodLabels(node) {
|
|
470
|
-
const methods = /* @__PURE__ */ new Set();
|
|
471
|
-
for (const ep of node.methods) methods.add(ep.method);
|
|
472
|
-
const paramChild = node.children.find((c) => c.isParam);
|
|
473
|
-
if (paramChild) for (const ep of paramChild.methods) methods.add(ep.method);
|
|
474
|
-
const order = ["get", "post", "put", "patch", "delete"];
|
|
475
|
-
return order.filter((m) => methods.has(m)).map((m) => METHOD_LABEL[m]).join(" ");
|
|
476
|
-
}
|
|
477
|
-
var RootHelp = class extends HelpPage {
|
|
478
|
-
tagline() {
|
|
479
|
-
return "A control surface of agentic I/O.";
|
|
480
|
-
}
|
|
481
|
-
usage() {
|
|
482
|
-
return [
|
|
483
|
-
"godmode <extension> [args]...",
|
|
484
|
-
"godmode <extension> <interface> [args]..."
|
|
485
|
-
];
|
|
486
|
-
}
|
|
487
|
-
sections() {
|
|
488
|
-
return [
|
|
489
|
-
{ title: "Built-in extensions:", rows: [
|
|
490
|
-
["ext", "Install and manage godmode extensions"],
|
|
491
|
-
["agent", "Coding-agent workflows"]
|
|
492
|
-
] },
|
|
493
|
-
{ title: "Options:", rows: ROOT_OPTION_ROWS }
|
|
494
|
-
];
|
|
495
|
-
}
|
|
496
|
-
footer() {
|
|
497
|
-
return {
|
|
498
|
-
extras: [
|
|
499
|
-
'Run "godmode ext list" to see installed extensions.',
|
|
500
|
-
'Run "godmode <extension> --help" for extension-specific usage.'
|
|
501
|
-
],
|
|
502
|
-
reportBugs: "https://github.com/tomsiwik/godmode/issues",
|
|
503
|
-
homepage: "https://godmode.so"
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
var ExtensionOverview = class extends HelpPage {
|
|
508
|
-
constructor(multi) {
|
|
509
|
-
super();
|
|
510
|
-
this.multi = multi;
|
|
511
|
-
}
|
|
512
|
-
title() {
|
|
513
|
-
return titleCase(this.multi.name || this.multi.slug);
|
|
514
|
-
}
|
|
515
|
-
usage() {
|
|
516
|
-
const declared = Object.keys(this.multi.interfaces);
|
|
517
|
-
const args = (iface) => iface === "mcp" ? " <tool> [args]" : iface === "graphql" ? " <query> [flags]" : " <method> <resource> [id] [flags]";
|
|
518
|
-
return declared.map((iface) => `godmode ${this.multi.slug} ${iface}${args(iface)}`);
|
|
519
|
-
}
|
|
520
|
-
sections() {
|
|
521
|
-
const declared = Object.keys(this.multi.interfaces);
|
|
522
|
-
return [
|
|
523
|
-
{ title: "Interfaces:", rows: declared.map((iface) => {
|
|
524
|
-
const d = this.multi.interfaces[iface];
|
|
525
|
-
const url = d && "url" in d && d.url ? d.url : "(local)";
|
|
526
|
-
return [iface, url];
|
|
527
|
-
}) },
|
|
528
|
-
{ title: "Options:", rows: [["-v, --version", "show extension spec versions"]] }
|
|
529
|
-
];
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
var ExtensionVersionPage = class extends HelpPage {
|
|
533
|
-
constructor(multi) {
|
|
534
|
-
super();
|
|
535
|
-
this.multi = multi;
|
|
536
|
-
}
|
|
537
|
-
title() {
|
|
538
|
-
return titleCase(this.multi.name || this.multi.slug);
|
|
539
|
-
}
|
|
540
|
-
sections() {
|
|
541
|
-
const declared = Object.keys(this.multi.interfaces);
|
|
542
|
-
return [
|
|
543
|
-
{ title: "", rows: declared.map((iface) => {
|
|
544
|
-
const d = this.multi.interfaces[iface];
|
|
545
|
-
return [iface, d?.specVersion || "(unversioned)"];
|
|
546
|
-
}) }
|
|
547
|
-
];
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
var InterfaceHelp = class extends HelpPage {
|
|
551
|
-
constructor(manifest, apiName, rawPath = [], opts = {}) {
|
|
552
|
-
super();
|
|
553
|
-
this.manifest = manifest;
|
|
554
|
-
this.apiName = apiName;
|
|
555
|
-
this.opts = opts;
|
|
556
|
-
const HTTP_VERBS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
557
|
-
const cleaned = [...rawPath];
|
|
558
|
-
while (cleaned.length && HTTP_VERBS.has(cleaned[cleaned.length - 1].toUpperCase())) {
|
|
559
|
-
cleaned.pop();
|
|
560
|
-
}
|
|
561
|
-
this.path = cleaned;
|
|
562
|
-
}
|
|
563
|
-
path;
|
|
564
|
-
get ifaceType() {
|
|
565
|
-
return this.manifest.config.type;
|
|
566
|
-
}
|
|
567
|
-
getNav() {
|
|
568
|
-
const root = buildTrie(this.manifest.routes);
|
|
569
|
-
return navigateTrie(root, this.path);
|
|
570
|
-
}
|
|
571
|
-
title() {
|
|
572
|
-
if (this.path.length) return null;
|
|
573
|
-
return `${titleCase(this.manifest.config.name || this.apiName)} ${INTERFACE_LABEL[this.ifaceType] || this.ifaceType}`;
|
|
574
|
-
}
|
|
575
|
-
authNote() {
|
|
576
|
-
const auth = this.manifest.config.auth;
|
|
577
|
-
if (!auth?.env) return null;
|
|
578
|
-
return {
|
|
579
|
-
env: auth.env,
|
|
580
|
-
authType: auth.type || "bearer",
|
|
581
|
-
present: !!process.env[auth.env]
|
|
582
|
-
};
|
|
148
|
+
Options:
|
|
149
|
+
--filter <text> Fuzzy-filter routes by resource name
|
|
150
|
+
--method <method> Filter by HTTP method (get, post, etc.)
|
|
151
|
+
|
|
152
|
+
For package-based extensions, spawns the extension's MCP server.
|
|
153
|
+
For spec-based extensions, serves routes as MCP tools.`);
|
|
154
|
+
process.exit(name ? 0 : 1);
|
|
583
155
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const args = (iface) => iface === "mcp" ? " <tool> [args]" : iface === "graphql" ? " <query> [flags]" : " <method> <resource> [id] [flags]";
|
|
590
|
-
return ordered.map((iface) => `godmode ${this.apiName} ${iface}${args(iface)}`);
|
|
591
|
-
}
|
|
592
|
-
const nav = this.getNav();
|
|
593
|
-
if (!nav) return [];
|
|
594
|
-
const paramHint = getParamName(nav.node);
|
|
595
|
-
const idRef = paramHint ? ` [${paramHint}]` : "";
|
|
596
|
-
const methodSlot = this.ifaceType === "api" ? "<method> " : "";
|
|
597
|
-
return [`godmode ${this.apiName} ${this.ifaceType} ${methodSlot}${nav.fullPath.join(" ")}${idRef} [flags]`];
|
|
156
|
+
let filter;
|
|
157
|
+
let method;
|
|
158
|
+
for (let i = 1; i < args.length; i++) {
|
|
159
|
+
if (args[i] === "--filter" && args[i + 1]) filter = args[++i];
|
|
160
|
+
else if (args[i] === "--method" && args[i + 1]) method = args[++i];
|
|
598
161
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const order = ["get", "post", "put", "patch", "delete", "head"];
|
|
615
|
-
const methodsHere = order.filter((m) => methodsPresent.has(m));
|
|
616
|
-
if (methodsHere.length) {
|
|
617
|
-
sections.push({
|
|
618
|
-
title: "Methods:",
|
|
619
|
-
rows: methodsHere.map((m) => [METHOD_LABEL[m] || m.toUpperCase(), METHOD_DESC[m]])
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
} else {
|
|
624
|
-
const mcpTools = this.manifest.config._mcpTools;
|
|
625
|
-
const mcpTool = mcpTools?.find((t) => t.name === this.path[this.path.length - 1]);
|
|
626
|
-
if (mcpTool?.inputSchema?.properties) {
|
|
627
|
-
const props = mcpTool.inputSchema.properties;
|
|
628
|
-
const required = new Set(mcpTool.inputSchema.required || []);
|
|
629
|
-
const rows = [];
|
|
630
|
-
for (const [name, schema] of Object.entries(props)) {
|
|
631
|
-
const req = required.has(name) ? `${RED}[REQUIRED]${RESET}` : "";
|
|
632
|
-
const desc = schema.description ? `${ITALIC}${schema.description}${RESET}` : "";
|
|
633
|
-
rows.push([name, req, desc]);
|
|
634
|
-
}
|
|
635
|
-
sections.push({ title: "Parameters:", rows });
|
|
636
|
-
} else {
|
|
637
|
-
const order = ["get", "post", "put", "patch", "delete"];
|
|
638
|
-
const pc = nav.node.children.find((c) => c.isParam);
|
|
639
|
-
const items = [];
|
|
640
|
-
for (const ep of nav.node.methods) items.push({ method: ep.method, summary: ep.summary, needsId: false });
|
|
641
|
-
if (pc) for (const ep of pc.methods) items.push({ method: ep.method, summary: ep.summary, needsId: true });
|
|
642
|
-
if (items.length) {
|
|
643
|
-
const rows = [];
|
|
644
|
-
for (const m of order) {
|
|
645
|
-
for (const r of items.filter((r2) => r2.method === m)) {
|
|
646
|
-
const label = METHOD_LABEL[m] || m.toUpperCase();
|
|
647
|
-
const idArg = r.needsId ? `<${pc.name}>` : "";
|
|
648
|
-
const desc = r.summary ? `${ITALIC}${r.summary}${RESET}` : "";
|
|
649
|
-
rows.push([label, idArg, desc]);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
sections.push({ title: "Methods:", rows });
|
|
653
|
-
}
|
|
654
|
-
}
|
|
162
|
+
const pkgName = name.startsWith("@") ? name : `@godmode-cli/${name}`;
|
|
163
|
+
const pkgDir = resolve2(rt.godmodeHome, "node_modules", pkgName);
|
|
164
|
+
const mcpConfigPath = resolve2(pkgDir, ".mcp.json");
|
|
165
|
+
try {
|
|
166
|
+
const mcpConfig = JSON.parse(await readFile(mcpConfigPath, "utf-8"));
|
|
167
|
+
const serverKey = Object.keys(mcpConfig.mcpServers)[0];
|
|
168
|
+
const server = mcpConfig.mcpServers[serverKey];
|
|
169
|
+
if (server.type === "stdio" && server.command) {
|
|
170
|
+
const child = spawn(server.command, server.args || [], {
|
|
171
|
+
stdio: "inherit",
|
|
172
|
+
cwd: pkgDir,
|
|
173
|
+
env: { ...process.env, ...server.env }
|
|
174
|
+
});
|
|
175
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
176
|
+
return;
|
|
655
177
|
}
|
|
656
|
-
|
|
657
|
-
const childNames = [...new Set(children.map((c) => c.name))];
|
|
658
|
-
const resourceSection = buildResourceSection(
|
|
659
|
-
children,
|
|
660
|
-
childNames,
|
|
661
|
-
this.manifest,
|
|
662
|
-
this.opts.filter,
|
|
663
|
-
this.opts.methodFilter,
|
|
664
|
-
this.opts.all
|
|
665
|
-
);
|
|
666
|
-
if (resourceSection) sections.push(resourceSection.section);
|
|
667
|
-
sections.push({ title: "Options:", rows: INTERFACE_OPTION_ROWS });
|
|
668
|
-
return sections;
|
|
669
|
-
}
|
|
670
|
-
/** Drilled-in view also shows param status lines (ANSI-only). */
|
|
671
|
-
render() {
|
|
672
|
-
super.render();
|
|
178
|
+
} catch {
|
|
673
179
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
function showExtensionOverview(multi) {
|
|
679
|
-
new ExtensionOverview(multi).render();
|
|
680
|
-
}
|
|
681
|
-
function showExtensionVersion(multi) {
|
|
682
|
-
new ExtensionVersionPage(multi).render();
|
|
683
|
-
}
|
|
684
|
-
function showApiHelp(manifest, apiName, path, filter, methodFilter, all, multi) {
|
|
685
|
-
new InterfaceHelp(manifest, apiName, path, { multi, filter, methodFilter, all }).render();
|
|
180
|
+
const manifest = await rt.loadManifest(name);
|
|
181
|
+
const { serveMcp } = await import("./server-L3L4TKK3.js");
|
|
182
|
+
await serveMcp(manifest, { filter, method, checkPermission: rt.checkPermission });
|
|
686
183
|
}
|
|
687
|
-
async function showVersion() {
|
|
688
|
-
const { readFile: readFile2 } = await import("fs/promises");
|
|
689
|
-
const { resolve: resolve6, dirname } = await import("path");
|
|
690
|
-
const { fileURLToPath } = await import("url");
|
|
691
|
-
const root = resolve6(dirname(fileURLToPath(import.meta.url)), "..");
|
|
692
|
-
const pkg = JSON.parse(await readFile2(resolve6(root, "package.json"), "utf-8"));
|
|
693
|
-
const license = await readFile2(resolve6(root, "LICENSE"), "utf-8");
|
|
694
|
-
const copyright = license.match(/^Copyright.*$/m)?.[0] ?? "";
|
|
695
|
-
console.log(`godmode ${pkg.version}
|
|
696
184
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
return methods.has(mf);
|
|
713
|
-
}) : childNames;
|
|
714
|
-
const filtered = filter ? fuzzysort.go(filter, candidates).map((r) => r.target) : candidates;
|
|
715
|
-
const title = filter || mf ? `Resources${filter ? ` matching "${filter}"` : ""}${mf ? ` with ${mf.toUpperCase()}` : ""}:` : "Resources:";
|
|
716
|
-
if (!filtered.length) return { section: { title, rows: [[" No matches.", ""]] } };
|
|
717
|
-
const limit = filter || mf || all ? filtered.length : 5;
|
|
718
|
-
const shown = filtered.slice(0, limit);
|
|
719
|
-
const more = filtered.length - shown.length;
|
|
720
|
-
const rows = [];
|
|
721
|
-
for (const n of shown) {
|
|
722
|
-
const child = children.find((c) => c.name === n);
|
|
723
|
-
const labels = child ? methodLabels(child) : "";
|
|
724
|
-
const subCount = child ? getChildren(child).filter((c) => !c.isParam).length : 0;
|
|
725
|
-
const sub = subCount ? `(${subCount} sub)` : "";
|
|
726
|
-
const rawDesc = manifest.resourceDescriptions[n] || "";
|
|
727
|
-
const desc = rawDesc ? `${ITALIC}${rawDesc}${RESET}` : "";
|
|
728
|
-
const parts = [labels, sub, desc].filter(Boolean);
|
|
729
|
-
rows.push([n, parts.join(" ")]);
|
|
730
|
-
}
|
|
731
|
-
if (more) {
|
|
732
|
-
const verb = mf ? ` ${mf.toUpperCase()}` : "";
|
|
733
|
-
const hintText = `${more} more${verb} resources. Use options to display more`;
|
|
734
|
-
const dots = USE_COLOR ? `${DIM}...${RESET}` : "...";
|
|
735
|
-
const body = USE_COLOR ? `${DIM}${hintText}${RESET}` : hintText;
|
|
736
|
-
rows.push([dots, body]);
|
|
185
|
+
// src/interfaces.ts
|
|
186
|
+
var Interface = class {
|
|
187
|
+
constructor(ctx) {
|
|
188
|
+
this.ctx = ctx;
|
|
189
|
+
}
|
|
190
|
+
ctx;
|
|
191
|
+
/** `godmode <ext> <iface>` with no resource — by default shows help.
|
|
192
|
+
* MCP overrides to serve as an MCP server over stdio. */
|
|
193
|
+
async handleEmpty() {
|
|
194
|
+
this.showHelp();
|
|
195
|
+
}
|
|
196
|
+
/** Interface-specific arg validation. Return an error string to bail, or
|
|
197
|
+
* null to proceed. Called after segments have been provided. */
|
|
198
|
+
validate() {
|
|
199
|
+
return null;
|
|
737
200
|
}
|
|
738
|
-
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
import { spawnSync } from "child_process";
|
|
743
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
744
|
-
import { resolve as resolve3 } from "path";
|
|
745
|
-
import { closeSync, existsSync as existsSync2, openSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
746
|
-
import { resolve as resolve22 } from "path";
|
|
747
|
-
import { randomUUID } from "crypto";
|
|
748
|
-
import { mkdirSync, readFileSync as readFileSync4, writeFileSync, existsSync, readdirSync } from "fs";
|
|
749
|
-
import { homedir } from "os";
|
|
750
|
-
import { resolve as resolve4 } from "path";
|
|
751
|
-
var DEFAULT_HARNESS_ORDER = ["claude", "codex", "gemini", "pi"];
|
|
752
|
-
var HARNESSES = {
|
|
753
|
-
claude: {
|
|
754
|
-
id: "claude",
|
|
755
|
-
displayName: "Claude Code",
|
|
756
|
-
command: "claude",
|
|
757
|
-
modelFlags: ["--model"],
|
|
758
|
-
effortFlags: ["--effort"],
|
|
759
|
-
helpArgs: ["--help"],
|
|
760
|
-
promptHints: ["[prompt]", "your prompt"],
|
|
761
|
-
buildTurnArgs: ({ prompt, model, effort, passthroughArgs, resumeToken }) => [
|
|
762
|
-
"-p",
|
|
763
|
-
"--verbose",
|
|
764
|
-
"--output-format",
|
|
765
|
-
"stream-json",
|
|
766
|
-
...resumeToken ? ["--resume", resumeToken] : [],
|
|
767
|
-
...model ? ["--model", model] : [],
|
|
768
|
-
...effort ? ["--effort", effort] : [],
|
|
769
|
-
prompt,
|
|
770
|
-
...passthroughArgs
|
|
771
|
-
]
|
|
772
|
-
},
|
|
773
|
-
codex: {
|
|
774
|
-
id: "codex",
|
|
775
|
-
displayName: "Codex CLI",
|
|
776
|
-
command: "codex",
|
|
777
|
-
modelFlags: ["-m", "--model"],
|
|
778
|
-
effortFlags: [],
|
|
779
|
-
helpArgs: ["--help"],
|
|
780
|
-
promptHints: ["[prompt]", "optional user prompt"],
|
|
781
|
-
buildTurnArgs: ({ prompt, model, passthroughArgs }) => [
|
|
782
|
-
"exec",
|
|
783
|
-
"--json",
|
|
784
|
-
...model ? ["-m", model] : [],
|
|
785
|
-
prompt,
|
|
786
|
-
...passthroughArgs
|
|
787
|
-
]
|
|
788
|
-
},
|
|
789
|
-
gemini: {
|
|
790
|
-
id: "gemini",
|
|
791
|
-
displayName: "Gemini CLI",
|
|
792
|
-
command: "gemini",
|
|
793
|
-
modelFlags: ["-m", "--model"],
|
|
794
|
-
effortFlags: [],
|
|
795
|
-
helpArgs: ["--help"],
|
|
796
|
-
promptHints: ["query", "initial prompt"],
|
|
797
|
-
buildTurnArgs: ({ prompt, model, passthroughArgs, resumeToken }) => [
|
|
798
|
-
...resumeToken ? ["--resume", resumeToken] : [],
|
|
799
|
-
...model ? ["-m", model] : [],
|
|
800
|
-
"-p",
|
|
801
|
-
prompt,
|
|
802
|
-
"-o",
|
|
803
|
-
"stream-json",
|
|
804
|
-
...passthroughArgs
|
|
805
|
-
]
|
|
806
|
-
},
|
|
807
|
-
pi: {
|
|
808
|
-
id: "pi",
|
|
809
|
-
displayName: "Pi",
|
|
810
|
-
command: "pi",
|
|
811
|
-
modelFlags: ["--model"],
|
|
812
|
-
effortFlags: ["--thinking"],
|
|
813
|
-
helpArgs: ["--help"],
|
|
814
|
-
promptHints: ["[messages...]", "initial prompt"],
|
|
815
|
-
buildTurnArgs: ({ prompt, model, effort, passthroughArgs, resumeToken, sessionDir }) => [
|
|
816
|
-
"--print",
|
|
817
|
-
"--mode",
|
|
818
|
-
"json",
|
|
819
|
-
...sessionDir ? ["--session-dir", sessionDir] : [],
|
|
820
|
-
...resumeToken ? ["--continue"] : [],
|
|
821
|
-
...model ? ["--model", model] : [],
|
|
822
|
-
...effort ? ["--thinking", effort] : [],
|
|
823
|
-
prompt,
|
|
824
|
-
...passthroughArgs
|
|
825
|
-
]
|
|
201
|
+
showHelp() {
|
|
202
|
+
const { manifest, extensionName, parsed, multi } = this.ctx;
|
|
203
|
+
const implicitMethodFilter = parsed.methodFilter || (parsed.explicitMethod ? parsed.method : void 0);
|
|
204
|
+
showApiHelp(manifest, extensionName, parsed.segments, parsed.filter, implicitMethodFilter, parsed.all, multi);
|
|
826
205
|
}
|
|
827
206
|
};
|
|
828
|
-
function
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
throw new Error(requested ? `Harness "${requested}" not found.` : "No supported coding harness found.");
|
|
836
|
-
}
|
|
837
|
-
function event(ts, id, turn, partial) {
|
|
838
|
-
return { ts, id, turn, ...partial };
|
|
839
|
-
}
|
|
840
|
-
function normalizeTurn(harness, id, turn, stdout, stderr, ts) {
|
|
841
|
-
const lines = stdout.split(/\r?\n/);
|
|
842
|
-
if (harness === "claude") return parseClaude(lines, id, turn, stderr, ts);
|
|
843
|
-
if (harness === "gemini") return parseGemini(lines, id, turn, stderr, ts);
|
|
844
|
-
if (harness === "pi") return parsePi(lines, id, turn, stderr, ts);
|
|
845
|
-
return {
|
|
846
|
-
sessionId: null,
|
|
847
|
-
assistantText: stdout.trim(),
|
|
848
|
-
events: [
|
|
849
|
-
event(ts, id, turn, { type: "turn.started" }),
|
|
850
|
-
event(ts, id, turn, { type: "assistant.completed", text: stdout.trim() }),
|
|
851
|
-
event(ts, id, turn, { type: "turn.completed", status: "completed" })
|
|
852
|
-
]
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
function parseClaude(lines, id, turn, stderr, ts) {
|
|
856
|
-
const events = [];
|
|
857
|
-
let sessionId = null;
|
|
858
|
-
let assistantText = "";
|
|
859
|
-
let usage;
|
|
860
|
-
let timing;
|
|
861
|
-
for (const line of lines) {
|
|
862
|
-
if (!line.trim()) continue;
|
|
863
|
-
let parsed;
|
|
864
|
-
try {
|
|
865
|
-
parsed = JSON.parse(line);
|
|
866
|
-
} catch {
|
|
867
|
-
continue;
|
|
868
|
-
}
|
|
869
|
-
if (parsed.type === "system" && parsed.subtype === "init") {
|
|
870
|
-
sessionId = parsed.session_id ?? sessionId;
|
|
871
|
-
events.push(event(ts, id, turn, { type: "turn.started" }));
|
|
872
|
-
events.push(event(ts, id, turn, { type: "state.changed", state: "starting" }));
|
|
873
|
-
}
|
|
874
|
-
if (parsed.type === "assistant") {
|
|
875
|
-
const text = (parsed.message?.content ?? []).filter((item) => item.type === "text").map((item) => item.text).join("");
|
|
876
|
-
if (text) assistantText = text;
|
|
877
|
-
if (text) events.push(event(ts, id, turn, { type: "assistant.completed", text }));
|
|
878
|
-
}
|
|
879
|
-
if (parsed.type === "result") {
|
|
880
|
-
sessionId = parsed.session_id ?? sessionId;
|
|
881
|
-
usage = {
|
|
882
|
-
inputTokens: parsed.usage?.input_tokens,
|
|
883
|
-
outputTokens: parsed.usage?.output_tokens,
|
|
884
|
-
cachedTokens: parsed.usage?.cache_read_input_tokens
|
|
885
|
-
};
|
|
886
|
-
timing = { durationMs: parsed.duration_ms };
|
|
887
|
-
events.push(event(ts, id, turn, { type: "turn.completed", status: parsed.is_error ? "failed" : "completed" }));
|
|
888
|
-
}
|
|
207
|
+
async function executeRoute(ctx) {
|
|
208
|
+
const { manifest, extensionName, iface, parsed } = ctx;
|
|
209
|
+
const query = parsed.query;
|
|
210
|
+
const hasBody = Object.keys(parsed.body).length > 0;
|
|
211
|
+
let body = hasBody ? JSON.stringify(parsed.body) : void 0;
|
|
212
|
+
if (!body && ["post", "put", "patch"].includes(parsed.method)) {
|
|
213
|
+
body = await readStdin();
|
|
889
214
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
if (parsed.type === "init") {
|
|
908
|
-
sessionId = parsed.session_id ?? sessionId;
|
|
909
|
-
events.push(event(ts, id, turn, { type: "turn.started" }));
|
|
910
|
-
events.push(event(ts, id, turn, { type: "state.changed", state: "starting" }));
|
|
911
|
-
}
|
|
912
|
-
if (parsed.type === "message" && parsed.role === "assistant" && parsed.delta === true && typeof parsed.content === "string") {
|
|
913
|
-
assistantText += parsed.content;
|
|
914
|
-
events.push(event(ts, id, turn, { type: "assistant.delta", text: parsed.content }));
|
|
915
|
-
}
|
|
916
|
-
if (parsed.type === "result") {
|
|
917
|
-
usage = {
|
|
918
|
-
inputTokens: parsed.stats?.input_tokens,
|
|
919
|
-
outputTokens: parsed.stats?.output_tokens,
|
|
920
|
-
cachedTokens: parsed.stats?.cached,
|
|
921
|
-
totalTokens: parsed.stats?.total_tokens
|
|
922
|
-
};
|
|
923
|
-
timing = { durationMs: parsed.stats?.duration_ms };
|
|
924
|
-
events.push(event(ts, id, turn, { type: "assistant.completed", text: assistantText }));
|
|
925
|
-
events.push(event(ts, id, turn, { type: "turn.completed", status: parsed.status === "success" ? "completed" : "failed" }));
|
|
215
|
+
if (parsed.segments[0]?.startsWith("/")) {
|
|
216
|
+
const rawPath = parsed.segments[0];
|
|
217
|
+
const route = manifest.routes.find((r) => r.method === parsed.method && r.path === rawPath);
|
|
218
|
+
const resource = route ? resourceFromSegments(route.segments) : resourceFromRawPath(rawPath);
|
|
219
|
+
const check2 = checkPermission({
|
|
220
|
+
extension: extensionName,
|
|
221
|
+
resource,
|
|
222
|
+
method: parsed.method
|
|
223
|
+
});
|
|
224
|
+
if (!check2.allowed) {
|
|
225
|
+
process.stderr.write(`Blocked: ${check2.reason}
|
|
226
|
+
`);
|
|
227
|
+
process.stderr.write(`Suggested allow rule:
|
|
228
|
+
${suggestedAllowRule({ extension: extensionName, resource, method: parsed.method })}
|
|
229
|
+
`);
|
|
230
|
+
process.exit(EXIT_CODES.permissionDenied);
|
|
926
231
|
}
|
|
232
|
+
const syntheticRoute = {
|
|
233
|
+
path: rawPath,
|
|
234
|
+
method: parsed.method,
|
|
235
|
+
summary: "",
|
|
236
|
+
version: "",
|
|
237
|
+
segments: []
|
|
238
|
+
};
|
|
239
|
+
await execute(manifest, { route: syntheticRoute, params: {} }, {
|
|
240
|
+
headers: parsed.headers,
|
|
241
|
+
query,
|
|
242
|
+
body,
|
|
243
|
+
debug: parsed.debug,
|
|
244
|
+
dryRun: parsed.dryRun
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
927
247
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const events = [];
|
|
933
|
-
let sessionId = null;
|
|
934
|
-
let assistantText = "";
|
|
935
|
-
for (const line of lines) {
|
|
936
|
-
if (!line.trim()) continue;
|
|
937
|
-
let parsed;
|
|
938
|
-
try {
|
|
939
|
-
parsed = JSON.parse(line);
|
|
940
|
-
} catch {
|
|
941
|
-
continue;
|
|
942
|
-
}
|
|
943
|
-
if (parsed.type === "session") {
|
|
944
|
-
sessionId = parsed.id ?? sessionId;
|
|
945
|
-
events.push(event(ts, id, turn, { type: "turn.started" }));
|
|
946
|
-
events.push(event(ts, id, turn, { type: "state.changed", state: "starting" }));
|
|
947
|
-
}
|
|
948
|
-
if (parsed.type === "turn_start") events.push(event(ts, id, turn, { type: "state.changed", state: "running" }));
|
|
949
|
-
if (parsed.type === "message_update" && parsed.assistantMessageEvent?.type === "thinking_start") {
|
|
950
|
-
events.push(event(ts, id, turn, { type: "state.changed", state: "thinking" }));
|
|
951
|
-
}
|
|
952
|
-
if (parsed.type === "message_update" && parsed.assistantMessageEvent?.type === "text_delta") {
|
|
953
|
-
const delta = parsed.assistantMessageEvent.delta ?? "";
|
|
954
|
-
assistantText += delta;
|
|
955
|
-
if (delta) events.push(event(ts, id, turn, { type: "assistant.delta", text: delta }));
|
|
956
|
-
}
|
|
957
|
-
if (parsed.type === "turn_end") {
|
|
958
|
-
const text = (parsed.message?.content ?? []).filter((item) => item.type === "text").map((item) => item.text).join("");
|
|
959
|
-
if (text) assistantText = text;
|
|
960
|
-
events.push(event(ts, id, turn, { type: "assistant.completed", text: assistantText }));
|
|
961
|
-
events.push(event(ts, id, turn, { type: "turn.completed", status: "completed" }));
|
|
962
|
-
}
|
|
248
|
+
const match = matchRoute(manifest, parsed.segments, parsed.method);
|
|
249
|
+
if (!match) {
|
|
250
|
+
reportNoMatch(ctx);
|
|
251
|
+
process.exit(EXIT_CODES.notFound);
|
|
963
252
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
function timestamp() {
|
|
972
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
973
|
-
}
|
|
974
|
-
function ensureDirs() {
|
|
975
|
-
mkdirSync(RUNS_DIR, { recursive: true });
|
|
976
|
-
}
|
|
977
|
-
function shellEscape(value) {
|
|
978
|
-
return `'${value.replace(/'/g, `"'"'`)}'`;
|
|
979
|
-
}
|
|
980
|
-
function writeJson(path, value) {
|
|
981
|
-
mkdirSync(resolve4(path, ".."), { recursive: true });
|
|
982
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
253
|
+
const check = checkPermission({
|
|
254
|
+
extension: extensionName,
|
|
255
|
+
resource: resourceFromSegments(match.route.segments),
|
|
256
|
+
method: parsed.method
|
|
257
|
+
});
|
|
258
|
+
if (!check.allowed) {
|
|
259
|
+
process.stderr.write(`Blocked: ${check.reason}
|
|
983
260
|
`);
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
} catch {
|
|
989
|
-
return null;
|
|
261
|
+
process.stderr.write(`Suggested allow rule:
|
|
262
|
+
${suggestedAllowRule({ extension: extensionName, resource: resourceFromSegments(match.route.segments), method: parsed.method })}
|
|
263
|
+
`);
|
|
264
|
+
process.exit(EXIT_CODES.permissionDenied);
|
|
990
265
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const projectSettings = readSettingsFile(resolve4(cwd, ".godmode", "settings.json"));
|
|
998
|
-
return {
|
|
999
|
-
...globalSettings,
|
|
1000
|
-
...projectSettings,
|
|
1001
|
-
plugins: {
|
|
1002
|
-
...globalSettings.plugins ?? {},
|
|
1003
|
-
...projectSettings.plugins ?? {},
|
|
1004
|
-
"coding-agents": {
|
|
1005
|
-
...globalSettings.plugins?.["coding-agents"] ?? {},
|
|
1006
|
-
...projectSettings.plugins?.["coding-agents"] ?? {}
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
function loadActiveRuns() {
|
|
1012
|
-
return readJson(ACTIVE_RUNS_PATH) ?? {};
|
|
1013
|
-
}
|
|
1014
|
-
function saveActiveRun(cwd, id) {
|
|
1015
|
-
const current = loadActiveRuns();
|
|
1016
|
-
current[cwd] = id;
|
|
1017
|
-
writeJson(ACTIVE_RUNS_PATH, current);
|
|
1018
|
-
}
|
|
1019
|
-
function activeRunIdForCwd(cwd) {
|
|
1020
|
-
return loadActiveRuns()[cwd] ?? null;
|
|
1021
|
-
}
|
|
1022
|
-
function runDir(id) {
|
|
1023
|
-
return resolve4(RUNS_DIR, id);
|
|
1024
|
-
}
|
|
1025
|
-
function runPath(id) {
|
|
1026
|
-
return resolve4(runDir(id), "run.json");
|
|
1027
|
-
}
|
|
1028
|
-
function turnDir(id, turn) {
|
|
1029
|
-
return resolve4(runDir(id), "turns", String(turn).padStart(4, "0"));
|
|
1030
|
-
}
|
|
1031
|
-
function loadRun(id) {
|
|
1032
|
-
const run = readJson(runPath(id));
|
|
1033
|
-
if (!run) throw new Error(`Run not found: ${id}`);
|
|
1034
|
-
return run;
|
|
1035
|
-
}
|
|
1036
|
-
function saveRun(run) {
|
|
1037
|
-
run.updatedAt = timestamp();
|
|
1038
|
-
writeJson(runPath(run.id), run);
|
|
1039
|
-
}
|
|
1040
|
-
function listRuns() {
|
|
1041
|
-
ensureDirs();
|
|
1042
|
-
return readdirSync(RUNS_DIR, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readJson(runPath(entry.name))).filter((value) => !!value).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1043
|
-
}
|
|
1044
|
-
function createRun(cwd, harness, model, effort) {
|
|
1045
|
-
ensureDirs();
|
|
1046
|
-
const id = `run-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
1047
|
-
const run = {
|
|
1048
|
-
id,
|
|
1049
|
-
cwd,
|
|
1050
|
-
harness: harness.id,
|
|
1051
|
-
harnessName: harness.displayName,
|
|
1052
|
-
sessions: {
|
|
1053
|
-
zmx: `godmode-agent-${harness.id}-${id.replace(/^run-/, "")}`
|
|
1054
|
-
},
|
|
1055
|
-
model: model ?? null,
|
|
1056
|
-
effort: effort ?? null,
|
|
1057
|
-
status: "idle",
|
|
1058
|
-
createdAt: timestamp(),
|
|
1059
|
-
updatedAt: timestamp(),
|
|
1060
|
-
lastTurn: 0
|
|
1061
|
-
};
|
|
1062
|
-
mkdirSync(runDir(id), { recursive: true });
|
|
1063
|
-
mkdirSync(resolve4(runDir(id), "turns"), { recursive: true });
|
|
1064
|
-
saveRun(run);
|
|
1065
|
-
saveActiveRun(cwd, id);
|
|
1066
|
-
return run;
|
|
1067
|
-
}
|
|
1068
|
-
function buildShellCommand(harness, run, turn, prompt, passthroughArgs) {
|
|
1069
|
-
const dir = turnDir(run.id, turn);
|
|
1070
|
-
mkdirSync(dir, { recursive: true });
|
|
1071
|
-
const stdoutPath = resolve4(dir, "stdout.log");
|
|
1072
|
-
const stderrPath = resolve4(dir, "stderr.log");
|
|
1073
|
-
const exitCodePath = resolve4(dir, "exit-code.txt");
|
|
1074
|
-
const sessionDir = resolve4(runDir(run.id), "pi-session");
|
|
1075
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
1076
|
-
const args = harness.buildTurnArgs({
|
|
1077
|
-
prompt,
|
|
1078
|
-
model: run.model ?? void 0,
|
|
1079
|
-
effort: run.effort ?? void 0,
|
|
1080
|
-
passthroughArgs,
|
|
1081
|
-
resumeToken: run.sessions[run.harness] ?? void 0,
|
|
1082
|
-
sessionDir
|
|
266
|
+
await execute(manifest, match, {
|
|
267
|
+
headers: parsed.headers,
|
|
268
|
+
query,
|
|
269
|
+
body,
|
|
270
|
+
debug: parsed.debug,
|
|
271
|
+
dryRun: parsed.dryRun
|
|
1083
272
|
});
|
|
1084
|
-
const command = `${[harness.command, ...args].map(shellEscape).join(" ")} > ${shellEscape(stdoutPath)} 2> ${shellEscape(stderrPath)}`;
|
|
1085
|
-
const shell = `${command}; code=$?; printf '%s' "$code" > ${shellEscape(exitCodePath)}; exit "$code"`;
|
|
1086
|
-
return { shell, stdoutPath, stderrPath, exitCodePath };
|
|
1087
|
-
}
|
|
1088
|
-
function waitForFile(path, timeoutMs = 3e5) {
|
|
1089
|
-
const started = Date.now();
|
|
1090
|
-
while (!existsSync(path)) {
|
|
1091
|
-
if (Date.now() - started > timeoutMs) throw new Error(`Timed out waiting for ${path}`);
|
|
1092
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
function latestTurn(run) {
|
|
1096
|
-
if (run.lastTurn < 1) throw new Error(`Run ${run.id} has no turns yet.`);
|
|
1097
|
-
const turn = readJson(resolve4(turnDir(run.id, run.lastTurn), "normalized.json"));
|
|
1098
|
-
if (!turn) throw new Error(`Turn ${run.lastTurn} not found for ${run.id}`);
|
|
1099
|
-
return turn;
|
|
1100
273
|
}
|
|
1101
|
-
function
|
|
1102
|
-
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (outputMode === "assistant-text") return turn.assistant.text;
|
|
1109
|
-
if (outputMode === "raw") {
|
|
1110
|
-
const stdout = existsSync2(turn.paths.stdout) ? readFileSync2(turn.paths.stdout, "utf-8") : "";
|
|
1111
|
-
const stderr = existsSync2(turn.paths.stderr) ? readFileSync2(turn.paths.stderr, "utf-8") : "";
|
|
1112
|
-
return JSON.stringify({ stdout, stderr }, null, 2);
|
|
1113
|
-
}
|
|
1114
|
-
if (outputMode === "events") {
|
|
1115
|
-
return existsSync2(turn.paths.events) ? readFileSync2(turn.paths.events, "utf-8").trimEnd() : "";
|
|
1116
|
-
}
|
|
1117
|
-
return JSON.stringify(turn, null, 2);
|
|
1118
|
-
}
|
|
1119
|
-
function renderStatus(run) {
|
|
1120
|
-
return JSON.stringify(run, null, 2);
|
|
1121
|
-
}
|
|
1122
|
-
function writeEvents(path, events) {
|
|
1123
|
-
writeFileSyncSafe(path, `${events.map((event2) => JSON.stringify(event2)).join("\n")}
|
|
274
|
+
function reportNoMatch(ctx) {
|
|
275
|
+
const { manifest, extensionName, iface, parsed } = ctx;
|
|
276
|
+
const HTTP_VERBS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
277
|
+
const trailingVerb = parsed.segments.map((s) => s.toUpperCase()).find((s) => HTTP_VERBS.has(s));
|
|
278
|
+
if (trailingVerb) {
|
|
279
|
+
const rest = parsed.segments.filter((s) => s.toUpperCase() !== trailingVerb);
|
|
280
|
+
process.stderr.write(`No route matching: ${parsed.segments.join(" ")}
|
|
1124
281
|
`);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}
|
|
1129
|
-
function attachHelp() {
|
|
1130
|
-
return [
|
|
1131
|
-
"Usage: godmode agent attach run <id>",
|
|
1132
|
-
" or: godmode agent attach session <zmx-session-id>"
|
|
1133
|
-
].join("\n");
|
|
1134
|
-
}
|
|
1135
|
-
var AgentHelp = class extends HelpPage {
|
|
1136
|
-
usage() {
|
|
1137
|
-
return [
|
|
1138
|
-
"godmode agent start [--harness <name>] [--model <id>] <prompt>",
|
|
1139
|
-
"godmode agent send [--harness <name>] [--model <id>] <prompt>",
|
|
1140
|
-
"godmode agent attach run <id>",
|
|
1141
|
-
"godmode agent attach session <session-id>",
|
|
1142
|
-
"godmode agent output [id] [--json|--assistant-text|--events|--raw]",
|
|
1143
|
-
"godmode agent status [id]",
|
|
1144
|
-
"godmode agent list"
|
|
1145
|
-
];
|
|
1146
|
-
}
|
|
1147
|
-
sections() {
|
|
1148
|
-
return [
|
|
1149
|
-
{ title: "Options:", rows: [
|
|
1150
|
-
[" --harness <name>", "coding agent harness (claude, codex, gemini, etc.)"],
|
|
1151
|
-
[" --model <id>", "model identifier passed to the harness"],
|
|
1152
|
-
[" --effort <level>", "effort level passed to the harness"],
|
|
1153
|
-
[" --json", "output machine-readable events"],
|
|
1154
|
-
[" --follow", "stream output until the run completes"]
|
|
1155
|
-
] },
|
|
1156
|
-
{ title: "Shortcuts:", rows: [
|
|
1157
|
-
["godmode agent <prompt>", "send prompt to active run, or start a new run"]
|
|
1158
|
-
] }
|
|
1159
|
-
];
|
|
1160
|
-
}
|
|
1161
|
-
};
|
|
1162
|
-
function agentHelp() {
|
|
1163
|
-
const lines = [];
|
|
1164
|
-
const origLog = console.log;
|
|
1165
|
-
console.log = (...args) => {
|
|
1166
|
-
lines.push(args.join(" "));
|
|
1167
|
-
};
|
|
1168
|
-
try {
|
|
1169
|
-
new AgentHelp().render();
|
|
1170
|
-
} finally {
|
|
1171
|
-
console.log = origLog;
|
|
282
|
+
process.stderr.write(`Method goes first: try 'godmode ${extensionName} ${iface} ${trailingVerb} ${rest.join(" ")}'
|
|
283
|
+
`);
|
|
284
|
+
return;
|
|
1172
285
|
}
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
function followOutput(run, turn, outputMode, writer) {
|
|
1176
|
-
const dir = turnDir(run.id, turn);
|
|
1177
|
-
const stdoutPath = resolve22(dir, "stdout.log");
|
|
1178
|
-
const stderrPath = resolve22(dir, "stderr.log");
|
|
1179
|
-
const statePath = runPath(run.id);
|
|
1180
|
-
let assistant = "";
|
|
1181
|
-
const emit = (event2) => {
|
|
1182
|
-
if (outputMode === "events") writer.write(`${JSON.stringify(event2)}
|
|
286
|
+
process.stderr.write(`No ${parsed.method.toUpperCase()} route matching: ${parsed.segments.join(" ")}
|
|
1183
287
|
`);
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
};
|
|
1189
|
-
streamFile(stdoutPath, (line) => {
|
|
1190
|
-
const normalized = normalizeTurn(run.harness, run.id, turn, `${line}
|
|
1191
|
-
`, "", timestamp());
|
|
1192
|
-
for (const event2 of normalized.events) {
|
|
1193
|
-
if (event2.type === "assistant.delta" && event2.text) assistant += event2.text;
|
|
1194
|
-
emit(event2);
|
|
1195
|
-
}
|
|
1196
|
-
}, () => (readJson2(statePath)?.status ?? "completed") === "running");
|
|
1197
|
-
if (outputMode === "assistant-text") writer.write("\n");
|
|
1198
|
-
if (outputMode === "events" && existsSync2(stderrPath)) {
|
|
1199
|
-
for (const line of readFileSync2(stderrPath, "utf-8").split(/\r?\n/).filter(Boolean)) {
|
|
1200
|
-
writer.write(`${JSON.stringify({ ts: timestamp(), id: run.id, turn, type: "warning", message: line })}
|
|
288
|
+
for (const m of ["get", "post", "put", "patch", "delete"]) {
|
|
289
|
+
if (m === parsed.method) continue;
|
|
290
|
+
if (matchRoute(manifest, parsed.segments, m)) {
|
|
291
|
+
process.stderr.write(` try: godmode ${extensionName} api ${m.toUpperCase()} ${parsed.segments.join(" ")}
|
|
1201
292
|
`);
|
|
1202
293
|
}
|
|
1203
294
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
try {
|
|
1215
|
-
const bytes = buffer.length ? readSync(fd, buffer, 0, buffer.length, offset) : 0;
|
|
1216
|
-
offset += bytes;
|
|
1217
|
-
pending += buffer.toString("utf-8", 0, bytes);
|
|
1218
|
-
const lines = pending.split(/\r?\n/);
|
|
1219
|
-
pending = lines.pop() ?? "";
|
|
1220
|
-
for (const line of lines) if (line) onLine(line);
|
|
1221
|
-
} finally {
|
|
1222
|
-
closeSync(fd);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
if (!until()) {
|
|
1227
|
-
if (pending) onLine(pending);
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
function readJson2(path) {
|
|
1234
|
-
try {
|
|
1235
|
-
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
1236
|
-
} catch {
|
|
1237
|
-
return null;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
function defaultExecutor(command, args, stdio = "pipe") {
|
|
1241
|
-
const spawnStdio = stdio === "inherit" ? "inherit" : "pipe";
|
|
1242
|
-
const result = spawnSync(command, args, { encoding: "utf-8", stdio: spawnStdio });
|
|
1243
|
-
return {
|
|
1244
|
-
status: result.status,
|
|
1245
|
-
stdout: stdio === "pipe" && typeof result.stdout === "string" ? result.stdout : "",
|
|
1246
|
-
stderr: stdio === "pipe" && typeof result.stderr === "string" ? result.stderr : "",
|
|
1247
|
-
error: result.error ?? void 0
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
function parseOutputMode(args) {
|
|
1251
|
-
let outputMode = "json";
|
|
1252
|
-
let follow = false;
|
|
1253
|
-
const remaining = [];
|
|
1254
|
-
for (const arg of args) {
|
|
1255
|
-
if (arg === "--json") outputMode = "json";
|
|
1256
|
-
else if (arg === "--assistant-text") outputMode = "assistant-text";
|
|
1257
|
-
else if (arg === "--events") outputMode = "events";
|
|
1258
|
-
else if (arg === "--raw") outputMode = "raw";
|
|
1259
|
-
else if (arg === "--follow") follow = true;
|
|
1260
|
-
else remaining.push(arg);
|
|
1261
|
-
}
|
|
1262
|
-
return { outputMode, follow, remaining };
|
|
1263
|
-
}
|
|
1264
|
-
function parseStartSendArgs(action, argv) {
|
|
1265
|
-
const { outputMode, remaining } = parseOutputMode(argv);
|
|
1266
|
-
let harness;
|
|
1267
|
-
let model;
|
|
1268
|
-
let effort;
|
|
1269
|
-
let passthroughArgs = [];
|
|
1270
|
-
const promptParts = [];
|
|
1271
|
-
for (let i = 0; i < remaining.length; i++) {
|
|
1272
|
-
const arg = remaining[i];
|
|
1273
|
-
if (arg === "--") {
|
|
1274
|
-
passthroughArgs = remaining.slice(i + 1);
|
|
1275
|
-
break;
|
|
1276
|
-
}
|
|
1277
|
-
if (arg === "--harness") {
|
|
1278
|
-
const value = remaining[++i];
|
|
1279
|
-
if (!value || !(value in HARNESSES)) throw new Error("Missing or invalid value for --harness");
|
|
1280
|
-
harness = value;
|
|
1281
|
-
continue;
|
|
1282
|
-
}
|
|
1283
|
-
if (arg === "--model") {
|
|
1284
|
-
model = remaining[++i];
|
|
1285
|
-
if (!model) throw new Error("Missing value for --model");
|
|
1286
|
-
continue;
|
|
1287
|
-
}
|
|
1288
|
-
if (arg === "--effort") {
|
|
1289
|
-
effort = remaining[++i];
|
|
1290
|
-
if (!effort) throw new Error("Missing value for --effort");
|
|
1291
|
-
continue;
|
|
295
|
+
const similar = suggestRoutes(manifest, parsed.segments).slice(0, 5);
|
|
296
|
+
if (similar.length) {
|
|
297
|
+
process.stderr.write("\nSimilar:\n");
|
|
298
|
+
const seen = /* @__PURE__ */ new Set();
|
|
299
|
+
for (const r of similar) {
|
|
300
|
+
const p = r.segments.map((s) => s.isParam ? `{${s.value}}` : s.value).join(" ");
|
|
301
|
+
if (seen.has(p)) continue;
|
|
302
|
+
seen.add(p);
|
|
303
|
+
process.stderr.write(` ${p}
|
|
304
|
+
`);
|
|
1292
305
|
}
|
|
1293
|
-
if (arg === "--help" || arg === "-h") throw new Error(agentHelp());
|
|
1294
|
-
if (arg.startsWith("-")) throw new Error(`Unknown flag: ${arg}. Use -- to pass flags through to the native harness.`);
|
|
1295
|
-
promptParts.push(arg);
|
|
1296
306
|
}
|
|
1297
|
-
return { action, prompt: promptParts.join(" ").trim(), harness, model, effort, passthroughArgs, outputMode };
|
|
1298
|
-
}
|
|
1299
|
-
function parseOutputArgs(argv) {
|
|
1300
|
-
const { outputMode, follow, remaining } = parseOutputMode(argv);
|
|
1301
|
-
return { outputMode, follow, id: remaining.find((arg) => !arg.startsWith("-")) };
|
|
1302
307
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
return "failed";
|
|
1309
|
-
}
|
|
1310
|
-
function resolveRunAndHarness(parsed, executor, cwd, options) {
|
|
1311
|
-
const settings = options.settings ?? loadSettings(cwd);
|
|
1312
|
-
const plugin = settings.plugins?.["coding-agents"];
|
|
1313
|
-
const requestedHarnessId = parsed.harness ?? plugin?.harness;
|
|
1314
|
-
let model = parsed.model ?? plugin?.model;
|
|
1315
|
-
let effort = parsed.effort ?? plugin?.effort;
|
|
1316
|
-
let run;
|
|
1317
|
-
let harness;
|
|
1318
|
-
if (parsed.action === "start") {
|
|
1319
|
-
harness = detectHarness(executor, requestedHarnessId);
|
|
1320
|
-
if (effort && harness.effortFlags.length === 0) throw new Error(`Harness "${harness.id}" does not support --effort.`);
|
|
1321
|
-
run = createRun(cwd, harness, model, effort);
|
|
1322
|
-
} else {
|
|
1323
|
-
const active = activeRunIdForCwd(cwd);
|
|
1324
|
-
if (active) {
|
|
1325
|
-
run = loadRun(active);
|
|
1326
|
-
if (parsed.harness && parsed.harness !== run.harness) throw new Error(`Active run uses harness ${run.harness}; start a new run for ${parsed.harness}.`);
|
|
1327
|
-
harness = detectHarness(executor, run.harness);
|
|
1328
|
-
model = parsed.model ?? run.model ?? plugin?.model ?? void 0;
|
|
1329
|
-
effort = parsed.effort ?? run.effort ?? plugin?.effort ?? void 0;
|
|
1330
|
-
if (effort && harness.effortFlags.length === 0) throw new Error(`Harness "${harness.id}" does not support --effort.`);
|
|
1331
|
-
run.model = model ?? run.model;
|
|
1332
|
-
run.effort = effort ?? run.effort;
|
|
1333
|
-
} else {
|
|
1334
|
-
harness = detectHarness(executor, requestedHarnessId);
|
|
1335
|
-
if (effort && harness.effortFlags.length === 0) throw new Error(`Harness "${harness.id}" does not support --effort.`);
|
|
1336
|
-
run = createRun(cwd, harness, model, effort);
|
|
308
|
+
var ApiInterface = class extends Interface {
|
|
309
|
+
validate() {
|
|
310
|
+
if (!this.ctx.parsed.explicitMethod) {
|
|
311
|
+
return `Missing HTTP method. Try: godmode ${this.ctx.extensionName} api GET ${this.ctx.parsed.segments.join(" ")}
|
|
312
|
+
Valid methods: GET, POST, PUT, PATCH, DELETE, HEAD.`;
|
|
1337
313
|
}
|
|
314
|
+
return null;
|
|
1338
315
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
run.effort = effort ?? run.effort;
|
|
1342
|
-
run.lastTurn += 1;
|
|
1343
|
-
saveRun(run);
|
|
1344
|
-
saveActiveRun(cwd, run.id);
|
|
1345
|
-
return { run, harness };
|
|
1346
|
-
}
|
|
1347
|
-
function executeTurn(parsed, options) {
|
|
1348
|
-
const executor = options.executor ?? defaultExecutor;
|
|
1349
|
-
const writer = options.writer ?? process.stdout;
|
|
1350
|
-
const errorWriter = options.errorWriter ?? process.stderr;
|
|
1351
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1352
|
-
const zmxCheck = executor("zmx", ["--help"]);
|
|
1353
|
-
if (zmxCheck.error || zmxCheck.status !== 0) {
|
|
1354
|
-
errorWriter.write("zmx not found. Install zmx to use coding agents.\n");
|
|
1355
|
-
return 2;
|
|
1356
|
-
}
|
|
1357
|
-
const { run, harness } = resolveRunAndHarness(parsed, executor, cwd, options);
|
|
1358
|
-
const { shell, stdoutPath, stderrPath, exitCodePath } = buildShellCommand(harness, run, run.lastTurn, parsed.prompt, parsed.passthroughArgs);
|
|
1359
|
-
const launch = executor("zmx", ["run", run.sessions.zmx, "sh", "-lc", shell], "ignore");
|
|
1360
|
-
if (launch.error || launch.status !== 0) {
|
|
1361
|
-
run.status = "failed";
|
|
1362
|
-
saveRun(run);
|
|
1363
|
-
errorWriter.write(`Failed to queue turn in zmx session ${run.sessions.zmx}.
|
|
1364
|
-
`);
|
|
1365
|
-
return 3;
|
|
316
|
+
execute() {
|
|
317
|
+
return executeRoute(this.ctx);
|
|
1366
318
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
const turnStatus = classifyStatus(exitCode, stdout, stderr);
|
|
1373
|
-
const turn = {
|
|
1374
|
-
id: run.id,
|
|
1375
|
-
turn: run.lastTurn,
|
|
1376
|
-
status: turnStatus,
|
|
1377
|
-
prompt: parsed.prompt,
|
|
1378
|
-
harness: run.harness,
|
|
1379
|
-
sessions: {
|
|
1380
|
-
zmx: run.sessions.zmx,
|
|
1381
|
-
...normalized.sessionId || run.sessions[run.harness] ? { [run.harness]: normalized.sessionId ?? run.sessions[run.harness] } : {}
|
|
1382
|
-
},
|
|
1383
|
-
assistant: { text: normalized.assistantText },
|
|
1384
|
-
usage: normalized.usage,
|
|
1385
|
-
timing: normalized.timing,
|
|
1386
|
-
paths: {
|
|
1387
|
-
stdout: stdoutPath,
|
|
1388
|
-
stderr: stderrPath,
|
|
1389
|
-
events: resolve3(turnDir(run.id, run.lastTurn), "events.jsonl")
|
|
1390
|
-
},
|
|
1391
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1392
|
-
};
|
|
1393
|
-
writeEvents(turn.paths.events, normalized.events);
|
|
1394
|
-
writeJson(resolve3(turnDir(run.id, run.lastTurn), "normalized.json"), turn);
|
|
1395
|
-
if (turn.sessions[run.harness]) run.sessions[run.harness] = turn.sessions[run.harness];
|
|
1396
|
-
run.status = turnStatus;
|
|
1397
|
-
saveRun(run);
|
|
1398
|
-
writer.write(`${renderTurn(turn, parsed.outputMode)}
|
|
1399
|
-
`);
|
|
1400
|
-
return turnStatus === "completed" ? 0 : 1;
|
|
1401
|
-
}
|
|
1402
|
-
function attachToTarget(argv, options) {
|
|
1403
|
-
const executor = options.executor ?? defaultExecutor;
|
|
1404
|
-
const errorWriter = options.errorWriter ?? process.stderr;
|
|
1405
|
-
if (argv.length < 2) {
|
|
1406
|
-
errorWriter.write(`${attachHelp()}
|
|
1407
|
-
`);
|
|
1408
|
-
return 1;
|
|
319
|
+
};
|
|
320
|
+
var GraphqlInterface = class extends Interface {
|
|
321
|
+
validate() {
|
|
322
|
+
const { parsed, extensionName } = this.ctx;
|
|
323
|
+
return validateGraphQLFlags(parsed.method, parsed.query, parsed.body, extensionName);
|
|
1409
324
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
errorWriter.write(`${attachHelp()}
|
|
1413
|
-
`);
|
|
1414
|
-
return 1;
|
|
325
|
+
execute() {
|
|
326
|
+
return executeRoute(this.ctx);
|
|
1415
327
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
328
|
+
};
|
|
329
|
+
var McpInterface = class extends Interface {
|
|
330
|
+
async handleEmpty() {
|
|
331
|
+
const { extensionName, rawRest } = this.ctx;
|
|
332
|
+
await runMcp(
|
|
333
|
+
{
|
|
334
|
+
godmodeHome: GODMODE_HOME,
|
|
335
|
+
loadManifest: (n) => loadManifest(n, "mcp"),
|
|
336
|
+
checkPermission
|
|
337
|
+
},
|
|
338
|
+
[extensionName, ...rawRest]
|
|
339
|
+
);
|
|
1426
340
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
`);
|
|
1442
|
-
return 0;
|
|
1443
|
-
}
|
|
1444
|
-
async function runAgentCommand(argv, options = {}) {
|
|
1445
|
-
ensureDirs();
|
|
1446
|
-
const writer = options.writer ?? process.stdout;
|
|
1447
|
-
const errorWriter = options.errorWriter ?? process.stderr;
|
|
1448
|
-
if (!argv.length) {
|
|
1449
|
-
writer.write(`${agentHelp()}
|
|
341
|
+
validate() {
|
|
342
|
+
const { parsed } = this.ctx;
|
|
343
|
+
return validateMcpFlags(parsed.method, parsed.query);
|
|
344
|
+
}
|
|
345
|
+
async execute() {
|
|
346
|
+
const { manifest, parsed, extensionName } = this.ctx;
|
|
347
|
+
const tool = parsed.segments[0];
|
|
348
|
+
const check = checkPermission({
|
|
349
|
+
extension: extensionName,
|
|
350
|
+
resource: resourceFromTool(tool),
|
|
351
|
+
method: "mcp"
|
|
352
|
+
});
|
|
353
|
+
if (!check.allowed) {
|
|
354
|
+
process.stderr.write(`Blocked: ${check.reason}
|
|
1450
355
|
`);
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
try {
|
|
1454
|
-
const [cmd, ...rest] = argv;
|
|
1455
|
-
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
1456
|
-
writer.write(`${agentHelp()}
|
|
356
|
+
process.stderr.write(`Suggested allow rule:
|
|
357
|
+
${suggestedAllowRule({ extension: extensionName, resource: resourceFromTool(tool), method: "mcp" })}
|
|
1457
358
|
`);
|
|
1458
|
-
|
|
359
|
+
process.exit(EXIT_CODES.permissionDenied);
|
|
1459
360
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
if (
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
361
|
+
const result = await executeMcpTool(manifest.config, tool, parsed.body, {
|
|
362
|
+
debug: parsed.debug,
|
|
363
|
+
dryRun: parsed.dryRun
|
|
364
|
+
});
|
|
365
|
+
if (result) process.stdout.write(result + "\n");
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
function getInterface(ctx) {
|
|
369
|
+
switch (ctx.iface) {
|
|
370
|
+
case "api":
|
|
371
|
+
return new ApiInterface(ctx);
|
|
372
|
+
case "graphql":
|
|
373
|
+
return new GraphqlInterface(ctx);
|
|
374
|
+
case "mcp":
|
|
375
|
+
return new McpInterface(ctx);
|
|
376
|
+
default:
|
|
377
|
+
throw new Error(`Unknown interface: ${ctx.iface}`);
|
|
1471
378
|
}
|
|
1472
379
|
}
|
|
1473
380
|
|
|
1474
381
|
// src/index.ts
|
|
1475
382
|
loadEnv();
|
|
1476
383
|
var VALID_INTERFACES = /* @__PURE__ */ new Set(["api", "graphql", "mcp", "skill"]);
|
|
1477
|
-
var RESERVED_SLUGS = /* @__PURE__ */ new Set(["ext", "agent"]);
|
|
1478
384
|
async function main() {
|
|
1479
385
|
const args = process.argv.slice(2);
|
|
1480
386
|
if (!args.length || args.length === 1 && (args[0] === "-h" || args[0] === "--help")) {
|
|
387
|
+
warnSettingsErrors();
|
|
1481
388
|
showHelp();
|
|
1482
389
|
return;
|
|
1483
390
|
}
|
|
@@ -1487,15 +394,12 @@ async function main() {
|
|
|
1487
394
|
}
|
|
1488
395
|
const extensionSlug = args[0];
|
|
1489
396
|
const rest = args.slice(1);
|
|
1490
|
-
|
|
1491
|
-
|
|
397
|
+
const builtin = BUILTINS.get(extensionSlug);
|
|
398
|
+
if (builtin) {
|
|
399
|
+
await builtin.run(rest);
|
|
1492
400
|
return;
|
|
1493
401
|
}
|
|
1494
|
-
|
|
1495
|
-
await runAgent(rest);
|
|
1496
|
-
return;
|
|
1497
|
-
}
|
|
1498
|
-
const multi = installedExtension(extensionSlug);
|
|
402
|
+
const multi = findInstalledManifestSync(extensionSlug);
|
|
1499
403
|
if (!multi) {
|
|
1500
404
|
process.stderr.write(`'${extensionSlug}' is not an installed extension.
|
|
1501
405
|
`);
|
|
@@ -1503,9 +407,9 @@ async function main() {
|
|
|
1503
407
|
`);
|
|
1504
408
|
process.stderr.write(`Try 'godmode --help' for more information.
|
|
1505
409
|
`);
|
|
1506
|
-
process.exit(
|
|
410
|
+
process.exit(EXIT_CODES.notFound);
|
|
1507
411
|
}
|
|
1508
|
-
const declared =
|
|
412
|
+
const declared = Object.keys(multi.interfaces);
|
|
1509
413
|
const first = rest[0];
|
|
1510
414
|
if (!first || first === "--help" || first === "-h") {
|
|
1511
415
|
showExtensionOverview(multi);
|
|
@@ -1522,7 +426,7 @@ async function main() {
|
|
|
1522
426
|
`);
|
|
1523
427
|
process.stderr.write(`Try 'godmode ${extensionSlug} ${declared[0]} ${first}' or 'godmode ${extensionSlug} --help'.
|
|
1524
428
|
`);
|
|
1525
|
-
process.exit(
|
|
429
|
+
process.exit(EXIT_CODES.usage);
|
|
1526
430
|
}
|
|
1527
431
|
if (!declared.includes(first)) {
|
|
1528
432
|
process.stderr.write(`'${extensionSlug}' does not declare a '${first}' interface.
|
|
@@ -1531,229 +435,47 @@ async function main() {
|
|
|
1531
435
|
`);
|
|
1532
436
|
process.stderr.write(`Try 'godmode ${extensionSlug} --help' for more information.
|
|
1533
437
|
`);
|
|
1534
|
-
process.exit(
|
|
438
|
+
process.exit(EXIT_CODES.usage);
|
|
1535
439
|
}
|
|
1536
440
|
await runInterface(first, extensionSlug, rest.slice(1));
|
|
1537
441
|
}
|
|
1538
|
-
function installedExtension(name) {
|
|
1539
|
-
const extPath = resolve5(GODMODE_EXTENSIONS_DIR, `${name}.json`);
|
|
1540
|
-
if (!existsSync4(extPath)) return null;
|
|
1541
|
-
try {
|
|
1542
|
-
return JSON.parse(readFileSync5(extPath, "utf-8"));
|
|
1543
|
-
} catch {
|
|
1544
|
-
return null;
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
function declaredInterfaces(m) {
|
|
1548
|
-
return Object.keys(m.interfaces);
|
|
1549
|
-
}
|
|
1550
|
-
function showExtHelp() {
|
|
1551
|
-
console.log(`Install, inspect, and manage godmode extensions.
|
|
1552
|
-
|
|
1553
|
-
Usage: godmode ext <command> [args]
|
|
1554
|
-
|
|
1555
|
-
Commands:
|
|
1556
|
-
install <name|folder> Install an extension
|
|
1557
|
-
uninstall <name> Uninstall an extension
|
|
1558
|
-
update <name> Re-fetch spec, rebuild routes
|
|
1559
|
-
list Show installed extensions
|
|
1560
|
-
create Interactive manifest wizard`);
|
|
1561
|
-
}
|
|
1562
|
-
async function runExt(rest) {
|
|
1563
|
-
const cmd = rest[0];
|
|
1564
|
-
if (!cmd || cmd === "--help" || cmd === "-h") {
|
|
1565
|
-
showExtHelp();
|
|
1566
|
-
return;
|
|
1567
|
-
}
|
|
1568
|
-
if (cmd === "install") {
|
|
1569
|
-
const target = rest[1];
|
|
1570
|
-
if (!target) {
|
|
1571
|
-
console.error("Usage: godmode ext install <name|folder>");
|
|
1572
|
-
process.exit(1);
|
|
1573
|
-
}
|
|
1574
|
-
if (RESERVED_SLUGS.has(target)) {
|
|
1575
|
-
process.stderr.write(`'${target}' is a reserved extension slug and cannot be installed.
|
|
1576
|
-
`);
|
|
1577
|
-
process.stderr.write(`Reserved: ${[...RESERVED_SLUGS].join(", ")}.
|
|
1578
|
-
`);
|
|
1579
|
-
process.exit(1);
|
|
1580
|
-
}
|
|
1581
|
-
const { runAdd } = await import("./add-AJERMPEA.js");
|
|
1582
|
-
await runAdd(rest.slice(1));
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
if (cmd === "uninstall") {
|
|
1586
|
-
if (!rest[1]) {
|
|
1587
|
-
console.error("Usage: godmode ext uninstall <name>");
|
|
1588
|
-
process.exit(1);
|
|
1589
|
-
}
|
|
1590
|
-
await removeApi(rest[1]);
|
|
1591
|
-
return;
|
|
1592
|
-
}
|
|
1593
|
-
if (cmd === "update") {
|
|
1594
|
-
if (!rest[1]) {
|
|
1595
|
-
console.error("Usage: godmode ext update <name>");
|
|
1596
|
-
process.exit(1);
|
|
1597
|
-
}
|
|
1598
|
-
await updateApi(rest[1]);
|
|
1599
|
-
return;
|
|
1600
|
-
}
|
|
1601
|
-
if (cmd === "list") {
|
|
1602
|
-
await listApis();
|
|
1603
|
-
return;
|
|
1604
|
-
}
|
|
1605
|
-
if (cmd === "create") {
|
|
1606
|
-
const { configWizard } = await import("./prompt-SWOWWAOH.js");
|
|
1607
|
-
await configWizard();
|
|
1608
|
-
return;
|
|
1609
|
-
}
|
|
1610
|
-
if (installedExtension(cmd)) {
|
|
1611
|
-
const multi = installedExtension(cmd);
|
|
1612
|
-
const ifaces = declaredInterfaces(multi);
|
|
1613
|
-
process.stderr.write(`'${cmd}' is an installed extension, not an ext command.
|
|
1614
|
-
|
|
1615
|
-
`);
|
|
1616
|
-
process.stderr.write(`Did you mean:
|
|
1617
|
-
`);
|
|
1618
|
-
process.stderr.write(` godmode ${cmd} --help
|
|
1619
|
-
`);
|
|
1620
|
-
for (const i of ifaces) {
|
|
1621
|
-
process.stderr.write(` godmode ${cmd} ${i} --help
|
|
1622
|
-
`);
|
|
1623
|
-
}
|
|
1624
|
-
process.stderr.write(`
|
|
1625
|
-
Try 'godmode ext --help' for more information.
|
|
1626
|
-
`);
|
|
1627
|
-
process.exit(1);
|
|
1628
|
-
}
|
|
1629
|
-
process.stderr.write(`Unknown ext command '${cmd}'.
|
|
1630
|
-
`);
|
|
1631
|
-
process.stderr.write(`Try 'godmode ext --help' for more information.
|
|
1632
|
-
`);
|
|
1633
|
-
process.exit(1);
|
|
1634
|
-
}
|
|
1635
|
-
async function runAgent(rest) {
|
|
1636
|
-
const code = await runAgentCommand(rest);
|
|
1637
|
-
if (code !== 0) process.exit(code);
|
|
1638
|
-
}
|
|
1639
442
|
async function runInterface(iface, extensionName, rest) {
|
|
1640
443
|
if (iface === "skill") {
|
|
1641
444
|
process.stderr.write(`Skill interface not yet implemented.
|
|
1642
445
|
`);
|
|
1643
|
-
process.exit(
|
|
446
|
+
process.exit(EXIT_CODES.usage);
|
|
1644
447
|
}
|
|
1645
448
|
if (rest.includes("--version") || rest.includes("-v")) {
|
|
1646
449
|
const multi2 = await loadMultiManifest(extensionName);
|
|
1647
450
|
showExtensionVersion(multi2);
|
|
1648
451
|
return;
|
|
1649
452
|
}
|
|
1650
|
-
const parsed = parseArgs(rest);
|
|
1651
453
|
const ifaceKey = iface;
|
|
454
|
+
const parsed = parseArgs(rest);
|
|
1652
455
|
const multi = await loadMultiManifest(extensionName);
|
|
1653
456
|
const manifest = await loadManifest(extensionName, ifaceKey);
|
|
1654
|
-
const
|
|
457
|
+
const handler = getInterface({
|
|
458
|
+
iface: ifaceKey,
|
|
459
|
+
extensionName,
|
|
460
|
+
manifest,
|
|
461
|
+
multi,
|
|
462
|
+
parsed,
|
|
463
|
+
rawRest: rest
|
|
464
|
+
});
|
|
1655
465
|
if (parsed.help) {
|
|
1656
|
-
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1659
|
-
if (iface === "mcp" && !parsed.segments.length) {
|
|
1660
|
-
await runMcp(
|
|
1661
|
-
{ godmodeHome: GODMODE_HOME, loadManifest: (n) => loadManifest(n, "mcp") },
|
|
1662
|
-
[extensionName, ...rest]
|
|
1663
|
-
);
|
|
466
|
+
handler.showHelp();
|
|
1664
467
|
return;
|
|
1665
468
|
}
|
|
1666
469
|
if (!parsed.segments.length) {
|
|
1667
|
-
|
|
470
|
+
await handler.handleEmpty();
|
|
1668
471
|
return;
|
|
1669
472
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
process.
|
|
1674
|
-
`);
|
|
1675
|
-
process.exit(1);
|
|
473
|
+
const err = handler.validate();
|
|
474
|
+
if (err) {
|
|
475
|
+
process.stderr.write(err + "\n");
|
|
476
|
+
process.exit(EXIT_CODES.usage);
|
|
1676
477
|
}
|
|
1677
|
-
|
|
1678
|
-
const err = validateGraphQLFlags(parsed.method, parsed.query, parsed.body, extensionName);
|
|
1679
|
-
if (err) {
|
|
1680
|
-
process.stderr.write(err + "\n");
|
|
1681
|
-
process.exit(1);
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
if (manifest.config.type === "mcp") {
|
|
1685
|
-
const err = validateMcpFlags(parsed.method, parsed.query);
|
|
1686
|
-
if (err) {
|
|
1687
|
-
process.stderr.write(err + "\n");
|
|
1688
|
-
process.exit(1);
|
|
1689
|
-
}
|
|
1690
|
-
const result = await executeMcpTool(manifest.config, parsed.segments[0], parsed.body, {
|
|
1691
|
-
verbose: parsed.verbose,
|
|
1692
|
-
dryRun: parsed.dryRun
|
|
1693
|
-
});
|
|
1694
|
-
if (result) process.stdout.write(result + "\n");
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
const query = parsed.query;
|
|
1698
|
-
const hasBody = Object.keys(parsed.body).length > 0;
|
|
1699
|
-
let body = hasBody ? JSON.stringify(parsed.body) : void 0;
|
|
1700
|
-
if (!body && ["post", "put", "patch"].includes(parsed.method)) body = await readStdin();
|
|
1701
|
-
if (parsed.segments[0]?.startsWith("/")) {
|
|
1702
|
-
const rawPath = parsed.segments[0];
|
|
1703
|
-
const syntheticRoute = { path: rawPath, method: parsed.method, summary: "", version: "", segments: [] };
|
|
1704
|
-
await execute(manifest, { route: syntheticRoute, params: {} }, {
|
|
1705
|
-
headers: parsed.headers,
|
|
1706
|
-
query,
|
|
1707
|
-
body,
|
|
1708
|
-
verbose: parsed.verbose,
|
|
1709
|
-
dryRun: parsed.dryRun
|
|
1710
|
-
});
|
|
1711
|
-
return;
|
|
1712
|
-
}
|
|
1713
|
-
const match = matchRoute(manifest, parsed.segments, parsed.method);
|
|
1714
|
-
if (!match) {
|
|
1715
|
-
const HTTP_VERBS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
1716
|
-
const trailingVerb = parsed.segments.map((s) => s.toUpperCase()).find((s) => HTTP_VERBS.has(s));
|
|
1717
|
-
if (trailingVerb) {
|
|
1718
|
-
const rest2 = parsed.segments.filter((s) => s.toUpperCase() !== trailingVerb);
|
|
1719
|
-
process.stderr.write(`No route matching: ${parsed.segments.join(" ")}
|
|
1720
|
-
`);
|
|
1721
|
-
process.stderr.write(`Method goes first: try 'godmode ${extensionName} ${iface} ${trailingVerb} ${rest2.join(" ")}'
|
|
1722
|
-
`);
|
|
1723
|
-
process.exit(1);
|
|
1724
|
-
}
|
|
1725
|
-
process.stderr.write(`No ${parsed.method.toUpperCase()} route matching: ${parsed.segments.join(" ")}
|
|
1726
|
-
`);
|
|
1727
|
-
for (const m of ["get", "post", "put", "patch", "delete"]) {
|
|
1728
|
-
if (m === parsed.method) continue;
|
|
1729
|
-
const alt = matchRoute(manifest, parsed.segments, m);
|
|
1730
|
-
if (alt) {
|
|
1731
|
-
process.stderr.write(` try: godmode ${extensionName} api ${m.toUpperCase()} ${parsed.segments.join(" ")}
|
|
1732
|
-
`);
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
const similar = suggestRoutes(manifest, parsed.segments).slice(0, 5);
|
|
1736
|
-
if (similar.length) {
|
|
1737
|
-
process.stderr.write("\nSimilar:\n");
|
|
1738
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1739
|
-
for (const r of similar) {
|
|
1740
|
-
const p = r.segments.map((s) => s.isParam ? `{${s.value}}` : s.value).join(" ");
|
|
1741
|
-
if (seen.has(p)) continue;
|
|
1742
|
-
seen.add(p);
|
|
1743
|
-
process.stderr.write(` ${p}
|
|
1744
|
-
`);
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
process.exit(1);
|
|
1748
|
-
}
|
|
1749
|
-
await execute(manifest, match, {
|
|
1750
|
-
headers: parsed.headers,
|
|
1751
|
-
query,
|
|
1752
|
-
body,
|
|
1753
|
-
token: parsed.token,
|
|
1754
|
-
verbose: parsed.verbose,
|
|
1755
|
-
dryRun: parsed.dryRun
|
|
1756
|
-
});
|
|
478
|
+
await handler.execute();
|
|
1757
479
|
}
|
|
1758
480
|
main().catch((err) => {
|
|
1759
481
|
process.stderr.write(`${err.message || err}
|