@simonren/quorum 0.7.0 → 0.8.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 CHANGED
@@ -42,9 +42,9 @@ Install at least one AI CLI:
42
42
  npm install -g @openai/codex-cli
43
43
  codex login
44
44
 
45
- # Google Gemini CLI
46
- npm install -g @google/gemini-cli
47
- gemini # follow auth prompts
45
+ # Google Gemini — via Antigravity CLI (`agy`). `gemini-cli` is sunset on 2026-06-18.
46
+ curl -fsSL https://antigravity.google/cli/install.sh | bash
47
+ agy # follow the Google OAuth flow
48
48
  ```
49
49
 
50
50
  ## Usage
@@ -1,9 +1,17 @@
1
1
  /**
2
- * Gemini CLI Adapter
2
+ * Gemini Adapter (via Antigravity CLI `agy`)
3
3
  *
4
- * Implements the ReviewerAdapter interface for Google's Gemini CLI.
5
- * Returns raw text no JSON parsing or schema enforcement.
6
- * CC handles interpretation of the reviewer's response.
4
+ * Google replaced `gemini-cli` with the Antigravity CLI (`agy`) at I/O 2026.
5
+ * The free-tier `gemini` binary stops serving requests on 2026-06-18, so this
6
+ * adapter now spawns `agy --print` with the prompt on stdin. The model is still
7
+ * Gemini under the hood — only the CLI brand changed — so the adapter id and
8
+ * config key remain `gemini`.
9
+ *
10
+ * Differences from the old gemini-cli adapter:
11
+ * - No `--output-format stream-json` → no live progress events
12
+ * - No `--model` flag → model selection is done in agy's settings file
13
+ * - `--include-directories` → `--add-dir`
14
+ * - `--approval-mode plan` → folded into `--sandbox`
7
15
  */
8
16
  import { ReviewerAdapter, ReviewerCapabilities, ReviewRequest, ReviewResult, ConsultRequest, ConsultResult } from './base.js';
9
17
  export declare class GeminiAdapter implements ReviewerAdapter {
@@ -1,27 +1,32 @@
1
1
  /**
2
- * Gemini CLI Adapter
2
+ * Gemini Adapter (via Antigravity CLI `agy`)
3
3
  *
4
- * Implements the ReviewerAdapter interface for Google's Gemini CLI.
5
- * Returns raw text no JSON parsing or schema enforcement.
6
- * CC handles interpretation of the reviewer's response.
4
+ * Google replaced `gemini-cli` with the Antigravity CLI (`agy`) at I/O 2026.
5
+ * The free-tier `gemini` binary stops serving requests on 2026-06-18, so this
6
+ * adapter now spawns `agy --print` with the prompt on stdin. The model is still
7
+ * Gemini under the hood — only the CLI brand changed — so the adapter id and
8
+ * config key remain `gemini`.
9
+ *
10
+ * Differences from the old gemini-cli adapter:
11
+ * - No `--output-format stream-json` → no live progress events
12
+ * - No `--model` flag → model selection is done in agy's settings file
13
+ * - `--include-directories` → `--add-dir`
14
+ * - `--approval-mode plan` → folded into `--sandbox`
7
15
  */
8
16
  import { spawn } from 'child_process';
9
17
  import { existsSync } from 'fs';
10
18
  import { registerAdapter, } from './base.js';
11
19
  import { CliExecutor } from '../executor.js';
12
- import { GeminiEventDecoder } from '../decoders/index.js';
13
20
  import { buildSimpleHandoff, buildHandoffPrompt, buildAdversarialHandoffPrompt, selectRole, } from '../handoff.js';
14
21
  import { buildConsultPrompt } from '../consult-prompt.js';
15
22
  import { getConfig } from '../config.js';
16
- // =============================================================================
17
- // GEMINI ADAPTER
18
- // =============================================================================
23
+ const AGY_INSTALL_CMD = 'curl -fsSL https://antigravity.google/cli/install.sh | bash';
19
24
  export class GeminiAdapter {
20
25
  id = 'gemini';
21
26
  getCapabilities() {
22
27
  return {
23
28
  name: 'Gemini',
24
- description: 'Google Gemini - excels at architecture analysis, design patterns, and large codebase understanding',
29
+ description: 'Google Gemini (via Antigravity CLI) - excels at architecture analysis, design patterns, and large codebase understanding',
25
30
  strengths: ['architecture', 'maintainability', 'scalability', 'documentation'],
26
31
  weaknesses: ['security'],
27
32
  hasFilesystemAccess: true,
@@ -38,7 +43,7 @@ export class GeminiAdapter {
38
43
  clearTimeout(timer);
39
44
  resolve(result);
40
45
  } };
41
- const proc = spawn('gemini', ['--version'], {
46
+ const proc = spawn('agy', ['--version'], {
42
47
  stdio: ['ignore', 'pipe', 'pipe'],
43
48
  });
44
49
  proc.on('close', (code) => done(code === 0));
@@ -69,7 +74,7 @@ export class GeminiAdapter {
69
74
  if (!result.stdout.trim()) {
70
75
  return {
71
76
  success: false,
72
- error: { type: 'cli_error', message: 'Gemini returned empty response' },
77
+ error: { type: 'cli_error', message: 'agy returned empty response' },
73
78
  suggestion: 'Try again or use /multi-review instead',
74
79
  executionTimeMs: Date.now() - startTime,
75
80
  };
@@ -82,45 +87,29 @@ export class GeminiAdapter {
82
87
  }
83
88
  async runCli(prompt, workingDir) {
84
89
  const cfg = getConfig().gemini;
90
+ // agy requires the prompt as the positional after --print; passing only via
91
+ // stdin makes it print --help and exit 0. Sandbox keeps terminal-restricted
92
+ // execution like the old gemini-cli's `--approval-mode plan`.
85
93
  const args = [
86
94
  '--sandbox',
87
- '--approval-mode', 'plan',
88
- '--output-format', 'stream-json',
89
- '--include-directories', workingDir,
90
- '-p', '',
95
+ '--add-dir', workingDir,
96
+ '--print', prompt,
91
97
  ];
92
- if (cfg.model) {
93
- args.push('--model', cfg.model);
94
- }
95
- const decoder = new GeminiEventDecoder();
96
98
  const cliStartTime = Date.now();
97
- console.error('[gemini] Running...');
98
- decoder.onProgress = (eventType, detail) => {
99
- const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
100
- const detailStr = detail ? ` — ${detail}` : '';
101
- console.error(`[gemini] ${eventType}${detailStr} (${elapsed}s)`);
102
- };
99
+ console.error('[gemini] Running agy...');
103
100
  const executor = new CliExecutor({
104
- command: 'gemini',
101
+ command: 'agy',
105
102
  args,
106
103
  cwd: workingDir,
107
- stdin: prompt,
108
104
  inactivityTimeoutMs: cfg.inactivityTimeoutMs,
109
105
  maxTimeoutMs: cfg.maxTimeoutMs,
110
106
  maxBufferSize: cfg.maxBufferSize,
111
- onLine: (line) => {
112
- decoder.processLine(line);
113
- },
114
107
  });
115
108
  const result = await executor.run();
116
109
  const elapsed = Math.round((Date.now() - cliStartTime) / 1000);
117
110
  console.error(`[gemini] ✓ complete (${elapsed}s)`);
118
- const finalResponse = decoder.getFinalResponse();
119
- if (!finalResponse && result.exitCode === 0) {
120
- return { stdout: '', stderr: 'Gemini produced no output — review may have failed silently', exitCode: 1, truncated: false };
121
- }
122
111
  return {
123
- stdout: finalResponse || '',
112
+ stdout: result.rawStdout,
124
113
  stderr: result.stderr,
125
114
  exitCode: result.exitCode,
126
115
  truncated: result.truncated,
@@ -129,11 +118,11 @@ export class GeminiAdapter {
129
118
  handleException(error, startTime) {
130
119
  const err = error;
131
120
  if (err.code === 'ENOENT') {
132
- return { success: false, error: { type: 'cli_not_found', message: 'Gemini CLI not found' },
133
- suggestion: 'Install with: npm install -g @google/gemini-cli', executionTimeMs: Date.now() - startTime };
121
+ return { success: false, error: { type: 'cli_not_found', message: 'agy CLI not found' },
122
+ suggestion: `Install with: ${AGY_INSTALL_CMD}`, executionTimeMs: Date.now() - startTime };
134
123
  }
135
124
  if (err.message === 'TIMEOUT') {
136
- return { success: false, error: { type: 'timeout', message: 'Gemini timed out — no events received' },
125
+ return { success: false, error: { type: 'timeout', message: 'agy timed out — no output received' },
137
126
  suggestion: 'Try a smaller scope or use /multi-review', executionTimeMs: Date.now() - startTime };
138
127
  }
139
128
  if (err.message === 'MAX_TIMEOUT') {
@@ -147,7 +136,7 @@ export class GeminiAdapter {
147
136
  if (lower.includes('rate limit') || lower.includes('quota')) {
148
137
  return { type: 'rate_limit', message: `Rate limit or quota exceeded: ${stderr.slice(0, 500)}` };
149
138
  }
150
- if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('api key') || stderr.includes('401') || stderr.includes('403')) {
139
+ if (lower.includes('unauthorized') || lower.includes('authentication') || lower.includes('oauth') || stderr.includes('401') || stderr.includes('403')) {
151
140
  return { type: 'auth_error', message: `Authentication failed: ${stderr.slice(0, 500)}`, details: { stderr } };
152
141
  }
153
142
  return { type: 'cli_error', message: stderr.slice(0, 500) || 'Unknown error' };
@@ -155,8 +144,8 @@ export class GeminiAdapter {
155
144
  getSuggestion(error) {
156
145
  switch (error.type) {
157
146
  case 'rate_limit': return 'Wait and retry, or use /multi-review instead';
158
- case 'auth_error': return 'Run `gemini` and follow auth prompts, or set GEMINI_API_KEY';
159
- case 'cli_not_found': return 'Install with: npm install -g @google/gemini-cli';
147
+ case 'auth_error': return 'Run `agy` and complete the Google OAuth sign-in';
148
+ case 'cli_not_found': return `Install with: ${AGY_INSTALL_CMD}`;
160
149
  default: return 'Check the error message and try again';
161
150
  }
162
151
  }
@@ -180,7 +169,7 @@ export class GeminiAdapter {
180
169
  if (!result.stdout.trim()) {
181
170
  return {
182
171
  success: false,
183
- error: { type: 'cli_error', message: 'Gemini returned empty response' },
172
+ error: { type: 'cli_error', message: 'agy returned empty response' },
184
173
  suggestion: 'Try again or use /multi-review instead',
185
174
  executionTimeMs: Date.now() - startTime,
186
175
  };
@@ -192,6 +181,5 @@ export class GeminiAdapter {
192
181
  }
193
182
  }
194
183
  }
195
- // Register the adapter
196
184
  registerAdapter(new GeminiAdapter());
197
185
  export const geminiAdapter = new GeminiAdapter();
package/dist/config.d.ts CHANGED
@@ -76,17 +76,14 @@ export declare const ClaudeConfigSchema: z.ZodDefault<z.ZodObject<{
76
76
  maxBufferSize?: number | undefined;
77
77
  }>>;
78
78
  export declare const GeminiConfigSchema: z.ZodDefault<z.ZodObject<{
79
- model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
80
79
  inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
81
80
  maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
82
81
  maxBufferSize: z.ZodDefault<z.ZodNumber>;
83
82
  }, "strip", z.ZodTypeAny, {
84
- model: string | null;
85
83
  inactivityTimeoutMs: number;
86
84
  maxTimeoutMs: number;
87
85
  maxBufferSize: number;
88
86
  }, {
89
- model?: string | null | undefined;
90
87
  inactivityTimeoutMs?: number | undefined;
91
88
  maxTimeoutMs?: number | undefined;
92
89
  maxBufferSize?: number | undefined;
@@ -155,17 +152,14 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
155
152
  maxBufferSize?: number | undefined;
156
153
  }>>;
157
154
  gemini: z.ZodDefault<z.ZodObject<{
158
- model: z.ZodDefault<z.ZodNullable<z.ZodString>>;
159
155
  inactivityTimeoutMs: z.ZodDefault<z.ZodNumber>;
160
156
  maxTimeoutMs: z.ZodDefault<z.ZodNumber>;
161
157
  maxBufferSize: z.ZodDefault<z.ZodNumber>;
162
158
  }, "strip", z.ZodTypeAny, {
163
- model: string | null;
164
159
  inactivityTimeoutMs: number;
165
160
  maxTimeoutMs: number;
166
161
  maxBufferSize: number;
167
162
  }, {
168
- model?: string | null | undefined;
169
163
  inactivityTimeoutMs?: number | undefined;
170
164
  maxTimeoutMs?: number | undefined;
171
165
  maxBufferSize?: number | undefined;
@@ -191,7 +185,6 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
191
185
  maxBufferSize: number;
192
186
  };
193
187
  gemini: {
194
- model: string | null;
195
188
  inactivityTimeoutMs: number;
196
189
  maxTimeoutMs: number;
197
190
  maxBufferSize: number;
@@ -217,7 +210,6 @@ export declare const ConfigSchema: z.ZodDefault<z.ZodObject<{
217
210
  maxBufferSize?: number | undefined;
218
211
  } | undefined;
219
212
  gemini?: {
220
- model?: string | null | undefined;
221
213
  inactivityTimeoutMs?: number | undefined;
222
214
  maxTimeoutMs?: number | undefined;
223
215
  maxBufferSize?: number | undefined;
package/dist/config.js CHANGED
@@ -47,9 +47,11 @@ export const ClaudeConfigSchema = z
47
47
  maxBufferSize: z.number().int().positive().default(1_048_576),
48
48
  })
49
49
  .default({});
50
+ // agy (Antigravity CLI) does not expose `--model`; model selection lives in
51
+ // agy's own settings file. We keep the timeout/buffer knobs because CliExecutor
52
+ // enforces them regardless of the underlying CLI.
50
53
  export const GeminiConfigSchema = z
51
54
  .object({
52
- model: z.string().nullable().default('gemini-3.1-pro-preview'),
53
55
  inactivityTimeoutMs: z.number().int().positive().default(300_000),
54
56
  maxTimeoutMs: z.number().int().positive().default(3_600_000),
55
57
  maxBufferSize: z.number().int().positive().default(1_048_576),
@@ -1,6 +1,4 @@
1
1
  export { CodexEventDecoder } from './codex.js';
2
2
  export type { CodexEvent } from './codex.js';
3
- export { GeminiEventDecoder } from './gemini.js';
4
- export type { GeminiEvent } from './gemini.js';
5
3
  export { ClaudeEventDecoder } from './claude.js';
6
4
  export type { ClaudeEvent } from './claude.js';
@@ -1,3 +1,2 @@
1
1
  export { CodexEventDecoder } from './codex.js';
2
- export { GeminiEventDecoder } from './gemini.js';
3
2
  export { ClaudeEventDecoder } from './claude.js';
package/dist/errors.js CHANGED
@@ -3,22 +3,23 @@
3
3
  */
4
4
  // CLI installation commands
5
5
  // Codex: https://developers.openai.com/codex/cli/
6
- // Gemini: https://github.com/google-gemini/gemini-cli
6
+ // Gemini: now ships as `agy` (Antigravity CLI) — https://antigravity.google/download
7
7
  const INSTALL_COMMANDS = {
8
8
  codex: 'npm install -g @openai/codex-cli',
9
- gemini: 'npm install -g @google/gemini-cli',
9
+ gemini: 'curl -fsSL https://antigravity.google/cli/install.sh | bash',
10
10
  claude: 'https://docs.anthropic.com/en/docs/claude-code'
11
11
  };
12
- // Environment variables for API keys
12
+ // Environment variables for API keys.
13
+ // agy uses Google OAuth and has no env var — null surfaces "no key needed".
13
14
  const ENV_VARS = {
14
15
  codex: 'OPENAI_API_KEY',
15
- gemini: 'GEMINI_API_KEY',
16
+ gemini: null,
16
17
  claude: 'ANTHROPIC_API_KEY'
17
18
  };
18
19
  // Authentication commands
19
20
  const AUTH_COMMANDS = {
20
21
  codex: 'codex login',
21
- gemini: 'gemini (follow prompts)',
22
+ gemini: 'agy (complete Google OAuth)',
22
23
  claude: 'claude auth'
23
24
  };
24
25
  /**
@@ -110,13 +111,14 @@ This might happen with complex reviews. Try:
110
111
  ${retryMsg}
111
112
 
112
113
  Alternative: Use /${otherCli}-review instead`;
