qwen-code/docs/users/features/status-line.md
Shaojin Wen b004450d7f
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
feat(cli): support multi-line status line output (#3311)
* feat(cli): support multi-line status line output (#3211)

Remove the single-line hard limit (.split('\n')[0]) from the status line
hook so user scripts can output multiple rows. Footer renders each line
as a separate <Text wrap="truncate"> element, preserving per-line
horizontal truncation. Ink's virtual DOM handles re-rendering without
manual ANSI cursor management.

* feat(cli): cap status line output at 3 lines

Prevent runaway scripts from flooding the footer — lines beyond the
third are silently discarded.

* docs: mention 3-line cap in status line docs and agent prompt

* fix(cli): cap status line at 2 lines to keep footer within 3 rows

Footer has a fixed bottom row (hint/mode indicator), so status line
gets at most 2 lines to keep the total footer height at 3 rows max.

* test(cli): improve useStatusLine coverage to 100% lines

Add tests for: per-model metrics payload, contextWindowSize/version/
model fallbacks, config removal with pending debounce, command change
cancelling pending debounce.

* docs: update status line ASCII diagram for multi-line layouts

Also fix TS error in test (null → null as never for mock return).

* refactor(cli): return string[] from useStatusLine, filter empty lines

Address review feedback:
- Hook returns `lines: string[]` instead of `text: string | null`,
  eliminating the join/split round-trip with Footer.
- Filter empty lines before slicing so leading blanks don't eat real
  content (e.g. "\n\nreal content" no longer yields ["", ""]).
- Export MAX_STATUS_LINES with comment explaining the 3-row constraint.
- Use `status-line-${i}` as React key for clarity.

* test(cli): add Footer multi-line rendering, \r\n, and pure-newline tests

Address remaining review feedback:
- Footer test: mock useStatusLine, verify multi-line rendering and
  hint suppression.
- useStatusLine test: add \r\n line ending and pure-newline edge case.

* fix(cli): align right footer indicators to top

When the status line has multiple rows, the left column becomes taller
than the right section. The outer Box defaults to `alignItems: stretch`
which caused the indicators to visually center; add `alignItems="flex-start"`
on the right Box so they stay anchored to the top row.

Reported via e2e test in #3311.
2026-04-17 12:44:30 +08:00

242 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Status Line
> Display custom information in the footer using a shell command.
The status line lets you run a shell command whose output is displayed in the footer's left section. The command receives structured JSON context via stdin, so it can show session-aware information like the current model, token usage, git branch, or anything else you can script.
```
Single-line status (default approval mode — 1 row):
┌─────────────────────────────────────────────────────────────────┐
│ user@host ~/project (main) ctx:34% 🔒 docker | Debug | 67% │ ← status line
└─────────────────────────────────────────────────────────────────┘
Multi-line status (up to 2 lines — 2 rows):
┌─────────────────────────────────────────────────────────────────┐
│ user@host ~/project (main) ctx:34% 🔒 docker | Debug | 67% │ ← status line 1
│ ████████░░░░░░░░░░ 34% context │ ← status line 2
└─────────────────────────────────────────────────────────────────┘
Multi-line status + non-default mode (3 rows max):
┌─────────────────────────────────────────────────────────────────┐
│ user@host ~/project (main) ctx:34% 🔒 docker | Debug | 67% │ ← status line 1
│ ████████░░░░░░░░░░ 34% context │ ← status line 2
│ auto-accept edits (shift + tab to cycle) │ ← mode indicator
└─────────────────────────────────────────────────────────────────┘
```
When configured, the status line replaces the default "? for shortcuts" hint. High-priority messages (Ctrl+C/D exit prompts, Esc, vim INSERT mode) temporarily override the status line. The status line text is truncated to fit within the available width.
## Prerequisites
- [`jq`](https://jqlang.github.io/jq/) is recommended for parsing the JSON input (install via `brew install jq`, `apt install jq`, etc.)
- Simple commands that don't need JSON data (e.g. `git branch --show-current`) work without `jq`
## Quick setup
The easiest way to configure a status line is the `/statusline` command. It launches a setup agent that reads your shell PS1 configuration and generates a matching status line:
```
/statusline
```
You can also give it specific instructions:
```
/statusline show model name and context usage percentage
```
## Manual configuration
Add a `statusLine` object under the `ui` key in `~/.qwen/settings.json`:
```json
{
"ui": {
"statusLine": {
"type": "command",
"command": "input=$(cat); model=$(echo \"$input\" | jq -r '.model.display_name'); pct=$(echo \"$input\" | jq -r '.context_window.used_percentage'); echo \"$model ctx:${pct}%\""
}
}
}
```
| Field | Type | Required | Description |
| --------- | ----------- | -------- | --------------------------------------------------------------------------------------- |
| `type` | `"command"` | Yes | Must be `"command"` |
| `command` | string | Yes | Shell command to execute. Receives JSON via stdin, stdout is displayed (up to 2 lines). |
## JSON input
The command receives a JSON object via stdin with the following fields:
```json
{
"session_id": "abc-123",
"version": "0.14.1",
"model": {
"display_name": "qwen-3-235b"
},
"context_window": {
"context_window_size": 131072,
"used_percentage": 34.3,
"remaining_percentage": 65.7,
"current_usage": 45000,
"total_input_tokens": 30000,
"total_output_tokens": 5000
},
"workspace": {
"current_dir": "/home/user/project"
},
"git": {
"branch": "main"
},
"metrics": {
"models": {
"qwen-3-235b": {
"api": {
"total_requests": 10,
"total_errors": 0,
"total_latency_ms": 5000
},
"tokens": {
"prompt": 30000,
"completion": 5000,
"total": 35000,
"cached": 10000,
"thoughts": 2000
}
}
},
"files": {
"total_lines_added": 120,
"total_lines_removed": 30
}
},
"vim": {
"mode": "INSERT"
}
}
```
| Field | Type | Description |
| ------------------------------------- | ---------------- | ---------------------------------------------------------------------------------- |
| `session_id` | string | Unique session identifier |
| `version` | string | Qwen Code version |
| `model.display_name` | string | Current model name |
| `context_window.context_window_size` | number | Total context window size in tokens |
| `context_window.used_percentage` | number | Context window usage as percentage (0100) |
| `context_window.remaining_percentage` | number | Context window remaining as percentage (0100) |
| `context_window.current_usage` | number | Token count from the last API call (current context size) |
| `context_window.total_input_tokens` | number | Total input tokens consumed this session |
| `context_window.total_output_tokens` | number | Total output tokens consumed this session |
| `workspace.current_dir` | string | Current working directory |
| `git` | object \| absent | Present only inside a git repository. |
| `git.branch` | string | Current branch name |
| `metrics.models.<id>.api` | object | Per-model API stats: `total_requests`, `total_errors`, `total_latency_ms` |
| `metrics.models.<id>.tokens` | object | Per-model token usage: `prompt`, `completion`, `total`, `cached`, `thoughts` |
| `metrics.files` | object | File change stats: `total_lines_added`, `total_lines_removed` |
| `vim` | object \| absent | Present only when vim mode is enabled. Contains `mode` (`"INSERT"` or `"NORMAL"`). |
> **Important:** stdin can only be read once. Always store it in a variable first: `input=$(cat)`.
## Examples
### Model and token usage
```json
{
"ui": {
"statusLine": {
"type": "command",
"command": "input=$(cat); model=$(echo \"$input\" | jq -r '.model.display_name'); pct=$(echo \"$input\" | jq -r '.context_window.used_percentage'); echo \"$model ctx:${pct}%\""
}
}
}
```
Output: `qwen-3-235b ctx:34%`
### Git branch + directory
```json
{
"ui": {
"statusLine": {
"type": "command",
"command": "input=$(cat); branch=$(echo \"$input\" | jq -r '.git.branch // empty'); dir=$(basename \"$(echo \"$input\" | jq -r '.workspace.current_dir')\"); echo \"$dir${branch:+ ($branch)}\""
}
}
}
```
Output: `my-project (main)`
> Note: The `git.branch` field is provided directly in the JSON input — no need to shell out to `git`.
### File change stats
```json
{
"ui": {
"statusLine": {
"type": "command",
"command": "input=$(cat); added=$(echo \"$input\" | jq -r '.metrics.files.total_lines_added'); removed=$(echo \"$input\" | jq -r '.metrics.files.total_lines_removed'); echo \"+$added/-$removed lines\""
}
}
}
```
Output: `+120/-30 lines`
### Script file for complex commands
For longer commands, save a script file at `~/.qwen/statusline-command.sh`:
```bash
#!/bin/bash
input=$(cat)
model=$(echo "$input" | jq -r '.model.display_name')
pct=$(echo "$input" | jq -r '.context_window.used_percentage')
branch=$(echo "$input" | jq -r '.git.branch // empty')
added=$(echo "$input" | jq -r '.metrics.files.total_lines_added')
removed=$(echo "$input" | jq -r '.metrics.files.total_lines_removed')
parts=()
[ -n "$model" ] && parts+=("$model")
[ -n "$branch" ] && parts+=("($branch)")
[ "$pct" != "0" ] 2>/dev/null && parts+=("ctx:${pct}%")
([ "$added" -gt 0 ] || [ "$removed" -gt 0 ]) 2>/dev/null && parts+=("+${added}/-${removed}")
echo "${parts[*]}"
```
Then reference it in settings:
```json
{
"ui": {
"statusLine": {
"type": "command",
"command": "bash ~/.qwen/statusline-command.sh"
}
}
}
```
## Behavior
- **Update triggers**: The status line updates when the model changes, a new message is sent (token count changes), vim mode is toggled, git branch changes, tool calls complete, or file changes occur. Updates are debounced (300ms).
- **Timeout**: Commands that take longer than 5 seconds are killed. The status line clears on failure.
- **Output**: Multi-line output is supported (up to 2 lines; extra lines are discarded). Each line is rendered as a separate row with dimmed colors in the footer's left section. Lines that exceed the available width are truncated.
- **Hot reload**: Changes to `ui.statusLine` in settings take effect immediately — no restart required.
- **Shell**: Commands run via `/bin/sh` on macOS/Linux. On Windows, `cmd.exe` is used by default — wrap POSIX commands with `bash -c "..."` or point to a bash script (e.g. `bash ~/.qwen/statusline-command.sh`).
- **Removal**: Delete the `ui.statusLine` key from settings to disable. The "? for shortcuts" hint returns.
## Troubleshooting
| Problem | Cause | Fix |
| ----------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Status line not showing | Config at wrong path | Must be under `ui.statusLine`, not root-level `statusLine` |
| Empty output | Command fails silently | Test manually: `echo '{"session_id":"test","version":"0.14.1","model":{"display_name":"test"},"context_window":{"context_window_size":0,"used_percentage":0,"remaining_percentage":100,"current_usage":0,"total_input_tokens":0,"total_output_tokens":0},"workspace":{"current_dir":"/tmp"},"metrics":{"models":{},"files":{"total_lines_added":0,"total_lines_removed":0}}}' \| sh -c 'your_command'` |
| Stale data | No trigger fired | Send a message or switch models to trigger an update |
| Command too slow | Complex script | Optimize the script or move heavy work to a background cache |