cc-agy 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -2
  3. package/assets/kit/apple-touch-icon.png +0 -0
  4. package/assets/kit/cc-agy-app-icon-128.png +0 -0
  5. package/assets/kit/cc-agy-app-icon-32.png +0 -0
  6. package/assets/kit/cc-agy-app-icon-512.png +0 -0
  7. package/assets/kit/cc-agy-app-icon-64.png +0 -0
  8. package/assets/kit/cc-agy-app-icon.svg +45 -0
  9. package/assets/kit/cc-agy-black-128.png +0 -0
  10. package/assets/kit/cc-agy-black-32.png +0 -0
  11. package/assets/kit/cc-agy-black-512.png +0 -0
  12. package/assets/kit/cc-agy-black-64.png +0 -0
  13. package/assets/kit/cc-agy-black.svg +11 -0
  14. package/assets/kit/cc-agy-favicon-128.png +0 -0
  15. package/assets/kit/cc-agy-favicon-32.png +0 -0
  16. package/assets/kit/cc-agy-favicon-512.png +0 -0
  17. package/assets/kit/cc-agy-favicon-64.png +0 -0
  18. package/assets/kit/cc-agy-favicon.svg +45 -0
  19. package/assets/kit/cc-agy-mono-38bdf8-128.png +0 -0
  20. package/assets/kit/cc-agy-mono-38bdf8-32.png +0 -0
  21. package/assets/kit/cc-agy-mono-38bdf8-512.png +0 -0
  22. package/assets/kit/cc-agy-mono-38bdf8-64.png +0 -0
  23. package/assets/kit/cc-agy-mono-38bdf8.svg +11 -0
  24. package/assets/kit/cc-agy-square-128.png +0 -0
  25. package/assets/kit/cc-agy-square-32.png +0 -0
  26. package/assets/kit/cc-agy-square-512.png +0 -0
  27. package/assets/kit/cc-agy-square-64.png +0 -0
  28. package/assets/kit/cc-agy-square.svg +45 -0
  29. package/assets/kit/cc-agy-white-128.png +0 -0
  30. package/assets/kit/cc-agy-white-32.png +0 -0
  31. package/assets/kit/cc-agy-white-512.png +0 -0
  32. package/assets/kit/cc-agy-white-64.png +0 -0
  33. package/assets/kit/cc-agy-white.svg +11 -0
  34. package/assets/kit/favicon-16.png +0 -0
  35. package/assets/kit/favicon-32.png +0 -0
  36. package/assets/kit/favicon-48.png +0 -0
  37. package/assets/kit/favicon.ico +0 -0
  38. package/assets/kit/favicon.svg +45 -0
  39. package/assets/kit/head-snippet.html +5 -0
  40. package/assets/kit/icon-192.png +0 -0
  41. package/assets/kit/icon-512.png +0 -0
  42. package/assets/kit/maskable-512.png +0 -0
  43. package/assets/kit/site.webmanifest +24 -0
  44. package/assets/logo.png +0 -0
  45. package/assets/logo.svg +45 -0
  46. package/bin/cc-agy.js +510 -0
  47. package/install.sh +212 -0
  48. package/package.json +48 -4
  49. package/scripts/import-agy.js +141 -0
  50. package/scripts/patch-combo.py +152 -0
  51. package/systemd/omniroute.service +15 -0
  52. package/uninstall.sh +64 -0
