diff --git a/.github/workflows/eval-guidance.yml b/.github/workflows/eval-guidance.yml
new file mode 100644
index 0000000000..e1f1ab3168
--- /dev/null
+++ b/.github/workflows/eval-guidance.yml
@@ -0,0 +1,69 @@
+name: 'Evals: PR Guidance'
+
+on:
+ pull_request:
+ paths:
+ - 'packages/core/src/**/*.ts'
+ - '!**/*.test.ts'
+ - '!**/*.test.tsx'
+
+permissions:
+ pull-requests: 'write'
+ contents: 'read'
+
+jobs:
+ provide-guidance:
+ name: 'Model Steering Guidance'
+ runs-on: 'ubuntu-latest'
+ if: "github.repository == 'google-gemini/gemini-cli'"
+ steps:
+ - name: 'Checkout'
+ uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: 'Set up Node.js'
+ uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4.4.0
+ with:
+ node-version-file: '.nvmrc'
+ cache: 'npm'
+
+ - name: 'Detect Steering Changes'
+ id: 'detect'
+ run: |
+ STEERING_DETECTED=$(node scripts/changed_prompt.js --steering-only)
+ echo "STEERING_DETECTED=$STEERING_DETECTED" >> "$GITHUB_OUTPUT"
+
+ - name: 'Analyze PR Content'
+ if: "steps.detect.outputs.STEERING_DETECTED == 'true'"
+ id: 'analysis'
+ env:
+ GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
+ run: |
+ # Check for behavioral eval changes
+ EVAL_CHANGES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep "^evals/" || true)
+ if [ -z "$EVAL_CHANGES" ]; then
+ echo "MISSING_EVALS=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ # Check if user is a maintainer (has write/admin access)
+ USER_PERMISSION=$(gh api repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission --jq '.permission')
+ if [[ "$USER_PERMISSION" == "admin" || "$USER_PERMISSION" == "write" ]]; then
+ echo "IS_MAINTAINER=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: 'Post Guidance Comment'
+ if: "steps.detect.outputs.STEERING_DETECTED == 'true'"
+ uses: 'thollander/actions-comment-pull-request@65f9e5c9a1f2cd378bd74b2e057c9736982a8e74' # ratchet:thollander/actions-comment-pull-request@v3
+ with:
+ comment-tag: 'eval-guidance-bot'
+ message: |
+ ### ๐ง Model Steering Guidance
+
+ This PR modifies files that affect the model's behavior (prompts, tools, or instructions).
+
+ ${{ steps.analysis.outputs.MISSING_EVALS == 'true' && '- โ ๏ธ **Consider adding Evals:** No behavioral evaluations (`evals/*.eval.ts`) were added or updated in this PR. Consider adding a test case to verify the new behavior and prevent regressions.' || '' }}
+ ${{ steps.analysis.outputs.IS_MAINTAINER == 'true' && '- ๐ **Maintainer Reminder:** Please ensure that these changes do not regress results on benchmark evals before merging.' || '' }}
+
+ ---
+ *This is an automated guidance message triggered by steering logic signatures.*
diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md
index 9b4945cd68..c784667ef0 100644
--- a/docs/reference/configuration.md
+++ b/docs/reference/configuration.md
@@ -1535,7 +1535,7 @@ their corresponding top-level category object in your `settings.json` file.
- **`experimental.enableAgents`** (boolean):
- **Description:** Enable local and remote subagents.
- - **Default:** `true`
+ - **Default:** `false`
- **Requires restart:** Yes
- **`experimental.worktrees`** (boolean):
diff --git a/integration-tests/browser-policy.test.ts b/integration-tests/browser-policy.test.ts
index f533cb3f5e..bb66b10aab 100644
--- a/integration-tests/browser-policy.test.ts
+++ b/integration-tests/browser-policy.test.ts
@@ -63,6 +63,9 @@ describe.skipIf(!chromeAvailable)('browser-policy', () => {
rig.setup('browser-policy-skip-confirmation', {
fakeResponsesPath: join(__dirname, 'browser-policy.responses'),
settings: {
+ experimental: {
+ enableAgents: true,
+ },
agents: {
overrides: {
browser_agent: {
@@ -180,6 +183,9 @@ priority = 200
rig.setup('browser-session-warning', {
fakeResponsesPath: join(__dirname, 'browser-agent.cleanup.responses'),
settings: {
+ experimental: {
+ enableAgents: true,
+ },
general: {
enableAutoUpdateNotification: false,
},
diff --git a/packages/a2a-server/src/config/config.test.ts b/packages/a2a-server/src/config/config.test.ts
index cfe77311ea..370c859944 100644
--- a/packages/a2a-server/src/config/config.test.ts
+++ b/packages/a2a-server/src/config/config.test.ts
@@ -341,11 +341,11 @@ describe('loadConfig', () => {
);
});
- it('should default enableAgents to true when not provided', async () => {
+ it('should default enableAgents to false when not provided', async () => {
await loadConfig(mockSettings, mockExtensionLoader, taskId);
expect(Config).toHaveBeenCalledWith(
expect.objectContaining({
- enableAgents: true,
+ enableAgents: false,
}),
);
});
diff --git a/packages/a2a-server/src/config/config.ts b/packages/a2a-server/src/config/config.ts
index 9474c4d9c5..97243c88d8 100644
--- a/packages/a2a-server/src/config/config.ts
+++ b/packages/a2a-server/src/config/config.ts
@@ -127,7 +127,7 @@ export async function loadConfig(
interactive: !isHeadlessMode(),
enableInteractiveShell: !isHeadlessMode(),
ptyInfo: 'auto',
- enableAgents: settings.experimental?.enableAgents ?? true,
+ enableAgents: settings.experimental?.enableAgents ?? false,
};
const fileService = new FileDiscoveryService(workspaceDir, {
diff --git a/packages/cli/src/config/settingsSchema.test.ts b/packages/cli/src/config/settingsSchema.test.ts
index c358cd65aa..9b643396ae 100644
--- a/packages/cli/src/config/settingsSchema.test.ts
+++ b/packages/cli/src/config/settingsSchema.test.ts
@@ -400,7 +400,7 @@ describe('SettingsSchema', () => {
expect(setting).toBeDefined();
expect(setting.type).toBe('boolean');
expect(setting.category).toBe('Experimental');
- expect(setting.default).toBe(true);
+ expect(setting.default).toBe(false);
expect(setting.requiresRestart).toBe(true);
expect(setting.showInDialog).toBe(false);
expect(setting.description).toBe('Enable local and remote subagents.');
diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts
index 277dcfdcb9..00ea1b6102 100644
--- a/packages/cli/src/config/settingsSchema.ts
+++ b/packages/cli/src/config/settingsSchema.ts
@@ -1922,7 +1922,7 @@ const SETTINGS_SCHEMA = {
label: 'Enable Agents',
category: 'Experimental',
requiresRestart: true,
- default: true,
+ default: false,
description: 'Enable local and remote subagents.',
showInDialog: false,
},
diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx
index 9f298af0db..4a6d876281 100644
--- a/packages/cli/src/gemini.tsx
+++ b/packages/cli/src/gemini.tsx
@@ -215,12 +215,36 @@ export async function main() {
loadSettingsHandle?.end();
// If a worktree is requested and enabled, set it up early.
+ // This must be awaited before any other async tasks that depend on CWD (like loadCliConfig)
+ // because setupWorktree calls process.chdir().
const requestedWorktree = cliConfig.getRequestedWorktreeName(settings);
let worktreeInfo: WorktreeInfo | undefined;
if (requestedWorktree !== undefined) {
+ const worktreeHandle = startupProfiler.start('setup_worktree');
worktreeInfo = await setupWorktree(requestedWorktree || undefined);
+ worktreeHandle?.end();
}
+ const cleanupOpsHandle = startupProfiler.start('cleanup_ops');
+ Promise.all([
+ cleanupCheckpoints(),
+ cleanupToolOutputFiles(settings.merged),
+ cleanupBackgroundLogs(),
+ ])
+ .catch((e) => {
+ debugLogger.error('Early cleanup failed:', e);
+ })
+ .finally(() => {
+ cleanupOpsHandle?.end();
+ });
+
+ const parseArgsHandle = startupProfiler.start('parse_arguments');
+ const argvPromise = parseArguments(settings.merged).finally(() => {
+ parseArgsHandle?.end();
+ });
+
+ const rawStartupWarningsPromise = getStartupWarnings();
+
// Report settings errors once during startup
settings.errors.forEach((error) => {
coreEvents.emitFeedback('warning', error.message);
@@ -234,15 +258,7 @@ export async function main() {
);
});
- await Promise.all([
- cleanupCheckpoints(),
- cleanupToolOutputFiles(settings.merged),
- cleanupBackgroundLogs(),
- ]);
-
- const parseArgsHandle = startupProfiler.start('parse_arguments');
- const argv = await parseArguments(settings.merged);
- parseArgsHandle?.end();
+ const argv = await argvPromise;
if (
(argv.allowedTools && argv.allowedTools.length > 0) ||
@@ -469,12 +485,10 @@ export async function main() {
await config.getHookSystem()?.fireSessionEndEvent(SessionEndReason.Exit);
});
- // Cleanup sessions after config initialization
- try {
- await cleanupExpiredSessions(config, settings.merged);
- } catch (e) {
+ // Launch cleanup expired sessions as a background task
+ cleanupExpiredSessions(config, settings.merged).catch((e) => {
debugLogger.error('Failed to cleanup expired sessions:', e);
- }
+ });
if (config.getListExtensions()) {
debugLogger.log('Installed extensions:');
@@ -526,7 +540,9 @@ export async function main() {
});
}
+ const terminalHandle = startupProfiler.start('setup_terminal');
await setupTerminalAndTheme(config, settings);
+ terminalHandle?.end();
const initAppHandle = startupProfiler.start('initialize_app');
const initializationResult = await initializeApp(config, settings);
@@ -550,7 +566,7 @@ export async function main() {
isAlternateBufferEnabled(config),
config.getScreenReader(),
);
- const rawStartupWarnings = await getStartupWarnings();
+ const rawStartupWarnings = await rawStartupWarningsPromise;
const startupWarnings: StartupWarning[] = [
...rawStartupWarnings.map((message) => ({
id: `startup-${createHash('sha256').update(message).digest('hex').substring(0, 16)}`,
diff --git a/packages/cli/src/test-utils/AppRig.tsx b/packages/cli/src/test-utils/AppRig.tsx
index dbbe84ca90..712a07c1d5 100644
--- a/packages/cli/src/test-utils/AppRig.tsx
+++ b/packages/cli/src/test-utils/AppRig.tsx
@@ -11,7 +11,11 @@ import os from 'node:os';
import path from 'node:path';
import fs from 'node:fs';
import { AppContainer } from '../ui/AppContainer.js';
-import { renderWithProviders, type RenderInstance } from './render.js';
+import {
+ renderWithProviders,
+ type RenderInstance,
+ persistentStateMock,
+} from './render.js';
import {
makeFakeConfig,
type Config,
@@ -194,6 +198,11 @@ export class AppRig {
}
async initialize() {
+ persistentStateMock.setData({
+ terminalSetupPromptShown: true,
+ tipsShown: 10,
+ });
+
this.setupEnvironment();
resetSettingsCacheForTesting();
this.settings = this.createRigSettings();
@@ -240,6 +249,8 @@ export class AppRig {
private setupEnvironment() {
// Stub environment variables to avoid interference from developer's machine
vi.stubEnv('GEMINI_CLI_HOME', this.testDir);
+ vi.stubEnv('TERM_PROGRAM', 'other');
+ vi.stubEnv('VSCODE_GIT_IPC_HANDLE', '');
if (this.options.fakeResponsesPath) {
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
MockShellExecutionService.setPassthrough(false);
@@ -305,7 +316,6 @@ export class AppRig {
const newContentGeneratorConfig = {
authType: authMethod,
-
proxy: gcConfig.getProxy(),
apiKey: process.env['GEMINI_API_KEY'] || 'test-api-key',
};
diff --git a/packages/cli/src/ui/__snapshots__/App.test.tsx.snap b/packages/cli/src/ui/__snapshots__/App.test.tsx.snap
index 9e1d66df01..1dec76271a 100644
--- a/packages/cli/src/ui/__snapshots__/App.test.tsx.snap
+++ b/packages/cli/src/ui/__snapshots__/App.test.tsx.snap
@@ -2,10 +2,13 @@
exports[`App > Snapshots > renders default layout correctly 1`] = `
"
- โโโ Gemini CLI v1.2.3
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v1.2.3
+
Tips for getting started:
@@ -31,9 +34,6 @@ Tips for getting started:
-
-
-
@@ -47,10 +47,13 @@ exports[`App > Snapshots > renders screen reader layout correctly 1`] = `
"Notifications
Footer
- โโโ Gemini CLI v1.2.3
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v1.2.3
+
Tips for getting started:
@@ -64,12 +67,12 @@ Composer
exports[`App > Snapshots > renders with dialogs visible 1`] = `
"
- โโโ Gemini CLI v1.2.3
- โโโ
- โโโ
- โโ
-
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+ Gemini CLI v1.2.3
@@ -107,10 +110,13 @@ DialogManager
exports[`App > should render ToolConfirmationQueue along with Composer when tool is confirming and experiment is on 1`] = `
"
- โโโ Gemini CLI v1.2.3
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v1.2.3
+
Tips for getting started:
@@ -140,9 +146,6 @@ HistoryItemDisplay
-
-
-
Notifications
Composer
"
diff --git a/packages/cli/src/ui/components/AppHeader.test.tsx b/packages/cli/src/ui/components/AppHeader.test.tsx
index 8ff4caaacf..5fba1b1ce5 100644
--- a/packages/cli/src/ui/components/AppHeader.test.tsx
+++ b/packages/cli/src/ui/components/AppHeader.test.tsx
@@ -10,6 +10,7 @@ import {
} from '../../test-utils/render.js';
import { AppHeader } from './AppHeader.js';
import { describe, it, expect, vi } from 'vitest';
+import { makeFakeConfig } from '@google/gemini-cli-core';
import crypto from 'node:crypto';
vi.mock('../utils/terminalSetup.js', () => ({
@@ -240,4 +241,27 @@ describe('', () => {
expect(session2.lastFrame()).not.toContain('Tips');
session2.unmount();
});
+
+ it('should render the full logo when logged out', async () => {
+ const mockConfig = makeFakeConfig();
+ vi.spyOn(mockConfig, 'getContentGeneratorConfig').mockReturnValue({
+ authType: undefined,
+ } as any); // eslint-disable-line @typescript-eslint/no-explicit-any
+
+ const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ ,
+ {
+ config: mockConfig,
+ uiState: {
+ terminalWidth: 120,
+ },
+ },
+ );
+ await waitUntilReady();
+
+ // Check for block characters from the logo
+ expect(lastFrame()).toContain('โโโโโโ');
+ expect(lastFrame()).toMatchSnapshot();
+ unmount();
+ });
});
diff --git a/packages/cli/src/ui/components/AppHeader.tsx b/packages/cli/src/ui/components/AppHeader.tsx
index 0b15f917a6..704b094663 100644
--- a/packages/cli/src/ui/components/AppHeader.tsx
+++ b/packages/cli/src/ui/components/AppHeader.tsx
@@ -19,6 +19,9 @@ import { CliSpinner } from './CliSpinner.js';
import { isAppleTerminal } from '@google/gemini-cli-core';
+import { longAsciiLogoCompactText } from './AsciiArt.js';
+import { getAsciiArtWidth } from '../utils/textUtils.js';
+
interface AppHeaderProps {
version: string;
showDetails?: boolean;
@@ -41,6 +44,18 @@ const MAC_TERMINAL_ICON = `โโโ
โโโ
โโโ `;
+/**
+ * The horizontal padding (in columns) required for metadata (version, identity, etc.)
+ * when rendered alongside the ASCII logo.
+ */
+const LOGO_METADATA_PADDING = 20;
+
+/**
+ * The terminal width below which we switch to a narrow/column layout to prevent
+ * UI elements from wrapping or overlapping.
+ */
+const NARROW_TERMINAL_BREAKPOINT = 60;
+
export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
const settings = useSettings();
const config = useConfig();
@@ -49,70 +64,90 @@ export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
const { bannerText } = useBanner(bannerData);
const { showTips } = useTips();
+ const authType = config.getContentGeneratorConfig()?.authType;
+ const loggedOut = !authType;
+
const showHeader = !(
settings.merged.ui.hideBanner || config.getScreenReader()
);
const ICON = isAppleTerminal() ? MAC_TERMINAL_ICON : DEFAULT_ICON;
- if (!showDetails) {
- return (
-
- {showHeader && (
-
-
- {ICON}
-
-
-
-
- Gemini CLI
-
- v{version}
-
-
+ let logoTextArt = '';
+ if (loggedOut) {
+ const widthOfLongLogo =
+ getAsciiArtWidth(longAsciiLogoCompactText) + LOGO_METADATA_PADDING;
+
+ if (terminalWidth >= widthOfLongLogo) {
+ logoTextArt = longAsciiLogoCompactText.trim();
+ }
+ }
+
+ // If the terminal is too narrow to fit the icon and metadata (especially long nightly versions)
+ // side-by-side, we switch to column mode to prevent wrapping.
+ const isNarrow = terminalWidth < NARROW_TERMINAL_BREAKPOINT;
+
+ const renderLogo = () => (
+
+
+ {ICON}
+
+ {logoTextArt && (
+
+ {logoTextArt}
+
+ )}
+
+ );
+
+ const renderMetadata = (isBelow = false) => (
+
+ {/* Line 1: Gemini CLI vVersion [Updating] */}
+
+
+ Gemini CLI
+
+ v{version}
+ {updateInfo && (
+
+
+ Updating
+
)}
- );
- }
+
+ {showDetails && (
+ <>
+ {/* Line 2: Blank */}
+
+
+ {/* Lines 3 & 4: User Identity info (Email /auth and Plan /upgrade) */}
+ {settings.merged.ui.showUserIdentity !== false && (
+
+ )}
+ >
+ )}
+
+ );
+
+ const useColumnLayout = !!logoTextArt || isNarrow;
return (
{showHeader && (
-
-
- {ICON}
-
-
- {/* Line 1: Gemini CLI vVersion [Updating] */}
-
-
- Gemini CLI
-
- v{version}
- {updateInfo && (
-
-
- Updating
-
-
- )}
-
-
- {/* Line 2: Blank */}
-
-
- {/* Lines 3 & 4: User Identity info (Email /auth and Plan /upgrade) */}
- {settings.merged.ui.showUserIdentity !== false && (
-
- )}
-
+
+ {renderLogo()}
+ {useColumnLayout ? (
+ {renderMetadata(true)}
+ ) : (
+ renderMetadata(false)
+ )}
)}
diff --git a/packages/cli/src/ui/components/AsciiArt.ts b/packages/cli/src/ui/components/AsciiArt.ts
index 79eb522c80..40f0eb8296 100644
--- a/packages/cli/src/ui/components/AsciiArt.ts
+++ b/packages/cli/src/ui/components/AsciiArt.ts
@@ -16,14 +16,14 @@ export const shortAsciiLogo = `
`;
export const longAsciiLogo = `
- โโโ โโโโโโโโโ โโโโโโโโโโ โโโโโโ โโโโโโ โโโโโ โโโโโโ โโโโโ โโโโโ
-โโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโ โโโโโ โโโโโโโโ โโโโโ โโโโโ
- โโโโโโ โโโ โโโ โโโโ โ โ โโโโโโโโโโโโโโ โโโโ โโโโโโโโ โโโโ โโโโ
- โโโโโโ โโโโ โโโโโโโ โโโโโโโโโ โโโโ โโโโ โโโโโโโโโโโโโ โโโโ
- โโโโ โโโโ โโโโโ โโโโโโโ โโโโ โโโ โโโโ โโโโ โโโโ โโโโโโโโ โโโโ
- โโโโ โโโโโ โโโโโ โโโโ โ โ โโโโ โโโโ โโโโ โโโโ โโโโโโโ โโโโ
- โโโโ โโโโโโโโโโโ โโโโโโโโโโ โโโโโ โโโโโ โโโโโ โโโโโ โโโโโโโ โโโโโ
-โโโ โโโโโโโโโ โโโโโโโโโโ โโโโโ โโโโโ โโโโโ โโโโโ โโโโโ โโโโโ
+ โโโโโโโโโ โโโโโโโโโโ โโโโโโ โโโโโโ โโโโโ โโโโโโ โโโโโ โโโโโ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโ โโโโโโโโโโโโโ โโโโโ โโโโโ
+โโโ โโโโโโโ โโโโ โ โ โโโโโโโโโโโโโโ โโโโ โโโโโโโโ โโโโ โโโโ
+โโโโ โโโโโโโ โโโโโโโโโ โโโโ โโโโ โโโโโโโโโโโโโ โโโโ
+โโโโ โโโโโ โโโโโโโ โโโโ โโโ โโโโ โโโโ โโโโ โโโโโโโโ โโโโ
+โโโโโ โโโโโ โโโโ โ โ โโโโ โโโโ โโโโ โโโโ โโโโโโโ โโโโ
+ โโโโโโโโโโโ โโโโโโโโโโ โโโโโ โโโโโ โโโโโ โโโโโ โโโโโโ โโโโโ
+ โโโโโโโโโ โโโโโโโโโโ โโโโโ โโโโโ โโโโโ โโโโโ โโโโ โโโโโ
`;
export const tinyAsciiLogo = `
@@ -36,3 +36,24 @@ export const tinyAsciiLogo = `
โโโโ โโโโโโโโโโโ
โโโ โโโโโโโโโ
`;
+
+export const shortAsciiLogoCompactText = `
+โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโ
+โโ โโโโ โโโโโโโ โโ โโโโโโ โโ
+โโโ โโโโโ โโ โโ โโ โโ โโ โโโ โโ
+ โโโโโ โโโโโโโโ โโโโโโโโโ โโโโโโ
+`;
+
+export const longAsciiLogoCompactText = `
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+`;
+
+export const tinyAsciiLogoCompactText = `
+โโโโโโ
+โโ
+โโโ โโโ
+ โโโโโ
+`;
diff --git a/packages/cli/src/ui/components/AskUserDialog.test.tsx b/packages/cli/src/ui/components/AskUserDialog.test.tsx
index 864800a061..3710068285 100644
--- a/packages/cli/src/ui/components/AskUserDialog.test.tsx
+++ b/packages/cli/src/ui/components/AskUserDialog.test.tsx
@@ -1453,4 +1453,42 @@ describe('AskUserDialog', () => {
});
});
});
+
+ it('shows at least 3 selection options even in small terminal heights', async () => {
+ const questions: Question[] = [
+ {
+ question:
+ 'A very long question that would normally take up most of the space and squeeze the list if we did not have a heuristic to prevent it. This line is just to make it longer. And another one. Imagine this is a plan.',
+ header: 'Test',
+ type: QuestionType.CHOICE,
+ options: [
+ { label: 'Option 1', description: 'Description 1' },
+ { label: 'Option 2', description: 'Description 2' },
+ { label: 'Option 3', description: 'Description 3' },
+ { label: 'Option 4', description: 'Description 4' },
+ ],
+ multiSelect: false,
+ },
+ ];
+
+ const { lastFrame, waitUntilReady } = await renderWithProviders(
+ ,
+ { width: 80 },
+ );
+
+ await waitFor(async () => {
+ await waitUntilReady();
+ const frame = lastFrame();
+ // Should show at least 3 options
+ expect(frame).toContain('1. Option 1');
+ expect(frame).toContain('2. Option 2');
+ expect(frame).toContain('3. Option 3');
+ });
+ });
});
diff --git a/packages/cli/src/ui/components/AskUserDialog.tsx b/packages/cli/src/ui/components/AskUserDialog.tsx
index b1d23885e6..57faaae87c 100644
--- a/packages/cli/src/ui/components/AskUserDialog.tsx
+++ b/packages/cli/src/ui/components/AskUserDialog.tsx
@@ -849,11 +849,19 @@ const ChoiceQuestionView: React.FC = ({
? Math.max(1, availableHeight - overhead)
: undefined;
+ // Reserve space for at least 3 items if more selectionItems available.
+ const reservedListHeight = Math.min(selectionItems.length * 2, 6);
const questionHeightLimit =
listHeight && !isAlternateBuffer
? question.unconstrainedHeight
? Math.max(1, listHeight - selectionItems.length * 2)
- : Math.min(15, Math.max(1, listHeight - DIALOG_PADDING))
+ : Math.min(
+ 15,
+ Math.max(
+ 1,
+ listHeight - Math.max(DIALOG_PADDING, reservedListHeight),
+ ),
+ )
: undefined;
const maxItemsToShow =
diff --git a/packages/cli/src/ui/components/GradientRegression.test.tsx b/packages/cli/src/ui/components/GradientRegression.test.tsx
index dfdad4f1aa..75ecac6f9a 100644
--- a/packages/cli/src/ui/components/GradientRegression.test.tsx
+++ b/packages/cli/src/ui/components/GradientRegression.test.tsx
@@ -10,7 +10,7 @@ import * as SessionContext from '../contexts/SessionContext.js';
import { type SessionStatsState } from '../contexts/SessionContext.js';
import { Banner } from './Banner.js';
import { Footer } from './Footer.js';
-import { Header } from './Header.js';
+import { AppHeader } from './AppHeader.js';
import { ModelDialog } from './ModelDialog.js';
import { StatsDisplay } from './StatsDisplay.js';
@@ -71,9 +71,9 @@ useSessionStatsMock.mockReturnValue({
});
describe('Gradient Crash Regression Tests', () => {
- it(' should not crash when theme.ui.gradient is empty', async () => {
+ it(' should not crash when theme.ui.gradient is empty', async () => {
const { lastFrame, unmount } = await renderWithProviders(
- ,
+ ,
{
width: 120,
},
diff --git a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
index 5394ab83c0..d4dc67bbc6 100644
--- a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
@@ -2,10 +2,13 @@
exports[`AlternateBufferQuittingDisplay > renders with a tool awaiting confirmation > with_confirming_tool 1`] = `
"
- โโโ Gemini CLI v0.10.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v0.10.0
+
Tips for getting started:
@@ -22,10 +25,13 @@ Action Required (was prompted):
exports[`AlternateBufferQuittingDisplay > renders with active and pending tool messages > with_history_and_pending 1`] = `
"
- โโโ Gemini CLI v0.10.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v0.10.0
+
Tips for getting started:
@@ -50,10 +56,13 @@ Tips for getting started:
exports[`AlternateBufferQuittingDisplay > renders with empty history and no pending items > empty 1`] = `
"
- โโโ Gemini CLI v0.10.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v0.10.0
+
Tips for getting started:
@@ -66,10 +75,13 @@ Tips for getting started:
exports[`AlternateBufferQuittingDisplay > renders with history but no pending items > with_history_no_pending 1`] = `
"
- โโโ Gemini CLI v0.10.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v0.10.0
+
Tips for getting started:
@@ -90,10 +102,13 @@ Tips for getting started:
exports[`AlternateBufferQuittingDisplay > renders with pending items but no history > with_pending_no_history 1`] = `
"
- โโโ Gemini CLI v0.10.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v0.10.0
+
Tips for getting started:
@@ -110,10 +125,13 @@ Tips for getting started:
exports[`AlternateBufferQuittingDisplay > renders with user and gemini messages > with_user_gemini_messages 1`] = `
"
- โโโ Gemini CLI v0.10.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v0.10.0
+
Tips for getting started:
diff --git a/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap
index 4411f766de..ee9ea5f708 100644
--- a/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap
@@ -2,10 +2,13 @@
exports[` > should not render the banner when no flags are set 1`] = `
"
- โโโ Gemini CLI v1.0.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v1.0.0
+
Tips for getting started:
@@ -18,10 +21,13 @@ Tips for getting started:
exports[` > should not render the default banner if shown count is 5 or more 1`] = `
"
- โโโ Gemini CLI v1.0.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v1.0.0
+
Tips for getting started:
@@ -34,10 +40,13 @@ Tips for getting started:
exports[` > should render the banner with default text 1`] = `
"
- โโโ Gemini CLI v1.0.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v1.0.0
+
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ This is the default banner โ
@@ -53,10 +62,13 @@ Tips for getting started:
exports[` > should render the banner with warning text 1`] = `
"
- โโโ Gemini CLI v1.0.0
- โโโ
- โโโ
- โโ
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v1.0.0
+
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ There are capacity issues โ
@@ -69,3 +81,14 @@ Tips for getting started:
4. Be specific for the best results
"
`;
+
+exports[` > should render the full logo when logged out 1`] = `
+"
+ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โโโ โโ โโโ โโโโโโโ โโ โโโโโโโ โโ
+ โโโ โโ โโโ โโโ โโโ โโ โโ โโ โโโโ โโ
+ โโ โโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ
+
+ Gemini CLI v1.0.0
+"
+`;
diff --git a/packages/cli/src/ui/components/__snapshots__/AppHeaderIcon-AppHeader-Icon-Rendering-renders-the-default-icon-in-standard-terminals.snap.svg b/packages/cli/src/ui/components/__snapshots__/AppHeaderIcon-AppHeader-Icon-Rendering-renders-the-default-icon-in-standard-terminals.snap.svg
index 4e9d0e67a5..5c4c6426b7 100644
--- a/packages/cli/src/ui/components/__snapshots__/AppHeaderIcon-AppHeader-Icon-Rendering-renders-the-default-icon-in-standard-terminals.snap.svg
+++ b/packages/cli/src/ui/components/__snapshots__/AppHeaderIcon-AppHeader-Icon-Rendering-renders-the-default-icon-in-standard-terminals.snap.svg
@@ -1,30 +1,34 @@
-