113
- case 'auth_error':
114
+ case 'auth_error': {
115
+ const envVar = ENV_VARS[error.cli];
116
+ const keyLine = envVar ? `\nCheck your API key: ${envVar}` : '';
114
117
  return `🔐 ${error.cli} authentication failed.
115
118
 
116
- ${error.message}
117
-
118
- Check your API key: ${ENV_VARS[error.cli]}
119
+ ${error.message}${keyLine}
119
120
  Run: ${AUTH_COMMANDS[error.cli]}`;
121
+ }
120
122
  case 'invalid_response':
121
123
  return `⚠️ ${error.cli} returned an unusable response.
122
124
 
@@ -180,8 +182,12 @@ export function getSuggestion(error) {
180
182
  return error.retryAfterMs
181
183
  ? `Wait ${Math.ceil(error.retryAfterMs / 1000)}s and retry`
182
184
  : 'Wait a moment and retry';
183
- case 'auth_error':
184
- return `Check your ${ENV_VARS[error.cli]} environment variable`;
185
+ case 'auth_error': {
186
+ const envVar = ENV_VARS[error.cli];
187
+ return envVar
188
+ ? `Check your ${envVar} environment variable`
189
+ : `Re-run \`${AUTH_COMMANDS[error.cli]}\` to refresh credentials`;
190
+ }
185
191
  case 'invalid_response':
