* feat(cli): OpenTUI foundation modules — theme, a11y, clipboard, keys, dialogs scaffolding
Foundation batch of the OpenTUI migration tracked in #8662. Adds the renderer-neutral foundation modules under ui/opentui: theme family, a11y (plain-text, screen-reader), clipboard, key-map, mouse hit/caret, link-click + osc8 parity, early-input, exit guard/lifecycle, kitty negotiation, event-adapter, item-projection, slash dispatch (+ command parsing), commands context/output, help content, input history, and the dialog scaffolding primitives (core/shared) with the theme dialog. Two helpers land inside ui/opentui rather than utils/ to respect the utils leaf-layer rule (#9737). Stacked on the infra batch: consumes ui/model streaming model and @opentui deps. No reachable ink code changes beyond a one-line export addition in the shared osc8 module.
* fix(cli): import originals instead of forking slash parser and dialog scope utils
* fix(cli): address R1 review findings in OpenTUI foundation modules
* fix(cli): align OpenTUI command host with the memory-file-count rename
Upstream renamed setGeminiMdFileCount to setMemoryFileCount in the command
UI contract; the rebase onto main surfaced the mismatch at build. Rename
the host interface member, the bridge wiring, the dispatch stub, and the
test mock to match.
* feat(cli): OpenTUI migration live-session and input batch
Third landing batch of the OpenTUI migration (#8662): live-session stream
fold and model, message rendering (markdown heal, MCP progressive, client
tool runs, text batching), transcript adapter with resume/session-switch,
sticky todos, the composer (input-prompt view/key/model), mouse rows and
scrollbar, unified-diff rendering, and session-compaction notice. All
additive — no reachable ink code path is touched, ink remains the default.
Carries the first consumer of the remend dependency deferred from the
infra batch, placed in devDependencies per the renderer-deps convention.
The stacked-skill completion helpers import from the relocated
ui/commands module following the upstream rename.
* feat(cli): OpenTUI migration batch 4 — dialogs, commands, and session-rewind
Adds the dialog layer and command-routing infrastructure for the OpenTUI
renderer: 19 dialog modules (auth, extensions, MCP, memory-status, misc,
model family, modes, permissions, settings, stats/skills, help overlay,
arena host, folder-trust gate), the commands registry with slash-to-dialog
routing, the commands dispatcher (action interpreter connecting the slash
gateway to the session and dialog layer), and the session-rewind viewer
with its history-folding model.
Also exports `isUserTextContent` from historyMapping so the rewind model
can classify user turns without duplicating the predicate.
Everything is additive: no reachable ink code path is touched, the default
renderer stays ink, and the dep-direction gate passes. Stacked on the
live-session batch.
56 test files / 886 tests, all green.
* fix(cli): address R2 review findings in OpenTUI foundation modules
Round-2 review fixes (17 Critical + 10 Suggestion resolved in code):
- dialogs-shared: move number-select flush out of the setState updater
(StrictMode double-fires onSelect); split setActiveIndex (ink
SET_ACTIVE_INDEX, lands on any in-range row) from highlightIndex
(arrow keys skip disabled rows) so wheel navigation never sticks
- event-adapter: chat_compressed notice mirrors ink formatCount ('~'
prefix for estimated counts); vision_bridge_notice renders
summary\nnotice; explicit projections for task_execution /
findings_list / terminal_image keep multi-MB payloads off the
transcript; retry-countdown-clear forwards isContinuation
- slash-dispatch: isSlashCommandInput drops the '?' branch (ink gate
routes ? input to the model); executeSlashCommand races the action
against the abort signal; dialog effects carry the
OpenDialogActionReturn payload; projected added-item text surfaces
alongside non-handled effects (notice); message-shaped items project
to their text; ui.history comes from env; absent sessionStats stamp
now, not epoch; telemetry parity (recordSkillInvocation /
recordAutoSkillCommandUsage / makeSlashCommandEvent)
- item-projection: model stats render per-(model,source) sections with
N/A for unpriced entries; Tool Calls line uses ASCII x like ink;
redactProxy deduplicated via systemInfoFields export
- theme: palette/syntax colors resolve through color-utils toHex before
parseColor (ink CSS names / *bright names no longer degrade to
magenta); unresolvable values stay unset
- key-map: kitty 'kpenter' normalizes to 'return'; resolveCommands
exposes ink's key fan-out (Ctrl+C fires QUIT + CLEAR_INPUT)
- a11y: hardWrap delegates to wrap-ansi (word-boundary parity with
ink's screen-reader path); markdown reducer tracks fence length,
keeps fence-like lines literal inside fences and inner backticks in
multi-backtick spans; stripAnsi delegates to strip-ansi plus a
private-parameter CSI pass (SGR mouse, DEC save/restore)
- clipboard: OSC 52 write gated on a TTY (stderr preferred), tests spy
the stream instead of writing real sequences to the runner's terminal
- exit-guard: independent per-key arm windows like ink
- dialogs-theme: diff preview pane receives syntaxStyle/filetype
* fix(cli): harden kitty probe and screen-reader writer per maintainer review
- kitty-negotiation: KITTY_REPLY_RE requires at least one flag digit
(\d+), so an echoed bare query \x1b[?u in PTY/CI environments no
longer resolves true and locks the renderer into kitty mode on a
terminal that never answers queries; the accumulation buffer keeps
only a 256-byte tail (bounded memory, bounded rescan under byte
floods); the settle-window drain is removed — an EventEmitter data
listener cannot consume chunks from other listeners, so late replies
flow to the renderer's input parser like any other terminal noise
- a11y-screen-reader: ScreenReaderOutputWriter sanitizes written
content (stripAnsi + drop bare C0/C1 controls, keep newlines) so the
plain-text-only contract is enforced at the writer instead of
trusting every future caller — smuggled OSC 52 clipboard writes or
title/cursor sequences cannot execute on the main screen
* test(cli): strengthen /branch awaits handleBranch assertion with a deferred race
The previous test asserted branchNames was populated after dispatch, but a
fire-and-forget void call passed because microtasks drained before the check.
Replace with a Promise.race that proves dispatch was still pending (blocked on
the closed gate) before the gate was resolved — a void call resolves dispatch
immediately, making the race return 'resolved' instead of the sentinel.
* fix(cli): correct stale 67→69 count in commands-registry docblocks; clarify gate test
Two docblock comments said "67 modules" but the table has 69 entries (verified
against BuiltinCommandLoader.ts). Fixed to 69.
The gated-commands test comment now explains that TypeScript enforces the
CommandGate type at compile time, so the runtime loop is unnecessary — the
literal name list is the intentional guard for a bogus gatedBy on an ungated
command.
* fix(cli): address dialogs-batch self-review findings in command registry
- /theme route results include 'message': themeCommand returns a
MessageActionReturn under NO_COLOR, so the declared results were
incomplete
- drop the unreachable 'branch' member from OpenTuiDialogRequest and
make routeDialogToOpenTui throw on dialog-branch instead: /branch is
a host action intercepted unconditionally by the dispatcher (ink
parity), and a compile-time exclusion is not expressible because
OpenDialogActionReturn is a single interface with a union dialog
field — the loud throw guards against a future refactor dropping the
interception; the branch route no longer advertises a dialogs entry
no renderer opens
- derive gate coverage from the loader instead of a hardcoded name
list: the coverage test loads with every gate ON (plus the
checkpointing flag the /restore factory needs) and asserts set
equality between route names and registered built-ins — no escape
hatches — and a new test proves every gatedBy route is genuinely
absent from a gates-off load, so a bogus gate on an always-registered
command fails
* fix(cli): address ytahdn independent review findings in OpenTUI foundation
All 15 findings from the independent static review verified in source and
fixed (no false positives; none deferred):
- quit effect carries QuitActionReturn.messages projected to text on a
notice field — ink renders them via QuittingDisplay and the payload was
permanently lost (Important #1)
- error and finished branches emit retry-countdown-clear like ink's
handleErrorEvent/handleFinishedEvent, so a terminal event inside the
countdown window no longer leaves a stale retry row (#2)
- projectContextUsage renders the compaction-threshold ladder and the
per-item detail sections (tools/memory/skills, ink's sort order) when
showDetails is on — /context detail transcripts no longer show strictly
less than the compact view (#3)
- projectMcpStatus honors showSchema (parameter JSON under each tool) and
showTips, so /mcp schema is distinguishable from /mcp (#4)
- SlashDispatchEnv.settings is required: the real
CommandContext.services.settings is non-null and a null surfaced as a
generic command failure on first .merged read (#5)
- dead singleColumn flag removed from the help width layout (clamp makes
it always false; ink has no single-column mode) (#6)
- truncated SS3 tail (bare ESC O) is stripped like the truncated CSI
tail, so a captured half F1-F4 no longer leaks 'O' into the composer (#7)
- readBufferRow trims cellColumns alongside text, so URLs ending at
end-of-row on a wide character hit-test on both halves of the cell (#8)
- kitty probe writes guarded: a synchronous stream throw settles the
probe (restores raw mode, removes the listener) instead of leaking (#9)
- eraseLines reuses the ansi-escapes helper (already a repo dependency)
instead of a byte-identical hand-rolled copy (#10)
- truncateText passthrough wrapper dropped; callers use the exported
truncateHelpText directly (#11)
- SkillsList truncate keeps total length n like ink, so the description
column no longer shifts by one cell when a name truncates (#12)
- model_fallback names pass through sanitizeDisplayText like ink (#13)
- selectIndex fires onHighlight before onSelect (ink dispatches
SET_ACTIVE_INDEX then SELECT_CURRENT), keeping highlight-driven
stay-open dialogs synced on mouse input (#14)
- mcp_app without fallbackText renders empty instead of JSON-dumping the
embedded HTML; projectAbout hides Base URL when selectedAuthType is
empty, matching ink's formatBaseUrl (#15)
* fix(cli): drop dead single-column branch in help overlay
The foundation batch removed the always-false singleColumn flag from
the help width layout (the 72-column clamp makes it unreachable and ink
has no single-column mode); the overlay's conditional branch on it no
longer compiles. Only the two-column path was ever taken.
* fix(cli): address R3 review findings in OpenTUI foundation modules
- executeSlashCommand catch checks the abort signal first: an ESC-
cancelled command (action rejects AbortError) returns handled with no
failure telemetry or error message, mirroring ink's processor — the
race promise never resolves when the signal is already aborted at
addEventListener time
- submit effect carries the full SubmitPromptActionReturn contract
(modelOverride, onComplete, refreshContextFilesOnWrite) so the
backend can honor /model <id> <prompt>, /dream's manual-run record,
and /remember's context refresh like ink instead of silently
degrading them
- closing fences cannot carry info text (CommonMark): a ```js line
inside an open block is literal body, not an early close that drops
the block and inverts parse state for the rest of the document
- info items append their linkUrl/linkText footer (ink's InfoMessage
renders it; headless/SSH users need the printed URL, e.g. /bug)
- the screen-reader sanitize keeps TAB: it separates words in
tool/model output and deleting it fused adjacent tokens
* fix(cli): address R4 review findings in OpenTUI foundation modules
- a11y-plain-text: split on all CommonMark line endings so CRLF
markdown opens/closes fences correctly; private-param CSI regex
covers ECMA-48 intermediate bytes; DCS/SOS/PM/APC and unterminated
OSC sequences consumed before strip-ansi; code-span pattern mirrors
ink's INLINE_CODE_SPAN_PATTERN (non-empty content, closing-run
lookbehind)
- dialogs-shared: clearNumberBuffer called from setActiveIndex,
selectIndex, and resyncKey block so wheel/hover/click/resync can't
commit a stale numeric-flush selection the user never made
- event-adapter: tool_call_response carries visionBridgeNotice on the
tool-result event (ink ToolMessage renders the egress disclosure)
- item-projection: projectContextUsage reads memoryFiles as { path,
tokens } (ContextMemoryDetail), not { name, tokens }
- key-map: 10 kp* keypad-navigation aliases (kpleft→left, …) and
super flag folded into meta (ink Cmd+Enter = newline, not submit)
- link-click: cellColumns no longer truncated to trimmed text length
(preserves the wide-glyph right-half boundary); findUrlAtRow end
boundary is width-aware (stringWidth of the last glyph)
- slash-dispatch: submit effect carries PartListUnion content + a
textContent string for text-only consumers (image parts survive);
toggleVimEnabled and startNewSession seams wired from env; abort
race resolves immediately for an already-aborted signal
- clipboard: OSC 52 self-write removed — copyToClipboard's existing
fallback (writeOsc52 / wrapForMultiplexer) is the single source
- a11y-screen-reader: appendStatic skips clean === '\n' (ink's
hasStaticOutput guard)
* fix(cli): address #10383 R1 findings in foundation modules
- event-adapter: finished branch emits retry-countdown-clear BEFORE the
info notice so the countdown row is actually cleared (the fold only
pops when the last item is the retry row)
- item-projection: /mcp tips now include all 5 lines ink renders (added
OAuth auth tip and Ctrl+T toggle tip)
- link-click: wide-glyph end boundary uses the last code point (not
UTF-16 code unit) so non-BMP emoji are measured correctly by
stringWidth
- a11y-plain-text: CSI_SEQUENCE replaces PRIVATE_PARAM_CSI — drops the
marker requirement so any CSI (with or without private parameter
marker, with or without intermediate bytes) is fully consumed
* fix(cli): address #10383 R1 findings in dialogs batch
- dialogs-mcp: flatServers derived from grouped render order, not raw
prop order, so keyboard selection matches the highlighted server
- dialogs-misc: highlightScope syncs editor selection (setSel) so Enter
persists the highlighted scope's own editor, not the previous one
- dialogs-settings: buildSettingsListItems forwards
excludeWorkspaceRestricted under Workspace scope, matching ink's
filter that prevents dead settings entries
- dialogs-extensions: all onDetailAction call sites now swallow async
rejections (.catch), matching session-rewind.tsx's pattern
- dialogs-stats-skills: metrics read from per-session bucket
(getMetricsForSession) not process-global; Wall Time computed from
uiTelemetryService.getSessionStartTime() not a module-load constant
- dialogs-modes: MODE_DESC key 'auto_edit' fixed to 'auto-edit' to
match ApprovalMode.AUTO_EDIT enum value
* fix(cli): address #10383 R2 Critical findings in slash-dispatch
- abort race: already-aborted signal now skips command.action entirely
(result = undefined) instead of eagerly evaluating it as a Promise.race
argument — the action's side effects (clear, persist, addItem) must not
run on a cancelled submission
- parent command telemetry: logEvent (slash_command SUCCESS) is now
called before the early return for parent commands with subCommands
(help listing) and bare handled — matching ink's finally-block logging
* fix(cli): address #10383 R2 Critical findings in dialogs batch
- dialog-data: buildModelEntries image mode gates on
isImageGenerationCapable (not imageOnly) so dual-role and visionOnly
image-capable models appear in the image selector like ink
- dialogs-extensions: detailSelect gets resyncKey: view so the cursor
re-syncs when re-entering detail (action list shrinks after
checked-update state resets)
- dialogs-stats-skills: subscribes to uiTelemetryService 'update' event
so stats stay live while the dialog is open (ink re-renders via
SessionStatsProvider)
* fix(cli): address #10383 R3 review findings in opentui dialogs/dispatch
- buildModelEntries: keep visionOnly models in the image selector (ink
ModelDialog parity: isVisionModelMode || isImageModelMode || !visionOnly)
- useDialogSelect: re-sync the cursor on items changes like ink's
useSelectionList INITIALIZE reducer — follow the active item's key,
fall back to the initial index when it is gone, so a shrinking list
never strands the cursor where Enter reads undefined
- OpenTuiSlashDispatcher: skip the action entirely when the signal is
already aborted before the race — a late 'abort' listener never fires,
so the eager race would run side effects before discarding
- tests: pin the pre-aborted skip in both dispatchers, the dual-role
image/vision fixture, the items-shrink clamp, and restore the
uninstall-backout guard assertion
* fix(cli): address #10368 R2 review findings in live-session batch
- input-prompt: convert OpenTUI display-width cursor coordinates to
code-point positions at the component boundary — the pinned
@opentui/core reports logicalCursor.col/offset and el.cursorOffset in
terminal-cell units (edit-buffer.zig), while the ported ink helpers
work in code points; wide characters previously shifted placeholder
backspace, the backslash continuation check, completion targeting,
and history edge compares
- input-prompt: bump both search sequence refs when Esc dismisses the
completion dropdown so an in-flight search resolving afterwards cannot
re-open it and hijack Enter
- live-session-model: carry the vision-bridge egress disclosure through
the tool-result fold (ink ToolMessage renders it under the result)
- messages: recognize the producers' two-L 'cancelled' summary spelling
so canceled tools get the CANCELED glyph with strikethrough instead of
the red ERROR glyph
- session-switch: wrap /resume and /branch in the telemetry swap
transaction (begin before the outgoing-session capture, commit at the
UI re-key, abort after a rolled-back swap) — restores the usage
aggregate on failed swaps and rejects concurrent switches
- tests: display-width fake editor, wide-char placeholder/continuation
witnesses, Esc invalidation, fold notice, cancelled spelling, and the
three swap-transaction lifecycle cases
* fix(cli): declare cursorOffset on the FakeEditor test interface
The display-width fake added in 656e996c9a implements a cursorOffset
getter/setter but the interface it is cast through never declared the
member, so tsc --build fails with TS2339 at the two reads in the
wide-char placeholder test. Typecheck ran before that commit's files
were staged and missed it.
* fix(cli): address #10383 R4 review findings in dialogs batch
- commands-dispatch.test: type the pre-aborted action mock as
SlashCommandActionReturn so tsc --build passes (the inferred
{ type: string } could not satisfy the literal 'message' kind)
- dialogs-shared.test: pin the resyncKey numeric-flush disarm — an
armed digit quick-select must not commit a selection in the view
swapped to before the flush timeout
- session-switch.test: pin the unarmed-swap settlement when the
resumed session is not found (commit, never abort) so the single
swap slot cannot stay latched forever
* test(cli): stub listStartingRunIds in the session-switch fake registry
The workflow-run registry gained listStartingRunIds with the workflow
tasks feature on main; backgroundWorkUtils iterates it when describing
blocking work, so the fake registry in session-switch.test.ts now
implements it (empty) to match the interface the merged code expects.
* fix(opentui): address P2 review findings in session-rewind and load_history
- session-rewind: add useRef re-entrancy guard to prevent double onRewind
from batched key events in single stdin chunk
- session-rewind: dispatch restore-error on onRewind rejection so the
dialog recovers from dead 'restoring' phase back to 'pick'
- commands-dispatch: pass Date.now()-based timestamps to addItem instead
of array indices in load_history branch
The open-source AI coding agent that lives in your terminal.
中文 | Deutsch | français | 日本語 | Русский | Português (Brasil) | 한국어
Why Qwen Code?
- Agentic out of the box — Auto-Memory, Auto-Skills, SubAgents, Agent Teams, and MCP. Dynamic workflows, zero setup.
- Open-source, inside and out — The framework and the Qwen models are open-source. They evolve together. No vendor lock-in.
- Multi-protocol — Supports OpenAI, Anthropic, Gemini, and Qwen APIs. Any third-party provider or local model (Ollama / vLLM). Switch at runtime.
- Beyond the terminal — IDE plugins, Desktop app, daemon mode, SDKs, and IM bots (Telegram / DingTalk / WeChat / Feishu).
Tip
Qwen Code is actively iterating on itself — using its own agent and models to file issues, submit PRs, review code, and run tests. Powered by the community, driven by AI.
Installation
Linux / macOS:
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh | bash
Windows:
irm https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.ps1 | iex
Restart your terminal after installation to ensure environment variables take effect.
NPM / Homebrew
NPM (requires Node.js 22+):
npm install -g @qwen-code/qwen-code@latest
Homebrew (macOS / Linux):
brew install qwen-code
Quick Start
qwen # Launch interactive terminal UI
# Inside the session:
/auth # Configure your provider and API key
See the Authentication Guide and Settings Reference for detailed setup.
How to Use Qwen Code
| Mode | Command | Use Case |
|---|---|---|
| Interactive | qwen |
Terminal UI with rich rendering, @file references, slash commands |
| Headless | qwen -p "..." |
Scripts, CI/CD, batch processing — no UI |
| IDE | — | VS Code, Zed, JetBrains |
| Desktop | — | Qwen Code Desktop — GUI for macOS, Windows, Linux |
| Daemon | qwen serve |
Shared agent session over HTTP+SSE (ACP). Multiple clients, one agent. (experimental) Docs |
| SDK | — | TypeScript, Python, Java |
| IM Bot | qwen channel |
Connect to Telegram, DingTalk, WeChat, or Feishu |
SDK example (Python)
import asyncio
from qwen_code_sdk import is_sdk_result_message, query
async def main() -> None:
result = query(
"Summarize the repository layout.",
{
"cwd": "/path/to/project",
"path_to_qwen_executable": "qwen",
},
)
async for message in result:
if is_sdk_result_message(message):
print(message["result"])
asyncio.run(main())
Capabilities
If you know Claude Code, you already know Qwen Code — and then some. We've put significant effort into bringing Qwen Code to feature parity with Claude Code, improving both breadth and reliability across the board.
| Feature | Qwen Code | Claude Code |
|---|---|---|
| SubAgents, Agent Teams, Dynamic Workflows | ✓ | ✓ |
| Auto-Memory, Auto-Skills, Hooks | ✓ | ✓ |
| Built-in Skills (/review, /batch, /loop, /bugfix…) | ✓ | ✓ |
| MCP, Plan Mode, LSP Integration | ✓ | ✓ |
| Auto Mode, Sandbox, Git Worktrees | ✓ | ✓ |
| Computer Use (desktop automation) | ✓ | ✓ |
| IDE Plugins (VS Code / JetBrains / Zed) | ✓ | ✓ |
| SDK | ✓ | ✓ |
| Headless Mode, Session Management | ✓ | ✓ |
| Open-source — model and framework | ✓ | — |
| Multi-protocol (OpenAI / Anthropic / Gemini / Qwen + any provider) | ✓ | — |
| Agent Arena (multi-model head-to-head on same task) | ✓ | — |
Daemon Mode — qwen serve (multi-client shared agent) |
✓ | — |
| IM Channels (Telegram / DingTalk / WeChat / Feishu) | ✓ | — |
Ecosystem
-
Qwen Code Desktop — Official desktop app for macOS, Windows, and Linux
-
AionUi — A modern GUI for command-line AI tools including Qwen Code
-
Gemini CLI Desktop — A cross-platform desktop/web/mobile UI for Qwen Code
-
🦞 Qwen Code Claw — Let other agents (Claude, Codex, etc.) delegate coding tasks to Qwen Code via ACP. Paste this prompt into your agent:
Use Qwen Code as a coding agent. First, fetch and read this skill reference:
https://raw.githubusercontent.com/QwenLM/qwen-code/refs/heads/main/.qwen/skills/qwen-code-claw/SKILL.md
Then follow the instructions inside to install, authenticate, and use Qwen Code via acpx for all coding tasks.
- Aliyun Model Studio CLI — Official CLI for Aliyun's AI platform (
bailian-cli). Extends Qwen Code with image/video generation, knowledge retrieval, app orchestration, and model deployment
Contributing
Contributions are welcome! See CONTRIBUTING.md for guidelines.
Acknowledgments
This project was originally based on Google Gemini CLI v0.8.2. We gratefully acknowledge the Gemini CLI team's excellent work. Starting from Qwen Code v0.1, we stopped syncing with upstream and began independent development as a multi-protocol, multi-platform agent framework with deep integrations for Qwen models and beyond.
