From ed2ce138e41f07997eb6fa7e650cb4f16849b3df Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 15 Jan 2026 10:49:23 -0500 Subject: [PATCH 1/4] feat(dashboard): add project_id display for Antigravity accounts Display GCP project_id for Antigravity (agy) accounts in the Dashboard: - Add projectId field to AccountInfo interface (account-manager.ts) - Read project_id from auth files when discovering/registering accounts - Pass projectId through token-manager when registering new accounts - Add projectId to OAuthAccount type (api-client.ts) - Add projectId to AccountRow type (auth-monitor/types.ts) - Display project_id in account-item.tsx with FolderCode icon - Show N/A warning with amber tooltip if project_id is missing suggesting user remove and re-add account to fetch it Note: project_id is read-only and respects privacy mode blur. --- src/cliproxy/account-manager.ts | 23 ++++++++-- src/cliproxy/auth/token-manager.ts | 9 +++- .../cliproxy/provider-editor/account-item.tsx | 44 +++++++++++++++++++ .../monitoring/auth-monitor/hooks.ts | 1 + .../monitoring/auth-monitor/types.ts | 2 + ui/src/lib/api-client.ts | 2 + 6 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/cliproxy/account-manager.ts b/src/cliproxy/account-manager.ts index fc450c7b..8906994b 100644 --- a/src/cliproxy/account-manager.ts +++ b/src/cliproxy/account-manager.ts @@ -47,6 +47,8 @@ export interface AccountInfo { pausedAt?: string; /** Account tier: free or paid (Pro/Ultra combined) */ tier?: AccountTier; + /** GCP Project ID (Antigravity only) - read-only, fetched from auth token */ + projectId?: string; } /** Provider accounts configuration */ @@ -293,7 +295,8 @@ export function registerAccount( provider: CLIProxyProvider, tokenFile: string, email?: string, - nickname?: string + nickname?: string, + projectId?: string ): AccountInfo { const registry = loadAccountsRegistry(); @@ -350,7 +353,7 @@ export function registerAccount( const isFirstAccount = Object.keys(providerAccounts.accounts).length === 0; // Create or update account - providerAccounts.accounts[accountId] = { + const accountMeta: Omit = { email, nickname: accountNickname, tokenFile, @@ -358,6 +361,13 @@ export function registerAccount( lastUsedAt: new Date().toISOString(), }; + // Include projectId for Antigravity accounts + if (provider === 'agy' && projectId) { + accountMeta.projectId = projectId; + } + + providerAccounts.accounts[accountId] = accountMeta; + // Set as default if first account if (isFirstAccount) { providerAccounts.default = accountId; @@ -671,13 +681,20 @@ export function discoverExistingAccounts(): void { // Register account with auto-generated nickname // Use mtime as lastUsedAt (when token was last modified = last auth/refresh) const lastModified = stats.mtime || stats.birthtime || new Date(); - providerAccounts.accounts[accountId] = { + const accountMeta: Omit = { email, nickname: generateNickname(email), tokenFile: file, createdAt: stats.birthtime?.toISOString() || new Date().toISOString(), lastUsedAt: lastModified.toISOString(), }; + + // Read project_id for Antigravity accounts (read-only field from auth token) + if (provider === 'agy' && data.project_id) { + accountMeta.projectId = data.project_id; + } + + providerAccounts.accounts[accountId] = accountMeta; } catch { // Skip invalid files continue; diff --git a/src/cliproxy/auth/token-manager.ts b/src/cliproxy/auth/token-manager.ts index 436539cc..16e79c78 100644 --- a/src/cliproxy/auth/token-manager.ts +++ b/src/cliproxy/auth/token-manager.ts @@ -229,8 +229,15 @@ export function registerAccountFromToken( const content = fs.readFileSync(tokenPath, 'utf-8'); const data = JSON.parse(content); const email = data.email || undefined; + const projectId = data.project_id || undefined; - return registerAccount(provider, newestFile, email, nickname || generateNickname(email)); + return registerAccount( + provider, + newestFile, + email, + nickname || generateNickname(email), + projectId + ); } catch { return null; } diff --git a/ui/src/components/cliproxy/provider-editor/account-item.tsx b/ui/src/components/cliproxy/provider-editor/account-item.tsx index 031a01b3..d61ea74d 100644 --- a/ui/src/components/cliproxy/provider-editor/account-item.tsx +++ b/ui/src/components/cliproxy/provider-editor/account-item.tsx @@ -25,6 +25,8 @@ import { Pause, Play, AlertCircle, + AlertTriangle, + FolderCode, } from 'lucide-react'; import { cn, @@ -167,6 +169,48 @@ export function AccountItem({ )} + {/* Project ID for Antigravity accounts - read-only */} + {account.provider === 'agy' && ( +
+ {account.projectId ? ( + + + +
+ + + {account.projectId} + +
+
+ +

GCP Project ID (read-only)

+
+
+
+ ) : ( + + + +
+ + Project ID: N/A +
+
+ +
+

Missing Project ID

+

+ This may cause errors. Remove the account and re-add it to fetch the + project ID. +

+
+
+
+
+ )} +
+ )} {account.lastUsedAt && (
diff --git a/ui/src/components/monitoring/auth-monitor/hooks.ts b/ui/src/components/monitoring/auth-monitor/hooks.ts index 1640f037..dd19b8bc 100644 --- a/ui/src/components/monitoring/auth-monitor/hooks.ts +++ b/ui/src/components/monitoring/auth-monitor/hooks.ts @@ -99,6 +99,7 @@ export function useAuthMonitorData(): AuthMonitorData { failureCount: failure, lastUsedAt: realStats?.lastUsedAt ?? account.lastUsedAt, color: ACCOUNT_COLORS[colorIndex % ACCOUNT_COLORS.length], + projectId: account.projectId, }; accountsList.push(row); providerData.accounts.push(row); diff --git a/ui/src/components/monitoring/auth-monitor/types.ts b/ui/src/components/monitoring/auth-monitor/types.ts index 1eb61953..408b1e71 100644 --- a/ui/src/components/monitoring/auth-monitor/types.ts +++ b/ui/src/components/monitoring/auth-monitor/types.ts @@ -12,6 +12,8 @@ export interface AccountRow { failureCount: number; lastUsedAt?: string; color: string; + /** GCP Project ID (Antigravity only) - read-only */ + projectId?: string; } export interface ProviderStats { diff --git a/ui/src/lib/api-client.ts b/ui/src/lib/api-client.ts index 22d67bb0..95ce900c 100644 --- a/ui/src/lib/api-client.ts +++ b/ui/src/lib/api-client.ts @@ -83,6 +83,8 @@ export interface OAuthAccount { pausedAt?: string; /** Account tier: free or paid (Pro/Ultra combined) */ tier?: 'free' | 'paid' | 'unknown'; + /** GCP Project ID (Antigravity only) - read-only */ + projectId?: string; } export interface AuthStatus { From 36367d49f0f51f4ecba9a32adf54308af153bdb2 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 15 Jan 2026 11:22:40 -0500 Subject: [PATCH 2/4] fix(dashboard): update projectId for existing accounts during discovery Previously, discoverExistingAccounts skipped existing token files entirely, so accounts created before the projectId feature never got their projectId populated from auth files. Now when a token file is already registered, we still check if projectId needs to be updated for agy accounts. --- src/cliproxy/account-manager.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/cliproxy/account-manager.ts b/src/cliproxy/account-manager.ts index 8906994b..7803b451 100644 --- a/src/cliproxy/account-manager.ts +++ b/src/cliproxy/account-manager.ts @@ -639,6 +639,15 @@ export function discoverExistingAccounts(): void { // Skip if token file already registered (under any accountId) const existingTokenFiles = Object.values(providerAccounts.accounts).map((a) => a.tokenFile); if (existingTokenFiles.includes(file)) { + // Token file exists - check if we need to update projectId for agy accounts + if (provider === 'agy' && data.project_id) { + const existingEntry = Object.entries(providerAccounts.accounts).find( + ([, meta]) => meta.tokenFile === file + ); + if (existingEntry && !existingEntry[1].projectId) { + existingEntry[1].projectId = data.project_id; + } + } continue; } @@ -710,7 +719,7 @@ export function discoverExistingAccounts(): void { if (!freshRegistry.providers[prov]) { freshRegistry.providers[prov] = discovered; } else { - // Merge accounts, preferring fresh registry's existing entries + // Merge accounts, preferring fresh registry's existing entries but updating projectId const freshProviderAccounts = freshRegistry.providers[prov]; if (!freshProviderAccounts) continue; for (const [id, meta] of Object.entries(discovered.accounts)) { @@ -720,6 +729,9 @@ export function discoverExistingAccounts(): void { if (!freshProviderAccounts.default || freshProviderAccounts.default === 'default') { freshProviderAccounts.default = id; } + } else if (meta.projectId && !freshProviderAccounts.accounts[id].projectId) { + // Update existing account with projectId if discovered from auth file + freshProviderAccounts.accounts[id].projectId = meta.projectId; } } } From bc02ecc94c5120bb0a4491fd9f88c71fb9f26b7f Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 15 Jan 2026 11:46:02 -0500 Subject: [PATCH 3/4] fix(dashboard): harden projectId handling with edge case fixes - Validate empty string projectId with typeof check and trim() - Include projectId in registerAccount return value - Update projectId when changed (not just when missing) - Add aria-hidden/aria-label for accessibility - Add max-width + truncate for long projectId overflow --- src/cliproxy/account-manager.ts | 20 ++++++++++++++----- .../cliproxy/provider-editor/account-item.tsx | 12 ++++++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/cliproxy/account-manager.ts b/src/cliproxy/account-manager.ts index 7803b451..29ce11ba 100644 --- a/src/cliproxy/account-manager.ts +++ b/src/cliproxy/account-manager.ts @@ -384,6 +384,7 @@ export function registerAccount( tokenFile, createdAt: providerAccounts.accounts[accountId].createdAt, lastUsedAt: providerAccounts.accounts[accountId].lastUsedAt, + projectId: providerAccounts.accounts[accountId].projectId, }; } @@ -640,12 +641,17 @@ export function discoverExistingAccounts(): void { const existingTokenFiles = Object.values(providerAccounts.accounts).map((a) => a.tokenFile); if (existingTokenFiles.includes(file)) { // Token file exists - check if we need to update projectId for agy accounts - if (provider === 'agy' && data.project_id) { + const projectIdValue = + typeof data.project_id === 'string' && data.project_id.trim() + ? data.project_id.trim() + : null; + if (provider === 'agy' && projectIdValue) { const existingEntry = Object.entries(providerAccounts.accounts).find( ([, meta]) => meta.tokenFile === file ); - if (existingEntry && !existingEntry[1].projectId) { - existingEntry[1].projectId = data.project_id; + // Update if missing or changed + if (existingEntry && existingEntry[1].projectId !== projectIdValue) { + existingEntry[1].projectId = projectIdValue; } } continue; @@ -699,8 +705,12 @@ export function discoverExistingAccounts(): void { }; // Read project_id for Antigravity accounts (read-only field from auth token) - if (provider === 'agy' && data.project_id) { - accountMeta.projectId = data.project_id; + const discoveredProjectId = + typeof data.project_id === 'string' && data.project_id.trim() + ? data.project_id.trim() + : null; + if (provider === 'agy' && discoveredProjectId) { + accountMeta.projectId = discoveredProjectId; } providerAccounts.accounts[accountId] = accountMeta; diff --git a/ui/src/components/cliproxy/provider-editor/account-item.tsx b/ui/src/components/cliproxy/provider-editor/account-item.tsx index d61ea74d..2aec131d 100644 --- a/ui/src/components/cliproxy/provider-editor/account-item.tsx +++ b/ui/src/components/cliproxy/provider-editor/account-item.tsx @@ -177,8 +177,14 @@ export function AccountItem({
- - +
@@ -193,7 +199,7 @@ export function AccountItem({
- + Project ID: N/A
From 28b0faa0cb842737c9a2b0409822b1339078cf0d Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 15 Jan 2026 11:59:41 -0500 Subject: [PATCH 4/4] feat(dashboard): show projectId warning in Live Account Monitor Add warning indicator on Antigravity account dots when projectId is missing. Matches the warning shown in Provider Editor for consistency. --- .../auth-monitor/components/provider-card.tsx | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/ui/src/components/monitoring/auth-monitor/components/provider-card.tsx b/ui/src/components/monitoring/auth-monitor/components/provider-card.tsx index d7faf5f9..56faedbc 100644 --- a/ui/src/components/monitoring/auth-monitor/components/provider-card.tsx +++ b/ui/src/components/monitoring/auth-monitor/components/provider-card.tsx @@ -3,10 +3,11 @@ */ import type React from 'react'; -import { ChevronRight } from 'lucide-react'; +import { ChevronRight, AlertTriangle } from 'lucide-react'; 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 { InlineStatsBadge } from './inline-stats-badge'; @@ -102,16 +103,35 @@ export function ProviderCard({
- {/* Account color dots */} -
- {stats.accounts.slice(0, 5).map((acc) => ( -
- ))} + {/* Account color dots with warning for agy accounts missing projectId */} +
+ {stats.accounts.slice(0, 5).map((acc) => { + const isMissingProjectId = stats.provider === 'agy' && !acc.projectId; + return ( +
+
+ {isMissingProjectId && ( + + + + + + + Missing Project ID - re-add account to fix + + + + )} +
+ ); + })} {stats.accounts.length > 5 && ( +{stats.accounts.length - 5}