agent-tank 0.9.5 → 0.9.7

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.
@@ -209,20 +209,28 @@ class ClaudeAgent extends BaseAgent {
209
209
  const hasSessionData = parsed.session && typeof parsed.session.percent === 'number';
210
210
  const hasLegacyWeekly = parsed.weekly && typeof parsed.weekly.percent === 'number';
211
211
  const hasAllModelsWeekly = parsed.weeklyAll && typeof parsed.weeklyAll.percent === 'number';
212
+ const hasSonnetWeekly = parsed.weeklySonnet && typeof parsed.weeklySonnet.percent === 'number';
212
213
 
213
214
  // Newer Claude builds often emit usable session/weekly data before the UI fully settles.
214
- // If we can already parse session data plus either weekly format, treat the response as complete.
215
- return hasSessionData && (hasLegacyWeekly || hasAllModelsWeekly);
215
+ // For the all-model weekly format, wait for the separate Sonnet section too.
216
+ return Boolean(hasSessionData && (hasLegacyWeekly || (hasAllModelsWeekly && hasSonnetWeekly)));
216
217
  }
217
218
 
218
219
  sendCommands(shell, _output) {
219
220
  logger.agent(this.name, 'Sending /usage command...');
220
221
  // Claude keeps slash-command suggestions open for /usage on newer builds.
221
222
  // Confirm the command selection, then submit the actual command execution.
222
- setTimeout(() => shell.write('\x1b'), 50);
223
- setTimeout(() => shell.write('/usage'), 600);
224
- setTimeout(() => shell.write('\r'), 1000);
225
- setTimeout(() => shell.write('\r'), 1400);
223
+ const writeIfActive = (value) => {
224
+ if (!shell) return;
225
+ try {
226
+ shell.write(value);
227
+ } catch (_err) {
228
+ // The persistent exit handler records the underlying process state.
229
+ }
230
+ };
231
+ setTimeout(() => writeIfActive('/usage'), 100);
232
+ setTimeout(() => writeIfActive('\r'), 500);
233
+ setTimeout(() => writeIfActive('\r'), 900);
226
234
  }
227
235
 
228
236
  // After getting /usage output, dismiss dialog so next refresh starts with clean prompt
@@ -286,6 +294,18 @@ class ClaudeAgent extends BaseAgent {
286
294
  this._onDataCallback = null;
287
295
  this._commandInFlight = false;
288
296
  clearTimeout(timer);
297
+ if (this.shell && statusOutput) {
298
+ try {
299
+ this.shell.write('\x1b');
300
+ } catch (_err) {
301
+ // The persistent exit handler records the underlying process state.
302
+ }
303
+ setTimeout(() => {
304
+ this.output = '';
305
+ resolve(result);
306
+ }, 300);
307
+ return;
308
+ }
289
309
  resolve(result);
290
310
  };
291
311
 
@@ -306,10 +326,19 @@ class ClaudeAgent extends BaseAgent {
306
326
 
307
327
  logger.agent(this.name, 'Sending /status command for metadata...');
308
328
  this.output = '';
309
- // Send escape first to dismiss any UI, then /status
310
- setTimeout(() => this.shell.write('\x1b'), 50);
311
- setTimeout(() => this.shell.write('/status'), 600);
312
- setTimeout(() => this.shell.write('\r'), 1000);
329
+ const writeIfActive = (value) => {
330
+ if (!this.shell) {
331
+ finish(this._parseStatusOutput(statusOutput));
332
+ return;
333
+ }
334
+ try {
335
+ this.shell.write(value);
336
+ } catch (_err) {
337
+ finish(this._parseStatusOutput(statusOutput));
338
+ }
339
+ };
340
+ setTimeout(() => writeIfActive('/status'), 100);
341
+ setTimeout(() => writeIfActive('\r'), 500);
313
342
  });
314
343
  }
315
344
 
@@ -125,6 +125,12 @@ function extractResetTime(section) {
125
125
  // Look for date+time pattern FIRST (more specific)
126
126
  const dateTimeMatch = section.match(/((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},?\s*\d{1,2}(?::\d{2})?\s*(am|pm))\s*\(([^)]+)\)/i);
127
127
  if (dateTimeMatch) return `${dateTimeMatch[1]} (${dateTimeMatch[3]})`;
