@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 +3 -3
- package/dist/adapters/gemini.d.ts +12 -4
- package/dist/adapters/gemini.js +31 -43
- package/dist/config.d.ts +0 -8
- package/dist/config.js +3 -1
- package/dist/decoders/index.d.ts +0 -2
- package/dist/decoders/index.js +0 -1
- package/dist/errors.js +17 -11
- package/dist/tools/consult.js +1 -1
- package/dist/tools/feedback.js +1 -1
- package/package.json +1 -1
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
|
-
|
|
47
|
-
|
|
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
|
|
2
|
+
* Gemini Adapter (via Antigravity CLI `agy`)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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 {
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gemini CLI
|
|
2
|
+
* Gemini Adapter (via Antigravity CLI `agy`)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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('
|
|
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: '
|
|
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
|
-
'--
|
|
88
|
-
'--
|
|
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: '
|
|
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:
|
|
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: '
|
|
133
|
-
suggestion:
|
|
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: '
|
|
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('
|
|
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 `
|
|
159
|
-
case 'cli_not_found': return
|
|
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: '
|
|
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),
|
package/dist/decoders/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/decoders/index.js
CHANGED
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://
|
|
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: '
|
|
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:
|
|
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: '
|
|
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
|
-
|
|
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':
|
package/dist/tools/consult.js
CHANGED
|
@@ -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:
|
|
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
|
}
|
package/dist/tools/feedback.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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",
|