diff --git a/.github/actions/publish-release/action.yml b/.github/actions/publish-release/action.yml index 70a413f13a..54c404c7c1 100644 --- a/.github/actions/publish-release/action.yml +++ b/.github/actions/publish-release/action.yml @@ -193,7 +193,7 @@ runs: INPUTS_A2A_PACKAGE_NAME: '${{ inputs.a2a-package-name }}' - name: '๐Ÿ“ฆ Prepare bundled CLI for npm release' - if: "inputs.npm-registry-url != 'https://npm.pkg.github.com/'" + if: "inputs.npm-registry-url != 'https://npm.pkg.github.com/' && inputs.npm-tag != 'latest'" working-directory: '${{ inputs.working-directory }}' shell: 'bash' run: | diff --git a/README.md b/README.md index 959b5a9534..2b25865179 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![License](https://img.shields.io/github/license/google-gemini/gemini-cli)](https://github.com/google-gemini/gemini-cli/blob/main/LICENSE) [![View Code Wiki](https://assets.codewiki.google/readme-badge/static.svg)](https://codewiki.google/github.com/google-gemini/gemini-cli?utm_source=badge&utm_medium=github&utm_campaign=github.com/google-gemini/gemini-cli) -![Gemini CLI Screenshot](./docs/assets/gemini-screenshot.png) +![Gemini CLI Screenshot](/docs/assets/gemini-screenshot.png) Gemini CLI is an open-source AI agent that brings the power of Gemini directly into your terminal. It provides lightweight access to Gemini, giving you the diff --git a/docs/assets/theme-ansi-dark.png b/docs/assets/theme-ansi-dark.png new file mode 100644 index 0000000000..10bcbd446e Binary files /dev/null and b/docs/assets/theme-ansi-dark.png differ diff --git a/docs/assets/theme-ansi-light.png b/docs/assets/theme-ansi-light.png index 9766ae7820..8973ef2f99 100644 Binary files a/docs/assets/theme-ansi-light.png and b/docs/assets/theme-ansi-light.png differ diff --git a/docs/assets/theme-ansi.png b/docs/assets/theme-ansi.png deleted file mode 100644 index 5d46dacab8..0000000000 Binary files a/docs/assets/theme-ansi.png and /dev/null differ diff --git a/docs/assets/theme-atom-one-dark.png b/docs/assets/theme-atom-one-dark.png new file mode 100644 index 0000000000..f81ba24812 Binary files /dev/null and b/docs/assets/theme-atom-one-dark.png differ diff --git a/docs/assets/theme-atom-one.png b/docs/assets/theme-atom-one.png deleted file mode 100644 index c2787d6b62..0000000000 Binary files a/docs/assets/theme-atom-one.png and /dev/null differ diff --git a/docs/assets/theme-ayu-dark.png b/docs/assets/theme-ayu-dark.png new file mode 100644 index 0000000000..3f5d01d110 Binary files /dev/null and b/docs/assets/theme-ayu-dark.png differ diff --git a/docs/assets/theme-ayu-light.png b/docs/assets/theme-ayu-light.png index f177465679..a276a13c05 100644 Binary files a/docs/assets/theme-ayu-light.png and b/docs/assets/theme-ayu-light.png differ diff --git a/docs/assets/theme-ayu.png b/docs/assets/theme-ayu.png deleted file mode 100644 index 99391f8271..0000000000 Binary files a/docs/assets/theme-ayu.png and /dev/null differ diff --git a/docs/assets/theme-default-dark.png b/docs/assets/theme-default-dark.png new file mode 100644 index 0000000000..2f3e2d7534 Binary files /dev/null and b/docs/assets/theme-default-dark.png differ diff --git a/docs/assets/theme-default-light.png b/docs/assets/theme-default-light.png index 829d4ed5cc..e454211fdb 100644 Binary files a/docs/assets/theme-default-light.png and b/docs/assets/theme-default-light.png differ diff --git a/docs/assets/theme-default.png b/docs/assets/theme-default.png deleted file mode 100644 index 0b93a33433..0000000000 Binary files a/docs/assets/theme-default.png and /dev/null differ diff --git a/docs/assets/theme-dracula-dark.png b/docs/assets/theme-dracula-dark.png new file mode 100644 index 0000000000..e95183708e Binary files /dev/null and b/docs/assets/theme-dracula-dark.png differ diff --git a/docs/assets/theme-dracula.png b/docs/assets/theme-dracula.png deleted file mode 100644 index 27213fbc5c..0000000000 Binary files a/docs/assets/theme-dracula.png and /dev/null differ diff --git a/docs/assets/theme-github-dark.png b/docs/assets/theme-github-dark.png new file mode 100644 index 0000000000..bcbd78ee29 Binary files /dev/null and b/docs/assets/theme-github-dark.png differ diff --git a/docs/assets/theme-github-light.png b/docs/assets/theme-github-light.png index 3cdc94aa49..35fbec5c8b 100644 Binary files a/docs/assets/theme-github-light.png and b/docs/assets/theme-github-light.png differ diff --git a/docs/assets/theme-github.png b/docs/assets/theme-github.png deleted file mode 100644 index a62961b650..0000000000 Binary files a/docs/assets/theme-github.png and /dev/null differ diff --git a/docs/assets/theme-google-light.png b/docs/assets/theme-google-light.png index 835ebc4bea..04f0aa8e46 100644 Binary files a/docs/assets/theme-google-light.png and b/docs/assets/theme-google-light.png differ diff --git a/docs/assets/theme-holiday-dark.png b/docs/assets/theme-holiday-dark.png new file mode 100644 index 0000000000..70416650d5 Binary files /dev/null and b/docs/assets/theme-holiday-dark.png differ diff --git a/docs/assets/theme-shades-of-purple-dark.png b/docs/assets/theme-shades-of-purple-dark.png new file mode 100644 index 0000000000..c3d2e50538 Binary files /dev/null and b/docs/assets/theme-shades-of-purple-dark.png differ diff --git a/docs/assets/theme-solarized-dark.png b/docs/assets/theme-solarized-dark.png new file mode 100644 index 0000000000..be57349283 Binary files /dev/null and b/docs/assets/theme-solarized-dark.png differ diff --git a/docs/assets/theme-solarized-light.png b/docs/assets/theme-solarized-light.png new file mode 100644 index 0000000000..838a3b6870 Binary files /dev/null and b/docs/assets/theme-solarized-light.png differ diff --git a/docs/assets/theme-xcode-light.png b/docs/assets/theme-xcode-light.png index eb056a5589..26f0a74314 100644 Binary files a/docs/assets/theme-xcode-light.png and b/docs/assets/theme-xcode-light.png differ diff --git a/docs/cli/themes.md b/docs/cli/themes.md index 08564a249a..adfe64d081 100644 --- a/docs/cli/themes.md +++ b/docs/cli/themes.md @@ -16,6 +16,8 @@ using the `/theme` command within Gemini CLI: - `Default` - `Dracula` - `GitHub` + - `Holiday` + - `Shades Of Purple` - `Solarized Dark` - **Light themes:** - `ANSI Light` @@ -185,7 +187,7 @@ untrusted sources. ### Example custom theme -Custom theme example +Custom theme example ### Using your custom theme @@ -212,58 +214,66 @@ identify their source, for example: `shades-of-green (green-extension)`. ### ANSI -ANSI theme +ANSI theme -### Atom OneDark +### Atom One -Atom One theme +Atom One theme ### Ayu -Ayu theme +Ayu theme ### Default -Default theme +Default theme ### Dracula -Dracula theme +Dracula theme ### GitHub -GitHub theme +GitHub theme + +### Holiday + +Holiday theme + +### Shades Of Purple + +Shades Of Purple theme ### Solarized Dark -Solarized Dark theme +Solarized Dark theme ## Light themes ### ANSI Light -ANSI Light theme +ANSI Light theme ### Ayu Light -Ayu Light theme +Ayu Light theme ### Default Light -Default Light theme +Default Light theme ### GitHub Light -GitHub Light theme +GitHub Light theme ### Google Code -Google Code theme +Google Code theme ### Solarized Light -Solarized Light theme +Solarized Light theme ### Xcode -Xcode Light theme +Xcode Light theme diff --git a/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx b/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx index da8b43dd20..b8de6adb0b 100644 --- a/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx +++ b/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx @@ -103,7 +103,7 @@ describe('ApiAuthDialog', () => { it.each([ { - keyName: 'return', + keyName: 'enter', sequence: '\r', expectedCall: onSubmit, args: ['submitted-key'], diff --git a/packages/cli/src/ui/components/ConfigExtensionDialog.tsx b/packages/cli/src/ui/components/ConfigExtensionDialog.tsx index b6fb8ce1b6..7f09d46491 100644 --- a/packages/cli/src/ui/components/ConfigExtensionDialog.tsx +++ b/packages/cli/src/ui/components/ConfigExtensionDialog.tsx @@ -210,7 +210,7 @@ export const ConfigExtensionDialog: React.FC = ({ useKeypress( (key: Key) => { if (state.type === 'ASK_CONFIRMATION') { - if (key.name === 'y' || key.name === 'return') { + if (key.name === 'y' || key.name === 'enter') { state.resolve(true); return true; } @@ -220,7 +220,7 @@ export const ConfigExtensionDialog: React.FC = ({ } } if (state.type === 'DONE' || state.type === 'ERROR') { - if (key.name === 'return' || key.name === 'escape') { + if (key.name === 'enter' || key.name === 'escape') { onClose(); return true; } diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx index e5a1f9e8b6..c6816339f5 100644 --- a/packages/cli/src/ui/components/Footer.tsx +++ b/packages/cli/src/ui/components/Footer.tsx @@ -106,6 +106,7 @@ export interface FooterRowItem { flexGrow?: number; flexShrink?: number; isFocused?: boolean; + alignItems?: 'flex-start' | 'center' | 'flex-end'; } const COLUMN_GAP = 3; @@ -117,10 +118,17 @@ export const FooterRow: React.FC<{ const elements: React.ReactNode[] = []; items.forEach((item, idx) => { - if (idx > 0 && !showLabels) { + if (idx > 0) { elements.push( - - ยท + + {!showLabels && ยท } , ); } @@ -131,6 +139,7 @@ export const FooterRow: React.FC<{ flexDirection="column" flexGrow={item.flexGrow ?? 0} flexShrink={item.flexShrink ?? 1} + alignItems={item.alignItems} backgroundColor={item.isFocused ? theme.background.focus : undefined} > {showLabels && ( @@ -148,12 +157,7 @@ export const FooterRow: React.FC<{ }); return ( - + {elements} ); @@ -441,8 +445,9 @@ export const Footer: React.FC = () => { } } - const rowItems: FooterRowItem[] = columnsToRender.map((col) => { + const rowItems: FooterRowItem[] = columnsToRender.map((col, index) => { const isWorkspace = col.id === 'workspace'; + const isLast = index === columnsToRender.length - 1; // Calculate exact space available for growth to prevent over-estimation truncation const otherItemsWidth = columnsToRender @@ -464,8 +469,10 @@ export const Footer: React.FC = () => { key: col.id, header: col.header, element: col.element(estimatedWidth), - flexGrow: isWorkspace ? 1 : 0, + flexGrow: 0, flexShrink: isWorkspace ? 1 : 0, + alignItems: + isLast && !droppedAny && index > 0 ? 'flex-end' : 'flex-start', }; }); @@ -476,6 +483,7 @@ export const Footer: React.FC = () => { element: โ€ฆ, flexGrow: 0, flexShrink: 0, + alignItems: 'flex-end', }); } diff --git a/packages/cli/src/ui/components/FooterConfigDialog.test.tsx b/packages/cli/src/ui/components/FooterConfigDialog.test.tsx index 3141c3a1d7..9d3688e17a 100644 --- a/packages/cli/src/ui/components/FooterConfigDialog.test.tsx +++ b/packages/cli/src/ui/components/FooterConfigDialog.test.tsx @@ -9,6 +9,7 @@ import { renderWithProviders } from '../../test-utils/render.js'; import { waitFor } from '../../test-utils/async.js'; import { FooterConfigDialog } from './FooterConfigDialog.js'; import { createMockSettings } from '../../test-utils/settings.js'; +import { ALL_ITEMS } from '../../config/footerItems.js'; import { act } from 'react'; describe('', () => { @@ -213,4 +214,60 @@ describe('', () => { expect(bIdxAfter).toBeLessThan(wIdxAfter); }); }); + + it('updates the preview when Show footer labels is toggled off', async () => { + const settings = createMockSettings(); + const renderResult = renderWithProviders( + , + { settings }, + ); + + const { lastFrame, stdin, waitUntilReady } = renderResult; + await waitUntilReady(); + + // By default labels are on + expect(lastFrame()).toContain('workspace (/directory)'); + expect(lastFrame()).toContain('sandbox'); + expect(lastFrame()).toContain('/model'); + + // Move to "Show footer labels" (which is the second to last item) + for (let i = 0; i < ALL_ITEMS.length; i++) { + act(() => { + stdin.write('\u001b[B'); // Down arrow + }); + } + + await waitFor(() => { + expect(lastFrame()).toMatch(/> \[โœ“\] Show footer labels/); + }); + + // Toggle it off + act(() => { + stdin.write('\r'); + }); + + await waitFor(() => { + expect(lastFrame()).toMatch(/> \[ \] Show footer labels/); + // The headers should no longer be in the preview + expect(lastFrame()).not.toContain('workspace (/directory)'); + expect(lastFrame()).not.toContain('/model'); + + // We can't strictly search for "sandbox" because the menu item also says "sandbox". + // Let's assert that the spacer dots are now present in the preview instead. + const previewLine = + lastFrame() + .split('\n') + .find((line) => line.includes('Preview:')) || ''; + const nextLine = + lastFrame().split('\n')[ + lastFrame().split('\n').indexOf(previewLine) + 1 + ] || ''; + expect(nextLine).toContain('ยท'); + expect(nextLine).toContain('~/project/path'); + expect(nextLine).toContain('docker'); + expect(nextLine).toContain('97%'); + }); + + await expect(renderResult).toMatchSvgSnapshot(); + }); }); diff --git a/packages/cli/src/ui/components/FooterConfigDialog.tsx b/packages/cli/src/ui/components/FooterConfigDialog.tsx index cda58574a3..562bbabd81 100644 --- a/packages/cli/src/ui/components/FooterConfigDialog.tsx +++ b/packages/cli/src/ui/components/FooterConfigDialog.tsx @@ -266,7 +266,7 @@ export const FooterConfigDialog: React.FC = ({ key: id, header: ALL_ITEMS.find((i) => i.id === id)?.header ?? id, element: mockData[id], - flexGrow: 1, + flexGrow: 0, isFocused: id === focusKey, })); diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index 785641a556..1cfa2d4215 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -972,7 +972,7 @@ export const InputPrompt: React.FC = ({ if (targetIndex < completion.suggestions.length) { const suggestion = completion.suggestions[targetIndex]; - const isEnterKey = key.name === 'return' && !key.ctrl; + const isEnterKey = key.name === 'enter' && !key.ctrl; if (isEnterKey && shellModeActive) { if (hasUserNavigatedSuggestions.current) { diff --git a/packages/cli/src/ui/components/SessionBrowser.test.tsx b/packages/cli/src/ui/components/SessionBrowser.test.tsx index 2e68cb6898..e97ae310bd 100644 --- a/packages/cli/src/ui/components/SessionBrowser.test.tsx +++ b/packages/cli/src/ui/components/SessionBrowser.test.tsx @@ -324,7 +324,7 @@ describe('SessionBrowser component', () => { await waitUntilReady(); // Press Enter. - triggerKey({ name: 'return', sequence: '\r' }); + triggerKey({ name: 'enter', sequence: '\r' }); await waitUntilReady(); expect(onResumeSession).toHaveBeenCalledTimes(1); @@ -367,7 +367,7 @@ describe('SessionBrowser component', () => { await waitUntilReady(); // Active selection is at 0 (current session). - triggerKey({ name: 'return', sequence: '\r' }); + triggerKey({ name: 'enter', sequence: '\r' }); await waitUntilReady(); expect(onResumeSession).not.toHaveBeenCalled(); diff --git a/packages/cli/src/ui/components/SessionBrowser.tsx b/packages/cli/src/ui/components/SessionBrowser.tsx index 154ad62522..72eb5ef55c 100644 --- a/packages/cli/src/ui/components/SessionBrowser.tsx +++ b/packages/cli/src/ui/components/SessionBrowser.tsx @@ -873,7 +873,7 @@ export const useSessionBrowserInput = ( // Handling regardless of search mode. if ( - key.name === 'return' && + key.name === 'enter' && state.filteredAndSortedSessions[state.activeIndex] ) { const selectedSession = diff --git a/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap index 2d98d66f03..3980ddbd0a 100644 --- a/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap @@ -1,26 +1,26 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`