godmode 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ explainPermission,
4
+ matchRoute,
5
+ resourceFromRawPath,
6
+ resourceFromSegments,
7
+ suggestedAllowRule
8
+ } from "./chunk-4EU5JX6C.js";
9
+ import {
10
+ EXIT_CODES
11
+ } from "./chunk-XFFBFBN6.js";
12
+ import {
13
+ warnSettingsErrors
14
+ } from "./chunk-SMQBMG24.js";
15
+ import {
16
+ loadManifest
17
+ } from "./chunk-KHFZI7IA.js";
18
+ import "./chunk-3AQ2CZKC.js";
19
+ import "./chunk-YDXTIN53.js";
20
+
21
+ // src/commands/permissions.ts
22
+ function showPermissionsHelp() {
23
+ console.log(`Inspect godmode permission policy.
24
+
25
+ Usage:
26
+ godmode permissions list
27
+ godmode permissions explain <extension> <interface> <target> [method]
28
+
29
+ Exit codes:
30
+ 0 success / allowed
31
+ 2 usage or parse error
32
+ 3 extension or route not found
33
+ 4 permission denied
34
+ 10 upstream HTTP 4xx
35
+ 11 upstream HTTP 5xx
36
+ 12 upstream failure`);
37
+ }
38
+ async function runPermissions(rest) {
39
+ const cmd = rest[0];
40
+ if (!cmd || cmd === "--help" || cmd === "-h") {
41
+ warnSettingsErrors();
42
+ showPermissionsHelp();
43
+ return;
44
+ }
45
+ if (cmd === "list") {
46
+ const { loadSettingsDetailed } = await import("./settings-3FF5WWEY.js");
47
+ const loaded = loadSettingsDetailed();
48
+ for (const error of loaded.errors) {
49
+ process.stderr.write(`Warning: cannot parse ${error.path}: ${error.message}
50
+ `);
51
+ }
52
+ const sources = loaded.sources.flatMap((source) => {
53
+ const extensions = source.settings.extensions ?? {};
54
+ return Object.entries(extensions).flatMap(([slug, settings]) => {
55
+ const permissions = settings.permissions;
56
+ if (!permissions?.allow?.length && !permissions?.deny?.length) return [];
57
+ return [{ slug, permissions, origin: `${source.scope}:${source.path}` }];
58
+ });
59
+ });
60
+ if (!sources.length) {
61
+ console.log("No permissions configured.");
62
+ return;
63
+ }
64
+ for (const { slug, permissions, origin } of sources) {
65
+ console.log(`${slug}:`);
66
+ for (const effect of ["allow", "deny"]) {
67
+ for (const rule of permissions[effect] ?? []) {
68
+ const resources = rule.resources?.join(", ") || "*";
69
+ const methods = rule.methods?.join(", ") || "*";
70
+ console.log(` ${effect} resources=[${resources}] methods=[${methods}] origin=${origin}`);
71
+ }
72
+ }
73
+ }
74
+ return;
75
+ }
76
+ if (cmd === "explain") {
77
+ const [extension, iface, target, maybeMethod] = rest.slice(1);
78
+ if (!extension || !iface || !target) {
79
+ process.stderr.write("Usage: godmode permissions explain <extension> <interface> <target> [method]\n");
80
+ process.exit(EXIT_CODES.usage);
81
+ }
82
+ const method = iface === "mcp" ? "mcp" : maybeMethod || "GET";
83
+ const resource = await explainResource(extension, iface, target, method);
84
+ const decision = explainPermission({ extension, resource, method });
85
+ console.log(`${decision.allowed ? "allow" : "deny"} ${extension} ${iface} ${target} ${method.toUpperCase()}`);
86
+ if (decision.reason) console.log(decision.reason);
87
+ if (decision.rule) {
88
+ console.log(`winning rule: resources=[${decision.rule.resources?.join(", ") || "*"}] methods=[${decision.rule.methods?.join(", ") || "*"}] origin=${decision.origin || "settings.yaml"}`);
89
+ }
90
+ if (!decision.allowed) {
91
+ console.log(`suggested allow rule:
92
+ ${suggestedAllowRule({ extension, resource, method })}`);
93
+ process.exit(EXIT_CODES.permissionDenied);
94
+ }
95
+ return;
96
+ }
97
+ process.stderr.write(`Unknown permissions command '${cmd}'.
98
+ `);
99
+ process.stderr.write(`Try 'godmode permissions --help' for more information.
100
+ `);
101
+ process.exit(EXIT_CODES.usage);
102
+ }
103
+ async function explainResource(extension, iface, target, method) {
104
+ if (iface === "mcp") return target;
105
+ if (iface === "api" || iface === "graphql") {
106
+ try {
107
+ const manifest = await loadManifest(extension, iface);
108
+ const normalizedMethod = method.toLowerCase();
109
+ if (target.startsWith("/")) {
110
+ const exact = manifest.routes.find((route) => route.method === normalizedMethod && route.path === target);
111
+ if (exact) return resourceFromSegments(exact.segments);
112
+ }
113
+ const segments = target.replace(/^\/+/, "").split(/[/.]/).filter(Boolean);
114
+ const match = matchRoute(manifest, segments, normalizedMethod);
115
+ if (match) return resourceFromSegments(match.route.segments);
116
+ return resourceFromRawPath(target);
117
+ } catch {
118
+ return target.replace(/^\/+/, "").replace(/\//g, ".").replace(/^v\d+\./i, "") || "*";
119
+ }
120
+ }
121
+ return target.replace(/^\/+/, "").replace(/\//g, ".").replace(/^v\d+\./i, "") || "*";
122
+ }
123
+ export {
124
+ runPermissions
125
+ };
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/prompt.ts
4
4
  import prompts from "prompts";
5
- import { writeFileSync } from "fs";
5
+ import { mkdirSync, writeFileSync } from "fs";
6
6
  import { resolve } from "path";
7
7
  import { stringify } from "yaml";
8
8
  async function configWizard() {
@@ -60,11 +60,15 @@ async function configWizard() {
60
60
  } });
61
61
  const config = {
62
62
  name: response.name.charAt(0).toUpperCase() + response.name.slice(1),
63
- type: "api",
64
- spec: response.spec
63
+ slug: response.name.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
64
+ interfaces: {
65
+ api: {
66
+ spec: response.spec
67
+ }
68
+ }
65
69
  };
66
70
  if (response.description) config.description = response.description;
67
- if (response.url) config.url = response.url;
71
+ if (response.url) config.interfaces.api.url = response.url;
68
72
  if (response.envVar) {
69
73
  config.auth = { env: response.envVar };
70
74
  if (response.authType && response.authType !== "bearer") {
@@ -72,7 +76,8 @@ async function configWizard() {
72
76
  }
73
77
  }
74
78
  const yaml = stringify(config);
75
- const filename = `${response.name.toLowerCase().replace(/[^a-z0-9]/g, "-")}.yaml`;
79
+ const dirname = response.name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
80
+ const filename = `${dirname}/manifest.yaml`;
76
81
  console.log(`
77
82
  \x1B[2m${yaml}\x1B[0m`);
78
83
  const { write } = await prompts({
@@ -86,10 +91,11 @@ async function configWizard() {
86
91
  return;
87
92
  }
88
93
  const filepath = resolve(process.cwd(), filename);
94
+ mkdirSync(resolve(process.cwd(), dirname), { recursive: true });
89
95
  writeFileSync(filepath, yaml);
90
96
  console.log(`
91
97
  Saved ${filepath}`);
92
- console.log(`Run \x1B[1mgodmode extension add ${response.name.toLowerCase()}\x1B[0m to register it.`);
98
+ console.log(`Run \x1B[1mgodmode ext install ./${dirname}\x1B[0m to register it.`);
93
99
  }
94
100
  export {
95
101
  configWizard
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  executeToString
4
- } from "./chunk-GJCJO7YT.js";
4
+ } from "./chunk-AWGLXW3B.js";
5
5
  import {
6
6
  executeMcpTool
7
- } from "./chunk-EXWYJU2I.js";
7
+ } from "./chunk-3AQ2CZKC.js";
8
+ import "./chunk-YDXTIN53.js";
8
9
 
9
10
  // ../../interfaces/mcp/src/server.ts
10
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -98,6 +99,15 @@ async function serveMcp(manifest, options = {}) {
98
99
  }
99
100
  try {
100
101
  let result;
102
+ const extension = manifest.config.slug || manifest.name;
103
+ const resource = type === "mcp" ? resourceFromTool(toolName) : resourceFromSegments(tool.route.segments);
104
+ const method = type === "mcp" ? "mcp" : tool.route.method;
105
+ if (options.checkPermission) {
106
+ const check = options.checkPermission({ extension, resource, method });
107
+ if (!check.allowed) {
108
+ return { content: [{ type: "text", text: `Blocked: ${check.reason}` }], isError: true };
109
+ }
110
+ }
101
111
  if (type === "mcp") {
102
112
  result = await executeMcpTool(manifest.config, toolName, args, {});
103
113
  } else if (type === "graphql") {
@@ -124,6 +134,13 @@ async function serveMcp(manifest, options = {}) {
124
134
  });
125
135
  await server.connect(new StdioServerTransport());
126
136
  }
137
+ function resourceFromSegments(segments) {
138
+ const parts = segments.filter((s) => !s.isParam).map((s) => s.value).filter((v) => !/^v\d+$/i.test(v));
139
+ return parts.join(".") || "*";
140
+ }
141
+ function resourceFromTool(toolName) {
142
+ return toolName;
143
+ }
127
144
  export {
128
145
  serveMcp
129
146
  };
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ extensionSettings,
4
+ loadSettings,
5
+ loadSettingsDetailed,
6
+ resetSettingsCache,
7
+ settingsErrorMessage,
8
+ warnSettingsErrors
9
+ } from "./chunk-SMQBMG24.js";
10
+ export {
11
+ extensionSettings,
12
+ loadSettings,
13
+ loadSettingsDetailed,
14
+ resetSettingsCache,
15
+ settingsErrorMessage,
16
+ warnSettingsErrors
17
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "godmode",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "any API as CLI. any API as MCP. zero code generation.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,6 +26,7 @@
26
26
  "exports": {
27
27
  "./spec": "./src/spec.ts",
28
28
  "./config": "./src/config.ts",
29
+ "./permissions": "./src/permissions.ts",
29
30
  "./help": "./src/help.ts"
30
31
  },
31
32
  "files": [
@@ -34,23 +35,23 @@
34
35
  "README.md"
35
36
  ],
36
37
  "dependencies": {
37
- "@hey-api/openapi-ts": "^0.94.0",
38
- "@modelcontextprotocol/sdk": "^1.12.1",
38
+ "@hey-api/openapi-ts": "^0.97.0",
39
+ "@modelcontextprotocol/sdk": "^1.29.0",
39
40
  "fuzzysort": "^3.1.0",
40
41
  "prompts": "^2.4.2",
41
- "yaml": "^2.8.0",
42
+ "yaml": "^2.8.4",
42
43
  "@godmode-cli/cli": "0.0.1",
43
44
  "@godmode-cli/command-agent": "0.0.1",
44
- "@godmode-cli/interface-graphql": "0.0.1",
45
45
  "@godmode-cli/interface-api": "0.0.1",
46
+ "@godmode-cli/interface-graphql": "0.0.1",
46
47
  "@godmode-cli/interface-mcp": "0.0.1"
47
48
  },
48
49
  "devDependencies": {
49
- "@types/node": "^22.0.0",
50
+ "@types/node": "^25.6.0",
50
51
  "@types/prompts": "^2.4.9",
51
- "tsup": "^8.0.0",
52
- "typescript": "^5.5.0",
53
- "vitest": "^4.1.0"
52
+ "tsup": "^8.5.1",
53
+ "typescript": "^6.0.3",
54
+ "vitest": "^4.1.5"
54
55
  },
55
56
  "engines": {
56
57
  "node": ">=20.0.0"