feat(cliproxy): support duplicate-email codex accounts

- keep Codex team and personal auth files as separate identities

- resolve quota and live monitor stats by token file-backed account id

- surface duplicate-aware account labels across the dashboard and variant UI
This commit is contained in:
Tam Nhu Tran
2026-03-30 15:30:11 -04:00
parent 43ea5554e6
commit c73f33872a
23 changed files with 781 additions and 143 deletions
@@ -12,6 +12,7 @@ import {
isClaudeQuotaResult,
isCodexQuotaResult,
} from '@/lib/utils';
import { formatAccountVariantLabel } from '@/lib/account-identity';
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
import {
GripVertical,
@@ -188,6 +189,7 @@ export function AccountCard({
account.tier &&
account.tier !== 'unknown' &&
account.tier !== 'free';
const variantLabel = formatAccountVariantLabel(account.id, account.email);
return (
<div
@@ -226,6 +228,11 @@ export function AccountCard({
>
{cleanEmail(account.email)}
</span>
{variantLabel && (
<span className="text-[7px] font-bold uppercase tracking-wide px-1 py-px rounded shrink-0 bg-muted text-muted-foreground">
{variantLabel}
</span>
)}
{showTierBadge && (
<span
className={cn(
@@ -18,6 +18,7 @@ import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import { useCreateVariant, useCliproxyAuth } from '@/hooks/use-cliproxy';
import { usePrivacy } from '@/contexts/privacy-context';
import { formatAccountDisplayName } from '@/lib/account-identity';
import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
import { isDeniedAgyModelId } from '@/lib/utils';
@@ -220,7 +221,7 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
<option value="">{t('cliproxyDialog.useDefaultAccount')}</option>
{providerAccounts.map((acc) => (
<option key={acc.id} value={acc.id}>
{privacyMode ? '••••••' : acc.email || acc.id}
{privacyMode ? '••••••' : formatAccountDisplayName(acc.id, acc.email)}
{acc.isDefault ? ` ${t('cliproxyDialog.defaultSuffix')}` : ''}
</option>
))}
@@ -40,6 +40,7 @@ import {
isClaudeQuotaResult,
isCodexQuotaResult,
} from '@/lib/utils';
import { formatAccountVariantLabel } from '@/lib/account-identity';
import { getAccountStats } from '@/lib/cliproxy-account-stats';
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
import { useAccountQuota, useCliproxyStats } from '@/hooks/use-cliproxy-stats';
@@ -187,6 +188,7 @@ export function AccountItem({
: failureInfo?.tone === 'destructive'
? 'border-destructive/50 text-destructive'
: 'border-muted-foreground/50 text-muted-foreground';
const variantLabel = formatAccountVariantLabel(account.id, account.email);
return (
<div
@@ -274,6 +276,11 @@ export function AccountItem({
>
{account.email || account.id}
</span>
{variantLabel && (
<Badge variant="outline" className="text-[10px] h-4 px-1.5">
{variantLabel}
</Badge>
)}
{account.isDefault && (
<Badge variant="secondary" className="text-[10px] h-4 px-1.5 gap-0.5">
<Star className="w-2.5 h-2.5 fill-current" />
@@ -4,12 +4,13 @@
import type React from 'react';
import { ChevronRight, AlertTriangle } from 'lucide-react';
import { formatAccountDisplayName } from '@/lib/account-identity';
import { cn, STATUS_COLORS } from '@/lib/utils';
import { PROVIDER_COLORS } from '@/lib/provider-config';
import { ProviderIcon } from '@/components/shared/provider-icon';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import type { ProviderStats } from '../types';
import { getSuccessRate, cleanEmail } from '../utils';
import { getSuccessRate } from '../utils';
import { InlineStatsBadge } from './inline-stats-badge';
interface ProviderCardProps {
@@ -112,7 +113,7 @@ export function ProviderCard({
<div
className={cn('w-2 h-2 rounded-full', acc.paused && 'opacity-50')}
style={{ backgroundColor: acc.color }}
title={privacyMode ? '••••••' : cleanEmail(acc.email)}
title={privacyMode ? '••••••' : formatAccountDisplayName(acc.id, acc.email)}
/>
{isMissingProjectId && (
<TooltipProvider>
@@ -4,6 +4,7 @@
import { Button } from '@/components/ui/button';
import { ChevronRight, ArrowLeft, User, ExternalLink } from 'lucide-react';
import { formatAccountDisplayName } from '@/lib/account-identity';
import { cn } from '@/lib/utils';
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
import type { AccountStepProps } from '../types';
@@ -37,7 +38,7 @@ export function AccountStep({
</div>
<div>
<div className={cn('font-medium', privacyMode && PRIVACY_BLUR_CLASS)}>
{acc.email || acc.id}
{formatAccountDisplayName(acc.id, acc.email)}
</div>
{acc.isDefault && (
<div className="text-xs text-muted-foreground">Default account</div>
@@ -6,6 +6,7 @@ import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { formatAccountDisplayName } from '@/lib/account-identity';
import {
Select,
SelectContent,
@@ -63,7 +64,7 @@ export function VariantStep({
<span>
{t('setupVariant.using')}{' '}
<span className={cn(privacyMode && PRIVACY_BLUR_CLASS)}>
{selectedAccount.email || selectedAccount.id}
{formatAccountDisplayName(selectedAccount.id, selectedAccount.email)}
</span>
</span>
</div>