Find a file
Edenman 07bd5c41cb
fix(mcp): make the OAuth authorization URL clickable when wrapped (#3489)
* fix(mcp): render OAuth URL as OSC 8 hyperlink so it stays clickable when wrapped

Closes #3470.

The MCP OAuth flow previously pushed the authorization URL through the
generic display-message list, where Ink rendered it as plain text. When
the URL exceeded the terminal width it got hard-wrapped into the message
buffer, and most terminals could no longer detect it as a single
hyperlink (cmd/ctrl+click did nothing, selecting it pulled in extra
whitespace).

Render the URL as an OSC 8 hyperlink in AuthenticateStep instead, and
stop duplicating it through the display-message stream when an event
emitter is available. Terminals that support OSC 8 (iTerm2, WezTerm,
Kitty, Windows Terminal, VS Code, GNOME Terminal, …) now treat the URL
as a single clickable link even when it visually wraps; terminals
without OSC 8 support ignore the escapes and fall back to the existing
"press c to copy" affordance.

* fix(mcp): pre-split OAuth URL so every wrapped line stays clickable

Wrapping the whole URL in a single OSC 8 hyperlink and letting Ink /
wrap-ansi break the line produced two bugs observed in iTerm2 etc.:
only the first visible segment was a hyperlink (wrap-ansi re-emits SGR
codes across wraps but does not re-open OSC 8 links), and the remaining
URL characters overflowed past the dialog border because wrap-ansi was
unable to break the unbroken URL token within the container width.

Manually slice the URL into chunks of `columns - 8` characters
(MCPManagementDialog's container width) and render each chunk as its
own OSC 8 hyperlink with `wrap="truncate"`. Every visible line now
carries a complete hyperlink pointing at the same URL, and no line
exceeds the container width.

* fix(mcp): terminate OSC 8 hyperlinks with BEL so Ink preserves them

Ink's renderer tokenizes text through @alcalzone/ansi-tokenize, which
only recognizes OSC 8 hyperlink escapes terminated with BEL (\x07).
The ST terminator (ESC \\) we were using is valid per the OSC 8 spec
but the tokenizer falls through and treats the escape bytes as regular
characters. That explains the two symptoms seen after the previous fix:

  - Only the first URL segment rendered as a clickable hyperlink. The
    rest of the lines had their opening \\x1b]8;; bytes tokenized as
    chars, so their hyperlink wrap was lost.
  - The dialog's right border disappeared because the mangled escape
    bytes consumed grid cells, pushing the container width past
    `columns - 8` and shoving the border off-screen.

Switch the helper to the BEL-terminated form. Ink now sees each line's
OSC 8 wrap as a proper zero-width code, every wrapped line stays
clickable, and the border is no longer displaced.

* fix(mcp): render OAuth URL via <Static> as a single unwrapped line

The per-line OSC 8 approach didn't make lines past the first clickable
in real terminals. Root cause: Ink's renderer runs text through
@alcalzone/ansi-tokenize, which:

  - Only accepts OSC 8 sequences with empty params (`\x1b]8;;URL\x07`).
    Any `id=` form is parsed as a bogus SGR code and the remainder
    leaks out as visible characters. Without an `id=` grouping
    parameter, terminals like iTerm2 don't reliably stitch adjacent
    OSC 8 escapes together as one hyperlink.
  - Re-emits styles per Ink row via styledCharsToString, so even when
    each slice carried a self-contained OSC 8 wrap, terminals still
    treated each visual line as an independent hyperlink that only
    the first row reliably activated.

Emit the URL through Ink's `<Static>` component instead, inside a
`<Box width={url.length}>`. Ink sees a single logical line that
doesn't need wrapping, so it hands the terminal one OSC 8 open, the
whole URL, and one close. The terminal then soft-wraps that line
visually, and the OSC 8 hyperlink state is carried across every
wrap — every visible line is clickable.

`<Static>` writes once above the dynamic dialog (scrollback-safe) and
isn't touched by re-renders, which also avoids the flicker we'd get
from repeatedly re-emitting the escape sequence inside the live tree.

* fix(mcp): render OAuth URL as live row so it clears on dialog dismissal

The previous <Static> emission made the URL stay permanently in the
scrollback after the OAuth flow finished — e.g. after the dialog was
dismissed the URL was still sitting above the prompt.

Switch to a normal (live) Ink row: a Box sized to the URL length
holding a single OSC 8 wrapped Text. Ink doesn't wrap the row
(maxWidth == content width), so it hands log-update one long line;
log-update's wrap-ansi pass then wraps it at terminal width and
re-emits the OSC 8 escape at every wrap boundary, so every visible
wrapped line is clickable. Because this is a regular child of the
dialog, log-update tracks its height and erases it cleanly when the
AuthenticateStep unmounts (auth succeeds / user backs out / dialog
closes).

* fix(mcp): pre-split OAuth URL so the live row clears cleanly

The wide-Box live approach left dialog fragments in the scrollback: Ink
ships its own log-update.js (packages/cli/.../ink/build/log-update.js)
which counts erase height with output.split('\n').length and does NOT
run wrap-ansi. A single Ink row that exceeds terminal width wraps
visually but the erase still covers only one terminal line, so
authState transitions (auth success, Esc-to-back, dialog dismiss) leave
the top rows of the previous frame behind.

Go back to pre-slicing the URL into chunks sized to the dialog content
width (columns - 8) and rendering each chunk as its own Ink row with
its own OSC 8 wrap. Log-update's row count then matches the visible
row count, so erase is clean on every transition. Terminals that group
adjacent OSC 8 sequences will still treat the whole URL as clickable;
those that don't at least keep the first slice clickable, and the
existing "press c to copy" affordance covers the rest.

* fix(mcp): commit to Static-rendered URL outside the dialog

Stop flip-flopping between in-box and out-of-box URL rendering. Every
in-box attempt hit one of two walls:

  - Per-slice OSC 8 rows: each Ink row is its own self-contained
    hyperlink, but some terminals (seen with the reporter's) only
    register the first adjacent OSC 8 without an id= parameter as
    clickable. Ink's @alcalzone/ansi-tokenize rejects OSC 8 with
    params, so id= grouping is not deliverable.
  - Wide-Box overflow rows: the single OSC 8 wrap keeps every wrapped
    line clickable because the hyperlink state persists across the
    terminal's soft-wraps, but Ink ships its own log-update.js that
    counts erase height by output.split('\n').length and never runs
    wrap-ansi. When the row visually wraps but Ink counts it as 1
    row, transitions (auth success / Esc / dismiss) erase too few
    lines and leave dialog fragments in the scrollback.

Render the URL through <Static> above the dialog: it writes once,
outside log-update's tracking, so the terminal soft-wraps a single
OSC 8 hyperlink and every visible line stays clickable. The trade-off
is that the URL stays in the scrollback after the dialog dismisses
(Static is append-only); that is acceptable given the URL is no
longer sensitive once auth has completed, and it avoids the
click-failure and residue problems of the other approaches.

* fix(mcp): print OAuth URL via useStdout, erase on unmount

Drop <Static> (which persisted the URL in the scrollback forever) and
print the authorization URL directly with Ink's `write` (useStdout)
instead. Ink's writeToStdout clears the live frame, writes our bytes
into the scrollback, and re-renders the frame below, so the URL goes
out in a single OSC 8 hyperlink sequence and the terminal's soft-wrap
preserves the hyperlink state across every wrapped row — every visible
line stays clickable.

On unmount (auth success, Esc, dialog dismiss) we use the same `write`
path to push a cursor-up + eraseLines sequence that removes the URL
rows (plus the leading/trailing blank separators) before log-update
redraws the now-smaller live frame. Net effect: URL shows above the
dialog while authenticating, disappears cleanly when the dialog goes
away, and every wrapped line is clickable throughout.

* fix(mcp): period-terminate prompt and restore wrap warning

Now that the OAuth URL renders above the dialog (outside the message
list), the in-dialog prompt no longer leads into the URL on the next
line — rename the i18n key from "…into your browser:" to "…into your
browser." and re-add the "Make sure to copy the COMPLETE URL — it may
wrap across multiple lines." warning that was dropped when the URL
was first moved out of displayMessage. Translations in de/en/fr/ja/pt/
ru/zh are updated to match and to point at the URL "above" rather
than "following".

* fix(mcp): correct OAuth URL erase count on unmount

The previous logic wrote the URL as `\n${URL}\n` (leading + trailing
newlines) and erased `urlVisualLines + 2` rows on unmount, but the
leading blank and the trailing "\n" don't both occupy their own rows
— the trailing newline just moves the cursor to where the dynamic UI
is re-rendered. For a typical URL whose length isn't an exact multiple
of the terminal width this left the erase off-by-one and wiped a row
above the dialog (e.g. a piece of the command prompt).

Drop the leading `\n` (no real visual benefit) and compute the erase
count as `urlVisualLines + (autoWrapOverflow ? 1 : 0)`. The overflow
term handles the aligned edge case where the terminal auto-wraps past
the last URL char, leaving a blank row between URL and re-rendered
dynamic UI that also needs erasing.

Also drop the stale comment about Ink's ansi-tokenize restricting
OSC 8 terminator choice — we now bypass Ink's tokenizer via
useStdout, so BEL is just the more compatible terminator.

* fix(mcp): pass OAuth URL hyperlink through multiplexer wrapper

Inside tmux or GNU screen the raw OSC 8 hyperlink escape is intercepted
by the multiplexer and never reaches the host terminal — users see
the URL as plain text, exactly the bug this PR is trying to fix. The
existing `wrapForMultiplexer` helper (already used for OSC 52 clipboard
writes) wraps the sequence in a DCS passthrough envelope that tmux /
screen forward to the host.

Apply the same helper to `osc8Hyperlink` so tmux / screen users get
clickable links for every wrapped line as well. Outside a multiplexer
the helper is a no-op, so native terminals are unchanged.

Also note in a comment that the captured `stdout.columns` goes stale
if the terminal is resized during the OAuth flow; this is acceptable
for a sub-minute flow on ASCII-only authorization URLs.

* docs(mcp): note tmux 3.3+ allow-passthrough requirement

* fix(mcp): render OAuth URL inside dialog box

Replace the useStdout().write + cursor-up/eraseLines scrollback
approach with an in-dialog <Box><Text>{osc8Hyperlink(url)}</Text></Box>.
Removes the Ink dynamic-frame interleave and the column-width erase
bookkeeping; the URL is owned by the dialog, so it disappears with it.

* refactor(mcp): drop redundant Fragment around single Text

* revert(mcp): restore original OAuth prompt wording

URL now renders inside the dialog box, so the "copy and paste this URL
into your browser:" prompt no longer needs the period-terminated /
"URL above" rewording. Revert the i18n keys and localized strings;
keep the event-driven dispatch so the URL isn't also pushed through
displayMessage (which would double-render in the UI).

* fix(mcp): sanitize URL/label before embedding in OSC 8 sequence

An unescaped \x07 (BEL) or \x1b (ESC) in the URL or label would
terminate the OSC 8 envelope early and let the tail bytes through
as interpretable terminal escapes. authUrl is normally built via
URL.toString() which percent-encodes controls, but the authorization
endpoint itself comes from server-controlled OAuth discovery, so
treat the input as untrusted and strip C0 + DEL before splicing.
2026-04-21 16:44:23 +08:00
.github ci(stale): enable 35+35 stale/close policy for pull requests (#3375) 2026-04-19 09:45:17 +08:00
.husky Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
.qwen feat: background subagents with headless and SDK support (#3076) 2026-04-17 18:23:06 +08:00
.vscode Merge branch 'main' into feat/sandbox-config-improvements 2026-03-06 14:38:39 +08:00
docs fix(cli): rework session recap rendering and add blur threshold setting (#3482) 2026-04-21 14:39:13 +08:00
docs-site feat: update docs 2025-12-15 09:47:03 +08:00
eslint-rules pre-release commit 2025-07-22 23:26:01 +08:00
integration-tests test(integration): switch settings-migration probe from --help to mcp list (#3486) 2026-04-21 14:19:44 +08:00
packages fix(mcp): make the OAuth authorization URL clickable when wrapped (#3489) 2026-04-21 16:44:23 +08:00
scripts feat(core): detect tool validation retry loops and inject stop directive (#3178) 2026-04-18 10:24:46 +08:00
.dockerignore fix(cli): skip stdin read for ACP mode 2026-03-27 11:47:01 +00:00
.editorconfig pre-release commit 2025-07-22 23:26:01 +08:00
.gitattributes pre-release commit 2025-07-22 23:26:01 +08:00
.gitignore feat: add bugfix workflow, test-engineer agent, and debugging skills 2026-04-04 18:30:09 +08:00
.npmrc chore: remove google registry 2025-08-08 20:45:54 +08:00
.nvmrc chore: Expand node version test matrix (#2700) 2025-07-21 16:33:54 -07:00
.prettierignore Merge branch 'main' into feat/add-vscode-settings-json-schema 2026-03-03 11:21:57 +08:00
.prettierrc.json pre-release commit 2025-07-22 23:26:01 +08:00
.yamllint.yml Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
AGENTS.md feat: add bugfix workflow, test-engineer agent, and debugging skills 2026-04-04 18:30:09 +08:00
CONTRIBUTING.md docs: add Screenshots/Video Demo section to PR template 2026-03-20 16:59:53 +08:00
Dockerfile refactor: Extract web-templates package and unify build/pack workflow 2026-02-26 21:02:46 +08:00
esbuild.config.js feat: add wasm build config (#2985) 2026-04-09 14:21:00 +08:00
eslint.config.js feat: add bugfix workflow, test-engineer agent, and debugging skills 2026-04-04 18:30:09 +08:00
LICENSE Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
Makefile feat: update docs 2025-12-22 21:11:33 +08:00
package-lock.json fix(tool-registry): add lazy factory registration with inflight concurrency dedup (#3297) 2026-04-18 10:31:50 +08:00
package.json fix(build): invoke tsx directly via node --import instead of npx (#3237) 2026-04-19 03:14:13 +08:00
README.md docs: update authentication methods to reflect OAuth discontinuation (#3325) 2026-04-17 15:34:18 +08:00
SECURITY.md fix: update security vulnerability reporting channel 2026-02-24 14:22:47 +08:00
tsconfig.json # 🚀 Sync Gemini CLI v0.2.1 - Major Feature Update (#483) 2025-09-01 14:48:55 +08:00
vitest.config.ts test(channels): add comprehensive test suites for channel adapters 2026-03-27 15:26:39 +00:00

npm version License Node.js Version Downloads

QwenLM%2Fqwen-code | Trendshift

An open-source AI agent that lives in your terminal.

中文 | Deutsch | français | 日本語 | Русский | Português (Brasil)

🎉 News

  • 2026-04-15: Qwen OAuth free tier has been discontinued. To continue using Qwen Code, switch to Alibaba Cloud Coding Plan, OpenRouter, Fireworks AI, or bring your own API key. Run qwen auth to configure.

  • 2026-04-13: Qwen OAuth free tier policy update: daily quota adjusted to 100 requests/day (from 1,000).

  • 2026-04-02: Qwen3.6-Plus is now live! Get an API key from Alibaba Cloud ModelStudio to access it through the OpenAI-compatible API.

  • 2026-02-16: Qwen3.5-Plus is now live!

Why Qwen Code?

Qwen Code is an open-source AI agent for the terminal, optimized for Qwen series models. It helps you understand large codebases, automate tedious work, and ship faster.

  • Multi-protocol, flexible providers: use OpenAI / Anthropic / Gemini-compatible APIs, Alibaba Cloud Coding Plan, OpenRouter, Fireworks AI, or bring your own API key.
  • Open-source, co-evolving: both the framework and the Qwen3-Coder model are open-source—and they ship and evolve together.
  • Agentic workflow, feature-rich: rich built-in tools (Skills, SubAgents) for a full agentic workflow and a Claude Code-like experience.
  • Terminal-first, IDE-friendly: built for developers who live in the command line, with optional integration for VS Code, Zed, and JetBrains IDEs.

Installation

Linux / macOS

bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"

Windows (Run as Administrator)

Works in both Command Prompt and PowerShell:

powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"

Note

: It's recommended to restart your terminal after installation to ensure environment variables take effect.

Manual Installation

Prerequisites

Make sure you have Node.js 20 or later installed. Download it from nodejs.org.

NPM

npm install -g @qwen-code/qwen-code@latest

Homebrew (macOS, Linux)

brew install qwen-code

Quick Start

# Start Qwen Code (interactive)
qwen

# Then, in the session:
/help
/auth

On first use, you'll be prompted to sign in. You can run /auth anytime to switch authentication methods.

Example prompts:

What does this project do?
Explain the codebase structure.
Help me refactor this function.
Generate unit tests for this module.
Click to watch a demo video

🦞 Use Qwen Code for Coding Tasks in Claw

Copy the prompt below and paste it 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.

Authentication

Qwen Code supports the following authentication methods:

  • API Key (recommended): use an API key from Alibaba Cloud Model Studio (Beijing / intl) or any supported provider (OpenAI, Anthropic, Google GenAI, and other compatible endpoints).
  • Coding Plan: subscribe to the Alibaba Cloud Coding Plan (Beijing / intl) for a fixed monthly fee with higher quotas.

⚠️ Qwen OAuth was discontinued on April 15, 2026. If you were previously using Qwen OAuth, please switch to one of the methods above. Run qwen and then /auth to reconfigure.

Use an API key to connect to Alibaba Cloud Model Studio or any supported provider. Supports multiple protocols:

  • OpenAI-compatible: Alibaba Cloud ModelStudio, ModelScope, OpenAI, OpenRouter, and other OpenAI-compatible providers
  • Anthropic: Claude models
  • Google GenAI: Gemini models

The recommended way to configure models and providers is by editing ~/.qwen/settings.json (create it if it doesn't exist). This file lets you define all available models, API keys, and default settings in one place.

Quick Setup in 3 Steps

Step 1: Create or edit ~/.qwen/settings.json

Here is a complete example:

{
  "modelProviders": {
    "openai": [
      {
        "id": "qwen3.6-plus",
        "name": "qwen3.6-plus",
        "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "description": "Qwen3-Coder via Dashscope",
        "envKey": "DASHSCOPE_API_KEY"
      }
    ]
  },
  "env": {
    "DASHSCOPE_API_KEY": "sk-xxxxxxxxxxxxx"
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "qwen3.6-plus"
  }
}

Step 2: Understand each field

Field What it does
modelProviders Declares which models are available and how to connect to them. Keys like openai, anthropic, gemini represent the API protocol.
modelProviders[].id The model ID sent to the API (e.g. qwen3.6-plus, gpt-4o).
modelProviders[].envKey The name of the environment variable that holds your API key.
modelProviders[].baseUrl The API endpoint URL (required for non-default endpoints).
env A fallback place to store API keys (lowest priority; prefer .env files or export for sensitive keys).
security.auth.selectedType The protocol to use on startup (openai, anthropic, gemini, vertex-ai).
model.name The default model to use when Qwen Code starts.

Step 3: Start Qwen Code — your configuration takes effect automatically:

qwen

Use the /model command at any time to switch between all configured models.

More Examples
Coding Plan (Alibaba Cloud ModelStudio) — fixed monthly fee, higher quotas
{
  "modelProviders": {
    "openai": [
      {
        "id": "qwen3.6-plus",
        "name": "qwen3.6-plus (Coding Plan)",
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "description": "qwen3.6-plus from ModelStudio Coding Plan",
        "envKey": "BAILIAN_CODING_PLAN_API_KEY"
      },
      {
        "id": "qwen3.5-plus",
        "name": "qwen3.5-plus (Coding Plan)",
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "description": "qwen3.5-plus with thinking enabled from ModelStudio Coding Plan",
        "envKey": "BAILIAN_CODING_PLAN_API_KEY",
        "generationConfig": {
          "extra_body": {
            "enable_thinking": true
          }
        }
      },
      {
        "id": "glm-4.7",
        "name": "glm-4.7 (Coding Plan)",
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "description": "glm-4.7 with thinking enabled from ModelStudio Coding Plan",
        "envKey": "BAILIAN_CODING_PLAN_API_KEY",
        "generationConfig": {
          "extra_body": {
            "enable_thinking": true
          }
        }
      },
      {
        "id": "kimi-k2.5",
        "name": "kimi-k2.5 (Coding Plan)",
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "description": "kimi-k2.5 with thinking enabled from ModelStudio Coding Plan",
        "envKey": "BAILIAN_CODING_PLAN_API_KEY",
        "generationConfig": {
          "extra_body": {
            "enable_thinking": true
          }
        }
      }
    ]
  },
  "env": {
    "BAILIAN_CODING_PLAN_API_KEY": "sk-xxxxxxxxxxxxx"
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "qwen3.6-plus"
  }
}

Subscribe to the Coding Plan and get your API key at Alibaba Cloud ModelStudio(Beijing) or Alibaba Cloud ModelStudio(intl).

Multiple providers (OpenAI + Anthropic + Gemini)
{
  "modelProviders": {
    "openai": [
      {
        "id": "gpt-4o",
        "name": "GPT-4o",
        "envKey": "OPENAI_API_KEY",
        "baseUrl": "https://api.openai.com/v1"
      }
    ],
    "anthropic": [
      {
        "id": "claude-sonnet-4-20250514",
        "name": "Claude Sonnet 4",
        "envKey": "ANTHROPIC_API_KEY"
      }
    ],
    "gemini": [
      {
        "id": "gemini-2.5-pro",
        "name": "Gemini 2.5 Pro",
        "envKey": "GEMINI_API_KEY"
      }
    ]
  },
  "env": {
    "OPENAI_API_KEY": "sk-xxxxxxxxxxxxx",
    "ANTHROPIC_API_KEY": "sk-ant-xxxxxxxxxxxxx",
    "GEMINI_API_KEY": "AIzaxxxxxxxxxxxxx"
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "gpt-4o"
  }
}
Enable thinking mode (for supported models like qwen3.5-plus)
{
  "modelProviders": {
    "openai": [
      {
        "id": "qwen3.5-plus",
        "name": "qwen3.5-plus (thinking)",
        "envKey": "DASHSCOPE_API_KEY",
        "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "generationConfig": {
          "extra_body": {
            "enable_thinking": true
          }
        }
      }
    ]
  },
  "env": {
    "DASHSCOPE_API_KEY": "sk-xxxxxxxxxxxxx"
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "qwen3.5-plus"
  }
}

Tip: You can also set API keys via export in your shell or .env files, which take higher priority than settings.jsonenv. See the authentication guide for full details.

Security note: Never commit API keys to version control. The ~/.qwen/settings.json file is in your home directory and should stay private.

Local Model Setup (Ollama / vLLM)

You can also run models locally — no API key or cloud account needed. This is not an authentication method; instead, configure your local model endpoint in ~/.qwen/settings.json using the modelProviders field.

Ollama setup
  1. Install Ollama from ollama.com
  2. Pull a model: ollama pull qwen3:32b
  3. Configure ~/.qwen/settings.json:
{
  "modelProviders": {
    "openai": [
      {
        "id": "qwen3:32b",
        "name": "Qwen3 32B (Ollama)",
        "baseUrl": "http://localhost:11434/v1",
        "description": "Qwen3 32B running locally via Ollama"
      }
    ]
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "qwen3:32b"
  }
}
vLLM setup
  1. Install vLLM: pip install vllm
  2. Start the server: vllm serve Qwen/Qwen3-32B
  3. Configure ~/.qwen/settings.json:
{
  "modelProviders": {
    "openai": [
      {
        "id": "Qwen/Qwen3-32B",
        "name": "Qwen3 32B (vLLM)",
        "baseUrl": "http://localhost:8000/v1",
        "description": "Qwen3 32B running locally via vLLM"
      }
    ]
  },
  "security": {
    "auth": {
      "selectedType": "openai"
    }
  },
  "model": {
    "name": "Qwen/Qwen3-32B"
  }
}

Usage

As an open-source terminal agent, you can use Qwen Code in four primary ways:

  1. Interactive mode (terminal UI)
  2. Headless mode (scripts, CI)
  3. IDE integration (VS Code, Zed)
  4. TypeScript SDK

Interactive mode

cd your-project/
qwen

Run qwen in your project folder to launch the interactive terminal UI. Use @ to reference local files (for example @src/main.ts).

Headless mode

cd your-project/
qwen -p "your question"

Use -p to run Qwen Code without the interactive UI—ideal for scripts, automation, and CI/CD. Learn more: Headless mode.

IDE integration

Use Qwen Code inside your editor (VS Code, Zed, and JetBrains IDEs):

TypeScript SDK

Build on top of Qwen Code with the TypeScript SDK:

Commands & Shortcuts

Session Commands

  • /help - Display available commands
  • /clear - Clear conversation history
  • /compress - Compress history to save tokens
  • /stats - Show current session information
  • /bug - Submit a bug report
  • /exit or /quit - Exit Qwen Code

Keyboard Shortcuts

  • Ctrl+C - Cancel current operation
  • Ctrl+D - Exit (on empty line)
  • Up/Down - Navigate command history

Learn more about Commands

Tip: In YOLO mode (--yolo), vision switching happens automatically without prompts when images are detected. Learn more about Approval Mode

Configuration

Qwen Code can be configured via settings.json, environment variables, and CLI flags.

File Scope Description
~/.qwen/settings.json User (global) Applies to all your Qwen Code sessions. Recommended for modelProviders and env.
.qwen/settings.json Project Applies only when running Qwen Code in this project. Overrides user settings.

The most commonly used top-level fields in settings.json:

Field Description
modelProviders Define available models per protocol (openai, anthropic, gemini, vertex-ai).
env Fallback environment variables (e.g. API keys). Lower priority than shell export and .env files.
security.auth.selectedType The protocol to use on startup (e.g. openai).
model.name The default model to use when Qwen Code starts.

See the Authentication section above for complete settings.json examples, and the settings reference for all available options.

Benchmark Results

Terminal-Bench Performance

Agent Model Accuracy
Qwen Code Qwen3-Coder-480A35 37.5%
Qwen Code Qwen3-Coder-30BA3B 31.3%

Ecosystem

Looking for a graphical interface?

  • 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

Troubleshooting

If you encounter issues, check the troubleshooting guide.

Common issues:

  • Qwen OAuth free tier was discontinued on 2026-04-15: Qwen OAuth is no longer available. Run qwen/auth and switch to API Key or Coding Plan. See the Authentication section above for setup instructions.

To report a bug from within the CLI, run /bug and include a short title and repro steps.

Connect with Us

Acknowledgments

This project is based on Google Gemini CLI. We acknowledge and appreciate the excellent work of the Gemini CLI team. Our main contribution focuses on parser-level adaptations to better support Qwen-Coder models.