agent-fuel 0.4.0 → 0.4.1

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.
@@ -1,13 +1,11 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
- import { TuiScraper } from '../tmux.js';
4
+ import { TuiScraper, sleep } from '../tmux.js';
5
+ import { debug } from '../debug.js';
5
6
  const CACHE_PATH = path.join(os.homedir(), '.gemini/antigravity-cli/.agent-fuel-quota-cache.json');
6
7
  const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
7
8
  // ── Scraping ───────────────────────────────────────────────────────────────
8
- async function sleep(ms) {
9
- return new Promise(res => setTimeout(res, ms));
10
- }
11
9
  /**
12
10
  * Launches `agy` in a tmux session, opens the `/usage` panel, waits for
13
11
  * the Model Quota list to render, then returns clean rendered screen text.
@@ -16,8 +14,16 @@ async function runAgyUsage() {
16
14
  const tui = new TuiScraper('agy');
17
15
  try {
18
16
  tui.start();
19
- // Wait for AGY main menu ready
20
- await tui.waitFor(/for shortcuts/, 20_000);
17
+ // Wait for AGY main menu ready.
18
+ // On first run in a new directory, AGY shows a "Do you trust this project?"
19
+ // prompt. The "Yes, I trust this folder" option is pre-selected; press Enter.
20
+ const firstScreen = await tui.waitFor(/for shortcuts|Do you trust/i, 20_000);
21
+ if (!/for shortcuts/i.test(firstScreen)) {
22
+ debug('agy:scrape', 'trust prompt detected — confirming with Enter');
23
+ await sleep(300); // ensure app is fully interactive before sending input
24
+ tui.sendKey('Enter');
25
+ await tui.waitFor(/for shortcuts/, 15_000);
26
+ }
21
27
  // Navigate to /usage panel
22
28
  tui.send('/usage');
23
29
  await tui.waitFor(/Model Quota/, 10_000);
@@ -1,9 +1,6 @@
1
- import { TuiScraper } from '../tmux.js';
1
+ import { TuiScraper, sleep } from '../tmux.js';
2
2
  import { debug } from '../debug.js';
3
3
  // ── TUI scraper ────────────────────────────────────────────────────────────
4
- async function sleep(ms) {
5
- return new Promise(res => setTimeout(res, ms));
6
- }
7
4
  /**
8
5
  * Launches `claude` in a tmux session, opens /status, navigates to the
9
6
  * Status tab (which shows real quota usage bars), and returns the captured
@@ -33,6 +30,23 @@ async function runClaudeScrape() {
33
30
  tui.kill();
34
31
  }
35
32
  }
33
+ // ── Parser ─────────────────────────────────────────────────────────────────
34
+ /** Convert any 12h am/pm time within a string to 24h (HH:MM). */
35
+ function to24h(s) {
36
+ return s.replace(/\b(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/gi, (_, h, m, meridiem) => {
37
+ let hour = parseInt(h, 10);
38
+ const min = m ?? '00';
39
+ if (meridiem.toLowerCase() === 'am') {
40
+ if (hour === 12)
41
+ hour = 0; // 12am → 00:xx
42
+ }
43
+ else {
44
+ if (hour !== 12)
45
+ hour += 12; // 1–11pm → 13–23
46
+ }
47
+ return `${String(hour).padStart(2, '0')}:${min}`;
48
+ });
49
+ }
36
50
  function parseScrapeOutput(screen) {
37
51
  debug('claude:parse', `screen length: ${screen.length}`);
38
52
  debug('claude:parse', 'screen', screen);
@@ -42,9 +56,10 @@ function parseScrapeOutput(screen) {
42
56
  debug('claude:parse', `found ${usedMatches.length} "% used" matches`);
43
57
  const sessionUsedPct = usedMatches[0] ? parseInt(usedMatches[0][1], 10) : null;
44
58
  const weeklyUsedPct = usedMatches[1] ? parseInt(usedMatches[1][1], 10) : null;
45
- // Reset time: "Resets H:MMam" or "Resets May 30 at 6am" — grab the first occurrence
59
+ // Reset time: "Resets H:MMam" or "Resets May 30 at 6am" — grab the first occurrence,
60
+ // then normalise any 12h am/pm component to 24h (e.g. "11:10pm" → "23:10").
46
61
  const resetMatch = screen.match(/Resets\s+([^\n\r]+)/i);
47
- const sessionResetAt = resetMatch ? resetMatch[1].trim() : null;
62
+ const sessionResetAt = resetMatch ? to24h(resetMatch[1].trim()) : null;
48
63
  debug('claude:parse', 'result', { sessionUsedPct, sessionResetAt, weeklyUsedPct });
49
64
  return { sessionUsedPct, sessionResetAt, weeklyUsedPct };
50
65
  }
@@ -1,7 +1,7 @@
1
1
  import { exec, execFileSync } from 'node:child_process';
2
2
  import { promisify } from 'node:util';
3
3
  import { readFileSync, unlinkSync } from 'node:fs';
4
- import { TuiScraper } from '../tmux.js';
4
+ import { TuiScraper, sleep } from '../tmux.js';
5
5
  import { debug } from '../debug.js';
6
6
  const execAsync = promisify(exec);
7
7
  // Used ONLY as a rough fallback estimate when the TUI scrape cannot determine
@@ -10,9 +10,14 @@ const execAsync = promisify(exec);
10
10
  const DEFAULT_BUDGET_USD = 20.0;
11
11
  const ROLLING_WINDOW_MS = 5 * 60 * 60 * 1000;
12
12
  // ── TUI scraper (tmux) ─────────────────────────────────────────────────────
13
- async function sleep(ms) {
14
- return new Promise(res => setTimeout(res, ms));
15
- }
13
+ // Codex may show one or more blocking dialogs before its main screen ("Tip:").
14
+ // Known dialogs and their dismissal key ("2" = skip/use existing):
15
+ // • Update nag: "Update available! x.x → y.y"
16
+ // • New-model intro: "Introducing GPT-5.5"
17
+ const CODEX_READY = /Tip:/i;
18
+ const CODEX_DIALOG = /Update available|Introducing GPT|Try new model|Use existing model/i;
19
+ const CODEX_EITHER = new RegExp(`(?:${CODEX_READY.source})|(?:${CODEX_DIALOG.source})`, 'i');
20
+ const CODEX_STARTUP_MS = 25_000;
16
21
  /**
17
22
  * Launches `codex` in a tmux session, pipes all terminal bytes to a temp
18
23
  * file, sends /status twice, then reads the file and returns the raw bytes.
@@ -31,8 +36,19 @@ async function runCodexScrape() {
31
36
  // Stream all pane output to a file from the start
32
37
  execFileSync('tmux', ['pipe-pane', '-t', tui.sessionId, `cat >> '${pipePath}'`]);
33
38
  debug('codex:scrape', `pipe-pane logging to ${pipePath}`);
34
- // Wait for TUI ready: current screen (historyLines=0) shows Tip, meaning MCP boot done
35
- await tui.waitFor(/Tip:/i, 20_000, 0);
39
+ // Wait for TUI ready, dismissing any blocking dialogs along the way.
40
+ const dialogDeadline = Date.now() + CODEX_STARTUP_MS;
41
+ let screen = await tui.waitFor(CODEX_EITHER, CODEX_STARTUP_MS, 0);
42
+ while (!CODEX_READY.test(screen)) {
43
+ debug('codex:scrape', 'blocking dialog detected — sending "2" to dismiss');
44
+ tui.send('2');
45
+ await sleep(1_000); // wait for dialog to actually clear before re-checking
46
+ const remaining = dialogDeadline - Date.now(); // compute AFTER sleep
47
+ if (remaining < 500) {
48
+ throw new Error('Codex TUI never reached ready state after dismissing dialogs');
49
+ }
50
+ screen = await tui.waitFor(CODEX_EITHER, remaining, 0);
51
+ }
36
52
  // First /status: panel says "Limits: refresh requested; run /status again shortly"
37
53
  tui.send('/status');
38
54
  await sleep(2_000);
@@ -156,7 +172,7 @@ async function fetchCcusageEstimate(budgetLimit) {
156
172
  if (latestActivity > 0) {
157
173
  try {
158
174
  resetAt = new Date(latestActivity + ROLLING_WINDOW_MS).toLocaleTimeString([], {
159
- hour: '2-digit', minute: '2-digit',
175
+ hour: '2-digit', minute: '2-digit', hour12: false,
160
176
  });
161
177
  }
162
178
  catch { /* leave null */ }
package/dist/index.js CHANGED
File without changes
package/dist/tmux.d.ts CHANGED
@@ -11,3 +11,4 @@ export declare class TuiScraper {
11
11
  waitFor(pattern: RegExp, timeoutMs: number, historyLines?: number): Promise<string>;
12
12
  kill(): void;
13
13
  }
14
+ export declare function sleep(ms: number): Promise<void>;
package/dist/tmux.js CHANGED
@@ -67,6 +67,6 @@ export class TuiScraper {
67
67
  catch { /* already dead */ }
68
68
  }
69
69
  }
70
- function sleep(ms) {
70
+ export function sleep(ms) {
71
71
  return new Promise(res => setTimeout(res, ms));
72
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-fuel",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Sleek term-based dashboard for AI coding CLI quotas",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "author": "Pedro Rodrigues",
29
29
  "license": "MIT",
30
30
  "devDependencies": {
31
- "@types/node": "^20.11.24",
32
- "typescript": "^5.3.3"
31
+ "@types/node": "^25.9.1",
32
+ "typescript": "^6.0.3"
33
33
  }
34
34
  }