diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx
index 507837be87..8f254cace7 100644
--- a/packages/cli/src/ui/AppContainer.tsx
+++ b/packages/cli/src/ui/AppContainer.tsx
@@ -189,6 +189,10 @@ export const AppContainer = (props: AppContainerProps) => {
const historyManager = useHistory({
chatRecordingService: config.getGeminiClient()?.getChatRecordingService(),
});
+<<<<<<< HEAD
+=======
+
+>>>>>>> d63c34b6e (feat(ui): move user identity display to header (#18216))
useMemoryMonitor(historyManager);
const settings = useSettings();
const isAlternateBuffer = useAlternateBuffer();
diff --git a/packages/cli/src/ui/components/AppHeader.tsx b/packages/cli/src/ui/components/AppHeader.tsx
index 77042c6e3a..01eac44496 100644
--- a/packages/cli/src/ui/components/AppHeader.tsx
+++ b/packages/cli/src/ui/components/AppHeader.tsx
@@ -7,6 +7,7 @@
import { Box } from 'ink';
import { Header } from './Header.js';
import { Tips } from './Tips.js';
+import { UserIdentity } from './UserIdentity.js';
import { useSettings } from '../contexts/SettingsContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
@@ -40,6 +41,9 @@ export const AppHeader = ({ version }: AppHeaderProps) => {
)}
>
)}
+ {settings.merged.ui.showUserIdentity !== false && (
+
+ )}
{!(settings.merged.ui.hideTips || config.getScreenReader()) &&
showTips && }
diff --git a/packages/cli/src/ui/components/UserIdentity.test.tsx b/packages/cli/src/ui/components/UserIdentity.test.tsx
new file mode 100644
index 0000000000..dcc37c5563
--- /dev/null
+++ b/packages/cli/src/ui/components/UserIdentity.test.tsx
@@ -0,0 +1,139 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { renderWithProviders } from '../../test-utils/render.js';
+import { UserIdentity } from './UserIdentity.js';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import {
+ makeFakeConfig,
+ AuthType,
+ UserAccountManager,
+ type ContentGeneratorConfig,
+} from '@google/gemini-cli-core';
+
+// Mock UserAccountManager to control cached account
+vi.mock('@google/gemini-cli-core', async (importOriginal) => {
+ const original =
+ await importOriginal();
+ return {
+ ...original,
+ UserAccountManager: vi.fn().mockImplementation(() => ({
+ getCachedGoogleAccount: () => 'test@example.com',
+ })),
+ };
+});
+
+describe('', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should render login message and auth indicator', () => {
+ const mockConfig = makeFakeConfig();
+ vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue({
+ authType: AuthType.LOGIN_WITH_GOOGLE,
+ model: 'gemini-pro',
+ } as unknown as ContentGeneratorConfig);
+ vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue(undefined);
+
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('Logged in with Google: test@example.com');
+ expect(output).toContain('/auth');
+ unmount();
+ });
+
+ it('should render login message without colon if email is missing', () => {
+ // Modify the mock for this specific test
+ vi.mocked(UserAccountManager).mockImplementationOnce(
+ () =>
+ ({
+ getCachedGoogleAccount: () => undefined,
+ }) as unknown as UserAccountManager,
+ );
+
+ const mockConfig = makeFakeConfig();
+ vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue({
+ authType: AuthType.LOGIN_WITH_GOOGLE,
+ model: 'gemini-pro',
+ } as unknown as ContentGeneratorConfig);
+ vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue(undefined);
+
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('Logged in with Google');
+ expect(output).not.toContain('Logged in with Google:');
+ expect(output).toContain('/auth');
+ unmount();
+ });
+
+ it('should render plan name on a separate line if provided', () => {
+ const mockConfig = makeFakeConfig();
+ vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue({
+ authType: AuthType.LOGIN_WITH_GOOGLE,
+ model: 'gemini-pro',
+ } as unknown as ContentGeneratorConfig);
+ vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue('Premium Plan');
+
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('Logged in with Google: test@example.com');
+ expect(output).toContain('/auth');
+ expect(output).toContain('Plan: Premium Plan');
+
+ // Check for two lines (or more if wrapped, but here it should be separate)
+ const lines = output?.split('\n').filter((line) => line.trim().length > 0);
+ expect(lines?.some((line) => line.includes('Logged in with Google'))).toBe(
+ true,
+ );
+ expect(lines?.some((line) => line.includes('Plan: Premium Plan'))).toBe(
+ true,
+ );
+
+ unmount();
+ });
+
+ it('should not render if authType is missing', () => {
+ const mockConfig = makeFakeConfig();
+ vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue(
+ {} as unknown as ContentGeneratorConfig,
+ );
+
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ );
+
+ expect(lastFrame()).toBe('');
+ unmount();
+ });
+
+ it('should render non-Google auth message', () => {
+ const mockConfig = makeFakeConfig();
+ vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue({
+ authType: AuthType.USE_GEMINI,
+ model: 'gemini-pro',
+ } as unknown as ContentGeneratorConfig);
+ vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue(undefined);
+
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain(`Authenticated with ${AuthType.USE_GEMINI}`);
+ expect(output).toContain('/auth');
+ unmount();
+ });
+});
diff --git a/packages/cli/src/ui/components/UserIdentity.tsx b/packages/cli/src/ui/components/UserIdentity.tsx
new file mode 100644
index 0000000000..ba7473723f
--- /dev/null
+++ b/packages/cli/src/ui/components/UserIdentity.tsx
@@ -0,0 +1,61 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type React from 'react';
+import { useMemo } from 'react';
+import { Box, Text } from 'ink';
+import { theme } from '../semantic-colors.js';
+import {
+ type Config,
+ UserAccountManager,
+ AuthType,
+} from '@google/gemini-cli-core';
+
+interface UserIdentityProps {
+ config: Config;
+}
+
+export const UserIdentity: React.FC = ({ config }) => {
+ const authType = config.getContentGeneratorConfig()?.authType;
+
+ const { email, tierName } = useMemo(() => {
+ if (!authType) {
+ return { email: undefined, tierName: undefined };
+ }
+ const userAccountManager = new UserAccountManager();
+ return {
+ email: userAccountManager.getCachedGoogleAccount(),
+ tierName: config.getUserTierName(),
+ };
+ }, [config, authType]);
+
+ if (!authType) {
+ return null;
+ }
+
+ return (
+
+
+
+ {authType === AuthType.LOGIN_WITH_GOOGLE ? (
+
+ Logged in with Google{email ? ':' : ''}
+ {email ? ` ${email}` : ''}
+
+ ) : (
+ `Authenticated with ${authType}`
+ )}
+
+ /auth
+
+ {tierName && (
+
+ Plan: {tierName}
+
+ )}
+
+ );
+};
diff --git a/packages/core/src/code_assist/setup.ts b/packages/core/src/code_assist/setup.ts
index dcd0210de7..0f16f422c0 100644
--- a/packages/core/src/code_assist/setup.ts
+++ b/packages/core/src/code_assist/setup.ts
@@ -132,8 +132,8 @@ export async function setupUser(
if (projectId) {
return {
projectId,
- userTier: loadRes.currentTier.id,
- userTierName: loadRes.currentTier.name,
+ userTier: loadRes.paidTier?.id ?? loadRes.currentTier.id,
+ userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
};
}
@@ -142,8 +142,8 @@ export async function setupUser(
}
return {
projectId: loadRes.cloudaicompanionProject,
- userTier: loadRes.currentTier.id,
- userTierName: loadRes.currentTier.name,
+ userTier: loadRes.paidTier?.id ?? loadRes.currentTier.id,
+ userTierName: loadRes.paidTier?.name ?? loadRes.currentTier.name,
};
}
diff --git a/packages/core/src/code_assist/types.ts b/packages/core/src/code_assist/types.ts
index 5e706cc207..74e11f78af 100644
--- a/packages/core/src/code_assist/types.ts
+++ b/packages/core/src/code_assist/types.ts
@@ -53,6 +53,7 @@ export interface LoadCodeAssistResponse {
allowedTiers?: GeminiUserTier[] | null;
ineligibleTiers?: IneligibleTier[] | null;
cloudaicompanionProject?: string | null;
+ paidTier?: GeminiUserTier | null;
}
/**
@@ -109,13 +110,17 @@ export enum IneligibleTierReasonCode {
/**
* UserTierId represents IDs returned from the Cloud Code Private API representing a user's tier
*
- * //depot/google3/cloud/developer_experience/cloudcode/pa/service/usertier.go;l=16
+ * http://google3/cloud/developer_experience/codeassist/shared/usertier/tiers.go
+ * This is a subset of all available tiers. Since the source list is frequently updated,
+ * only add a tierId here if specific client-side handling is required.
*/
-export enum UserTierId {
- FREE = 'free-tier',
- LEGACY = 'legacy-tier',
- STANDARD = 'standard-tier',
-}
+export const UserTierId = {
+ FREE: 'free-tier',
+ LEGACY: 'legacy-tier',
+ STANDARD: 'standard-tier',
+} as const;
+
+export type UserTierId = (typeof UserTierId)[keyof typeof UserTierId] | string;
/**
* PrivacyNotice reflects the structure received from the CodeAssist in regards to a tier