diff --git a/packages/web/src/components/AccountCard.tsx b/packages/web/src/components/AccountCard.tsx index 4270098..3e68351 100644 --- a/packages/web/src/components/AccountCard.tsx +++ b/packages/web/src/components/AccountCard.tsx @@ -93,10 +93,14 @@ export type AccountData = { }; export function AccountCard({ account }: { account: AccountData }) { + const hasWarning = account.credentialsValid && !account.isExhausted && account.error; + const statusIcon = !account.credentialsValid ? ( ) : account.isExhausted ? ( + ) : hasWarning ? ( + ) : ( ); @@ -105,7 +109,9 @@ export function AccountCard({ account }: { account: AccountData }) { ? "Invalid credentials" : account.isExhausted ? `Exhausted until ${account.exhaustedUntil ? new Date(account.exhaustedUntil).toLocaleTimeString() : "unknown"}` - : "Available"; + : hasWarning + ? "Setup incomplete" + : "Available"; const usage = account.usage; @@ -189,9 +195,11 @@ export function AccountCard({ account }: { account: AccountData }) { )} - {/* Error message */} + {/* Error / warning message */} {account.error && ( -

{account.error}

+

+ {account.error} +

)} diff --git a/src/agent/accounts/usage.ts b/src/agent/accounts/usage.ts index 0fc2a7f..d75c761 100644 --- a/src/agent/accounts/usage.ts +++ b/src/agent/accounts/usage.ts @@ -149,7 +149,12 @@ async function refreshToken( } } -async function fetchUsage(accessToken: string): Promise { +type FetchUsageResult = + | { ok: true; usage: AccountUsage } + | { ok: false; status: number; statusText: string } + | { ok: false; status: 0; statusText: string }; + +async function fetchUsage(accessToken: string): Promise { try { const response = await fetch(USAGE_API_URL, { method: 'GET', @@ -159,17 +164,22 @@ async function fetchUsage(accessToken: string): Promise { 'Content-Type': 'application/json', }, }); - if (!response.ok) return null; + if (!response.ok) { + return { ok: false, status: response.status, statusText: response.statusText }; + } const data = await response.json(); return { - five_hour: data.five_hour ?? null, - seven_day: data.seven_day ?? null, - seven_day_sonnet: data.seven_day_sonnet ?? null, - seven_day_opus: data.seven_day_opus ?? null, - extra_usage: data.extra_usage ?? null, + ok: true, + usage: { + five_hour: data.five_hour ?? null, + seven_day: data.seven_day ?? null, + seven_day_sonnet: data.seven_day_sonnet ?? null, + seven_day_opus: data.seven_day_opus ?? null, + extra_usage: data.extra_usage ?? null, + }, }; - } catch { - return null; + } catch (err) { + return { ok: false, status: 0, statusText: err instanceof Error ? err.message : 'Network error' }; } } @@ -280,12 +290,30 @@ export async function checkAccountHealth( } } - const usage = await fetchUsage(accessToken); - if (!usage) { + const isSetupToken = !currentExpiresAt; + const usageResult = await fetchUsage(accessToken); + + if (!usageResult.ok) { + const statusDetail = usageResult.status > 0 + ? `HTTP ${usageResult.status} ${usageResult.statusText}` + : usageResult.statusText; + + if (isSetupToken) { + // Setup tokens often can't query the usage API — not a hard error + return { + ...base, + credentialsValid: true, + tokenValid: true, + tokenExpiresAt: null, + subscriptionType, + error: `Usage API unavailable for setup token (${statusDetail}). Run \`claude\` with this account to complete OAuth setup.`, + }; + } + return { ...base, credentialsValid: true, - error: 'Usage API request failed', + error: `Usage API request failed: ${statusDetail}`, }; } @@ -295,7 +323,7 @@ export async function checkAccountHealth( tokenValid: true, tokenExpiresAt: currentExpiresAt ? new Date(currentExpiresAt).toISOString() : null, subscriptionType, - usage, + usage: usageResult.usage, }; } catch (err) { return {