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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +133 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/tools/ping.d.ts +14 -0
  8. package/dist/tools/ping.d.ts.map +1 -0
  9. package/dist/tools/ping.js +66 -0
  10. package/dist/tools/ping.js.map +1 -0
  11. package/dist/tools/query.d.ts +20 -0
  12. package/dist/tools/query.d.ts.map +1 -0
  13. package/dist/tools/query.js +67 -0
  14. package/dist/tools/query.js.map +1 -0
  15. package/dist/tools/review.d.ts +18 -0
  16. package/dist/tools/review.d.ts.map +1 -0
  17. package/dist/tools/review.js +93 -0
  18. package/dist/tools/review.js.map +1 -0
  19. package/dist/utils/env.d.ts +7 -0
  20. package/dist/utils/env.d.ts.map +1 -0
  21. package/dist/utils/env.js +34 -0
  22. package/dist/utils/env.js.map +1 -0
  23. package/dist/utils/files.d.ts +15 -0
  24. package/dist/utils/files.d.ts.map +1 -0
  25. package/dist/utils/files.js +42 -0
  26. package/dist/utils/files.js.map +1 -0
  27. package/dist/utils/git.d.ts +14 -0
  28. package/dist/utils/git.d.ts.map +1 -0
  29. package/dist/utils/git.js +57 -0
  30. package/dist/utils/git.js.map +1 -0
  31. package/dist/utils/parse.d.ts +15 -0
  32. package/dist/utils/parse.d.ts.map +1 -0
  33. package/dist/utils/parse.js +77 -0
  34. package/dist/utils/parse.js.map +1 -0
  35. package/dist/utils/security.d.ts +18 -0
  36. package/dist/utils/security.d.ts.map +1 -0
  37. package/dist/utils/security.js +37 -0
  38. package/dist/utils/security.js.map +1 -0
  39. package/dist/utils/spawn.d.ts +26 -0
  40. package/dist/utils/spawn.d.ts.map +1 -0
  41. package/dist/utils/spawn.js +166 -0
  42. package/dist/utils/spawn.js.map +1 -0
  43. 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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ }