godmode 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,18 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  execute
4
- } from "./chunk-I4WS4MMI.js";
4
+ } from "./chunk-GJCJO7YT.js";
5
5
  import {
6
+ GODMODE_EXTENSIONS_DIR,
7
+ GODMODE_HOME,
6
8
  listApis,
7
9
  loadManifest,
10
+ loadMultiManifest,
8
11
  removeApi,
9
12
  updateApi,
10
13
  validateGraphQLFlags
11
- } from "./chunk-FBMQ3AC4.js";
14
+ } from "./chunk-2J2VDCQ4.js";
12
15
  import {
13
16
  executeMcpTool,
14
17
  validateMcpFlags
15
- } from "./chunk-EHS56XXY.js";
18
+ } from "./chunk-EXWYJU2I.js";
19
+
20
+ // src/index.ts
21
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
22
+ import { resolve as resolve5 } from "path";
16
23
 
17
24
  // src/env.ts
18
25
  import { readFileSync } from "fs";
@@ -35,7 +42,7 @@ function loadEnv() {
35
42
  }
36
43
  }
37
44
 
38
- // src/match.ts
45
+ // ../../interfaces/api/src/match.ts
39
46
  function matchRoute(manifest, userSegments, method) {
40
47
  if (userSegments.length > 0) {
41
48
  const ver = manifest.versions.find((v) => v.name === userSegments[0]);
@@ -88,47 +95,73 @@ function suggestRoutes(manifest, userSegments) {
88
95
  });
89
96
  }
90
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
+
91
147
  // src/args.ts