package/bin/cc-agy.js ADDED
@@ -0,0 +1,510 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cc-agy: Autonomous Self-Healing CLI Wrapper & Gateway Controller
4
+ * Seamlessly connects Claude Code to Google Antigravity (Gemini 3.8 Flash High & Claude on TPU)
5
+ * Author: Farhat Iqbal (@farhatkoka)
6
+ */
7
+
8
+ const { spawnSync, spawn } = require('child_process');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const os = require('os');
12
+ const http = require('http');
13
+ const readline = require('readline');
14
+
15
+ const BOLD = '\x1b[1m';
16
+ const GREEN = '\x1b[32m';
17
+ const BLUE = '\x1b[34m';
18
+ const YELLOW = '\x1b[33m';
19
+ const RED = '\x1b[31m';
20
+ const CYAN = '\x1b[36m';
21
+ const DIM = '\x1b[2m';
22
+ const NC = '\x1b[0m';
23
+
24
+ const HOME = os.homedir();
25
+ const AGY_TOKEN_PATH = path.join(HOME, '.gemini', 'antigravity-cli', 'antigravity-oauth-token');
26
+ const CLAUDE_SETTINGS_PATH = path.join(HOME, '.claude', 'settings.json');
27
+ const SYSTEMD_SERVICE_PATH = path.join(HOME, '.config', 'systemd', 'user', 'omniroute.service');
28
+ const CONFIG_DIR = path.join(HOME, '.config', 'cc-agy');
29
+ const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
30
+ const HEALTH_URL = 'http://127.0.0.1:20128/api/monitoring/health';
31
+
32
+ const AGY_MODELS = [
33
+ {
34
+ id: 'gemini-3.8-flash-high',
35
+ name: 'Gemini 3.8 Flash High',
36
+ description: 'Google Deep Thinking (TPU v5p Reasoning Engine)',
37
+ aliases: ['1', 'flash', 'high', 'flash-high', 'gemini']
38
+ },
39
+ {
40
+ id: 'gemini-3.8-flash-medium',
41
+ name: 'Gemini 3.8 Flash Medium',
42
+ description: 'Balanced latency & test-time reasoning',
43
+ aliases: ['2', 'medium', 'med', 'flash-medium']
44
+ },
45
+ {
46
+ id: 'gemini-3.8-flash-low',
47
+ name: 'Gemini 3.8 Flash Low',
48
+ description: 'Distilled sub-second speed',
49
+ aliases: ['3', 'low', 'flash-low']
50
+ },
51
+ {
52
+ id: 'claude-sonnet-4-6',
53
+ name: 'Claude Sonnet 4.6 (Thinking)',
54
+ description: 'Anthropic flagship agentic reasoning on TPU',
55
+ aliases: ['4', 'sonnet', 'sonnet-4-6']
56
+ },
57
+ {
58
+ id: 'claude-opus-4-6-thinking',
59
+ name: 'Claude Opus 4.6 (Thinking)',
60
+ description: 'Anthropic maximum depth reasoning',
61
+ aliases: ['5', 'opus', 'opus-4-6', 'opus-thinking']
62
+ },
63
+ {
64
+ id: 'gpt-oss-120b-medium',
65
+ name: 'GPT-OSS 120B (Medium)',
66
+ description: 'Open-weights high-density reasoning',
67
+ aliases: ['6', 'gpt', 'oss', '120b']
68
+ },
69
+ {
70
+ id: 'claude-auto',
71
+ name: 'Claude-Auto Priority Cascade',
72
+ description: 'Dynamic failover: Flash High -> Sonnet 4.6 -> Flash Lite',
73
+ aliases: ['7', 'auto', 'cascade', 'default']
74
+ }
75
+ ];
76
+
77
+ function loadConfig() {
78
+ try {
79
+ if (fs.existsSync(CONFIG_PATH)) {
80
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
81
+ }
82
+ } catch {}
83
+ return { defaultModel: 'gemini-3.8-flash-high' };
84
+ }
85
+
86
+ function saveConfig(cfg) {
87
+ try {
88
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
89
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf8');
90
+ } catch (err) {
91
+ console.error(`${RED}[-] Failed to persist config:${NC}`, err.message);
92
+ }
93
+ }
94
+
95
+ function resolveModel(query) {
96
+ if (!query) return null;
97
+ const q = query.trim().toLowerCase();
98
+ for (const m of AGY_MODELS) {
99
+ if (m.id.toLowerCase() === q) return m;
100
+ if (m.aliases.includes(q)) return m;
101
+ }
102
+ return null;
103
+ }
104
+
105
+ function checkHealth(timeoutMs = 1500) {
106
+ return new Promise((resolve) => {
107
+ const req = http.get(HEALTH_URL, { timeout: timeoutMs }, (res) => {
108
+ let data = '';
109
+ res.on('data', chunk => { data += chunk; });
110
+ res.on('end', () => {
111
+ try {
112
+ const json = JSON.parse(data);
113
+ resolve(json.status === 'healthy' || res.statusCode === 200);
114
+ } catch {
115
+ resolve(res.statusCode === 200);
116
+ }
117
+ });
118
+ });
119
+ req.on('error', () => resolve(false));
120
+ req.on('timeout', () => { req.destroy(); resolve(false); });
121
+ });
122
+ }
123
+
124
+ function healNativeBinary() {
125
+ const possiblePaths = [
126
+ path.join(HOME, '.local', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'install.cjs'),
127
+ ];
128
+
129
+ try {
130
+ const globalRoot = spawnSync('npm', ['root', '-g'], { encoding: 'utf-8' }).stdout?.trim();
131
+ if (globalRoot) {
132
+ possiblePaths.unshift(path.join(globalRoot, '@anthropic-ai', 'claude-code', 'install.cjs'));
133
+ }
134
+ } catch {}
135
+
136
+ for (const scriptPath of possiblePaths) {
137
+ if (fs.existsSync(scriptPath)) {
138
+ // Purge any conflicting nested platform package to prevent 'Two copies installed' error
139
+ const nestedPkg = path.join(path.dirname(scriptPath), 'node_modules', '@anthropic-ai', 'claude-code-linux-x64');
140
+ if (fs.existsSync(nestedPkg)) {
141
+ try { fs.rmSync(nestedPkg, { recursive: true, force: true }); } catch {}
142
+ }
143
+ process.stdout.write(`${YELLOW}⚡ [cc-agy] Linking platform native ELF binary...${NC} `);
144
+ const res = spawnSync('node', [scriptPath], { encoding: 'utf-8' });
145
+ if (res.status === 0) {
146
+ console.log(`${GREEN}✔ Done.${NC}`);
147
+ return true;
148
+ }
149
+ }
150
+ }
151
+ return false;
152
+ }
153
+
154
+ function ensureNativeBinaryHealthy() {
155
+ try {
156
+ const res = spawnSync('claude', ['--version'], { encoding: 'utf-8' });
157
+ const output = (res.stdout || '') + (res.stderr || '');
158
+ if (output.includes('native binary not installed') || res.status !== 0) {
159
+ healNativeBinary();
160
+ }
161
+ } catch (err) {
162
+ healNativeBinary();
163
+ }
164
+ }
165
+
166
+ async function ensureDaemonRunning() {
167
+ const isHealthy = await checkHealth(1000);
168
+ if (isHealthy) return true;
169
+
170
+ process.stdout.write(`${YELLOW}⚡ [cc-agy] Gateway daemon inactive. Resurrecting omniroute.service...${NC} `);
171
+ try {
172
+ spawnSync('systemctl', ['--user', 'start', 'omniroute'], { encoding: 'utf-8' });
173
+ for (let i = 0; i < 10; i++) {
174
+ await new Promise(r => setTimeout(r, 400));
175
+ if (await checkHealth(500)) {
176
+ console.log(`${GREEN}✔ Gateway ready (:20128).${NC}`);
177
+ return true;
178
+ }
179
+ }
180
+ } catch (e) {}
181
+
182
+ console.log(`${RED}✖ Failed to start omniroute.service.${NC}`);
183
+ return false;
184
+ }
185
+
186
+ function getLocalApiKey() {
187
+ if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
188
+ try {
189
+ const data = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf-8'));
190
+ if (data?.env?.ANTHROPIC_AUTH_TOKEN) {
191
+ return data.env.ANTHROPIC_AUTH_TOKEN;
192
+ }
193
+ } catch {}
194
+ }
195
+ return 'sk-omniroute-local';
196
+ }
197
+
198
+ function runSetup() {
199
+ console.log(`\n${BOLD}${BLUE}=== Running cc-agy Headless Setup ===${NC}\n`);
200
+ const installScript = path.join(__dirname, '..', 'install.sh');
201
+ if (fs.existsSync(installScript)) {
202
+ const child = spawnSync('bash', [installScript], { stdio: 'inherit' });
203
+ return child.status === 0;
204
+ } else {
205
+ console.error(`${RED}[-] install.sh not found in package directory.${NC}`);
206
+ return false;
207
+ }
208
+ }
209
+
210
+ async function runDoctor() {
211
+ console.log(`\n${BOLD}${CYAN}====================================================================${NC}`);
212
+ console.log(`${BOLD}${CYAN} cc-agy: System Architecture & Diagnostic Telemetry ${NC}`);
213
+ console.log(`${BOLD}${CYAN}====================================================================${NC}\n`);
214
+
215
+ const hasToken = fs.existsSync(AGY_TOKEN_PATH);
216
+ console.log(`[1] Google Antigravity Token : ${hasToken ? `${GREEN}✔ Present (${AGY_TOKEN_PATH})${NC}` : `${RED}✖ Missing. Run 'agy' to sign in.${NC}`}`);
217
+
218
+ const serviceActive = spawnSync('systemctl', ['--user', 'is-active', 'omniroute'], { encoding: 'utf-8' }).stdout?.trim() === 'active';
219
+ console.log(`[2] OmniRoute User Daemon : ${serviceActive ? `${GREEN}✔ active (systemd --user)${NC}` : `${YELLOW}⚠ inactive / not running${NC}`}`);
220
+
221
+ const healthy = await checkHealth(2000);
222
+ console.log(`[3] Gateway Health Endpoint : ${healthy ? `${GREEN}✔ http://127.0.0.1:20128 (healthy)${NC}` : `${RED}✖ Unreachable${NC}`}`);
223
+
224
+ let claudeVersion = 'Unknown';
225
+ let binaryStatus = `${RED}✖ Broken / Unlinked${NC}`;
226
+ try {
227
+ const cRes = spawnSync('claude', ['--version'], { encoding: 'utf-8' });
228
+ const cOut = (cRes.stdout || '') + (cRes.stderr || '');
229
+ if (!cOut.includes('native binary not installed') && cRes.status === 0) {
230
+ claudeVersion = cRes.stdout.trim();
231
+ binaryStatus = `${GREEN}✔ Native ELF Binary Linked & Operational${NC}`;
232
+ }
233
+ } catch {}
234
+ console.log(`[4] Claude Code CLI Version : ${CYAN}${claudeVersion}${NC}`);
235
+ console.log(`[5] Platform Native Binary : ${binaryStatus}`);
236
+
237
+ const cfg = loadConfig();
238
+ const current = resolveModel(cfg.defaultModel) || AGY_MODELS[0];
239
+ console.log(`[6] Active Cognitive Model : ${BOLD}${CYAN}${current.name}${NC} (${current.id})`);
240
+ console.log(`[7] Token Billing / Cost : ${GREEN}Asymmetric Zero Marginal Cost ($0 / token)${NC}\n`);
241
+ }
242
+
243
+ async function handleModelCommand(targetArg) {
244
+ const cfg = loadConfig();
245
+ const currentId = cfg.defaultModel || 'gemini-3.8-flash-high';
246
+
247
+ if (targetArg) {
248
+ const matched = resolveModel(targetArg);
249
+ if (!matched) {
250
+ console.log(`\n${RED}[-] Unknown model alias: '${targetArg}'${NC}`);
251
+ console.log(` Available aliases: flash, medium, low, sonnet, opus, gpt, auto\n`);
252
+ process.exit(1);
253
+ }
254
+ cfg.defaultModel = matched.id;
255
+ saveConfig(cfg);
256
+ console.log(`\n${GREEN}✔ Active Antigravity Model switched to:${NC} ${BOLD}${matched.name}${NC} (${matched.id})\n`);
257
+ return;
258
+ }
259
+
260
+ // Interactive Model Selector Menu
261
+ console.log(`\n${BOLD}${CYAN}====================================================================${NC}`);
262
+ console.log(`${BOLD}${CYAN} cc-agy: Antigravity Model Switcher (TPU v5p Compute) ${NC}`);
263
+ console.log(`${BOLD}${CYAN}====================================================================${NC}\n`);
264
+
265
+ AGY_MODELS.forEach((m, idx) => {
266
+ const isActive = m.id === currentId;
267
+ const marker = isActive ? `${GREEN}● [ACTIVE]${NC}` : ` `;
268
+ const num = `${BOLD}[${idx + 1}]${NC}`;
269
+ console.log(` ${num} ${BOLD}${m.name.padEnd(28)}${NC} ${marker}`);
270
+ console.log(` ${DIM}${m.description}${NC}`);
271
+ console.log(` ${DIM}ID: ${m.id} | Aliases: ${m.aliases.join(', ')}${NC}\n`);
272
+ });
273
+
274
+ const rl = readline.createInterface({
275
+ input: process.stdin,
276
+ output: process.stdout,
277
+ });
278
+
279
+ rl.question(`${BOLD}Select model [1-7] (or press Enter to keep current): ${NC}`, (answer) => {
280
+ rl.close();
281
+ const trimmed = answer.trim();
282
+ if (!trimmed) {
283
+ console.log(`${CYAN}Retaining current active model: ${currentId}${NC}\n`);
284
+ return;
285
+ }
286
+ const matched = resolveModel(trimmed);
287
+ if (matched) {
288
+ cfg.defaultModel = matched.id;
289
+ saveConfig(cfg);
290
+ console.log(`\n${GREEN}✔ Active Antigravity Model set to:${NC} ${BOLD}${matched.name}${NC} (${matched.id})\n`);
291
+ } else {
292
+ console.log(`\n${RED}[-] Invalid selection. Keeping ${currentId}.${NC}\n`);
293
+ }
294
+ });
295
+ }
296
+
297
+ async function handleUpdateCommand() {
298
+ const pkgPath = path.join(__dirname, '..', 'package.json');
299
+ let pkg = { version: '1.1.0' };
300
+ try {
301
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
302
+ } catch (e) {}
303
+ const currentVersion = pkg.version;
304
+
305
+ console.log(`\n${BOLD}${CYAN}cc-agy Update Engine${NC}`);
306
+ console.log(`Current installed version: ${BOLD}v${currentVersion}${NC}`);
307
+
308
+ // Query registry for latest published version
309
+ let latestNpm = null;
310
+ try {
311
+ const res = spawnSync('npm', ['view', 'cc-agy', 'version'], { encoding: 'utf8' });
312
+ if (res.status === 0 && res.stdout) {
313
+ latestNpm = res.stdout.trim();
314
+ }
315
+ } catch (e) {}
316
+
317
+ if (latestNpm) {
318
+ console.log(`Latest registry version: ${BOLD}v${latestNpm}${NC}`);
319
+ }
320
+
321
+ const isGitRepo = fs.existsSync(path.join(__dirname, '..', '.git'));
322
+
323
+ if (isGitRepo) {
324
+ console.log(`\n${DIM}Repository checkout detected (${path.resolve(__dirname, '..')})${NC}`);
325
+ console.log(`Syncing latest commits from GitHub origin/main...`);
326
+ const pull = spawnSync('git', ['pull', '--rebase', 'origin', 'main'], {
327
+ cwd: path.join(__dirname, '..'),
328
+ stdio: 'inherit',
329
+ });
330
+ if (pull.status !== 0) {
331
+ console.log(`\n${RED}✖ Git pull failed. Please check local git working tree.${NC}\n`);
332
+ process.exit(1);
333
+ }
334
+ } else {
335
+ console.log(`\nUpdating global npm package: ${BOLD}npm install -g cc-agy@latest${NC}...`);
336
+ const npmUp = spawnSync('npm', ['install', '-g', 'cc-agy@latest'], {
337
+ stdio: 'inherit',
338
+ });
339
+ if (npmUp.status !== 0) {
340
+ console.log(`\n${RED}✖ npm update failed.${NC}\n`);
341
+ process.exit(1);
342
+ }
343
+ }
344
+
345
+ // Synchronize database combos if omniroute is installed
346
+ const patchScript = path.join(__dirname, '..', 'scripts', 'patch-combo.py');
347
+ if (fs.existsSync(patchScript)) {
348
+ console.log(`\nSynchronizing Antigravity model combos in SQLite...`);
349
+ spawnSync('python3', [patchScript], { stdio: 'inherit' });
350
+ }
351
+
352
+ // Ensure native binary link for claude-code
353
+ ensureNativeBinaryHealthy();
354
+
355
+ // Restart proxy daemon if running
356
+ console.log(`Verifying gateway daemon status...`);
357
+ spawnSync('systemctl', ['--user', 'restart', 'omniroute.service'], { stdio: 'ignore' });
358
+
359
+ let updatedVersion = currentVersion;
360
+ try {
361
+ const updatedPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
362
+ updatedVersion = updatedPkg.version;
363
+ } catch (e) {}
364
+
365
+ console.log(`\n${GREEN}✔ cc-agy is now at v${updatedVersion}!${NC}\n`);
366
+ process.exit(0);
367
+ }
368
+
369
+ async function main() {
370
+ const args = [...process.argv.slice(2)];
371
+ const command = args[0];
372
+
373
+ if (command === 'doctor' || command === 'status') {
374
+ await runDoctor();
375
+ process.exit(0);
376
+ }
377
+
378
+ if (command === 'restart') {
379
+ process.stdout.write(`${YELLOW}Restarting omniroute.service...${NC} `);
380
+ spawnSync('systemctl', ['--user', 'restart', 'omniroute'], { encoding: 'utf-8' });
381
+ const ok = await checkHealth(3000);
382
+ console.log(ok ? `${GREEN}✔ Gateway restarted & healthy.${NC}` : `${RED}✖ Failed.${NC}`);
383
+ process.exit(ok ? 0 : 1);
384
+ }
385
+
386
+ if (command === 'setup') {
387
+ const ok = runSetup();
388
+ process.exit(ok ? 0 : 1);
389
+ }
390
+
391
+ if (command === 'update' || command === 'upgrade') {
392
+ await handleUpdateCommand();
393
+ return;
394
+ }
395
+
396
+ if (command === 'model' || command === 'models' || command === 'switch') {
397
+ await handleModelCommand(args[1]);
398
+ return;
399
+ }
400
+
401
+ if (command === '--help' || command === '-h' || command === 'help') {
402
+ console.log(`
403
+ ${BOLD}cc-agy${NC} - Claude Code + Google Antigravity (Gemini 3.8 Flash High & Claude on TPU)
404
+
405
+ ${BOLD}USAGE:${NC}
406
+ cc-agy [options] [claude-flags] Launch Claude Code connected to Antigravity
407
+ cc-agy model [alias] Switch default active model (or show interactive menu)
408
+ cc-agy update Self-update to the latest version & sync model combos
409
+ cc-agy doctor Run full diagnostic telemetry & healthcheck
410
+ cc-agy restart Restart local proxy daemon
411
+ cc-agy setup Force re-run provisioning and database patches
412
+
413
+ ${BOLD}MODELS & ALIASES:${NC}
414
+ flash / high Gemini 3.8 Flash High (Deep Reasoning)
415
+ medium / med Gemini 3.8 Flash Medium
416
+ low Gemini 3.8 Flash Low (Sub-second)
417
+ sonnet Claude Sonnet 4.6 (Thinking on TPU)
418
+ opus Claude Opus 4.6 (Thinking on TPU)
419
+ gpt / oss GPT-OSS 120B (Medium)
420
+ auto Priority Cascade (High -> Sonnet -> Lite)
421
+
422
+ ${BOLD}EXAMPLES:${NC}
423
+ cc-agy Launch with active model
424
+ cc-agy --model sonnet Launch a session with Claude Sonnet 4.6
425
+ cc-agy --model opus Launch a session with Claude Opus 4.6
426
+ cc-agy model Open interactive model switcher
427
+ cc-agy model sonnet Set default model to Sonnet 4.6
428
+ cc-agy --dangerously-skip-permissions
429
+ `);
430
+ process.exit(0);
431
+ }
432
+
433
+ // Pre-flight setup check
434
+ const isSetup = fs.existsSync(SYSTEMD_SERVICE_PATH) && fs.existsSync(CLAUDE_SETTINGS_PATH);
435
+ if (!isSetup) {
436
+ if (!fs.existsSync(AGY_TOKEN_PATH)) {
437
+ console.log(`${RED}[-] Antigravity OAuth token not found at: ${AGY_TOKEN_PATH}${NC}`);
438
+ console.log(` Please install and run ${BOLD}'agy'${NC} once to sign in with your Google account.`);
439
+ process.exit(1);
440
+ }
441
+ const success = runSetup();
442
+ if (!success) {
443
+ console.error(`${RED}[-] Setup failed. Please inspect logs above.${NC}`);
444
+ process.exit(1);
445
+ }
446
+ }
447
+
448
+ // Ensure native binary and daemon are healthy
449
+ ensureNativeBinaryHealthy();
450
+ await ensureDaemonRunning();
451
+
452
+ // Model resolution: check command-line --model / -m override, otherwise use saved default
453
+ const cfg = loadConfig();
454
+ let selectedModelId = cfg.defaultModel || 'gemini-3.8-flash-high';
455
+ let modelIndex = -1;
456
+
457
+ for (let i = 0; i < args.length; i++) {
458
+ if (args[i] === '--model' || args[i] === '-m') {
459
+ if (args[i + 1]) {
460
+ const resolved = resolveModel(args[i + 1]);
461
+ if (resolved) {
462
+ selectedModelId = resolved.id;
463
+ } else {
464
+ selectedModelId = args[i + 1];
465
+ }
466
+ modelIndex = i;
467
+ }
468
+ break;
469
+ }
470
+ }
471
+
472
+ // Strip custom --model/-m from args to pass cleaned model id to claude
473
+ const forwardArgs = [...args];
474
+ if (modelIndex !== -1) {
475
+ forwardArgs.splice(modelIndex, 2);
476
+ }
477
+ forwardArgs.unshift('--model', selectedModelId);
478
+
479
+ // Inject gateway environment variables & disable broken background autoupdate
480
+ const apiKey = getLocalApiKey();
481
+ const env = {
482
+ ...process.env,
483
+ ANTHROPIC_BASE_URL: 'http://localhost:20128',
484
+ ANTHROPIC_AUTH_TOKEN: apiKey,
485
+ ANTHROPIC_API_KEY: '',
486
+ ANTHROPIC_MODEL: selectedModelId,
487
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: '1',
488
+ DISABLE_AUTOUPDATER: '1',
489
+ CLAUDE_CODE_AUTOUPDATER_DISABLED_BY_HOST: '1',
490
+ };
491
+
492
+ // Launch Claude Code transparently
493
+ const child = spawn('claude', forwardArgs, {
494
+ stdio: 'inherit',
495
+ env,
496
+ });
497
+
498
+ child.on('exit', (code, signal) => {
499
+ if (signal) {
500
+ process.kill(process.pid, signal);
501
+ } else {
502
+ process.exit(code ?? 0);
503
+ }
504
+ });
505
+ }
506
+
507
+ main().catch(err => {
508
+ console.error(`${RED}[cc-agy error]:${NC}`, err);
509
+ process.exit(1);
510
+ });