@joeytheman/gemini-mcp-tool 1.3.0 → 2.3.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/README.md +127 -35
- package/dist/__tests__/agyModelSteps.test.d.ts +2 -0
- package/dist/__tests__/agyModelSteps.test.d.ts.map +1 -0
- package/dist/__tests__/agyModelSteps.test.js +58 -0
- package/dist/__tests__/agyModelSteps.test.js.map +1 -0
- package/dist/__tests__/ask-gemini.test.js +99 -37
- package/dist/__tests__/ask-gemini.test.js.map +1 -1
- package/dist/__tests__/capture-script.chromium.test.d.ts +2 -0
- package/dist/__tests__/capture-script.chromium.test.d.ts.map +1 -0
- package/dist/__tests__/capture-script.chromium.test.js +235 -0
- package/dist/__tests__/capture-script.chromium.test.js.map +1 -0
- package/dist/__tests__/layout-snippet.chromium.test.d.ts +2 -0
- package/dist/__tests__/layout-snippet.chromium.test.d.ts.map +1 -0
- package/dist/__tests__/layout-snippet.chromium.test.js +333 -0
- package/dist/__tests__/layout-snippet.chromium.test.js.map +1 -0
- package/dist/__tests__/layout-snippet.test.d.ts +2 -0
- package/dist/__tests__/layout-snippet.test.d.ts.map +1 -0
- package/dist/__tests__/layout-snippet.test.js +12 -0
- package/dist/__tests__/layout-snippet.test.js.map +1 -0
- package/dist/__tests__/live-pass.test.d.ts +2 -0
- package/dist/__tests__/live-pass.test.d.ts.map +1 -0
- package/dist/__tests__/live-pass.test.js +1249 -0
- package/dist/__tests__/live-pass.test.js.map +1 -0
- package/dist/__tests__/screen-review.test.d.ts +2 -0
- package/dist/__tests__/screen-review.test.d.ts.map +1 -0
- package/dist/__tests__/screen-review.test.js +553 -0
- package/dist/__tests__/screen-review.test.js.map +1 -0
- package/dist/__tests__/server-progress.test.d.ts +2 -0
- package/dist/__tests__/server-progress.test.d.ts.map +1 -0
- package/dist/__tests__/server-progress.test.js +63 -0
- package/dist/__tests__/server-progress.test.js.map +1 -0
- package/dist/__tests__/server.test.js +17 -19
- package/dist/__tests__/server.test.js.map +1 -1
- package/dist/__tests__/simple-tools.test.d.ts +2 -0
- package/dist/__tests__/simple-tools.test.d.ts.map +1 -0
- package/dist/__tests__/simple-tools.test.js +30 -0
- package/dist/__tests__/simple-tools.test.js.map +1 -0
- package/dist/__tests__/tools.test.js +91 -0
- package/dist/__tests__/tools.test.js.map +1 -1
- package/dist/__tests__/utils/agyExecutor.test.d.ts +2 -0
- package/dist/__tests__/utils/agyExecutor.test.d.ts.map +1 -0
- package/dist/__tests__/utils/agyExecutor.test.js +731 -0
- package/dist/__tests__/utils/agyExecutor.test.js.map +1 -0
- package/dist/__tests__/utils/agyTranscriptRecovery.test.d.ts +2 -0
- package/dist/__tests__/utils/agyTranscriptRecovery.test.d.ts.map +1 -0
- package/dist/__tests__/utils/agyTranscriptRecovery.test.js +127 -0
- package/dist/__tests__/utils/agyTranscriptRecovery.test.js.map +1 -0
- package/dist/__tests__/utils/chunkCache.test.js +26 -2
- package/dist/__tests__/utils/chunkCache.test.js.map +1 -1
- package/dist/__tests__/utils/commandExecutor.test.js +185 -7
- package/dist/__tests__/utils/commandExecutor.test.js.map +1 -1
- package/dist/__tests__/utils/fileReferences.test.d.ts +2 -0
- package/dist/__tests__/utils/fileReferences.test.d.ts.map +1 -0
- package/dist/__tests__/utils/fileReferences.test.js +87 -0
- package/dist/__tests__/utils/fileReferences.test.js.map +1 -0
- package/dist/__tests__/utils/gitStatusGuard.test.d.ts +2 -0
- package/dist/__tests__/utils/gitStatusGuard.test.d.ts.map +1 -0
- package/dist/__tests__/utils/gitStatusGuard.test.js +106 -0
- package/dist/__tests__/utils/gitStatusGuard.test.js.map +1 -0
- package/dist/__tests__/utils/progressUpdates.test.d.ts +2 -0
- package/dist/__tests__/utils/progressUpdates.test.d.ts.map +1 -0
- package/dist/__tests__/utils/progressUpdates.test.js +62 -0
- package/dist/__tests__/utils/progressUpdates.test.js.map +1 -0
- package/dist/__tests__/utils/responseCache.test.js +31 -11
- package/dist/__tests__/utils/responseCache.test.js.map +1 -1
- package/dist/__tests__/utils/test-helpers.d.ts +4 -0
- package/dist/__tests__/utils/test-helpers.d.ts.map +1 -1
- package/dist/__tests__/utils/test-helpers.js +16 -0
- package/dist/__tests__/utils/test-helpers.js.map +1 -1
- package/dist/constants.d.ts +91 -26
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +101 -28
- package/dist/constants.js.map +1 -1
- package/dist/index.js +20 -95
- package/dist/index.js.map +1 -1
- package/dist/layoutCheckSnippet.generated.d.ts +2 -0
- package/dist/layoutCheckSnippet.generated.d.ts.map +1 -0
- package/dist/layoutCheckSnippet.generated.js +3 -0
- package/dist/layoutCheckSnippet.generated.js.map +1 -0
- package/dist/tools/ask-gemini.tool.d.ts +34 -0
- package/dist/tools/ask-gemini.tool.d.ts.map +1 -1
- package/dist/tools/ask-gemini.tool.js +40 -23
- package/dist/tools/ask-gemini.tool.js.map +1 -1
- package/dist/tools/brainstorm.tool.d.ts.map +1 -1
- package/dist/tools/brainstorm.tool.js +7 -5
- package/dist/tools/brainstorm.tool.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/live-pass.tool.d.ts +206 -0
- package/dist/tools/live-pass.tool.d.ts.map +1 -0
- package/dist/tools/live-pass.tool.js +670 -0
- package/dist/tools/live-pass.tool.js.map +1 -0
- package/dist/tools/registry.d.ts +6 -4
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +14 -11
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/screen-review.tool.d.ts +124 -0
- package/dist/tools/screen-review.tool.d.ts.map +1 -0
- package/dist/tools/screen-review.tool.js +449 -0
- package/dist/tools/screen-review.tool.js.map +1 -0
- package/dist/tools/simple-tools.d.ts.map +1 -1
- package/dist/tools/simple-tools.js +9 -3
- package/dist/tools/simple-tools.js.map +1 -1
- package/dist/utils/agyExecutor.d.ts +76 -0
- package/dist/utils/agyExecutor.d.ts.map +1 -0
- package/dist/utils/agyExecutor.js +449 -0
- package/dist/utils/agyExecutor.js.map +1 -0
- package/dist/utils/agyModelSteps.d.ts +5 -0
- package/dist/utils/agyModelSteps.d.ts.map +1 -0
- package/dist/utils/agyModelSteps.js +33 -0
- package/dist/utils/agyModelSteps.js.map +1 -0
- package/dist/utils/agyReport.d.ts +19 -0
- package/dist/utils/agyReport.d.ts.map +1 -0
- package/dist/utils/agyReport.js +62 -0
- package/dist/utils/agyReport.js.map +1 -0
- package/dist/utils/agyTranscriptRecovery.d.ts +16 -0
- package/dist/utils/agyTranscriptRecovery.d.ts.map +1 -0
- package/dist/utils/agyTranscriptRecovery.js +149 -0
- package/dist/utils/agyTranscriptRecovery.js.map +1 -0
- package/dist/utils/artifactFile.d.ts +6 -0
- package/dist/utils/artifactFile.d.ts.map +1 -0
- package/dist/utils/artifactFile.js +55 -0
- package/dist/utils/artifactFile.js.map +1 -0
- package/dist/utils/captureScript.d.ts +24 -0
- package/dist/utils/captureScript.d.ts.map +1 -0
- package/dist/utils/captureScript.js +83 -0
- package/dist/utils/captureScript.js.map +1 -0
- package/dist/utils/chunkCache.d.ts.map +1 -1
- package/dist/utils/chunkCache.js +19 -5
- package/dist/utils/chunkCache.js.map +1 -1
- package/dist/utils/commandExecutor.d.ts +7 -1
- package/dist/utils/commandExecutor.d.ts.map +1 -1
- package/dist/utils/commandExecutor.js +162 -22
- package/dist/utils/commandExecutor.js.map +1 -1
- package/dist/utils/fileReferences.d.ts +45 -0
- package/dist/utils/fileReferences.d.ts.map +1 -0
- package/dist/utils/fileReferences.js +169 -0
- package/dist/utils/fileReferences.js.map +1 -0
- package/dist/utils/gitCheckout.d.ts +14 -0
- package/dist/utils/gitCheckout.d.ts.map +1 -0
- package/dist/utils/gitCheckout.js +36 -0
- package/dist/utils/gitCheckout.js.map +1 -0
- package/dist/utils/gitStatusGuard.d.ts +35 -0
- package/dist/utils/gitStatusGuard.d.ts.map +1 -0
- package/dist/utils/gitStatusGuard.js +82 -0
- package/dist/utils/gitStatusGuard.js.map +1 -0
- package/dist/utils/layoutCheck.d.ts +58 -0
- package/dist/utils/layoutCheck.d.ts.map +1 -0
- package/dist/utils/layoutCheck.js +84 -0
- package/dist/utils/layoutCheck.js.map +1 -0
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +0 -6
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/progressUpdates.d.ts +14 -0
- package/dist/utils/progressUpdates.d.ts.map +1 -0
- package/dist/utils/progressUpdates.js +40 -0
- package/dist/utils/progressUpdates.js.map +1 -0
- package/dist/utils/responseCache.d.ts +3 -3
- package/dist/utils/responseCache.d.ts.map +1 -1
- package/dist/utils/responseCache.js +11 -6
- package/dist/utils/responseCache.js.map +1 -1
- package/package.json +19 -16
- package/dist/__tests__/utils/geminiExecutor.test.d.ts +0 -2
- package/dist/__tests__/utils/geminiExecutor.test.d.ts.map +0 -1
- package/dist/__tests__/utils/geminiExecutor.test.js +0 -337
- package/dist/__tests__/utils/geminiExecutor.test.js.map +0 -1
- package/dist/utils/geminiExecutor.d.ts +0 -17
- package/dist/utils/geminiExecutor.d.ts.map +0 -1
- package/dist/utils/geminiExecutor.js +0 -238
- package/dist/utils/geminiExecutor.js.map +0 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
> **Fork Notice:** This project is a fork of [jamubc/gemini-mcp-tool](https://github.com/jamubc/gemini-mcp-tool), originally created by [jamubc](https://github.com/jamubc). We are grateful to the original author for their foundational work.
|
|
17
17
|
|
|
18
|
-
This is a simple Model Context Protocol (MCP) server that
|
|
18
|
+
This is a simple Model Context Protocol (MCP) server that lets AI assistants ask Gemini through Google's Antigravity CLI (`agy`). The primary use case is giving Codex, Claude Code, and other MCP clients a second opinion from Gemini on plans, implementations, code reviews, architecture tradeoffs, and large files using the `@` syntax.
|
|
19
19
|
|
|
20
20
|
- Ask gemini natural questions, through claude or Brainstorm new ideas in a party of 3!
|
|
21
21
|
|
|
@@ -23,28 +23,66 @@ This is a simple Model Context Protocol (MCP) server that allows AI assistants t
|
|
|
23
23
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/@joeytheman/gemini-mcp-tool/badge" alt="Gemini Tool MCP server" />
|
|
24
24
|
</a>
|
|
25
25
|
|
|
26
|
-
## TLDR: [](#) + [](#)
|
|
26
|
+
## TLDR: [](#) + [](#) + [](#)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
**Goal**: Use Gemini's
|
|
29
|
+
**Goal**: Use Gemini's analysis capabilities directly from Codex or Claude Code for reviews, critiques, and large-file questions.
|
|
30
|
+
|
|
31
|
+
## What's New in 2.2
|
|
32
|
+
|
|
33
|
+
- **Gemini 4 Argon support** — `ask-gemini` and `brainstorm` now default to **Gemini 4 Argon (High)**. Argon Low and Medium are available as explicit overrides when listed by `agy models`. `live-pass` and `screen-review` continue to default to **Gemini 3.8 Flash (Medium)**.
|
|
34
|
+
|
|
35
|
+
## What's New in 2.1
|
|
36
|
+
|
|
37
|
+
- **`live-pass`** — hand a live UI verification to Gemini. It drives its own Playwright (web) or Maestro (iOS simulator) MCP server, captures each requested screen/state/viewport into an artifact directory inside the worktree under test, opens every PNG itself, and returns a PASS/FAIL/BLOCKED verdict with a sentence per frame. The caller never opens an image. The verdict is derived in code from the requested targets, so a frame Gemini forgot, a stale PNG, or an uncovered target fails the pass even when Gemini says PASS.
|
|
38
|
+
- **`screen-review`** — a second pair of eyes on frames someone else captured (`mode: "review"`), or an action inventory of a route file before anything is built (`mode: "plan"`). Also text only.
|
|
39
|
+
- **JSON envelopes for `ask-gemini`** — `outputFormat: "json"` returns agy's envelope: the conversation ID line, the response (or `structured_output` when you pass a `jsonSchema`), and a token-usage line. `conversationId` resumes an exact conversation and **errors** if agy silently started a different one.
|
|
40
|
+
|
|
41
|
+
## What's New in 2.0.2
|
|
42
|
+
|
|
43
|
+
- **Gemini 3.8 Flash support** — the default model is now **Gemini 3.8 Flash (High)** for strong coding and complex-analysis performance. Pass `model: "Gemini 3.8 Flash (Low)"` or `model: "Gemini 3.8 Flash (Medium)"` when latency matters more than the High reasoning budget. Gemini 3.1 Pro (Low/High) remains available as an explicit alternative when exposed by `agy models`.
|
|
44
|
+
|
|
45
|
+
## What's New in 2.0
|
|
46
|
+
|
|
47
|
+
Version 2.0 moves the Gemini backend from the retired Gemini CLI/API path to Google's **Antigravity CLI (`agy`)** and brings the MCP-facing `ask-gemini` workflow along with several capabilities:
|
|
48
|
+
|
|
49
|
+
- **Antigravity backend** — Gemini is reached through `agy --print`.
|
|
50
|
+
- **Opt-in response caching** — LRU cache (30-minute TTL, 10 MB max) for repeated queries, enabled with `AGY_CACHE_ENABLED=true`.
|
|
51
|
+
- **Conversation resume** — continue the latest `agy` conversation or resume a specific one via the `resume` option (`--continue` / `--conversation <id>`).
|
|
52
|
+
- **Extra workspace directories** — add directories to the Antigravity workspace with `includeDirectories` (maps to repeated `--add-dir`).
|
|
53
|
+
- **Configurable print timeout** — bound long-running calls with `printTimeout` (for example `5m` or `90s`; maps to `--print-timeout`).
|
|
54
|
+
- **Working directory control** — run `agy` from a chosen directory (or a drive root on Windows) via `workingDirectory`, supported by both `ask-gemini` and `brainstorm`.
|
|
55
|
+
- **Structured change mode** — `OLD/NEW` edit suggestions Claude can apply directly, with automatic chunking and a `fetch-chunk` tool for large edit sets.
|
|
56
|
+
|
|
57
|
+
**Removed:** there is no longer a Pro→Flash quota fallback, and legacy Gemini CLI flags (`outputFormat`, `extensions`, `debug`, `promptInteractive`, and `approvalMode` other than `yolo`) now return explicit unsupported-option errors.
|
|
30
58
|
|
|
31
59
|
## Prerequisites
|
|
32
60
|
|
|
33
61
|
Before using this tool, ensure you have:
|
|
34
62
|
|
|
35
|
-
1. **[Node.js](https://nodejs.org/)** (
|
|
36
|
-
2. **
|
|
63
|
+
1. **[Node.js](https://nodejs.org/)** (v20.12.0 or higher)
|
|
64
|
+
2. **Antigravity CLI (`agy`)** installed and configured
|
|
65
|
+
|
|
66
|
+
Verify `agy` before adding the MCP server:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
agy --version
|
|
70
|
+
agy install
|
|
71
|
+
agy models
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The `ask-gemini` and `brainstorm` default requires `Gemini 4 Argon (High)` to appear in `agy models` (verified with agy 1.3.4). Google currently lists Argon access in Antigravity for **Google AI Ultra and Enterprise** accounts; see [model availability](https://antigravity.google/docs/models/). If Argon is missing, update Antigravity CLI and check your account's access. You can explicitly select an available Flash tier, such as `model: "Gemini 3.8 Flash (High)"`. The MCP server does not automatically fall back. `live-pass` and `screen-review` continue to require `Gemini 3.8 Flash (Medium)` for their default requests.
|
|
37
75
|
|
|
38
76
|
|
|
39
77
|
### One-Line Setup
|
|
40
78
|
|
|
41
79
|
```bash
|
|
42
|
-
claude mcp add gemini-
|
|
80
|
+
claude mcp add gemini-feedback -- npx -y @joeytheman/gemini-mcp-tool
|
|
43
81
|
```
|
|
44
82
|
|
|
45
83
|
### Verify Installation
|
|
46
84
|
|
|
47
|
-
Type `/mcp` inside Claude Code to verify the gemini-
|
|
85
|
+
Type `/mcp` inside Claude Code to verify the gemini-feedback MCP is active.
|
|
48
86
|
|
|
49
87
|
---
|
|
50
88
|
|
|
@@ -54,7 +92,7 @@ If you already have it configured in Claude Desktop:
|
|
|
54
92
|
|
|
55
93
|
1. Add to your Claude Desktop config:
|
|
56
94
|
```json
|
|
57
|
-
"gemini-
|
|
95
|
+
"gemini-feedback": {
|
|
58
96
|
"command": "npx",
|
|
59
97
|
"args": ["-y", "@joeytheman/gemini-mcp-tool"]
|
|
60
98
|
}
|
|
@@ -76,7 +114,7 @@ Add this configuration to your Claude Desktop config file:
|
|
|
76
114
|
```json
|
|
77
115
|
{
|
|
78
116
|
"mcpServers": {
|
|
79
|
-
"gemini-
|
|
117
|
+
"gemini-feedback": {
|
|
80
118
|
"command": "npx",
|
|
81
119
|
"args": ["-y", "@joeytheman/gemini-mcp-tool"]
|
|
82
120
|
}
|
|
@@ -91,7 +129,7 @@ If you installed globally, use this configuration instead:
|
|
|
91
129
|
```json
|
|
92
130
|
{
|
|
93
131
|
"mcpServers": {
|
|
94
|
-
"gemini-
|
|
132
|
+
"gemini-feedback": {
|
|
95
133
|
"command": "gemini-mcp"
|
|
96
134
|
}
|
|
97
135
|
}
|
|
@@ -100,16 +138,16 @@ If you installed globally, use this configuration instead:
|
|
|
100
138
|
|
|
101
139
|
### Optional: Enable Response Caching
|
|
102
140
|
|
|
103
|
-
To enable the LRU response cache for near-instant repeated queries, add the `
|
|
141
|
+
To enable the LRU response cache for near-instant repeated queries, add the `AGY_CACHE_ENABLED` environment variable:
|
|
104
142
|
|
|
105
143
|
```json
|
|
106
144
|
{
|
|
107
145
|
"mcpServers": {
|
|
108
|
-
"gemini-
|
|
146
|
+
"gemini-feedback": {
|
|
109
147
|
"command": "npx",
|
|
110
148
|
"args": ["-y", "@joeytheman/gemini-mcp-tool"],
|
|
111
149
|
"env": {
|
|
112
|
-
"
|
|
150
|
+
"AGY_CACHE_ENABLED": "true"
|
|
113
151
|
}
|
|
114
152
|
}
|
|
115
153
|
}
|
|
@@ -127,10 +165,27 @@ Caching is disabled by default. When enabled, responses are cached with a 30-min
|
|
|
127
165
|
|
|
128
166
|
After updating the configuration, restart your terminal session.
|
|
129
167
|
|
|
168
|
+
## Live UI passes (setup)
|
|
169
|
+
|
|
170
|
+
`live-pass` needs Gemini to have its own MCP servers registered with Antigravity CLI. Register them once:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
agy mcp add playwright -- npx -y @playwright/mcp@latest --browser chromium --headless --isolated \
|
|
174
|
+
--codegen none --allow-unrestricted-file-access # artifact dirs live inside each worktree, so no fixed --output-dir
|
|
175
|
+
agy mcp add maestro /path/to/maestro mcp
|
|
176
|
+
agy mcp list # playwright + maestro
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`--allow-unrestricted-file-access` is what lets `browser_run_code_unsafe` read each target's capture file and `browser_evaluate` write its layout file in a worktree-local artifact directory; the capture file's `page.screenshot` writes the PNG itself. `--codegen none` keeps `@playwright/mcp` from echoing each capture file's code back into Gemini's history under "Ran Playwright code", which would add about 5.5k characters per frame. A web pass needs a `@playwright/mcp` with `browser_run_code_unsafe` (0.0.84 has it). The tool compensates: `artifactDir` must be inside `workingDirectory`, every frame is checked on disk (matching PNG or JPEG filename and file signature, fresh, non-empty, inside the artifact dir), and the working tree is diffed before and after the run.
|
|
180
|
+
|
|
181
|
+
Add `.live-pass/` to the repository's `.gitignore` so frames never land in a commit; deleting the worktree then removes them. Add `.playwright-mcp/` too — the Playwright MCP server creates that directory in whatever it runs from, which the working-tree guard will otherwise report as a WARNING on every pass.
|
|
182
|
+
|
|
183
|
+
A full pass can run for many minutes inside one MCP call. The server sends keepalive progress notifications every 25 seconds when the client passes a `progressToken`; raise your client's `MCP_TIMEOUT` if a pass outlives it, and consider calling `live-pass` from a small relay sub-agent so the orchestrating session stays free.
|
|
184
|
+
|
|
130
185
|
## Example Workflow
|
|
131
186
|
|
|
132
187
|
- **Natural language**: "use gemini to explain index.html", "understand the massive project using gemini", "ask gemini to search for latest news"
|
|
133
|
-
- **Claude Code**:
|
|
188
|
+
- **Claude Code**: Use `/ask-gemini` directly, or ask naturally for Gemini feedback.
|
|
134
189
|
|
|
135
190
|
## Usage Examples
|
|
136
191
|
|
|
@@ -146,9 +201,9 @@ After updating the configuration, restart your terminal session.
|
|
|
146
201
|
- `use gemini to explain div centering`
|
|
147
202
|
- `ask gemini about best practices for React development related to @file_im_confused_about`
|
|
148
203
|
|
|
149
|
-
### Using
|
|
204
|
+
### Using Antigravity Sandbox Mode
|
|
150
205
|
|
|
151
|
-
The sandbox mode
|
|
206
|
+
The sandbox mode maps to `agy --sandbox`, which runs with terminal restrictions enabled.
|
|
152
207
|
|
|
153
208
|
- `use gemini sandbox to create and run a Python script that processes data`
|
|
154
209
|
- `ask gemini to safely test @script.py and explain what it does`
|
|
@@ -159,29 +214,57 @@ The sandbox mode allows you to safely test code changes, run scripts, or execute
|
|
|
159
214
|
|
|
160
215
|
These tools are designed to be used by the AI assistant.
|
|
161
216
|
|
|
162
|
-
- **`ask-gemini`**:
|
|
163
|
-
- **`prompt`** (required): The analysis request. Use the `@` syntax to include file or directory references (e.g., `@src/main.js
|
|
164
|
-
- **`model`** (optional): The
|
|
165
|
-
- **`sandbox`** (optional): Set to `true` to
|
|
217
|
+
- **`ask-gemini`**: Ask Gemini through Antigravity CLI (`agy`) for plan review, implementation critique, code review, architecture feedback, debugging, and tradeoff analysis.
|
|
218
|
+
- **`prompt`** (required): The analysis request. Use the `@` syntax to include file or directory references (e.g., `@src/main.js review this implementation`) or ask general questions.
|
|
219
|
+
- **`model`** (optional): The Antigravity model to use. Defaults to `Gemini 4 Argon (High)`. Other verified options include `Gemini 4 Argon (Low)`, `Gemini 4 Argon (Medium)`, and `Gemini 3.8 Flash (Low/Medium/High)` (use the exact tier name, such as `Gemini 3.8 Flash (High)`). Availability depends on your CLI version and account; check `agy models`. Some accounts also expose `Gemini 3.1 Pro (Low)` and `Gemini 3.1 Pro (High)`.
|
|
220
|
+
- **`sandbox`** (optional): Set to `true` to pass `--sandbox`.
|
|
166
221
|
- **`changeMode`** (optional): Enable structured change mode for edit suggestions that Claude can apply directly.
|
|
167
|
-
- **`yolo`** (optional):
|
|
168
|
-
- **`
|
|
169
|
-
- **`
|
|
170
|
-
- **`
|
|
171
|
-
- **`
|
|
172
|
-
- **`
|
|
173
|
-
- **`
|
|
174
|
-
- **`
|
|
175
|
-
- **`workingDirectory`** (optional): Working directory to run
|
|
222
|
+
- **`yolo`** (optional): Pass `--dangerously-skip-permissions`. Use with caution.
|
|
223
|
+
- **`includeDirectories`** (optional): Additional directories to include in the Antigravity workspace; maps to repeated `--add-dir`.
|
|
224
|
+
- **`printTimeout`** (optional): Pass `--print-timeout`: a duration such as `90s`, `15m`, `2h30m` or `2.5h`, or `0` for no limit; other values are rejected. The server stops agy 2 minutes after this limit, or after 2 hours when no limit is given.
|
|
225
|
+
- **`resume`** (optional): `true`, `latest`, or `continue` maps to `--continue`; any other string maps to `--conversation <id>`.
|
|
226
|
+
- **`conversationId`** (optional): Resume this exact conversation (`--conversation <id>`); wins over `resume`. The call fails with `CONVERSATION_NOT_RESUMED` when agy silently starts a different conversation — a stale ID only warns on stderr and would otherwise return an unrelated answer. IDs come from the `[GEMINI_CONVERSATION_ID=...]` line of a previous json call; a text call reports its ID only when it fails with an empty answer.
|
|
227
|
+
- **`outputFormat`** (optional): `"text"` (default) or `"json"`. `"json"` returns agy's envelope rendered as the conversation line, the answer, and a usage line. `"stream-json"` is unsupported.
|
|
228
|
+
- **`jsonSchema`** (optional): Inline JSON Schema passed to `--json-schema`, constraining the answer to `structured_output`. Requires `outputFormat: "json"`.
|
|
229
|
+
- **`effort`** (optional): `low`, `medium`, or `high`, passed to `--effort`. Tiered Gemini names already encode effort in the name, and agy rejects `--effort` for them — the rejection is surfaced verbatim.
|
|
230
|
+
- **`workingDirectory`** (optional): Working directory to run `agy` from. Use drive root (e.g., 'C:/' or 'D:/') on Windows to access files across drives.
|
|
231
|
+
- **Unsupported legacy options**: `extensions`, `debug`, `promptInteractive`, `outputFormat: "stream-json"`, and `approvalMode` except `approvalMode: "yolo"`.
|
|
232
|
+
|
|
233
|
+
- **`live-pass`**: Delegate a live UI verification to Gemini. It drives its own Playwright or Maestro MCP server, captures the requested frames, looks at them, and returns text.
|
|
234
|
+
- **`targets`** (required): One `{screen, state, viewport, expected}` per frame the pass must produce; for `playwright`, `viewport` must be `WxH`, such as `390x844`. `expected` is what that state must show — for an intentional error/empty/offline state, name the reason text and the recovery affordance. The effective verdict is derived from this list: **any target that produced no frame fails the pass**, whether Gemini skipped it with a reason or never reported it at all. A skip explains missing evidence; it does not excuse it. Each target's screenshot filename is derived in code from its `screen`/`state`/`viewport`, so two targets can never be answered by one image — a reused file is reported as `DUPLICATE` and fails the pass.
|
|
235
|
+
- **`brief`** (required): Every fact Gemini needs, because the pass forbids it to read product code, tests, docs, tool schemas or the database, or to run git: the app's start URL or app id, any link a user arrives by (an invite, email or share link) with where it comes from, the served-branch marker, and per target the user path, the labels or testIDs on it, the seed account and ids, and how long toasts and pending states stay on screen. App specifics belong here, not in the tool. The pass opens the app once, at its start URL (or by launching it on a device). A target may begin at a link of its own only when the brief says the user arrives by that link and names where it comes from; that frame quotes the source in `link_source`, and the report prints it. Every other target is reached by taps from the previous screen (sign-in form, navigation taps, form submits; no scripted shortcuts), and any other URL in the brief is context, never a navigation target. A reload opens the current URL, and only where the brief makes it part of the path. A target whose path breaks is skipped with the step where it broke, and one whose fact is missing is skipped with `brief missing: <fact>`; either fails the pass. The branch and HEAD of `workingDirectory` are read in code and printed in the prompt as context: the pass is BLOCKED only when the app's served-branch marker contradicts them (a detached HEAD never contradicts a branch name).
|
|
236
|
+
- **`workingDirectory`** (required): Absolute path to the worktree under test. Used as agy's cwd and workspace root.
|
|
237
|
+
- **`driver`** (required): `playwright` or `maestro`.
|
|
238
|
+
- **`artifactDir`** (optional): Absolute path, strictly inside `workingDirectory`. Defaults to `<workingDirectory>/.live-pass/<timestamp>`, so deleting the worktree cleans up the frames. Add `.live-pass/` to the repo's `.gitignore`.
|
|
239
|
+
- **`publishCommand`** (optional): Shell command run **in code** after validation, with `{files}` replaced by the shell-quoted paths of the frames that validated OK; its stdout is returned as PR markdown. Never run on a BLOCKED pass.
|
|
240
|
+
- **`conversationId`** (optional): Resume the previous pass so Gemini reports deltas (`resolved`, `regressions`, `changes`) instead of a bare snapshot. The browser or simulator still starts fresh — resume only changes what Gemini remembers. Start fresh when the brief changed materially, after a BLOCKED pass, or after about three rounds (each resumed round roughly doubles input tokens).
|
|
241
|
+
- **`yolo`** (optional, default **true**): Without `--dangerously-skip-permissions`, headless agy denies the MCP tool calls and the pass is BLOCKED. The tool snapshots the working tree before and after — status lines *and* content hashes of every dirty file, since a file Gemini rewrites in a PR worktree was already modified before the run — and reports any change as a WARNING, including when the agy call itself fails.
|
|
242
|
+
- **`model`** (optional, default `Gemini 3.8 Flash (Medium)`), **`printTimeout`** (optional, default `15m`).
|
|
243
|
+
- **Capture files and layout checks** (`playwright` only): before agy starts, the tool writes one capture file per target next to its frame, `<frame>.capture.js`, and Gemini runs it with `browser_run_code_unsafe {filename}` once the state has settled. In one call it sets the target's viewport, measures how far the page or its largest shown scroller overflows (only scrollers inside an open `aria-modal` sheet count while one shows, and textareas never do), grows the viewport by that much and measures again, a few times at most, while the overflow keeps shrinking and up to 4000 px tall (a screen that still scrolls after that is shot as it stands), saves the PNG, runs a fixed layout snippet in the page at the shot's size, and restores the viewport. A failed restore is noted in the call's answer and never hides the capture's own result or error. Gemini then saves that snippet's result next to the frame as `<frame>.layout.json` with a one-line `browser_evaluate`. Neither snippet is in Gemini's prompt. A failed screenshot fails the capture, so Gemini skips the target; a failed layout step still keeps the frame and leaves the layout file "not run". The layout check lists horizontal page scroll, text spilling its box, clipped text, controls covered by something else (hit-testing only the part of a control its scroll containers show, and skipping controls under an open `aria-modal` sheet, and a control in a horizontal scroller only when what covers it belongs to that scroller's row: a sibling, or the row's edge control within the row's height), and controls cut off at the screen edge, each as `{kind, text, testID, rect}` (at most 50). A framework dev overlay (Expo's `#error-overlay` / `#error-toast`) is kept out of every check and reported once as `dev-overlay` when its host or anything inside it shows, including inside the host's shadow root as Expo draws it, with the largest shown box and its text, marking the frame as polluted. Text capped with `-webkit-line-clamp` or `text-overflow: ellipsis` (React Native Web's `numberOfLines`) is listed separately as "capped (line clamp / ellipsis)". The report's "Layout checks (measured in the page)" section shows each frame's entries, or "not run" when the file is missing, stale, outside `artifactDir`, malformed, or measured at another width than the frame. The snippet's readable source is `src/layout-check.snippet.js`; `npm run snippet:build` regenerates the minified constant, and `npm test` and the CI Chromium step fail when the two drift. The caller judges the entries; they never change the verdict.
|
|
244
|
+
- The usage line is followed by Gemini's model steps, read from agy's conversation store (`~/.gemini/antigravity-cli/conversations/<id>.db`), and per frame for a live pass. It prints `?` when the store can't be read.
|
|
245
|
+
|
|
246
|
+
- **`screen-review`**: Have Gemini judge captured screens, or inventory a route before it is built.
|
|
247
|
+
- **`mode`** (required): `review` judges PNGs after a live pass; `plan` inventories a route file and returns no verdict.
|
|
248
|
+
- **`instructions`** (required): The caller's reviewer prompt, passed through verbatim so rule citations stay exact.
|
|
249
|
+
- **`workingDirectory`** (required): The checkout the instructions cite; used as agy's cwd so repository-relative rule paths resolve.
|
|
250
|
+
- **`images`** (review mode, required): Absolute PNG/JPEG paths that must exist. **`routeFile`** (plan mode, required): absolute path inside `workingDirectory`. Neither may contain whitespace — agy resolves an `@reference` up to the first space, so a spaced path would hang until `printTimeout`; the tool rejects it up front instead.
|
|
251
|
+
- **`context`** (optional): Composition contract, issue text, anything else the reviewer prompt cites.
|
|
252
|
+
- **`changedRoutes`** (optional, review mode): Repo-relative files the diff touched (from `git diff --name-only`). Gemini maps each image to the route file it shows, and a changed screen (under an `app/` directory) that no image shows makes the review REQUEST_CHANGES — partial screenshot coverage is as unreviewed as none. A changed file under `components/` only warns. The report prints a route → image coverage table.
|
|
253
|
+
- **`baseRef`** (optional, review mode): The git ref the diff is based on, such as `origin/main`. The tool resolves its merge base with HEAD in code and fails before calling Gemini when it does not resolve. Gemini may then run read-only `git diff <merge-base>...HEAD` (optionally `--name-only`, `-- <paths>`) and `git show <merge-base>:<path>`, so severity follows the branch's own diff (set `yolo: true`, since headless agy denies the shell otherwise); without it Gemini runs no git. Both modes bound Gemini's reads to the route file each image shows (or the plan's route file) and the components and primitives it renders, the changed files, the producer lines a capability check needs, and the cited rule files, READMEs and boards, and forbid running tests or npm and reading `node_modules` or release notes.
|
|
254
|
+
- **`conversationId`** (optional): Round 2 keeps round 1's visual analysis.
|
|
255
|
+
- **`model`** (optional, default `Gemini 3.8 Flash (Medium)`), **`printTimeout`** (optional, default `10m`).
|
|
256
|
+
- **`yolo`** (optional, default **false**): a review only reads. Set it to `true` when the instructions ask Gemini to read repository files that are not among the images — it reaches for a shell to do that, headless agy denies the shell, and the call fails naming the denied action.
|
|
176
257
|
|
|
177
258
|
- **`brainstorm`**: Generate creative ideas with structured methodologies and domain context.
|
|
178
259
|
- **`prompt`** (required): Brainstorming challenge or question to explore.
|
|
179
|
-
- **`model`** (optional): The
|
|
260
|
+
- **`model`** (optional): The Antigravity model to use. Defaults to `Gemini 4 Argon (High)`. Pass an exact model listed by `agy models` to override it.
|
|
180
261
|
- **`methodology`** (optional): Framework to use: `divergent`, `convergent`, `scamper`, `design-thinking`, `lateral`, or `auto` (default).
|
|
181
262
|
- **`domain`** (optional): Domain context (e.g., 'software', 'business', 'creative', 'research').
|
|
182
263
|
- **`constraints`** (optional): Known limitations or requirements.
|
|
264
|
+
- **`existingContext`** (optional): Background information, previous attempts, or current state to build upon.
|
|
183
265
|
- **`ideaCount`** (optional): Number of ideas to generate (default: 12).
|
|
184
266
|
- **`includeAnalysis`** (optional): Include feasibility and impact analysis (default: true).
|
|
267
|
+
- **`workingDirectory`** (optional): Working directory to run `agy` from.
|
|
185
268
|
|
|
186
269
|
- **`fetch-chunk`**: Retrieve cached chunks from large changeMode responses.
|
|
187
270
|
- **`cacheKey`** (required): Cache key from initial changeMode response.
|
|
@@ -190,26 +273,35 @@ These tools are designed to be used by the AI assistant.
|
|
|
190
273
|
- **`ping`**: Echo test message to verify server connection.
|
|
191
274
|
- **`prompt`** (optional): Message to echo back.
|
|
192
275
|
|
|
193
|
-
- **`Help`**: Display
|
|
276
|
+
- **`Help`**: Display Antigravity CLI (`agy`) help information.
|
|
194
277
|
|
|
195
278
|
### Slash Commands (for the User)
|
|
196
279
|
|
|
197
280
|
You can use these commands directly in Claude Code's interface (compatibility with other clients has not been tested).
|
|
198
281
|
|
|
199
|
-
- **/ask-gemini**:
|
|
282
|
+
- **/ask-gemini**: Ask Gemini through Antigravity CLI with caching and change mode.
|
|
200
283
|
- **Example**: `/ask-gemini prompt:@src/ summarize this directory`
|
|
201
284
|
- **With sandbox**: `/ask-gemini prompt:@script.py test this safely sandbox:true`
|
|
202
285
|
- **With change mode**: `/ask-gemini prompt:Refactor this code changeMode:true`
|
|
203
|
-
- **
|
|
286
|
+
- **Supported backend flags**: model, sandbox, yolo, includeDirectories, printTimeout, resume, workingDirectory.
|
|
204
287
|
|
|
205
288
|
- **/brainstorm**: Generate structured ideas with creative methodologies.
|
|
206
289
|
- **Example**: `/brainstorm prompt:How can we improve user onboarding? methodology:design-thinking domain:software`
|
|
207
290
|
- **Quick use**: `/brainstorm prompt:Ideas for a mobile app feature`
|
|
291
|
+
- **Supported flags**: model, methodology, domain, constraints, existingContext, ideaCount, includeAnalysis, workingDirectory.
|
|
292
|
+
|
|
293
|
+
- **/live-pass**: Walk a running app with Gemini and get a text verdict with per-frame observations.
|
|
294
|
+
- **Example**: `/live-pass driver:playwright workingDirectory:/path/to/worktree brief:"Base URL http://localhost:8081; marker: footer BRANCH: my-branch" targets:[{"screen":"home","state":"default","viewport":"390x844","expected":"order list with one seeded order"}]`
|
|
295
|
+
- **Supported flags**: targets, brief, workingDirectory, driver, artifactDir, model, printTimeout, yolo, publishCommand, conversationId.
|
|
296
|
+
|
|
297
|
+
- **/screen-review**: Judge captured frames, or inventory a route file before building it.
|
|
298
|
+
- **Example**: `/screen-review mode:review workingDirectory:/path/to/worktree images:["/path/to/.live-pass/run/home-default-390x844.png"] instructions:"<reviewer prompt>"`
|
|
299
|
+
- **Plan mode**: `/screen-review mode:plan workingDirectory:/path/to/worktree routeFile:/path/to/app/orders.tsx instructions:"<action inventory rules>"`
|
|
208
300
|
|
|
209
301
|
- **/fetch-chunk**: Retrieve next chunk of a large changeMode response.
|
|
210
302
|
- **Example**: `/fetch-chunk cacheKey:abc123 chunkIndex:2`
|
|
211
303
|
|
|
212
|
-
- **/Help**: Display
|
|
304
|
+
- **/Help**: Display Antigravity CLI help information.
|
|
213
305
|
- **Example**: `/Help`
|
|
214
306
|
|
|
215
307
|
- **/ping**: Test the MCP server connection.
|
|
@@ -219,7 +311,7 @@ You can use these commands directly in Claude Code's interface (compatibility wi
|
|
|
219
311
|
|
|
220
312
|
This MCP server includes several performance optimizations:
|
|
221
313
|
|
|
222
|
-
- **LRU Response Cache** (opt-in): Near-instant responses for repeated queries with 30-minute TTL and 10MB max size. Enable via `
|
|
314
|
+
- **LRU Response Cache** (opt-in): Near-instant responses for repeated queries with 30-minute TTL and 10MB max size. Enable via `AGY_CACHE_ENABLED=true` env var.
|
|
223
315
|
- **Efficient Command Execution**: O(n) array buffer performance for large outputs
|
|
224
316
|
- **Smart Chunking**: Large changeMode responses are automatically chunked for better handling
|
|
225
317
|
- **Progress Notifications**: Real-time progress updates during long-running operations
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agyModelSteps.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/agyModelSteps.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { agyConversationStore, countAgyModelSteps, formatModelSteps } from '../utils/agyModelSteps.js';
|
|
7
|
+
const CONVERSATION = '6a157307-cd13-4652-8725-f3500c103f2b';
|
|
8
|
+
const roots = [];
|
|
9
|
+
function storeWithSteps(stepTypes) {
|
|
10
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agy-steps-'));
|
|
11
|
+
roots.push(root);
|
|
12
|
+
const store = agyConversationStore(CONVERSATION, root);
|
|
13
|
+
fs.mkdirSync(path.dirname(store), { recursive: true });
|
|
14
|
+
const rows = stepTypes.map((type, idx) => `insert into steps (idx, step_type) values (${idx}, ${type});`).join('');
|
|
15
|
+
execFileSync('sqlite3', [store, `pragma journal_mode=wal;create table steps (idx integer primary key, step_type integer not null default 0);${rows}pragma wal_checkpoint(truncate);`]);
|
|
16
|
+
for (const side of ['-wal', '-shm'])
|
|
17
|
+
fs.rmSync(`${store}${side}`, { force: true });
|
|
18
|
+
return root;
|
|
19
|
+
}
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
for (const root of roots.splice(0))
|
|
22
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
describe('countAgyModelSteps', () => {
|
|
25
|
+
it("counts only agy's model steps (step_type 15), not the user turn or tool results", async () => {
|
|
26
|
+
const root = storeWithSteps([14, 15, 132, 15, 132, 15]);
|
|
27
|
+
await expect(countAgyModelSteps(CONVERSATION, root)).resolves.toBe(3);
|
|
28
|
+
});
|
|
29
|
+
it("reads agy's write-ahead-log store without leaving side files next to it", async () => {
|
|
30
|
+
const root = storeWithSteps([15, 15]);
|
|
31
|
+
const store = agyConversationStore(CONVERSATION, root);
|
|
32
|
+
await expect(countAgyModelSteps(CONVERSATION, root)).resolves.toBe(2);
|
|
33
|
+
expect(fs.readdirSync(path.dirname(store))).toEqual([path.basename(store)]);
|
|
34
|
+
});
|
|
35
|
+
it('is null when the conversation has no step store', async () => {
|
|
36
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agy-steps-'));
|
|
37
|
+
roots.push(root);
|
|
38
|
+
await expect(countAgyModelSteps(CONVERSATION, root)).resolves.toBeNull();
|
|
39
|
+
});
|
|
40
|
+
it('is null for a conversation id that is not a uuid, without touching the filesystem', async () => {
|
|
41
|
+
await expect(countAgyModelSteps('../../etc/passwd')).resolves.toBeNull();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('formatModelSteps', () => {
|
|
45
|
+
it('divides the steps by the reported frames, to one decimal', () => {
|
|
46
|
+
expect(formatModelSteps(81, 6)).toBe('Gemini steps: 81 (13.5 per frame, 6 frames)');
|
|
47
|
+
});
|
|
48
|
+
it('says so when no frame was reported', () => {
|
|
49
|
+
expect(formatModelSteps(12, 0)).toBe('Gemini steps: 12, no frames');
|
|
50
|
+
});
|
|
51
|
+
it('prints the bare count for a review, which has no frames', () => {
|
|
52
|
+
expect(formatModelSteps(9)).toBe('Gemini steps: 9');
|
|
53
|
+
});
|
|
54
|
+
it("marks an unreadable step store with ?", () => {
|
|
55
|
+
expect(formatModelSteps(null, 3)).toBe("Gemini steps: ? (agy's step store was unreadable)");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=agyModelSteps.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agyModelSteps.test.js","sourceRoot":"","sources":["../../src/__tests__/agyModelSteps.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,47 +1,58 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
const mockExecuteAgyCLI = vi.fn();
|
|
3
|
+
const mockExecuteAgyJson = vi.fn();
|
|
4
|
+
vi.mock('../utils/agyExecutor.js', async (importOriginal) => {
|
|
5
|
+
const actual = await importOriginal();
|
|
6
|
+
return {
|
|
7
|
+
...actual,
|
|
8
|
+
executeAgyCLI: (...args) => mockExecuteAgyCLI(...args),
|
|
9
|
+
executeAgyJson: (...args) => mockExecuteAgyJson(...args),
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
import { askGeminiArgsSchema, askGeminiTool } from '../tools/ask-gemini.tool.js';
|
|
3
13
|
describe('ask-gemini Tool', () => {
|
|
4
14
|
describe('Argument Schema Validation', () => {
|
|
5
|
-
const askGeminiArgsSchema = z.object({
|
|
6
|
-
prompt: z.string().min(1),
|
|
7
|
-
model: z.string().optional(),
|
|
8
|
-
sandbox: z.boolean().default(false),
|
|
9
|
-
changeMode: z.boolean().default(false),
|
|
10
|
-
chunkIndex: z.union([z.number(), z.string()]).optional(),
|
|
11
|
-
chunkCacheKey: z.string().optional(),
|
|
12
|
-
yolo: z.boolean().default(false),
|
|
13
|
-
approvalMode: z.enum(['default', 'auto_edit', 'yolo']).optional(),
|
|
14
|
-
outputFormat: z.enum(['text', 'json', 'stream-json']).optional(),
|
|
15
|
-
includeDirectories: z.union([z.string(), z.array(z.string())]).optional(),
|
|
16
|
-
debug: z.boolean().default(false),
|
|
17
|
-
promptInteractive: z.string().optional(),
|
|
18
|
-
extensions: z.union([z.string(), z.array(z.string())]).optional(),
|
|
19
|
-
resume: z.string().optional(),
|
|
20
|
-
});
|
|
21
15
|
it('should accept valid minimal arguments', () => {
|
|
22
16
|
const result = askGeminiArgsSchema.parse({ prompt: 'test prompt' });
|
|
23
17
|
expect(result.prompt).toBe('test prompt');
|
|
24
18
|
expect(result.sandbox).toBe(false);
|
|
25
19
|
expect(result.changeMode).toBe(false);
|
|
26
20
|
});
|
|
27
|
-
it('should accept
|
|
21
|
+
it('should accept supported agy optional flags', () => {
|
|
28
22
|
const result = askGeminiArgsSchema.parse({
|
|
29
23
|
prompt: 'test prompt',
|
|
30
|
-
model: '
|
|
24
|
+
model: 'Gemini 3.8 Flash (Medium)',
|
|
31
25
|
sandbox: true,
|
|
32
26
|
changeMode: true,
|
|
33
27
|
yolo: true,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
includeDirectories: ['src', 'tests'],
|
|
29
|
+
printTimeout: '10m',
|
|
30
|
+
resume: true,
|
|
31
|
+
workingDirectory: '/tmp/project',
|
|
37
32
|
});
|
|
38
|
-
expect(result.model).toBe('
|
|
33
|
+
expect(result.model).toBe('Gemini 3.8 Flash (Medium)');
|
|
39
34
|
expect(result.sandbox).toBe(true);
|
|
40
35
|
expect(result.changeMode).toBe(true);
|
|
41
36
|
expect(result.yolo).toBe(true);
|
|
37
|
+
expect(result.includeDirectories).toEqual(['src', 'tests']);
|
|
38
|
+
expect(result.printTimeout).toBe('10m');
|
|
39
|
+
expect(result.resume).toBe(true);
|
|
40
|
+
expect(result.workingDirectory).toBe('/tmp/project');
|
|
41
|
+
});
|
|
42
|
+
it('should accept legacy fields so implementation can return explicit unsupported errors', () => {
|
|
43
|
+
const result = askGeminiArgsSchema.parse({
|
|
44
|
+
prompt: 'test prompt',
|
|
45
|
+
approvalMode: 'auto_edit',
|
|
46
|
+
outputFormat: 'json',
|
|
47
|
+
debug: true,
|
|
48
|
+
extensions: ['ts', 'js'],
|
|
49
|
+
promptInteractive: 'continue after this',
|
|
50
|
+
});
|
|
42
51
|
expect(result.approvalMode).toBe('auto_edit');
|
|
43
52
|
expect(result.outputFormat).toBe('json');
|
|
44
53
|
expect(result.debug).toBe(true);
|
|
54
|
+
expect(result.extensions).toEqual(['ts', 'js']);
|
|
55
|
+
expect(result.promptInteractive).toBe('continue after this');
|
|
45
56
|
});
|
|
46
57
|
it('should reject missing prompt', () => {
|
|
47
58
|
expect(() => askGeminiArgsSchema.parse({})).toThrow();
|
|
@@ -89,6 +100,22 @@ describe('ask-gemini Tool', () => {
|
|
|
89
100
|
});
|
|
90
101
|
expect(result.extensions).toEqual(['ts', 'js']);
|
|
91
102
|
});
|
|
103
|
+
it('should accept the json envelope arguments', () => {
|
|
104
|
+
const result = askGeminiArgsSchema.parse({
|
|
105
|
+
prompt: 'test',
|
|
106
|
+
outputFormat: 'json',
|
|
107
|
+
jsonSchema: '{"type":"object"}',
|
|
108
|
+
effort: 'medium',
|
|
109
|
+
conversationId: 'conv-abc',
|
|
110
|
+
});
|
|
111
|
+
expect(result.outputFormat).toBe('json');
|
|
112
|
+
expect(result.jsonSchema).toBe('{"type":"object"}');
|
|
113
|
+
expect(result.effort).toBe('medium');
|
|
114
|
+
expect(result.conversationId).toBe('conv-abc');
|
|
115
|
+
});
|
|
116
|
+
it('should reject an effort level agy does not accept', () => {
|
|
117
|
+
expect(() => askGeminiArgsSchema.parse({ prompt: 'test', effort: 'extreme' })).toThrow();
|
|
118
|
+
});
|
|
92
119
|
it('should accept resume parameter', () => {
|
|
93
120
|
const result = askGeminiArgsSchema.parse({
|
|
94
121
|
prompt: 'test',
|
|
@@ -96,6 +123,13 @@ describe('ask-gemini Tool', () => {
|
|
|
96
123
|
});
|
|
97
124
|
expect(result.resume).toBe('latest');
|
|
98
125
|
});
|
|
126
|
+
it('should accept boolean resume parameter', () => {
|
|
127
|
+
const result = askGeminiArgsSchema.parse({
|
|
128
|
+
prompt: 'test',
|
|
129
|
+
resume: true,
|
|
130
|
+
});
|
|
131
|
+
expect(result.resume).toBe(true);
|
|
132
|
+
});
|
|
99
133
|
it('should accept chunkIndex as number', () => {
|
|
100
134
|
const result = askGeminiArgsSchema.parse({
|
|
101
135
|
prompt: 'test',
|
|
@@ -112,12 +146,6 @@ describe('ask-gemini Tool', () => {
|
|
|
112
146
|
});
|
|
113
147
|
});
|
|
114
148
|
describe('Flag Combinations', () => {
|
|
115
|
-
const askGeminiArgsSchema = z.object({
|
|
116
|
-
prompt: z.string().min(1),
|
|
117
|
-
sandbox: z.boolean().default(false),
|
|
118
|
-
yolo: z.boolean().default(false),
|
|
119
|
-
approvalMode: z.enum(['default', 'auto_edit', 'yolo']).optional(),
|
|
120
|
-
});
|
|
121
149
|
it('should allow sandbox with yolo', () => {
|
|
122
150
|
const result = askGeminiArgsSchema.parse({
|
|
123
151
|
prompt: 'test',
|
|
@@ -127,7 +155,7 @@ describe('ask-gemini Tool', () => {
|
|
|
127
155
|
expect(result.sandbox).toBe(true);
|
|
128
156
|
expect(result.yolo).toBe(true);
|
|
129
157
|
});
|
|
130
|
-
it('should allow approvalMode
|
|
158
|
+
it('should allow legacy approvalMode through schema for executor validation', () => {
|
|
131
159
|
const result = askGeminiArgsSchema.parse({
|
|
132
160
|
prompt: 'test',
|
|
133
161
|
yolo: true,
|
|
@@ -135,20 +163,54 @@ describe('ask-gemini Tool', () => {
|
|
|
135
163
|
});
|
|
136
164
|
expect(result.yolo).toBe(true);
|
|
137
165
|
expect(result.approvalMode).toBe('auto_edit');
|
|
138
|
-
// Note: In actual implementation, approvalMode should take precedence
|
|
139
166
|
});
|
|
140
167
|
});
|
|
141
|
-
describe('
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
describe('json output', () => {
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
vi.clearAllMocks();
|
|
171
|
+
mockExecuteAgyJson.mockResolvedValue({
|
|
172
|
+
conversation_id: 'conv-abc',
|
|
173
|
+
status: 'SUCCESS',
|
|
174
|
+
response: 'plain answer',
|
|
175
|
+
structured_output: { ok: true },
|
|
176
|
+
num_turns: 1,
|
|
177
|
+
duration_seconds: 3,
|
|
178
|
+
usage: { input_tokens: 10, output_tokens: 2, total_tokens: 12 },
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
it('starts with the conversation line and renders structured output under a schema', async () => {
|
|
182
|
+
const result = await askGeminiTool.execute({
|
|
183
|
+
prompt: 'Return ok true',
|
|
184
|
+
outputFormat: 'json',
|
|
185
|
+
jsonSchema: '{"type":"object"}',
|
|
186
|
+
conversationId: 'conv-abc',
|
|
187
|
+
});
|
|
188
|
+
expect(result.startsWith('[GEMINI_CONVERSATION_ID=conv-abc]')).toBe(true);
|
|
189
|
+
expect(result).toContain('"ok": true');
|
|
190
|
+
expect(result).toContain('Usage:');
|
|
191
|
+
expect(mockExecuteAgyJson).toHaveBeenCalledWith('Return ok true', expect.objectContaining({ conversationId: 'conv-abc', jsonSchema: '{"type":"object"}' }), undefined);
|
|
192
|
+
expect(mockExecuteAgyCLI).not.toHaveBeenCalled();
|
|
193
|
+
});
|
|
194
|
+
it('renders the plain response when no schema was passed', async () => {
|
|
195
|
+
const result = await askGeminiTool.execute({ prompt: 'hi', outputFormat: 'json' });
|
|
196
|
+
expect(result).toContain('plain answer');
|
|
197
|
+
expect(result).not.toContain('"ok"');
|
|
198
|
+
});
|
|
199
|
+
it('uses the text path for the default output format', async () => {
|
|
200
|
+
mockExecuteAgyCLI.mockResolvedValue('text answer');
|
|
201
|
+
const result = await askGeminiTool.execute({ prompt: 'hi' });
|
|
202
|
+
expect(result).toContain('text answer');
|
|
203
|
+
expect(mockExecuteAgyJson).not.toHaveBeenCalled();
|
|
144
204
|
});
|
|
205
|
+
});
|
|
206
|
+
describe('Error Messages', () => {
|
|
145
207
|
it('should provide clear error for missing prompt', () => {
|
|
146
208
|
try {
|
|
147
209
|
askGeminiArgsSchema.parse({});
|
|
148
210
|
}
|
|
149
211
|
catch (error) {
|
|
150
212
|
expect(error.issues[0].path).toContain('prompt');
|
|
151
|
-
expect(error.issues[0].message).toMatch(/required/i);
|
|
213
|
+
expect(error.issues[0].message).toMatch(/required|expected string/i);
|
|
152
214
|
}
|
|
153
215
|
});
|
|
154
216
|
it('should provide clear error for empty prompt', () => {
|
|
@@ -157,7 +219,7 @@ describe('ask-gemini Tool', () => {
|
|
|
157
219
|
}
|
|
158
220
|
catch (error) {
|
|
159
221
|
expect(error.issues[0].path).toContain('prompt');
|
|
160
|
-
expect(error.issues[0].message).toMatch(/at least 1/i);
|
|
222
|
+
expect(error.issues[0].message).toMatch(/at least 1|>=1/i);
|
|
161
223
|
}
|
|
162
224
|
});
|
|
163
225
|
});
|