@dhrupo/codex-claude-bridge 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -17
- package/package.json +8 -3
- package/src/claude-cli.mjs +15 -49
- package/src/codex-claude-command.mjs +282 -11
- package/src/codex-cli.mjs +6 -42
- package/src/process-utils.mjs +53 -0
- package/src/render.mjs +104 -0
- package/tests/claude-cli.test.mjs +0 -57
- package/tests/codex-cli.test.mjs +0 -52
- package/tests/server.test.mjs +0 -12
- package/tests/tools.test.mjs +0 -19
package/README.md
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@dhrupo/codex-claude-bridge)
|
|
2
|
+
[](https://github.com/dhrupo/codex-claude-bridge/actions/workflows/ci.yml)
|
|
3
|
+
[](https://github.com/dhrupo/codex-claude-bridge/actions/workflows/publish.yml)
|
|
4
|
+
|
|
1
5
|
# codex-claude-bridge
|
|
2
6
|
|
|
3
7
|
Use Claude Code from inside Codex.
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
`@dhrupo/codex-claude-bridge` exposes Claude Code as MCP tools that Codex can call, and also ships a `codex-claude` helper command for common workflows.
|
|
6
10
|
|
|
7
11
|
## What You Get
|
|
8
12
|
|
|
9
13
|
- `codex-claude-mcp`: an MCP server that wraps the local `claude` CLI
|
|
10
|
-
- `codex-claude`: a helper command that
|
|
14
|
+
- `codex-claude`: a helper command that asks Codex to call Claude tools
|
|
11
15
|
- Three MCP tools:
|
|
12
16
|
- `claude_ask`
|
|
13
17
|
- `claude_review`
|
|
14
18
|
- `claude_delegate`
|
|
19
|
+
- Extra helper workflows:
|
|
20
|
+
- `codex-claude status`
|
|
21
|
+
- `codex-claude doctor`
|
|
22
|
+
- `codex-claude compare`
|
|
15
23
|
|
|
16
24
|
## Requirements
|
|
17
25
|
|
|
@@ -19,7 +27,7 @@ This package exposes Claude Code as MCP tools that Codex can call, and also ship
|
|
|
19
27
|
- `codex` installed and authenticated
|
|
20
28
|
- `claude` installed and authenticated
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
Quick checks:
|
|
23
31
|
|
|
24
32
|
```bash
|
|
25
33
|
codex --version
|
|
@@ -35,37 +43,34 @@ Install the package:
|
|
|
35
43
|
npm install -g @dhrupo/codex-claude-bridge
|
|
36
44
|
```
|
|
37
45
|
|
|
38
|
-
Register the MCP
|
|
46
|
+
Register the MCP bridge in Codex:
|
|
39
47
|
|
|
40
48
|
```bash
|
|
41
49
|
codex-claude install
|
|
42
50
|
```
|
|
43
51
|
|
|
44
|
-
That
|
|
52
|
+
That creates a global Codex MCP server named `claude-bridge`.
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
Manual alternatives:
|
|
47
55
|
|
|
48
56
|
```bash
|
|
49
57
|
codex mcp add claude-bridge -- codex-claude-mcp
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Or without a global npm install:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
58
|
codex mcp add claude-bridge -- npx -y @dhrupo/codex-claude-bridge
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
Verify:
|
|
59
62
|
|
|
60
63
|
```bash
|
|
61
64
|
codex mcp list
|
|
65
|
+
codex-claude status
|
|
66
|
+
codex-claude doctor
|
|
62
67
|
```
|
|
63
68
|
|
|
64
69
|
## Usage
|
|
65
70
|
|
|
66
|
-
###
|
|
71
|
+
### Helper Commands
|
|
67
72
|
|
|
68
|
-
Ask Claude
|
|
73
|
+
Ask Claude through Codex:
|
|
69
74
|
|
|
70
75
|
```bash
|
|
71
76
|
codex-claude ask "Summarize the current repository."
|
|
@@ -78,12 +83,40 @@ codex-claude review
|
|
|
78
83
|
codex-claude review "Review the current workspace for bugs and missing tests."
|
|
79
84
|
```
|
|
80
85
|
|
|
86
|
+
Run a review and compare Claude vs Codex side by side:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
codex-claude review --compare "Review the current workspace for bugs and missing tests."
|
|
90
|
+
```
|
|
91
|
+
|
|
81
92
|
Delegate an implementation task:
|
|
82
93
|
|
|
83
94
|
```bash
|
|
84
95
|
codex-claude delegate "Fix the failing tests with the smallest safe patch."
|
|
85
96
|
```
|
|
86
97
|
|
|
98
|
+
Compare Claude and Codex on the same prompt in one readable report:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
codex-claude compare "Review this architecture and suggest the safest refactor."
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use separate model choices when comparing:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
codex-claude compare \
|
|
108
|
+
--codex-model gpt-5.4 \
|
|
109
|
+
--claude-model sonnet \
|
|
110
|
+
"Summarize the tradeoffs in this module."
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Write reports to disk:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
codex-claude compare --out compare.md "Review this change and compare results."
|
|
117
|
+
codex-claude status --json --out status.json
|
|
118
|
+
```
|
|
119
|
+
|
|
87
120
|
If you are outside a git repository, add:
|
|
88
121
|
|
|
89
122
|
```bash
|
|
@@ -96,7 +129,16 @@ Example:
|
|
|
96
129
|
codex-claude ask --cwd /path/to/project --skip-git-repo-check "Reply with exactly OK."
|
|
97
130
|
```
|
|
98
131
|
|
|
99
|
-
|
|
132
|
+
You can also pass through Claude-focused options when using `ask`, `review`, or `delegate`:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
codex-claude review \
|
|
136
|
+
--model sonnet \
|
|
137
|
+
--permission-mode dontAsk \
|
|
138
|
+
--append-system-prompt "Focus on reliability and regression risk."
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Direct Codex MCP Usage
|
|
100
142
|
|
|
101
143
|
Once installed, Codex can call these tools directly:
|
|
102
144
|
|
|
@@ -104,7 +146,7 @@ Once installed, Codex can call these tools directly:
|
|
|
104
146
|
- `claude_review`: stricter review pass
|
|
105
147
|
- `claude_delegate`: writable implementation/delegation flow
|
|
106
148
|
|
|
107
|
-
Example
|
|
149
|
+
Example:
|
|
108
150
|
|
|
109
151
|
```bash
|
|
110
152
|
codex exec --cd /path/to/project --skip-git-repo-check \
|
|
@@ -116,10 +158,34 @@ codex exec --cd /path/to/project --skip-git-repo-check \
|
|
|
116
158
|
- Codex discovers the `claude-bridge` MCP server
|
|
117
159
|
- Codex calls one of the bridge tools
|
|
118
160
|
- The bridge runs the local `claude --print` command
|
|
119
|
-
-
|
|
161
|
+
- Claude output is returned through MCP back to Codex
|
|
120
162
|
|
|
121
163
|
This is an MCP bridge, not a native Codex slash-command plugin.
|
|
122
164
|
|
|
165
|
+
## Status And Comparison
|
|
166
|
+
|
|
167
|
+
`codex-claude status` checks:
|
|
168
|
+
|
|
169
|
+
- whether `codex` is installed
|
|
170
|
+
- whether `claude` is installed
|
|
171
|
+
- whether Claude auth is available
|
|
172
|
+
- whether the `claude-bridge` MCP server is registered in Codex
|
|
173
|
+
|
|
174
|
+
`codex-claude doctor` adds a simple pass/fail diagnostic summary and suggested fixes.
|
|
175
|
+
|
|
176
|
+
`codex-claude compare` runs both tools on the same prompt:
|
|
177
|
+
|
|
178
|
+
- Claude runs directly through `claude --print`
|
|
179
|
+
- Codex runs directly through `codex exec`
|
|
180
|
+
- the command returns a single report with:
|
|
181
|
+
- Claude output
|
|
182
|
+
- Codex output
|
|
183
|
+
- a line-by-line diff block for quick human review
|
|
184
|
+
|
|
185
|
+
`--json` returns machine-readable report data for `status`, `doctor`, and `compare`.
|
|
186
|
+
|
|
187
|
+
`--out <file>` writes the rendered or JSON report to disk and also prints it to stdout.
|
|
188
|
+
|
|
123
189
|
## Development
|
|
124
190
|
|
|
125
191
|
Install dependencies:
|
|
@@ -140,6 +206,20 @@ Start the MCP server directly:
|
|
|
140
206
|
npm start
|
|
141
207
|
```
|
|
142
208
|
|
|
209
|
+
## Publishing
|
|
210
|
+
|
|
211
|
+
This package is published on npm as:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
@dhrupo/codex-claude-bridge
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The repo includes a GitHub Actions publish workflow. To publish a new version:
|
|
218
|
+
|
|
219
|
+
1. Bump `package.json`
|
|
220
|
+
2. Commit and push to `main`
|
|
221
|
+
3. Create and push a matching tag like `v0.1.1`
|
|
222
|
+
|
|
143
223
|
## Troubleshooting
|
|
144
224
|
|
|
145
225
|
If the bridge works but Claude returns an auth or access error, test Claude directly first:
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhrupo/codex-claude-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Expose Claude Code as MCP tools for Codex.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"bin",
|
|
8
|
+
"src",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
6
11
|
"bin": {
|
|
7
|
-
"codex-claude": "
|
|
8
|
-
"codex-claude-mcp": "
|
|
12
|
+
"codex-claude": "bin/codex-claude.mjs",
|
|
13
|
+
"codex-claude-mcp": "bin/codex-claude-mcp.mjs"
|
|
9
14
|
},
|
|
10
15
|
"engines": {
|
|
11
16
|
"node": ">=18.18.0"
|
package/src/claude-cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runCommand } from "./process-utils.mjs";
|
|
2
2
|
|
|
3
3
|
function pushListOption(args, flag, values) {
|
|
4
4
|
if (!Array.isArray(values) || values.length === 0) {
|
|
@@ -50,53 +50,19 @@ export async function runClaude(input, options = {}) {
|
|
|
50
50
|
const cwd = input.cwd || options.cwd || process.cwd();
|
|
51
51
|
const args = buildClaudeArgs(input, options.defaults);
|
|
52
52
|
const outputFormat = input.outputFormat || options.defaults?.outputFormat || "text";
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
...process.env,
|
|
59
|
-
...options.env
|
|
60
|
-
},
|
|
61
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
let stdout = "";
|
|
65
|
-
let stderr = "";
|
|
66
|
-
|
|
67
|
-
child.stdout.on("data", (chunk) => {
|
|
68
|
-
stdout += String(chunk);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
child.stderr.on("data", (chunk) => {
|
|
72
|
-
stderr += String(chunk);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
child.on("error", (error) => {
|
|
76
|
-
reject(
|
|
77
|
-
new Error(`Failed to start Claude CLI. Ensure \`claude\` is installed and on PATH. ${error.message}`)
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
child.on("close", (code) => {
|
|
82
|
-
const trimmedStdout = stdout.trim();
|
|
83
|
-
const trimmedStderr = stderr.trim();
|
|
84
|
-
|
|
85
|
-
if (code !== 0) {
|
|
86
|
-
reject(
|
|
87
|
-
new Error(trimmedStderr || trimmedStdout || `Claude CLI exited with status ${code ?? "unknown"}.`)
|
|
88
|
-
);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
resolve({
|
|
93
|
-
command,
|
|
94
|
-
cwd,
|
|
95
|
-
args,
|
|
96
|
-
outputFormat,
|
|
97
|
-
stdout: normalizeStdout(trimmedStdout, outputFormat),
|
|
98
|
-
stderr: trimmedStderr
|
|
99
|
-
});
|
|
100
|
-
});
|
|
53
|
+
const result = await runCommand(command, args, {
|
|
54
|
+
cwd,
|
|
55
|
+
env: options.env
|
|
56
|
+
}).catch((error) => {
|
|
57
|
+
throw new Error(`Failed to start Claude CLI. Ensure \`claude\` is installed and on PATH. ${error.message}`);
|
|
101
58
|
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
command,
|
|
62
|
+
cwd,
|
|
63
|
+
args,
|
|
64
|
+
outputFormat,
|
|
65
|
+
stdout: normalizeStdout(result.stdout, outputFormat),
|
|
66
|
+
stderr: result.stderr
|
|
67
|
+
};
|
|
102
68
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
|
|
4
5
|
import { DEFAULT_MCP_SERVER_NAME } from "./constants.mjs";
|
|
6
|
+
import { runClaude } from "./claude-cli.mjs";
|
|
5
7
|
import { buildCodexExecArgs, buildCodexMcpAddArgs, runCodex } from "./codex-cli.mjs";
|
|
8
|
+
import { runCommand } from "./process-utils.mjs";
|
|
9
|
+
import { formatCompareReport, formatDoctorReport, formatStatusReport, toJsonReport } from "./render.mjs";
|
|
6
10
|
|
|
7
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
12
|
const serverScript = path.resolve(__dirname, "../bin/codex-claude-mcp.mjs");
|
|
@@ -11,9 +15,12 @@ function usage() {
|
|
|
11
15
|
return [
|
|
12
16
|
"Usage:",
|
|
13
17
|
" codex-claude install [--name <server-name>]",
|
|
14
|
-
" codex-claude
|
|
15
|
-
" codex-claude
|
|
16
|
-
" codex-claude
|
|
18
|
+
" codex-claude status [--name <server-name>] [--json] [--out <file>]",
|
|
19
|
+
" codex-claude doctor [--name <server-name>] [--json] [--out <file>]",
|
|
20
|
+
" codex-claude ask [--cwd <dir>] [--model <model>] [--permission-mode <mode>] [--skip-git-repo-check] <prompt>",
|
|
21
|
+
" codex-claude review [--compare] [--cwd <dir>] [--model <model>] [--permission-mode <mode>] [--skip-git-repo-check] [prompt]",
|
|
22
|
+
" codex-claude delegate [--cwd <dir>] [--model <model>] [--permission-mode <mode>] [--skip-git-repo-check] <prompt>",
|
|
23
|
+
" codex-claude compare [--cwd <dir>] [--codex-model <model>] [--claude-model <model>] [--skip-git-repo-check] [--json] [--out <file>] <prompt>"
|
|
17
24
|
].join("\n");
|
|
18
25
|
}
|
|
19
26
|
|
|
@@ -27,6 +34,15 @@ function parseCommand(argv) {
|
|
|
27
34
|
command,
|
|
28
35
|
cwd: undefined,
|
|
29
36
|
model: undefined,
|
|
37
|
+
claudeModel: undefined,
|
|
38
|
+
codexModel: undefined,
|
|
39
|
+
permissionMode: undefined,
|
|
40
|
+
appendSystemPrompt: undefined,
|
|
41
|
+
outputFormat: "text",
|
|
42
|
+
maxBudgetUsd: undefined,
|
|
43
|
+
json: false,
|
|
44
|
+
out: undefined,
|
|
45
|
+
compare: false,
|
|
30
46
|
skipGitRepoCheck: false,
|
|
31
47
|
serverName: DEFAULT_MCP_SERVER_NAME,
|
|
32
48
|
prompt: ""
|
|
@@ -44,15 +60,58 @@ function parseCommand(argv) {
|
|
|
44
60
|
options.model = rest[index];
|
|
45
61
|
continue;
|
|
46
62
|
}
|
|
63
|
+
if (token === "--claude-model") {
|
|
64
|
+
index += 1;
|
|
65
|
+
options.claudeModel = rest[index];
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (token === "--codex-model") {
|
|
69
|
+
index += 1;
|
|
70
|
+
options.codexModel = rest[index];
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
47
73
|
if (token === "--name") {
|
|
48
74
|
index += 1;
|
|
49
75
|
options.serverName = rest[index];
|
|
50
76
|
continue;
|
|
51
77
|
}
|
|
78
|
+
if (token === "--permission-mode") {
|
|
79
|
+
index += 1;
|
|
80
|
+
options.permissionMode = rest[index];
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (token === "--append-system-prompt") {
|
|
84
|
+
index += 1;
|
|
85
|
+
options.appendSystemPrompt = rest[index];
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (token === "--output-format") {
|
|
89
|
+
index += 1;
|
|
90
|
+
options.outputFormat = rest[index];
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (token === "--max-budget-usd") {
|
|
94
|
+
index += 1;
|
|
95
|
+
options.maxBudgetUsd = Number(rest[index]);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
52
98
|
if (token === "--skip-git-repo-check") {
|
|
53
99
|
options.skipGitRepoCheck = true;
|
|
54
100
|
continue;
|
|
55
101
|
}
|
|
102
|
+
if (token === "--json") {
|
|
103
|
+
options.json = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (token === "--compare") {
|
|
107
|
+
options.compare = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (token === "--out") {
|
|
111
|
+
index += 1;
|
|
112
|
+
options.out = rest[index];
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
56
115
|
options.prompt = rest.slice(index).join(" ");
|
|
57
116
|
break;
|
|
58
117
|
}
|
|
@@ -81,9 +140,22 @@ export function buildCodexClaudePrompt(input) {
|
|
|
81
140
|
const call = {
|
|
82
141
|
prompt,
|
|
83
142
|
cwd: input.cwd || process.cwd(),
|
|
84
|
-
outputFormat: "text"
|
|
143
|
+
outputFormat: input.outputFormat || "text"
|
|
85
144
|
};
|
|
86
145
|
|
|
146
|
+
if (input.model) {
|
|
147
|
+
call.model = input.model;
|
|
148
|
+
}
|
|
149
|
+
if (input.permissionMode) {
|
|
150
|
+
call.permissionMode = input.permissionMode;
|
|
151
|
+
}
|
|
152
|
+
if (input.appendSystemPrompt) {
|
|
153
|
+
call.appendSystemPrompt = input.appendSystemPrompt;
|
|
154
|
+
}
|
|
155
|
+
if (typeof input.maxBudgetUsd === "number" && !Number.isNaN(input.maxBudgetUsd)) {
|
|
156
|
+
call.maxBudgetUsd = input.maxBudgetUsd;
|
|
157
|
+
}
|
|
158
|
+
|
|
87
159
|
return [
|
|
88
160
|
`Use the MCP tool \`${toolName}\` exactly once.`,
|
|
89
161
|
"Do not solve this task yourself.",
|
|
@@ -93,21 +165,40 @@ export function buildCodexClaudePrompt(input) {
|
|
|
93
165
|
].join("\n");
|
|
94
166
|
}
|
|
95
167
|
|
|
96
|
-
async function installBridge(input) {
|
|
168
|
+
async function installBridge(input, options = {}) {
|
|
169
|
+
const cwd = input.cwd || process.cwd();
|
|
170
|
+
const codexBinary = options.codexCommand || "codex";
|
|
171
|
+
const existing = await runCommand(codexBinary, ["mcp", "list"], {
|
|
172
|
+
cwd,
|
|
173
|
+
rejectOnNonZero: false
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const serverName = input.serverName || DEFAULT_MCP_SERVER_NAME;
|
|
177
|
+
const alreadyInstalled = (existing.stdout || "")
|
|
178
|
+
.split("\n")
|
|
179
|
+
.some((line) => line.trim().startsWith(serverName));
|
|
180
|
+
|
|
181
|
+
if (alreadyInstalled) {
|
|
182
|
+
await runCommand(codexBinary, ["mcp", "remove", serverName], {
|
|
183
|
+
cwd,
|
|
184
|
+
rejectOnNonZero: false
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
97
188
|
const args = buildCodexMcpAddArgs({
|
|
98
|
-
serverName
|
|
189
|
+
serverName,
|
|
99
190
|
serverScript
|
|
100
191
|
});
|
|
101
192
|
|
|
102
193
|
return runCodex(args, {
|
|
103
|
-
cwd
|
|
194
|
+
cwd
|
|
104
195
|
});
|
|
105
196
|
}
|
|
106
197
|
|
|
107
198
|
async function runViaCodex(input) {
|
|
108
199
|
const args = buildCodexExecArgs({
|
|
109
200
|
cwd: input.cwd,
|
|
110
|
-
model: input.model,
|
|
201
|
+
model: input.codexModel || input.model,
|
|
111
202
|
skipGitRepoCheck: input.skipGitRepoCheck,
|
|
112
203
|
prompt: buildCodexClaudePrompt(input)
|
|
113
204
|
});
|
|
@@ -117,6 +208,156 @@ async function runViaCodex(input) {
|
|
|
117
208
|
});
|
|
118
209
|
}
|
|
119
210
|
|
|
211
|
+
export async function collectStatus(input, options = {}) {
|
|
212
|
+
const cwd = input.cwd || process.cwd();
|
|
213
|
+
const serverName = input.serverName || DEFAULT_MCP_SERVER_NAME;
|
|
214
|
+
const notes = [];
|
|
215
|
+
|
|
216
|
+
const codexVersion = await runCommand(options.codexCommand || "codex", ["--version"], {
|
|
217
|
+
cwd,
|
|
218
|
+
rejectOnNonZero: false
|
|
219
|
+
});
|
|
220
|
+
const claudeVersion = await runCommand(options.claudeCommand || "claude", ["--version"], {
|
|
221
|
+
cwd,
|
|
222
|
+
rejectOnNonZero: false
|
|
223
|
+
});
|
|
224
|
+
const claudeAuth = await runCommand(options.claudeCommand || "claude", ["auth", "status"], {
|
|
225
|
+
cwd,
|
|
226
|
+
rejectOnNonZero: false
|
|
227
|
+
});
|
|
228
|
+
const mcpList = await runCommand(options.codexCommand || "codex", ["mcp", "list"], {
|
|
229
|
+
cwd,
|
|
230
|
+
rejectOnNonZero: false
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (codexVersion.code !== 0) {
|
|
234
|
+
notes.push("Codex CLI is not available or not executable.");
|
|
235
|
+
}
|
|
236
|
+
if (claudeVersion.code !== 0) {
|
|
237
|
+
notes.push("Claude CLI is not available or not executable.");
|
|
238
|
+
}
|
|
239
|
+
if (claudeAuth.code !== 0) {
|
|
240
|
+
notes.push("Claude auth check failed. Run `claude auth login`.");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const mcpLine = (mcpList.stdout || "")
|
|
244
|
+
.split("\n")
|
|
245
|
+
.find((line) => line.trim().startsWith(serverName));
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
codex: {
|
|
249
|
+
version: codexVersion.stdout || codexVersion.stderr || "missing"
|
|
250
|
+
},
|
|
251
|
+
claude: {
|
|
252
|
+
version: claudeVersion.stdout || claudeVersion.stderr || "missing",
|
|
253
|
+
auth: claudeAuth.stdout || claudeAuth.stderr || "unknown"
|
|
254
|
+
},
|
|
255
|
+
mcp: {
|
|
256
|
+
serverName,
|
|
257
|
+
installed: Boolean(mcpLine),
|
|
258
|
+
command: mcpLine || ""
|
|
259
|
+
},
|
|
260
|
+
notes
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function collectDoctorReport(input, options = {}) {
|
|
265
|
+
const status = await collectStatus(input, options);
|
|
266
|
+
const checks = [
|
|
267
|
+
{
|
|
268
|
+
name: "codex cli",
|
|
269
|
+
ok: !/missing|not available/i.test(status.codex.version),
|
|
270
|
+
detail: status.codex.version
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
name: "claude cli",
|
|
274
|
+
ok: !/missing|not available/i.test(status.claude.version),
|
|
275
|
+
detail: status.claude.version
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "claude auth",
|
|
279
|
+
ok: /loggedIn|logged in|subscriptionType/i.test(status.claude.auth),
|
|
280
|
+
detail: status.claude.auth.split("\n")[0]
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "mcp server",
|
|
284
|
+
ok: status.mcp.installed,
|
|
285
|
+
detail: status.mcp.serverName
|
|
286
|
+
}
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const nextSteps = [];
|
|
290
|
+
if (!checks[0].ok) {
|
|
291
|
+
nextSteps.push("Install or fix the Codex CLI, then rerun `codex-claude doctor`.");
|
|
292
|
+
}
|
|
293
|
+
if (!checks[1].ok) {
|
|
294
|
+
nextSteps.push("Install or fix the Claude CLI, then rerun `codex-claude doctor`.");
|
|
295
|
+
}
|
|
296
|
+
if (!checks[2].ok) {
|
|
297
|
+
nextSteps.push("Run `claude auth login` and verify direct Claude access with `claude -p --output-format text \"Reply with exactly OK.\"`.");
|
|
298
|
+
}
|
|
299
|
+
if (!checks[3].ok) {
|
|
300
|
+
nextSteps.push("Run `codex-claude install` to register the MCP bridge in Codex.");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
ok: checks.every((check) => check.ok),
|
|
305
|
+
checks,
|
|
306
|
+
nextSteps,
|
|
307
|
+
status
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function compareCodexAndClaude(input) {
|
|
312
|
+
const cwd = input.cwd || process.cwd();
|
|
313
|
+
const prompt = input.prompt;
|
|
314
|
+
|
|
315
|
+
if (!prompt) {
|
|
316
|
+
throw new Error(usage());
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const [claudeResult, codexResult] = await Promise.allSettled([
|
|
320
|
+
runClaude({
|
|
321
|
+
prompt,
|
|
322
|
+
cwd,
|
|
323
|
+
outputFormat: "text",
|
|
324
|
+
model: input.claudeModel || input.model,
|
|
325
|
+
permissionMode: input.permissionMode,
|
|
326
|
+
appendSystemPrompt: input.appendSystemPrompt,
|
|
327
|
+
maxBudgetUsd: input.maxBudgetUsd
|
|
328
|
+
}),
|
|
329
|
+
runCodex(
|
|
330
|
+
buildCodexExecArgs({
|
|
331
|
+
cwd,
|
|
332
|
+
model: input.codexModel || input.model,
|
|
333
|
+
skipGitRepoCheck: input.skipGitRepoCheck,
|
|
334
|
+
prompt
|
|
335
|
+
}),
|
|
336
|
+
{ cwd }
|
|
337
|
+
)
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
prompt,
|
|
342
|
+
cwd,
|
|
343
|
+
claudeOutput:
|
|
344
|
+
claudeResult.status === "fulfilled"
|
|
345
|
+
? claudeResult.value.stdout
|
|
346
|
+
: `ERROR: ${claudeResult.reason instanceof Error ? claudeResult.reason.message : String(claudeResult.reason)}`,
|
|
347
|
+
codexOutput:
|
|
348
|
+
codexResult.status === "fulfilled"
|
|
349
|
+
? codexResult.value.stdout
|
|
350
|
+
: `ERROR: ${codexResult.reason instanceof Error ? codexResult.reason.message : String(codexResult.reason)}`
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function emitOutput(output, input) {
|
|
355
|
+
if (input.out) {
|
|
356
|
+
await writeFile(path.resolve(input.cwd || process.cwd(), input.out), output, "utf8");
|
|
357
|
+
}
|
|
358
|
+
console.log(output);
|
|
359
|
+
}
|
|
360
|
+
|
|
120
361
|
export async function runCodexClaudeCli(argv) {
|
|
121
362
|
const input = parseCommand(argv);
|
|
122
363
|
|
|
@@ -133,11 +374,41 @@ export async function runCodexClaudeCli(argv) {
|
|
|
133
374
|
return;
|
|
134
375
|
}
|
|
135
376
|
|
|
377
|
+
if (input.command === "status") {
|
|
378
|
+
const report = await collectStatus(input);
|
|
379
|
+
const output = input.json ? toJsonReport(report) : formatStatusReport(report);
|
|
380
|
+
await emitOutput(output, input);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (input.command === "doctor") {
|
|
385
|
+
const report = await collectDoctorReport(input);
|
|
386
|
+
const output = input.json ? toJsonReport(report) : formatDoctorReport(report);
|
|
387
|
+
await emitOutput(output, input);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (input.command === "compare") {
|
|
392
|
+
const report = await compareCodexAndClaude(input);
|
|
393
|
+
const output = input.json ? toJsonReport(report) : formatCompareReport(report);
|
|
394
|
+
await emitOutput(output, input);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (input.command === "review" && input.compare) {
|
|
399
|
+
const report = await compareCodexAndClaude({
|
|
400
|
+
...input,
|
|
401
|
+
prompt:
|
|
402
|
+
input.prompt || "Review the current repository for bugs, regressions, and missing tests."
|
|
403
|
+
});
|
|
404
|
+
const output = input.json ? toJsonReport(report) : formatCompareReport(report);
|
|
405
|
+
await emitOutput(output, input);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
136
409
|
if (input.command === "ask" || input.command === "review" || input.command === "delegate") {
|
|
137
410
|
const result = await runViaCodex(input);
|
|
138
|
-
|
|
139
|
-
console.log(result.stdout);
|
|
140
|
-
}
|
|
411
|
+
await emitOutput(result.stdout || "", input);
|
|
141
412
|
return;
|
|
142
413
|
}
|
|
143
414
|
|
package/src/codex-cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runCommand } from "./process-utils.mjs";
|
|
2
2
|
|
|
3
3
|
export function buildCodexExecArgs(input) {
|
|
4
4
|
const args = ["exec"];
|
|
@@ -33,46 +33,10 @@ export function buildCodexMcpAddArgs(input) {
|
|
|
33
33
|
|
|
34
34
|
export async function runCodex(args, options = {}) {
|
|
35
35
|
const command = options.command || "codex";
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
env: {
|
|
42
|
-
...process.env,
|
|
43
|
-
...options.env
|
|
44
|
-
},
|
|
45
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
let stdout = "";
|
|
49
|
-
let stderr = "";
|
|
50
|
-
|
|
51
|
-
child.stdout.on("data", (chunk) => {
|
|
52
|
-
stdout += String(chunk);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
child.stderr.on("data", (chunk) => {
|
|
56
|
-
stderr += String(chunk);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
child.on("error", (error) => {
|
|
60
|
-
reject(new Error(`Failed to start Codex CLI. Ensure \`codex\` is installed and on PATH. ${error.message}`));
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
child.on("close", (code) => {
|
|
64
|
-
if (code !== 0) {
|
|
65
|
-
reject(new Error(stderr.trim() || stdout.trim() || `Codex CLI exited with status ${code ?? "unknown"}.`));
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
resolve({
|
|
70
|
-
command,
|
|
71
|
-
args,
|
|
72
|
-
cwd,
|
|
73
|
-
stdout: stdout.trim(),
|
|
74
|
-
stderr: stderr.trim()
|
|
75
|
-
});
|
|
76
|
-
});
|
|
36
|
+
return runCommand(command, args, {
|
|
37
|
+
cwd: options.cwd,
|
|
38
|
+
env: options.env
|
|
39
|
+
}).catch((error) => {
|
|
40
|
+
throw new Error(`Failed to start Codex CLI. Ensure \`codex\` is installed and on PATH. ${error.message}`);
|
|
77
41
|
});
|
|
78
42
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export async function runCommand(command, args, options = {}) {
|
|
4
|
+
if (typeof globalThis.__runCommandForTest === "function") {
|
|
5
|
+
return globalThis.__runCommandForTest(command, args, options);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const cwd = options.cwd || process.cwd();
|
|
9
|
+
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const child = spawn(command, args, {
|
|
12
|
+
cwd,
|
|
13
|
+
env: {
|
|
14
|
+
...process.env,
|
|
15
|
+
...options.env
|
|
16
|
+
},
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let stdout = "";
|
|
21
|
+
let stderr = "";
|
|
22
|
+
|
|
23
|
+
child.stdout.on("data", (chunk) => {
|
|
24
|
+
stdout += String(chunk);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.stderr.on("data", (chunk) => {
|
|
28
|
+
stderr += String(chunk);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
child.on("error", (error) => {
|
|
32
|
+
reject(new Error(`Failed to start \`${command}\`. ${error.message}`));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
child.on("close", (code) => {
|
|
36
|
+
const result = {
|
|
37
|
+
command,
|
|
38
|
+
args,
|
|
39
|
+
cwd,
|
|
40
|
+
code: code ?? 1,
|
|
41
|
+
stdout: stdout.trim(),
|
|
42
|
+
stderr: stderr.trim()
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (result.code !== 0 && options.rejectOnNonZero !== false) {
|
|
46
|
+
reject(new Error(result.stderr || result.stdout || `\`${command}\` exited with status ${result.code}.`));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
resolve(result);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
package/src/render.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
function normalizeForDiff(text) {
|
|
2
|
+
return String(text || "")
|
|
3
|
+
.replace(/\r\n/g, "\n")
|
|
4
|
+
.replace(/[ \t]+$/gm, "")
|
|
5
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
6
|
+
.trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function makeSection(title, body) {
|
|
10
|
+
return [`## ${title}`, "", body || "_No output_", ""].join("\n");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function diffLines(left, right) {
|
|
14
|
+
const leftLines = normalizeForDiff(left).split("\n");
|
|
15
|
+
const rightLines = normalizeForDiff(right).split("\n");
|
|
16
|
+
const max = Math.max(leftLines.length, rightLines.length);
|
|
17
|
+
const lines = [];
|
|
18
|
+
|
|
19
|
+
for (let index = 0; index < max; index += 1) {
|
|
20
|
+
const leftLine = leftLines[index];
|
|
21
|
+
const rightLine = rightLines[index];
|
|
22
|
+
if (leftLine === rightLine) {
|
|
23
|
+
if (leftLine !== undefined) {
|
|
24
|
+
lines.push(` ${leftLine}`);
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (leftLine !== undefined) {
|
|
29
|
+
lines.push(`- ${leftLine}`);
|
|
30
|
+
}
|
|
31
|
+
if (rightLine !== undefined) {
|
|
32
|
+
lines.push(`+ ${rightLine}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return lines.join("\n").trim() || "Outputs are identical.";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function formatStatusReport(report) {
|
|
40
|
+
const lines = [
|
|
41
|
+
"# codex-claude status",
|
|
42
|
+
"",
|
|
43
|
+
`- codex cli: ${report.codex.version}`,
|
|
44
|
+
`- claude cli: ${report.claude.version}`,
|
|
45
|
+
`- claude auth: ${report.claude.auth}`,
|
|
46
|
+
`- mcp server (${report.mcp.serverName}): ${report.mcp.installed ? "installed" : "missing"}`
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
if (report.mcp.installed && report.mcp.command) {
|
|
50
|
+
lines.push(`- mcp command: ${report.mcp.command}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (report.notes.length) {
|
|
54
|
+
lines.push("", "Notes:");
|
|
55
|
+
for (const note of report.notes) {
|
|
56
|
+
lines.push(`- ${note}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `${lines.join("\n")}\n`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function toJsonReport(report) {
|
|
64
|
+
return JSON.stringify(report, null, 2);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function formatCompareReport(report) {
|
|
68
|
+
const lines = [
|
|
69
|
+
"# codex-claude compare",
|
|
70
|
+
"",
|
|
71
|
+
`Prompt: ${report.prompt}`,
|
|
72
|
+
`Working directory: \`${report.cwd}\``,
|
|
73
|
+
""
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
lines.push(makeSection("Claude", ["```text", report.claudeOutput, "```"].join("\n")));
|
|
77
|
+
lines.push(makeSection("Codex", ["```text", report.codexOutput, "```"].join("\n")));
|
|
78
|
+
lines.push(makeSection("Diff", ["```diff", diffLines(report.claudeOutput, report.codexOutput), "```"].join("\n")));
|
|
79
|
+
|
|
80
|
+
return lines.join("\n").trimEnd();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function formatDoctorReport(report) {
|
|
84
|
+
const lines = [
|
|
85
|
+
"# codex-claude doctor",
|
|
86
|
+
"",
|
|
87
|
+
`- overall: ${report.ok ? "ok" : "needs attention"}`,
|
|
88
|
+
""
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
lines.push("Checks:");
|
|
92
|
+
for (const check of report.checks) {
|
|
93
|
+
lines.push(`- ${check.name}: ${check.ok ? "ok" : "failed"}${check.detail ? ` (${check.detail})` : ""}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (report.nextSteps.length) {
|
|
97
|
+
lines.push("", "Next steps:");
|
|
98
|
+
for (const step of report.nextSteps) {
|
|
99
|
+
lines.push(`- ${step}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return `${lines.join("\n")}\n`;
|
|
104
|
+
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
import { buildClaudeArgs } from "../src/claude-cli.mjs";
|
|
5
|
-
|
|
6
|
-
test("buildClaudeArgs creates the expected Claude CLI invocation", () => {
|
|
7
|
-
const args = buildClaudeArgs({
|
|
8
|
-
prompt: "Review the current diff",
|
|
9
|
-
outputFormat: "json",
|
|
10
|
-
model: "sonnet",
|
|
11
|
-
permissionMode: "dontAsk",
|
|
12
|
-
allowedTools: ["Read", "Bash(git:*)"],
|
|
13
|
-
disallowedTools: ["Edit"],
|
|
14
|
-
appendSystemPrompt: "Be strict.",
|
|
15
|
-
maxBudgetUsd: 1.5
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
assert.deepEqual(args, [
|
|
19
|
-
"--print",
|
|
20
|
-
"--output-format",
|
|
21
|
-
"json",
|
|
22
|
-
"--model",
|
|
23
|
-
"sonnet",
|
|
24
|
-
"--permission-mode",
|
|
25
|
-
"dontAsk",
|
|
26
|
-
"--max-budget-usd",
|
|
27
|
-
"1.5",
|
|
28
|
-
"--append-system-prompt",
|
|
29
|
-
"Be strict.",
|
|
30
|
-
"--allowedTools",
|
|
31
|
-
"Read Bash(git:*)",
|
|
32
|
-
"--disallowedTools",
|
|
33
|
-
"Edit",
|
|
34
|
-
"Review the current diff"
|
|
35
|
-
]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("buildClaudeArgs falls back to text output when unspecified", () => {
|
|
39
|
-
const args = buildClaudeArgs(
|
|
40
|
-
{
|
|
41
|
-
prompt: "Summarize the repo"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
outputFormat: "text",
|
|
45
|
-
permissionMode: "default"
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
assert.deepEqual(args, [
|
|
50
|
-
"--print",
|
|
51
|
-
"--output-format",
|
|
52
|
-
"text",
|
|
53
|
-
"--permission-mode",
|
|
54
|
-
"default",
|
|
55
|
-
"Summarize the repo"
|
|
56
|
-
]);
|
|
57
|
-
});
|
package/tests/codex-cli.test.mjs
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
import { buildCodexExecArgs, buildCodexMcpAddArgs } from "../src/codex-cli.mjs";
|
|
5
|
-
import { buildCodexClaudePrompt } from "../src/codex-claude-command.mjs";
|
|
6
|
-
|
|
7
|
-
test("buildCodexExecArgs creates the expected codex exec invocation", () => {
|
|
8
|
-
const args = buildCodexExecArgs({
|
|
9
|
-
cwd: "/repo",
|
|
10
|
-
model: "gpt-5.4",
|
|
11
|
-
skipGitRepoCheck: true,
|
|
12
|
-
prompt: "Use the tool."
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
assert.deepEqual(args, [
|
|
16
|
-
"exec",
|
|
17
|
-
"--cd",
|
|
18
|
-
"/repo",
|
|
19
|
-
"--model",
|
|
20
|
-
"gpt-5.4",
|
|
21
|
-
"--skip-git-repo-check",
|
|
22
|
-
"Use the tool."
|
|
23
|
-
]);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("buildCodexMcpAddArgs registers the local bridge server", () => {
|
|
27
|
-
const args = buildCodexMcpAddArgs({
|
|
28
|
-
serverName: "claude-bridge",
|
|
29
|
-
serverScript: "/tmp/codex-claude-mcp.mjs"
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
assert.deepEqual(args, [
|
|
33
|
-
"mcp",
|
|
34
|
-
"add",
|
|
35
|
-
"claude-bridge",
|
|
36
|
-
"--",
|
|
37
|
-
"node",
|
|
38
|
-
"/tmp/codex-claude-mcp.mjs"
|
|
39
|
-
]);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("buildCodexClaudePrompt targets the review tool with a default review request", () => {
|
|
43
|
-
const prompt = buildCodexClaudePrompt({
|
|
44
|
-
command: "review",
|
|
45
|
-
cwd: "/repo"
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
assert.match(prompt, /claude_review/);
|
|
49
|
-
assert.match(prompt, /Return only the tool result/);
|
|
50
|
-
assert.match(prompt, /Review the current repository for bugs, regressions, and missing tests/);
|
|
51
|
-
assert.match(prompt, /"cwd":"\/repo"/);
|
|
52
|
-
});
|
package/tests/server.test.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
import { createServer } from "../src/server.mjs";
|
|
5
|
-
|
|
6
|
-
test("createServer registers the expected Claude bridge tools", async () => {
|
|
7
|
-
const server = await createServer();
|
|
8
|
-
assert.deepEqual(
|
|
9
|
-
Object.keys(server._registeredTools).sort(),
|
|
10
|
-
["claude_ask", "claude_delegate", "claude_review"]
|
|
11
|
-
);
|
|
12
|
-
});
|
package/tests/tools.test.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
import { handleClaudeAsk } from "../src/tools.mjs";
|
|
5
|
-
|
|
6
|
-
test("handleClaudeAsk renders a Codex-friendly text block", async () => {
|
|
7
|
-
const originalPath = process.env.PATH;
|
|
8
|
-
process.env.PATH = "/tmp";
|
|
9
|
-
|
|
10
|
-
const response = await handleClaudeAsk({
|
|
11
|
-
prompt: "Summarize",
|
|
12
|
-
outputFormat: "text"
|
|
13
|
-
}).catch((error) => error);
|
|
14
|
-
|
|
15
|
-
process.env.PATH = originalPath;
|
|
16
|
-
|
|
17
|
-
assert.equal(response instanceof Error, true);
|
|
18
|
-
assert.match(response.message, /Failed to start Claude CLI|Ensure `claude` is installed/);
|
|
19
|
-
});
|