Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,7 @@ gemini-debug.log
evals/logs/

temp_agents/

# Claude Code and OpenSpec working directories
.claude/
openspec/
157 changes: 157 additions & 0 deletions docs/cli/web-gui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Web GUI

The `/web` command launches a browser-based chat interface for Gemini CLI,
served locally on port 11267. It provides the same AI assistant capabilities as
the terminal UI in a visual dashboard built with Material You design.

## Getting started

Start the web GUI from the Gemini CLI prompt:

```
/web
```

This opens `http://localhost:11267` in your default browser. The web server runs
alongside the CLI session and shares the same authentication and configuration.

## Subcommands

| Command | Description |
| ------------- | -------------------------------------------- |
| `/web` | Start the server and open the browser |
| `/web start` | Start the server without opening the browser |
| `/web stop` | Stop the running web server |
| `/web status` | Show whether the server is running |

## Features

### Chat with streaming

Send messages and receive streamed responses from the configured Gemini model.
The interface supports markdown rendering, syntax-highlighted code blocks, and a
copy button on each block.

### Model selection

Click the model chip in the input toolbar to switch between available models.
The model list is loaded from your CLI configuration and grouped by tier (Pro,
Flash, Flash Lite). Preview models are only shown if your account has access.

### File mentions

Type `@` in the input field to search project files by name. Selected files are
read and included as context with your message, similar to the CLI's `@`-mention
feature.

### File creation

When you ask the model to create a file, it outputs a special code block that
renders as an approval card. You can review the content and choose:

- **Approve** — write the file to disk immediately
- **Approve for session** — write and auto-approve future writes to the same
path
- **Decline** — reject the write

Files are written relative to the working directory. The status bar shows a
count of files written during the session.

### Slash commands

Type `/` in the input field to access commands:

| Command | Action |
| --------- | -------------------------------------------- |
| `/model` | Open the model picker |
| `/clear` | Clear chat history |
| `/stats` | Show session statistics (model, auth, quota) |
| `/tools` | List registered tools |
| `/memory` | View GEMINI.md memory |
| `/about` | Show version and environment info |
| `/theme` | Change the visual theme |
| `/help` | Show available commands |

### Sessions

Chat sessions are saved to browser local storage. The sidebar lists recent
sessions with search and two view modes:

- **List view** — flat chronological list
- **Tree view** — sessions grouped by working directory

Click the `+` button to open the directory picker and start a new session in a
different workspace.

### Themes

Four built-in themes are available via `/theme`:

- **Dark** (default) — Material You dark surface
- **Light** — Material You light surface
- **Ocean** — deep blue palette
- **Forest** — green palette

### Context tracking

The context chip in the status bar shows current token usage as a percentage of
the model's context window. Click it to see a breakdown of input tokens
(regular, cache read, cache write) and output tokens.

### Responsive design

The interface adapts to screen size:

- **Desktop** (> 1024px) — sidebar + main content side by side
- **Tablet** (768–1024px) — narrower sidebar
- **Mobile** (< 768px) — sidebar as overlay drawer, model picker as bottom
sheet, safe-area support for notch devices

## Architecture

The web GUI is packaged as `@google/gemini-cli-webui` and consists of:

| Module | Role |
| ----------- | -------------------------------------------------------------------- |
| `server.ts` | HTTP server, HTML template, route dispatch |
| `api.ts` | API handlers: chat streaming, command proxy, file operations, models |
| `client.ts` | Browser-side JavaScript (ES5-compatible) |
| `styles.ts` | CSS (Material You tokens, responsive breakpoints, theme variants) |
| `icons.ts` | SVG icon helpers |

The `/web` slash command in `packages/cli` is a thin bridge that wires the
server lifecycle into the CLI's `SlashCommand` interface.

### API endpoints

| Method | Path | Description |
| ------ | ------------------- | ---------------------------------------- |
| `POST` | `/api/chat` | SSE-streamed chat via `ContentGenerator` |
| `POST` | `/api/command` | Proxy for slash commands |
| `POST` | `/api/files/search` | Fuzzy file search for `@` mentions |
| `POST` | `/api/files/read` | Read file content |
| `POST` | `/api/files/write` | Write file (used by approval flow) |
| `GET` | `/api/models` | List available models |

### Authentication

The web GUI reuses the CLI's `ContentGenerator` abstraction, which supports all
authentication types: Google OAuth (`LOGIN_WITH_GOOGLE`), API key
(`USE_GEMINI`), Vertex AI, Application Default Credentials, and Gateway. No
separate authentication is needed.

### Security

- File read and write operations are restricted to paths within the working
directory (path traversal prevention).
- The server binds to `localhost` only — it is not accessible from other
machines on the network.
- CORS is restricted to `http://localhost:11267`.

## Limitations