186
192
  return 'Retry with a more specific focus area';
187
193
  case 'cli_error':
@@ -163,7 +163,7 @@ export async function handleMultiConsult(input) {
163
163
  return {
164
164
  content: [{
165
165
  type: 'text',
166
- text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini: npm install -g @google/gemini-cli',
166
+ text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini (agy): curl -fsSL https://antigravity.google/cli/install.sh | bash',
167
167
  }],
168
168
  };
169
169
  }
@@ -57,7 +57,7 @@ export async function handleMultiReview(input) {
57
57
  const request = toReviewRequest(input);
58
58
  const availableAdapters = await getAvailableAdapters();
59
59
  if (availableAdapters.length === 0) {
60
- return { content: [{ type: 'text', text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini: npm install -g @google/gemini-cli' }] };
60
+ return { content: [{ type: 'text', text: '❌ No AI CLIs found.\n\nInstall at least one:\n - Codex: npm install -g @openai/codex-cli\n - Gemini (agy): curl -fsSL https://antigravity.google/cli/install.sh | bash' }] };
61
61
  }
62
62
  // Spawn 2 reviews per adapter: standard + adversarial (all in parallel)
63
63
  // customPrompt steers the adversarial focus only — strip it from standard pass to avoid bias
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonren/quorum",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "MCP server for Claude Code — a quorum of AI models (Codex, Gemini, Claude) for adversarial review and consultation, synthesized by Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",