Harden usage dashboard and codex auth

This commit is contained in:
Eyejoker
2026-03-25 10:40:02 +09:00
parent 00ffde7623
commit 83aedba7f3
4 changed files with 47 additions and 18 deletions

View File

@@ -108,7 +108,10 @@ async function fetchUsageForToken(
if (res.status === 401) { if (res.status === 401) {
logger.warn( logger.warn(
{ account: accountIndex != null ? accountIndex + 1 : '?', tokenKey: key }, {
account: accountIndex != null ? accountIndex + 1 : '?',
tokenKey: key,
},
'Claude usage API: token expired or invalid (401)', 'Claude usage API: token expired or invalid (401)',
); );
return null; return null;
@@ -116,7 +119,11 @@ async function fetchUsageForToken(
if (res.status === 429) { if (res.status === 429) {
const staleMs = cached ? Date.now() - cached.fetchedAt : 0; const staleMs = cached ? Date.now() - cached.fetchedAt : 0;
logger.warn( 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', 'Claude usage API: rate limited (429), returning cached data',
); );
// Record attempt time so we don't retry for MIN_FETCH_INTERVAL_MS // Record attempt time so we don't retry for MIN_FETCH_INTERVAL_MS
@@ -128,7 +135,11 @@ async function fetchUsageForToken(
} }
if (!res.ok) { if (!res.ok) {
logger.warn( 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}`, `Claude usage API: unexpected status ${res.status}`,
); );
if (cached) { if (cached) {
@@ -153,7 +164,11 @@ async function fetchUsageForToken(
saveUsageDiskCache(); saveUsageDiskCache();
logger.debug( 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', 'Claude usage API: fetched successfully',
); );

View File

@@ -344,7 +344,12 @@ export function updateCodexAccountUsage(
saveCodexState(); saveCodexState();
// Auto-rotate away from 7d-exhausted current account to avoid API billing // 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); const nextIdx = findNextCodexAvailable(idx);
if (nextIdx !== null && nextIdx !== idx) { if (nextIdx !== null && nextIdx !== idx) {
logger.info( logger.info(

View File

@@ -33,15 +33,15 @@ export function stripInternalTags(text: string): string {
* Matched strings are replaced with `[REDACTED]`. * Matched strings are replaced with `[REDACTED]`.
*/ */
const SECRET_PATTERNS: RegExp[] = [ const SECRET_PATTERNS: RegExp[] = [
/sk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic /sk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic
/sk-[A-Za-z0-9_-]{20,}/g, // OpenAI /sk-[A-Za-z0-9_-]{20,}/g, // OpenAI
/gsk_[A-Za-z0-9_-]{20,}/g, // Groq /gsk_[A-Za-z0-9_-]{20,}/g, // Groq
/xai-[A-Za-z0-9_-]{20,}/g, // xAI /xai-[A-Za-z0-9_-]{20,}/g, // xAI
/ghp_[A-Za-z0-9_]{36,}/g, // GitHub PAT classic /ghp_[A-Za-z0-9_]{36,}/g, // GitHub PAT classic
/github_pat_[A-Za-z0-9_]{20,}/g, // GitHub PAT fine-grained /github_pat_[A-Za-z0-9_]{20,}/g, // GitHub PAT fine-grained
/glpat-[A-Za-z0-9_-]{20,}/g, // GitLab PAT /glpat-[A-Za-z0-9_-]{20,}/g, // GitLab PAT
/AKIA[A-Z0-9]{16}/g, // AWS Access Key /AKIA[A-Z0-9]{16}/g, // AWS Access Key
/Bearer\s+eyJ[A-Za-z0-9_-]{40,}/g, // Bearer JWT /Bearer\s+eyJ[A-Za-z0-9_-]{40,}/g, // Bearer JWT
]; ];
function redactSecrets(text: string): string { function redactSecrets(text: string): string {

View File

@@ -558,8 +558,9 @@ async function buildUsageContent(): Promise<string> {
// Codex usage: read from Codex service's own status snapshot. // Codex usage: read from Codex service's own status snapshot.
// Each service owns its usage data — no cross-service auth access needed. // Each service owns its usage data — no cross-service auth access needed.
const codexSnapshot = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS) const codexSnapshot = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS).find(
.find(s => s.agentType === 'codex'); (s) => s.agentType === 'codex',
);
rows.push(...extractCodexUsageRows(codexSnapshot, USAGE_SNAPSHOT_MAX_AGE_MS)); rows.push(...extractCodexUsageRows(codexSnapshot, USAGE_SNAPSHOT_MAX_AGE_MS));
if (rows.length > 0) { if (rows.length > 0) {
@@ -720,7 +721,13 @@ function applyCodexUsageToAccount(
: undefined; : undefined;
updateCodexAccountUsage(pct, resetStr, accountIndex, d7Pct, resetD7Str); updateCodexAccountUsage(pct, resetStr, accountIndex, d7Pct, resetD7Str);
logger.info( 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}%`, `Codex account #${accountIndex + 1} usage: 5h=${pct}% 7d=${d7Pct}%`,
); );
} }
@@ -895,7 +902,9 @@ export async function startUnifiedDashboard(
} else { } else {
// Codex service: fetch own usage and expose via status snapshot. // Codex service: fetch own usage and expose via status snapshot.
// Active account every usageUpdateInterval; full scan on startup + hourly. // 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 refreshActiveCodexUsage(), opts.usageUpdateInterval);
setInterval( setInterval(
() => void refreshAllCodexAccountUsage(), () => void refreshAllCodexAccountUsage(),