- The web GUI cannot execute shell commands. When you ask for a command to run,
the model provides it as a copyable code block.
- Tool use (beyond file read/write) is not available in web mode. The model
operates in a text-only conversation mode.
- Sessions are stored in browser local storage, not synced with CLI sessions.
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"dependencies": {
"@agentclientprotocol/sdk": "^0.16.1",
"@google/gemini-cli-core": "file:../core",
"@google/gemini-cli-webui": "file:../webui",
"@google/genai": "1.30.0",
"@iarna/toml": "^2.2.5",
"@modelcontextprotocol/sdk": "^1.23.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/services/BuiltinCommandLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { vimCommand } from '../ui/commands/vimCommand.js';
import { setupGithubCommand } from '../ui/commands/setupGithubCommand.js';
import { terminalSetupCommand } from '../ui/commands/terminalSetupCommand.js';
import { upgradeCommand } from '../ui/commands/upgradeCommand.js';
import { webCommand } from '../ui/commands/webCommand.js';

/**
* Loads the core, hard-coded slash commands that are an integral part
Expand Down Expand Up @@ -229,6 +230,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
AuthType.LOGIN_WITH_GOOGLE
? [upgradeCommand]
: []),
webCommand,
];
handle?.end();
return allDefinitions.filter((cmd): cmd is SlashCommand => cmd !== null);
Expand Down
140 changes: 140 additions & 0 deletions packages/cli/src/ui/commands/webCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* /web slash command — thin bridge to @google/gemini-cli-webui.
* All web GUI logic lives in packages/webui; this file only wires
* the server lifecycle into the CLI's SlashCommand interface.
*/

import open from 'open';
import process from 'node:process';
import {
type CommandContext,
type SlashCommand,
CommandKind,
} from './types.js';
import { MessageType } from '../types.js';
import {
startServer,
stopServer,
setConfig,
isRunning,
WEB_PORT,
WEB_URL,
} from '@google/gemini-cli-webui';

const webStartSubCommand: SlashCommand = {
name: 'start',
description: 'Start the web server without opening the browser',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context: CommandContext): Promise<void> => {
if (isRunning()) {
context.ui.addItem(
{
type: MessageType.INFO,
text: `Web server is already running at ${WEB_URL}`,
},
Date.now(),
);
return;
}
try {
setConfig(context.services.agentContext?.config ?? null);
await startServer();
context.ui.addItem(
{ type: MessageType.INFO, text: `Web server started at ${WEB_URL}` },
Date.now(),
);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
context.ui.addItem(
{ type: MessageType.ERROR, text: `Failed to start web server: ${msg}` },
Date.now(),
);
}
},
};

const webStopSubCommand: SlashCommand = {
name: 'stop',
description: 'Stop the running web server',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context: CommandContext): Promise<void> => {
if (!isRunning()) {
context.ui.addItem(
{ type: MessageType.INFO, text: 'Web server is not running.' },
Date.now(),
);
return;
}
await stopServer();
context.ui.addItem(
{ type: MessageType.INFO, text: 'Web server stopped.' },
Date.now(),
);
},
};

const webStatusSubCommand: SlashCommand = {
name: 'status',
description: 'Show current web server status',
kind: CommandKind.BUILT_IN,
autoExecute: true,
isSafeConcurrent: true,
action: async (context: CommandContext): Promise<void> => {
const status = isRunning()
? `Running at ${WEB_URL}`
: 'Not running. Use /web or /web start to launch.';
context.ui.addItem(
{ type: MessageType.INFO, text: `Web server status: ${status}` },
Date.now(),
);
},
};

export const webCommand: SlashCommand = {
name: 'web',
description:
'Open the Gemini chat web interface in your browser (port ' +
WEB_PORT +
')',
kind: CommandKind.BUILT_IN,
autoExecute: true,
subCommands: [webStartSubCommand, webStopSubCommand, webStatusSubCommand],
action: async (context: CommandContext): Promise<void> => {
setConfig(context.services.agentContext?.config ?? null);

try {
await startServer();
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
context.ui.addItem(
{ type: MessageType.ERROR, text: `Failed to start web server: ${msg}` },
Date.now(),
);
return;
}

if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
context.ui.addItem(
{
type: MessageType.INFO,
text: `Gemini Chat UI available at: ${WEB_URL}`,
},
Date.now(),
);
} else {
context.ui.addItem(
{ type: MessageType.INFO, text: `Opening Gemini Chat UI: ${WEB_URL}` },
Date.now(),
);
await open(WEB_URL);
}
},
};
2 changes: 1 addition & 1 deletion packages/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"./package.json"
],
"exclude": ["node_modules", "dist"],
"references": [{ "path": "../core" }]
"references": [{ "path": "../core" }, { "path": "../webui" }]
}
Loading