148
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
92
149
  function parseArgs(args) {
93
150
  const segments = [];
94
151
  const headers = {};
95
152
  const query = {};
96
153
  const body = {};
97
154
  let method = "get";
98
- let token;
155
+ let explicitMethod = false;
99
156
  let filter;
100
157
  let methodFilter;
101
158
  let all = false;
102
- let verbose = false;
103
- let dryRun = false;
159
+ const verbose = process.env.GODMODE_VERBOSE === "1";
160
+ const dryRun = process.env.GODMODE_DRY_RUN === "1";
104
161
  let help = false;
105
162
  for (let i = 0; i < args.length; i++) {
106
163
  const arg = args[i];
107
164
  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
165
  // Options
133
166
  case "-H":
134
167
  case "--header": {
@@ -137,25 +170,18 @@ function parseArgs(args) {
137
170
  if (idx > 0) headers[val.slice(0, idx).trim()] = val.slice(idx + 1).trim();
138
171
  break;
139
172
  }
140
- case "--token":
141
- token = args[++i];
142
- break;
173
+ case "-F":
143
174
  case "--filter":
144
175
  filter = args[++i];
145
176
  break;
177
+ case "-X":
146
178
  case "--method":
147
179
  methodFilter = args[++i];
148
180
  break;
181
+ case "-A":
149
182
  case "--all":
150
183
  all = true;
151
184
  break;
152
- case "-v":
153
- case "--verbose":
154
- verbose = true;
155
- break;
156
- case "--dry-run":
157
- dryRun = true;
158
- break;
159
185
  case "-h":
160
186
  case "--help":
161
187
  help = true;
@@ -166,6 +192,11 @@ function parseArgs(args) {
166
192
  `);
167
193
  process.exit(1);
168
194
  }
195
+ if (!segments.length && !explicitMethod && HTTP_METHODS.has(arg.toUpperCase())) {
196
+ method = arg.toLowerCase();
197
+ explicitMethod = true;
198
+ break;
199
+ }
169
200
  const eqeq = arg.indexOf("==");
170
201
  const eq = arg.indexOf("=");
171
202
  if (eqeq > 0) {
@@ -177,8 +208,7 @@ function parseArgs(args) {
177
208
  }
178
209
  }
179
210
  }
180
- if (Object.keys(body).length && method === "get") method = "post";
181
- return { segments, method, headers, query, body, token, filter, methodFilter, all, verbose, dryRun, help };
211
+ return { segments, method, explicitMethod, headers, query, body, filter, methodFilter, all, verbose, dryRun, help };
182
212
  }
183
213
  async function readStdin() {
184
214
  if (process.stdin.isTTY) return void 0;
@@ -190,6 +220,171 @@ async function readStdin() {
190
220
 
191
221
  // src/help.ts
192
222
  import fuzzysort from "fuzzysort";
223
+
224
+ // ../cli-tools/src/index.ts
225
+ var USE_COLOR = process.stdout.isTTY;
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
+ };
386
+
387
+ // src/help.ts
193
388
  function buildTrie(routes) {
194
389
  const root = { name: "", isParam: false, methods: [], children: [] };
195
390
  for (const route of routes) {
@@ -243,42 +438,31 @@ function getChildren(node) {
243
438
  result.sort((a, b) => a.name.localeCompare(b.name));
244
439
  return result;
245
440
  }
246
- var ACTION_ORDER = ["list", "create", "get", "update", "delete"];
247
- var METHOD_LABEL = {
441
+ var METHOD_LABEL = USE_COLOR ? {
248
442
  get: "\x1B[1;38;5;34mGET\x1B[0m",
249
443
  post: "\x1B[1;38;5;25mPOST\x1B[0m",
250
444
  put: "\x1B[1;38;5;172mPUT\x1B[0m",
251
445
  patch: "\x1B[1;38;5;30mPATCH\x1B[0m",
252
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"
253
453
  };
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
- }
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
+ ];
282
466
  function getParamName(node) {
283
467
  return node.children.find((c) => c.isParam)?.name || null;
284
468
  }
@@ -290,251 +474,1208 @@ function methodLabels(node) {
290
474
  const order = ["get", "post", "put", "patch", "delete"];
291
475
  return order.filter((m) => methods.has(m)).map((m) => METHOD_LABEL[m]).join(" ");
292
476
  }
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;
477
+ var RootHelp = class extends HelpPage {
478
+ tagline() {
479
+ return "A control surface of agentic I/O.";
339
480
  }
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
- `);
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();
362
560
  }
363
- } else {
364
- const paramHint = getParamName(node);
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
+ };
583
+ }
584
+ usage() {
585
+ if (!this.path.length) {
586
+ const multi = this.opts.multi;
587
+ const declared = multi ? Object.keys(multi.interfaces) : [this.ifaceType];
588
+ const ordered = [this.ifaceType, ...declared.filter((k) => k !== this.ifaceType)];
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);
365
595
  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}`);
596
+ const methodSlot = this.ifaceType === "api" ? "<method> " : "";
597
+ return [`godmode ${this.apiName} ${this.ifaceType} ${methodSlot}${nav.fullPath.join(" ")}${idRef} [flags]`];
598
+ }
599
+ sections() {
600
+ const nav = this.getNav();
601
+ if (!nav) return [];
602
+ const sections = [];
603
+ if (!this.path.length) {
604
+ if (this.ifaceType === "api") {
605
+ const methodsPresent = new Set(this.manifest.routes.map((r) => r.method.toLowerCase()));
606
+ const METHOD_DESC = {
607
+ get: "retrieve a resource",
608
+ post: "create a resource or send a command",
609
+ put: "replace a resource",
610
+ patch: "modify a resource",
611
+ delete: "remove a resource",
612
+ head: "retrieve headers only"
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
+ }
396
622
  }
397
- console.log("");
398
- console.log(`Example:
399
- godmode ${apiName} ${toolName} ${[...required].map((r) => `${r}=...`).join(" ")}`);
400
- console.log("");
401
623
  } 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 });
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]);
411
634
  }
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 || "" });
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
+ }
427
651
  }
652
+ sections.push({ title: "Methods:", rows });
428
653
  }
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
654
  }
436
655
  }
656
+ const children = getChildren(nav.node).filter((c) => !c.isParam);
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;
437
669
  }
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;
670
+ /** Drilled-in view also shows param status lines (ANSI-only). */
671
+ render() {
672
+ super.render();
673
+ }
674
+ };
675
+ function showHelp() {
676
+ new RootHelp().render();
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();
686
+ }
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
+
697
+ ${pkg.license} License
698
+ ${copyright}`);
699
+ }
700
+ function buildResourceSection(children, childNames, manifest, filter, methodFilter, all) {
701
+ if (!childNames.length) return null;
702
+ const ALL_METHODS = ["get", "post", "put", "patch", "delete"];
703
+ const mf = methodFilter ? fuzzysort.go(methodFilter, ALL_METHODS)[0]?.target : void 0;
704
+ if (methodFilter && !mf) return null;
705
+ const candidates = mf ? childNames.filter((n) => {
706
+ const child = children.find((c) => c.name === n);
707
+ if (!child) return false;
708
+ const methods = /* @__PURE__ */ new Set();
709
+ for (const ep of child.methods) methods.add(ep.method);
710
+ const pc = child.children.find((c) => c.isParam);
711
+ if (pc) for (const ep of pc.methods) methods.add(ep.method);
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]);
737
+ }
738
+ return { section: { title, rows } };
739
+ }
740
+
741
+ // ../../commands/agent/dist/index.js
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
+ ]
826
+ }
827
+ };
828
+ function detectHarness(executor, requested) {
829
+ const ids = requested ? [requested] : DEFAULT_HARNESS_ORDER;
830
+ for (const id of ids) {
831
+ const harness = HARNESSES[id];
832
+ const result = executor(harness.command, harness.helpArgs);
833
+ if (!result.error && result.status === 0) return harness;
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;
445
868
  }
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}`);
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
+ }
889
+ }
890
+ for (const line of stderr.split(/\r?\n/).filter(Boolean)) events.push(event(ts, id, turn, { type: "warning", message: line }));
891
+ return { sessionId, assistantText, events, usage, timing };
892
+ }
893
+ function parseGemini(lines, id, turn, stderr, ts) {
894
+ const events = [];
895
+ let sessionId = null;
896
+ let assistantText = "";
897
+ let usage;
898
+ let timing;
899
+ for (const line of lines) {
900
+ if (!line.trim()) continue;
901
+ let parsed;
902
+ try {
903
+ parsed = JSON.parse(line);
904
+ } catch {
905
+ continue;
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" }));
926
+ }
927
+ }
928
+ for (const line of stderr.split(/\r?\n/).filter(Boolean)) events.push(event(ts, id, turn, { type: "warning", message: line }));
929
+ return { sessionId, assistantText, events, usage, timing };
930
+ }
931
+ function parsePi(lines, id, turn, stderr, ts) {
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
+ }
963
+ }
964
+ for (const line of stderr.split(/\r?\n/).filter(Boolean)) events.push(event(ts, id, turn, { type: "warning", message: line }));
965
+ return { sessionId, assistantText, events };
966
+ }
967
+ var GODMODE_HOME2 = process.platform === "linux" && process.env.XDG_CONFIG_HOME ? resolve4(process.env.XDG_CONFIG_HOME, "godmode") : resolve4(homedir(), ".godmode");
968
+ var CODING_AGENTS_HOME = resolve4(GODMODE_HOME2, "coding-agents");
969
+ var RUNS_DIR = resolve4(CODING_AGENTS_HOME, "runs");
970
+ var ACTIVE_RUNS_PATH = resolve4(CODING_AGENTS_HOME, "active-runs.json");
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)}
983
+ `);
984
+ }
985
+ function readJson(path) {
986
+ try {
987
+ return JSON.parse(readFileSync4(path, "utf-8"));
988
+ } catch {
989
+ return null;
990
+ }
991
+ }
992
+ function readSettingsFile(path) {
993
+ return readJson(path) ?? {};
994
+ }
995
+ function loadSettings(cwd = process.cwd()) {
996
+ const globalSettings = readSettingsFile(resolve4(GODMODE_HOME2, "settings.json"));
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"] ?? {}
475
1007
  }
