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;
/** 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<AccountInfo, 'id' | 'provider' | 'isDefault'> = {
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<AccountInfo, 'id' | 'provider' | 'isDefault'> = {
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;
+8 -1
View File
@@ -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;
}
@@ -25,6 +25,8 @@ import {
Pause,
Play,
AlertCircle,
AlertTriangle,
FolderCode,
} from 'lucide-react';
import {
cn,
@@ -167,6 +169,48 @@ export function AccountItem({
</Badge>
)}
</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 && (
<div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5">
<Clock className="w-3 h-3" />
@@ -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);
@@ -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 {
+2
View File
@@ -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 {