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.
This commit is contained in:
kaitranntt
2026-01-15 10:49:23 -05:00
parent 3de894784f
commit ed2ce138e4
6 changed files with 77 additions and 4 deletions
+20 -3
View File
@@ -47,6 +47,8 @@ export interface AccountInfo {
pausedAt?: string; pausedAt?: string;
/** Account tier: free or paid (Pro/Ultra combined) */ /** Account tier: free or paid (Pro/Ultra combined) */
tier?: AccountTier; tier?: AccountTier;
/** GCP Project ID (Antigravity only) - read-only, fetched from auth token */
projectId?: string;
} }
/** Provider accounts configuration */ /** Provider accounts configuration */
@@ -293,7 +295,8 @@ export function registerAccount(
provider: CLIProxyProvider, provider: CLIProxyProvider,
tokenFile: string, tokenFile: string,
email?: string, email?: string,
nickname?: string nickname?: string,
projectId?: string
): AccountInfo { ): AccountInfo {
const registry = loadAccountsRegistry(); const registry = loadAccountsRegistry();
@@ -350,7 +353,7 @@ export function registerAccount(
const isFirstAccount = Object.keys(providerAccounts.accounts).length === 0; const isFirstAccount = Object.keys(providerAccounts.accounts).length === 0;
// Create or update account // Create or update account
providerAccounts.accounts[accountId] = { const accountMeta: Omit<AccountInfo, 'id' | 'provider' | 'isDefault'> = {
email, email,
nickname: accountNickname, nickname: accountNickname,
tokenFile, tokenFile,
@@ -358,6 +361,13 @@ export function registerAccount(
lastUsedAt: new Date().toISOString(), 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 // Set as default if first account
if (isFirstAccount) { if (isFirstAccount) {
providerAccounts.default = accountId; providerAccounts.default = accountId;
@@ -671,13 +681,20 @@ export function discoverExistingAccounts(): void {
// Register account with auto-generated nickname // Register account with auto-generated nickname
// Use mtime as lastUsedAt (when token was last modified = last auth/refresh) // Use mtime as lastUsedAt (when token was last modified = last auth/refresh)
const lastModified = stats.mtime || stats.birthtime || new Date(); const lastModified = stats.mtime || stats.birthtime || new Date();
providerAccounts.accounts[accountId] = { const accountMeta: Omit<AccountInfo, 'id' | 'provider' | 'isDefault'> = {
email, email,
nickname: generateNickname(email), nickname: generateNickname(email),
tokenFile: file, tokenFile: file,
createdAt: stats.birthtime?.toISOString() || new Date().toISOString(), createdAt: stats.birthtime?.toISOString() || new Date().toISOString(),
lastUsedAt: lastModified.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 { } catch {
// Skip invalid files // Skip invalid files
continue; continue;
+8 -1
View File
@@ -229,8 +229,15 @@ export function registerAccountFromToken(
const content = fs.readFileSync(tokenPath, 'utf-8'); const content = fs.readFileSync(tokenPath, 'utf-8');
const data = JSON.parse(content); const data = JSON.parse(content);
const email = data.email || undefined; 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 { } catch {
return null; return null;
} }
@@ -25,6 +25,8 @@ import {
Pause, Pause,
Play, Play,
AlertCircle, AlertCircle,
AlertTriangle,
FolderCode,
} from 'lucide-react'; } from 'lucide-react';
import { import {
cn, cn,
@@ -167,6 +169,48 @@ export function AccountItem({
</Badge> </Badge>
)} )}
</div> </div>
{/* Project ID for Antigravity accounts - read-only */}
{account.provider === 'agy' && (
<div className="flex items-center gap-1.5 mt-1">
{account.projectId ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<FolderCode className="w-3 h-3" />
<span className={cn('font-mono', privacyMode && PRIVACY_BLUR_CLASS)}>
{account.projectId}
</span>
</div>
</TooltipTrigger>
<TooltipContent side="bottom">
<p className="text-xs">GCP Project ID (read-only)</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1 text-xs text-amber-600 dark:text-amber-500">
<AlertTriangle className="w-3 h-3" />
<span>Project ID: N/A</span>
</div>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-[250px]">
<div className="text-xs space-y-1">
<p className="font-medium text-amber-600">Missing Project ID</p>
<p>
This may cause errors. Remove the account and re-add it to fetch the
project ID.
</p>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
)}
{account.lastUsedAt && ( {account.lastUsedAt && (
<div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5"> <div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5">
<Clock className="w-3 h-3" /> <Clock className="w-3 h-3" />
@@ -99,6 +99,7 @@ export function useAuthMonitorData(): AuthMonitorData {
failureCount: failure, failureCount: failure,
lastUsedAt: realStats?.lastUsedAt ?? account.lastUsedAt, lastUsedAt: realStats?.lastUsedAt ?? account.lastUsedAt,
color: ACCOUNT_COLORS[colorIndex % ACCOUNT_COLORS.length], color: ACCOUNT_COLORS[colorIndex % ACCOUNT_COLORS.length],
projectId: account.projectId,
}; };
accountsList.push(row); accountsList.push(row);
providerData.accounts.push(row); providerData.accounts.push(row);
@@ -12,6 +12,8 @@ export interface AccountRow {
failureCount: number; failureCount: number;
lastUsedAt?: string; lastUsedAt?: string;
color: string; color: string;
/** GCP Project ID (Antigravity only) - read-only */
projectId?: string;
} }
export interface ProviderStats { export interface ProviderStats {
+2
View File
@@ -83,6 +83,8 @@ export interface OAuthAccount {
pausedAt?: string; pausedAt?: string;
/** Account tier: free or paid (Pro/Ultra combined) */ /** Account tier: free or paid (Pro/Ultra combined) */
tier?: 'free' | 'paid' | 'unknown'; tier?: 'free' | 'paid' | 'unknown';
/** GCP Project ID (Antigravity only) - read-only */
projectId?: string;
} }
export interface AuthStatus { export interface AuthStatus {