diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx
index 85c3e0f305..f1e87c0f15 100644
--- a/packages/cli/src/test-utils/render.tsx
+++ b/packages/cli/src/test-utils/render.tsx
@@ -40,6 +40,7 @@ vi.mock('../utils/persistentState.js', () => ({
vi.mock('../ui/utils/terminalUtils.js', () => ({
isLowColorDepth: vi.fn(() => false),
getColorDepth: vi.fn(() => 24),
+ isITerm2: vi.fn(() => false),
}));
// Wrapper around ink-testing-library's render that ensures act() is called
diff --git a/packages/cli/src/ui/components/shared/HalfLinePaddedBox.test.tsx b/packages/cli/src/ui/components/shared/HalfLinePaddedBox.test.tsx
new file mode 100644
index 0000000000..2f4b51966e
--- /dev/null
+++ b/packages/cli/src/ui/components/shared/HalfLinePaddedBox.test.tsx
@@ -0,0 +1,64 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { renderWithProviders } from '../../../test-utils/render.js';
+import { HalfLinePaddedBox } from './HalfLinePaddedBox.js';
+import { Text } from 'ink';
+import { describe, it, expect, vi, afterEach } from 'vitest';
+import { isITerm2 } from '../../utils/terminalUtils.js';
+
+describe('', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it('renders standard background and blocks when not iTerm2', async () => {
+ vi.mocked(isITerm2).mockReturnValue(false);
+
+ const { lastFrame, unmount } = renderWithProviders(
+
+ Content
+ ,
+ { width: 10 },
+ );
+
+ expect(lastFrame()).toMatchSnapshot();
+
+ unmount();
+ });
+
+ it('renders iTerm2-specific blocks when iTerm2 is detected', async () => {
+ vi.mocked(isITerm2).mockReturnValue(true);
+
+ const { lastFrame, unmount } = renderWithProviders(
+
+ Content
+ ,
+ { width: 10 },
+ );
+
+ expect(lastFrame()).toMatchSnapshot();
+
+ unmount();
+ });
+
+ it('renders nothing when useBackgroundColor is false', async () => {
+ const { lastFrame, unmount } = renderWithProviders(
+
+ Content
+ ,
+ { width: 10 },
+ );
+
+ expect(lastFrame()).toMatchSnapshot();
+
+ unmount();
+ });
+});
diff --git a/packages/cli/src/ui/components/shared/HalfLinePaddedBox.tsx b/packages/cli/src/ui/components/shared/HalfLinePaddedBox.tsx
index 8c4a089bd0..0978c6d736 100644
--- a/packages/cli/src/ui/components/shared/HalfLinePaddedBox.tsx
+++ b/packages/cli/src/ui/components/shared/HalfLinePaddedBox.tsx
@@ -13,7 +13,7 @@ import {
resolveColor,
getSafeLowColorBackground,
} from '../../themes/color-utils.js';
-import { isLowColorDepth } from '../../utils/terminalUtils.js';
+import { isLowColorDepth, isITerm2 } from '../../utils/terminalUtils.js';
export interface HalfLinePaddedBoxProps {
/**
@@ -77,6 +77,35 @@ const HalfLinePaddedBoxInternal: React.FC = ({
return <>{children}>;
}
+ const isITerm = isITerm2();
+
+ if (isITerm) {
+ return (
+
+
+ {'▄'.repeat(terminalWidth)}
+
+
+ {children}
+
+
+ {'▀'.repeat(terminalWidth)}
+
+
+ );
+ }
+
return (
> renders iTerm2-specific blocks when iTerm2 is detected 1`] = `
+"▄▄▄▄▄▄▄▄▄▄
+Content
+▀▀▀▀▀▀▀▀▀▀"
+`;
+
+exports[` > renders nothing when useBackgroundColor is false 1`] = `"Content"`;
+
+exports[` > renders standard background and blocks when not iTerm2 1`] = `
+"▀▀▀▀▀▀▀▀▀▀
+Content
+▄▄▄▄▄▄▄▄▄▄"
+`;
diff --git a/packages/cli/src/ui/utils/terminalUtils.test.ts b/packages/cli/src/ui/utils/terminalUtils.test.ts
new file mode 100644
index 0000000000..70b2a08f17
--- /dev/null
+++ b/packages/cli/src/ui/utils/terminalUtils.test.ts
@@ -0,0 +1,48 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import { isITerm2, resetITerm2Cache } from './terminalUtils.js';
+
+describe('terminalUtils', () => {
+ beforeEach(() => {
+ vi.stubEnv('TERM_PROGRAM', '');
+ vi.stubEnv('ITERM_SESSION_ID', '');
+ resetITerm2Cache();
+ });
+
+ afterEach(() => {
+ vi.unstubAllEnvs();
+ vi.restoreAllMocks();
+ });
+
+ it('should detect iTerm2 via TERM_PROGRAM', () => {
+ vi.stubEnv('TERM_PROGRAM', 'iTerm.app');
+ expect(isITerm2()).toBe(true);
+ });
+
+ it('should detect iTerm2 via ITERM_SESSION_ID', () => {
+ vi.stubEnv('ITERM_SESSION_ID', 'w0t0p0:6789...');
+ expect(isITerm2()).toBe(true);
+ });
+
+ it('should return false if not iTerm2', () => {
+ vi.stubEnv('TERM_PROGRAM', 'vscode');
+ expect(isITerm2()).toBe(false);
+ });
+
+ it('should cache the result', () => {
+ vi.stubEnv('TERM_PROGRAM', 'iTerm.app');
+ expect(isITerm2()).toBe(true);
+
+ // Change env but should still be true due to cache
+ vi.stubEnv('TERM_PROGRAM', 'vscode');
+ expect(isITerm2()).toBe(true);
+
+ resetITerm2Cache();
+ expect(isITerm2()).toBe(false);
+ });
+});
diff --git a/packages/cli/src/ui/utils/terminalUtils.ts b/packages/cli/src/ui/utils/terminalUtils.ts
index b1506c2817..5c03198f71 100644
--- a/packages/cli/src/ui/utils/terminalUtils.ts
+++ b/packages/cli/src/ui/utils/terminalUtils.ts
@@ -20,3 +20,28 @@ export function getColorDepth(): number {
export function isLowColorDepth(): boolean {
return getColorDepth() < 24;
}
+
+let cachedIsITerm2: boolean | undefined;
+
+/**
+ * Returns true if the current terminal is iTerm2.
+ */
+export function isITerm2(): boolean {
+ if (cachedIsITerm2 !== undefined) {
+ return cachedIsITerm2;
+ }
+
+ cachedIsITerm2 =
+ process.env['TERM_PROGRAM'] === 'iTerm.app' ||
+ !!process.env['ITERM_SESSION_ID'];
+
+ return cachedIsITerm2;
+}
+
+/**
+ * Resets the cached iTerm2 detection value.
+ * Primarily used for testing.
+ */
+export function resetITerm2Cache(): void {
+ cachedIsITerm2 = undefined;
+}