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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "qwen-code",
"interface": {
"displayName": "Qwen Code"
},
"plugins": [
{
"name": "qwen",
"source": {
"source": "local",
"path": "./plugins/qwen-codex"
},
"policy": {
"installation": "AVAILABLE"
},
"category": "Developer Tools"
}
]
}
6 changes: 3 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"name": "doudouOUC"
},
"metadata": {
"description": "Qwen Code plugins for Claude Code.",
"version": "0.4.0"
"description": "Qwen Code plugins for Claude Code and Codex CLI.",
"version": "0.5.0"
},
"plugins": [
{
"name": "qwen",
"description": "Run Qwen Code review from Claude Code.",
"version": "0.4.0",
"version": "0.5.0",
"author": {
"name": "doudouOUC"
},
Expand Down
89 changes: 25 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Qwen Code Plugin for Claude Code
# Qwen Code Review Plugin

Use Qwen Code review from inside Claude Code.
Use Qwen Code review from Claude Code and Codex CLI.

This plugin adds:
This repository contains two plugins for the same Qwen Code review capability:

- `/qwen:setup` to check whether the local `qwen` CLI is available.
- `/qwen:review` to run Qwen Code's built-in `/review` skill against the current repository.
- `/qwen:status`, `/qwen:result`, and `/qwen:cancel` to manage long-running review jobs.
- **Claude Code plugin** (`plugins/qwen/`) — adds `/qwen:review` and job management commands.
- **Codex CLI plugin** (`plugins/qwen-codex/`) — adds the `$qwen-review` skill.

Both plugins call the local `qwen` CLI to run Qwen Code's built-in `/review` skill.

## Requirements

- Claude Code with plugin support.
- Node.js 18.18 or later.
- Qwen Code installed and available on `PATH`.

Expand All @@ -20,33 +20,23 @@ Install Qwen Code if needed:
npm install -g @qwen-code/qwen-code
```

## Install

Add this repository as a Claude Code plugin marketplace:
## Install — Claude Code

```bash
/plugin marketplace add doudouOUC/qwen-code-plugin-cc
```

Install the plugin:

```bash
/plugin install qwen@qwen-code
```

Reload plugins:

```bash
/reload-plugins
/qwen:setup
```

Check setup:
## Install — Codex CLI

```bash
/qwen:setup
codex plugin marketplace add doudouOUC/qwen-code-plugin-cc
codex plugin add qwen@qwen-code
```

## Usage
## Claude Code Usage

Review local changes:

Expand All @@ -71,13 +61,6 @@ Run with a specific Qwen Code model:
/qwen:review -m qwen3-coder-plus 123 --comment
```

Recommended model style for larger reviews:

```bash
/qwen:review --model qwen3.7-max
/qwen:review --model <deepseek-model-name>
```

Review a pull request:

```bash
Expand All @@ -96,60 +79,38 @@ Post Qwen Code inline comments on a PR:
/qwen:review 123 --comment
```

Check a long-running review while it is still running:
Manage jobs:

```bash
/qwen:status
/qwen:status qwen-review-1234abcd
```

Read a finished review again:

```bash
/qwen:result qwen-review-1234abcd
```

Cancel a running review:

```bash
/qwen:cancel qwen-review-1234abcd
```

## Notes

`/qwen:review` is review-only from Claude Code's side. It forwards your review target arguments to Qwen Code's `/review` skill and appends a run-scoped review-only system prompt. In the default background mode Claude Code owns the background command, so it can report completion and surface the final Qwen Code output automatically. Use `/qwen:status` to inspect an active run, `/qwen:cancel` to stop it, and `/qwen:result <job-id>` if you want to read a stored result again. With `--wait`, it prints Qwen Code output unchanged in the current turn.
## Codex CLI / App Usage

`--wait`, `--background`, `--model <model>`, `--model=<model>`, and `-m <model>` are handled by this plugin. Other arguments are passed to Qwen Code's `/review` prompt unchanged.
Once installed, the `$qwen-review` skill is available in Codex CLI and Codex App. Ask Codex to review your code:

## How Qwen Code review runs
```
Use $qwen-review to review the current changes
```

This plugin does not implement its own reviewer. It starts the local Qwen Code
CLI and asks it to run its built-in `/review` skill. Qwen Code decides the
review target from the forwarded arguments:
Codex will run the `qwen` CLI with review-only flags and present the findings.

- no extra arguments: local uncommitted changes
- PR number or PR URL: that pull request's diff and PR context
- file path: that file's diff, or the current file content when there is no diff
## Notes

Qwen Code review is usually more expensive than a single prompt over `git diff`.
It can collect PR context, load project review rules, run deterministic checks,
inspect changed files, and synthesize verified findings. For large PRs this can
send repeated overlapping repository and review context to the model.
`/qwen:review` (Claude Code) and `$qwen-review` (Codex) are both review-only. They forward your review target arguments to Qwen Code's `/review` skill and append a review-only system prompt.

For that reason, prefer strong models with good prompt-cache behavior for larger
reviews. In environments where they are available, `qwen3.7-max` and DeepSeek
family models are good candidates because high cache hit rates can reduce repeat
review latency and cost. The plugin only forwards the model name to Qwen Code;
it does not validate model availability, so use the exact model identifier
configured in your Qwen Code environment.
`--wait`, `--background`, `--model <model>`, `--model=<model>`, and `-m <model>` are handled by the Claude Code plugin. Other arguments are passed to Qwen Code's `/review` prompt unchanged.

The companion runs Qwen Code with `--approval-mode yolo` so headless review can execute the analysis commands required by `/review`. Sandboxing is enabled by default. If your environment cannot run Qwen Code sandboxing, explicitly disable it with:
The Claude Code companion runs Qwen Code with `--approval-mode yolo` so headless review can execute the analysis commands required by `/review`. Sandboxing is enabled by default. If your environment cannot run Qwen Code sandboxing, explicitly disable it with:

```bash
export QWEN_PLUGIN_NO_SANDBOX=1
```

Background job state is stored outside the project under:
Claude Code background job state is stored under:

```text
~/.qwen-code-plugin-cc/workspaces/
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "qwen-code-plugin-cc",
"version": "0.4.0",
"version": "0.5.0",
"private": true,
"type": "module",
"description": "Claude Code plugin for running Qwen Code review.",
"description": "Qwen Code review plugin for Claude Code and Codex CLI.",
"license": "Apache-2.0",
"scripts": {
"test": "node --test tests/*.test.mjs"
Expand Down
26 changes: 26 additions & 0 deletions plugins/qwen-codex/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "qwen",
"version": "0.5.0",
"description": "Run Qwen Code review from Codex CLI.",
"author": {
"name": "doudouOUC"
},
"homepage": "https://github.com/doudouOUC/qwen-code-plugin-cc",
"repository": "https://github.com/doudouOUC/qwen-code-plugin-cc",
"license": "Apache-2.0",
"keywords": ["qwen", "review", "code-review"],
"skills": "./skills/",
"interface": {
"displayName": "Qwen Code Review",
"shortDescription": "Run Qwen Code review from Codex.",
"longDescription": "Use Qwen Code's /review skill to review local changes, pull requests, or specific files from within Codex CLI or Codex App.",
"developerName": "doudouOUC",
"category": "Developer Tools",
"capabilities": ["Read"],
"defaultPrompt": [
"Use $qwen-review to review the current changes",
"Use $qwen-review to review PR #",
"Use $qwen-review to review src/"
]
}
}
122 changes: 122 additions & 0 deletions plugins/qwen-codex/lib/argv.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
export function scanRawArgumentString(raw) {
const source = String(raw ?? '');
const tokens = [];
let current = '';
let start = null;
let quote = null;
let escaped = false;
let quoted = false;
let escapedToken = false;

function startToken(index) {
if (start === null) {
start = index;
}
}

function finishToken(end) {
if (start === null) {
return;
}
tokens.push({
value: current,
start,
end,
quoted,
escaped: escapedToken,
});
current = '';
start = null;
quoted = false;
escapedToken = false;
}

for (let index = 0; index < source.length; index += 1) {
const char = source[index];
if (escaped) {
current += char;
escaped = false;
continue;
}
if (char === '\\') {
startToken(index);
escaped = true;
escapedToken = true;
continue;
}
if (quote) {
if (char === quote) {
quote = null;
} else {
current += char;
}
continue;
}
if (char === '"' || char === "'") {
startToken(index);
quote = char;
quoted = true;
continue;
}
if (/\s/.test(char)) {
finishToken(index);
continue;
}
startToken(index);
current += char;
}

if (escaped) {
current += '\\';
}
finishToken(source.length);
return tokens;
}

export function splitRawArgumentString(raw) {
return scanRawArgumentString(raw).map((token) => token.value);
}

export function normalizeArgv(args) {
return args.length === 1 ? splitRawArgumentString(args[0]) : args;
}

export function isModeFlag(token, flag) {
return token.value === flag && !token.quoted && !token.escaped;
}

export function isModelEqualsFlag(token) {
return (
!token.quoted &&
!token.escaped &&
token.value.startsWith('--model=') &&
token.value.length > '--model='.length
);
}

export function validateModelValue(flag, value) {
if (!value || value.startsWith('-')) {
throw new Error(`Missing value for ${flag}.`);
}
return value;
}

export function removeRawTokenSpans(raw, spans) {
const source = String(raw ?? '');
let result = String(raw ?? '');
const expandedSpans = spans.map((span) => {
let end = span.end;
while (end < source.length && /\s/.test(source[end])) {
end += 1;
}
return {
...span,
end,
};
});

for (const span of expandedSpans.sort((left, right) => right.start - left.start)) {
result = `${result.slice(0, span.start)}${result.slice(span.end)}`;
}
return result.trim();
}
Loading