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/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # godmode
2
+
3
+ any API as CLI. any API as MCP. zero code generation.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install -g godmode
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```sh
14
+ godmode ext install stripe
15
+ godmode stripe api customers cus_123
16
+ godmode stripe mcp # serve as MCP server
17
+ ```
18
+
19
+ Run `godmode --help` or `godmode <extension> --help` at any nesting level for usage.
20
+
21
+ ## Claude Code
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "stripe": {
27
+ "command": "godmode",
28
+ "args": ["stripe", "mcp"]
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ See [documentation](https://docs.godmode.so) for more.
@@ -1,20 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  addApi
4
- } from "./chunk-FBMQ3AC4.js";
5
- import "./chunk-EHS56XXY.js";
4
+ } from "./chunk-2J2VDCQ4.js";
5
+ import "./chunk-EXWYJU2I.js";
6
6
 
7
7
  // src/commands/add.ts
8
8
  async function runAdd(args) {
9
9
  if (!args[0] || args[0] === "--help" || args[0] === "-h") {
10
- console.log(`Add an adapter from a folder, built-in name, or manifest.
10
+ console.log(`Add an extension from a folder, built-in name, or manifest.
11
11
 
12
12
  Usage:
13
- godmode add <name> Built-in adapter (e.g. stripe, github)
14
- godmode add <folder> Folder containing manifest.yaml
13
+ godmode extension add <name> Built-in extension (e.g. stripe, github)
14
+ godmode extension add <folder> Folder containing manifest.yaml
15
+ godmode extension add <manifest> Direct path to manifest.yaml/json
15
16
 
16
17
  Manifest format (manifest.yaml):
17
- slug: stripe CLI name (used as "godmode <slug>")
18
+ slug: stripe CLI name
18
19
  name: Stripe Display name
19
20
  description: Payments API Short description
20
21
  type: api api | graphql | mcp
@@ -32,9 +33,10 @@ Types:
32
33
  mcp MCP endpoint (requires url)
33
34
 
34
35
  Examples:
35
- $ godmode add stripe
36
- $ godmode add ./my-adapter
37
- $ godmode add openai`);
36
+ $ godmode extension add stripe
37
+ $ godmode extension add ./my-extension
38
+ $ godmode extension add ./my-extension/manifest.yaml
39
+ $ godmode extension add openai`);
38
40
  process.exit(args[0] ? 0 : 1);
39
41
  }
40
42
  await addApi(args[0]);
@@ -0,0 +1,503 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ parseMcp
4
+ } from "./chunk-EXWYJU2I.js";
5
+
6
+ // ../../interfaces/graphql/src/index.ts
7
+ import { readFile } from "fs/promises";
8
+ import { isAbsolute, resolve } from "path";
9
+ var INTROSPECTION_QUERY = `{
10
+ __schema {
11
+ queryType { name }
12
+ mutationType { name }
13
+ subscriptionType { name }
14
+ types {
15
+ name kind description
16
+ fields { name description args { name type { name kind ofType { name kind } } } }
17
+ }
18
+ }
19
+ }`;
20
+ async function introspect(url, headers) {
21
+ const res = await fetch(url, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json", ...headers },
24
+ body: JSON.stringify({ query: INTROSPECTION_QUERY })
25
+ });
26
+ if (!res.ok) throw new Error(`Introspection failed: ${res.status} ${res.statusText}`);
27
+ const json = await res.json();
28
+ return json.data?.__schema?.types?.filter((t) => !t.name.startsWith("__")) || [];
29
+ }
30
+ function parseSdl(text) {
31
+ const types = [];
32
+ const typeRegex = /type\s+(\w+)\s*\{([^}]*)}/g;
33
+ let match;
34
+ while ((match = typeRegex.exec(text)) !== null) {
35
+ const [, typeName, body] = match;
36
+ const fields = [];
37
+ const fieldRegex = /(\w+)(?:\(([^)]*)\))?\s*:\s*\S+/g;
38
+ let fm;
39
+ while ((fm = fieldRegex.exec(body)) !== null) {
40
+ const args = [];
41
+ if (fm[2]) {
42
+ const argRegex = /(\w+)\s*:\s*(\w+)/g;
43
+ let am;
44
+ while ((am = argRegex.exec(fm[2])) !== null) {
45
+ args.push({ name: am[1], type: { name: am[2], kind: "SCALAR" } });
46
+ }
47
+ }
48
+ fields.push({ name: fm[1], args: args.length ? args : void 0 });
49
+ }
50
+ types.push({ name: typeName, kind: "OBJECT", fields });
51
+ }
52
+ return types;
53
+ }
54
+ async function parseGraphQL(name, config) {
55
+ let types;
56
+ if (config.spec) {
57
+ const isRemote = /^https?:\/\//.test(config.spec);
58
+ if (isRemote) {
59
+ process.stderr.write(`Fetching ${config.spec}...
60
+ `);
61
+ const res = await fetch(config.spec);
62
+ if (!res.ok) throw new Error(`Failed to fetch spec: ${res.status}`);
63
+ types = parseSdl(await res.text());
64
+ } else {
65
+ const specPath = isAbsolute(config.spec) ? config.spec : resolve(process.cwd(), config.spec);
66
+ process.stderr.write(`Loading ${specPath}...
67
+ `);
68
+ types = parseSdl(await readFile(specPath, "utf-8"));
69
+ }
70
+ } else {
71
+ if (!config.url) throw new Error('GraphQL config needs either "spec" or "url"');
72
+ process.stderr.write(`Introspecting ${config.url}...
73
+ `);
74
+ const headers = { ...config.headers };
75
+ const token = config.auth?.env ? process.env[config.auth.env] : void 0;
76
+ if (token) {
77
+ const authType = config.auth?.type || "bearer";
78
+ if (authType === "bearer") headers["Authorization"] = `Bearer ${token}`;
79
+ else if (authType === "api-key") headers[config.auth?.header || "X-API-Key"] = token;
80
+ }
81
+ types = await introspect(config.url, headers);
82
+ }
83
+ const routes = [];
84
+ for (const type of types) {
85
+ if (!type.fields) continue;
86
+ const isQuery = type.name === "Query" || type.name === "RootQuery";
87
+ const isMutation = type.name === "Mutation" || type.name === "RootMutation";
88
+ if (!isQuery && !isMutation) continue;
89
+ const method = isMutation ? "post" : "get";
90
+ for (const field of type.fields) {
91
+ routes.push({
92
+ path: field.name,
93
+ method,
94
+ summary: field.description || "",
95
+ version: "",
96
+ segments: [{ value: field.name, isParam: false }]
97
+ });
98
+ }
99
+ }
100
+ routes.sort((a, b) => a.path.localeCompare(b.path));
101
+ process.stderr.write(`Parsed ${routes.length} fields (${types.length} types)
102
+ `);
103
+ return { name, description: config.description || "", specVersion: "", config, versions: [], resourceDescriptions: {}, routes };
104
+ }
105
+ function validateGraphQLFlags(method, query, body, apiName) {
106
+ if (["put", "patch", "delete", "head"].includes(method)) {
107
+ return `GraphQL only supports POST. ${method.toUpperCase()} is not valid.`;
108
+ }
109
+ if (method === "post") {
110
+ process.stderr.write(`Note: POST is implicit for GraphQL, flag not needed.
111
+ `);
112
+ }
113
+ if (Object.keys(query).length || Object.keys(body).length) {
114
+ return `GraphQL uses document syntax, not key=value params.
115
+ godmode ${apiName} '{ field { subfield } }'`;
116
+ }
117
+ return null;
118
+ }
119
+
120
+ // src/config.ts
121
+ import { resolve as resolve2, basename, dirname, extname } from "path";
122
+ import { homedir } from "os";
123
+ import { mkdir, readFile as readFile2, writeFile, readdir, unlink, access, rename } from "fs/promises";
124
+ import { execSync } from "child_process";
125
+ import { parse as parseYaml2 } from "yaml";
126
+
127
+ // ../../interfaces/api/src/index.ts
128
+ import { parse as parseYaml } from "yaml";
129
+ async function parseOpenApi(name, config) {
130
+ process.stderr.write(`Fetching ${config.spec}...
131
+ `);
132
+ const res = await fetch(config.spec);
133
+ if (!res.ok) throw new Error(`Failed to fetch spec: ${res.status} ${res.statusText}`);
134
+ const text = await res.text();
135
+ const isJson = text.trimStart().startsWith("{");
136
+ const spec = isJson ? JSON.parse(text) : parseYaml(text);
137
+ if (!spec.paths) throw new Error("No paths found - is this a valid OpenAPI document?");
138
+ if (!config.url) {
139
+ if (spec.servers?.[0]?.url) {
140
+ config.url = spec.servers[0].url;
141
+ } else if (spec.host) {
142
+ const scheme = spec.schemes?.[0] || "https";
143
+ config.url = `${scheme}://${spec.host}${spec.basePath || ""}`;
144
+ }
145
+ }
146
+ const versions = resolveVersions(config, Object.keys(spec.paths));
147
+ const httpMethods = ["get", "post", "put", "patch", "delete", "head"];
148
+ const routes = [];
149
+ for (const [path, pathItem] of Object.entries(spec.paths)) {
150
+ for (const method of httpMethods) {
151
+ const op = pathItem[method];
152
+ if (!op) continue;
153
+ const ver = versions.find((v) => path.startsWith(v.prefix));
154
+ const version = ver?.name || "";
155
+ const stripped = ver ? path.slice(ver.prefix.length).replace(/^\//, "") : path.slice(1);
156
+ const rawSegments = stripped.split("/").filter(Boolean);
157
+ const segments = rawSegments.map((s) => {
158
+ const isParam = s.startsWith("{") && s.endsWith("}");
159
+ return { value: isParam ? s.slice(1, -1) : s, isParam };
160
+ });
161
+ const tag = op.tags?.[0] || "";
162
+ routes.push({ path, method, summary: op.summary || "", version, tag, segments });
163
+ }
164
+ }
165
+ routes.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
166
+ const resourceDescriptions = {};
167
+ if (spec.tags) {
168
+ const stripHtml = (s) => s.replace(/<[^>]+>/g, "").trim();
169
+ for (const t of spec.tags) {
170
+ if (t.name && t.description) {
171
+ const first = stripHtml(t.description).split(/\.\s/)[0];
172
+ resourceDescriptions[t.name] = first.endsWith(".") ? first : first + ".";
173
+ }
174
+ }
175
+ }
176
+ const description = config.description || spec.info?.description || "";
177
+ const specVersion = spec.info?.version || "";
178
+ const versionNames = versions.map((v) => v.name).join(", ");
179
+ process.stderr.write(`Parsed ${routes.length} routes (versions: ${versionNames || "none"})
180
+ `);
181
+ return { name, description, specVersion, config, versions, resourceDescriptions, routes };
182
+ }
183
+ function resolveVersions(config, paths) {
184
+ if (config.versions?.length) {
185
+ return config.versions.map((v) => ({
186
+ prefix: v.prefix.endsWith("/") ? v.prefix.slice(0, -1) : v.prefix,
187
+ name: v.name || v.prefix.replace(/^\//, "")
188
+ }));
189
+ }
190
+ if (config.prefix) {
191
+ const prefix = config.prefix.endsWith("/") ? config.prefix.slice(0, -1) : config.prefix;
192
+ return [{ prefix, name: prefix.replace(/^\//, "") }];
193
+ }
194
+ const versionPattern = /^(\/(?:api\/)?v\d+)/;
195
+ const detected = /* @__PURE__ */ new Map();
196
+ for (const p of paths) {
197
+ const match = p.match(versionPattern);
198
+ if (match) detected.set(match[1], (detected.get(match[1]) || 0) + 1);
199
+ }
200
+ if (detected.size) {
201
+ return [...detected.entries()].sort((a, b) => b[1] - a[1]).map(([prefix]) => ({ prefix, name: prefix.replace(/^\//, "") }));
202
+ }
203
+ return [{ prefix: "", name: "" }];
204
+ }
205
+
206
+ // src/spec.ts
207
+ function projectManifest(multi, iface) {
208
+ const data = multi.interfaces[iface];
209
+ if (!data) {
210
+ throw new Error(
211
+ `extension '${multi.slug}' does not declare an '${iface}' interface (declared: ${Object.keys(multi.interfaces).join(", ")})`
212
+ );
213
+ }
214
+ const config = {
215
+ slug: multi.slug,
216
+ name: multi.name,
217
+ description: multi.description,
218
+ type: iface,
219
+ auth: multi.auth,
220
+ headers: multi.headers,
221
+ ..."spec" in data ? { spec: data.spec } : {},
222
+ ..."url" in data && data.url ? { url: data.url } : {},
223
+ ..."prefix" in data && data.prefix ? { prefix: data.prefix } : {},
224
+ versions: data.versions,
225
+ ...iface === "mcp" && data._mcpTools ? { _mcpTools: data._mcpTools } : {}
226
+ };
227
+ return {
228
+ name: multi.name,
229
+ description: multi.description,
230
+ specVersion: data.specVersion,
231
+ config,
232
+ versions: data.versions,
233
+ resourceDescriptions: data.resourceDescriptions,
234
+ routes: data.routes
235
+ };
236
+ }
237
+ var parsers = {
238
+ api: parseOpenApi,
239
+ graphql: parseGraphQL,
240
+ mcp: parseMcp
241
+ };
242
+ async function compileInterface(iface, name, source) {
243
+ const parser = parsers[iface];
244
+ if (!parser) throw new Error(`Unknown interface '${iface}'`);
245
+ const ifaceSource = source.interfaces[iface];
246
+ if (!ifaceSource) throw new Error(`Interface '${iface}' not declared on '${name}'`);
247
+ const legacyConfig = {
248
+ slug: source.slug || name,
249
+ name: source.name,
250
+ description: source.description,
251
+ type: iface,
252
+ auth: source.auth,
253
+ headers: source.headers,
254
+ ..."spec" in ifaceSource && ifaceSource.spec ? { spec: ifaceSource.spec } : {},
255
+ ..."url" in ifaceSource && ifaceSource.url ? { url: ifaceSource.url } : {},
256
+ ..."prefix" in ifaceSource && ifaceSource.prefix ? { prefix: ifaceSource.prefix } : {},
257
+ versions: ifaceSource.versions
258
+ };
259
+ const flat = await parser(name, legacyConfig);
260
+ const base = {
261
+ type: iface,
262
+ specVersion: flat.specVersion,
263
+ versions: flat.versions,
264
+ resourceDescriptions: flat.resourceDescriptions,
265
+ routes: flat.routes
266
+ };
267
+ if (iface === "api") {
268
+ const s2 = ifaceSource;
269
+ return {
270
+ ...base,
271
+ type: "api",
272
+ spec: s2.spec,
273
+ url: flat.config.url,
274
+ prefix: s2.prefix
275
+ };
276
+ }
277
+ if (iface === "graphql") {
278
+ const s2 = ifaceSource;
279
+ return {
280
+ ...base,
281
+ type: "graphql",
282
+ spec: s2.spec,
283
+ url: flat.config.url
284
+ };
285
+ }
286
+ const s = ifaceSource;
287
+ return {
288
+ ...base,
289
+ type: "mcp",
290
+ url: s.url,
291
+ _mcpTools: flat.config._mcpTools
292
+ };
293
+ }
294
+
295
+ // src/config.ts
296
+ var GODMODE_HOME = process.platform === "linux" && process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME, "godmode") : resolve2(homedir(), ".godmode");
297
+ var GODMODE_EXTENSIONS_DIR = process.env.GODMODE_EXTENSIONS_DIR ? resolve2(process.env.GODMODE_EXTENSIONS_DIR) : resolve2(GODMODE_HOME, "extensions");
298
+ var LEGACY_APIS_DIR = resolve2(GODMODE_HOME, "apis");
299
+ var INTERFACE_KEYS = ["api", "graphql", "mcp"];
300
+ async function ensureDirs() {
301
+ if (await exists(LEGACY_APIS_DIR) && !await exists(GODMODE_EXTENSIONS_DIR)) {
302
+ await mkdir(dirname(GODMODE_EXTENSIONS_DIR), { recursive: true });
303
+ await rename(LEGACY_APIS_DIR, GODMODE_EXTENSIONS_DIR);
304
+ process.stderr.write(`Migrated ${LEGACY_APIS_DIR} -> ${GODMODE_EXTENSIONS_DIR}
305
+ `);
306
+ }
307
+ await mkdir(GODMODE_EXTENSIONS_DIR, { recursive: true });
308
+ }
309
+ async function exists(path) {
310
+ try {
311
+ await access(path);
312
+ return true;
313
+ } catch {
314
+ return false;
315
+ }
316
+ }
317
+ function validateSource(raw, origin) {
318
+ if (!raw || typeof raw !== "object") {
319
+ throw new Error(`${origin}: not a valid object`);
320
+ }
321
+ const m = raw;
322
+ if (!m.name || typeof m.name !== "string") {
323
+ throw new Error(`${origin}: missing or invalid 'name'`);
324
+ }
325
+ if (!m.interfaces || typeof m.interfaces !== "object" || Object.keys(m.interfaces).length === 0) {
326
+ throw new Error(`${origin}: 'interfaces' must be an object with at least one key`);
327
+ }
328
+ for (const key of Object.keys(m.interfaces)) {
329
+ if (!INTERFACE_KEYS.includes(key)) {
330
+ throw new Error(
331
+ `${origin}: unknown interface '${key}' (valid: ${INTERFACE_KEYS.join(", ")})`
332
+ );
333
+ }
334
+ }
335
+ return m;
336
+ }
337
+ async function loadSourceFromDir(dir) {
338
+ for (const ext of [".yaml", ".yml", ".json"]) {
339
+ const filePath = resolve2(dir, `manifest${ext}`);
340
+ if (await exists(filePath)) {
341
+ const text = await readFile2(filePath, "utf-8");
342
+ const raw = ext === ".json" ? JSON.parse(text) : parseYaml2(text);
343
+ return validateSource(raw, filePath);
344
+ }
345
+ }
346
+ return null;
347
+ }
348
+ async function loadSourceFromFile(filePath) {
349
+ if (!await exists(filePath)) return null;
350
+ const ext = extname(filePath);
351
+ if (![".yaml", ".yml", ".json"].includes(ext)) return null;
352
+ const text = await readFile2(filePath, "utf-8");
353
+ const raw = ext === ".json" ? JSON.parse(text) : parseYaml2(text);
354
+ return { source: validateSource(raw, filePath), dir: dirname(filePath) };
355
+ }
356
+ async function resolveSource(input) {
357
+ const asPath = resolve2(process.cwd(), input);
358
+ const fromFile = await loadSourceFromFile(asPath);
359
+ if (fromFile) {
360
+ const name = fromFile.source.slug || fromFile.source.name || basename(fromFile.dir);
361
+ return { name, source: fromFile.source, dir: fromFile.dir };
362
+ }
363
+ const fromDir = await loadSourceFromDir(asPath);
364
+ if (fromDir) {
365
+ const name = fromDir.slug || fromDir.name || basename(asPath);
366
+ return { name, source: fromDir, dir: asPath };
367
+ }
368
+ try {
369
+ const pkgDir = resolve2(import.meta.dirname, "..", "..", "..", "extensions", input);
370
+ const fromPkg = await loadSourceFromDir(pkgDir);
371
+ if (fromPkg) {
372
+ const name = fromPkg.slug || fromPkg.name || input;
373
+ return { name, source: fromPkg, dir: pkgDir };
374
+ }
375
+ } catch {
376
+ }
377
+ throw new Error(
378
+ `No manifest found: ${input} (expected manifest file, folder containing manifest.yaml, or @godmode-cli/${input})`
379
+ );
380
+ }
381
+ async function addApi(input) {
382
+ await ensureDirs();
383
+ const resolved = await resolveSource(input).catch(() => null);
384
+ if (resolved) {
385
+ const { name: name2, source } = resolved;
386
+ const ifaceKeys = Object.keys(source.interfaces).filter(
387
+ (k) => INTERFACE_KEYS.includes(k)
388
+ );
389
+ if (ifaceKeys.length > 0) {
390
+ const multi = {
391
+ name: source.name,
392
+ slug: source.slug || name2,
393
+ description: source.description || "",
394
+ auth: source.auth,
395
+ headers: source.headers,
396
+ interfaces: {}
397
+ };
398
+ for (const iface of ifaceKeys) {
399
+ const data = await compileInterface(iface, name2, source);
400
+ multi.interfaces[iface] = data;
401
+ }
402
+ await writeFile(resolve2(GODMODE_EXTENSIONS_DIR, `${name2}.json`), JSON.stringify(multi, null, 2));
403
+ const ifaceSummary = ifaceKeys.map((k) => {
404
+ const d = multi.interfaces[k];
405
+ return d ? `${k}=${d.routes.length}` : k;
406
+ }).join(", ");
407
+ process.stderr.write(`Registered "${name2}" - interfaces: ${ifaceSummary}
408
+ `);
409
+ return;
410
+ }
411
+ }
412
+ const name = resolved?.name || input;
413
+ const installTarget = resolved?.dir && await exists(resolve2(resolved.dir, "package.json")) ? resolved.dir : input.startsWith("@") ? input : `@godmode-cli/${input}`;
414
+ process.stderr.write(`Installing ${name}...
415
+ `);
416
+ try {
417
+ execSync(`npm install ${installTarget} --prefix ${GODMODE_HOME}`, { stdio: "pipe" });
418
+ } catch (e) {
419
+ const err = e;
420
+ throw new Error(`Failed to install ${name}: ${err.stderr?.toString().trim() || err.message}`);
421
+ }
422
+ const pkgName = `@godmode-cli/${name}`;
423
+ const mcpConfigPath = resolve2(GODMODE_HOME, "node_modules", pkgName, ".mcp.json");
424
+ if (await exists(mcpConfigPath)) {
425
+ process.stderr.write(`Installed "${name}" (MCP server extension)
426
+ `);
427
+ } else {
428
+ process.stderr.write(`Installed "${name}"
429
+ `);
430
+ }
431
+ }
432
+ async function updateApi(name) {
433
+ await addApi(name);
434
+ }
435
+ async function removeApi(name) {
436
+ try {
437
+ await unlink(resolve2(GODMODE_EXTENSIONS_DIR, `${name}.json`));
438
+ process.stderr.write(`Removed "${name}"
439
+ `);
440
+ } catch {
441
+ process.stderr.write(`Extension "${name}" not found
442
+ `);
443
+ process.exit(1);
444
+ }
445
+ }
446
+ async function listApis() {
447
+ await ensureDirs();
448
+ const files = await readdir(GODMODE_EXTENSIONS_DIR);
449
+ const apis = files.filter((f) => f.endsWith(".json"));
450
+ if (!apis.length) {
451
+ console.log("No extensions registered. Create a manifest.yaml and run: godmode extension add <name>");
452
+ return;
453
+ }
454
+ for (const file of apis) {
455
+ const m = JSON.parse(await readFile2(resolve2(GODMODE_EXTENSIONS_DIR, file), "utf-8"));
456
+ const ifaces = Object.keys(m.interfaces).join(", ");
457
+ const desc = m.description ? ` ${m.description}` : "";
458
+ const routeTotal = Object.values(m.interfaces).reduce((n, d) => n + (d?.routes.length ?? 0), 0);
459
+ console.log(` ${m.slug} [${ifaces}] ${routeTotal} routes${desc}`);
460
+ }
461
+ }
462
+ async function loadMultiManifest(name) {
463
+ await ensureDirs();
464
+ try {
465
+ return JSON.parse(await readFile2(resolve2(GODMODE_EXTENSIONS_DIR, `${name}.json`), "utf-8"));
466
+ } catch {
467
+ process.stderr.write(`Extension "${name}" not found. Create ${name}.yaml and run: godmode extension add ${name}
468
+ `);
469
+ process.exit(1);
470
+ }
471
+ }
472
+ async function loadManifest(name, iface) {
473
+ const multi = await loadMultiManifest(name);
474
+ const declared = Object.keys(multi.interfaces);
475
+ if (declared.length === 0) {
476
+ process.stderr.write(`Extension "${name}" has no interfaces declared
477
+ `);
478
+ process.exit(1);
479
+ }
480
+ const target = iface ?? declared[0];
481
+ if (!multi.interfaces[target]) {
482
+ process.stderr.write(
483
+ `Extension "${name}" does not declare a '${target}' interface (declared: ${declared.join(", ")}).
484
+ `
485
+ );
486
+ process.stderr.write(`Try: godmode ${declared[0]} ${name} --help
487
+ `);
488
+ process.exit(1);
489
+ }
490
+ return projectManifest(multi, target);
491
+ }
492
+
493
+ export {
494
+ validateGraphQLFlags,
495
+ GODMODE_HOME,
496
+ GODMODE_EXTENSIONS_DIR,
497
+ addApi,
498
+ updateApi,
499
+ removeApi,
500
+ listApis,
501
+ loadMultiManifest,
502
+ loadManifest
503
+ };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/protocols/mcp.ts
3
+ // ../../interfaces/mcp/src/index.ts
4
4
  var requestId = 0;
5
5
  async function mcpRequest(url, method, params, headers, sessionId) {
6
6
  const body = {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/request.ts
3
+ // ../../interfaces/api/src/request.ts
4
4
  async function executeToString(manifest, match, options) {
5
5
  const baseUrl = manifest.config.url;
6
6
  if (!baseUrl) {
@@ -16,9 +16,9 @@ async function executeToString(manifest, match, options) {
16
16
  }
17
17
  const headers = { ...manifest.config.headers, ...options.headers };
18
18
  const authConfig = manifest.config.auth;
19
- const token = options.token || (authConfig?.env ? process.env[authConfig.env] : void 0);
19
+ const token = authConfig?.env ? process.env[authConfig.env] : void 0;
20
20
  if (authConfig?.env && !token && !options.dryRun) {
21
- throw new Error(`Missing ${authConfig.env}. Set it in .env or environment, or use --token.`);
21
+ throw new Error(`Missing ${authConfig.env}. Set it in .env or export it in your shell.`);
22
22
  }
23
23
  if (token) {
24
24
  const authType = authConfig?.type || "bearer";