gemini-mcp-bridge 0.1.0
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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/ping.d.ts +14 -0
- package/dist/tools/ping.d.ts.map +1 -0
- package/dist/tools/ping.js +66 -0
- package/dist/tools/ping.js.map +1 -0
- package/dist/tools/query.d.ts +20 -0
- package/dist/tools/query.d.ts.map +1 -0
- package/dist/tools/query.js +67 -0
- package/dist/tools/query.js.map +1 -0
- package/dist/tools/review.d.ts +18 -0
- package/dist/tools/review.d.ts.map +1 -0
- package/dist/tools/review.js +93 -0
- package/dist/tools/review.js.map +1 -0
- package/dist/utils/env.d.ts +7 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +34 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/files.d.ts +15 -0
- package/dist/utils/files.d.ts.map +1 -0
- package/dist/utils/files.js +42 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/git.d.ts +14 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +57 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/parse.d.ts +15 -0
- package/dist/utils/parse.d.ts.map +1 -0
- package/dist/utils/parse.js +77 -0
- package/dist/utils/parse.js.map +1 -0
- package/dist/utils/security.d.ts +18 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +37 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/utils/spawn.d.ts +26 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +166 -0
- package/dist/utils/spawn.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tim Birrell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# gemini-mcp-bridge
|
|
2
|
+
|
|
3
|
+
MCP server that wraps [Gemini CLI](https://github.com/google-gemini/gemini-cli) as a subprocess, exposing its best features as [Model Context Protocol](https://modelcontextprotocol.io/) tools.
|
|
4
|
+
|
|
5
|
+
Works with any MCP client: Claude Code, Codex CLI, Cursor, Windsurf, VS Code, or any tool that speaks MCP.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
| **query** | Send a prompt to Gemini with optional file context. The CLI reads your GEMINI.md for project context automatically. |
|
|
12
|
+
| **review** | Code review via git diff. Computes the diff locally and asks Gemini for structured findings (severity, file, line, suggestion). |
|
|
13
|
+
| **ping** | Health check. Verifies CLI is installed and authenticated, reports versions and capabilities. |
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i -g @google/gemini-cli
|
|
19
|
+
gemini auth login
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### Claude Code
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
claude mcp add gemini -s user -- npx -y gemini-mcp-bridge
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Codex CLI
|
|
31
|
+
|
|
32
|
+
Add to `~/.codex/config.json`:
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"gemini": {
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["-y", "gemini-mcp-bridge"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Cursor / Windsurf / VS Code
|
|
45
|
+
|
|
46
|
+
Add to your MCP settings:
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"gemini": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "gemini-mcp-bridge"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tool Reference
|
|
57
|
+
|
|
58
|
+
### query
|
|
59
|
+
|
|
60
|
+
Send a prompt to Gemini, optionally including file contents.
|
|
61
|
+
|
|
62
|
+
| Parameter | Type | Default | Description |
|
|
63
|
+
|-----------|------|---------|-------------|
|
|
64
|
+
| `prompt` | string | *required* | The prompt to send |
|
|
65
|
+
| `files` | string[] | `[]` | File paths (relative to workingDirectory) to include |
|
|
66
|
+
| `model` | string | CLI default | Model to use (e.g. `gemini-2.5-flash`) |
|
|
67
|
+
| `workingDirectory` | string | cwd | Working directory (CLI reads GEMINI.md from here) |
|
|
68
|
+
| `timeout` | number | 60000 | Timeout in ms (max 600000) |
|
|
69
|
+
|
|
70
|
+
### review
|
|
71
|
+
|
|
72
|
+
Code review via git diff sent to Gemini.
|
|
73
|
+
|
|
74
|
+
| Parameter | Type | Default | Description |
|
|
75
|
+
|-----------|------|---------|-------------|
|
|
76
|
+
| `uncommitted` | boolean | `true` | Review uncommitted changes (staged + unstaged) |
|
|
77
|
+
| `base` | string | — | Base branch to diff against (e.g. `main`) |
|
|
78
|
+
| `workingDirectory` | string | cwd | Repository directory (auto-resolves to git root) |
|
|
79
|
+
| `timeout` | number | 120000 | Timeout in ms (max 600000) |
|
|
80
|
+
|
|
81
|
+
### ping
|
|
82
|
+
|
|
83
|
+
Health check with no parameters. Returns CLI version, auth status, and server info.
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
Environment variables:
|
|
88
|
+
|
|
89
|
+
| Variable | Default | Description |
|
|
90
|
+
|----------|---------|-------------|
|
|
91
|
+
| `GEMINI_CLI_PATH` | `gemini` | Path to gemini CLI binary |
|
|
92
|
+
| `GEMINI_MAX_CONCURRENT` | `3` | Max concurrent subprocess spawns |
|
|
93
|
+
|
|
94
|
+
## Security
|
|
95
|
+
|
|
96
|
+
- **Environment isolation**: Subprocess receives a minimal env allowlist (HOME, PATH, GOOGLE_*, GEMINI_*). Your API keys, tokens, and credentials are not leaked.
|
|
97
|
+
- **Path sandboxing**: All file paths are resolved via `realpath` and verified within the working directory. No path traversal via `..` or symlinks.
|
|
98
|
+
- **No shell execution**: Subprocess spawned with `shell: false` and args as an array. No command injection.
|
|
99
|
+
- **Resource limits**: Max 3 concurrent spawns (configurable), 600s hard timeout cap, 1MB per-file size limit, 20 files max.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { executeQuery } from "./tools/query.js";
|
|
7
|
+
import { executeReview } from "./tools/review.js";
|
|
8
|
+
import { executePing } from "./tools/ping.js";
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const { version: PKG_VERSION } = require("../package.json");
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: "gemini-mcp-bridge",
|
|
13
|
+
version: PKG_VERSION,
|
|
14
|
+
});
|
|
15
|
+
// --- query tool ---
|
|
16
|
+
server.tool("query", "Send a prompt to Gemini CLI with optional file context. The CLI reads GEMINI.md for project context automatically.", {
|
|
17
|
+
prompt: z.string().describe("The prompt to send to Gemini"),
|
|
18
|
+
files: z
|
|
19
|
+
.array(z.string())
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("File paths (relative to workingDirectory) to include in the prompt"),
|
|
22
|
+
model: z.string().optional().describe("Gemini model to use (e.g. gemini-2.5-flash, gemini-2.5-pro)"),
|
|
23
|
+
workingDirectory: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Working directory for the CLI (reads GEMINI.md from here)"),
|
|
27
|
+
timeout: z
|
|
28
|
+
.number()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Timeout in milliseconds (default: 60000, max: 600000)"),
|
|
31
|
+
}, async (input) => {
|
|
32
|
+
try {
|
|
33
|
+
const result = await executeQuery(input);
|
|
34
|
+
const meta = [];
|
|
35
|
+
if (result.filesIncluded.length > 0) {
|
|
36
|
+
meta.push(`Files included: ${result.filesIncluded.join(", ")}`);
|
|
37
|
+
}
|
|
38
|
+
if (result.filesSkipped.length > 0) {
|
|
39
|
+
meta.push(`Files skipped: ${result.filesSkipped.join(", ")}`);
|
|
40
|
+
}
|
|
41
|
+
if (result.timedOut) {
|
|
42
|
+
meta.push("(timed out)");
|
|
43
|
+
}
|
|
44
|
+
if (result.model) {
|
|
45
|
+
meta.push(`Model: ${result.model}`);
|
|
46
|
+
}
|
|
47
|
+
const text = meta.length > 0
|
|
48
|
+
? `${result.response}\n\n---\n${meta.join("\n")}`
|
|
49
|
+
: result.response;
|
|
50
|
+
return { content: [{ type: "text", text }] };
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: `Error: ${e.message}` }],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// --- review tool ---
|
|
60
|
+
server.tool("review", "Code review via git diff sent to Gemini. Computes diff locally and asks Gemini for a structured review.", {
|
|
61
|
+
uncommitted: z
|
|
62
|
+
.boolean()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Review uncommitted changes (staged + unstaged). Default: true"),
|
|
65
|
+
base: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Base branch/ref to diff against (e.g. 'main'). Overrides uncommitted."),
|
|
69
|
+
workingDirectory: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Repository directory (auto-resolves to git root)"),
|
|
73
|
+
timeout: z
|
|
74
|
+
.number()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Timeout in milliseconds (default: 120000, max: 600000)"),
|
|
77
|
+
}, async (input) => {
|
|
78
|
+
try {
|
|
79
|
+
const result = await executeReview(input);
|
|
80
|
+
const meta = [
|
|
81
|
+
`Diff source: ${result.diffSource}`,
|
|
82
|
+
];
|
|
83
|
+
if (result.base)
|
|
84
|
+
meta.push(`Base: ${result.base}`);
|
|
85
|
+
if (result.timedOut)
|
|
86
|
+
meta.push("(timed out)");
|
|
87
|
+
return {
|
|
88
|
+
content: [{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: `${result.response}\n\n---\n${meta.join("\n")}`,
|
|
91
|
+
}],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: `Error: ${e.message}` }],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// --- ping tool ---
|
|
102
|
+
server.tool("ping", "Health check: verifies gemini CLI is installed and authenticated, reports versions and capabilities.", {}, async () => {
|
|
103
|
+
try {
|
|
104
|
+
const result = await executePing();
|
|
105
|
+
const lines = [
|
|
106
|
+
`CLI found: ${result.cliFound ? "yes" : "NO — install with: npm i -g @google/gemini-cli"}`,
|
|
107
|
+
`CLI version: ${result.version ?? "unknown"}`,
|
|
108
|
+
`Auth status: ${result.authStatus}`,
|
|
109
|
+
`Server version: ${result.serverVersion}`,
|
|
110
|
+
`Node version: ${result.nodeVersion}`,
|
|
111
|
+
`Max concurrent: ${result.maxConcurrent}`,
|
|
112
|
+
];
|
|
113
|
+
return {
|
|
114
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: `Error: ${e.message}` }],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// --- Start server ---
|
|
125
|
+
async function main() {
|
|
126
|
+
const transport = new StdioServerTransport();
|
|
127
|
+
await server.connect(transport);
|
|
128
|
+
}
|
|
129
|
+
main().catch((e) => {
|
|
130
|
+
console.error("Fatal:", e);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PingResult {
|
|
2
|
+
cliFound: boolean;
|
|
3
|
+
version: string | null;
|
|
4
|
+
authStatus: "ok" | "expired" | "missing" | "unknown";
|
|
5
|
+
serverVersion: string;
|
|
6
|
+
nodeVersion: string;
|
|
7
|
+
maxConcurrent: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Health check and capability detection.
|
|
11
|
+
* Checks if gemini CLI is installed, authenticated, and reports versions.
|
|
12
|
+
*/
|
|
13
|
+
export declare function executePing(): Promise<PingResult>;
|
|
14
|
+
//# sourceMappingURL=ping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ping.d.ts","sourceRoot":"","sources":["../../src/tools/ping.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,IAAI,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,UAAU,CAAC,CA+DvD"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { findGeminiBinary } from "../utils/spawn.js";
|
|
4
|
+
import { buildSubprocessEnv } from "../utils/env.js";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const PKG_VERSION = require("../../package.json").version;
|
|
7
|
+
/**
|
|
8
|
+
* Health check and capability detection.
|
|
9
|
+
* Checks if gemini CLI is installed, authenticated, and reports versions.
|
|
10
|
+
*/
|
|
11
|
+
export async function executePing() {
|
|
12
|
+
const binary = findGeminiBinary();
|
|
13
|
+
const maxConcurrent = parseInt(process.env["GEMINI_MAX_CONCURRENT"] ?? "3", 10);
|
|
14
|
+
// Try to get CLI version
|
|
15
|
+
let cliFound = false;
|
|
16
|
+
let version = null;
|
|
17
|
+
try {
|
|
18
|
+
const output = execFileSync(binary, ["--version"], {
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
timeout: 10_000,
|
|
21
|
+
}).trim();
|
|
22
|
+
cliFound = true;
|
|
23
|
+
version = output;
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
const err = e;
|
|
27
|
+
if (err.code === "ENOENT") {
|
|
28
|
+
return {
|
|
29
|
+
cliFound: false,
|
|
30
|
+
version: null,
|
|
31
|
+
authStatus: "missing",
|
|
32
|
+
serverVersion: PKG_VERSION,
|
|
33
|
+
nodeVersion: process.version,
|
|
34
|
+
maxConcurrent,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// CLI found but --version failed? Unusual but possible
|
|
38
|
+
cliFound = true;
|
|
39
|
+
}
|
|
40
|
+
// Check auth status by running a minimal prompt
|
|
41
|
+
let authStatus = "unknown";
|
|
42
|
+
try {
|
|
43
|
+
const result = execFileSync(binary, ["--output-format", "json", "Reply with exactly: OK"], {
|
|
44
|
+
encoding: "utf8",
|
|
45
|
+
timeout: 15_000,
|
|
46
|
+
env: buildSubprocessEnv(),
|
|
47
|
+
});
|
|
48
|
+
// If we got a response, auth is working
|
|
49
|
+
if (result && result.length > 0) {
|
|
50
|
+
authStatus = "ok";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Auth check failed — could be expired or missing credentials
|
|
55
|
+
authStatus = "expired";
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
cliFound,
|
|
59
|
+
version,
|
|
60
|
+
authStatus,
|
|
61
|
+
serverVersion: PKG_VERSION,
|
|
62
|
+
nodeVersion: process.version,
|
|
63
|
+
maxConcurrent,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=ping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ping.js","sourceRoot":"","sources":["../../src/tools/ping.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,WAAW,GAAY,OAAO,CAAC,oBAAoB,CAAyB,CAAC,OAAO,CAAC;AAW3F;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,aAAa,GAAG,QAAQ,CAC5B,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,GAAG,EAC3C,EAAE,CACH,CAAC;IAEF,yBAAyB;IACzB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAO,GAAkB,IAAI,CAAC;IAElC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,EAAE;YACjD,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;SAChB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,QAAQ,GAAG,IAAI,CAAC;QAChB,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAA0B,CAAC;QACvC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC1B,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,SAAS;gBACrB,aAAa,EAAE,WAAW;gBAC1B,WAAW,EAAE,OAAO,CAAC,OAAO;gBAC5B,aAAa;aACd,CAAC;QACJ,CAAC;QACD,uDAAuD;QACvD,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,gDAAgD;IAChD,IAAI,UAAU,GAA6B,SAAS,CAAC;IACrD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CACzB,MAAM,EACN,CAAC,iBAAiB,EAAE,MAAM,EAAE,wBAAwB,CAAC,EACrD;YACE,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,GAAG,EAAE,kBAAkB,EAAE;SAC1B,CACF,CAAC;QACF,wCAAwC;QACxC,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,8DAA8D;QAC9D,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC;IAED,OAAO;QACL,QAAQ;QACR,OAAO;QACP,UAAU;QACV,aAAa,EAAE,WAAW;QAC1B,WAAW,EAAE,OAAO,CAAC,OAAO;QAC5B,aAAa;KACd,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface QueryInput {
|
|
2
|
+
prompt: string;
|
|
3
|
+
files?: string[];
|
|
4
|
+
model?: string;
|
|
5
|
+
workingDirectory?: string;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface QueryResult {
|
|
9
|
+
response: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
filesIncluded: string[];
|
|
12
|
+
filesSkipped: string[];
|
|
13
|
+
timedOut: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Execute a one-shot query against Gemini CLI.
|
|
17
|
+
* Optionally includes file contents in the prompt.
|
|
18
|
+
*/
|
|
19
|
+
export declare function executeQuery(input: QueryInput): Promise<QueryResult>;
|
|
20
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/tools/query.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAQD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAoE1E"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { spawnGemini } from "../utils/spawn.js";
|
|
2
|
+
import { parseGeminiOutput } from "../utils/parse.js";
|
|
3
|
+
import { readFiles, assemblePrompt } from "../utils/files.js";
|
|
4
|
+
import { verifyDirectory } from "../utils/security.js";
|
|
5
|
+
/**
|
|
6
|
+
* Prompt length threshold for using stdin vs positional arg.
|
|
7
|
+
* Positional args are subject to ARG_MAX (~2MB), so pipe large prompts via stdin.
|
|
8
|
+
*/
|
|
9
|
+
const STDIN_THRESHOLD = 4000;
|
|
10
|
+
/**
|
|
11
|
+
* Execute a one-shot query against Gemini CLI.
|
|
12
|
+
* Optionally includes file contents in the prompt.
|
|
13
|
+
*/
|
|
14
|
+
export async function executeQuery(input) {
|
|
15
|
+
const { prompt, files = [], model, timeout } = input;
|
|
16
|
+
// Resolve working directory
|
|
17
|
+
const cwd = input.workingDirectory
|
|
18
|
+
? await verifyDirectory(input.workingDirectory)
|
|
19
|
+
: process.cwd();
|
|
20
|
+
// Read and assemble files into prompt
|
|
21
|
+
const fileContents = files.length > 0 ? await readFiles(files, cwd) : [];
|
|
22
|
+
const fullPrompt = assemblePrompt(prompt, fileContents);
|
|
23
|
+
// Large prompts or file attachments: pipe via stdin
|
|
24
|
+
// Short prompts: pass as positional arg (gemini "prompt")
|
|
25
|
+
const useStdin = fullPrompt.length > STDIN_THRESHOLD || files.length > 0;
|
|
26
|
+
const args = [];
|
|
27
|
+
if (model) {
|
|
28
|
+
args.push("--model", model);
|
|
29
|
+
}
|
|
30
|
+
args.push("--output-format", "json");
|
|
31
|
+
if (!useStdin) {
|
|
32
|
+
args.push(fullPrompt); // positional prompt
|
|
33
|
+
}
|
|
34
|
+
const result = await spawnGemini({
|
|
35
|
+
args,
|
|
36
|
+
cwd,
|
|
37
|
+
stdin: useStdin ? fullPrompt : undefined,
|
|
38
|
+
timeout,
|
|
39
|
+
});
|
|
40
|
+
if (result.timedOut) {
|
|
41
|
+
return {
|
|
42
|
+
response: `Query timed out after ${(timeout ?? 60000) / 1000}s. Try a simpler prompt or increase the timeout.`,
|
|
43
|
+
filesIncluded: fileContents.filter((f) => !f.skipped).map((f) => f.path),
|
|
44
|
+
filesSkipped: fileContents.filter((f) => f.skipped).map((f) => `${f.path}: ${f.skipped}`),
|
|
45
|
+
timedOut: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Check for common error patterns in stderr
|
|
49
|
+
if (result.exitCode !== 0 && result.stderr) {
|
|
50
|
+
const stderr = result.stderr.toLowerCase();
|
|
51
|
+
if (stderr.includes("auth") || stderr.includes("credential") || stderr.includes("login")) {
|
|
52
|
+
throw new Error(`Gemini CLI authentication error. Run: gemini auth login\n\nDetails: ${result.stderr.trim()}`);
|
|
53
|
+
}
|
|
54
|
+
if (stderr.includes("rate") || stderr.includes("429") || stderr.includes("quota")) {
|
|
55
|
+
throw new Error(`Gemini API rate limit hit. Wait and retry.\n\nDetails: ${result.stderr.trim()}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const parsed = parseGeminiOutput(result.stdout, result.stderr);
|
|
59
|
+
return {
|
|
60
|
+
response: parsed.response,
|
|
61
|
+
model,
|
|
62
|
+
filesIncluded: fileContents.filter((f) => !f.skipped).map((f) => f.path),
|
|
63
|
+
filesSkipped: fileContents.filter((f) => f.skipped).map((f) => `${f.path}: ${f.skipped}`),
|
|
64
|
+
timedOut: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/tools/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ReviewInput {
|
|
2
|
+
uncommitted?: boolean;
|
|
3
|
+
base?: string;
|
|
4
|
+
workingDirectory?: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ReviewResult {
|
|
8
|
+
response: string;
|
|
9
|
+
diffSource: "uncommitted" | "branch";
|
|
10
|
+
base?: string;
|
|
11
|
+
timedOut: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Execute a code review by computing a git diff and sending it to Gemini.
|
|
15
|
+
* Native mode only (no extension dependency).
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeReview(input: ReviewInput): Promise<ReviewResult>;
|
|
18
|
+
//# sourceMappingURL=review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/tools/review.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,aAAa,GAAG,QAAQ,CAAC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAwBD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAsE7E"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { spawnGemini } from "../utils/spawn.js";
|
|
2
|
+
import { parseGeminiOutput } from "../utils/parse.js";
|
|
3
|
+
import { getGitRoot, getUncommittedDiff, getBranchDiff } from "../utils/git.js";
|
|
4
|
+
import { verifyDirectory } from "../utils/security.js";
|
|
5
|
+
const REVIEW_PROMPT = `You are an expert code reviewer. Review the following git diff carefully.
|
|
6
|
+
|
|
7
|
+
For each issue found, provide:
|
|
8
|
+
- **Severity**: critical / warning / suggestion
|
|
9
|
+
- **File**: the file path
|
|
10
|
+
- **Line**: approximate line number (from the diff)
|
|
11
|
+
- **Issue**: clear description
|
|
12
|
+
- **Suggestion**: how to fix it
|
|
13
|
+
|
|
14
|
+
Focus on:
|
|
15
|
+
- Bugs and logic errors
|
|
16
|
+
- Security vulnerabilities
|
|
17
|
+
- Performance issues
|
|
18
|
+
- Missing error handling
|
|
19
|
+
- Code style issues (only if significant)
|
|
20
|
+
|
|
21
|
+
If the code looks good, say so briefly. Don't invent issues.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
`;
|
|
26
|
+
/**
|
|
27
|
+
* Execute a code review by computing a git diff and sending it to Gemini.
|
|
28
|
+
* Native mode only (no extension dependency).
|
|
29
|
+
*/
|
|
30
|
+
export async function executeReview(input) {
|
|
31
|
+
const { uncommitted = true, base, timeout = 120_000 } = input;
|
|
32
|
+
// Resolve to git root
|
|
33
|
+
const requestedDir = input.workingDirectory
|
|
34
|
+
? await verifyDirectory(input.workingDirectory)
|
|
35
|
+
: process.cwd();
|
|
36
|
+
const cwd = getGitRoot(requestedDir);
|
|
37
|
+
// Get the diff
|
|
38
|
+
let diff;
|
|
39
|
+
let diffSource;
|
|
40
|
+
try {
|
|
41
|
+
if (base) {
|
|
42
|
+
diff = getBranchDiff(cwd, base);
|
|
43
|
+
diffSource = "branch";
|
|
44
|
+
}
|
|
45
|
+
else if (uncommitted) {
|
|
46
|
+
diff = getUncommittedDiff(cwd);
|
|
47
|
+
diffSource = "uncommitted";
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
throw new Error("Either 'uncommitted' must be true or 'base' must be specified");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof Error && (e.message.includes("No uncommitted changes") || e.message.includes("No diff found"))) {
|
|
55
|
+
return {
|
|
56
|
+
response: e.message,
|
|
57
|
+
diffSource: base ? "branch" : "uncommitted",
|
|
58
|
+
base,
|
|
59
|
+
timedOut: false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
const fullPrompt = REVIEW_PROMPT + diff;
|
|
65
|
+
const result = await spawnGemini({
|
|
66
|
+
args: ["--output-format", "json"],
|
|
67
|
+
cwd,
|
|
68
|
+
stdin: fullPrompt,
|
|
69
|
+
timeout,
|
|
70
|
+
});
|
|
71
|
+
if (result.timedOut) {
|
|
72
|
+
return {
|
|
73
|
+
response: `Review timed out after ${timeout / 1000}s. The diff may be too large. Try reviewing a smaller scope.`,
|
|
74
|
+
diffSource,
|
|
75
|
+
base,
|
|
76
|
+
timedOut: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (result.exitCode !== 0 && result.stderr) {
|
|
80
|
+
const stderr = result.stderr.toLowerCase();
|
|
81
|
+
if (stderr.includes("auth") || stderr.includes("credential")) {
|
|
82
|
+
throw new Error(`Gemini CLI authentication error. Run: gemini auth login\n\nDetails: ${result.stderr.trim()}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const parsed = parseGeminiOutput(result.stdout, result.stderr);
|
|
86
|
+
return {
|
|
87
|
+
response: parsed.response,
|
|
88
|
+
diffSource,
|
|
89
|
+
base,
|
|
90
|
+
timedOut: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/tools/review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardened subprocess environment builder.
|
|
3
|
+
* Never spreads process.env — uses an explicit allowlist.
|
|
4
|
+
*/
|
|
5
|
+
/** Build a minimal, safe environment for gemini CLI subprocesses. */
|
|
6
|
+
export declare function buildSubprocessEnv(): Record<string, string>;
|
|
7
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,qEAAqE;AACrE,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiB3D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardened subprocess environment builder.
|
|
3
|
+
* Never spreads process.env — uses an explicit allowlist.
|
|
4
|
+
*/
|
|
5
|
+
const ALLOWED_ENV_PREFIXES = ["GOOGLE_", "GEMINI_", "CLOUDSDK_"];
|
|
6
|
+
const ALLOWED_ENV_KEYS = [
|
|
7
|
+
"HOME",
|
|
8
|
+
"PATH",
|
|
9
|
+
"USER",
|
|
10
|
+
"SHELL",
|
|
11
|
+
"LANG",
|
|
12
|
+
"TERM",
|
|
13
|
+
"XDG_CONFIG_HOME",
|
|
14
|
+
];
|
|
15
|
+
/** Build a minimal, safe environment for gemini CLI subprocesses. */
|
|
16
|
+
export function buildSubprocessEnv() {
|
|
17
|
+
const env = {
|
|
18
|
+
NO_COLOR: "1",
|
|
19
|
+
FORCE_COLOR: "0",
|
|
20
|
+
NODE_OPTIONS: "--max-old-space-size=8192",
|
|
21
|
+
};
|
|
22
|
+
for (const [key, val] of Object.entries(process.env)) {
|
|
23
|
+
if (!val)
|
|
24
|
+
continue;
|
|
25
|
+
if (ALLOWED_ENV_KEYS.includes(key)) {
|
|
26
|
+
env[key] = val;
|
|
27
|
+
}
|
|
28
|
+
else if (ALLOWED_ENV_PREFIXES.some((p) => key.startsWith(p))) {
|
|
29
|
+
env[key] = val;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return env;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;AACjE,MAAM,gBAAgB,GAAG;IACvB,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,iBAAiB;CAClB,CAAC;AAEF,qEAAqE;AACrE,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAA2B;QAClC,QAAQ,EAAE,GAAG;QACb,WAAW,EAAE,GAAG;QAChB,YAAY,EAAE,2BAA2B;KAC1C,CAAC;IAEF,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,IAAI,gBAAgB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QACjB,CAAC;aAAM,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QACjB,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface FileContent {
|
|
2
|
+
path: string;
|
|
3
|
+
content: string;
|
|
4
|
+
skipped?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Read multiple files with path sandboxing and size limits.
|
|
8
|
+
* Returns file contents assembled for prompt inclusion.
|
|
9
|
+
*/
|
|
10
|
+
export declare function readFiles(files: string[], rootDir: string): Promise<FileContent[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Assemble a prompt with file contents for Gemini.
|
|
13
|
+
*/
|
|
14
|
+
export declare function assemblePrompt(prompt: string, fileContents: FileContent[]): string;
|
|
15
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/utils/files.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,EAAE,CAAC,CAyBxB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CAWlF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolveAndVerify, checkFileSize, MAX_FILE_SIZE, MAX_FILES, } from "./security.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read multiple files with path sandboxing and size limits.
|
|
5
|
+
* Returns file contents assembled for prompt inclusion.
|
|
6
|
+
*/
|
|
7
|
+
export async function readFiles(files, rootDir) {
|
|
8
|
+
if (files.length > MAX_FILES) {
|
|
9
|
+
throw new Error(`Too many files: ${files.length} (max ${MAX_FILES})`);
|
|
10
|
+
}
|
|
11
|
+
const results = [];
|
|
12
|
+
for (const f of files) {
|
|
13
|
+
const resolved = await resolveAndVerify(f, rootDir);
|
|
14
|
+
const size = await checkFileSize(resolved);
|
|
15
|
+
if (size > MAX_FILE_SIZE) {
|
|
16
|
+
results.push({
|
|
17
|
+
path: f,
|
|
18
|
+
content: "",
|
|
19
|
+
skipped: `${(size / 1024).toFixed(0)}KB exceeds ${(MAX_FILE_SIZE / 1024).toFixed(0)}KB limit`,
|
|
20
|
+
});
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const content = await readFile(resolved, "utf8");
|
|
24
|
+
results.push({ path: f, content });
|
|
25
|
+
}
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Assemble a prompt with file contents for Gemini.
|
|
30
|
+
*/
|
|
31
|
+
export function assemblePrompt(prompt, fileContents) {
|
|
32
|
+
if (fileContents.length === 0)
|
|
33
|
+
return prompt;
|
|
34
|
+
const parts = fileContents.map((f) => {
|
|
35
|
+
if (f.skipped) {
|
|
36
|
+
return `--- ${f.path} ---\n[SKIPPED: ${f.skipped}]`;
|
|
37
|
+
}
|
|
38
|
+
return `--- ${f.path} ---\n${f.content}`;
|
|
39
|
+
});
|
|
40
|
+
return `${prompt}\n\n${parts.join("\n\n")}`;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/utils/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,SAAS,GACV,MAAM,eAAe,CAAC;AAQvB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAe,EACf,OAAe;IAEf,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,CAAC,MAAM,SAAS,SAAS,GAAG,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE3C,IAAI,IAAI,GAAG,aAAa,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,CAAC;gBACP,OAAO,EAAE,EAAE;gBACX,OAAO,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU;aAC9F,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,YAA2B;IACxE,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAE7C,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACnC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,OAAO,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC,OAAO,GAAG,CAAC;QACtD,CAAC;QACD,OAAO,OAAO,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,MAAM,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the git repository root for a given directory.
|
|
3
|
+
* Throws if not inside a git repo.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getGitRoot(cwd: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get a unified diff of uncommitted changes (staged + unstaged).
|
|
8
|
+
*/
|
|
9
|
+
export declare function getUncommittedDiff(cwd: string, contextLines?: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Get a diff between the current branch and a base branch/ref.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getBranchDiff(cwd: string, base: string, contextLines?: number): string;
|
|
14
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAS9C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,MAAM,CA2BxE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,MAAM,CAkBjF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Find the git repository root for a given directory.
|
|
4
|
+
* Throws if not inside a git repo.
|
|
5
|
+
*/
|
|
6
|
+
export function getGitRoot(cwd) {
|
|
7
|
+
try {
|
|
8
|
+
return execFileSync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
timeout: 5000,
|
|
11
|
+
}).trim();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
throw new Error(`Not a git repository: ${cwd}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get a unified diff of uncommitted changes (staged + unstaged).
|
|
19
|
+
*/
|
|
20
|
+
export function getUncommittedDiff(cwd, contextLines = 5) {
|
|
21
|
+
try {
|
|
22
|
+
// Staged changes
|
|
23
|
+
const staged = execFileSync("git", ["-C", cwd, "diff", "--cached", `-U${contextLines}`], { encoding: "utf8", timeout: 30000 }).trim();
|
|
24
|
+
// Unstaged changes
|
|
25
|
+
const unstaged = execFileSync("git", ["-C", cwd, "diff", `-U${contextLines}`], { encoding: "utf8", timeout: 30000 }).trim();
|
|
26
|
+
const parts = [staged, unstaged].filter(Boolean);
|
|
27
|
+
if (parts.length === 0) {
|
|
28
|
+
throw new Error("No uncommitted changes found");
|
|
29
|
+
}
|
|
30
|
+
return parts.join("\n");
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
if (e instanceof Error && e.message === "No uncommitted changes found") {
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Failed to get git diff: ${e}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get a diff between the current branch and a base branch/ref.
|
|
41
|
+
*/
|
|
42
|
+
export function getBranchDiff(cwd, base, contextLines = 5) {
|
|
43
|
+
try {
|
|
44
|
+
const diff = execFileSync("git", ["-C", cwd, "diff", `${base}...HEAD`, `-U${contextLines}`], { encoding: "utf8", timeout: 30000 }).trim();
|
|
45
|
+
if (!diff) {
|
|
46
|
+
throw new Error(`No diff found between ${base} and HEAD`);
|
|
47
|
+
}
|
|
48
|
+
return diff;
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
if (e instanceof Error && e.message.startsWith("No diff found")) {
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Failed to get branch diff against "${base}": ${e}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE;YACtE,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,IAAI;SACd,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,YAAY,GAAG,CAAC;IAC9D,IAAI,CAAC;QACH,iBAAiB;QACjB,MAAM,MAAM,GAAG,YAAY,CACzB,KAAK,EACL,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,CAAC,EACpD,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CACrC,CAAC,IAAI,EAAE,CAAC;QAET,mBAAmB;QACnB,MAAM,QAAQ,GAAG,YAAY,CAC3B,KAAK,EACL,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,CAAC,EACxC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CACrC,CAAC,IAAI,EAAE,CAAC;QAET,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,OAAO,KAAK,8BAA8B,EAAE,CAAC;YACvE,MAAM,CAAC,CAAC;QACV,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,IAAY,EAAE,YAAY,GAAG,CAAC;IACvE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,YAAY,CACvB,KAAK,EACL,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,SAAS,EAAE,KAAK,YAAY,EAAE,CAAC,EAC1D,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CACrC,CAAC,IAAI,EAAE,CAAC;QAET,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,WAAW,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YAChE,MAAM,CAAC,CAAC;QACV,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface GeminiJsonOutput {
|
|
2
|
+
/** The main text response from Gemini. */
|
|
3
|
+
response: string;
|
|
4
|
+
/** Raw parsed JSON (full structure from CLI). */
|
|
5
|
+
raw?: unknown;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parse Gemini CLI output, handling JSON and plain text modes.
|
|
9
|
+
*
|
|
10
|
+
* Strategy:
|
|
11
|
+
* 1. Try parsing as JSON (--output-format json)
|
|
12
|
+
* 2. Fall back to ANSI-stripped plain text
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseGeminiOutput(stdout: string, stderr: string): GeminiJsonOutput;
|
|
15
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/utils/parse.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAuBlF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import stripAnsi from "strip-ansi";
|
|
2
|
+
/**
|
|
3
|
+
* Parse Gemini CLI output, handling JSON and plain text modes.
|
|
4
|
+
*
|
|
5
|
+
* Strategy:
|
|
6
|
+
* 1. Try parsing as JSON (--output-format json)
|
|
7
|
+
* 2. Fall back to ANSI-stripped plain text
|
|
8
|
+
*/
|
|
9
|
+
export function parseGeminiOutput(stdout, stderr) {
|
|
10
|
+
const cleaned = stripAnsi(stdout).trim();
|
|
11
|
+
// Try JSON parse first
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(cleaned);
|
|
14
|
+
return extractFromJson(parsed);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Not JSON — treat as plain text response
|
|
18
|
+
}
|
|
19
|
+
// Plain text fallback
|
|
20
|
+
if (cleaned.length > 0) {
|
|
21
|
+
return { response: cleaned };
|
|
22
|
+
}
|
|
23
|
+
// No stdout — check stderr for useful info
|
|
24
|
+
const cleanedStderr = stripAnsi(stderr).trim();
|
|
25
|
+
if (cleanedStderr.length > 0) {
|
|
26
|
+
throw new Error(`Gemini CLI produced no output. stderr: ${cleanedStderr}`);
|
|
27
|
+
}
|
|
28
|
+
throw new Error("Gemini CLI produced no output");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extract the response text from Gemini's JSON output.
|
|
32
|
+
* The schema is undocumented, so we parse defensively.
|
|
33
|
+
*/
|
|
34
|
+
function extractFromJson(parsed) {
|
|
35
|
+
if (typeof parsed === "string") {
|
|
36
|
+
return { response: parsed };
|
|
37
|
+
}
|
|
38
|
+
if (parsed && typeof parsed === "object") {
|
|
39
|
+
const obj = parsed;
|
|
40
|
+
// Try common response fields
|
|
41
|
+
for (const key of ["response", "text", "content", "message", "output"]) {
|
|
42
|
+
if (typeof obj[key] === "string") {
|
|
43
|
+
return { response: obj[key], raw: parsed };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Nested: result.response or result.text
|
|
47
|
+
if (obj["result"] && typeof obj["result"] === "object") {
|
|
48
|
+
const result = obj["result"];
|
|
49
|
+
for (const key of ["response", "text", "content"]) {
|
|
50
|
+
if (typeof result[key] === "string") {
|
|
51
|
+
return { response: result[key], raw: parsed };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// If it's an array (stream-json collected), join text parts
|
|
56
|
+
if (Array.isArray(parsed)) {
|
|
57
|
+
const texts = parsed
|
|
58
|
+
.map((item) => {
|
|
59
|
+
if (typeof item === "string")
|
|
60
|
+
return item;
|
|
61
|
+
if (item && typeof item === "object") {
|
|
62
|
+
const i = item;
|
|
63
|
+
return (i["text"] ?? i["response"] ?? i["content"] ?? "");
|
|
64
|
+
}
|
|
65
|
+
return "";
|
|
66
|
+
})
|
|
67
|
+
.filter(Boolean);
|
|
68
|
+
if (texts.length > 0) {
|
|
69
|
+
return { response: texts.join(""), raw: parsed };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Last resort: stringify the whole thing
|
|
73
|
+
return { response: JSON.stringify(parsed, null, 2), raw: parsed };
|
|
74
|
+
}
|
|
75
|
+
return { response: String(parsed) };
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/utils/parse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Maximum file size in bytes (1MB). */
|
|
2
|
+
export declare const MAX_FILE_SIZE = 1000000;
|
|
3
|
+
/** Maximum number of files that can be attached to a query. */
|
|
4
|
+
export declare const MAX_FILES = 20;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a path and verify it's within the allowed root directory.
|
|
7
|
+
* Prevents path traversal attacks via symlinks, `..`, etc.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveAndVerify(filePath: string, rootDir: string): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Verify a directory exists and is actually a directory.
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyDirectory(dir: string): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Check if a file is within size limits.
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkFileSize(filePath: string): Promise<number>;
|
|
18
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/utils/security.ts"],"names":[],"mappings":"AAGA,wCAAwC;AACxC,eAAO,MAAM,aAAa,UAAY,CAAC;AAEvC,+DAA+D;AAC/D,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOlE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { realpath, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** Maximum file size in bytes (1MB). */
|
|
4
|
+
export const MAX_FILE_SIZE = 1_000_000;
|
|
5
|
+
/** Maximum number of files that can be attached to a query. */
|
|
6
|
+
export const MAX_FILES = 20;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a path and verify it's within the allowed root directory.
|
|
9
|
+
* Prevents path traversal attacks via symlinks, `..`, etc.
|
|
10
|
+
*/
|
|
11
|
+
export async function resolveAndVerify(filePath, rootDir) {
|
|
12
|
+
const resolved = await realpath(path.resolve(rootDir, filePath));
|
|
13
|
+
const resolvedRoot = await realpath(rootDir);
|
|
14
|
+
if (!resolved.startsWith(resolvedRoot + path.sep) && resolved !== resolvedRoot) {
|
|
15
|
+
throw new Error(`Path traversal blocked: "${filePath}" resolves outside allowed root "${rootDir}"`);
|
|
16
|
+
}
|
|
17
|
+
return resolved;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verify a directory exists and is actually a directory.
|
|
21
|
+
*/
|
|
22
|
+
export async function verifyDirectory(dir) {
|
|
23
|
+
const resolved = await realpath(dir);
|
|
24
|
+
const s = await stat(resolved);
|
|
25
|
+
if (!s.isDirectory()) {
|
|
26
|
+
throw new Error(`Not a directory: ${dir}`);
|
|
27
|
+
}
|
|
28
|
+
return resolved;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if a file is within size limits.
|
|
32
|
+
*/
|
|
33
|
+
export async function checkFileSize(filePath) {
|
|
34
|
+
const s = await stat(filePath);
|
|
35
|
+
return s.size;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/utils/security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,wCAAwC;AACxC,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AAEvC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAE5B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE7C,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;QAC/E,MAAM,IAAI,KAAK,CACb,4BAA4B,QAAQ,oCAAoC,OAAO,GAAG,CACnF,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB;IAClD,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface SpawnOptions {
|
|
2
|
+
args: string[];
|
|
3
|
+
cwd: string;
|
|
4
|
+
stdin?: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SpawnResult {
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
exitCode: number | null;
|
|
11
|
+
timedOut: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Find the gemini CLI binary path.
|
|
15
|
+
*/
|
|
16
|
+
export declare function findGeminiBinary(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Spawn a gemini CLI subprocess with hardened environment,
|
|
19
|
+
* timeout management, and concurrency limiting.
|
|
20
|
+
*/
|
|
21
|
+
export declare function spawnGemini(options: SpawnOptions): Promise<SpawnResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Reset concurrency state (for testing).
|
|
24
|
+
*/
|
|
25
|
+
export declare function resetConcurrency(): void;
|
|
26
|
+
//# sourceMappingURL=spawn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../src/utils/spawn.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;CACnB;AA6CD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAU7E;AA6GD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { buildSubprocessEnv } from "./env.js";
|
|
3
|
+
/** Hard maximum timeout — no request can exceed this. */
|
|
4
|
+
const HARD_TIMEOUT_CAP = 600_000; // 10 minutes
|
|
5
|
+
/** Default max concurrent subprocess spawns. */
|
|
6
|
+
const DEFAULT_MAX_CONCURRENT = 3;
|
|
7
|
+
/** Queue timeout — how long a request waits for a slot. */
|
|
8
|
+
const QUEUE_TIMEOUT = 30_000;
|
|
9
|
+
// Concurrency management
|
|
10
|
+
let activeCount = 0;
|
|
11
|
+
const maxConcurrent = parseInt(process.env["GEMINI_MAX_CONCURRENT"] ?? String(DEFAULT_MAX_CONCURRENT), 10);
|
|
12
|
+
const waitQueue = [];
|
|
13
|
+
function acquireSlot() {
|
|
14
|
+
if (activeCount < maxConcurrent) {
|
|
15
|
+
activeCount++;
|
|
16
|
+
return Promise.resolve();
|
|
17
|
+
}
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const timer = setTimeout(() => {
|
|
20
|
+
const idx = waitQueue.findIndex((w) => w.resolve === resolve);
|
|
21
|
+
if (idx !== -1)
|
|
22
|
+
waitQueue.splice(idx, 1);
|
|
23
|
+
reject(new Error(`Concurrency queue timeout after ${QUEUE_TIMEOUT}ms — ${activeCount} processes active`));
|
|
24
|
+
}, QUEUE_TIMEOUT);
|
|
25
|
+
waitQueue.push({
|
|
26
|
+
resolve: () => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
activeCount++;
|
|
29
|
+
resolve();
|
|
30
|
+
},
|
|
31
|
+
reject,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function releaseSlot() {
|
|
36
|
+
activeCount--;
|
|
37
|
+
const next = waitQueue.shift();
|
|
38
|
+
if (next) {
|
|
39
|
+
next.resolve();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Find the gemini CLI binary path.
|
|
44
|
+
*/
|
|
45
|
+
export function findGeminiBinary() {
|
|
46
|
+
return process.env["GEMINI_CLI_PATH"] ?? "gemini";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Spawn a gemini CLI subprocess with hardened environment,
|
|
50
|
+
* timeout management, and concurrency limiting.
|
|
51
|
+
*/
|
|
52
|
+
export async function spawnGemini(options) {
|
|
53
|
+
const timeout = Math.min(options.timeout ?? 60_000, HARD_TIMEOUT_CAP);
|
|
54
|
+
await acquireSlot();
|
|
55
|
+
try {
|
|
56
|
+
return await doSpawn(options, timeout);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
releaseSlot();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function doSpawn(options, timeout) {
|
|
63
|
+
const binary = findGeminiBinary();
|
|
64
|
+
const env = buildSubprocessEnv();
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
let child;
|
|
67
|
+
const detached = process.platform !== "win32";
|
|
68
|
+
try {
|
|
69
|
+
child = spawn(binary, options.args, {
|
|
70
|
+
cwd: options.cwd,
|
|
71
|
+
env,
|
|
72
|
+
shell: false,
|
|
73
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
74
|
+
detached,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
reject(new Error(`Failed to spawn gemini CLI: ${e}`));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
let stdout = "";
|
|
82
|
+
let stderr = "";
|
|
83
|
+
let timedOut = false;
|
|
84
|
+
let settled = false;
|
|
85
|
+
let killTimer;
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
timedOut = true;
|
|
88
|
+
killTimer = killProcessGroup(child);
|
|
89
|
+
}, timeout);
|
|
90
|
+
child.stdout?.on("data", (chunk) => {
|
|
91
|
+
stdout += chunk.toString();
|
|
92
|
+
});
|
|
93
|
+
child.stderr?.on("data", (chunk) => {
|
|
94
|
+
stderr += chunk.toString();
|
|
95
|
+
});
|
|
96
|
+
child.on("error", (err) => {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
if (killTimer)
|
|
99
|
+
clearTimeout(killTimer);
|
|
100
|
+
if (!settled) {
|
|
101
|
+
settled = true;
|
|
102
|
+
if (err.code === "ENOENT") {
|
|
103
|
+
reject(new Error("gemini CLI not found. Install with: npm i -g @google/gemini-cli"));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
reject(new Error(`Failed to run gemini CLI: ${err.message}`));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
child.on("close", (code) => {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
if (killTimer)
|
|
113
|
+
clearTimeout(killTimer);
|
|
114
|
+
if (!settled) {
|
|
115
|
+
settled = true;
|
|
116
|
+
resolve({ stdout, stderr, exitCode: code, timedOut });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// Write stdin if provided, then close
|
|
120
|
+
if (options.stdin) {
|
|
121
|
+
child.stdin?.write(options.stdin);
|
|
122
|
+
}
|
|
123
|
+
child.stdin?.end();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Kill a process and its children. SIGTERM first, SIGKILL after grace period.
|
|
128
|
+
* Only uses process group kill (-pid) when the child was spawned with detached: true,
|
|
129
|
+
* which gives it its own process group. Without detached, -pid would target the
|
|
130
|
+
* parent's process group and kill the MCP server itself.
|
|
131
|
+
*/
|
|
132
|
+
function killProcessGroup(child) {
|
|
133
|
+
const pid = child.pid;
|
|
134
|
+
if (!pid)
|
|
135
|
+
return undefined;
|
|
136
|
+
const useGroupKill = process.platform !== "win32";
|
|
137
|
+
const kill = (signal) => {
|
|
138
|
+
try {
|
|
139
|
+
if (useGroupKill) {
|
|
140
|
+
process.kill(-pid, signal);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
child.kill(signal);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
try {
|
|
148
|
+
child.kill(signal);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Already dead
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
kill("SIGTERM");
|
|
156
|
+
// Force kill after 5s grace
|
|
157
|
+
return setTimeout(() => kill("SIGKILL"), 5000);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Reset concurrency state (for testing).
|
|
161
|
+
*/
|
|
162
|
+
export function resetConcurrency() {
|
|
163
|
+
activeCount = 0;
|
|
164
|
+
waitQueue.length = 0;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=spawn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/utils/spawn.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gemini-mcp-bridge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server that wraps Gemini CLI for query, code review, and health checks",
|
|
5
|
+
"author": "Tim Birrell",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"gemini-mcp-bridge": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"lint": "eslint src/ tests/",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"test:integration": "GEMINI_INTEGRATION=1 vitest run tests/integration/",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mcp",
|
|
29
|
+
"gemini",
|
|
30
|
+
"gemini-cli",
|
|
31
|
+
"claude-code",
|
|
32
|
+
"codex",
|
|
33
|
+
"code-review",
|
|
34
|
+
"ai-tools",
|
|
35
|
+
"model-context-protocol"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/hampsterx/gemini-mcp-bridge.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/hampsterx/gemini-mcp-bridge/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/hampsterx/gemini-mcp-bridge#readme",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
50
|
+
"strip-ansi": "^7.1.0",
|
|
51
|
+
"zod": "^3.24.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^22.0.0",
|
|
55
|
+
"eslint": "^9.0.0",
|
|
56
|
+
"typescript": "^5.7.0",
|
|
57
|
+
"typescript-eslint": "^8.0.0",
|
|
58
|
+
"vitest": "^3.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|