128
+ // Compact PTY output can lose the column spaces: "Jun10,6pm(Europe/Berlin)".
129
+ const compactDateTimeMatch = section.match(/((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))(\d{1,2}),?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*\(([^)]+)\)/i);
130
+ if (compactDateTimeMatch) {
131
+ const [, month, day, hour, minutes, ampm, tz] = compactDateTimeMatch;
132
+ return `${month} ${day}, ${hour}${minutes ? `:${minutes}` : ''}${ampm} (${tz})`;
133
+ }
128
134
  // Try clean time-only pattern
129
135
  const timeOnlyMatch = section.match(/(\d{1,2}(?::\d{2})?\s*(?:am|pm))\s*\(([^)]+)\)/i);
130
136
  if (timeOnlyMatch) return `${timeOnlyMatch[1]} (${timeOnlyMatch[2]})`;
@@ -149,7 +155,7 @@ function extractResetTime(section) {
149
155
  */
150
156
  function parseUsageSection(section) {
151
157
  if (!section) return null;
152
- const percentMatch = section.match(/(\d+)\s*%\s*used/i);
158
+ const percentMatch = section.match(/(\d+)\s*%\s*use?d/i);
153
159
  if (!percentMatch) return null;
154
160
  const resetsAt = extractResetTime(section);
155
161
  const resetData = resetsAt ? parseResetTime(resetsAt) : null;
@@ -245,21 +251,21 @@ function parsePtyOutput(clean) {
245
251
  const usage = { session: null, weeklyAll: null, weeklySonnet: null };
246
252
 
247
253
  // Parse session
248
- const sessionData = parseUsageSection(extractSection(clean, 'Current\\s+session', 'Current\\s+week'));
254
+ const sessionData = parseUsageSection(extractSection(clean, 'Current\\s*session', 'Current\\s*week'));
249
255
  if (sessionData) {
250
256
  usage.session = { label: 'Current session', ...sessionData };
251
257
  addPaceData(usage.session, 'session');
252
258
  }
253
259
 
254
260
  // Parse weekly (all models)
255
- const weeklyAllData = parseUsageSection(extractSection(clean, 'Current\\s+week\\s*\\(?\\s*all\\s+models\\s*\\)?', 'Current\\s+week\\s*\\(?\\s*Sonnet'));
261
+ const weeklyAllData = parseUsageSection(extractSection(clean, 'Current\\s*week\\s*\\(?\\s*all\\s*models\\s*\\)?', 'Current\\s*week\\s*\\(?\\s*Son'));
256
262
  if (weeklyAllData) {
257
263
  usage.weeklyAll = { label: 'Current week (all models)', ...weeklyAllData };
258
264
  addPaceData(usage.weeklyAll, 'weekly');
259
265
  }
260
266
 
261
267
  // Parse weekly (Sonnet only)
262
- const weeklySonnetData = parseUsageSection(extractSection(clean, 'Current\\s+week\\s*\\(?\\s*Sonnet\\s+only\\s*\\)?', 'Extra\\s+usage|esc\\s+to\\s+cancel|Current\\s+week\\s*\\('));
268
+ const weeklySonnetData = parseUsageSection(extractSection(clean, 'Current\\s*week\\s*\\(?\\s*Son\\w*\\s*\\w*ly\\s*\\)?', 'Extra\\s*usage|esc\\s*to\\s*cancel|Current\\s*week\\s*\\('));
263
269
  if (weeklySonnetData) {
264
270
  usage.weeklySonnet = { label: 'Current week (Sonnet only)', ...weeklySonnetData };
265
271
  addPaceData(usage.weeklySonnet, 'weekly');
@@ -41,7 +41,7 @@ const timeHelpers = `
41
41
  // Cycle duration constants (mirrored from server-side)
42
42
  const CYCLE_DURATIONS = {
43
43
  session: 5 * 60 * 60,
44
- sessionGemini: 24 * 60 * 60,
44
+ sessionAgy: 24 * 60 * 60,
45
45
  weekly: 7 * 24 * 60 * 60,
46
46
  fiveHour: 5 * 60 * 60
47
47
  };
@@ -74,21 +74,21 @@ const metricExtractors = `
74
74
  return metrics;
75
75
  }
76
76
 
77
- // Extract metrics from Gemini usage data
78
- function extractGeminiMetrics(usage) {
77
+ // Extract metrics from Antigravity usage data
78
+ function extractAgyMetrics(usage) {
79
79
  const metrics = [];
80
80
  if (usage.models && usage.models.length > 0) {
81
81
  for (const model of usage.models) {
82
- const modelName = model.model.toLowerCase();
83
- const metricId = \`gemini-\${modelName.replace(/[^a-z0-9]/g, '-')}\`;
82
+ const modelName = model.model;
83
+ const metricId = \`agy-\${modelName.toLowerCase().replace(/[^a-z0-9]/g, '-')}\`;
84
84
  metrics.push({
85
85
  metricId,
86
- agent: 'gemini',
86
+ agent: 'agy',
87
87
  label: modelName,
88
88
  percent: model.percentUsed ?? 0,
89
89
  resetsIn: model.resetsIn || '',
90
90
  resetsAt: model.resetsAt || '',
91
- cycle: 'sessionGemini'
91
+ cycle: 'sessionAgy'
92
92
  });
93
93
  }
94
94
  }
@@ -142,8 +142,8 @@ ${metricExtractors}
142
142
  let metrics = [];
143
143
  if (agentName === 'claude') {
144
144
  metrics = extractClaudeMetrics(agentData.usage);
145
- } else if (agentName === 'gemini') {
146
- metrics = extractGeminiMetrics(agentData.usage);
145
+ } else if (agentName === 'agy') {
146
+ metrics = extractAgyMetrics(agentData.usage);
147
147
  } else if (agentName === 'codex') {
148
148
  metrics = extractCodexMetrics(agentData.usage);
149
149
  }
@@ -183,7 +183,7 @@ const monitorScript = `
183
183
  const { percent, color, agent, label } = trackedMetric;
184
184
 
185
185
  // Update page title: XX% Agent | Agent Tank (clean format for squeezed tabs)
186
- const agentName = agent.charAt(0).toUpperCase() + agent.slice(1);
186
+ const agentName = agent === 'agy' ? 'Antigravity' : agent.charAt(0).toUpperCase() + agent.slice(1);
187
187
  document.title = \`\${percent}% \${agentName} | Agent Tank\`;
188
188
 
189
189
  // Generate favicon using Canvas API
@@ -204,7 +204,7 @@ const monitorScript = `
204
204
 
205
205
  // Send browser notification
206
206
  if ('Notification' in window && Notification.permission === 'granted') {
207
- const agentName = agent.charAt(0).toUpperCase() + agent.slice(1);
207
+ const agentName = agent === 'agy' ? 'Antigravity' : agent.charAt(0).toUpperCase() + agent.slice(1);
208
208
  new Notification('Agent Tank Warning', {
209
209
  body: \`\${agentName} \${label} is at \${percent}% usage!\`,
210
210
  icon: '/favicon.ico',
package/src/discovery.js CHANGED
@@ -22,22 +22,15 @@ async function discoverAgents() {
22
22
  }
23
23
  }
24
24
 
25
- // Check for Gemini with version requirement
26
- if (commandExists('gemini')) {
27
- const version = getGeminiVersion();
25
+ // Check for Antigravity CLI
26
+ if (commandExists('agy')) {
27
+ const version = getAgyVersion();
28
28
  if (version) {
29
- if (!compareVersion(version, 0, 24, 5)) {
30
- logger.warn(`Gemini CLI version ${version.full} detected. Version 0.24.5+ required for /stats command.`);
31
- logger.warn('Please update: npm update -g gemini');
32
- } else {
33
- logger.success(`✅ Gemini CLI version ${version.full} detected`);
34
- found.push('gemini');
35
- }
29
+ logger.success(`✅ Antigravity CLI version ${version.full} detected`);
36
30
  } else {
37
- // If we can't determine version, add with warning
38
- logger.warn('Gemini found but version unknown. Version 0.24.5+ required for /stats command.');
39
- found.push('gemini');
31
+ logger.success('✅ Antigravity CLI detected (version unknown)');
40
32
  }
33
+ found.push('agy');
41
34
  }
42
35
 
43
36
  // Check for Codex with version info
@@ -81,9 +74,9 @@ function getClaudeVersion() {
81
74
  return null;
82
75
  }
83
76
 
84
- function getGeminiVersion() {
77
+ function getAgyVersion() {
85
78
  try {
86
- const output = execSync('gemini --version 2>&1', { encoding: 'utf-8' });
79
+ const output = execSync('agy --version 2>&1', { encoding: 'utf-8' });
87
80
  const match = output.match(/(\d+)\.(\d+)\.(\d+)/);
88
81
  if (match) {
89
82
  return {
@@ -117,12 +110,4 @@ function getCodexVersion() {
117
110
  return null;
118
111
  }
119
112
 
120
- function compareVersion(version, minMajor, minMinor, minPatch) {
121
- if (version.major > minMajor) return true;
122
- if (version.major < minMajor) return false;
123
- if (version.minor > minMinor) return true;
124
- if (version.minor < minMinor) return false;
125
- return version.patch >= minPatch;
126
- }
127
-
128
- module.exports = { discoverAgents };
113
+ module.exports = { discoverAgents };
@@ -130,7 +130,7 @@ class HistoryStore {
130
130
  /**
131
131
  * Add a usage snapshot to history.
132
132
  *
133
- * @param {string} agentName - Name of the agent (e.g., 'claude', 'gemini', 'codex')
133
+ * @param {string} agentName - Name of the agent (e.g., 'claude', 'agy', 'codex')
134
134
  * @param {Object} usageData - The usage data to store
135
135
  * @param {string} [timestamp] - ISO timestamp (defaults to current time)
136
136
  * @returns {Object} The stored record
package/src/icons.js CHANGED
@@ -12,8 +12,8 @@ const agentIcons = {
12
12
  claude: `<svg class="agent-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
13
13
  <path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" />
14
14
  </svg>`,
15
- gemini: `<svg class="agent-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
16
- <path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" />
15
+ agy: `<svg class="agent-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-label="Antigravity">
16
+ <path d="M21.751 22.607c1.34 1.005 3.35.335 1.508-1.508C17.73 15.74 18.904 1 12.037 1 5.17 1 6.342 15.74.815 21.1c-2.01 2.009.167 2.511 1.507 1.506 5.192-3.517 4.857-9.714 9.715-9.714 4.857 0 4.522 6.197 9.714 9.715z" />
17
17
  </svg>`,
18
18
  codex: `<svg class="agent-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
19
19
  <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
package/src/index.js CHANGED
@@ -2,7 +2,7 @@ const path = require('node:path');
2
2
  const os = require('node:os');
3
3
  const { execFileSync } = require('node:child_process');
4
4
  const { ClaudeAgent } = require('./agents/claude.js');
5
- const { GeminiAgent } = require('./agents/gemini.js');
5
+ const { AgyAgent } = require('./agents/agy.js');
6
6
  const { CodexAgent } = require('./agents/codex.js');
7
7
  const { discoverAgents } = require('./discovery.js');
8
8
  const { fetchPublicStatus } = require('./public-status.js');
@@ -235,11 +235,11 @@ class AgentTank {
235
235
  logger.warn('No compatible LLM agents found.');
236
236
  logger.info('Requirements:');
237
237
  logger.info(' - Claude Code 2.0+ (for /usage command support)');
238
- logger.info(' - Gemini CLI 0.24.5+ (for /stats command support)');
238
+ logger.info(' - Antigravity CLI as agy (for /usage command support)');
239
239
  logger.info(' - Codex CLI');
240
240
  logger.info('\nInstall/update with:');
241
241
  logger.info(' npm install -g @anthropic-ai/claude-code@latest');
242
- logger.info(' npm install -g gemini@latest');
242
+ logger.info(' Install Antigravity CLI from https://antigravity.google/docs/cli-getting-started');
243
243
  throw new Error('No agents found');
244
244
  }
245
245
  logger.success(`✅ Found agents: ${agentNames.join(', ')}`);
@@ -326,8 +326,8 @@ class AgentTank {
326
326
  switch (name) {
327
327
  case 'claude':
328
328
  return new ClaudeAgent({ useApi: this.claudeApi });
329
- case 'gemini':
330
- return new GeminiAgent();
329
+ case 'agy':
330
+ return new AgyAgent();
331
331
  case 'codex':
332
332
  return new CodexAgent();
333
333
  default:
package/src/logger.js CHANGED
@@ -39,7 +39,7 @@ const ANSI = {
39
39
  // Agent color mapping
40
40
  const AGENT_COLORS = {
41
41
  claude: ANSI.magenta,
42
- gemini: ANSI.cyan,
42
+ agy: ANSI.cyan,
43
43
  codex: ANSI.green,
44
44
  };
45
45
 
@@ -139,7 +139,7 @@ const logger = {
139
139
 
140
140
  /**
141
141
  * Log an agent-specific message with mapped color
142
- * @param {string} agentName - Name of the agent (claude, gemini, codex)
142
+ * @param {string} agentName - Name of the agent (claude, agy, codex)
143
143
  * @param {...any} args - Message arguments
144
144
  */
145
145
  agent(agentName, ...args) {
@@ -47,14 +47,14 @@ function attachClaudePace(usage) {
47
47
  }
48
48
 
49
49
  /**
50
- * Attach pace evaluation to Gemini usage metrics.
51
- * @param {Object} usage - Gemini usage data
50
+ * Attach pace evaluation to Antigravity usage metrics.
51
+ * @param {Object} usage - Antigravity usage data
52
52
  */
53
- function attachGeminiPace(usage) {
53
+ function attachAgyPace(usage) {
54
54
  if (!usage.models || !Array.isArray(usage.models)) return;
55
55
 
56
56
  for (const model of usage.models) {
57
- attachPaceToData(model, 'percentUsed', 'sessionGemini');
57
+ attachPaceToData(model, 'percentUsed', 'sessionAgy');
58
58
  }
59
59
  }
60
60
 
@@ -86,8 +86,8 @@ function attachPaceEvaluation(agentName, usage) {
86
86
  case 'claude':
87
87
  attachClaudePace(usage);
88
88
  break;
89
- case 'gemini':
90
- attachGeminiPace(usage);
89
+ case 'agy':
90
+ attachAgyPace(usage);
91
91
  break;
92
92
  case 'codex':
93
93
  attachCodexPace(usage);
@@ -98,7 +98,7 @@ function attachPaceEvaluation(agentName, usage) {
98
98
  module.exports = {
99
99
  attachPaceEvaluation,
100
100
  attachClaudePace,
101
- attachGeminiPace,
101
+ attachAgyPace,
102
102
  attachCodexPace,
103
103
  attachPaceToData
104
104
  };
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Fetches status information from public API status pages for Anthropic, OpenAI,
5
5
  * and Google Cloud. Uses Statuspage.io JSON API for Anthropic and OpenAI, and
6
- * Google Cloud's incidents JSON for Google/Gemini.
6
+ * Google Cloud's incidents JSON for Antigravity's Google-backed models.
7
7
  */
8
8
 
9
9
  const https = require('node:https');
@@ -20,11 +20,11 @@ const STATUS_PAGES = {
20
20
  type: 'statuspage',
21
21
  name: 'OpenAI',
22
22
  },
23
- gemini: {
23
+ agy: {
24
24
  url: 'https://status.cloud.google.com/incidents.json',
25
25
  type: 'google-cloud',
26
26
  name: 'Google Cloud',
27
- // Vertex AI Gemini product ID for filtering incidents
27
+ // Vertex AI Gemini product ID for filtering incidents that may affect Antigravity.
28
28
  productId: 'Z0FZJAMvEB4j3NbCJs6B',
29
29
  },
30
30
  };
@@ -99,7 +99,7 @@ function parseStatuspageResponse(json) {
99
99
  function parseGoogleCloudResponse(json, productId) {
100
100
  const incidents = JSON.parse(json);
101
101
 
102
- // Find active incidents affecting the specified product (e.g., Vertex AI Gemini)
102
+ // Find active incidents affecting the specified product.
103
103
  const now = new Date();
104
104
  const activeIncidents = incidents.filter((incident) => {
105
105
  // Check if incident is ongoing (no end date or recent)
@@ -171,7 +171,7 @@ function parseGoogleCloudResponse(json, productId) {
171
171
 
172
172
  /**
173
173
  * Fetch public status for a single agent
174
- * @param {string} agentName - Name of the agent (claude, gemini, codex)
174
+ * @param {string} agentName - Name of the agent (claude, agy, codex)
175
175
  * @returns {Promise<object>} - Status object with status, indicator, description
176
176
  */
177
177
  async function fetchAgentPublicStatus(agentName) {
@@ -20,11 +20,11 @@ function extractClaudeMetrics(usage) {
20
20
  }
21
21
 
22
22
  /**
23
- * Extract Gemini metrics from usage data.
24
- * @param {Object} usage - Gemini usage data
23
+ * Extract Antigravity metrics from usage data.
24
+ * @param {Object} usage - Antigravity usage data
25
25
  * @returns {Object|null} Extracted metrics or null
26
26
  */
27
- function extractGeminiMetrics(usage) {
27
+ function extractAgyMetrics(usage) {
28
28
  if (!usage.models || !Array.isArray(usage.models)) {
29
29
  return null;
30
30
  }
@@ -62,8 +62,8 @@ function extractSnapshotMetrics(agentName, usage) {
62
62
  switch (agentName) {
63
63
  case 'claude':
64
64
  return extractClaudeMetrics(usage);
65
- case 'gemini':
66
- return extractGeminiMetrics(usage);
65
+ case 'agy':
66
+ return extractAgyMetrics(usage);
67
67
  case 'codex':
68
68
  return extractCodexMetrics(usage);
69
69
  default:
@@ -74,6 +74,6 @@ function extractSnapshotMetrics(agentName, usage) {
74
74
  module.exports = {
75
75
  extractSnapshotMetrics,
76
76
  extractClaudeMetrics,
77
- extractGeminiMetrics,
77
+ extractAgyMetrics,
78
78
  extractCodexMetrics
79
79
  };
@@ -671,20 +671,20 @@ body.light-mode .status-badge-grey {
671
671
  /* Agent icons - neutral low-opacity white/grey for industrial look
672
672
  Status dots (LEDs) are now the primary color indicators */
673
673
  .agent-claude,
674
- .agent-gemini,
674
+ .agent-agy,
675
675
  .agent-codex {
676
676
  --agent-bg-subtle: rgba(255, 255, 255, 0.04);
677
677
  }
678
678
 
679
679
  .agent-claude .agent-icon,
680
- .agent-gemini .agent-icon,
680
+ .agent-agy .agent-icon,
681
681
  .agent-codex .agent-icon {
682
682
  color: var(--text-tertiary);
683
683
  opacity: 0.7;
684
684
  }
685
685
 
686
686
  body.light-mode .agent-claude .agent-icon,
687
- body.light-mode .agent-gemini .agent-icon,
687
+ body.light-mode .agent-agy .agent-icon,
688
688
  body.light-mode .agent-codex .agent-icon {
689
689
  color: var(--text-secondary);
690
690
  opacity: 0.6;
@@ -692,21 +692,21 @@ body.light-mode .agent-codex .agent-icon {
692
692
 
693
693
  /* Agent cards have simple borders - brand colors only on icons */
694
694
  .agent-claude,
695
- .agent-gemini,
695
+ .agent-agy,
696
696
  .agent-codex {
697
697
  background: var(--bg-card);
698
698
  }
699
699
 
700
700
  /* Light mode adjustments for subtle backgrounds */
701
701
  body.light-mode .agent-claude,
702
- body.light-mode .agent-gemini,
702
+ body.light-mode .agent-agy,
703
703
  body.light-mode .agent-codex {
704
704
  --agent-bg-subtle: rgba(0, 0, 0, 0.02);
705
705
  }
706
706
 
707
707
  /* Agent headings use neutral color, only icons have brand colors */
708
708
  .agent-claude h2,
709
- .agent-gemini h2,
709
+ .agent-agy h2,
710
710
  .agent-codex h2 {
711
711
  color: var(--text-primary);
712
712
  }
@@ -8,6 +8,14 @@ const { clientScript } = require('./client-script');
8
8
  const { getStatusBadgeClass, getStatusText } = require('./public-status');
9
9
 
10
10
  const styles = fs.readFileSync(path.join(__dirname, 'status-page.css'), 'utf8');
11
+ const AGENT_DISPLAY_NAMES = {
12
+ agy: 'Antigravity',
13
+ };
14
+
15
+ function getAgentDisplayName(name) {
16
+ if (AGENT_DISPLAY_NAMES[name]) return AGENT_DISPLAY_NAMES[name];
17
+ return name.charAt(0).toUpperCase() + name.slice(1);
18
+ }
11
19
 
12
20
  function escapeHtml(value) {
13
21
  return String(value)
@@ -39,7 +47,7 @@ function statusPage(status) {
39
47
  const statusClass = data.error ? 'error' : data.isRefreshing ? 'refreshing' : 'ok';
40
48
 
41
49
  const icon = agentIcons[name] || '';
42
- const displayName = name.charAt(0).toUpperCase() + name.slice(1);
50
+ const displayName = getAgentDisplayName(name);
43
51
 
44
52
  // Public API status badge
45
53
  const publicStatus = data.publicStatus;
@@ -48,7 +56,7 @@ function statusPage(status) {
48
56
  const badgeTitle = escapeHtml(publicStatus?.description || 'Unable to fetch status');
49
57
  const statusBadgeHtml = `<span class="status-badge ${badgeClass}" title="${badgeTitle}">${badgeText}</span>`;
50
58
 
51
- // Version update notice - check usage.version (Codex) or metadata.updateAvailable (Gemini)
59
+ // Version update notice - check usage.version or metadata.updateAvailable.
52
60
  const version = data.usage?.version || data.metadata?.updateAvailable;
53
61
  const updateHtml = (version && version.current && version.latest && version.current !== version.latest)
54
62
  ? `<div class="version-update-notice">Update available: v${version.current} → v${version.latest}</div>`
@@ -4,7 +4,7 @@ const { formatPaceWarning } = require('./pace-evaluator');
4
4
  // Cycle duration constants in seconds
5
5
  const CYCLE_DURATIONS = {
6
6
  session: 5 * 60 * 60, // 5 hours = 18000 seconds
7
- sessionGemini: 24 * 60 * 60, // 24 hours = 86400 seconds (Gemini uses 24h sessions)
7
+ sessionAgy: 24 * 60 * 60, // 24 hours = 86400 seconds
8
8
  weekly: 7 * 24 * 60 * 60, // 7 days = 604800 seconds
9
9
  fiveHour: 5 * 60 * 60 // 5 hours = 18000 seconds
10
10
  };
@@ -169,22 +169,21 @@ function formatClaudeUsage(usage) {
169
169
  return html;
170
170
  }
171
171
 
172
- function formatGeminiUsage(usage) {
172
+ function formatAgyUsage(usage) {
173
173
  let html = '';
174
174
  if (usage.models && usage.models.length > 0) {
175
175
  for (const model of usage.models) {
176
176
  const percent = model.percentUsed ?? 0;
177
177
  const isZero = percent === 0;
178
- // Use lowercase model name with model-name styling
179
- const modelName = model.model.toLowerCase();
178
+ const modelName = model.model;
180
179
  const resetsIn = model.resetsIn || '';
181
180
 
182
181
  // Use pre-calculated pace data from the usage object
183
182
  const paceData = model.pace || null;
184
183
 
185
184
  html += '<div class="model-container">';
186
- html += usageItem(modelName, percent, '% used', { isZero, isModelName: true, agentName: 'gemini', resetsIn });
187
- html += resetInfoItem(model.resetsIn, null, 'sessionGemini', { isZero, paceData });
185
+ html += usageItem(modelName, percent, '% used', { isZero, isModelName: true, agentName: 'agy', resetsIn });
186
+ html += resetInfoItem(model.resetsIn, null, 'sessionAgy', { isZero, paceData });
188
187
  html += '</div>';
189
188
  }
190
189
  }
@@ -240,7 +239,7 @@ function formatUsage(agentName, usage) {
240
239
  return '<p class="usage-item">No data available</p>';
241
240
  }
242
241
 
243
- const formatters = { claude: formatClaudeUsage, gemini: formatGeminiUsage, codex: formatCodexUsage };
242
+ const formatters = { claude: formatClaudeUsage, agy: formatAgyUsage, codex: formatCodexUsage };
244
243
  const formatter = formatters[agentName];
245
244
  const html = formatter ? formatter(usage) : '';
246
245
 
@@ -250,7 +249,7 @@ function formatUsage(agentName, usage) {
250
249
  module.exports = {
251
250
  formatUsage,
252
251
  formatClaudeUsage,
253
- formatGeminiUsage,
252
+ formatAgyUsage,
254
253
  formatCodexUsage,
255
254
  usageItem,
256
255
  resetInfoItem,