feat: fetch Claude account profiles (plan type) on startup
- Call /api/oauth/profile for each token, cache in memory - Display plan type (max/pro/free) next to Claude account names - Profiles fetched once on startup, no periodic refresh needed
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
import { logger } from './logger.js';
|
||||
import { getCurrentToken, getAllTokens } from './token-rotation.js';
|
||||
|
||||
const PROFILE_ENDPOINT = 'https://api.anthropic.com/api/oauth/profile';
|
||||
|
||||
export interface ClaudeUsageData {
|
||||
five_hour?: { utilization: number; resets_at: string };
|
||||
seven_day?: { utilization: number; resets_at: string };
|
||||
@@ -99,6 +101,66 @@ export async function fetchClaudeUsage(): Promise<ClaudeUsageData | null> {
|
||||
return fetchUsageForToken(token);
|
||||
}
|
||||
|
||||
export interface ClaudeAccountProfile {
|
||||
email: string;
|
||||
planType: string; // "max", "pro", "free"
|
||||
}
|
||||
|
||||
const profileCache = new Map<number, ClaudeAccountProfile>();
|
||||
|
||||
async function fetchProfileForToken(token: string): Promise<ClaudeAccountProfile | null> {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
||||
try {
|
||||
const res = await fetch(PROFILE_ENDPOINT, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'anthropic-beta': 'oauth-2025-04-20',
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json() as {
|
||||
account?: { email?: string; has_claude_max?: boolean; has_claude_pro?: boolean };
|
||||
organization?: { organization_type?: string };
|
||||
};
|
||||
const orgType = data.organization?.organization_type || '';
|
||||
const planType = data.account?.has_claude_max ? 'max'
|
||||
: data.account?.has_claude_pro ? 'pro'
|
||||
: orgType.replace('claude_', '') || 'free';
|
||||
return {
|
||||
email: data.account?.email || '?',
|
||||
planType,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch profiles for all Claude tokens (cached, called once on startup).
|
||||
*/
|
||||
export async function fetchAllClaudeProfiles(): Promise<void> {
|
||||
const allTokens = getAllTokens();
|
||||
for (const t of allTokens) {
|
||||
const profile = await fetchProfileForToken(t.token);
|
||||
if (profile) {
|
||||
profileCache.set(t.index, profile);
|
||||
logger.info(
|
||||
{ account: t.index + 1, plan: profile.planType, email: profile.email },
|
||||
`Claude account #${t.index + 1}: ${profile.planType}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getClaudeProfile(index: number): ClaudeAccountProfile | undefined {
|
||||
return profileCache.get(index);
|
||||
}
|
||||
|
||||
export interface ClaudeAccountUsage {
|
||||
index: number;
|
||||
masked: string;
|
||||
|
||||
@@ -7,6 +7,8 @@ import { STATUS_SHOW_ROOMS, USAGE_DASHBOARD_ENABLED } from './config.js';
|
||||
import {
|
||||
fetchClaudeUsage as fetchClaudeUsageApi,
|
||||
fetchAllClaudeUsage,
|
||||
fetchAllClaudeProfiles,
|
||||
getClaudeProfile,
|
||||
type ClaudeUsageData,
|
||||
type ClaudeAccountUsage,
|
||||
} from './claude-usage.js';
|
||||
@@ -600,12 +602,14 @@ async function buildUsageContent(): Promise<string> {
|
||||
if (!u) continue;
|
||||
const h5 = u.five_hour;
|
||||
const d7 = u.seven_day;
|
||||
const profile = getClaudeProfile(account.index);
|
||||
const planSuffix = profile ? ` ${profile.planType}` : '';
|
||||
const label =
|
||||
claudeAccounts.length > 1
|
||||
? `Claude${account.index + 1}${account.isActive ? '*' : ''}${account.isRateLimited ? '!' : ''}`
|
||||
? `Claude${account.index + 1}${account.isActive ? '*' : ''}${account.isRateLimited ? '!' : ''}${planSuffix}`
|
||||
: claudeUsageIsCached
|
||||
? 'Claude*'
|
||||
: 'Claude';
|
||||
? `Claude*${planSuffix}`
|
||||
: `Claude${planSuffix}`;
|
||||
rows.push({
|
||||
name: label,
|
||||
h5pct: h5
|
||||
@@ -863,6 +867,7 @@ export async function startUnifiedDashboard(
|
||||
}
|
||||
|
||||
if (isRenderer) {
|
||||
await fetchAllClaudeProfiles();
|
||||
await refreshUsageCache();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user