godmode 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/{add-ZRNIX6DM.js → add-D74TIN3J.js} +14 -11
- package/dist/{chunk-EHS56XXY.js → chunk-3AQ2CZKC.js} +6 -5
- package/dist/chunk-4EU5JX6C.js +160 -0
- package/dist/{chunk-I4WS4MMI.js → chunk-AWGLXW3B.js} +27 -14
- package/dist/chunk-KHFZI7IA.js +745 -0
- package/dist/chunk-SMQBMG24.js +102 -0
- package/dist/chunk-XFFBFBN6.js +16 -0
- package/dist/chunk-YDXTIN53.js +212 -0
- package/dist/chunk-ZTPILA7B.js +403 -0
- package/dist/dist-I5CEQ4AC.js +779 -0
- package/dist/ext-K4XEAZGS.js +102 -0
- package/dist/index.js +340 -469
- package/dist/permissions-E4G66OUZ.js +125 -0
- package/dist/{prompt-DCFFOQ7K.js → prompt-YPAZ5433.js} +12 -6
- package/dist/{mcp-server.js → server-L3L4TKK3.js} +20 -3
- package/dist/settings-3FF5WWEY.js +17 -0
- package/package.json +34 -15
- package/dist/chunk-FBMQ3AC4.js +0 -339
- package/dist/mcp-NZIEMQOM.js +0 -58
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,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
addApi
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-KHFZI7IA.js";
|
|
5
|
+
import "./chunk-3AQ2CZKC.js";
|
|
6
|
+
import "./chunk-YDXTIN53.js";
|
|
6
7
|
|
|
7
8
|
// src/commands/add.ts
|
|
8
|
-
async function runAdd(args) {
|
|
9
|
+
async function runAdd(args, scope = "project") {
|
|
9
10
|
if (!args[0] || args[0] === "--help" || args[0] === "-h") {
|
|
10
|
-
console.log(`Add an
|
|
11
|
+
console.log(`Add an extension from a folder, built-in name, or manifest.
|
|
11
12
|
|
|
12
13
|
Usage:
|
|
13
|
-
godmode
|
|
14
|
-
godmode
|
|
14
|
+
godmode ext install <name> Built-in extension (e.g. stripe, github)
|
|
15
|
+
godmode ext install <folder> Folder containing manifest.yaml
|
|
16
|
+
godmode ext install <manifest> Direct path to manifest.yaml/json
|
|
15
17
|
|
|
16
18
|
Manifest format (manifest.yaml):
|
|
17
|
-
slug: stripe CLI name
|
|
19
|
+
slug: stripe CLI name
|
|
18
20
|
name: Stripe Display name
|
|
19
21
|
description: Payments API Short description
|
|
20
22
|
type: api api | graphql | mcp
|
|
@@ -32,12 +34,13 @@ Types:
|
|
|
32
34
|
mcp MCP endpoint (requires url)
|
|
33
35
|
|
|
34
36
|
Examples:
|
|
35
|
-
$ godmode
|
|
36
|
-
$ godmode
|
|
37
|
-
$ godmode
|
|
37
|
+
$ godmode ext install stripe
|
|
38
|
+
$ godmode ext install ./my-extension
|
|
39
|
+
$ godmode ext install ./my-extension/manifest.yaml
|
|
40
|
+
$ godmode ext install openai`);
|
|
38
41
|
process.exit(args[0] ? 0 : 1);
|
|
39
42
|
}
|
|
40
|
-
await addApi(args[0]);
|
|
43
|
+
await addApi(args[0], scope);
|
|
41
44
|
}
|
|
42
45
|
export {
|
|
43
46
|
runAdd
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AuthStrategy
|
|
4
|
+
} from "./chunk-YDXTIN53.js";
|
|
2
5
|
|
|
3
|
-
// src/
|
|
6
|
+
// ../../interfaces/mcp/src/index.ts
|
|
4
7
|
var requestId = 0;
|
|
5
8
|
async function mcpRequest(url, method, params, headers, sessionId) {
|
|
6
9
|
const body = {
|
|
@@ -53,9 +56,7 @@ function buildAuthHeaders(config) {
|
|
|
53
56
|
const headers = { ...config.headers };
|
|
54
57
|
const token = config.auth?.env ? process.env[config.auth.env] : void 0;
|
|
55
58
|
if (token) {
|
|
56
|
-
|
|
57
|
-
if (authType === "bearer") headers["Authorization"] = `Bearer ${token}`;
|
|
58
|
-
else if (authType === "api-key") headers[config.auth?.header || "X-API-Key"] = token;
|
|
59
|
+
AuthStrategy.for(config.auth).apply(headers, token);
|
|
59
60
|
}
|
|
60
61
|
return headers;
|
|
61
62
|
}
|
|
@@ -120,7 +121,7 @@ function validateMcpFlags(method, query) {
|
|
|
120
121
|
}
|
|
121
122
|
async function executeMcpTool(config, toolName, args, options) {
|
|
122
123
|
const headers = buildAuthHeaders(config);
|
|
123
|
-
if (options.dryRun || options.
|
|
124
|
+
if (options.dryRun || options.debug) {
|
|
124
125
|
process.stderr.write(`CALL ${config.url} \u2192 ${toolName}
|
|
125
126
|
`);
|
|
126
127
|
if (Object.keys(args).length) {
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadSettingsDetailed,
|
|
4
|
+
settingsErrorMessage
|
|
5
|
+
} from "./chunk-SMQBMG24.js";
|
|
6
|
+
|
|
7
|
+
// src/permissions.ts
|
|
8
|
+
function checkPermission(input) {
|
|
9
|
+
const loaded = loadSettingsDetailed();
|
|
10
|
+
const settingsError = loaded.errors[0];
|
|
11
|
+
if (settingsError) {
|
|
12
|
+
return {
|
|
13
|
+
allowed: false,
|
|
14
|
+
reason: settingsErrorMessage(settingsError),
|
|
15
|
+
defaultDenied: true
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const blocks = loaded.sources.map((source) => ({
|
|
19
|
+
origin: `${source.scope}:${source.path}`,
|
|
20
|
+
permissions: source.settings.extensions?.[input.extension]?.permissions
|
|
21
|
+
})).filter((source) => !!source.permissions);
|
|
22
|
+
const hasAny = blocks.some(({ permissions }) => !!(permissions?.allow?.length || permissions?.deny?.length));
|
|
23
|
+
if (!hasAny) return { allowed: true };
|
|
24
|
+
for (const block of blocks) {
|
|
25
|
+
for (const rule of block.permissions?.deny ?? []) {
|
|
26
|
+
if (ruleMatches(rule, input)) {
|
|
27
|
+
return {
|
|
28
|
+
allowed: false,
|
|
29
|
+
reason: `denied by permissions for ${input.extension}: ${describe(rule, input, "deny")}`,
|
|
30
|
+
rule,
|
|
31
|
+
origin: block.origin
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const block of blocks) {
|
|
37
|
+
for (const rule of block.permissions?.allow ?? []) {
|
|
38
|
+
if (ruleMatches(rule, input)) return { allowed: true, rule, origin: block.origin };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
allowed: false,
|
|
43
|
+
reason: `no allow rule matches ${input.resource} ${input.method.toUpperCase()} for ${input.extension}`,
|
|
44
|
+
defaultDenied: true
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function explainPermission(input) {
|
|
48
|
+
return { ...checkPermission(input), input };
|
|
49
|
+
}
|
|
50
|
+
function suggestedAllowRule(input) {
|
|
51
|
+
return [
|
|
52
|
+
"permissions:",
|
|
53
|
+
" allow:",
|
|
54
|
+
` - resources: [${input.resource}]`,
|
|
55
|
+
` methods: [${input.method.toUpperCase()}]`
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
58
|
+
function ruleMatches(rule, input) {
|
|
59
|
+
const resources = rule.resources ?? ["*"];
|
|
60
|
+
const resourceOk = resources.some((pattern) => matchResource(pattern, input.resource));
|
|
61
|
+
if (!resourceOk) return false;
|
|
62
|
+
if (!rule.methods || rule.methods.length === 0) return true;
|
|
63
|
+
return rule.methods.some((m) => m.toLowerCase() === input.method.toLowerCase());
|
|
64
|
+
}
|
|
65
|
+
function matchResource(pattern, resource) {
|
|
66
|
+
if (pattern === "*" || pattern === resource) return true;
|
|
67
|
+
const pp = pattern.split(".");
|
|
68
|
+
const rp = resource.split(".");
|
|
69
|
+
if (pp[pp.length - 1] === "*" && rp.length >= pp.length) {
|
|
70
|
+
for (let i = 0; i < pp.length - 1; i++) {
|
|
71
|
+
if (pp[i] !== "*" && pp[i] !== rp[i]) return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (pp.length !== rp.length) return false;
|
|
76
|
+
for (let i = 0; i < pp.length; i++) {
|
|
77
|
+
if (pp[i] !== "*" && pp[i] !== rp[i]) return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
function describe(rule, input, effect) {
|
|
82
|
+
const res = (rule.resources ?? ["*"]).join(", ");
|
|
83
|
+
const mth = rule.methods?.join(", ") || "any method";
|
|
84
|
+
return `${effect} ${res} [${mth}] while acting on ${input.resource} ${input.method.toUpperCase()}`;
|
|
85
|
+
}
|
|
86
|
+
function resourceFromSegments(segments) {
|
|
87
|
+
const parts = segments.filter((s) => !s.isParam).map((s) => s.value).filter((v) => !/^v\d+$/i.test(v));
|
|
88
|
+
return parts.join(".") || "*";
|
|
89
|
+
}
|
|
90
|
+
function resourceFromTool(toolName) {
|
|
91
|
+
return toolName;
|
|
92
|
+
}
|
|
93
|
+
function resourceFromRawPath(path) {
|
|
94
|
+
const parts = path.split("/").filter(Boolean).filter((v) => !/^v\d+$/i.test(v));
|
|
95
|
+
return parts.join(".") || "*";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ../../interfaces/api/src/match.ts
|
|
99
|
+
function matchRoute(manifest, userSegments, method) {
|
|
100
|
+
if (userSegments.length > 0) {
|
|
101
|
+
const ver = manifest.versions.find((v) => v.name === userSegments[0]);
|
|
102
|
+
if (ver) {
|
|
103
|
+
const match = findMatch(manifest.routes, userSegments.slice(1), method, ver.name);
|
|
104
|
+
if (match) return match;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return findMatch(manifest.routes, userSegments, method);
|
|
108
|
+
}
|
|
109
|
+
function findMatch(routes, userSegments, method, version) {
|
|
110
|
+
let bestMatch = null;
|
|
111
|
+
let bestScore = -1;
|
|
112
|
+
for (const route of routes) {
|
|
113
|
+
if (route.method !== method) continue;
|
|
114
|
+
if (route.segments.length !== userSegments.length) continue;
|
|
115
|
+
if (version !== void 0 && route.version !== version) continue;
|
|
116
|
+
let score = 0;
|
|
117
|
+
const params = {};
|
|
118
|
+
let matches = true;
|
|
119
|
+
for (let i = 0; i < route.segments.length; i++) {
|
|
120
|
+
const seg = route.segments[i];
|
|
121
|
+
const userSeg = userSegments[i];
|
|
122
|
+
if (seg.isParam) {
|
|
123
|
+
params[seg.value] = userSeg;
|
|
124
|
+
} else if (seg.value === userSeg) {
|
|
125
|
+
score += 1;
|
|
126
|
+
} else {
|
|
127
|
+
matches = false;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (matches && (score > bestScore || score === bestScore && isLaterVersion(route, bestMatch?.route))) {
|
|
132
|
+
bestScore = score;
|
|
133
|
+
bestMatch = { route, params };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return bestMatch;
|
|
137
|
+
}
|
|
138
|
+
function isLaterVersion(a, b) {
|
|
139
|
+
if (!b) return true;
|
|
140
|
+
return a.version > b.version;
|
|
141
|
+
}
|
|
142
|
+
function suggestRoutes(manifest, userSegments) {
|
|
143
|
+
if (!userSegments.length) return [];
|
|
144
|
+
return manifest.routes.filter((route) => {
|
|
145
|
+
if (route.segments.length < userSegments.length) return false;
|
|
146
|
+
const firstSeg = route.segments[0];
|
|
147
|
+
return !firstSeg.isParam && firstSeg.value === userSegments[0];
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export {
|
|
152
|
+
checkPermission,
|
|
153
|
+
explainPermission,
|
|
154
|
+
suggestedAllowRule,
|
|
155
|
+
resourceFromSegments,
|
|
156
|
+
resourceFromTool,
|
|
157
|
+
resourceFromRawPath,
|
|
158
|
+
matchRoute,
|
|
159
|
+
suggestRoutes
|
|
160
|
+
};
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AuthStrategy
|
|
4
|
+
} from "./chunk-YDXTIN53.js";
|
|
2
5
|
|
|
3
|
-
// src/request.ts
|
|
6
|
+
// ../../interfaces/api/src/request.ts
|
|
7
|
+
var HttpStatusError = class extends Error {
|
|
8
|
+
constructor(status, statusText, body) {
|
|
9
|
+
super(body || `${status} ${statusText}`);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.statusText = statusText;
|
|
12
|
+
}
|
|
13
|
+
status;
|
|
14
|
+
statusText;
|
|
15
|
+
};
|
|
4
16
|
async function executeToString(manifest, match, options) {
|
|
5
17
|
const baseUrl = manifest.config.url;
|
|
6
18
|
if (!baseUrl) {
|
|
@@ -16,25 +28,18 @@ async function executeToString(manifest, match, options) {
|
|
|
16
28
|
}
|
|
17
29
|
const headers = { ...manifest.config.headers, ...options.headers };
|
|
18
30
|
const authConfig = manifest.config.auth;
|
|
19
|
-
const token =
|
|
31
|
+
const token = authConfig?.env ? process.env[authConfig.env] : void 0;
|
|
20
32
|
if (authConfig?.env && !token && !options.dryRun) {
|
|
21
|
-
throw new Error(`Missing ${authConfig.env}. Set it in .env or
|
|
33
|
+
throw new Error(`Missing ${authConfig.env}. Set it in .env or export it in your shell.`);
|
|
22
34
|
}
|
|
23
35
|
if (token) {
|
|
24
|
-
|
|
25
|
-
if (authType === "bearer") {
|
|
26
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
27
|
-
} else if (authType === "api-key") {
|
|
28
|
-
headers[authConfig?.header || "X-API-Key"] = token;
|
|
29
|
-
} else if (authType === "basic") {
|
|
30
|
-
headers["Authorization"] = `Basic ${token}`;
|
|
31
|
-
}
|
|
36
|
+
AuthStrategy.for(authConfig).apply(headers, token);
|
|
32
37
|
}
|
|
33
38
|
if (options.body && !headers["Content-Type"]) {
|
|
34
39
|
headers["Content-Type"] = "application/json";
|
|
35
40
|
}
|
|
36
41
|
const method = match.route.method.toUpperCase();
|
|
37
|
-
if (options.dryRun || options.
|
|
42
|
+
if (options.dryRun || options.debug) {
|
|
38
43
|
process.stderr.write(`${method} ${url.toString()}
|
|
39
44
|
`);
|
|
40
45
|
for (const [k, v] of Object.entries(headers)) {
|
|
@@ -54,7 +59,7 @@ async function executeToString(manifest, match, options) {
|
|
|
54
59
|
headers,
|
|
55
60
|
body: options.body || void 0
|
|
56
61
|
});
|
|
57
|
-
if (options.
|
|
62
|
+
if (options.debug) {
|
|
58
63
|
process.stderr.write(`${response.status} ${response.statusText}
|
|
59
64
|
`);
|
|
60
65
|
response.headers.forEach((v, k) => process.stderr.write(` ${k}: ${v}
|
|
@@ -73,7 +78,7 @@ async function executeToString(manifest, match, options) {
|
|
|
73
78
|
} else {
|
|
74
79
|
result = text;
|
|
75
80
|
}
|
|
76
|
-
if (!response.ok) throw new
|
|
81
|
+
if (!response.ok) throw new HttpStatusError(response.status, response.statusText, result);
|
|
77
82
|
return result;
|
|
78
83
|
}
|
|
79
84
|
async function execute(manifest, match, options) {
|
|
@@ -81,6 +86,14 @@ async function execute(manifest, match, options) {
|
|
|
81
86
|
const result = await executeToString(manifest, match, options);
|
|
82
87
|
if (result) process.stdout.write(result + "\n");
|
|
83
88
|
} catch (err) {
|
|
89
|
+
if (err instanceof HttpStatusError) {
|
|
90
|
+
process.stderr.write(`${err.status} ${err.statusText}
|
|
91
|
+
`);
|
|
92
|
+
if (err.message) process.stderr.write(err.message + "\n");
|
|
93
|
+
if (err.status >= 400 && err.status < 500) process.exit(10);
|
|
94
|
+
if (err.status >= 500) process.exit(11);
|
|
95
|
+
process.exit(12);
|
|
96
|
+
}
|
|
84
97
|
process.stderr.write(err.message + "\n");
|
|
85
98
|
process.exit(1);
|
|
86
99
|
}
|