476
- if (more) {
477
- console.log(` \x1B[2m...${more} more \u2192 use --all, --filter or --method\x1B[0m`);
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
1083
+ });
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
+ }
1101
+ function resolveRunId(cwd, explicit) {
1102
+ if (explicit) return explicit;
1103
+ const active = activeRunIdForCwd(cwd);
1104
+ if (!active) throw new Error(`No active coding-agent run for ${cwd}`);
1105
+ return active;
1106
+ }
1107
+ function renderTurn(turn, outputMode) {
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")}
1124
+ `);
1125
+ }
1126
+ function writeFileSyncSafe(path, content) {
1127
+ writeFileSync2(path, content);
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;
1172
+ }
1173
+ return lines.join("\n");
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)}
1183
+ `);
1184
+ if (outputMode === "assistant-text") {
1185
+ if (event2.type === "assistant.delta" && event2.text) writer.write(event2.text);
1186
+ if (event2.type === "assistant.completed" && !assistant && event2.text) writer.write(event2.text);
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 })}
1201
+ `);
1202
+ }
1203
+ }
1204
+ }
1205
+ function streamFile(path, onLine, until) {
1206
+ let offset = 0;
1207
+ let pending = "";
1208
+ while (true) {
1209
+ if (existsSync2(path)) {
1210
+ const size = statSync(path).size;
1211
+ if (size > offset) {
1212
+ const fd = openSync(path, "r");
1213
+ const buffer = Buffer.alloc(size - offset);
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
+ }
478
1224
  }
479
1225
  }
480
- console.log(`
481
- Use "${prefix} <resource> --help" for more.`);
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;
1292
+ }
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
+ }
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
+ }
1303
+ function classifyStatus(exitCode, stdout, stderr) {
1304
+ const text = `${stdout}
1305
+ ${stderr}`.toLowerCase();
1306
+ if (exitCode === 0) return "completed";
1307
+ if (text.includes("auth") || text.includes("api key") || text.includes("permission") || text.includes("may not exist")) return "blocked";
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);
1337
+ }
1338
+ }
1339
+ run.status = "running";
1340
+ run.model = model ?? run.model;
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;
1366
+ }
1367
+ waitForFile(exitCodePath);
1368
+ const exitCode = Number(readFileSync3(exitCodePath, "utf-8").trim() || "1");
1369
+ const stdout = existsSync3(stdoutPath) ? readFileSync3(stdoutPath, "utf-8") : "";
1370
+ const stderr = existsSync3(stderrPath) ? readFileSync3(stderrPath, "utf-8") : "";
1371
+ const normalized = normalizeTurn(run.harness, run.id, run.lastTurn, stdout, stderr, (/* @__PURE__ */ new Date()).toISOString());
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;
1409
+ }
1410
+ const session = argv[0] === "run" ? loadRun(argv[1]).sessions.zmx : argv[0] === "session" ? argv[1] : null;
1411
+ if (!session) {
1412
+ errorWriter.write(`${attachHelp()}
1413
+ `);
1414
+ return 1;
1415
+ }
1416
+ return executor("zmx", ["attach", session], "inherit").status ?? 1;
1417
+ }
1418
+ function outputCommand(argv, options) {
1419
+ const writer = options.writer ?? process.stdout;
1420
+ const cwd = options.cwd ?? process.cwd();
1421
+ const parsed = parseOutputArgs(argv);
1422
+ const run = loadRun(resolveRunId(cwd, parsed.id));
1423
+ if (parsed.follow && run.status === "running") {
1424
+ followOutput(run, run.lastTurn, parsed.outputMode, writer);
1425
+ return 0;
1426
+ }
1427
+ writer.write(`${renderTurn(latestTurn(run), parsed.outputMode)}
1428
+ `);
1429
+ return 0;
1430
+ }
1431
+ function statusCommand(argv, options) {
1432
+ const writer = options.writer ?? process.stdout;
1433
+ const cwd = options.cwd ?? process.cwd();
1434
+ writer.write(`${renderStatus(loadRun(resolveRunId(cwd, argv.find((arg) => !arg.startsWith("-")))))}
1435
+ `);
1436
+ return 0;
1437
+ }
1438
+ function listCommand(options) {
1439
+ const writer = options.writer ?? process.stdout;
1440
+ writer.write(`${JSON.stringify(listRuns(), null, 2)}
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()}
1450
+ `);
1451
+ return 0;
1452
+ }
1453
+ try {
1454
+ const [cmd, ...rest] = argv;
1455
+ if (cmd === "--help" || cmd === "-h" || cmd === "help") {
1456
+ writer.write(`${agentHelp()}
1457
+ `);
1458
+ return 0;
1459
+ }
1460
+ if (cmd === "start") return executeTurn(parseStartSendArgs("start", rest), options);
1461
+ if (cmd === "send") return executeTurn(parseStartSendArgs("send", rest), options);
1462
+ if (cmd === "attach") return attachToTarget(rest, options);
1463
+ if (cmd === "output") return outputCommand(rest, options);
1464
+ if (cmd === "status") return statusCommand(rest, options);
1465
+ if (cmd === "list") return listCommand(options);
1466
+ return executeTurn(parseStartSendArgs("send", argv), options);
1467
+ } catch (error) {
1468
+ errorWriter.write(`${error.message || error}
1469
+ `);
1470
+ return 1;
482
1471
  }
483
1472
  }
484
1473
 
485
1474
  // src/index.ts
486
1475
  loadEnv();
1476
+ var VALID_INTERFACES = /* @__PURE__ */ new Set(["api", "graphql", "mcp", "skill"]);
1477
+ var RESERVED_SLUGS = /* @__PURE__ */ new Set(["ext", "agent"]);
487
1478
  async function main() {
488
1479
  const args = process.argv.slice(2);
489
1480
  if (!args.length || args.length === 1 && (args[0] === "-h" || args[0] === "--help")) {
490
1481
  showHelp();
491
1482
  return;
492
1483
  }
493
- const cmd = args[0];
494
- if (cmd === "create") {
495
- const { configWizard } = await import("./prompt-DCFFOQ7K.js");
496
- await configWizard();
1484
+ if (args.length === 1 && (args[0] === "--version" || args[0] === "-v")) {
1485
+ await showVersion();
497
1486
  return;
498
1487
  }
499
- if (cmd === "add") {
500
- const { runAdd } = await import("./add-ZRNIX6DM.js");
501
- await runAdd(args.slice(1));
1488
+ const extensionSlug = args[0];
1489
+ const rest = args.slice(1);
1490
+ if (extensionSlug === "ext") {
1491
+ await runExt(rest);
502
1492
  return;
503
1493
  }
504
- if (cmd === "update") {
505
- if (!args[1]) {
506
- console.error("Usage: godmode update <name>");
1494
+ if (extensionSlug === "agent") {
1495
+ await runAgent(rest);
1496
+ return;
1497
+ }
1498
+ const multi = installedExtension(extensionSlug);
1499
+ if (!multi) {
1500
+ process.stderr.write(`'${extensionSlug}' is not an installed extension.
1501
+ `);
1502
+ process.stderr.write(`Try 'godmode ext list' to see installed extensions.
1503
+ `);
1504
+ process.stderr.write(`Try 'godmode --help' for more information.
1505
+ `);
1506
+ process.exit(1);
1507
+ }
1508
+ const declared = declaredInterfaces(multi);
1509
+ const first = rest[0];
1510
+ if (!first || first === "--help" || first === "-h") {
1511
+ showExtensionOverview(multi);
1512
+ return;
1513
+ }
1514
+ if (first === "--version" || first === "-v") {
1515
+ showExtensionVersion(multi);
1516
+ return;
1517
+ }
1518
+ if (!VALID_INTERFACES.has(first)) {
1519
+ process.stderr.write(`Missing interface.
1520
+ `);
1521
+ process.stderr.write(`'${extensionSlug}' declares: ${declared.join(", ")}.
1522
+ `);
1523
+ process.stderr.write(`Try 'godmode ${extensionSlug} ${declared[0]} ${first}' or 'godmode ${extensionSlug} --help'.
1524
+ `);
1525
+ process.exit(1);
1526
+ }
1527
+ if (!declared.includes(first)) {
1528
+ process.stderr.write(`'${extensionSlug}' does not declare a '${first}' interface.
1529
+ `);
1530
+ process.stderr.write(`Declared: ${declared.join(", ")}.
1531
+ `);
1532
+ process.stderr.write(`Try 'godmode ${extensionSlug} --help' for more information.
1533
+ `);
1534
+ process.exit(1);
1535
+ }
1536
+ await runInterface(first, extensionSlug, rest.slice(1));
1537
+ }
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
+ `);
507
1579
  process.exit(1);
