From 83aedba7f3e1598216bd4dbb4af1571b60a8f5fb Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Wed, 25 Mar 2026 10:40:02 +0900 Subject: [PATCH] Harden usage dashboard and codex auth --- src/claude-usage.ts | 23 +++++++++++++++++++---- src/codex-token-rotation.ts | 7 ++++++- src/router.ts | 18 +++++++++--------- src/unified-dashboard.ts | 17 +++++++++++++---- 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/claude-usage.ts b/src/claude-usage.ts index cd7fb7f..a07bf4a 100644 --- a/src/claude-usage.ts +++ b/src/claude-usage.ts @@ -108,7 +108,10 @@ async function fetchUsageForToken( if (res.status === 401) { logger.warn( - { account: accountIndex != null ? accountIndex + 1 : '?', tokenKey: key }, + { + account: accountIndex != null ? accountIndex + 1 : '?', + tokenKey: key, + }, 'Claude usage API: token expired or invalid (401)', ); return null; @@ -116,7 +119,11 @@ async function fetchUsageForToken( if (res.status === 429) { const staleMs = cached ? Date.now() - cached.fetchedAt : 0; logger.warn( - { account: accountIndex != null ? accountIndex + 1 : '?', tokenKey: key, staleMinutes: Math.round(staleMs / 60_000) }, + { + account: accountIndex != null ? accountIndex + 1 : '?', + tokenKey: key, + staleMinutes: Math.round(staleMs / 60_000), + }, 'Claude usage API: rate limited (429), returning cached data', ); // Record attempt time so we don't retry for MIN_FETCH_INTERVAL_MS @@ -128,7 +135,11 @@ async function fetchUsageForToken( } if (!res.ok) { logger.warn( - { account: accountIndex != null ? accountIndex + 1 : '?', status: res.status, tokenKey: key }, + { + account: accountIndex != null ? accountIndex + 1 : '?', + status: res.status, + tokenKey: key, + }, `Claude usage API: unexpected status ${res.status}`, ); if (cached) { @@ -153,7 +164,11 @@ async function fetchUsageForToken( saveUsageDiskCache(); logger.debug( - { tokenKey: key, h5: result.five_hour?.utilization, d7: result.seven_day?.utilization }, + { + tokenKey: key, + h5: result.five_hour?.utilization, + d7: result.seven_day?.utilization, + }, 'Claude usage API: fetched successfully', ); diff --git a/src/codex-token-rotation.ts b/src/codex-token-rotation.ts index 26a166b..49ed617 100644 --- a/src/codex-token-rotation.ts +++ b/src/codex-token-rotation.ts @@ -344,7 +344,12 @@ export function updateCodexAccountUsage( saveCodexState(); // Auto-rotate away from 7d-exhausted current account to avoid API billing - if (idx === currentIndex && d7Pct != null && d7Pct >= 100 && accounts.length > 1) { + if ( + idx === currentIndex && + d7Pct != null && + d7Pct >= 100 && + accounts.length > 1 + ) { const nextIdx = findNextCodexAvailable(idx); if (nextIdx !== null && nextIdx !== idx) { logger.info( diff --git a/src/router.ts b/src/router.ts index 121c925..c66680c 100644 --- a/src/router.ts +++ b/src/router.ts @@ -33,15 +33,15 @@ export function stripInternalTags(text: string): string { * Matched strings are replaced with `[REDACTED]`. */ const SECRET_PATTERNS: RegExp[] = [ - /sk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic - /sk-[A-Za-z0-9_-]{20,}/g, // OpenAI - /gsk_[A-Za-z0-9_-]{20,}/g, // Groq - /xai-[A-Za-z0-9_-]{20,}/g, // xAI - /ghp_[A-Za-z0-9_]{36,}/g, // GitHub PAT classic - /github_pat_[A-Za-z0-9_]{20,}/g, // GitHub PAT fine-grained - /glpat-[A-Za-z0-9_-]{20,}/g, // GitLab PAT - /AKIA[A-Z0-9]{16}/g, // AWS Access Key - /Bearer\s+eyJ[A-Za-z0-9_-]{40,}/g, // Bearer JWT + /sk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic + /sk-[A-Za-z0-9_-]{20,}/g, // OpenAI + /gsk_[A-Za-z0-9_-]{20,}/g, // Groq + /xai-[A-Za-z0-9_-]{20,}/g, // xAI + /ghp_[A-Za-z0-9_]{36,}/g, // GitHub PAT classic + /github_pat_[A-Za-z0-9_]{20,}/g, // GitHub PAT fine-grained + /glpat-[A-Za-z0-9_-]{20,}/g, // GitLab PAT + /AKIA[A-Z0-9]{16}/g, // AWS Access Key + /Bearer\s+eyJ[A-Za-z0-9_-]{40,}/g, // Bearer JWT ]; function redactSecrets(text: string): string { diff --git a/src/unified-dashboard.ts b/src/unified-dashboard.ts index 8ae2e5a..8c6fa6d 100644 --- a/src/unified-dashboard.ts +++ b/src/unified-dashboard.ts @@ -558,8 +558,9 @@ async function buildUsageContent(): Promise { // Codex usage: read from Codex service's own status snapshot. // Each service owns its usage data — no cross-service auth access needed. - const codexSnapshot = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS) - .find(s => s.agentType === 'codex'); + const codexSnapshot = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS).find( + (s) => s.agentType === 'codex', + ); rows.push(...extractCodexUsageRows(codexSnapshot, USAGE_SNAPSHOT_MAX_AGE_MS)); if (rows.length > 0) { @@ -720,7 +721,13 @@ function applyCodexUsageToAccount( : undefined; updateCodexAccountUsage(pct, resetStr, accountIndex, d7Pct, resetD7Str); logger.info( - { account: accountIndex + 1, bucket: effective.limitId, h5: pct, d7: d7Pct, reset: resetStr }, + { + account: accountIndex + 1, + bucket: effective.limitId, + h5: pct, + d7: d7Pct, + reset: resetStr, + }, `Codex account #${accountIndex + 1} usage: 5h=${pct}% 7d=${d7Pct}%`, ); } @@ -895,7 +902,9 @@ export async function startUnifiedDashboard( } else { // Codex service: fetch own usage and expose via status snapshot. // Active account every usageUpdateInterval; full scan on startup + hourly. - void refreshAllCodexAccountUsage().then(() => void refreshActiveCodexUsage()); + void refreshAllCodexAccountUsage().then( + () => void refreshActiveCodexUsage(), + ); setInterval(() => void refreshActiveCodexUsage(), opts.usageUpdateInterval); setInterval( () => void refreshAllCodexAccountUsage(),