Compare commits
96 Commits
v0.1.1
...
refactor-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfe8133ea3 | ||
|
|
17785c418d | ||
|
|
6729980b47 | ||
|
|
2ca36d7508 | ||
|
|
e426c15e9e | ||
|
|
0a75d85ac9 | ||
|
|
a7abd8d09f | ||
|
|
c9af74816a | ||
|
|
9cfea73207 | ||
|
|
87b1ffe017 | ||
|
|
83fc321e15 | ||
|
|
48b77541c3 | ||
|
|
f2439f8d53 | ||
|
|
fb6d0b43fa | ||
|
|
627283d357 | ||
|
|
640f30655d | ||
|
|
9e5387f159 | ||
|
|
442a9aed58 | ||
|
|
a15b84e2a1 | ||
|
|
07069f00d1 | ||
|
|
e1e7a0d606 | ||
|
|
fc638851e7 | ||
|
|
e1f793b2e0 | ||
|
|
3c64f7bff5 | ||
|
|
97bf48b14c | ||
|
|
d0e76c76a8 | ||
|
|
3ed93d5b5d | ||
|
|
71646490f1 | ||
|
|
f0bbeac04a | ||
|
|
efca0bc795 | ||
|
|
6bb829f876 | ||
|
|
5bc309b3dc | ||
|
|
0eeffc6875 | ||
|
|
f0e21374c1 | ||
|
|
29261c75e1 | ||
|
|
b4eba6584a | ||
|
|
e6d08f0596 | ||
|
|
160b64523e | ||
|
|
0752a31e1e | ||
|
|
b029f0d2ce | ||
|
|
d5d96c726a | ||
|
|
06141cda8d | ||
|
|
22edef0cb9 | ||
|
|
ca1ae19715 | ||
|
|
6aaac12d70 | ||
|
|
3c01c7153b | ||
|
|
7a472e4fcf | ||
|
|
5390f662fc | ||
|
|
c3d427730e | ||
|
|
21fba6eb89 | ||
|
|
d17c37af7d | ||
|
|
82170e96c6 | ||
|
|
decb04efc4 | ||
|
|
3bd0cb36c4 | ||
|
|
553a36302a | ||
|
|
498d7a083a | ||
|
|
3a69931791 | ||
|
|
d4ab328671 | ||
|
|
90500ea67b | ||
|
|
335e765df0 | ||
|
|
448e30bf88 | ||
|
|
26215b6d0a | ||
|
|
f6f76a17e6 | ||
|
|
55a3b69a8e | ||
|
|
22bd108775 | ||
|
|
7ff07fd88c | ||
|
|
2967bec11c | ||
|
|
6357a5c87e | ||
|
|
7e827833bf | ||
|
|
d1507e73fe | ||
|
|
45f1000dea | ||
|
|
04f0996327 | ||
|
|
d8cc0a1f04 | ||
|
|
512c91a969 | ||
|
|
ff8a8ac693 | ||
|
|
908ac5e1b0 | ||
|
|
ea4a7a2368 | ||
|
|
50d5cc2f6a | ||
|
|
5386099559 | ||
|
|
495a9d6d92 | ||
|
|
db58aaff3a | ||
|
|
817218f1cf | ||
|
|
7843de882a | ||
|
|
40d82a2b25 | ||
|
|
a40479d40a | ||
|
|
7cb068ceb2 | ||
|
|
864bf03fee | ||
|
|
9a41db612a | ||
|
|
4781736f99 | ||
|
|
ced79cf4e3 | ||
|
|
33e22713a0 | ||
|
|
799d2bf0db | ||
|
|
741eaf91c2 | ||
|
|
79b4821499 | ||
|
|
b1ece177b7 | ||
|
|
f9f6eb52dd |
3
.github/workflows/release.yml
vendored
@@ -224,5 +224,4 @@ jobs:
|
||||
run: |-
|
||||
gh issue create \
|
||||
--title "Release Failed for ${RELEASE_TAG} on $(date +'%Y-%m-%d')" \
|
||||
--body "The release workflow failed. See the full run for details: ${DETAILS_URL}" \
|
||||
--label "kind/bug,release-failure"
|
||||
--body "The release workflow failed. See the full run for details: ${DETAILS_URL}"
|
||||
|
||||
11
.vscode/launch.json
vendored
@@ -73,7 +73,16 @@
|
||||
"request": "launch",
|
||||
"name": "Launch CLI Non-Interactive",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "start", "--", "-p", "${input:prompt}", "-y"],
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"start",
|
||||
"--",
|
||||
"-p",
|
||||
"${input:prompt}",
|
||||
"-y",
|
||||
"--output-format",
|
||||
"stream-json"
|
||||
],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
Qwen Code is a powerful command-line AI workflow tool adapted from [**Gemini CLI**](https://github.com/google-gemini/gemini-cli) ([details](./README.gemini.md)), specifically optimized for [Qwen3-Coder](https://github.com/QwenLM/Qwen3-Coder) models. It enhances your development workflow with advanced code understanding, automated tasks, and intelligent assistance.
|
||||
Qwen Code is a powerful command-line AI workflow tool adapted from [**Gemini CLI**](https://github.com/google-gemini/gemini-cli), specifically optimized for [Qwen3-Coder](https://github.com/QwenLM/Qwen3-Coder) models. It enhances your development workflow with advanced code understanding, automated tasks, and intelligent assistance.
|
||||
|
||||
## 💡 Free Options Available
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
export default {
|
||||
index: 'Welcome to Qwen Code',
|
||||
cli: 'CLI',
|
||||
core: 'Core',
|
||||
tools: 'Tools',
|
||||
features: 'Features',
|
||||
'ide-integration': 'IDE Integration',
|
||||
development: 'Development',
|
||||
support: 'Support',
|
||||
index: {
|
||||
type: 'page',
|
||||
display: 'hidden',
|
||||
},
|
||||
users: {
|
||||
type: 'page',
|
||||
title: 'User Guide',
|
||||
},
|
||||
developers: {
|
||||
type: 'page',
|
||||
title: 'Developer Guide',
|
||||
},
|
||||
};
|
||||
|
||||
23
docs/developers/_meta.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
'Contribute to Qwen Code': {
|
||||
title: 'Contribute to Qwen Code',
|
||||
type: 'separator',
|
||||
},
|
||||
architecture: 'Architecture',
|
||||
contributing: 'Contributing Guide',
|
||||
roadmap: 'Roadmap',
|
||||
'Qwen Code SDK': {
|
||||
title: 'Qwen Code SDK',
|
||||
type: 'separator',
|
||||
},
|
||||
'Dive Into Qwen Code': {
|
||||
title: 'Dive Into Qwen Code',
|
||||
type: 'separator',
|
||||
},
|
||||
cli: {
|
||||
display: 'hidden',
|
||||
},
|
||||
core: 'Core',
|
||||
tools: 'Tools',
|
||||
// development: 'Development',
|
||||
};
|
||||
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 350 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 381 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
@@ -5,12 +5,7 @@ export default {
|
||||
commands: 'Commands',
|
||||
configuration: 'Configuration',
|
||||
'configuration-v1': 'Configuration (v1)',
|
||||
themes: 'Themes',
|
||||
tutorials: 'Tutorials',
|
||||
'keyboard-shortcuts': 'Keyboard Shortcuts',
|
||||
'trusted-folders': 'Trusted Folders',
|
||||
'qwen-ignore': 'Ignoring Files',
|
||||
Uninstall: 'Uninstall',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -11,31 +11,8 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- **`/bug`**
|
||||
- **Description:** File an issue about Qwen Code. By default, the issue is filed within the GitHub repository for Qwen Code. The string you enter after `/bug` will become the headline for the bug being filed. The default `/bug` behavior can be modified using the `advanced.bugCommand` setting in your `.qwen/settings.json` files.
|
||||
|
||||
- **`/chat`**
|
||||
- **Description:** Save and resume conversation history for branching conversation state interactively, or resuming a previous state from a later session.
|
||||
- **Sub-commands:**
|
||||
- **`save`**
|
||||
- **Description:** Saves the current conversation history. You must add a `<tag>` for identifying the conversation state.
|
||||
- **Usage:** `/chat save <tag>`
|
||||
- **Details on Checkpoint Location:** The default locations for saved chat checkpoints are:
|
||||
- Linux/macOS: `~/.qwen/tmp/<project_hash>/`
|
||||
- Windows: `C:\Users\<YourUsername>\.qwen\tmp\<project_hash>\`
|
||||
- When you run `/chat list`, the CLI only scans these specific directories to find available checkpoints.
|
||||
- **Note:** These checkpoints are for manually saving and resuming conversation states. For automatic checkpoints created before file modifications, see the [Checkpointing documentation](../checkpointing.md).
|
||||
- **`resume`**
|
||||
- **Description:** Resumes a conversation from a previous save.
|
||||
- **Usage:** `/chat resume <tag>`
|
||||
- **`list`**
|
||||
- **Description:** Lists available tags for chat state resumption.
|
||||
- **`delete`**
|
||||
- **Description:** Deletes a saved conversation checkpoint.
|
||||
- **Usage:** `/chat delete <tag>`
|
||||
- **`share`**
|
||||
- **Description** Writes the current conversation to a provided Markdown or JSON file.
|
||||
- **Usage** `/chat share file.md` or `/chat share file.json`. If no filename is provided, then the CLI will generate one.
|
||||
|
||||
- **`/clear`**
|
||||
- **Description:** Clear the terminal screen, including the visible session history and scrollback within the CLI. The underlying session data (for history recall) might be preserved depending on the exact implementation, but the visual display is cleared.
|
||||
- **`/clear`** (aliases: `reset`, `new`)
|
||||
- **Description:** Clear conversation history and free up context by starting a fresh session. Also clears the terminal output and scrollback within the CLI.
|
||||
- **Keyboard shortcut:** Press **Ctrl+L** at any time to perform a clear action.
|
||||
|
||||
- **`/summary`**
|
||||
@@ -195,6 +172,16 @@ Slash commands provide meta-level control over the CLI itself.
|
||||
- **`/init`**
|
||||
- **Description:** Analyzes the current directory and creates a `QWEN.md` context file by default (or the filename specified by `contextFileName`). If a non-empty file already exists, no changes are made. The command seeds an empty file and prompts the model to populate it with project-specific instructions.
|
||||
|
||||
- [**`/language`**](./language.md)
|
||||
- **Description:** View or change the language setting for both UI and LLM output.
|
||||
- **Sub-commands:**
|
||||
- **`ui`**: Set the UI language (zh-CN or en-US)
|
||||
- **`output`**: Set the LLM output language
|
||||
- **Usage:** `/language [ui|output] [language]`
|
||||
- **Examples:**
|
||||
- `/language ui zh-CN` (set UI language to Simplified Chinese)
|
||||
- `/language output English` (set LLM output language to English)
|
||||
|
||||
### Custom Commands
|
||||
|
||||
For a quick start, see the [example](#example-a-pure-function-refactoring-command) below.
|
||||
@@ -309,7 +309,8 @@ If you are experiencing performance issues with file searching (e.g., with `@` c
|
||||
```
|
||||
|
||||
- **`tavilyApiKey`** (string):
|
||||
- **Description:** API key for Tavily web search service. Required to enable the `web_search` tool functionality. If not configured, the web search tool will be disabled and skipped.
|
||||
- **Description:** API key for Tavily web search service. Used to enable the `web_search` tool functionality.
|
||||
- **Note:** This is a legacy configuration format. For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers using the new `webSearch` configuration format.
|
||||
- **Default:** `undefined` (web search disabled)
|
||||
- **Example:** `"tavilyApiKey": "tvly-your-api-key-here"`
|
||||
- **`chatCompression`** (object):
|
||||
@@ -465,8 +466,8 @@ The CLI automatically loads environment variables from an `.env` file. The loadi
|
||||
- This is useful for development and testing.
|
||||
- **`TAVILY_API_KEY`**:
|
||||
- Your API key for the Tavily web search service.
|
||||
- Required to enable the `web_search` tool functionality.
|
||||
- If not configured, the web search tool will be disabled and skipped.
|
||||
- Used to enable the `web_search` tool functionality.
|
||||
- **Note:** For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers to enable web search.
|
||||
- Example: `export TAVILY_API_KEY="tvly-your-api-key-here"`
|
||||
|
||||
## Command-Line Arguments
|
||||
@@ -540,6 +541,9 @@ Arguments passed directly when running the CLI can override other configurations
|
||||
- Displays the version of the CLI.
|
||||
- **`--openai-logging`**:
|
||||
- Enables logging of OpenAI API calls for debugging and analysis. This flag overrides the `enableOpenAILogging` setting in `settings.json`.
|
||||
- **`--openai-logging-dir <directory>`**:
|
||||
- Sets a custom directory path for OpenAI API logs. This flag overrides the `openAILoggingDir` setting in `settings.json`. Supports absolute paths, relative paths, and `~` expansion.
|
||||
- **Example:** `qwen --openai-logging-dir "~/qwen-logs" --openai-logging`
|
||||
- **`--tavily-api-key <api_key>`**:
|
||||
- Sets the Tavily API key for web search functionality for this session.
|
||||
- Example: `qwen --tavily-api-key tvly-your-api-key-here`
|
||||
@@ -667,4 +671,4 @@ Note: When usage statistics are enabled, events are sent to an Alibaba Cloud RUM
|
||||
- **Category:** UI
|
||||
- **Requires Restart:** No
|
||||
- **Example:** `"enableWelcomeBack": false`
|
||||
- **Details:** When enabled, Qwen Code will automatically detect if you're returning to a project with a previously generated project summary (`.qwen/PROJECT_SUMMARY.md`) and show a dialog allowing you to continue your previous conversation or start fresh. This feature integrates with the `/chat summary` command and quit confirmation dialog. See the [Welcome Back documentation](./welcome-back.md) for more details.
|
||||
- **Details:** When enabled, Qwen Code will automatically detect if you're returning to a project with a previously generated project summary (`.qwen/PROJECT_SUMMARY.md`) and show a dialog allowing you to continue your previous conversation or start fresh. This feature integrates with the `/summary` command and quit confirmation dialog. See the [Welcome Back documentation](./welcome-back.md) for more details.
|
||||
@@ -160,9 +160,30 @@ Settings are organized into categories. All settings should be placed within the
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`model.chatCompression.contextPercentageThreshold`** (number):
|
||||
- **Description:** Sets the threshold for chat history compression as a percentage of the model's total token limit. This is a value between 0 and 1 that applies to both automatic compression and the manual `/compress` command. For example, a value of `0.6` will trigger compression when the chat history exceeds 60% of the token limit.
|
||||
- **Description:** Sets the threshold for chat history compression as a percentage of the model's total token limit. This is a value between 0 and 1 that applies to both automatic compression and the manual `/compress` command. For example, a value of `0.6` will trigger compression when the chat history exceeds 60% of the token limit. Use `0` to disable compression entirely.
|
||||
- **Default:** `0.7`
|
||||
|
||||
- **`model.generationConfig`** (object):
|
||||
- **Description:** Advanced overrides passed to the underlying content generator. Supports request controls such as `timeout`, `maxRetries`, and `disableCacheControl`, along with fine-tuning knobs under `samplingParams` (for example `temperature`, `top_p`, `max_tokens`). Leave unset to rely on provider defaults.
|
||||
- **Default:** `undefined`
|
||||
- **Example:**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": {
|
||||
"generationConfig": {
|
||||
"timeout": 60000,
|
||||
"disableCacheControl": false,
|
||||
"samplingParams": {
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"max_tokens": 1024
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`model.skipNextSpeakerCheck`** (boolean):
|
||||
- **Description:** Skip the next speaker check.
|
||||
- **Default:** `false`
|
||||
@@ -171,6 +192,22 @@ Settings are organized into categories. All settings should be placed within the
|
||||
- **Description:** Disables loop detection checks. Loop detection prevents infinite loops in AI responses but can generate false positives that interrupt legitimate workflows. Enable this option if you experience frequent false positive loop detection interruptions.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`model.skipStartupContext`** (boolean):
|
||||
- **Description:** Skips sending the startup workspace context (environment summary and acknowledgement) at the beginning of each session. Enable this if you prefer to provide context manually or want to save tokens on startup.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`model.enableOpenAILogging`** (boolean):
|
||||
- **Description:** Enables logging of OpenAI API calls for debugging and analysis. When enabled, API requests and responses are logged to JSON files.
|
||||
- **Default:** `false`
|
||||
|
||||
- **`model.openAILoggingDir`** (string):
|
||||
- **Description:** Custom directory path for OpenAI API logs. If not specified, defaults to `logs/openai` in the current working directory. Supports absolute paths, relative paths (resolved from current working directory), and `~` expansion (home directory).
|
||||
- **Default:** `undefined`
|
||||
- **Examples:**
|
||||
- `"~/qwen-logs"` - Logs to `~/qwen-logs` directory
|
||||
- `"./custom-logs"` - Logs to `./custom-logs` relative to current directory
|
||||
- `"/tmp/openai-logs"` - Logs to absolute path `/tmp/openai-logs`
|
||||
|
||||
#### `context`
|
||||
|
||||
- **`context.fileName`** (string or array of strings):
|
||||
@@ -246,6 +283,29 @@ Settings are organized into categories. All settings should be placed within the
|
||||
- It must return function output as JSON on `stdout`, analogous to [`functionResponse.response.content`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functionresponse).
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`tools.useRipgrep`** (boolean):
|
||||
- **Description:** Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`tools.useBuiltinRipgrep`** (boolean):
|
||||
- **Description:** Use the bundled ripgrep binary. When set to `false`, the system-level `rg` command will be used instead. This setting is only effective when `tools.useRipgrep` is `true`.
|
||||
- **Default:** `true`
|
||||
|
||||
- **`tools.enableToolOutputTruncation`** (boolean):
|
||||
- **Description:** Enable truncation of large tool outputs.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.truncateToolOutputThreshold`** (number):
|
||||
- **Description:** Truncate tool output if it is larger than this many characters. Applies to Shell, Grep, Glob, ReadFile and ReadManyFiles tools.
|
||||
- **Default:** `25000`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.truncateToolOutputLines`** (number):
|
||||
- **Description:** Maximum lines or entries kept when truncating tool output. Applies to Shell, Grep, Glob, ReadFile and ReadManyFiles tools.
|
||||
- **Default:** `1000`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
#### `mcp`
|
||||
|
||||
- **`mcp.serverCommand`** (string):
|
||||
@@ -297,7 +357,8 @@ Settings are organized into categories. All settings should be placed within the
|
||||
- **Default:** `undefined`
|
||||
|
||||
- **`advanced.tavilyApiKey`** (string):
|
||||
- **Description:** API key for Tavily web search service. Required to enable the `web_search` tool functionality. If not configured, the web search tool will be disabled and skipped.
|
||||
- **Description:** API key for Tavily web search service. Used to enable the `web_search` tool functionality.
|
||||
- **Note:** This is a legacy configuration format. For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers using the new `webSearch` configuration format.
|
||||
- **Default:** `undefined`
|
||||
|
||||
#### `mcpServers`
|
||||
@@ -378,6 +439,8 @@ Here is an example of a `settings.json` file with the nested structure, new as o
|
||||
"model": {
|
||||
"name": "qwen3-coder-plus",
|
||||
"maxSessionTurns": 10,
|
||||
"enableOpenAILogging": false,
|
||||
"openAILoggingDir": "~/qwen-logs",
|
||||
"summarizeToolOutput": {
|
||||
"run_shell_command": {
|
||||
"tokenBudget": 100
|
||||
@@ -466,8 +529,8 @@ The CLI automatically loads environment variables from an `.env` file. The loadi
|
||||
- Set to a string to customize the title of the CLI.
|
||||
- **`TAVILY_API_KEY`**:
|
||||
- Your API key for the Tavily web search service.
|
||||
- Required to enable the `web_search` tool functionality.
|
||||
- If not configured, the web search tool will be disabled and skipped.
|
||||
- Used to enable the `web_search` tool functionality.
|
||||
- **Note:** For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers to enable web search.
|
||||
- Example: `export TAVILY_API_KEY="tvly-your-api-key-here"`
|
||||
|
||||
## Command-Line Arguments
|
||||
@@ -485,12 +548,31 @@ Arguments passed directly when running the CLI can override other configurations
|
||||
- The prompt is processed within the interactive session, not before it.
|
||||
- Cannot be used when piping input from stdin.
|
||||
- Example: `qwen -i "explain this code"`
|
||||
- **`--output-format <format>`**:
|
||||
- **`--continue`**:
|
||||
- Resume the most recent session for the current project (current working directory).
|
||||
- Works in interactive and headless modes (e.g., `qwen --continue -p "Keep going"`).
|
||||
- **`--resume [sessionId]`**:
|
||||
- Resume a specific session for the current project. When called without an ID, an interactive picker lists only this project's sessions with prompt preview, timestamps, message count, and optional git branch.
|
||||
- If an ID is provided and not found for this project, the CLI exits with an error.
|
||||
- **`--output-format <format>`** (**`-o <format>`**):
|
||||
- **Description:** Specifies the format of the CLI output for non-interactive mode.
|
||||
- **Values:**
|
||||
- `text`: (Default) The standard human-readable output.
|
||||
- `json`: A machine-readable JSON output.
|
||||
- **Note:** For structured output and scripting, use the `--output-format json` flag.
|
||||
- `json`: A machine-readable JSON output emitted at the end of execution.
|
||||
- `stream-json`: Streaming JSON messages emitted as they occur during execution.
|
||||
- **Note:** For structured output and scripting, use the `--output-format json` or `--output-format stream-json` flag. See [Headless Mode](../features/headless.md) for detailed information.
|
||||
- **`--input-format <format>`**:
|
||||
- **Description:** Specifies the format consumed from standard input.
|
||||
- **Values:**
|
||||
- `text`: (Default) Standard text input from stdin or command-line arguments.
|
||||
- `stream-json`: JSON message protocol via stdin for bidirectional communication.
|
||||
- **Requirement:** `--input-format stream-json` requires `--output-format stream-json` to be set.
|
||||
- **Note:** When using `stream-json`, stdin is reserved for protocol messages. See [Headless Mode](../features/headless.md) for detailed information.
|
||||
- **`--include-partial-messages`**:
|
||||
- **Description:** Include partial assistant messages when using `stream-json` output format. When enabled, emits stream events (message_start, content_block_delta, etc.) as they occur during streaming.
|
||||
- **Default:** `false`
|
||||
- **Requirement:** Requires `--output-format stream-json` to be set.
|
||||
- **Note:** See [Headless Mode](../features/headless.md) for detailed information about stream events.
|
||||
- **`--sandbox`** (**`-s`**):
|
||||
- Enables sandbox mode for this session.
|
||||
- **`--sandbox-image`**:
|
||||
@@ -515,7 +597,7 @@ Arguments passed directly when running the CLI can override other configurations
|
||||
- Example: `qwen --approval-mode auto-edit`
|
||||
- **`--allowed-tools <tool1,tool2,...>`**:
|
||||
- A comma-separated list of tool names that will bypass the confirmation dialog.
|
||||
- Example: `qwen --allowed-tools "ShellTool(git status)"`
|
||||
- Example: `qwen --allowed-tools "Shell(git status)"`
|
||||
- **`--telemetry`**:
|
||||
- Enables [telemetry](../telemetry.md).
|
||||
- **`--telemetry-target`**:
|
||||
@@ -548,6 +630,9 @@ Arguments passed directly when running the CLI can override other configurations
|
||||
- Displays the version of the CLI.
|
||||
- **`--openai-logging`**:
|
||||
- Enables logging of OpenAI API calls for debugging and analysis. This flag overrides the `enableOpenAILogging` setting in `settings.json`.
|
||||
- **`--openai-logging-dir <directory>`**:
|
||||
- Sets a custom directory path for OpenAI API logs. This flag overrides the `openAILoggingDir` setting in `settings.json`. Supports absolute paths, relative paths, and `~` expansion.
|
||||
- **Example:** `qwen --openai-logging-dir "~/qwen-logs" --openai-logging`
|
||||
- **`--tavily-api-key <api_key>`**:
|
||||
- Sets the Tavily API key for web search functionality for this session.
|
||||
- Example: `qwen --tavily-api-key tvly-your-api-key-here`
|
||||
71
docs/developers/cli/language.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Language Command
|
||||
|
||||
The `/language` command allows you to customize the language settings for both the Qwen Code user interface (UI) and the language model's output. This command supports two distinct functionalities:
|
||||
|
||||
1. Setting the UI language for the Qwen Code interface
|
||||
2. Setting the output language for the language model (LLM)
|
||||
|
||||
## UI Language Settings
|
||||
|
||||
To change the UI language of Qwen Code, use the `ui` subcommand:
|
||||
|
||||
```
|
||||
/language ui [zh-CN|en-US]
|
||||
```
|
||||
|
||||
### Available UI Languages
|
||||
|
||||
- **zh-CN**: Simplified Chinese (简体中文)
|
||||
- **en-US**: English
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
/language ui zh-CN # Set UI language to Simplified Chinese
|
||||
/language ui en-US # Set UI language to English
|
||||
```
|
||||
|
||||
### UI Language Subcommands
|
||||
|
||||
You can also use direct subcommands for convenience:
|
||||
|
||||
- `/language ui zh-CN` or `/language ui zh` or `/language ui 中文`
|
||||
- `/language ui en-US` or `/language ui en` or `/language ui english`
|
||||
|
||||
## LLM Output Language Settings
|
||||
|
||||
To set the language for the language model's responses, use the `output` subcommand:
|
||||
|
||||
```
|
||||
/language output <language>
|
||||
```
|
||||
|
||||
This command generates a language rule file that instructs the LLM to respond in the specified language. The rule file is saved to `~/.qwen/output-language.md`.
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
/language output 中文 # Set LLM output language to Chinese
|
||||
/language output English # Set LLM output language to English
|
||||
/language output 日本語 # Set LLM output language to Japanese
|
||||
```
|
||||
|
||||
## Viewing Current Settings
|
||||
|
||||
When used without arguments, the `/language` command displays the current language settings:
|
||||
|
||||
```
|
||||
/language
|
||||
```
|
||||
|
||||
This will show:
|
||||
|
||||
- Current UI language
|
||||
- Current LLM output language (if set)
|
||||
- Available subcommands
|
||||
|
||||
## Notes
|
||||
|
||||
- UI language changes take effect immediately and reload all command descriptions
|
||||
- LLM output language settings are persisted in a rule file that is automatically included in the model's context
|
||||
- To request additional UI language packs, please open an issue on GitHub
|
||||
0
docs/developers/contributing.md
Normal file
@@ -21,7 +21,7 @@ The Qwen Code core (`packages/core`) features a robust system for defining, regi
|
||||
- **Returning Rich Content:** Tools are not limited to returning simple text. The `llmContent` can be a `PartListUnion`, which is an array that can contain a mix of `Part` objects (for images, audio, etc.) and `string`s. This allows a single tool execution to return multiple pieces of rich content.
|
||||
|
||||
- **Tool Registry (`tool-registry.ts`):** A class (`ToolRegistry`) responsible for:
|
||||
- **Registering Tools:** Holding a collection of all available built-in tools (e.g., `ReadFileTool`, `ShellTool`).
|
||||
- **Registering Tools:** Holding a collection of all available built-in tools (e.g., `ListFiles`, `ReadFile`).
|
||||
- **Discovering Tools:** It can also discover tools dynamically:
|
||||
- **Command-based Discovery:** If `tools.toolDiscoveryCommand` is configured in settings, this command is executed. It's expected to output JSON describing custom tools, which are then registered as `DiscoveredTool` instances.
|
||||
- **MCP-based Discovery:** If `mcp.mcpServerCommand` is configured, the registry can connect to a Model Context Protocol (MCP) server to list and register tools (`DiscoveredMCPTool`).
|
||||
@@ -33,20 +33,24 @@ The Qwen Code core (`packages/core`) features a robust system for defining, regi
|
||||
The core comes with a suite of pre-defined tools, typically found in `packages/core/src/tools/`. These include:
|
||||
|
||||
- **File System Tools:**
|
||||
- `LSTool` (`ls.ts`): Lists directory contents.
|
||||
- `ReadFileTool` (`read-file.ts`): Reads the content of a single file. It takes an `absolute_path` parameter, which must be an absolute path.
|
||||
- `WriteFileTool` (`write-file.ts`): Writes content to a file.
|
||||
- `GrepTool` (`grep.ts`): Searches for patterns in files.
|
||||
- `GlobTool` (`glob.ts`): Finds files matching glob patterns.
|
||||
- `EditTool` (`edit.ts`): Performs in-place modifications to files (often requiring confirmation).
|
||||
- `ReadManyFilesTool` (`read-many-files.ts`): Reads and concatenates content from multiple files or glob patterns (used by the `@` command in CLI).
|
||||
- `ListFiles` (`ls.ts`): Lists directory contents.
|
||||
- `ReadFile` (`read-file.ts`): Reads the content of a single file. It takes an `absolute_path` parameter, which must be an absolute path.
|
||||
- `WriteFile` (`write-file.ts`): Writes content to a file.
|
||||
- `ReadManyFiles` (`read-many-files.ts`): Reads and concatenates content from multiple files or glob patterns (used by the `@` command in CLI).
|
||||
- `Grep` (`grep.ts`): Searches for patterns in files.
|
||||
- `Glob` (`glob.ts`): Finds files matching glob patterns.
|
||||
- `Edit` (`edit.ts`): Performs in-place modifications to files (often requiring confirmation).
|
||||
- **Execution Tools:**
|
||||
- `ShellTool` (`shell.ts`): Executes arbitrary shell commands (requires careful sandboxing and user confirmation).
|
||||
- `Shell` (`shell.ts`): Executes arbitrary shell commands (requires careful sandboxing and user confirmation).
|
||||
- **Web Tools:**
|
||||
- `WebFetchTool` (`web-fetch.ts`): Fetches content from a URL.
|
||||
- `WebSearchTool` (`web-search.ts`): Performs a web search.
|
||||
- `WebFetch` (`web-fetch.ts`): Fetches content from a URL.
|
||||
- `WebSearch` (`web-search.ts`): Performs a web search.
|
||||
- **Memory Tools:**
|
||||
- `MemoryTool` (`memoryTool.ts`): Interacts with the AI's memory.
|
||||
- `SaveMemory` (`memoryTool.ts`): Interacts with the AI's memory.
|
||||
- **Planning Tools:**
|
||||
- `Task` (`task.ts`): Delegates tasks to specialized subagents.
|
||||
- `TodoWrite` (`todoWrite.ts`): Creates and manages a structured task list.
|
||||
- `ExitPlanMode` (`exitPlanMode.ts`): Exits plan mode and returns to normal operation.
|
||||
|
||||
Each of these tools extends `BaseTool` and implements the required methods for its specific functionality.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
export default {
|
||||
architecture: 'Architecture',
|
||||
npm: 'NPM',
|
||||
deployment: 'Deployment',
|
||||
telemetry: 'Telemetry',
|
||||
'integration-tests': 'Integration Tests',
|
||||
'issue-and-pr-automation': 'Issue and PR Automation',
|
||||
1
docs/developers/roadmap.md
Normal file
@@ -0,0 +1 @@
|
||||
# Qwen Code RoadMap
|
||||
@@ -4,12 +4,12 @@ Qwen Code provides a comprehensive suite of tools for interacting with the local
|
||||
|
||||
**Note:** All file system tools operate within a `rootDirectory` (usually the current working directory where you launched the CLI) for security. Paths that you provide to these tools are generally expected to be absolute or are resolved relative to this root directory.
|
||||
|
||||
## 1. `list_directory` (ReadFolder)
|
||||
## 1. `list_directory` (ListFiles)
|
||||
|
||||
`list_directory` lists the names of files and subdirectories directly within a specified directory path. It can optionally ignore entries matching provided glob patterns.
|
||||
|
||||
- **Tool name:** `list_directory`
|
||||
- **Display name:** ReadFolder
|
||||
- **Display name:** ListFiles
|
||||
- **File:** `ls.ts`
|
||||
- **Parameters:**
|
||||
- `path` (string, required): The absolute path to the directory to list.
|
||||
@@ -59,86 +59,80 @@ Qwen Code provides a comprehensive suite of tools for interacting with the local
|
||||
- **Output (`llmContent`):** A success message, e.g., `Successfully overwrote file: /path/to/your/file.txt` or `Successfully created and wrote to new file: /path/to/new/file.txt`.
|
||||
- **Confirmation:** Yes. Shows a diff of changes and asks for user approval before writing.
|
||||
|
||||
## 4. `glob` (FindFiles)
|
||||
## 4. `glob` (Glob)
|
||||
|
||||
`glob` finds files matching specific glob patterns (e.g., `src/**/*.ts`, `*.md`), returning absolute paths sorted by modification time (newest first).
|
||||
|
||||
- **Tool name:** `glob`
|
||||
- **Display name:** FindFiles
|
||||
- **Display name:** Glob
|
||||
- **File:** `glob.ts`
|
||||
- **Parameters:**
|
||||
- `pattern` (string, required): The glob pattern to match against (e.g., `"*.py"`, `"src/**/*.js"`).
|
||||
- `path` (string, optional): The absolute path to the directory to search within. If omitted, searches the tool's root directory.
|
||||
- `case_sensitive` (boolean, optional): Whether the search should be case-sensitive. Defaults to `false`.
|
||||
- `respect_git_ignore` (boolean, optional): Whether to respect .gitignore patterns when finding files. Defaults to `true`.
|
||||
- `path` (string, optional): The directory to search in. If not specified, the current working directory will be used.
|
||||
- **Behavior:**
|
||||
- Searches for files matching the glob pattern within the specified directory.
|
||||
- Returns a list of absolute paths, sorted with the most recently modified files first.
|
||||
- Ignores common nuisance directories like `node_modules` and `.git` by default.
|
||||
- **Output (`llmContent`):** A message like: `Found 5 file(s) matching "*.ts" within src, sorted by modification time (newest first):\nsrc/file1.ts\nsrc/subdir/file2.ts...`
|
||||
- Respects .gitignore and .qwenignore patterns by default.
|
||||
- Limits results to 100 files to prevent context overflow.
|
||||
- **Output (`llmContent`):** A message like: `Found 5 file(s) matching "*.ts" within /path/to/search/dir, sorted by modification time (newest first):\n---\n/path/to/file1.ts\n/path/to/subdir/file2.ts\n---\n[95 files truncated] ...`
|
||||
- **Confirmation:** No.
|
||||
|
||||
## 5. `search_file_content` (SearchText)
|
||||
## 5. `grep_search` (Grep)
|
||||
|
||||
`search_file_content` searches for a regular expression pattern within the content of files in a specified directory. Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.
|
||||
`grep_search` searches for a regular expression pattern within the content of files in a specified directory. Can filter files by a glob pattern. Returns the lines containing matches, along with their file paths and line numbers.
|
||||
|
||||
- **Tool name:** `search_file_content`
|
||||
- **Display name:** SearchText
|
||||
- **File:** `grep.ts`
|
||||
- **Tool name:** `grep_search`
|
||||
- **Display name:** Grep
|
||||
- **File:** `ripGrep.ts` (with `grep.ts` as fallback)
|
||||
- **Parameters:**
|
||||
- `pattern` (string, required): The regular expression (regex) to search for (e.g., `"function\s+myFunction"`).
|
||||
- `path` (string, optional): The absolute path to the directory to search within. Defaults to the current working directory.
|
||||
- `include` (string, optional): A glob pattern to filter which files are searched (e.g., `"*.js"`, `"src/**/*.{ts,tsx}"`). If omitted, searches most files (respecting common ignores).
|
||||
- `maxResults` (number, optional): Maximum number of matches to return to prevent context overflow (default: 20, max: 100). Use lower values for broad searches, higher for specific searches.
|
||||
- `pattern` (string, required): The regular expression pattern to search for in file contents (e.g., `"function\\s+myFunction"`, `"log.*Error"`).
|
||||
- `path` (string, optional): File or directory to search in. Defaults to current working directory.
|
||||
- `glob` (string, optional): Glob pattern to filter files (e.g. `"*.js"`, `"src/**/*.{ts,tsx}"`).
|
||||
- `limit` (number, optional): Limit output to first N matching lines. Optional - shows all matches if not specified.
|
||||
- **Behavior:**
|
||||
- Uses `git grep` if available in a Git repository for speed; otherwise, falls back to system `grep` or a JavaScript-based search.
|
||||
- Returns a list of matching lines, each prefixed with its file path (relative to the search directory) and line number.
|
||||
- Limits results to a maximum of 20 matches by default to prevent context overflow. When results are truncated, shows a clear warning with guidance on refining searches.
|
||||
- Uses ripgrep for fast search when available; otherwise falls back to a JavaScript-based search implementation.
|
||||
- Returns matching lines with file paths and line numbers.
|
||||
- Case-insensitive by default.
|
||||
- Respects .gitignore and .qwenignore patterns.
|
||||
- Limits output to prevent context overflow.
|
||||
- **Output (`llmContent`):** A formatted string of matches, e.g.:
|
||||
|
||||
```
|
||||
Found 3 matches for pattern "myFunction" in path "." (filter: "*.ts"):
|
||||
---
|
||||
File: src/utils.ts
|
||||
L15: export function myFunction() {
|
||||
L22: myFunction.call();
|
||||
---
|
||||
File: src/index.ts
|
||||
L5: import { myFunction } from './utils';
|
||||
src/utils.ts:15:export function myFunction() {
|
||||
src/utils.ts:22: myFunction.call();
|
||||
src/index.ts:5:import { myFunction } from './utils';
|
||||
---
|
||||
|
||||
WARNING: Results truncated to prevent context overflow. To see more results:
|
||||
- Use a more specific pattern to reduce matches
|
||||
- Add file filters with the 'include' parameter (e.g., "*.js", "src/**")
|
||||
- Specify a narrower 'path' to search in a subdirectory
|
||||
- Increase 'maxResults' parameter if you need more matches (current: 20)
|
||||
[0 lines truncated] ...
|
||||
```
|
||||
|
||||
- **Confirmation:** No.
|
||||
|
||||
### `search_file_content` examples
|
||||
### `grep_search` examples
|
||||
|
||||
Search for a pattern with default result limiting:
|
||||
|
||||
```
|
||||
search_file_content(pattern="function\s+myFunction", path="src")
|
||||
grep_search(pattern="function\\s+myFunction", path="src")
|
||||
```
|
||||
|
||||
Search for a pattern with custom result limiting:
|
||||
|
||||
```
|
||||
search_file_content(pattern="function", path="src", maxResults=50)
|
||||
grep_search(pattern="function", path="src", limit=50)
|
||||
```
|
||||
|
||||
Search for a pattern with file filtering and custom result limiting:
|
||||
|
||||
```
|
||||
search_file_content(pattern="function", include="*.js", maxResults=10)
|
||||
grep_search(pattern="function", glob="*.js", limit=10)
|
||||
```
|
||||
|
||||
## 6. `edit` (Edit)
|
||||
|
||||
`edit` replaces text within a file. By default, replaces a single occurrence, but can replace multiple occurrences when `expected_replacements` is specified. This tool is designed for precise, targeted changes and requires significant context around the `old_string` to ensure it modifies the correct location.
|
||||
`edit` replaces text within a file. By default it requires `old_string` to match a single unique location; set `replace_all` to `true` when you intentionally want to change every occurrence. This tool is designed for precise, targeted changes and requires significant context around the `old_string` to ensure it modifies the correct location.
|
||||
|
||||
- **Tool name:** `edit`
|
||||
- **Display name:** Edit
|
||||
@@ -150,12 +144,12 @@ search_file_content(pattern="function", include="*.js", maxResults=10)
|
||||
**CRITICAL:** This string must uniquely identify the single instance to change. It should include at least 3 lines of context _before_ and _after_ the target text, matching whitespace and indentation precisely. If `old_string` is empty, the tool attempts to create a new file at `file_path` with `new_string` as content.
|
||||
|
||||
- `new_string` (string, required): The exact literal text to replace `old_string` with.
|
||||
- `expected_replacements` (number, optional): The number of occurrences to replace. Defaults to `1`.
|
||||
- `replace_all` (boolean, optional): Replace all occurrences of `old_string`. Defaults to `false`.
|
||||
|
||||
- **Behavior:**
|
||||
- If `old_string` is empty and `file_path` does not exist, creates a new file with `new_string` as content.
|
||||
- If `old_string` is provided, it reads the `file_path` and attempts to find exactly one occurrence of `old_string`.
|
||||
- If one occurrence is found, it replaces it with `new_string`.
|
||||
- If `old_string` is provided, it reads the `file_path` and attempts to find exactly one occurrence unless `replace_all` is true.
|
||||
- If the match is unique (or `replace_all` is true), it replaces the text with `new_string`.
|
||||
- **Enhanced Reliability (Multi-Stage Edit Correction):** To significantly improve the success rate of edits, especially when the model-provided `old_string` might not be perfectly precise, the tool incorporates a multi-stage edit correction mechanism.
|
||||
- If the initial `old_string` isn't found or matches multiple locations, the tool can leverage the Qwen model to iteratively refine `old_string` (and potentially `new_string`).
|
||||
- This self-correction process attempts to identify the unique segment the model intended to modify, making the `edit` operation more robust even with slightly imperfect initial context.
|
||||
@@ -164,10 +158,10 @@ search_file_content(pattern="function", include="*.js", maxResults=10)
|
||||
- `old_string` is not empty, but the `file_path` does not exist.
|
||||
- `old_string` is empty, but the `file_path` already exists.
|
||||
- `old_string` is not found in the file after attempts to correct it.
|
||||
- `old_string` is found multiple times, and the self-correction mechanism cannot resolve it to a single, unambiguous match.
|
||||
- `old_string` is found multiple times, `replace_all` is false, and the self-correction mechanism cannot resolve it to a single, unambiguous match.
|
||||
- **Output (`llmContent`):**
|
||||
- On success: `Successfully modified file: /path/to/file.txt (1 replacements).` or `Created new file: /path/to/new_file.txt with provided content.`
|
||||
- On failure: An error message explaining the reason (e.g., `Failed to edit, 0 occurrences found...`, `Failed to edit, expected 1 occurrences but found 2...`).
|
||||
- On failure: An error message explaining the reason (e.g., `Failed to edit, 0 occurrences found...`, `Failed to edit because the text matches multiple locations...`).
|
||||
- **Confirmation:** Yes. Shows a diff of the proposed changes and asks for user approval before writing to the file.
|
||||
|
||||
These file system tools provide a foundation for Qwen Code to understand and interact with your local project context.
|
||||
186
docs/developers/tools/web-search.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# Web Search Tool (`web_search`)
|
||||
|
||||
This document describes the `web_search` tool for performing web searches using multiple providers.
|
||||
|
||||
## Description
|
||||
|
||||
Use `web_search` to perform a web search and get information from the internet. The tool supports multiple search providers and returns a concise answer with source citations when available.
|
||||
|
||||
### Supported Providers
|
||||
|
||||
1. **DashScope** (Official, Free) - Automatically available for Qwen OAuth users (200 requests/minute, 2000 requests/day)
|
||||
2. **Tavily** - High-quality search API with built-in answer generation
|
||||
3. **Google Custom Search** - Google's Custom Search JSON API
|
||||
|
||||
### Arguments
|
||||
|
||||
`web_search` takes two arguments:
|
||||
|
||||
- `query` (string, required): The search query
|
||||
- `provider` (string, optional): Specific provider to use ("dashscope", "tavily", "google")
|
||||
- If not specified, uses the default provider from configuration
|
||||
|
||||
## Configuration
|
||||
|
||||
### Method 1: Settings File (Recommended)
|
||||
|
||||
Add to your `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"webSearch": {
|
||||
"provider": [
|
||||
{ "type": "dashscope" },
|
||||
{ "type": "tavily", "apiKey": "tvly-xxxxx" },
|
||||
{
|
||||
"type": "google",
|
||||
"apiKey": "your-google-api-key",
|
||||
"searchEngineId": "your-search-engine-id"
|
||||
}
|
||||
],
|
||||
"default": "dashscope"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
|
||||
- DashScope doesn't require an API key (official, free service)
|
||||
- **Qwen OAuth users:** DashScope is automatically added to your provider list, even if not explicitly configured
|
||||
- Configure additional providers (Tavily, Google) if you want to use them alongside DashScope
|
||||
- Set `default` to specify which provider to use by default (if not set, priority order: Tavily > Google > DashScope)
|
||||
|
||||
### Method 2: Environment Variables
|
||||
|
||||
Set environment variables in your shell or `.env` file:
|
||||
|
||||
```bash
|
||||
# Tavily
|
||||
export TAVILY_API_KEY="tvly-xxxxx"
|
||||
|
||||
# Google
|
||||
export GOOGLE_API_KEY="your-api-key"
|
||||
export GOOGLE_SEARCH_ENGINE_ID="your-engine-id"
|
||||
```
|
||||
|
||||
### Method 3: Command Line Arguments
|
||||
|
||||
Pass API keys when running Qwen Code:
|
||||
|
||||
```bash
|
||||
# Tavily
|
||||
qwen --tavily-api-key tvly-xxxxx
|
||||
|
||||
# Google
|
||||
qwen --google-api-key your-key --google-search-engine-id your-id
|
||||
|
||||
# Specify default provider
|
||||
qwen --web-search-default tavily
|
||||
```
|
||||
|
||||
### Backward Compatibility (Deprecated)
|
||||
|
||||
⚠️ **DEPRECATED:** The legacy `tavilyApiKey` configuration is still supported for backward compatibility but is deprecated:
|
||||
|
||||
```json
|
||||
{
|
||||
"advanced": {
|
||||
"tavilyApiKey": "tvly-xxxxx" // ⚠️ Deprecated
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** This configuration is deprecated and will be removed in a future version. Please migrate to the new `webSearch` configuration format shown above. The old configuration will automatically configure Tavily as a provider, but we strongly recommend updating your configuration.
|
||||
|
||||
## Disabling Web Search
|
||||
|
||||
If you want to disable the web search functionality, you can exclude the `web_search` tool in your `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"exclude": ["web_search"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** This setting requires a restart of Qwen Code to take effect. Once disabled, the `web_search` tool will not be available to the model, even if web search providers are configured.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic search (using default provider)
|
||||
|
||||
```
|
||||
web_search(query="latest advancements in AI")
|
||||
```
|
||||
|
||||
### Search with specific provider
|
||||
|
||||
```
|
||||
web_search(query="latest advancements in AI", provider="tavily")
|
||||
```
|
||||
|
||||
### Real-world examples
|
||||
|
||||
```
|
||||
web_search(query="weather in San Francisco today")
|
||||
web_search(query="latest Node.js LTS version", provider="google")
|
||||
web_search(query="best practices for React 19", provider="dashscope")
|
||||
```
|
||||
|
||||
## Provider Details
|
||||
|
||||
### DashScope (Official)
|
||||
|
||||
- **Cost:** Free
|
||||
- **Authentication:** Automatically available when using Qwen OAuth authentication
|
||||
- **Configuration:** No API key required, automatically added to provider list for Qwen OAuth users
|
||||
- **Quota:** 200 requests/minute, 2000 requests/day
|
||||
- **Best for:** General queries, always available as fallback for Qwen OAuth users
|
||||
- **Auto-registration:** If you're using Qwen OAuth, DashScope is automatically added to your provider list even if you don't configure it explicitly
|
||||
|
||||
### Tavily
|
||||
|
||||
- **Cost:** Requires API key (paid service with free tier)
|
||||
- **Sign up:** https://tavily.com
|
||||
- **Features:** High-quality results with AI-generated answers
|
||||
- **Best for:** Research, comprehensive answers with citations
|
||||
|
||||
### Google Custom Search
|
||||
|
||||
- **Cost:** Free tier available (100 queries/day)
|
||||
- **Setup:**
|
||||
1. Enable Custom Search API in Google Cloud Console
|
||||
2. Create a Custom Search Engine at https://programmablesearchengine.google.com
|
||||
- **Features:** Google's search quality
|
||||
- **Best for:** Specific, factual queries
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Response format:** Returns a concise answer with numbered source citations
|
||||
- **Citations:** Source links are appended as a numbered list: [1], [2], etc.
|
||||
- **Multiple providers:** If one provider fails, manually specify another using the `provider` parameter
|
||||
- **DashScope availability:** Automatically available for Qwen OAuth users, no configuration needed
|
||||
- **Default provider selection:** The system automatically selects a default provider based on availability:
|
||||
1. Your explicit `default` configuration (highest priority)
|
||||
2. CLI argument `--web-search-default`
|
||||
3. First available provider by priority: Tavily > Google > DashScope
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Tool not available?**
|
||||
|
||||
- **For Qwen OAuth users:** The tool is automatically registered with DashScope provider, no configuration needed
|
||||
- **For other authentication types:** Ensure at least one provider (Tavily or Google) is configured
|
||||
- For Tavily/Google: Verify your API keys are correct
|
||||
|
||||
**Provider-specific errors?**
|
||||
|
||||
- Use the `provider` parameter to try a different search provider
|
||||
- Check your API quotas and rate limits
|
||||
- Verify API keys are properly set in configuration
|
||||
|
||||
**Need help?**
|
||||
|
||||
- Check your configuration: Run `qwen` and use the settings dialog
|
||||
- View your current settings in `~/.qwen-code/settings.json` (macOS/Linux) or `%USERPROFILE%\.qwen-code\settings.json` (Windows)
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
subagents: 'Subagents',
|
||||
checkpointing: 'Checkpointing',
|
||||
sandbox: 'Sandbox Support',
|
||||
headless: 'Headless Mode',
|
||||
'welcome-back': 'Welcome Back',
|
||||
'token-caching': 'Token Caching',
|
||||
};
|
||||
@@ -1,308 +0,0 @@
|
||||
# Headless Mode
|
||||
|
||||
Headless mode allows you to run Qwen Code programmatically from command line
|
||||
scripts and automation tools without any interactive UI. This is ideal for
|
||||
scripting, automation, CI/CD pipelines, and building AI-powered tools.
|
||||
|
||||
- [Headless Mode](#headless-mode)
|
||||
- [Overview](#overview)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [Direct Prompts](#direct-prompts)
|
||||
- [Stdin Input](#stdin-input)
|
||||
- [Combining with File Input](#combining-with-file-input)
|
||||
- [Output Formats](#output-formats)
|
||||
- [Text Output (Default)](#text-output-default)
|
||||
- [JSON Output](#json-output)
|
||||
- [Response Schema](#response-schema)
|
||||
- [Example Usage](#example-usage)
|
||||
- [File Redirection](#file-redirection)
|
||||
- [Configuration Options](#configuration-options)
|
||||
- [Examples](#examples)
|
||||
- [Code review](#code-review)
|
||||
- [Generate commit messages](#generate-commit-messages)
|
||||
- [API documentation](#api-documentation)
|
||||
- [Batch code analysis](#batch-code-analysis)
|
||||
- [Code review](#code-review-1)
|
||||
- [Log analysis](#log-analysis)
|
||||
- [Release notes generation](#release-notes-generation)
|
||||
- [Model and tool usage tracking](#model-and-tool-usage-tracking)
|
||||
- [Resources](#resources)
|
||||
|
||||
## Overview
|
||||
|
||||
The headless mode provides a headless interface to Qwen Code that:
|
||||
|
||||
- Accepts prompts via command line arguments or stdin
|
||||
- Returns structured output (text or JSON)
|
||||
- Supports file redirection and piping
|
||||
- Enables automation and scripting workflows
|
||||
- Provides consistent exit codes for error handling
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Direct Prompts
|
||||
|
||||
Use the `--prompt` (or `-p`) flag to run in headless mode:
|
||||
|
||||
```bash
|
||||
qwen --prompt "What is machine learning?"
|
||||
```
|
||||
|
||||
### Stdin Input
|
||||
|
||||
Pipe input to Qwen Code from your terminal:
|
||||
|
||||
```bash
|
||||
echo "Explain this code" | qwen
|
||||
```
|
||||
|
||||
### Combining with File Input
|
||||
|
||||
Read from files and process with Qwen Code:
|
||||
|
||||
```bash
|
||||
cat README.md | qwen --prompt "Summarize this documentation"
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
|
||||
### Text Output (Default)
|
||||
|
||||
Standard human-readable output:
|
||||
|
||||
```bash
|
||||
qwen -p "What is the capital of France?"
|
||||
```
|
||||
|
||||
Response format:
|
||||
|
||||
```
|
||||
The capital of France is Paris.
|
||||
```
|
||||
|
||||
### JSON Output
|
||||
|
||||
Returns structured data including response, statistics, and metadata. This
|
||||
format is ideal for programmatic processing and automation scripts.
|
||||
|
||||
#### Response Schema
|
||||
|
||||
The JSON output follows this high-level structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"response": "string", // The main AI-generated content answering your prompt
|
||||
"stats": {
|
||||
// Usage metrics and performance data
|
||||
"models": {
|
||||
// Per-model API and token usage statistics
|
||||
"[model-name]": {
|
||||
"api": {
|
||||
/* request counts, errors, latency */
|
||||
},
|
||||
"tokens": {
|
||||
/* prompt, response, cached, total counts */
|
||||
}
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
// Tool execution statistics
|
||||
"totalCalls": "number",
|
||||
"totalSuccess": "number",
|
||||
"totalFail": "number",
|
||||
"totalDurationMs": "number",
|
||||
"totalDecisions": {
|
||||
/* accept, reject, modify, auto_accept counts */
|
||||
},
|
||||
"byName": {
|
||||
/* per-tool detailed stats */
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
// File modification statistics
|
||||
"totalLinesAdded": "number",
|
||||
"totalLinesRemoved": "number"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
// Present only when an error occurred
|
||||
"type": "string", // Error type (e.g., "ApiError", "AuthError")
|
||||
"message": "string", // Human-readable error description
|
||||
"code": "number" // Optional error code
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Example Usage
|
||||
|
||||
```bash
|
||||
qwen -p "What is the capital of France?" --output-format json
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"response": "The capital of France is Paris.",
|
||||
"stats": {
|
||||
"models": {
|
||||
"qwen3-coder-plus": {
|
||||
"api": {
|
||||
"totalRequests": 2,
|
||||
"totalErrors": 0,
|
||||
"totalLatencyMs": 5053
|
||||
},
|
||||
"tokens": {
|
||||
"prompt": 24939,
|
||||
"candidates": 20,
|
||||
"total": 25113,
|
||||
"cached": 21263,
|
||||
"thoughts": 154,
|
||||
"tool": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"totalCalls": 1,
|
||||
"totalSuccess": 1,
|
||||
"totalFail": 0,
|
||||
"totalDurationMs": 1881,
|
||||
"totalDecisions": {
|
||||
"accept": 0,
|
||||
"reject": 0,
|
||||
"modify": 0,
|
||||
"auto_accept": 1
|
||||
},
|
||||
"byName": {
|
||||
"google_web_search": {
|
||||
"count": 1,
|
||||
"success": 1,
|
||||
"fail": 0,
|
||||
"durationMs": 1881,
|
||||
"decisions": {
|
||||
"accept": 0,
|
||||
"reject": 0,
|
||||
"modify": 0,
|
||||
"auto_accept": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"totalLinesAdded": 0,
|
||||
"totalLinesRemoved": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### File Redirection
|
||||
|
||||
Save output to files or pipe to other commands:
|
||||
|
||||
```bash
|
||||
# Save to file
|
||||
qwen -p "Explain Docker" > docker-explanation.txt
|
||||
qwen -p "Explain Docker" --output-format json > docker-explanation.json
|
||||
|
||||
# Append to file
|
||||
qwen -p "Add more details" >> docker-explanation.txt
|
||||
|
||||
# Pipe to other tools
|
||||
qwen -p "What is Kubernetes?" --output-format json | jq '.response'
|
||||
qwen -p "Explain microservices" | wc -w
|
||||
qwen -p "List programming languages" | grep -i "python"
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
Key command-line options for headless usage:
|
||||
|
||||
| Option | Description | Example |
|
||||
| ----------------------- | ---------------------------------- | ------------------------------------------------ |
|
||||
| `--prompt`, `-p` | Run in headless mode | `qwen -p "query"` |
|
||||
| `--output-format` | Specify output format (text, json) | `qwen -p "query" --output-format json` |
|
||||
| `--model`, `-m` | Specify the Qwen model | `qwen -p "query" -m qwen3-coder-plus` |
|
||||
| `--debug`, `-d` | Enable debug mode | `qwen -p "query" --debug` |
|
||||
| `--all-files`, `-a` | Include all files in context | `qwen -p "query" --all-files` |
|
||||
| `--include-directories` | Include additional directories | `qwen -p "query" --include-directories src,docs` |
|
||||
| `--yolo`, `-y` | Auto-approve all actions | `qwen -p "query" --yolo` |
|
||||
| `--approval-mode` | Set approval mode | `qwen -p "query" --approval-mode auto_edit` |
|
||||
|
||||
For complete details on all available configuration options, settings files, and environment variables, see the [Configuration Guide](./cli/configuration.md).
|
||||
|
||||
## Examples
|
||||
|
||||
#### Code review
|
||||
|
||||
```bash
|
||||
cat src/auth.py | qwen -p "Review this authentication code for security issues" > security-review.txt
|
||||
```
|
||||
|
||||
#### Generate commit messages
|
||||
|
||||
```bash
|
||||
result=$(git diff --cached | qwen -p "Write a concise commit message for these changes" --output-format json)
|
||||
echo "$result" | jq -r '.response'
|
||||
```
|
||||
|
||||
#### API documentation
|
||||
|
||||
```bash
|
||||
result=$(cat api/routes.js | qwen -p "Generate OpenAPI spec for these routes" --output-format json)
|
||||
echo "$result" | jq -r '.response' > openapi.json
|
||||
```
|
||||
|
||||
#### Batch code analysis
|
||||
|
||||
```bash
|
||||
for file in src/*.py; do
|
||||
echo "Analyzing $file..."
|
||||
result=$(cat "$file" | qwen -p "Find potential bugs and suggest improvements" --output-format json)
|
||||
echo "$result" | jq -r '.response' > "reports/$(basename "$file").analysis"
|
||||
echo "Completed analysis for $(basename "$file")" >> reports/progress.log
|
||||
done
|
||||
```
|
||||
|
||||
#### Code review
|
||||
|
||||
```bash
|
||||
result=$(git diff origin/main...HEAD | qwen -p "Review these changes for bugs, security issues, and code quality" --output-format json)
|
||||
echo "$result" | jq -r '.response' > pr-review.json
|
||||
```
|
||||
|
||||
#### Log analysis
|
||||
|
||||
```bash
|
||||
grep "ERROR" /var/log/app.log | tail -20 | qwen -p "Analyze these errors and suggest root cause and fixes" > error-analysis.txt
|
||||
```
|
||||
|
||||
#### Release notes generation
|
||||
|
||||
```bash
|
||||
result=$(git log --oneline v1.0.0..HEAD | qwen -p "Generate release notes from these commits" --output-format json)
|
||||
response=$(echo "$result" | jq -r '.response')
|
||||
echo "$response"
|
||||
echo "$response" >> CHANGELOG.md
|
||||
```
|
||||
|
||||
#### Model and tool usage tracking
|
||||
|
||||
```bash
|
||||
result=$(qwen -p "Explain this database schema" --include-directories db --output-format json)
|
||||
total_tokens=$(echo "$result" | jq -r '.stats.models // {} | to_entries | map(.value.tokens.total) | add // 0')
|
||||
models_used=$(echo "$result" | jq -r '.stats.models // {} | keys | join(", ") | if . == "" then "none" else . end')
|
||||
tool_calls=$(echo "$result" | jq -r '.stats.tools.totalCalls // 0')
|
||||
tools_used=$(echo "$result" | jq -r '.stats.tools.byName // {} | keys | join(", ") | if . == "" then "none" else . end')
|
||||
echo "$(date): $total_tokens tokens, $tool_calls tool calls ($tools_used) used with models: $models_used" >> usage.log
|
||||
echo "$result" | jq -r '.response' > schema-docs.md
|
||||
echo "Recent usage trends:"
|
||||
tail -5 usage.log
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [CLI Configuration](./cli/configuration.md) - Complete configuration guide
|
||||
- [Authentication](./cli/authentication.md) - Setup authentication
|
||||
- [Commands](./cli/commands.md) - Interactive commands reference
|
||||
- [Tutorials](./cli/tutorials.md) - Step-by-step automation guides
|
||||
344
docs/index.md
@@ -1,344 +0,0 @@
|
||||
# Welcome to Qwen Code documentation
|
||||
|
||||
Qwen Code is a powerful command-line AI workflow tool adapted from [**Gemini CLI**](https://github.com/google-gemini/gemini-cli) ([details](./README.gemini.md)), specifically optimized for [Qwen3-Coder](https://github.com/QwenLM/Qwen3-Coder) models. It enhances your development workflow with advanced code understanding, automated tasks, and intelligent assistance.
|
||||
|
||||
## 🚀 Why Choose Qwen Code?
|
||||
|
||||
- 🎯 **Free Tier:** Up to 60 requests/min and 2,000 requests/day with your [QwenChat](https://chat.qwen.ai/) account.
|
||||
- 🧠 **Advanced Model:** Specially optimized for [Qwen3-Coder](https://github.com/QwenLM/Qwen3-Coder) for superior code understanding and assistance.
|
||||
- 🏆 **Comprehensive Features:** Includes subagents, Plan Mode, TodoWrite, vision model support, and full OpenAI API compatibility—all seamlessly integrated.
|
||||
- 🔧 **Built-in & Extensible Tools:** Includes file system operations, shell command execution, web fetch/search, and more—all easily extended via the Model Context Protocol (MCP) for custom integrations.
|
||||
- 💻 **Developer-Centric:** Built for terminal-first workflows—perfect for command-line enthusiasts.
|
||||
- 🛡️ **Open Source:** Apache 2.0 licensed for maximum freedom and transparency.
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ensure you have [Node.js version 20](https://nodejs.org/en/download) or higher installed.
|
||||
|
||||
```bash
|
||||
curl -qL https://www.npmjs.com/install.sh | sh
|
||||
```
|
||||
|
||||
### Install from npm
|
||||
|
||||
```bash
|
||||
npm install -g @qwen-code/qwen-code@latest
|
||||
qwen --version
|
||||
```
|
||||
|
||||
### Install from source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/QwenLM/qwen-code.git
|
||||
cd qwen-code
|
||||
npm install
|
||||
npm install -g .
|
||||
```
|
||||
|
||||
### Install globally with Homebrew (macOS/Linux)
|
||||
|
||||
```bash
|
||||
brew install qwen-code
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Start Qwen Code
|
||||
qwen
|
||||
|
||||
# Example commands
|
||||
> Explain this codebase structure
|
||||
> Help me refactor this function
|
||||
> Generate unit tests for this module
|
||||
```
|
||||
|
||||
### Session Management
|
||||
|
||||
Control your token usage with configurable session limits to optimize costs and performance.
|
||||
|
||||
#### Configure Session Token Limit
|
||||
|
||||
Create or edit `.qwen/settings.json` in your home directory:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionTokenLimit": 32000
|
||||
}
|
||||
```
|
||||
|
||||
#### Session Commands
|
||||
|
||||
- **`/compress`** - Compress conversation history to continue within token limits
|
||||
- **`/clear`** - Clear all conversation history and start fresh
|
||||
- **`/stats`** - Check current token usage and limits
|
||||
|
||||
> 📝 **Note**: Session token limit applies to a single conversation, not cumulative API calls.
|
||||
|
||||
### Vision Model Configuration
|
||||
|
||||
Qwen Code includes intelligent vision model auto-switching that detects images in your input and can automatically switch to vision-capable models for multimodal analysis. **This feature is enabled by default** - when you include images in your queries, you'll see a dialog asking how you'd like to handle the vision model switch.
|
||||
|
||||
#### Skip the Switch Dialog (Optional)
|
||||
|
||||
If you don't want to see the interactive dialog each time, configure the default behavior in your `.qwen/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"vlmSwitchMode": "once"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Available modes:**
|
||||
|
||||
- **`"once"`** - Switch to vision model for this query only, then revert
|
||||
- **`"session"`** - Switch to vision model for the entire session
|
||||
- **`"persist"`** - Continue with current model (no switching)
|
||||
- **Not set** - Show interactive dialog each time (default)
|
||||
|
||||
#### Command Line Override
|
||||
|
||||
You can also set the behavior via command line:
|
||||
|
||||
```bash
|
||||
# Switch once per query
|
||||
qwen --vlm-switch-mode once
|
||||
|
||||
# Switch for entire session
|
||||
qwen --vlm-switch-mode session
|
||||
|
||||
# Never switch automatically
|
||||
qwen --vlm-switch-mode persist
|
||||
```
|
||||
|
||||
#### Disable Vision Models (Optional)
|
||||
|
||||
To completely disable vision model support, add to your `.qwen/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"visionModelPreview": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 💡 **Tip**: In YOLO mode (`--yolo`), vision switching happens automatically without prompts when images are detected.
|
||||
|
||||
### Authorization
|
||||
|
||||
Choose your preferred authentication method based on your needs:
|
||||
|
||||
#### 1. Qwen OAuth (🚀 Recommended - Start in 30 seconds)
|
||||
|
||||
The easiest way to get started - completely free with generous quotas:
|
||||
|
||||
```bash
|
||||
# Just run this command and follow the browser authentication
|
||||
qwen
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
|
||||
1. **Instant Setup**: CLI opens your browser automatically
|
||||
2. **One-Click Login**: Authenticate with your qwen.ai account
|
||||
3. **Automatic Management**: Credentials cached locally for future use
|
||||
4. **No Configuration**: Zero setup required - just start coding!
|
||||
|
||||
**Free Tier Benefits:**
|
||||
|
||||
- ✅ **2,000 requests/day** (no token counting needed)
|
||||
- ✅ **60 requests/minute** rate limit
|
||||
- ✅ **Automatic credential refresh**
|
||||
- ✅ **Zero cost** for individual users
|
||||
- ℹ️ **Note**: Model fallback may occur to maintain service quality
|
||||
|
||||
#### 2. OpenAI-Compatible API
|
||||
|
||||
Use API keys for OpenAI or other compatible providers:
|
||||
|
||||
**Configuration Methods:**
|
||||
|
||||
1. **Environment Variables**
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="your_api_endpoint"
|
||||
export OPENAI_MODEL="your_model_choice"
|
||||
```
|
||||
|
||||
2. **Project `.env` File**
|
||||
Create a `.env` file in your project root:
|
||||
```env
|
||||
OPENAI_API_KEY=your_api_key_here
|
||||
OPENAI_BASE_URL=your_api_endpoint
|
||||
OPENAI_MODEL=your_model_choice
|
||||
```
|
||||
|
||||
**API Provider Options**
|
||||
|
||||
> ⚠️ **Regional Notice:**
|
||||
>
|
||||
> - **Mainland China**: Use Alibaba Cloud Bailian or ModelScope
|
||||
> - **International**: Use Alibaba Cloud ModelStudio or OpenRouter
|
||||
|
||||
<details>
|
||||
<summary><b>🇨🇳 For Users in Mainland China</b></summary>
|
||||
|
||||
**Option 1: Alibaba Cloud Bailian** ([Apply for API Key](https://bailian.console.aliyun.com/))
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
export OPENAI_MODEL="qwen3-coder-plus"
|
||||
```
|
||||
|
||||
**Option 2: ModelScope (Free Tier)** ([Apply for API Key](https://modelscope.cn/docs/model-service/API-Inference/intro))
|
||||
|
||||
- ✅ **2,000 free API calls per day**
|
||||
- ⚠️ Connect your Aliyun account to avoid authentication errors
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="https://api-inference.modelscope.cn/v1"
|
||||
export OPENAI_MODEL="Qwen/Qwen3-Coder-480B-A35B-Instruct"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>🌍 For International Users</b></summary>
|
||||
|
||||
**Option 1: Alibaba Cloud ModelStudio** ([Apply for API Key](https://modelstudio.console.alibabacloud.com/))
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
||||
export OPENAI_MODEL="qwen3-coder-plus"
|
||||
```
|
||||
|
||||
**Option 2: OpenRouter (Free Tier Available)** ([Apply for API Key](https://openrouter.ai/))
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
|
||||
export OPENAI_MODEL="qwen/qwen3-coder:free"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 🔍 Explore Codebases
|
||||
|
||||
```bash
|
||||
cd your-project/
|
||||
qwen
|
||||
|
||||
# Architecture analysis
|
||||
> Describe the main pieces of this system's architecture
|
||||
> What are the key dependencies and how do they interact?
|
||||
> Find all API endpoints and their authentication methods
|
||||
```
|
||||
|
||||
### 💻 Code Development
|
||||
|
||||
```bash
|
||||
# Refactoring
|
||||
> Refactor this function to improve readability and performance
|
||||
> Convert this class to use dependency injection
|
||||
> Split this large module into smaller, focused components
|
||||
|
||||
# Code generation
|
||||
> Create a REST API endpoint for user management
|
||||
> Generate unit tests for the authentication module
|
||||
> Add error handling to all database operations
|
||||
```
|
||||
|
||||
### 🔄 Automate Workflows
|
||||
|
||||
```bash
|
||||
# Git automation
|
||||
> Analyze git commits from the last 7 days, grouped by feature
|
||||
> Create a changelog from recent commits
|
||||
> Find all TODO comments and create GitHub issues
|
||||
|
||||
# File operations
|
||||
> Convert all images in this directory to PNG format
|
||||
> Rename all test files to follow the *.test.ts pattern
|
||||
> Find and remove all console.log statements
|
||||
```
|
||||
|
||||
### 🐛 Debugging & Analysis
|
||||
|
||||
```bash
|
||||
# Performance analysis
|
||||
> Identify performance bottlenecks in this React component
|
||||
> Find all N+1 query problems in the codebase
|
||||
|
||||
# Security audit
|
||||
> Check for potential SQL injection vulnerabilities
|
||||
> Find all hardcoded credentials or API keys
|
||||
```
|
||||
|
||||
## Popular Tasks
|
||||
|
||||
### 📚 Understand New Codebases
|
||||
|
||||
```text
|
||||
> What are the core business logic components?
|
||||
> What security mechanisms are in place?
|
||||
> How does the data flow through the system?
|
||||
> What are the main design patterns used?
|
||||
> Generate a dependency graph for this module
|
||||
```
|
||||
|
||||
### 🔨 Code Refactoring & Optimization
|
||||
|
||||
```text
|
||||
> What parts of this module can be optimized?
|
||||
> Help me refactor this class to follow SOLID principles
|
||||
> Add proper error handling and logging
|
||||
> Convert callbacks to async/await pattern
|
||||
> Implement caching for expensive operations
|
||||
```
|
||||
|
||||
### 📝 Documentation & Testing
|
||||
|
||||
```text
|
||||
> Generate comprehensive JSDoc comments for all public APIs
|
||||
> Write unit tests with edge cases for this component
|
||||
> Create API documentation in OpenAPI format
|
||||
> Add inline comments explaining complex algorithms
|
||||
> Generate a README for this module
|
||||
```
|
||||
|
||||
### 🚀 Development Acceleration
|
||||
|
||||
```text
|
||||
> Set up a new Express server with authentication
|
||||
> Create a React component with TypeScript and tests
|
||||
> Implement a rate limiter middleware
|
||||
> Add database migrations for new schema
|
||||
> Configure CI/CD pipeline for this project
|
||||
```
|
||||
|
||||
## Commands & Shortcuts
|
||||
|
||||
### Session Commands
|
||||
|
||||
- `/help` - Display available commands
|
||||
- `/clear` - Clear conversation history
|
||||
- `/compress` - Compress history to save tokens
|
||||
- `/stats` - Show current session information
|
||||
- `/exit` or `/quit` - Exit Qwen Code
|
||||
|
||||
### Keyboard Shortcuts
|
||||
|
||||
- `Ctrl+C` - Cancel current operation
|
||||
- `Ctrl+D` - Exit (on empty line)
|
||||
- `Up/Down` - Navigate command history
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
[
|
||||
{
|
||||
"label": "Overview",
|
||||
"items": [
|
||||
{ "label": "Welcome", "slug": "docs" },
|
||||
{ "label": "Execution and Deployment", "slug": "docs/deployment" },
|
||||
{ "label": "Architecture Overview", "slug": "docs/architecture" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "CLI",
|
||||
"items": [
|
||||
{ "label": "Introduction", "slug": "docs/cli" },
|
||||
{ "label": "Authentication", "slug": "docs/cli/authentication" },
|
||||
{ "label": "Commands", "slug": "docs/cli/commands" },
|
||||
{ "label": "Configuration", "slug": "docs/cli/configuration" },
|
||||
{ "label": "Checkpointing", "slug": "docs/checkpointing" },
|
||||
{ "label": "Extensions", "slug": "docs/extension" },
|
||||
{ "label": "Headless Mode", "slug": "docs/headless" },
|
||||
{ "label": "IDE Integration", "slug": "docs/ide-integration" },
|
||||
{
|
||||
"label": "IDE Companion Spec",
|
||||
"slug": "docs/ide-companion-spec"
|
||||
},
|
||||
{ "label": "Telemetry", "slug": "docs/telemetry" },
|
||||
{ "label": "Themes", "slug": "docs/cli/themes" },
|
||||
{ "label": "Token Caching", "slug": "docs/cli/token-caching" },
|
||||
{ "label": "Trusted Folders", "slug": "docs/trusted-folders" },
|
||||
{ "label": "Tutorials", "slug": "docs/cli/tutorials" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Core",
|
||||
"items": [
|
||||
{ "label": "Introduction", "slug": "docs/core" },
|
||||
{ "label": "Tools API", "slug": "docs/core/tools-api" },
|
||||
{ "label": "Memory Import Processor", "slug": "docs/core/memport" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Tools",
|
||||
"items": [
|
||||
{ "label": "Overview", "slug": "docs/tools" },
|
||||
{ "label": "File System", "slug": "docs/tools/file-system" },
|
||||
{ "label": "Multi-File Read", "slug": "docs/tools/multi-file" },
|
||||
{ "label": "Shell", "slug": "docs/tools/shell" },
|
||||
{ "label": "Web Fetch", "slug": "docs/tools/web-fetch" },
|
||||
{ "label": "Web Search", "slug": "docs/tools/web-search" },
|
||||
{ "label": "Memory", "slug": "docs/tools/memory" },
|
||||
{ "label": "MCP Servers", "slug": "docs/tools/mcp-server" },
|
||||
{ "label": "Sandboxing", "slug": "docs/sandbox" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Development",
|
||||
"items": [
|
||||
{ "label": "NPM", "slug": "docs/npm" },
|
||||
{ "label": "Releases", "slug": "docs/releases" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Support",
|
||||
"items": [
|
||||
{ "label": "Troubleshooting", "slug": "docs/troubleshooting" },
|
||||
{ "label": "Terms of Service", "slug": "docs/tos-privacy" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,43 +0,0 @@
|
||||
# Web Search Tool (`web_search`)
|
||||
|
||||
This document describes the `web_search` tool.
|
||||
|
||||
## Description
|
||||
|
||||
Use `web_search` to perform a web search using the Tavily API. The tool returns a concise answer with sources when possible.
|
||||
|
||||
### Arguments
|
||||
|
||||
`web_search` takes one argument:
|
||||
|
||||
- `query` (string, required): The search query.
|
||||
|
||||
## How to use `web_search`
|
||||
|
||||
`web_search` calls the Tavily API directly. You must configure the `TAVILY_API_KEY` through one of the following methods:
|
||||
|
||||
1. **Settings file**: Add `"tavilyApiKey": "your-key-here"` to your `settings.json`
|
||||
2. **Environment variable**: Set `TAVILY_API_KEY` in your environment or `.env` file
|
||||
3. **Command line**: Use `--tavily-api-key your-key-here` when running the CLI
|
||||
|
||||
If the key is not configured, the tool will be disabled and skipped.
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
web_search(query="Your query goes here.")
|
||||
```
|
||||
|
||||
## `web_search` examples
|
||||
|
||||
Get information on a topic:
|
||||
|
||||
```
|
||||
web_search(query="latest advancements in AI-powered code generation")
|
||||
```
|
||||
|
||||
## Important notes
|
||||
|
||||
- **Response returned:** The `web_search` tool returns a concise answer when available, with a list of source links.
|
||||
- **Citations:** Source links are appended as a numbered list.
|
||||
- **API key:** Configure `TAVILY_API_KEY` via settings.json, environment variables, .env files, or command line arguments. If not configured, the tool is not registered.
|
||||
28
docs/users/_meta.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
'Getting started': {
|
||||
type: 'separator',
|
||||
title: 'Getting started', // Title is optional
|
||||
},
|
||||
overview: 'Overview',
|
||||
'quick-start': 'QuickStart',
|
||||
'common-workflow': 'Command Workflows',
|
||||
'Outside of the terminal': {
|
||||
type: 'separator',
|
||||
title: 'Outside of the terminal', // Title is optional
|
||||
},
|
||||
'integration-github-action': 'Github Action',
|
||||
'integration-vscode': 'VSCode Extension',
|
||||
'integration-zed': 'Zed IDE',
|
||||
'Code with Qwen Code': {
|
||||
type: 'separator',
|
||||
title: 'Code with Qwen Code', // Title is optional
|
||||
},
|
||||
features: 'Features',
|
||||
configuration: 'Configuration',
|
||||
reference: 'Reference',
|
||||
support: 'Support',
|
||||
// need refine
|
||||
'ide-integration': {
|
||||
display: 'hidden',
|
||||
},
|
||||
};
|
||||
0
docs/users/common-workflow.md
Normal file
7
docs/users/configuration/_meta.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
settings: 'Settings File',
|
||||
memory: 'Memory Management',
|
||||
'trusted-folders': 'Trusted Folders',
|
||||
'qwen-ignore': 'Ignoring Files',
|
||||
themes: 'Themes',
|
||||
};
|
||||
0
docs/users/configuration/memory.md
Normal file
0
docs/users/configuration/settings.md
Normal file
15
docs/users/features/_meta.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
subagents: 'Subagents',
|
||||
'sub-commands': 'Sub Commands',
|
||||
checkpointing: {
|
||||
display: 'hidden',
|
||||
},
|
||||
headless: 'Headless Mode',
|
||||
'welcome-back': {
|
||||
display: 'hidden',
|
||||
},
|
||||
'approval-mode': 'Approval Mode',
|
||||
'token-caching': 'Token Caching',
|
||||
mcp: 'MCP',
|
||||
sandbox: 'Sandboxing',
|
||||
};
|
||||
0
docs/users/features/approval-mode.md
Normal file
307
docs/users/features/headless.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# Headless Mode
|
||||
|
||||
Headless mode allows you to run Qwen Code programmatically from command line
|
||||
scripts and automation tools without any interactive UI. This is ideal for
|
||||
scripting, automation, CI/CD pipelines, and building AI-powered tools.
|
||||
|
||||
- [Headless Mode](#headless-mode)
|
||||
- [Overview](#overview)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [Direct Prompts](#direct-prompts)
|
||||
- [Stdin Input](#stdin-input)
|
||||
- [Combining with File Input](#combining-with-file-input)
|
||||
- [Output Formats](#output-formats)
|
||||
- [Text Output (Default)](#text-output-default)
|
||||
- [JSON Output](#json-output)
|
||||
- [Example Usage](#example-usage)
|
||||
- [Stream-JSON Output](#stream-json-output)
|
||||
- [Input Format](#input-format)
|
||||
- [File Redirection](#file-redirection)
|
||||
- [Configuration Options](#configuration-options)
|
||||
- [Examples](#examples)
|
||||
- [Code review](#code-review)
|
||||
- [Generate commit messages](#generate-commit-messages)
|
||||
- [API documentation](#api-documentation)
|
||||
- [Batch code analysis](#batch-code-analysis)
|
||||
- [PR code review](#pr-code-review)
|
||||
- [Log analysis](#log-analysis)
|
||||
- [Release notes generation](#release-notes-generation)
|
||||
- [Model and tool usage tracking](#model-and-tool-usage-tracking)
|
||||
- [Resources](#resources)
|
||||
|
||||
## Overview
|
||||
|
||||
The headless mode provides a headless interface to Qwen Code that:
|
||||
|
||||
- Accepts prompts via command line arguments or stdin
|
||||
- Returns structured output (text or JSON)
|
||||
- Supports file redirection and piping
|
||||
- Enables automation and scripting workflows
|
||||
- Provides consistent exit codes for error handling
|
||||
- Can resume previous sessions scoped to the current project for multi-step automation
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Direct Prompts
|
||||
|
||||
Use the `--prompt` (or `-p`) flag to run in headless mode:
|
||||
|
||||
```bash
|
||||
qwen --prompt "What is machine learning?"
|
||||
```
|
||||
|
||||
### Stdin Input
|
||||
|
||||
Pipe input to Qwen Code from your terminal:
|
||||
|
||||
```bash
|
||||
echo "Explain this code" | qwen
|
||||
```
|
||||
|
||||
### Combining with File Input
|
||||
|
||||
Read from files and process with Qwen Code:
|
||||
|
||||
```bash
|
||||
cat README.md | qwen --prompt "Summarize this documentation"
|
||||
```
|
||||
|
||||
### Resume Previous Sessions (Headless)
|
||||
|
||||
Reuse conversation context from the current project in headless scripts:
|
||||
|
||||
```bash
|
||||
# Continue the most recent session for this project and run a new prompt
|
||||
qwen --continue -p "Run the tests again and summarize failures"
|
||||
|
||||
# Resume a specific session ID directly (no UI)
|
||||
qwen --resume 123e4567-e89b-12d3-a456-426614174000 -p "Apply the follow-up refactor"
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Session data is project-scoped JSONL under `~/.qwen/projects/<sanitized-cwd>/chats`.
|
||||
- Restores conversation history, tool outputs, and chat-compression checkpoints before sending the new prompt.
|
||||
|
||||
## Output Formats
|
||||
|
||||
Qwen Code supports multiple output formats for different use cases:
|
||||
|
||||
### Text Output (Default)
|
||||
|
||||
Standard human-readable output:
|
||||
|
||||
```bash
|
||||
qwen -p "What is the capital of France?"
|
||||
```
|
||||
|
||||
Response format:
|
||||
|
||||
```
|
||||
The capital of France is Paris.
|
||||
```
|
||||
|
||||
### JSON Output
|
||||
|
||||
Returns structured data as a JSON array. All messages are buffered and output together when the session completes. This format is ideal for programmatic processing and automation scripts.
|
||||
|
||||
The JSON output is an array of message objects. The output includes multiple message types: system messages (session initialization), assistant messages (AI responses), and result messages (execution summary).
|
||||
|
||||
#### Example Usage
|
||||
|
||||
```bash
|
||||
qwen -p "What is the capital of France?" --output-format json
|
||||
```
|
||||
|
||||
Output (at end of execution):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "system",
|
||||
"subtype": "session_start",
|
||||
"uuid": "...",
|
||||
"session_id": "...",
|
||||
"model": "qwen3-coder-plus",
|
||||
...
|
||||
},
|
||||
{
|
||||
"type": "assistant",
|
||||
"uuid": "...",
|
||||
"session_id": "...",
|
||||
"message": {
|
||||
"id": "...",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "qwen3-coder-plus",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The capital of France is Paris."
|
||||
}
|
||||
],
|
||||
"usage": {...}
|
||||
},
|
||||
"parent_tool_use_id": null
|
||||
},
|
||||
{
|
||||
"type": "result",
|
||||
"subtype": "success",
|
||||
"uuid": "...",
|
||||
"session_id": "...",
|
||||
"is_error": false,
|
||||
"duration_ms": 1234,
|
||||
"result": "The capital of France is Paris.",
|
||||
"usage": {...}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Stream-JSON Output
|
||||
|
||||
Stream-JSON format emits JSON messages immediately as they occur during execution, enabling real-time monitoring. This format uses line-delimited JSON where each message is a complete JSON object on a single line.
|
||||
|
||||
```bash
|
||||
qwen -p "Explain TypeScript" --output-format stream-json
|
||||
```
|
||||
|
||||
Output (streaming as events occur):
|
||||
|
||||
```json
|
||||
{"type":"system","subtype":"session_start","uuid":"...","session_id":"..."}
|
||||
{"type":"assistant","uuid":"...","session_id":"...","message":{...}}
|
||||
{"type":"result","subtype":"success","uuid":"...","session_id":"..."}
|
||||
```
|
||||
|
||||
When combined with `--include-partial-messages`, additional stream events are emitted in real-time (message_start, content_block_delta, etc.) for real-time UI updates.
|
||||
|
||||
```bash
|
||||
qwen -p "Write a Python script" --output-format stream-json --include-partial-messages
|
||||
```
|
||||
|
||||
### Input Format
|
||||
|
||||
The `--input-format` parameter controls how Qwen Code consumes input from standard input:
|
||||
|
||||
- **`text`** (default): Standard text input from stdin or command-line arguments
|
||||
- **`stream-json`**: JSON message protocol via stdin for bidirectional communication
|
||||
|
||||
> **Note:** Stream-json input mode is currently under construction and is intended for SDK integration. It requires `--output-format stream-json` to be set.
|
||||
|
||||
### File Redirection
|
||||
|
||||
Save output to files or pipe to other commands:
|
||||
|
||||
```bash
|
||||
# Save to file
|
||||
qwen -p "Explain Docker" > docker-explanation.txt
|
||||
qwen -p "Explain Docker" --output-format json > docker-explanation.json
|
||||
|
||||
# Append to file
|
||||
qwen -p "Add more details" >> docker-explanation.txt
|
||||
|
||||
# Pipe to other tools
|
||||
qwen -p "What is Kubernetes?" --output-format json | jq '.response'
|
||||
qwen -p "Explain microservices" | wc -w
|
||||
qwen -p "List programming languages" | grep -i "python"
|
||||
|
||||
# Stream-JSON output for real-time processing
|
||||
qwen -p "Explain Docker" --output-format stream-json | jq '.type'
|
||||
qwen -p "Write code" --output-format stream-json --include-partial-messages | jq '.event.type'
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
Key command-line options for headless usage:
|
||||
|
||||
| Option | Description | Example |
|
||||
| ---------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `--prompt`, `-p` | Run in headless mode | `qwen -p "query"` |
|
||||
| `--output-format`, `-o` | Specify output format (text, json, stream-json) | `qwen -p "query" --output-format json` |
|
||||
| `--input-format` | Specify input format (text, stream-json) | `qwen --input-format text --output-format stream-json` |
|
||||
| `--include-partial-messages` | Include partial messages in stream-json output | `qwen -p "query" --output-format stream-json --include-partial-messages` |
|
||||
| `--debug`, `-d` | Enable debug mode | `qwen -p "query" --debug` |
|
||||
| `--all-files`, `-a` | Include all files in context | `qwen -p "query" --all-files` |
|
||||
| `--include-directories` | Include additional directories | `qwen -p "query" --include-directories src,docs` |
|
||||
| `--yolo`, `-y` | Auto-approve all actions | `qwen -p "query" --yolo` |
|
||||
| `--approval-mode` | Set approval mode | `qwen -p "query" --approval-mode auto_edit` |
|
||||
| `--continue` | Resume the most recent session for this project | `qwen --continue -p "Pick up where we left off"` |
|
||||
| `--resume [sessionId]` | Resume a specific session (or choose interactively) | `qwen --resume 123e... -p "Finish the refactor"` |
|
||||
|
||||
For complete details on all available configuration options, settings files, and environment variables, see the [Configuration Guide](./cli/configuration.md).
|
||||
|
||||
## Examples
|
||||
|
||||
### Code review
|
||||
|
||||
```bash
|
||||
cat src/auth.py | qwen -p "Review this authentication code for security issues" > security-review.txt
|
||||
```
|
||||
|
||||
### Generate commit messages
|
||||
|
||||
```bash
|
||||
result=$(git diff --cached | qwen -p "Write a concise commit message for these changes" --output-format json)
|
||||
echo "$result" | jq -r '.response'
|
||||
```
|
||||
|
||||
### API documentation
|
||||
|
||||
```bash
|
||||
result=$(cat api/routes.js | qwen -p "Generate OpenAPI spec for these routes" --output-format json)
|
||||
echo "$result" | jq -r '.response' > openapi.json
|
||||
```
|
||||
|
||||
### Batch code analysis
|
||||
|
||||
```bash
|
||||
for file in src/*.py; do
|
||||
echo "Analyzing $file..."
|
||||
result=$(cat "$file" | qwen -p "Find potential bugs and suggest improvements" --output-format json)
|
||||
echo "$result" | jq -r '.response' > "reports/$(basename "$file").analysis"
|
||||
echo "Completed analysis for $(basename "$file")" >> reports/progress.log
|
||||
done
|
||||
```
|
||||
|
||||
### PR code review
|
||||
|
||||
```bash
|
||||
result=$(git diff origin/main...HEAD | qwen -p "Review these changes for bugs, security issues, and code quality" --output-format json)
|
||||
echo "$result" | jq -r '.response' > pr-review.json
|
||||
```
|
||||
|
||||
### Log analysis
|
||||
|
||||
```bash
|
||||
grep "ERROR" /var/log/app.log | tail -20 | qwen -p "Analyze these errors and suggest root cause and fixes" > error-analysis.txt
|
||||
```
|
||||
|
||||
### Release notes generation
|
||||
|
||||
```bash
|
||||
result=$(git log --oneline v1.0.0..HEAD | qwen -p "Generate release notes from these commits" --output-format json)
|
||||
response=$(echo "$result" | jq -r '.response')
|
||||
echo "$response"
|
||||
echo "$response" >> CHANGELOG.md
|
||||
```
|
||||
|
||||
### Model and tool usage tracking
|
||||
|
||||
```bash
|
||||
result=$(qwen -p "Explain this database schema" --include-directories db --output-format json)
|
||||
total_tokens=$(echo "$result" | jq -r '.stats.models // {} | to_entries | map(.value.tokens.total) | add // 0')
|
||||
models_used=$(echo "$result" | jq -r '.stats.models // {} | keys | join(", ") | if . == "" then "none" else . end')
|
||||
tool_calls=$(echo "$result" | jq -r '.stats.tools.totalCalls // 0')
|
||||
tools_used=$(echo "$result" | jq -r '.stats.tools.byName // {} | keys | join(", ") | if . == "" then "none" else . end')
|
||||
echo "$(date): $total_tokens tokens, $tool_calls tool calls ($tools_used) used with models: $models_used" >> usage.log
|
||||
echo "$result" | jq -r '.response' > schema-docs.md
|
||||
echo "Recent usage trends:"
|
||||
tail -5 usage.log
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [CLI Configuration](./cli/configuration.md) - Complete configuration guide
|
||||
- [Authentication](./cli/authentication.md) - Setup authentication
|
||||
- [Commands](./cli/commands.md) - Interactive commands reference
|
||||
- [Tutorials](./cli/tutorials.md) - Step-by-step automation guides
|
||||
0
docs/users/features/mcp.md
Normal file
0
docs/users/features/sub-commands.md
Normal file
@@ -106,7 +106,10 @@ Subagents are configured using Markdown files with YAML frontmatter. This format
|
||||
---
|
||||
name: agent-name
|
||||
description: Brief description of when and how to use this agent
|
||||
tools: tool1, tool2, tool3 # Optional
|
||||
tools:
|
||||
- tool1
|
||||
- tool2
|
||||
- tool3 # Optional
|
||||
---
|
||||
|
||||
System prompt content goes here.
|
||||
@@ -167,7 +170,11 @@ Perfect for comprehensive test creation and test-driven development.
|
||||
---
|
||||
name: testing-expert
|
||||
description: Writes comprehensive unit tests, integration tests, and handles test automation with best practices
|
||||
tools: read_file, write_file, read_many_files, run_shell_command
|
||||
tools:
|
||||
- read_file
|
||||
- write_file
|
||||
- read_many_files
|
||||
- run_shell_command
|
||||
---
|
||||
|
||||
You are a testing specialist focused on creating high-quality, maintainable tests.
|
||||
@@ -207,7 +214,11 @@ Specialized in creating clear, comprehensive documentation.
|
||||
---
|
||||
name: documentation-writer
|
||||
description: Creates comprehensive documentation, README files, API docs, and user guides
|
||||
tools: read_file, write_file, read_many_files, web_search
|
||||
tools:
|
||||
- read_file
|
||||
- write_file
|
||||
- read_many_files
|
||||
- web_search
|
||||
---
|
||||
|
||||
You are a technical documentation specialist for ${project_name}.
|
||||
@@ -256,7 +267,9 @@ Focused on code quality, security, and best practices.
|
||||
---
|
||||
name: code-reviewer
|
||||
description: Reviews code for best practices, security issues, performance, and maintainability
|
||||
tools: read_file, read_many_files
|
||||
tools:
|
||||
- read_file
|
||||
- read_many_files
|
||||
---
|
||||
|
||||
You are an experienced code reviewer focused on quality, security, and maintainability.
|
||||
@@ -298,7 +311,11 @@ Optimized for React development, hooks, and component patterns.
|
||||
---
|
||||
name: react-specialist
|
||||
description: Expert in React development, hooks, component patterns, and modern React best practices
|
||||
tools: read_file, write_file, read_many_files, run_shell_command
|
||||
tools:
|
||||
- read_file
|
||||
- write_file
|
||||
- read_many_files
|
||||
- run_shell_command
|
||||
---
|
||||
|
||||
You are a React specialist with deep expertise in modern React development.
|
||||
@@ -339,7 +356,11 @@ Specialized in Python development, frameworks, and best practices.
|
||||
---
|
||||
name: python-expert
|
||||
description: Expert in Python development, frameworks, testing, and Python-specific best practices
|
||||
tools: read_file, write_file, read_many_files, run_shell_command
|
||||
tools:
|
||||
- read_file
|
||||
- write_file
|
||||
- read_many_files
|
||||
- run_shell_command
|
||||
---
|
||||
|
||||
You are a Python expert with deep knowledge of the Python ecosystem.
|
||||
@@ -75,9 +75,9 @@ Add to your `.qwen/settings.json`:
|
||||
|
||||
### Project Summary Generation
|
||||
|
||||
The Welcome Back feature works seamlessly with the `/chat summary` command:
|
||||
The Welcome Back feature works seamlessly with the `/summary` command:
|
||||
|
||||
1. **Generate Summary:** Use `/chat summary` to create a project summary
|
||||
1. **Generate Summary:** Use `/summary` to create a project summary
|
||||
2. **Automatic Detection:** Next time you start Qwen Code in this project, Welcome Back will detect the summary
|
||||
3. **Resume Work:** Choose to continue and the summary will be loaded as context
|
||||
|
||||
0
docs/users/integration-github-action.md
Normal file
0
docs/users/integration-vscode.md
Normal file
0
docs/users/integration-zed.md
Normal file
0
docs/users/overview.md
Normal file
0
docs/users/quick-start.md
Normal file
3
docs/users/reference/_meta.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
'keyboard-shortcuts': 'Keyboard Shortcuts',
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
export default {
|
||||
troubleshooting: 'Troubleshooting',
|
||||
'tos-privacy': 'Terms of Service',
|
||||
|
||||
Uninstall: 'Uninstall',
|
||||
};
|
||||
@@ -14,6 +14,13 @@ This guide provides solutions to common issues and debugging tips, including top
|
||||
- **Solution:** Set the `NODE_EXTRA_CA_CERTS` environment variable to the absolute path of your corporate root CA certificate file.
|
||||
- Example: `export NODE_EXTRA_CA_CERTS=/path/to/your/corporate-ca.crt`
|
||||
|
||||
- **Issue: Unable to display UI after authentication failure**
|
||||
- **Cause:** If authentication fails after selecting an authentication type, the `security.auth.selectedType` setting may be persisted in `settings.json`. On restart, the CLI may get stuck trying to authenticate with the failed auth type and fail to display the UI.
|
||||
- **Solution:** Clear the `security.auth.selectedType` configuration item in your `settings.json` file:
|
||||
- Open `~/.qwen/settings.json` (or `./.qwen/settings.json` for project-specific settings)
|
||||
- Remove the `security.auth.selectedType` field
|
||||
- Restart the CLI to allow it to prompt for authentication again
|
||||
|
||||
## Frequently asked questions (FAQs)
|
||||
|
||||
- **Q: How do I update Qwen Code to the latest version?**
|
||||
590
integration-tests/acp-integration.test.ts
Normal file
@@ -0,0 +1,590 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { createInterface } from 'node:readline';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
|
||||
const REQUEST_TIMEOUT_MS = 60_000;
|
||||
const INITIAL_PROMPT = 'Create a quick note (smoke test).';
|
||||
const RESUME_PROMPT = 'Continue the note after reload.';
|
||||
const LIST_SIZE = 5;
|
||||
const IS_SANDBOX =
|
||||
process.env['GEMINI_SANDBOX'] &&
|
||||
process.env['GEMINI_SANDBOX']!.toLowerCase() !== 'false';
|
||||
|
||||
type PendingRequest = {
|
||||
resolve: (value: unknown) => void;
|
||||
reject: (reason: Error) => void;
|
||||
timeout: NodeJS.Timeout;
|
||||
};
|
||||
|
||||
type SessionUpdateNotification = {
|
||||
sessionId?: string;
|
||||
update?: {
|
||||
sessionUpdate?: string;
|
||||
availableCommands?: Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
input?: { hint: string } | null;
|
||||
}>;
|
||||
content?: {
|
||||
type: string;
|
||||
text?: string;
|
||||
};
|
||||
modeId?: string;
|
||||
};
|
||||
};
|
||||
|
||||
type PermissionRequest = {
|
||||
id: number;
|
||||
sessionId?: string;
|
||||
toolCall?: {
|
||||
toolCallId: string;
|
||||
title: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
content?: Array<{
|
||||
type: string;
|
||||
text?: string;
|
||||
path?: string;
|
||||
oldText?: string;
|
||||
newText?: string;
|
||||
}>;
|
||||
};
|
||||
options?: Array<{
|
||||
optionId: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
type PermissionHandler = (
|
||||
request: PermissionRequest,
|
||||
) => { optionId: string } | { outcome: 'cancelled' };
|
||||
|
||||
/**
|
||||
* Sets up an ACP test environment with all necessary utilities.
|
||||
*/
|
||||
function setupAcpTest(
|
||||
rig: TestRig,
|
||||
options?: { permissionHandler?: PermissionHandler },
|
||||
) {
|
||||
const pending = new Map<number, PendingRequest>();
|
||||
let nextRequestId = 1;
|
||||
const sessionUpdates: SessionUpdateNotification[] = [];
|
||||
const permissionRequests: PermissionRequest[] = [];
|
||||
const stderr: string[] = [];
|
||||
|
||||
// Default permission handler: auto-approve all
|
||||
const permissionHandler =
|
||||
options?.permissionHandler ?? (() => ({ optionId: 'proceed_once' }));
|
||||
|
||||
const agent = spawn('node', [rig.bundlePath, '--experimental-acp'], {
|
||||
cwd: rig.testDir!,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
agent.stderr?.on('data', (chunk) => {
|
||||
stderr.push(chunk.toString());
|
||||
});
|
||||
|
||||
const rl = createInterface({ input: agent.stdout });
|
||||
|
||||
const send = (json: unknown) => {
|
||||
agent.stdin.write(`${JSON.stringify(json)}\n`);
|
||||
};
|
||||
|
||||
const sendResponse = (id: number, result: unknown) => {
|
||||
send({ jsonrpc: '2.0', id, result });
|
||||
};
|
||||
|
||||
const sendRequest = (method: string, params?: unknown) =>
|
||||
new Promise<unknown>((resolve, reject) => {
|
||||
const id = nextRequestId++;
|
||||
const timeout = setTimeout(() => {
|
||||
pending.delete(id);
|
||||
reject(new Error(`Request ${id} (${method}) timed out`));
|
||||
}, REQUEST_TIMEOUT_MS);
|
||||
pending.set(id, { resolve, reject, timeout });
|
||||
send({ jsonrpc: '2.0', id, method, params });
|
||||
});
|
||||
|
||||
const handleResponse = (msg: {
|
||||
id: number;
|
||||
result?: unknown;
|
||||
error?: { message?: string };
|
||||
}) => {
|
||||
const waiter = pending.get(msg.id);
|
||||
if (!waiter) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(waiter.timeout);
|
||||
pending.delete(msg.id);
|
||||
if (msg.error) {
|
||||
waiter.reject(new Error(msg.error.message ?? 'Unknown error'));
|
||||
} else {
|
||||
waiter.resolve(msg.result);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMessage = (msg: {
|
||||
id?: number;
|
||||
method?: string;
|
||||
params?: SessionUpdateNotification & {
|
||||
path?: string;
|
||||
content?: string;
|
||||
sessionId?: string;
|
||||
toolCall?: PermissionRequest['toolCall'];
|
||||
options?: PermissionRequest['options'];
|
||||
};
|
||||
result?: unknown;
|
||||
error?: { message?: string };
|
||||
}) => {
|
||||
if (typeof msg.id !== 'undefined' && ('result' in msg || 'error' in msg)) {
|
||||
handleResponse(
|
||||
msg as {
|
||||
id: number;
|
||||
result?: unknown;
|
||||
error?: { message?: string };
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.method === 'session/update') {
|
||||
sessionUpdates.push({
|
||||
sessionId: msg.params?.sessionId,
|
||||
update: msg.params?.update,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
msg.method === 'session/request_permission' &&
|
||||
typeof msg.id === 'number'
|
||||
) {
|
||||
// Track permission request
|
||||
const permRequest: PermissionRequest = {
|
||||
id: msg.id,
|
||||
sessionId: msg.params?.sessionId,
|
||||
toolCall: msg.params?.toolCall,
|
||||
options: msg.params?.options,
|
||||
};
|
||||
permissionRequests.push(permRequest);
|
||||
|
||||
// Use custom handler or default
|
||||
const response = permissionHandler(permRequest);
|
||||
if ('outcome' in response) {
|
||||
sendResponse(msg.id, { outcome: response });
|
||||
} else {
|
||||
sendResponse(msg.id, {
|
||||
outcome: { optionId: response.optionId, outcome: 'selected' },
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.method === 'fs/read_text_file' && typeof msg.id === 'number') {
|
||||
try {
|
||||
const content = readFileSync(msg.params?.path ?? '', 'utf8');
|
||||
sendResponse(msg.id, { content });
|
||||
} catch (e) {
|
||||
sendResponse(msg.id, { content: `ERROR: ${(e as Error).message}` });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.method === 'fs/write_text_file' && typeof msg.id === 'number') {
|
||||
try {
|
||||
writeFileSync(
|
||||
msg.params?.path ?? '',
|
||||
msg.params?.content ?? '',
|
||||
'utf8',
|
||||
);
|
||||
sendResponse(msg.id, null);
|
||||
} catch (e) {
|
||||
sendResponse(msg.id, { message: (e as Error).message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rl.on('line', (line) => {
|
||||
if (!line.trim()) return;
|
||||
try {
|
||||
const msg = JSON.parse(line);
|
||||
handleMessage(msg);
|
||||
} catch {
|
||||
// Ignore non-JSON output from the agent.
|
||||
}
|
||||
});
|
||||
|
||||
const waitForExit = () =>
|
||||
new Promise<void>((resolve) => {
|
||||
if (agent.exitCode !== null || agent.signalCode) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
agent.once('exit', () => resolve());
|
||||
});
|
||||
|
||||
const cleanup = async () => {
|
||||
rl.close();
|
||||
agent.kill();
|
||||
pending.forEach(({ timeout }) => clearTimeout(timeout));
|
||||
pending.clear();
|
||||
await waitForExit();
|
||||
};
|
||||
|
||||
return {
|
||||
sendRequest,
|
||||
sendResponse,
|
||||
cleanup,
|
||||
stderr,
|
||||
sessionUpdates,
|
||||
permissionRequests,
|
||||
};
|
||||
}
|
||||
|
||||
(IS_SANDBOX ? describe.skip : describe)('acp integration', () => {
|
||||
it('creates, lists, loads, and resumes a session', async () => {
|
||||
const rig = new TestRig();
|
||||
rig.setup('acp load session');
|
||||
|
||||
const { sendRequest, cleanup, stderr, sessionUpdates } = setupAcpTest(rig);
|
||||
|
||||
try {
|
||||
const initResult = await sendRequest('initialize', {
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: {
|
||||
fs: { readTextFile: true, writeTextFile: true },
|
||||
},
|
||||
});
|
||||
expect(initResult).toBeDefined();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
expect((initResult as any).agentInfo.version).toBeDefined();
|
||||
|
||||
await sendRequest('authenticate', { methodId: 'openai' });
|
||||
|
||||
const newSession = (await sendRequest('session/new', {
|
||||
cwd: rig.testDir!,
|
||||
mcpServers: [],
|
||||
})) as { sessionId: string };
|
||||
expect(newSession.sessionId).toBeTruthy();
|
||||
|
||||
const promptResult = await sendRequest('session/prompt', {
|
||||
sessionId: newSession.sessionId,
|
||||
prompt: [{ type: 'text', text: INITIAL_PROMPT }],
|
||||
});
|
||||
expect(promptResult).toBeDefined();
|
||||
|
||||
await delay(500);
|
||||
|
||||
const listResult = (await sendRequest('session/list', {
|
||||
cwd: rig.testDir!,
|
||||
size: LIST_SIZE,
|
||||
})) as { items?: Array<{ sessionId: string }> };
|
||||
|
||||
expect(Array.isArray(listResult.items)).toBe(true);
|
||||
expect(listResult.items?.length ?? 0).toBeGreaterThan(0);
|
||||
|
||||
const sessionToLoad = listResult.items![0].sessionId;
|
||||
await sendRequest('session/load', {
|
||||
cwd: rig.testDir!,
|
||||
sessionId: sessionToLoad,
|
||||
mcpServers: [],
|
||||
});
|
||||
|
||||
const resumeResult = await sendRequest('session/prompt', {
|
||||
sessionId: sessionToLoad,
|
||||
prompt: [{ type: 'text', text: RESUME_PROMPT }],
|
||||
});
|
||||
expect(resumeResult).toBeDefined();
|
||||
|
||||
const sessionsWithUpdates = sessionUpdates
|
||||
.map((update) => update.sessionId)
|
||||
.filter(Boolean);
|
||||
expect(sessionsWithUpdates).toContain(sessionToLoad);
|
||||
} catch (e) {
|
||||
if (stderr.length) {
|
||||
console.error('Agent stderr:', stderr.join(''));
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('returns modes on initialize and allows setting approval mode', async () => {
|
||||
const rig = new TestRig();
|
||||
rig.setup('acp approval mode');
|
||||
|
||||
const { sendRequest, cleanup, stderr } = setupAcpTest(rig);
|
||||
|
||||
try {
|
||||
// Test 1: Initialize and verify modes are returned
|
||||
const initResult = (await sendRequest('initialize', {
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: {
|
||||
fs: { readTextFile: true, writeTextFile: true },
|
||||
},
|
||||
})) as {
|
||||
protocolVersion: number;
|
||||
modes: {
|
||||
currentModeId: string;
|
||||
availableModes: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
expect(initResult).toBeDefined();
|
||||
expect(initResult.protocolVersion).toBe(1);
|
||||
|
||||
// Verify modes data is present
|
||||
expect(initResult.modes).toBeDefined();
|
||||
expect(initResult.modes.currentModeId).toBeDefined();
|
||||
expect(Array.isArray(initResult.modes.availableModes)).toBe(true);
|
||||
expect(initResult.modes.availableModes.length).toBeGreaterThan(0);
|
||||
|
||||
// Verify available modes have expected structure
|
||||
const modeIds = initResult.modes.availableModes.map((m) => m.id);
|
||||
expect(modeIds).toContain('default');
|
||||
expect(modeIds).toContain('yolo');
|
||||
expect(modeIds).toContain('auto-edit');
|
||||
expect(modeIds).toContain('plan');
|
||||
|
||||
// Verify each mode has required fields
|
||||
for (const mode of initResult.modes.availableModes) {
|
||||
expect(mode.id).toBeTruthy();
|
||||
expect(mode.name).toBeTruthy();
|
||||
expect(mode.description).toBeTruthy();
|
||||
}
|
||||
|
||||
// Test 2: Authenticate
|
||||
await sendRequest('authenticate', { methodId: 'openai' });
|
||||
|
||||
// Test 3: Create a new session
|
||||
const newSession = (await sendRequest('session/new', {
|
||||
cwd: rig.testDir!,
|
||||
mcpServers: [],
|
||||
})) as { sessionId: string };
|
||||
expect(newSession.sessionId).toBeTruthy();
|
||||
|
||||
// Test 4: Set approval mode to 'yolo'
|
||||
const setModeResult = (await sendRequest('session/set_mode', {
|
||||
sessionId: newSession.sessionId,
|
||||
modeId: 'yolo',
|
||||
})) as { modeId: string };
|
||||
expect(setModeResult).toBeDefined();
|
||||
expect(setModeResult.modeId).toBe('yolo');
|
||||
|
||||
// Test 5: Set approval mode to 'auto-edit'
|
||||
const setModeResult2 = (await sendRequest('session/set_mode', {
|
||||
sessionId: newSession.sessionId,
|
||||
modeId: 'auto-edit',
|
||||
})) as { modeId: string };
|
||||
expect(setModeResult2).toBeDefined();
|
||||
expect(setModeResult2.modeId).toBe('auto-edit');
|
||||
|
||||
// Test 6: Set approval mode back to 'default'
|
||||
const setModeResult3 = (await sendRequest('session/set_mode', {
|
||||
sessionId: newSession.sessionId,
|
||||
modeId: 'default',
|
||||
})) as { modeId: string };
|
||||
expect(setModeResult3).toBeDefined();
|
||||
expect(setModeResult3.modeId).toBe('default');
|
||||
} catch (e) {
|
||||
if (stderr.length) {
|
||||
console.error('Agent stderr:', stderr.join(''));
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('receives available_commands_update with slash commands after session creation', async () => {
|
||||
const rig = new TestRig();
|
||||
rig.setup('acp slash commands');
|
||||
|
||||
const { sendRequest, cleanup, stderr, sessionUpdates } = setupAcpTest(rig);
|
||||
|
||||
try {
|
||||
// Initialize
|
||||
await sendRequest('initialize', {
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: {
|
||||
fs: { readTextFile: true, writeTextFile: true },
|
||||
},
|
||||
});
|
||||
|
||||
await sendRequest('authenticate', { methodId: 'openai' });
|
||||
|
||||
// Create a new session
|
||||
const newSession = (await sendRequest('session/new', {
|
||||
cwd: rig.testDir!,
|
||||
mcpServers: [],
|
||||
})) as { sessionId: string };
|
||||
expect(newSession.sessionId).toBeTruthy();
|
||||
|
||||
// Wait for available_commands_update to be received
|
||||
await delay(1000);
|
||||
|
||||
// Verify available_commands_update is received
|
||||
const commandsUpdate = sessionUpdates.find(
|
||||
(update) =>
|
||||
update.update?.sessionUpdate === 'available_commands_update',
|
||||
);
|
||||
|
||||
expect(commandsUpdate).toBeDefined();
|
||||
expect(commandsUpdate?.update?.availableCommands).toBeDefined();
|
||||
expect(Array.isArray(commandsUpdate?.update?.availableCommands)).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
// Verify that the 'init' command is present (the only allowed built-in command for ACP)
|
||||
const initCommand = commandsUpdate?.update?.availableCommands?.find(
|
||||
(cmd) => cmd.name === 'init',
|
||||
);
|
||||
expect(initCommand).toBeDefined();
|
||||
expect(initCommand?.description).toBeTruthy();
|
||||
|
||||
// Note: We don't test /init execution here because it triggers a complex
|
||||
// multi-step process (listing files, reading up to 10 files, generating QWEN.md)
|
||||
// that can take 30-60+ seconds, exceeding the request timeout.
|
||||
// The slash command execution path is tested via simpler prompts in other tests.
|
||||
} catch (e) {
|
||||
if (stderr.length) {
|
||||
console.error('Agent stderr:', stderr.join(''));
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it('handles exit plan mode with permission request and mode update notification', async () => {
|
||||
const rig = new TestRig();
|
||||
rig.setup('acp exit plan mode');
|
||||
|
||||
// Track which permission requests we've seen
|
||||
const planModeRequests: PermissionRequest[] = [];
|
||||
|
||||
const { sendRequest, cleanup, stderr, sessionUpdates, permissionRequests } =
|
||||
setupAcpTest(rig, {
|
||||
permissionHandler: (request) => {
|
||||
// Track all permission requests for later verification
|
||||
// Auto-approve exit plan mode requests with "proceed_always" to trigger auto-edit mode
|
||||
if (request.toolCall?.kind === 'switch_mode') {
|
||||
planModeRequests.push(request);
|
||||
// Return proceed_always to switch to auto-edit mode
|
||||
return { optionId: 'proceed_always' };
|
||||
}
|
||||
// Auto-approve all other requests
|
||||
return { optionId: 'proceed_once' };
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Initialize
|
||||
await sendRequest('initialize', {
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: {
|
||||
fs: { readTextFile: true, writeTextFile: true },
|
||||
},
|
||||
});
|
||||
|
||||
await sendRequest('authenticate', { methodId: 'openai' });
|
||||
|
||||
// Create a new session
|
||||
const newSession = (await sendRequest('session/new', {
|
||||
cwd: rig.testDir!,
|
||||
mcpServers: [],
|
||||
})) as { sessionId: string };
|
||||
expect(newSession.sessionId).toBeTruthy();
|
||||
|
||||
// Set mode to 'plan' to enable plan mode
|
||||
const setModeResult = (await sendRequest('session/set_mode', {
|
||||
sessionId: newSession.sessionId,
|
||||
modeId: 'plan',
|
||||
})) as { modeId: string };
|
||||
expect(setModeResult.modeId).toBe('plan');
|
||||
|
||||
// Send a prompt that should trigger the LLM to call exit_plan_mode
|
||||
// The prompt is designed to trigger planning behavior
|
||||
const promptResult = await sendRequest('session/prompt', {
|
||||
sessionId: newSession.sessionId,
|
||||
prompt: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Create a simple hello world function in Python. Make a brief plan and when ready, use the exit_plan_mode tool to present it for approval.',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(promptResult).toBeDefined();
|
||||
|
||||
// Give time for all notifications to be processed
|
||||
await delay(1000);
|
||||
|
||||
// Verify: If exit_plan_mode was called, we should have received:
|
||||
// 1. A permission request with kind: "switch_mode"
|
||||
// 2. A current_mode_update notification after approval
|
||||
|
||||
// Check for switch_mode permission requests
|
||||
const switchModeRequests = permissionRequests.filter(
|
||||
(req) => req.toolCall?.kind === 'switch_mode',
|
||||
);
|
||||
|
||||
// Check for current_mode_update notifications
|
||||
const modeUpdateNotifications = sessionUpdates.filter(
|
||||
(update) => update.update?.sessionUpdate === 'current_mode_update',
|
||||
);
|
||||
|
||||
// If the LLM called exit_plan_mode, verify the flow
|
||||
if (switchModeRequests.length > 0) {
|
||||
// Verify permission request structure
|
||||
const permReq = switchModeRequests[0];
|
||||
expect(permReq.toolCall).toBeDefined();
|
||||
expect(permReq.toolCall?.kind).toBe('switch_mode');
|
||||
expect(permReq.toolCall?.status).toBe('pending');
|
||||
expect(permReq.options).toBeDefined();
|
||||
expect(Array.isArray(permReq.options)).toBe(true);
|
||||
|
||||
// Verify options include appropriate choices
|
||||
const optionKinds = permReq.options?.map((opt) => opt.kind) ?? [];
|
||||
expect(optionKinds).toContain('allow_once');
|
||||
expect(optionKinds).toContain('allow_always');
|
||||
|
||||
// After approval, should have received current_mode_update
|
||||
expect(modeUpdateNotifications.length).toBeGreaterThan(0);
|
||||
|
||||
// Verify mode update structure
|
||||
const modeUpdate = modeUpdateNotifications[0];
|
||||
expect(modeUpdate.sessionId).toBe(newSession.sessionId);
|
||||
expect(modeUpdate.update?.modeId).toBeDefined();
|
||||
// Mode should be auto-edit since we approved with proceed_always
|
||||
expect(modeUpdate.update?.modeId).toBe('auto-edit');
|
||||
}
|
||||
|
||||
// Note: If the LLM didn't call exit_plan_mode, that's acceptable
|
||||
// since LLM behavior is non-deterministic. The test setup and structure
|
||||
// is verified regardless.
|
||||
} catch (e) {
|
||||
if (stderr.length) {
|
||||
console.error('Agent stderr:', stderr.join(''));
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -21,23 +21,21 @@ describe('Interactive Mode', () => {
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'should trigger chat compression with /compress command',
|
||||
async () => {
|
||||
await rig.setup('interactive-compress-test');
|
||||
await rig.setup('interactive-compress-test', {
|
||||
settings: {
|
||||
security: {
|
||||
auth: {
|
||||
selectedType: 'openai',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { ptyProcess } = rig.runInteractive();
|
||||
|
||||
let fullOutput = '';
|
||||
ptyProcess.onData((data) => (fullOutput += data));
|
||||
|
||||
const authDialogAppeared = await rig.waitForText(
|
||||
'How would you like to authenticate',
|
||||
5000,
|
||||
);
|
||||
|
||||
// select the second option if auth dialog come's up
|
||||
if (authDialogAppeared) {
|
||||
ptyProcess.write('2');
|
||||
}
|
||||
|
||||
// Wait for the app to be ready
|
||||
const isReady = await rig.waitForText('Type your message', 15000);
|
||||
expect(
|
||||
@@ -68,49 +66,43 @@ describe('Interactive Mode', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'should handle compression failure on token inflation',
|
||||
async () => {
|
||||
await rig.setup('interactive-compress-test');
|
||||
it.skip('should handle compression failure on token inflation', async () => {
|
||||
await rig.setup('interactive-compress-test', {
|
||||
settings: {
|
||||
security: {
|
||||
auth: {
|
||||
selectedType: 'openai',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { ptyProcess } = rig.runInteractive();
|
||||
const { ptyProcess } = rig.runInteractive();
|
||||
|
||||
let fullOutput = '';
|
||||
ptyProcess.onData((data) => (fullOutput += data));
|
||||
let fullOutput = '';
|
||||
ptyProcess.onData((data) => (fullOutput += data));
|
||||
|
||||
const authDialogAppeared = await rig.waitForText(
|
||||
'How would you like to authenticate',
|
||||
5000,
|
||||
);
|
||||
// Wait for the app to be ready
|
||||
const isReady = await rig.waitForText('Type your message', 25000);
|
||||
expect(isReady, 'CLI did not start up in interactive mode correctly').toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
// select the second option if auth dialog come's up
|
||||
if (authDialogAppeared) {
|
||||
ptyProcess.write('2');
|
||||
}
|
||||
await type(ptyProcess, '/compress');
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
await type(ptyProcess, '\r');
|
||||
|
||||
// Wait for the app to be ready
|
||||
const isReady = await rig.waitForText('Type your message', 25000);
|
||||
expect(
|
||||
isReady,
|
||||
'CLI did not start up in interactive mode correctly',
|
||||
).toBe(true);
|
||||
const foundEvent = await rig.waitForTelemetryEvent(
|
||||
'chat_compression',
|
||||
90000,
|
||||
);
|
||||
expect(foundEvent).toBe(true);
|
||||
|
||||
await type(ptyProcess, '/compress');
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await type(ptyProcess, '\r');
|
||||
const compressionFailed = await rig.waitForText(
|
||||
'Nothing to compress.',
|
||||
25000,
|
||||
);
|
||||
|
||||
const foundEvent = await rig.waitForTelemetryEvent(
|
||||
'chat_compression',
|
||||
90000,
|
||||
);
|
||||
expect(foundEvent).toBe(true);
|
||||
|
||||
const compressionFailed = await rig.waitForText(
|
||||
'compression was not beneficial',
|
||||
25000,
|
||||
);
|
||||
|
||||
expect(compressionFailed).toBe(true);
|
||||
},
|
||||
);
|
||||
expect(compressionFailed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,124 +6,71 @@
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestRig } from './test-helper.js';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
describe('Ctrl+C exit', () => {
|
||||
// (#9782) Temporarily disabling on windows because it is failing on main and every
|
||||
// PR, which is potentially hiding other failures
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'should exit gracefully on second Ctrl+C',
|
||||
async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should exit gracefully on second Ctrl+C');
|
||||
it.skip('should exit gracefully on second Ctrl+C', async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup('should exit gracefully on second Ctrl+C');
|
||||
|
||||
const { ptyProcess, promise } = rig.runInteractive();
|
||||
const { ptyProcess, promise } = rig.runInteractive();
|
||||
|
||||
let output = '';
|
||||
ptyProcess.onData((data) => {
|
||||
output += data;
|
||||
});
|
||||
let output = '';
|
||||
ptyProcess.onData((data) => {
|
||||
output += data;
|
||||
});
|
||||
|
||||
// Wait for the app to be ready by looking for the initial prompt indicator
|
||||
await rig.poll(() => output.includes('▶'), 5000, 100);
|
||||
const isReady = await rig.waitForText('Type your message', 15000);
|
||||
expect(isReady, 'CLI did not start up in interactive mode correctly').toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
// Send first Ctrl+C
|
||||
ptyProcess.write(String.fromCharCode(3));
|
||||
// Send first Ctrl+C
|
||||
ptyProcess.write(String.fromCharCode(3));
|
||||
|
||||
// Wait for the exit prompt
|
||||
await rig.poll(
|
||||
() => output.includes('Press Ctrl+C again to exit'),
|
||||
1500,
|
||||
50,
|
||||
);
|
||||
// Wait for the exit prompt
|
||||
const showedExitPrompt = await rig.poll(
|
||||
() => output.includes('Press Ctrl+C again to exit'),
|
||||
1500,
|
||||
50,
|
||||
);
|
||||
expect(showedExitPrompt, `Exit prompt not shown. Output: ${output}`).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
// Send second Ctrl+C
|
||||
ptyProcess.write(String.fromCharCode(3));
|
||||
// Send second Ctrl+C
|
||||
ptyProcess.write(String.fromCharCode(3));
|
||||
|
||||
const result = await promise;
|
||||
// Wait for process exit with timeout to fail fast
|
||||
const EXIT_TIMEOUT = 5000;
|
||||
const result = await Promise.race([
|
||||
promise,
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
`Process did not exit within ${EXIT_TIMEOUT}ms. Output: ${output}`,
|
||||
),
|
||||
),
|
||||
EXIT_TIMEOUT,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
// Expect a graceful exit (code 0)
|
||||
expect(
|
||||
result.exitCode,
|
||||
`Process exited with code ${result.exitCode}. Output: ${result.output}`,
|
||||
).toBe(0);
|
||||
// Expect a graceful exit (code 0)
|
||||
expect(
|
||||
result.exitCode,
|
||||
`Process exited with code ${result.exitCode}. Output: ${result.output}`,
|
||||
).toBe(0);
|
||||
|
||||
// Check that the quitting message is displayed
|
||||
const quittingMessage = 'Agent powering down. Goodbye!';
|
||||
// The regex below is intentionally matching the ESC control character (\x1b)
|
||||
// to strip ANSI color codes from the terminal output.
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
expect(cleanOutput).toContain(quittingMessage);
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'should exit gracefully on second Ctrl+C when calling a tool',
|
||||
async () => {
|
||||
const rig = new TestRig();
|
||||
await rig.setup(
|
||||
'should exit gracefully on second Ctrl+C when calling a tool',
|
||||
);
|
||||
|
||||
const childProcessFile = 'child_process_file.txt';
|
||||
rig.createFile(
|
||||
'wait.js',
|
||||
`setTimeout(() => require('fs').writeFileSync('${childProcessFile}', 'done'), 5000)`,
|
||||
);
|
||||
|
||||
const { ptyProcess, promise } = rig.runInteractive();
|
||||
|
||||
let output = '';
|
||||
ptyProcess.onData((data) => {
|
||||
output += data;
|
||||
});
|
||||
|
||||
// Wait for the app to be ready by looking for the initial prompt indicator
|
||||
await rig.poll(() => output.includes('▶'), 5000, 100);
|
||||
|
||||
ptyProcess.write('use the tool to run "node -e wait.js"\n');
|
||||
|
||||
await rig.poll(() => output.includes('Shell'), 5000, 100);
|
||||
|
||||
// Send first Ctrl+C
|
||||
ptyProcess.write(String.fromCharCode(3));
|
||||
|
||||
// Wait for the exit prompt
|
||||
await rig.poll(
|
||||
() => output.includes('Press Ctrl+C again to exit'),
|
||||
1500,
|
||||
50,
|
||||
);
|
||||
|
||||
// Send second Ctrl+C
|
||||
ptyProcess.write(String.fromCharCode(3));
|
||||
|
||||
const result = await promise;
|
||||
|
||||
// Expect a graceful exit (code 0)
|
||||
expect(
|
||||
result.exitCode,
|
||||
`Process exited with code ${result.exitCode}. Output: ${result.output}`,
|
||||
).toBe(0);
|
||||
|
||||
// Check that the quitting message is displayed
|
||||
const quittingMessage = 'Agent powering down. Goodbye!';
|
||||
// The regex below is intentionally matching the ESC control character (\x1b)
|
||||
// to strip ANSI color codes from the terminal output.
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
expect(cleanOutput).toContain(quittingMessage);
|
||||
|
||||
// Check that the child process was terminated and did not create the file.
|
||||
const childProcessFileExists = fs.existsSync(
|
||||
path.join(rig.testDir!, childProcessFile),
|
||||
);
|
||||
expect(
|
||||
childProcessFileExists,
|
||||
'Child process file should not exist',
|
||||
).toBe(false);
|
||||
},
|
||||
);
|
||||
// Check that the quitting message is displayed
|
||||
const quittingMessage = 'Agent powering down. Goodbye!';
|
||||
// The regex below is intentionally matching the ESC control character (\x1b)
|
||||
// to strip ANSI color codes from the terminal output.
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
expect(cleanOutput).toContain(quittingMessage);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,21 +22,19 @@ describe('Interactive file system', () => {
|
||||
'should perform a read-then-write sequence in interactive mode',
|
||||
async () => {
|
||||
const fileName = 'version.txt';
|
||||
await rig.setup('interactive-read-then-write');
|
||||
await rig.setup('interactive-read-then-write', {
|
||||
settings: {
|
||||
security: {
|
||||
auth: {
|
||||
selectedType: 'openai',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
rig.createFile(fileName, '1.0.0');
|
||||
|
||||
const { ptyProcess } = rig.runInteractive();
|
||||
|
||||
const authDialogAppeared = await rig.waitForText(
|
||||
'How would you like to authenticate',
|
||||
5000,
|
||||
);
|
||||
|
||||
// select the second option if auth dialog come's up
|
||||
if (authDialogAppeared) {
|
||||
ptyProcess.write('2');
|
||||
}
|
||||
|
||||
// Wait for the app to be ready
|
||||
const isReady = await rig.waitForText('Type your message', 15000);
|
||||
expect(
|
||||
|
||||