From c0cd44ecaa0546b9ca9f2ce55840c539c8bba676 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sat, 30 May 2026 14:57:12 -0400 Subject: [PATCH] fix(cliproxy): infer emails from alternate token prefixes --- .../account-registry-integrity.test.ts | 34 +++++++++++++++++++ src/cliproxy/accounts/registry.ts | 15 +++++--- src/cliproxy/quota/quota-manager.ts | 10 +++--- src/management/checks/image-analysis-check.ts | 5 ++- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/cliproxy/accounts/__tests__/account-registry-integrity.test.ts b/src/cliproxy/accounts/__tests__/account-registry-integrity.test.ts index 935f9787..a12f514c 100644 --- a/src/cliproxy/accounts/__tests__/account-registry-integrity.test.ts +++ b/src/cliproxy/accounts/__tests__/account-registry-integrity.test.ts @@ -280,6 +280,40 @@ describe('account registry integrity', () => { }); }); + it('strips alternate token file prefixes when inferring missing emails', async () => { + const cases = [ + { provider: 'gemini', tokenFile: 'google-user@example.com.json', type: 'gemini' }, + { provider: 'codex', tokenFile: 'openai-user@example.com.json', type: 'codex' }, + { provider: 'agy', tokenFile: 'antigravity-user@example.com.json', type: 'antigravity' }, + { + provider: 'ghcp', + tokenFile: 'github-copilot-user@example.com.json', + type: 'github-copilot', + }, + ] as const; + + for (const testCase of cases) { + await withIsolatedHome(async (homeDir) => { + const authDir = path.join(homeDir, '.ccs', 'cliproxy', 'auth'); + fs.mkdirSync(authDir, { recursive: true }); + fs.writeFileSync( + path.join(authDir, testCase.tokenFile), + JSON.stringify({ type: testCase.type }), + 'utf8' + ); + + const { discoverExistingAccounts, loadAccountsRegistry } = await loadRegistryModule(); + discoverExistingAccounts(); + const registry = loadAccountsRegistry(); + const providerAccounts = registry.providers[testCase.provider]; + + expect(Object.keys(providerAccounts?.accounts ?? {})).toEqual(['user@example.com']); + expect(providerAccounts?.accounts['user@example.com']?.email).toBe('user@example.com'); + expect(providerAccounts?.accounts['user@example.com']?.tokenFile).toBe(testCase.tokenFile); + }); + } + }); + it('preserves the corrupted registry backup when recovery cannot rewrite accounts.json', async () => { await withIsolatedHome(async (homeDir) => { const authDir = path.join(homeDir, '.ccs', 'cliproxy', 'auth'); diff --git a/src/cliproxy/accounts/registry.ts b/src/cliproxy/accounts/registry.ts index fb688341..bf175186 100644 --- a/src/cliproxy/accounts/registry.ts +++ b/src/cliproxy/accounts/registry.ts @@ -7,6 +7,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as lockfile from 'proper-lockfile'; import { CLIProxyProvider } from '../types'; +import { PROVIDER_CAPABILITIES } from '../provider-capabilities'; import { PROVIDER_TYPE_VALUES } from '../auth/auth-types'; import { getAuthDir, getCliproxyDir } from '../config/config-generator'; import { AccountsRegistry, AccountInfo, PROVIDERS_WITHOUT_EMAIL } from './types'; @@ -61,15 +62,21 @@ function resolveProviderFromTokenType(typeValue: string): CLIProxyProvider | und const EMAIL_FILE_NAME_PATTERN = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i; +function stripTokenFileProviderPrefix(baseName: string, provider: CLIProxyProvider): string { + const knownPrefixes = [...PROVIDER_CAPABILITIES[provider].authFilePrefixes, `${provider}-`].sort( + (a, b) => b.length - a.length + ); + const prefix = knownPrefixes.find((knownPrefix) => baseName.startsWith(knownPrefix)); + + return prefix ? baseName.slice(prefix.length) : baseName; +} + function inferEmailFromTokenFileName( tokenFile: string, provider: CLIProxyProvider ): string | undefined { const baseName = tokenFile.replace(/\.json$/i, ''); - const providerPrefix = `${provider}-`; - const candidate = baseName.startsWith(providerPrefix) - ? baseName.slice(providerPrefix.length) - : baseName; + const candidate = stripTokenFileProviderPrefix(baseName, provider); if (PROVIDERS_WITHOUT_EMAIL.includes(provider)) { const scopedCandidate = candidate.slice(candidate.indexOf('-') + 1); diff --git a/src/cliproxy/quota/quota-manager.ts b/src/cliproxy/quota/quota-manager.ts index 8574b079..041e2748 100644 --- a/src/cliproxy/quota/quota-manager.ts +++ b/src/cliproxy/quota/quota-manager.ts @@ -483,9 +483,8 @@ export async function reconcileExhaustedRotationAccounts( return []; } - const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = await import( - '../accounts/account-safety' - ); + const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = + await import('../accounts/account-safety'); restoreExpiredQuotaPauses(); const config = loadOrCreateUnifiedConfig(); @@ -593,9 +592,8 @@ export async function preflightCheck(provider: CLIProxyProvider): Promise * Fix image analysis configuration issues */ export async function fixImageAnalysisConfig(): Promise { - const { updateConfig, loadOrCreateUnifiedConfig } = await import( - '../../config/config-loader-facade' - ); + const { updateConfig, loadOrCreateUnifiedConfig } = + await import('../../config/config-loader-facade'); const config = loadOrCreateUnifiedConfig(); let fixed = false;