508
1580
  }
509
- await updateApi(args[1]);
1581
+ const { runAdd } = await import("./add-AJERMPEA.js");
1582
+ await runAdd(rest.slice(1));
510
1583
  return;
511
1584
  }
512
- if (cmd === "remove") {
513
- if (!args[1]) {
514
- console.error("Usage: godmode remove <name>");
1585
+ if (cmd === "uninstall") {
1586
+ if (!rest[1]) {
1587
+ console.error("Usage: godmode ext uninstall <name>");
515
1588
  process.exit(1);
516
1589
  }
517
- await removeApi(args[1]);
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]);
518
1599
  return;
519
1600
  }
520
1601
  if (cmd === "list") {
521
1602
  await listApis();
522
1603
  return;
523
1604
  }
524
- if (cmd === "mcp") {
525
- const { runMcp } = await import("./mcp-NZIEMQOM.js");
526
- await runMcp(args.slice(1));
1605
+ if (cmd === "create") {
1606
+ const { configWizard } = await import("./prompt-SWOWWAOH.js");
1607
+ await configWizard();
527
1608
  return;
528
1609
  }
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);
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
+ async function runInterface(iface, extensionName, rest) {
1640
+ if (iface === "skill") {
1641
+ process.stderr.write(`Skill interface not yet implemented.
1642
+ `);
1643
+ process.exit(1);
1644
+ }
1645
+ if (rest.includes("--version") || rest.includes("-v")) {
1646
+ const multi2 = await loadMultiManifest(extensionName);
1647
+ showExtensionVersion(multi2);
1648
+ return;
1649
+ }
1650
+ const parsed = parseArgs(rest);
1651
+ const ifaceKey = iface;
1652
+ const multi = await loadMultiManifest(extensionName);
1653
+ const manifest = await loadManifest(extensionName, ifaceKey);
1654
+ const implicitMethodFilter = parsed.methodFilter || (parsed.explicitMethod ? parsed.method : void 0);
1655
+ if (parsed.help) {
1656
+ showApiHelp(manifest, extensionName, parsed.segments, parsed.filter, implicitMethodFilter, parsed.all, multi);
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
+ );
534
1664
  return;
535
1665
  }
