From 79b8a741e073f8bf34af1f9ebf876c3c7198fc0b Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 31 Jul 2026 11:45:19 +0800 Subject: [PATCH 1/3] fix: use documented Antigravity MCP config path --- src/cli/agents/antigravity.ts | 18 ++++++++---------- src/cli/tui/agents.ts | 4 ++-- tests/unit/cli/tui/agents.test.ts | 8 ++++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/cli/agents/antigravity.ts b/src/cli/agents/antigravity.ts index c4c371962..2c0da7446 100644 --- a/src/cli/agents/antigravity.ts +++ b/src/cli/agents/antigravity.ts @@ -1,11 +1,9 @@ /** * Antigravity integration. * - * Antigravity is Anthropic's IDE (VS Code-derived), storing config in ~/.antigravity/. - * MCP config format mirrors VS Code's mcp.json (mcpServers key). - * - * TODO: When Antigravity's official MCP config docs are published, verify the - * exact config path and key structure. + * Antigravity is Google's IDE. + * The global MCP configuration is ~/.gemini/config/mcp_config.json. + * The file stores servers under the mcpServers key. */ import { existsSync, mkdirSync } from 'node:fs'; import { join } from 'node:path'; @@ -16,7 +14,7 @@ import { mergeMcpJson, removeMcpJson } from './utils.js'; const MCP_KEY_PATH = ['mcpServers', 'wigolo']; function antigravityDir(): string { - return join(homedir(), '.antigravity'); + return join(homedir(), '.gemini', 'config'); } function detect(): boolean { @@ -32,16 +30,16 @@ function detect(): boolean { async function installMcp(cmd: { command: string; args: string[] }): Promise { const dir = antigravityDir(); mkdirSync(dir, { recursive: true }); - const configPath = join(dir, 'mcp.json'); + const configPath = join(dir, 'mcp_config.json'); mergeMcpJson(configPath, { command: cmd.command, args: cmd.args }, MCP_KEY_PATH); } async function uninstall(): Promise<{ removed: string[] }> { const removed: string[] = []; - const configPath = join(antigravityDir(), 'mcp.json'); + const configPath = join(antigravityDir(), 'mcp_config.json'); if (existsSync(configPath)) { removeMcpJson(configPath, MCP_KEY_PATH); - removed.push('~/.antigravity/mcp.json (wigolo entry)'); + removed.push('~/.gemini/config/mcp_config.json (wigolo entry)'); } return { removed }; } @@ -53,7 +51,7 @@ export const antigravityHandler = { supportsCommands: false, detect, installMcp, - // No instructions layer for antigravity (config format unclear, MCP only) + // Antigravity has no instruction file in this integration. installInstructions: async () => { /* noop */ }, uninstall, }; diff --git a/src/cli/tui/agents.ts b/src/cli/tui/agents.ts index d0d0c9ec3..c12f5f3cf 100644 --- a/src/cli/tui/agents.ts +++ b/src/cli/tui/agents.ts @@ -95,8 +95,8 @@ const antigravity: AgentDescriptor = { installType: 'config-file', detect: ({ home }) => binaryInPath('antigravity') !== null || - dirExists(join(home, '.antigravity')), - configPath: ({ home }) => join(home, '.antigravity', 'mcp.json'), + dirExists(join(home, '.gemini', 'config')), + configPath: ({ home }) => join(home, '.gemini', 'config', 'mcp_config.json'), }; export const AGENTS: readonly AgentDescriptor[] = [ diff --git a/tests/unit/cli/tui/agents.test.ts b/tests/unit/cli/tui/agents.test.ts index bd9c65f0b..35ef69226 100644 --- a/tests/unit/cli/tui/agents.test.ts +++ b/tests/unit/cli/tui/agents.test.ts @@ -250,13 +250,13 @@ describe('Antigravity descriptor', () => { expect(getDescriptor('antigravity').detect(ENV)).toBe(true); }); - it('detects when ~/.antigravity dir exists', () => { - vi.mocked(dirExists).mockImplementation((p) => p === join('/home/test', '.antigravity')); + it('detects when ~/.gemini/config dir exists', () => { + vi.mocked(dirExists).mockImplementation((p) => p === join('/home/test', '.gemini', 'config')); expect(getDescriptor('antigravity').detect(ENV)).toBe(true); }); - it('configPath returns ~/.antigravity/mcp.json', () => { - expect(getDescriptor('antigravity').configPath(ENV)).toBe(join('/home/test', '.antigravity', 'mcp.json')); + it('configPath returns ~/.gemini/config/mcp_config.json', () => { + expect(getDescriptor('antigravity').configPath(ENV)).toBe(join('/home/test', '.gemini', 'config', 'mcp_config.json')); }); }); From 9d05b67df5ade62aabcde4323c31c5a352f5d7ab Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 31 Jul 2026 12:01:02 +0800 Subject: [PATCH 2/3] fix: complete Antigravity MCP path update --- site/public/llms.txt | 2 +- src/cli/agents/antigravity.ts | 28 +++--- src/cli/tui/agents.ts | 8 +- tests/unit/cli/agents/antigravity.test.ts | 101 ++++++++++++++++++++++ tests/unit/cli/tui/agents.test.ts | 17 +++- 5 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 tests/unit/cli/agents/antigravity.test.ts diff --git a/site/public/llms.txt b/site/public/llms.txt index c268ff873..042f69647 100644 --- a/site/public/llms.txt +++ b/site/public/llms.txt @@ -6,7 +6,7 @@ For AI assistants: if a user asks you to install or set up wigolo (for any agent Install: `npx wigolo init --agents=` auto-wires the agent (writes its MCP config and, where supported, instructions). Init is unattended by default — no prompts, safe in scripts and CI. `--agents` is optional — omit it for engine-only setup and point any other MCP client at `npx wigolo mcp` by hand. Init is instant; the browser engine and on-device models download automatically on first use (`npx wigolo warmup --all` pre-caches them). Supported agents (auto-wire): Claude Code, Cursor, Codex, Gemini CLI, OpenCode, VS Code, Windsurf, Zed, Antigravity. Any MCP-capable agent works via manual config. -Manual MCP setup (custom agent, or any agent not in the auto-wire list — e.g. an in-house agent): run `npx wigolo init` (engine-only setup, instant, unattended), then add wigolo to the agent's MCP config — command `npx` args `["-y","wigolo"]` (or command `wigolo` args `[]` if globally installed; bare `wigolo` starts the stdio MCP server, the default). Servers key by client: `mcpServers` (Cursor `~/.cursor/mcp.json`, Windsurf `~/.codeium/windsurf/mcp_config.json`, Gemini CLI `~/.gemini/settings.json`, Antigravity `~/.antigravity/mcp.json`), `servers` (VS Code user mcp.json), `context_servers` (Zed `~/.config/zed/settings.json`), `mcp` with `{ "type": "local", "command": ["npx", "-y", "wigolo"], "enabled": true }` (OpenCode `~/.config/opencode/opencode.json`), TOML `[mcp_servers.wigolo]` (Codex `~/.codex/config.toml`). Claude Code: `claude mcp add wigolo --scope user -- npx -y wigolo` (--scope user = global; omit for project-only). Add LLM keys via the server `env` block (e.g. WIGOLO_LLM_PROVIDER, GEMINI_API_KEY). +Manual MCP setup (custom agent, or any agent not in the auto-wire list — e.g. an in-house agent): run `npx wigolo init` (engine-only setup, instant, unattended), then add wigolo to the agent's MCP config — command `npx` args `["-y","wigolo"]` (or command `wigolo` args `[]` if globally installed; bare `wigolo` starts the stdio MCP server, the default). Servers key by client: `mcpServers` (Cursor `~/.cursor/mcp.json`, Windsurf `~/.codeium/windsurf/mcp_config.json`, Gemini CLI `~/.gemini/settings.json`, Antigravity `~/.gemini/config/mcp_config.json`), `servers` (VS Code user mcp.json), `context_servers` (Zed `~/.config/zed/settings.json`), `mcp` with `{ "type": "local", "command": ["npx", "-y", "wigolo"], "enabled": true }` (OpenCode `~/.config/opencode/opencode.json`), TOML `[mcp_servers.wigolo]` (Codex `~/.codex/config.toml`). Claude Code: `claude mcp add wigolo --scope user -- npx -y wigolo` (--scope user = global; omit for project-only). Add LLM keys via the server `env` block (e.g. WIGOLO_LLM_PROVIDER, GEMINI_API_KEY). Optional answer synthesis (research/agent/answer-format search): set `WIGOLO_LLM_PROVIDER` plus its key/model — a provider alone is not enough. Easiest is a free Gemini key: `WIGOLO_LLM_PROVIDER=gemini` + `GEMINI_API_KEY` (free from aistudio.google.com; model defaults to gemini-2.5-flash-lite). Fully local & keyless: `WIGOLO_LLM_PROVIDER=ollama` + `WIGOLO_LLM_MODEL` with a running Ollama server. Also works: Anthropic/OpenAI/Groq keys. Requirements: Node.js >= 20, ~1.5 GB disk, macOS/Linux/Windows. Keyless core tools: search, fetch, crawl, extract, cache, find_similar (plus diff and watch). diff --git a/src/cli/agents/antigravity.ts b/src/cli/agents/antigravity.ts index 2c0da7446..3c4355cf0 100644 --- a/src/cli/agents/antigravity.ts +++ b/src/cli/agents/antigravity.ts @@ -6,37 +6,45 @@ * The file stores servers under the mcpServers key. */ import { existsSync, mkdirSync } from 'node:fs'; -import { join } from 'node:path'; +import { dirname, join } from 'node:path'; import { homedir } from 'node:os'; import { execSync } from 'node:child_process'; import { mergeMcpJson, removeMcpJson } from './utils.js'; const MCP_KEY_PATH = ['mcpServers', 'wigolo']; -function antigravityDir(): string { - return join(homedir(), '.gemini', 'config'); +export function antigravityDataDir(home: string = homedir()): string { + return join(home, '.gemini', 'antigravity'); } -function detect(): boolean { - if (existsSync(antigravityDir())) return true; +export function antigravityMcpConfigPath(home: string = homedir()): string { + return join(home, '.gemini', 'config', 'mcp_config.json'); +} + +function binaryExists(name: string): boolean { try { - execSync('which antigravity', { stdio: ['pipe', 'pipe', 'pipe'] }); + execSync(`which ${name}`, { stdio: ['pipe', 'pipe', 'pipe'] }); return true; } catch { return false; } } +function detect(): boolean { + return existsSync(antigravityDataDir()) || + binaryExists('agy') || + binaryExists('antigravity'); +} + async function installMcp(cmd: { command: string; args: string[] }): Promise { - const dir = antigravityDir(); - mkdirSync(dir, { recursive: true }); - const configPath = join(dir, 'mcp_config.json'); + const configPath = antigravityMcpConfigPath(); + mkdirSync(dirname(configPath), { recursive: true }); mergeMcpJson(configPath, { command: cmd.command, args: cmd.args }, MCP_KEY_PATH); } async function uninstall(): Promise<{ removed: string[] }> { const removed: string[] = []; - const configPath = join(antigravityDir(), 'mcp_config.json'); + const configPath = antigravityMcpConfigPath(); if (existsSync(configPath)) { removeMcpJson(configPath, MCP_KEY_PATH); removed.push('~/.gemini/config/mcp_config.json (wigolo entry)'); diff --git a/src/cli/tui/agents.ts b/src/cli/tui/agents.ts index c12f5f3cf..fddc906f5 100644 --- a/src/cli/tui/agents.ts +++ b/src/cli/tui/agents.ts @@ -1,6 +1,7 @@ import { join } from 'node:path'; import type { AgentDescriptor, DetectedAgent } from './agents-types.js'; import { binaryInPath, dirExists, fileExists, getHome, getCwd } from './detect-helpers.js'; +import { antigravityDataDir, antigravityMcpConfigPath } from '../agents/antigravity.js'; import { vscodeUserDir } from '../agents/vscode.js'; export type { AgentId, AgentDescriptor, DetectedAgent, InstallType } from './agents-types.js'; @@ -94,9 +95,10 @@ const antigravity: AgentDescriptor = { displayName: 'Antigravity', installType: 'config-file', detect: ({ home }) => - binaryInPath('antigravity') !== null || - dirExists(join(home, '.gemini', 'config')), - configPath: ({ home }) => join(home, '.gemini', 'config', 'mcp_config.json'), + dirExists(antigravityDataDir(home)) || + binaryInPath('agy') !== null || + binaryInPath('antigravity') !== null, + configPath: ({ home }) => antigravityMcpConfigPath(home), }; export const AGENTS: readonly AgentDescriptor[] = [ diff --git a/tests/unit/cli/agents/antigravity.test.ts b/tests/unit/cli/agents/antigravity.test.ts new file mode 100644 index 000000000..a6e1b1910 --- /dev/null +++ b/tests/unit/cli/agents/antigravity.test.ts @@ -0,0 +1,101 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; + +vi.mock('node:os', async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, homedir: vi.fn(() => tmpHome) }; +}); + +vi.mock('node:child_process', () => ({ + execSync: vi.fn(), +})); + +import { execSync } from 'node:child_process'; +import { homedir } from 'node:os'; + +let tmpHome: string; + +beforeEach(() => { + tmpHome = join(tmpdir(), `wigolo-antigravity-test-${Date.now()}`); + mkdirSync(tmpHome, { recursive: true }); + vi.mocked(homedir).mockReturnValue(tmpHome); +}); + +afterEach(() => { + rmSync(tmpHome, { recursive: true, force: true }); + vi.clearAllMocks(); +}); + +describe('antigravityHandler.detect', () => { + it('returns true when the Antigravity data directory exists', async () => { + mkdirSync(join(tmpHome, '.gemini', 'antigravity'), { recursive: true }); + const { antigravityHandler } = await import('../../../../src/cli/agents/antigravity.js'); + expect(antigravityHandler.detect()).toBe(true); + }); + + it('returns true when `agy` is on PATH', async () => { + vi.mocked(execSync).mockImplementation((command) => { + if (command === 'which agy') return Buffer.from('/usr/bin/agy'); + throw new Error('not found'); + }); + const { antigravityHandler } = await import('../../../../src/cli/agents/antigravity.js'); + expect(antigravityHandler.detect()).toBe(true); + }); + + it('returns false when no product signal exists', async () => { + mkdirSync(join(tmpHome, '.gemini', 'config'), { recursive: true }); + vi.mocked(execSync).mockImplementation(() => { throw new Error('not found'); }); + const { antigravityHandler } = await import('../../../../src/cli/agents/antigravity.js'); + expect(antigravityHandler.detect()).toBe(false); + }); +}); + +describe('antigravityHandler.installMcp', () => { + it('writes mcpServers.wigolo to the documented global config', async () => { + const { antigravityHandler } = await import('../../../../src/cli/agents/antigravity.js'); + await antigravityHandler.installMcp({ command: 'npx', args: ['-y', 'wigolo'] }); + const configPath = join(tmpHome, '.gemini', 'config', 'mcp_config.json'); + const parsed = JSON.parse(readFileSync(configPath, 'utf-8')); + expect(parsed.mcpServers.wigolo).toEqual({ + command: 'npx', + args: ['-y', 'wigolo'], + }); + }); + + it('preserves other MCP server entries', async () => { + const configDir = join(tmpHome, '.gemini', 'config'); + const configPath = join(configDir, 'mcp_config.json'); + mkdirSync(configDir, { recursive: true }); + writeFileSync( + configPath, + JSON.stringify({ mcpServers: { other: { command: 'other' } } }), + ); + const { antigravityHandler } = await import('../../../../src/cli/agents/antigravity.js'); + await antigravityHandler.installMcp({ command: 'npx', args: ['-y', 'wigolo'] }); + const parsed = JSON.parse(readFileSync(configPath, 'utf-8')); + expect(parsed.mcpServers.other).toEqual({ command: 'other' }); + expect(parsed.mcpServers.wigolo).toBeDefined(); + }); +}); + +describe('antigravityHandler.uninstall', () => { + it('removes only the wigolo MCP server entry', async () => { + const { antigravityHandler } = await import('../../../../src/cli/agents/antigravity.js'); + await antigravityHandler.installMcp({ command: 'npx', args: ['-y', 'wigolo'] }); + const configPath = join(tmpHome, '.gemini', 'config', 'mcp_config.json'); + const before = JSON.parse(readFileSync(configPath, 'utf-8')); + before.mcpServers.other = { command: 'other' }; + writeFileSync(configPath, JSON.stringify(before)); + + const result = await antigravityHandler.uninstall(); + const parsed = JSON.parse(readFileSync(configPath, 'utf-8')); + expect(parsed.mcpServers.wigolo).toBeUndefined(); + expect(parsed.mcpServers.other).toEqual({ command: 'other' }); + expect(result.removed).toEqual([ + '~/.gemini/config/mcp_config.json (wigolo entry)', + ]); + expect(existsSync(configPath)).toBe(true); + }); +}); diff --git a/tests/unit/cli/tui/agents.test.ts b/tests/unit/cli/tui/agents.test.ts index 35ef69226..2727fd973 100644 --- a/tests/unit/cli/tui/agents.test.ts +++ b/tests/unit/cli/tui/agents.test.ts @@ -245,16 +245,27 @@ describe('OpenCode descriptor', () => { describe('Antigravity descriptor', () => { beforeEach(() => vi.clearAllMocks()); - it('detects when `antigravity` binary is on PATH', () => { + it('detects when `agy` binary is on PATH', () => { + vi.mocked(binaryInPath).mockImplementation((n) => (n === 'agy' ? '/usr/local/bin/agy' : null)); + expect(getDescriptor('antigravity').detect(ENV)).toBe(true); + }); + + it('detects the legacy `antigravity` binary alias', () => { vi.mocked(binaryInPath).mockImplementation((n) => (n === 'antigravity' ? '/usr/local/bin/antigravity' : null)); expect(getDescriptor('antigravity').detect(ENV)).toBe(true); }); - it('detects when ~/.gemini/config dir exists', () => { - vi.mocked(dirExists).mockImplementation((p) => p === join('/home/test', '.gemini', 'config')); + it('detects when ~/.gemini/antigravity dir exists', () => { + vi.mocked(dirExists).mockImplementation((p) => p === join('/home/test', '.gemini', 'antigravity')); expect(getDescriptor('antigravity').detect(ENV)).toBe(true); }); + it('does not treat the shared ~/.gemini/config dir as an install signal', () => { + vi.mocked(dirExists).mockImplementation((p) => p === join('/home/test', '.gemini', 'config')); + vi.mocked(binaryInPath).mockReturnValue(null); + expect(getDescriptor('antigravity').detect(ENV)).toBe(false); + }); + it('configPath returns ~/.gemini/config/mcp_config.json', () => { expect(getDescriptor('antigravity').configPath(ENV)).toBe(join('/home/test', '.gemini', 'config', 'mcp_config.json')); }); From c5b2e5be02fde620f2490ef45c2b7885bb826206 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 31 Jul 2026 15:13:48 +0800 Subject: [PATCH 3/3] fix(antigravity): windows shell command no find which --- src/cli/agents/antigravity.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/cli/agents/antigravity.ts b/src/cli/agents/antigravity.ts index 3c4355cf0..66a91fcaf 100644 --- a/src/cli/agents/antigravity.ts +++ b/src/cli/agents/antigravity.ts @@ -8,7 +8,7 @@ import { existsSync, mkdirSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { homedir } from 'node:os'; -import { execSync } from 'node:child_process'; +import { spawnSync } from 'node:child_process'; import { mergeMcpJson, removeMcpJson } from './utils.js'; const MCP_KEY_PATH = ['mcpServers', 'wigolo']; @@ -22,9 +22,15 @@ export function antigravityMcpConfigPath(home: string = homedir()): string { } function binaryExists(name: string): boolean { + const comand = process.platform === 'win32' ? 'where': 'which'; + try { - execSync(`which ${name}`, { stdio: ['pipe', 'pipe', 'pipe'] }); - return true; + const result = spawnSync(comand, [name], { + encoding:'utf-8', + timeout: 3000, + }); + + return !result.error && result.status === 0; } catch { return false; }