diff --git a/src/cliproxy/auth-utils.ts b/src/cliproxy/auth-utils.ts new file mode 100644 index 00000000..e4ba226c --- /dev/null +++ b/src/cliproxy/auth-utils.ts @@ -0,0 +1,27 @@ +/** + * Shared Authentication Utilities + * + * Common functions for OAuth token handling across quota fetchers. + */ + +/** + * Sanitize email to match CLIProxyAPI auth file naming convention. + * Replaces @ and . with underscores for filesystem compatibility. + */ +export function sanitizeEmail(email: string): string { + return email.replace(/@/g, '_').replace(/\\./g, '_'); +} + +/** + * Check if token is expired based on the expired timestamp. + * Returns false if timestamp is missing or invalid (fail-open for quota display). + */ +export function isTokenExpired(expiredStr?: string): boolean { + if (!expiredStr) return false; + try { + const expiredDate = new Date(expiredStr); + return expiredDate.getTime() < Date.now(); + } catch { + return false; + } +} diff --git a/src/cliproxy/quota-fetcher-codex.ts b/src/cliproxy/quota-fetcher-codex.ts index 27f66f92..d8fd53b6 100644 --- a/src/cliproxy/quota-fetcher-codex.ts +++ b/src/cliproxy/quota-fetcher-codex.ts @@ -9,12 +9,16 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { getAuthDir } from './config-generator'; import { getProviderAccounts, getPausedDir } from './account-manager'; +import { sanitizeEmail, isTokenExpired } from './auth-utils'; import type { CodexQuotaResult, CodexQuotaWindow } from './quota-types'; /** ChatGPT backend API base URL */ const CODEX_API_BASE = 'https://chatgpt.com/backend-api'; -/** User agent matching Codex CLI */ +/** + * User agent matching Codex CLI for API compatibility. + * Update when Codex CLI releases new versions to maintain compatibility. + */ const USER_AGENT = 'codex_cli_rs/0.76.0 (Debian 13.0.0; x86_64) WindowsTerminal'; /** Auth data extracted from Codex auth file */ @@ -51,26 +55,6 @@ interface CodexWindowData { resetAfterSeconds?: number | null; } -/** - * Sanitize email to match CLIProxyAPI auth file naming convention - */ -function sanitizeEmail(email: string): string { - return email.replace(/@/g, '_').replace(/\./g, '_'); -} - -/** - * Check if token is expired based on the expired timestamp - */ -function isTokenExpired(expiredStr?: string): boolean { - if (!expiredStr) return false; - try { - const expiredDate = new Date(expiredStr); - return expiredDate.getTime() < Date.now(); - } catch { - return false; - } -} - /** * Read auth data from Codex auth file */ diff --git a/src/cliproxy/quota-fetcher-gemini-cli.ts b/src/cliproxy/quota-fetcher-gemini-cli.ts index e013e23e..1eca2583 100644 --- a/src/cliproxy/quota-fetcher-gemini-cli.ts +++ b/src/cliproxy/quota-fetcher-gemini-cli.ts @@ -9,6 +9,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { getAuthDir } from './config-generator'; import { getProviderAccounts, getPausedDir } from './account-manager'; +import { sanitizeEmail, isTokenExpired } from './auth-utils'; import type { GeminiCliQuotaResult, GeminiCliBucket } from './quota-types'; /** Google Cloud Code API endpoints */ @@ -81,26 +82,6 @@ function resolveGeminiCliProjectId(accountField: string): string | null { return lastMatch; } -/** - * Sanitize email to match CLIProxyAPI auth file naming convention - */ -function sanitizeEmail(email: string): string { - return email.replace(/@/g, '_').replace(/\./g, '_'); -} - -/** - * Check if token is expired based on the expired timestamp - */ -function isTokenExpired(expiredStr?: string): boolean { - if (!expiredStr) return false; - try { - const expiredDate = new Date(expiredStr); - return expiredDate.getTime() < Date.now(); - } catch { - return false; - } -} - /** * Read auth data from Gemini CLI auth file */ diff --git a/src/commands/cliproxy-command.ts b/src/commands/cliproxy-command.ts index e15474f7..d9cb5670 100644 --- a/src/commands/cliproxy-command.ts +++ b/src/commands/cliproxy-command.ts @@ -1002,7 +1002,7 @@ async function handleQuotaStatus( // Display Antigravity section if (agyResults && agyResults.accounts.length > 0) { - displayAntigravityQuotaSection(agyResults, verbose); + displayAntigravityQuotaSection(agyResults); } else if (shouldFetch.agy) { console.log(subheader('Antigravity (0 accounts)')); console.log(info('No Antigravity accounts configured')); @@ -1035,8 +1035,7 @@ async function handleQuotaStatus( * Display Antigravity quota section */ function displayAntigravityQuotaSection( - quotaResult: Awaited>, - _verbose: boolean + quotaResult: Awaited> ): void { const provider: CLIProxyProvider = 'agy'; const accounts = getProviderAccounts(provider);