536
- if (manifest.config.type === "graphql") {
537
- const err = validateGraphQLFlags(parsed.method, parsed.query, parsed.body, apiName);
1666
+ if (!parsed.segments.length) {
1667
+ showApiHelp(manifest, extensionName, parsed.segments, parsed.filter, implicitMethodFilter, parsed.all, multi);
1668
+ return;
1669
+ }
1670
+ if (iface === "api" && !parsed.explicitMethod) {
1671
+ process.stderr.write(`Missing HTTP method. Try: godmode ${extensionName} api GET ${parsed.segments.join(" ")}
1672
+ `);
1673
+ process.stderr.write(`Valid methods: GET, POST, PUT, PATCH, DELETE, HEAD.
1674
+ `);
1675
+ process.exit(1);
1676
+ }
1677
+ if (iface === "graphql") {
1678
+ const err = validateGraphQLFlags(parsed.method, parsed.query, parsed.body, extensionName);
538
1679
  if (err) {
539
1680
  process.stderr.write(err + "\n");
540
1681
  process.exit(1);
@@ -564,7 +1705,6 @@ async function main() {
564
1705
  headers: parsed.headers,
565
1706
  query,
566
1707
  body,
567
- token: parsed.token,
568
1708
  verbose: parsed.verbose,
569
1709
  dryRun: parsed.dryRun
570
1710
  });
@@ -572,14 +1712,23 @@ async function main() {
572
1712
  }
573
1713
  const match = matchRoute(manifest, parsed.segments, parsed.method);
574
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
+ }
575
1725
  process.stderr.write(`No ${parsed.method.toUpperCase()} route matching: ${parsed.segments.join(" ")}
576
1726
  `);
577
1727
  for (const m of ["get", "post", "put", "patch", "delete"]) {
578
1728
  if (m === parsed.method) continue;
579
1729
  const alt = matchRoute(manifest, parsed.segments, m);
580
1730
  if (alt) {
581
- const flag = m === "delete" ? "-d" : `--${m}`;
582
- process.stderr.write(` try: godmode ${apiName} ${parsed.segments.join(" ")} ${flag}
1731
+ process.stderr.write(` try: godmode ${extensionName} api ${m.toUpperCase()} ${parsed.segments.join(" ")}
583
1732
  `);
584
1733
  }
585
1734
  }