ai-cli-mcp 2.9.1 → 2.11.0
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/.github/pull_request_template.md +28 -0
- package/.github/workflows/watch-session-prs.yml +276 -0
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +14 -0
- package/README.ja.md +4 -4
- package/README.md +4 -4
- package/dist/__tests__/cli-builder.test.js +52 -5
- package/dist/__tests__/validation.test.js +2 -2
- package/dist/cli-builder.js +29 -17
- package/dist/cli.js +1 -1
- package/dist/server.js +3 -3
- package/docs/concept.md +154 -0
- package/docs/prd.md +146 -0
- package/docs/session-stacking.md +67 -0
- package/package.json +4 -2
- package/src/__tests__/cli-builder.test.ts +65 -6
- package/src/__tests__/validation.test.ts +2 -2
- package/src/cli-builder.ts +32 -16
- package/src/cli.ts +1 -1
- package/src/server.ts +3 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
<!-- Briefly describe what this PR does -->
|
|
4
|
+
|
|
5
|
+
## Type of Change
|
|
6
|
+
|
|
7
|
+
- [ ] feat: New feature
|
|
8
|
+
- [ ] fix: Bug fix
|
|
9
|
+
- [ ] refactor: Refactoring
|
|
10
|
+
- [ ] docs: Documentation
|
|
11
|
+
- [ ] test: Add or update tests
|
|
12
|
+
- [ ] chore: Build, CI, dependencies, etc.
|
|
13
|
+
|
|
14
|
+
## Changes
|
|
15
|
+
|
|
16
|
+
<!-- List the main changes -->
|
|
17
|
+
|
|
18
|
+
-
|
|
19
|
+
|
|
20
|
+
## Test Plan
|
|
21
|
+
|
|
22
|
+
- [ ] `npm run test:unit` passes
|
|
23
|
+
- [ ] `npm run build` passes
|
|
24
|
+
- [ ] Manual testing (if applicable)
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
|
|
28
|
+
<!-- Any additional context for reviewers -->
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
name: Watch Session PRs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '17 0 * * *'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
issues: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
watch-codex-fork:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Check Codex fork PR and update issue
|
|
17
|
+
uses: actions/github-script@v7
|
|
18
|
+
env:
|
|
19
|
+
UPSTREAM_OWNER: openai
|
|
20
|
+
UPSTREAM_REPO: codex
|
|
21
|
+
UPSTREAM_PR_NUMBER: '13537'
|
|
22
|
+
TARGET_ISSUE_NUMBER: '7'
|
|
23
|
+
COMMENT_MARKER: '<!-- codex-fork-pr-13537-status -->'
|
|
24
|
+
READY_LABEL: '[ready]'
|
|
25
|
+
STOP_LABEL: '[stop]'
|
|
26
|
+
with:
|
|
27
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
28
|
+
script: |
|
|
29
|
+
const owner = process.env.UPSTREAM_OWNER;
|
|
30
|
+
const repo = process.env.UPSTREAM_REPO;
|
|
31
|
+
const pull_number = Number(process.env.UPSTREAM_PR_NUMBER);
|
|
32
|
+
const issue_number = Number(process.env.TARGET_ISSUE_NUMBER);
|
|
33
|
+
const marker = process.env.COMMENT_MARKER;
|
|
34
|
+
const readyLabel = process.env.READY_LABEL;
|
|
35
|
+
const stopLabel = process.env.STOP_LABEL;
|
|
36
|
+
|
|
37
|
+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
|
|
38
|
+
const desiredLabel = pr.merged_at ? readyLabel : stopLabel;
|
|
39
|
+
const staleLabel = pr.merged_at ? stopLabel : readyLabel;
|
|
40
|
+
const { data: issue } = await github.rest.issues.get({
|
|
41
|
+
owner: context.repo.owner,
|
|
42
|
+
repo: context.repo.repo,
|
|
43
|
+
issue_number,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!issue.labels.some((label) => label.name === desiredLabel)) {
|
|
47
|
+
await github.rest.issues.addLabels({
|
|
48
|
+
owner: context.repo.owner,
|
|
49
|
+
repo: context.repo.repo,
|
|
50
|
+
issue_number,
|
|
51
|
+
labels: [desiredLabel],
|
|
52
|
+
});
|
|
53
|
+
core.info(`Added label ${desiredLabel} to issue #${issue_number}.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (issue.labels.some((label) => label.name === staleLabel)) {
|
|
57
|
+
try {
|
|
58
|
+
await github.rest.issues.removeLabel({
|
|
59
|
+
owner: context.repo.owner,
|
|
60
|
+
repo: context.repo.repo,
|
|
61
|
+
issue_number,
|
|
62
|
+
name: staleLabel,
|
|
63
|
+
});
|
|
64
|
+
core.info(`Removed label ${staleLabel} from issue #${issue_number}.`);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
core.warning(`Failed to remove label ${staleLabel}: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
core.summary
|
|
71
|
+
.addHeading('Codex PR status')
|
|
72
|
+
.addTable([
|
|
73
|
+
[
|
|
74
|
+
{ data: 'Field', header: true },
|
|
75
|
+
{ data: 'Value', header: true },
|
|
76
|
+
],
|
|
77
|
+
['PR', `${owner}/${repo}#${pr.number}`],
|
|
78
|
+
['Title', pr.title],
|
|
79
|
+
['State', pr.state],
|
|
80
|
+
['Merged', pr.merged_at ? 'yes' : 'no'],
|
|
81
|
+
['Merged at', pr.merged_at ?? 'not merged'],
|
|
82
|
+
['Updated at', pr.updated_at],
|
|
83
|
+
['URL', pr.html_url],
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
87
|
+
owner: context.repo.owner,
|
|
88
|
+
repo: context.repo.repo,
|
|
89
|
+
issue_number,
|
|
90
|
+
per_page: 100,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const existingComment = comments.find((comment) => comment.body?.includes(marker));
|
|
94
|
+
const body = [
|
|
95
|
+
marker,
|
|
96
|
+
'upstream PR `openai/codex#13537` status watcher',
|
|
97
|
+
'',
|
|
98
|
+
`- PR: ${pr.html_url}`,
|
|
99
|
+
`- State: ${pr.state}`,
|
|
100
|
+
`- Merged: ${pr.merged_at ? 'yes' : 'no'}`,
|
|
101
|
+
`- Merged at: ${pr.merged_at ?? 'not merged'}`,
|
|
102
|
+
`- Updated at: ${pr.updated_at}`,
|
|
103
|
+
'',
|
|
104
|
+
pr.merged_at
|
|
105
|
+
? 'This PR has been merged. `codex exec --fork <SESSION_ID> [PROMPT]` should become available once the change lands in the installed Codex version.'
|
|
106
|
+
: 'This PR is not merged yet. Non-interactive forking is still not available in released Codex builds.',
|
|
107
|
+
'A new session ID is expected when using `--fork`; `resume` itself continues the same session.',
|
|
108
|
+
].join('\n');
|
|
109
|
+
|
|
110
|
+
if (existingComment?.body === body) {
|
|
111
|
+
core.info(`Issue #${issue_number} status comment is already up to date.`);
|
|
112
|
+
await core.summary.addRaw(`No update needed: ${existingComment.html_url}\n`).write();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (existingComment) {
|
|
117
|
+
const { data: comment } = await github.rest.issues.updateComment({
|
|
118
|
+
owner: context.repo.owner,
|
|
119
|
+
repo: context.repo.repo,
|
|
120
|
+
comment_id: existingComment.id,
|
|
121
|
+
body,
|
|
122
|
+
});
|
|
123
|
+
core.info(`Updated issue #${issue_number}: ${comment.html_url}`);
|
|
124
|
+
await core.summary.addRaw(`Updated status comment: ${comment.html_url}\n`).write();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { data: comment } = await github.rest.issues.createComment({
|
|
129
|
+
owner: context.repo.owner,
|
|
130
|
+
repo: context.repo.repo,
|
|
131
|
+
issue_number,
|
|
132
|
+
body,
|
|
133
|
+
});
|
|
134
|
+
core.info(`Created issue #${issue_number} status comment: ${comment.html_url}`);
|
|
135
|
+
await core.summary.addRaw(`Created status comment: ${comment.html_url}\n`).write();
|
|
136
|
+
|
|
137
|
+
watch-gemini-session-work:
|
|
138
|
+
runs-on: ubuntu-latest
|
|
139
|
+
steps:
|
|
140
|
+
- name: Check Gemini session PRs and update issue
|
|
141
|
+
uses: actions/github-script@v7
|
|
142
|
+
env:
|
|
143
|
+
UPSTREAM_OWNER: google-gemini
|
|
144
|
+
UPSTREAM_REPO: gemini-cli
|
|
145
|
+
TARGET_ISSUE_NUMBER: '16'
|
|
146
|
+
COMMENT_MARKER: '<!-- gemini-session-pr-status -->'
|
|
147
|
+
WATCH_PR_NUMBERS: '17921,19629,19994'
|
|
148
|
+
READY_PR_NUMBERS: '17921,19629'
|
|
149
|
+
READY_LABEL: '[ready]'
|
|
150
|
+
STOP_LABEL: '[stop]'
|
|
151
|
+
with:
|
|
152
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
153
|
+
script: |
|
|
154
|
+
const owner = process.env.UPSTREAM_OWNER;
|
|
155
|
+
const repo = process.env.UPSTREAM_REPO;
|
|
156
|
+
const issue_number = Number(process.env.TARGET_ISSUE_NUMBER);
|
|
157
|
+
const marker = process.env.COMMENT_MARKER;
|
|
158
|
+
const pullNumbers = process.env.WATCH_PR_NUMBERS.split(',').map((value) => Number(value.trim()));
|
|
159
|
+
const readyPullNumbers = process.env.READY_PR_NUMBERS.split(',').map((value) => Number(value.trim()));
|
|
160
|
+
const readyLabel = process.env.READY_LABEL;
|
|
161
|
+
const stopLabel = process.env.STOP_LABEL;
|
|
162
|
+
|
|
163
|
+
const pulls = await Promise.all(
|
|
164
|
+
pullNumbers.map(async (pull_number) => {
|
|
165
|
+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
|
|
166
|
+
return pr;
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
const isReady = pulls.some((pr) => readyPullNumbers.includes(pr.number) && Boolean(pr.merged_at));
|
|
170
|
+
const desiredLabel = isReady ? readyLabel : stopLabel;
|
|
171
|
+
const staleLabel = isReady ? stopLabel : readyLabel;
|
|
172
|
+
|
|
173
|
+
const { data: issue } = await github.rest.issues.get({
|
|
174
|
+
owner: context.repo.owner,
|
|
175
|
+
repo: context.repo.repo,
|
|
176
|
+
issue_number,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!issue.labels.some((label) => label.name === desiredLabel)) {
|
|
180
|
+
await github.rest.issues.addLabels({
|
|
181
|
+
owner: context.repo.owner,
|
|
182
|
+
repo: context.repo.repo,
|
|
183
|
+
issue_number,
|
|
184
|
+
labels: [desiredLabel],
|
|
185
|
+
});
|
|
186
|
+
core.info(`Added label ${desiredLabel} to issue #${issue_number}.`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (issue.labels.some((label) => label.name === staleLabel)) {
|
|
190
|
+
try {
|
|
191
|
+
await github.rest.issues.removeLabel({
|
|
192
|
+
owner: context.repo.owner,
|
|
193
|
+
repo: context.repo.repo,
|
|
194
|
+
issue_number,
|
|
195
|
+
name: staleLabel,
|
|
196
|
+
});
|
|
197
|
+
core.info(`Removed label ${staleLabel} from issue #${issue_number}.`);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
core.warning(`Failed to remove label ${staleLabel}: ${error.message}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const lines = pulls.flatMap((pr) => [
|
|
204
|
+
`- \`${owner}/${repo}#${pr.number}\` ${pr.title}`,
|
|
205
|
+
` - State: ${pr.state}`,
|
|
206
|
+
` - Merged: ${pr.merged_at ? 'yes' : 'no'}`,
|
|
207
|
+
` - Merged at: ${pr.merged_at ?? 'not merged'}`,
|
|
208
|
+
` - Updated at: ${pr.updated_at}`,
|
|
209
|
+
` - URL: ${pr.html_url}`,
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
core.summary
|
|
213
|
+
.addHeading('Gemini PR status')
|
|
214
|
+
.addTable([
|
|
215
|
+
[
|
|
216
|
+
{ data: 'PR', header: true },
|
|
217
|
+
{ data: 'State', header: true },
|
|
218
|
+
{ data: 'Merged', header: true },
|
|
219
|
+
{ data: 'Updated at', header: true },
|
|
220
|
+
],
|
|
221
|
+
...pulls.map((pr) => [
|
|
222
|
+
`${owner}/${repo}#${pr.number}`,
|
|
223
|
+
pr.state,
|
|
224
|
+
pr.merged_at ? 'yes' : 'no',
|
|
225
|
+
pr.updated_at,
|
|
226
|
+
]),
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
230
|
+
owner: context.repo.owner,
|
|
231
|
+
repo: context.repo.repo,
|
|
232
|
+
issue_number,
|
|
233
|
+
per_page: 100,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const existingComment = comments.find((comment) => comment.body?.includes(marker));
|
|
237
|
+
const body = [
|
|
238
|
+
marker,
|
|
239
|
+
'upstream Gemini CLI session-related PR status watcher',
|
|
240
|
+
'',
|
|
241
|
+
'Tracked PRs:',
|
|
242
|
+
...lines,
|
|
243
|
+
'',
|
|
244
|
+
'Interpretation:',
|
|
245
|
+
'- `#19994` is a fix for resumed session ID stats mismatch, not a fork/new-session feature.',
|
|
246
|
+
'- `#17921` is the `/new` command proposal for starting a fresh session while preserving the previous one.',
|
|
247
|
+
'- `#19629` is the `--session-id` proposal for explicit session UUID control.',
|
|
248
|
+
'- Even if these land, that still does not automatically imply Codex-style headless fork semantics from `--resume` alone.',
|
|
249
|
+
].join('\n');
|
|
250
|
+
|
|
251
|
+
if (existingComment?.body === body) {
|
|
252
|
+
core.info(`Issue #${issue_number} Gemini status comment is already up to date.`);
|
|
253
|
+
await core.summary.addRaw(`No update needed: ${existingComment.html_url}\n`).write();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (existingComment) {
|
|
258
|
+
const { data: comment } = await github.rest.issues.updateComment({
|
|
259
|
+
owner: context.repo.owner,
|
|
260
|
+
repo: context.repo.repo,
|
|
261
|
+
comment_id: existingComment.id,
|
|
262
|
+
body,
|
|
263
|
+
});
|
|
264
|
+
core.info(`Updated issue #${issue_number}: ${comment.html_url}`);
|
|
265
|
+
await core.summary.addRaw(`Updated status comment: ${comment.html_url}\n`).write();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const { data: comment } = await github.rest.issues.createComment({
|
|
270
|
+
owner: context.repo.owner,
|
|
271
|
+
repo: context.repo.repo,
|
|
272
|
+
issue_number,
|
|
273
|
+
body,
|
|
274
|
+
});
|
|
275
|
+
core.info(`Created issue #${issue_number} status comment: ${comment.html_url}`);
|
|
276
|
+
await core.summary.addRaw(`Created status comment: ${comment.html_url}\n`).write();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run test:unit
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [2.11.0](https://github.com/mkXultra/ai-cli-mcp/compare/v2.10.0...v2.11.0) (2026-03-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* ClaudeモデルへのReasoning Effort(--effort)サポートを追加 ([ac6b8cc](https://github.com/mkXultra/ai-cli-mcp/commit/ac6b8cc47a06a2d207258e85cc21b1d4ce83e5b2))
|
|
7
|
+
|
|
8
|
+
# [2.10.0](https://github.com/mkXultra/ai-cli-mcp/compare/v2.9.1...v2.10.0) (2026-03-07)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* gpt-5.4モデルを追加しcodex-ultraエイリアスを更新 ([0fb15a6](https://github.com/mkXultra/ai-cli-mcp/commit/0fb15a635ab9d5450da16cb3b15132054c8c0dbe))
|
|
14
|
+
|
|
1
15
|
## [2.9.1](https://github.com/mkXultra/ai-cli-mcp/compare/v2.9.0...v2.9.1) (2026-03-07)
|
|
2
16
|
|
|
3
17
|
|
package/README.ja.md
CHANGED
|
@@ -18,7 +18,7 @@ Cursorなどのエディタが、複雑な手順を伴う編集や操作に苦
|
|
|
18
18
|
- 自動承認モードでGemini CLIを実行(`-y` を使用)
|
|
19
19
|
- 複数のAIモデルのサポート:
|
|
20
20
|
- Claude (sonnet, sonnet[1m], opus, opusplan, haiku)
|
|
21
|
-
- Codex (gpt-5.3-codex, gpt-5.2-codex, gpt-5.1-codex-mini, gpt-5.1-codex-max, など)
|
|
21
|
+
- Codex (gpt-5.4, gpt-5.3-codex, gpt-5.2-codex, gpt-5.1-codex-mini, gpt-5.1-codex-max, など)
|
|
22
22
|
- Gemini (gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview)
|
|
23
23
|
- PID追跡によるバックグラウンドプロセスの管理
|
|
24
24
|
- ツールからの構造化された出力の解析と返却
|
|
@@ -131,11 +131,11 @@ Claude CLI、Codex CLI、またはGemini CLIを使用してプロンプトを実
|
|
|
131
131
|
- `prompt_file` (string, 任意): プロンプトを含むファイルへのパス。`prompt` または `prompt_file` のいずれかが必須です。絶対パス、または `workFolder` からの相対パスが指定可能です。
|
|
132
132
|
- `workFolder` (string, 必須): CLIを実行する作業ディレクトリ。絶対パスである必要があります。
|
|
133
133
|
- **モデル (Models):**
|
|
134
|
-
- **Ultra エイリアス:** `claude-ultra
|
|
134
|
+
- **Ultra エイリアス:** `claude-ultra` (自動的に high effort に設定), `codex-ultra` (自動的に xhigh reasoning に設定), `gemini-ultra`
|
|
135
135
|
- Claude: `sonnet`, `sonnet[1m]`, `opus`, `opusplan`, `haiku`
|
|
136
|
-
- Codex: `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5`
|
|
136
|
+
- Codex: `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5`
|
|
137
137
|
- Gemini: `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-pro-preview`, `gemini-3-flash-preview`
|
|
138
|
-
- `reasoning_effort` (string, 任意): Codex
|
|
138
|
+
- `reasoning_effort` (string, 任意): Claude と Codex の推論制御。Claude では `--effort` を使います(許容値: "low", "medium", "high")。Codex では `model_reasoning_effort` を使います(許容値: "low", "medium", "high", "xhigh")。
|
|
139
139
|
- `session_id` (string, 任意): 以前のセッションを再開するためのセッションID。対応モデル: haiku, sonnet, opus, gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview。
|
|
140
140
|
|
|
141
141
|
### `wait`
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ This MCP server provides tools that can be used by LLMs to interact with AI CLI
|
|
|
18
18
|
- Run Claude CLI with all permissions bypassed (using `--dangerously-skip-permissions`)
|
|
19
19
|
- Execute Codex CLI with automatic approval mode (using `--full-auto`)
|
|
20
20
|
- Execute Gemini CLI with automatic approval mode (using `-y`)
|
|
21
|
-
- Support multiple AI models: Claude (sonnet, sonnet[1m], opus, opusplan, haiku), Codex (gpt-5.3-codex, gpt-5.2-codex, gpt-5.1-codex-mini, gpt-5.1-codex-max, gpt-5.2, gpt-5.1, gpt-5.1-codex, gpt-5-codex, gpt-5-codex-mini, gpt-5), and Gemini (gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview)
|
|
21
|
+
- Support multiple AI models: Claude (sonnet, sonnet[1m], opus, opusplan, haiku), Codex (gpt-5.4, gpt-5.3-codex, gpt-5.2-codex, gpt-5.1-codex-mini, gpt-5.1-codex-max, gpt-5.2, gpt-5.1, gpt-5.1-codex, gpt-5-codex, gpt-5-codex-mini, gpt-5), and Gemini (gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview)
|
|
22
22
|
- Manage background processes with PID tracking
|
|
23
23
|
- Parse and return structured outputs from both tools
|
|
24
24
|
|
|
@@ -130,11 +130,11 @@ Executes a prompt using Claude CLI, Codex CLI, or Gemini CLI. The appropriate CL
|
|
|
130
130
|
- `prompt_file` (string, optional): Path to a file containing the prompt. Either `prompt` or `prompt_file` is required. Can be absolute path or relative to `workFolder`.
|
|
131
131
|
- `workFolder` (string, required): The working directory for the CLI execution. Must be an absolute path.
|
|
132
132
|
**Models:**
|
|
133
|
-
- **Ultra Aliases:** `claude-ultra
|
|
133
|
+
- **Ultra Aliases:** `claude-ultra` (defaults to high effort), `codex-ultra` (defaults to xhigh reasoning), `gemini-ultra`
|
|
134
134
|
- Claude: `sonnet`, `sonnet[1m]`, `opus`, `opusplan`, `haiku`
|
|
135
|
-
- Codex: `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5`
|
|
135
|
+
- Codex: `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5`
|
|
136
136
|
- Gemini: `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-pro-preview`, `gemini-3-flash-preview`
|
|
137
|
-
- `reasoning_effort` (string, optional): Codex
|
|
137
|
+
- `reasoning_effort` (string, optional): Reasoning control for Claude and Codex. Claude uses `--effort` (allowed: "low", "medium", "high"). Codex uses `model_reasoning_effort` (allowed: "low", "medium", "high", "xhigh").
|
|
138
138
|
- `session_id` (string, optional): Optional session ID to resume a previous session. Supported for: haiku, sonnet, opus, gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview, gemini-3-pro-preview, gemini-3-flash-preview.
|
|
139
139
|
|
|
140
140
|
### `wait`
|
|
@@ -25,8 +25,8 @@ describe('cli-builder', () => {
|
|
|
25
25
|
it('should resolve claude-ultra to opus', () => {
|
|
26
26
|
expect(resolveModelAlias('claude-ultra')).toBe('opus');
|
|
27
27
|
});
|
|
28
|
-
it('should resolve codex-ultra to gpt-5.
|
|
29
|
-
expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.
|
|
28
|
+
it('should resolve codex-ultra to gpt-5.4', () => {
|
|
29
|
+
expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.4');
|
|
30
30
|
});
|
|
31
31
|
it('should resolve gemini-ultra to gemini-3.1-pro-preview', () => {
|
|
32
32
|
expect(resolveModelAlias('gemini-ultra')).toBe('gemini-3.1-pro-preview');
|
|
@@ -58,12 +58,17 @@ describe('cli-builder', () => {
|
|
|
58
58
|
expect(getReasoningEffort('gpt-5.2', 'medium')).toBe('medium');
|
|
59
59
|
expect(getReasoningEffort('gpt-5.2', 'high')).toBe('high');
|
|
60
60
|
expect(getReasoningEffort('gpt-5.2', 'xhigh')).toBe('xhigh');
|
|
61
|
+
expect(getReasoningEffort('sonnet', 'high')).toBe('high');
|
|
62
|
+
expect(getReasoningEffort('', 'low')).toBe('low');
|
|
61
63
|
});
|
|
62
64
|
it('should throw for invalid reasoning effort value', () => {
|
|
63
65
|
expect(() => getReasoningEffort('gpt-5.2', 'ultra')).toThrow('Invalid reasoning_effort: ultra. Allowed values: low, medium, high, xhigh.');
|
|
64
66
|
});
|
|
65
|
-
it('should
|
|
66
|
-
expect(() => getReasoningEffort('sonnet', '
|
|
67
|
+
it('should reject xhigh for claude models', () => {
|
|
68
|
+
expect(() => getReasoningEffort('sonnet', 'xhigh')).toThrow('Claude reasoning_effort supports only low, medium, high.');
|
|
69
|
+
});
|
|
70
|
+
it('should throw for unsupported model families', () => {
|
|
71
|
+
expect(() => getReasoningEffort('gemini-2.5-pro', 'high')).toThrow('reasoning_effort is only supported for Claude and Codex models.');
|
|
67
72
|
});
|
|
68
73
|
});
|
|
69
74
|
describe('buildCliCommand', () => {
|
|
@@ -173,6 +178,48 @@ describe('cli-builder', () => {
|
|
|
173
178
|
expect(cmd.resolvedModel).toBe('opus');
|
|
174
179
|
expect(cmd.args).toContain('opus');
|
|
175
180
|
});
|
|
181
|
+
it('should resolve claude-ultra and default to high effort', () => {
|
|
182
|
+
const cmd = buildCliCommand({
|
|
183
|
+
prompt: 'test',
|
|
184
|
+
workFolder: '/tmp',
|
|
185
|
+
model: 'claude-ultra',
|
|
186
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
187
|
+
});
|
|
188
|
+
expect(cmd.args).toContain('--effort');
|
|
189
|
+
expect(cmd.args).toContain('high');
|
|
190
|
+
});
|
|
191
|
+
it('should build claude command with reasoning_effort using --effort', () => {
|
|
192
|
+
const cmd = buildCliCommand({
|
|
193
|
+
prompt: 'test',
|
|
194
|
+
workFolder: '/tmp',
|
|
195
|
+
model: 'sonnet',
|
|
196
|
+
reasoning_effort: 'medium',
|
|
197
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
198
|
+
});
|
|
199
|
+
expect(cmd.args).toContain('--effort');
|
|
200
|
+
expect(cmd.args).toContain('medium');
|
|
201
|
+
});
|
|
202
|
+
it('should reject xhigh reasoning_effort for claude', () => {
|
|
203
|
+
expect(() => buildCliCommand({
|
|
204
|
+
prompt: 'test',
|
|
205
|
+
workFolder: '/tmp',
|
|
206
|
+
model: 'sonnet',
|
|
207
|
+
reasoning_effort: 'xhigh',
|
|
208
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
209
|
+
})).toThrow('Claude reasoning_effort supports only low, medium, high.');
|
|
210
|
+
});
|
|
211
|
+
it('should allow overriding reasoning_effort for claude-ultra', () => {
|
|
212
|
+
const cmd = buildCliCommand({
|
|
213
|
+
prompt: 'test',
|
|
214
|
+
workFolder: '/tmp',
|
|
215
|
+
model: 'claude-ultra',
|
|
216
|
+
reasoning_effort: 'low',
|
|
217
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
218
|
+
});
|
|
219
|
+
expect(cmd.args).toContain('--effort');
|
|
220
|
+
expect(cmd.args).toContain('low');
|
|
221
|
+
expect(cmd.args).not.toContain('high');
|
|
222
|
+
});
|
|
176
223
|
});
|
|
177
224
|
describe('codex agent', () => {
|
|
178
225
|
it('should build codex command', () => {
|
|
@@ -221,7 +268,7 @@ describe('cli-builder', () => {
|
|
|
221
268
|
cliPaths: DEFAULT_CLI_PATHS,
|
|
222
269
|
});
|
|
223
270
|
expect(cmd.agent).toBe('codex');
|
|
224
|
-
expect(cmd.resolvedModel).toBe('gpt-5.
|
|
271
|
+
expect(cmd.resolvedModel).toBe('gpt-5.4');
|
|
225
272
|
expect(cmd.args).toContain('-c');
|
|
226
273
|
expect(cmd.args).toContain('model_reasoning_effort=xhigh');
|
|
227
274
|
});
|
|
@@ -251,7 +251,7 @@ describe('Argument Validation Tests', () => {
|
|
|
251
251
|
}
|
|
252
252
|
})).rejects.toThrow(/reasoning_effort/i);
|
|
253
253
|
});
|
|
254
|
-
it('should reject reasoning_effort for
|
|
254
|
+
it('should reject reasoning_effort for unsupported model families', async () => {
|
|
255
255
|
await setupServer();
|
|
256
256
|
const handler = handlers.get('callTool');
|
|
257
257
|
await expect(handler({
|
|
@@ -260,7 +260,7 @@ describe('Argument Validation Tests', () => {
|
|
|
260
260
|
arguments: {
|
|
261
261
|
prompt: 'test',
|
|
262
262
|
workFolder: '/tmp',
|
|
263
|
-
model: '
|
|
263
|
+
model: 'gemini-2.5-pro',
|
|
264
264
|
reasoning_effort: 'low'
|
|
265
265
|
}
|
|
266
266
|
}
|
package/dist/cli-builder.js
CHANGED
|
@@ -3,10 +3,20 @@ import { resolve as pathResolve, isAbsolute } from 'node:path';
|
|
|
3
3
|
// Model alias mappings for user-friendly model names
|
|
4
4
|
export const MODEL_ALIASES = {
|
|
5
5
|
'claude-ultra': 'opus',
|
|
6
|
-
'codex-ultra': 'gpt-5.
|
|
6
|
+
'codex-ultra': 'gpt-5.4',
|
|
7
7
|
'gemini-ultra': 'gemini-3.1-pro-preview'
|
|
8
8
|
};
|
|
9
9
|
export const ALLOWED_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
|
|
10
|
+
const CLAUDE_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
11
|
+
function getAgentForModel(model) {
|
|
12
|
+
if (model.startsWith('gpt-')) {
|
|
13
|
+
return 'codex';
|
|
14
|
+
}
|
|
15
|
+
if (model.startsWith('gemini')) {
|
|
16
|
+
return 'gemini';
|
|
17
|
+
}
|
|
18
|
+
return 'claude';
|
|
19
|
+
}
|
|
10
20
|
/**
|
|
11
21
|
* Resolves model aliases to their full model names
|
|
12
22
|
* @param model - The model name or alias to resolve
|
|
@@ -32,8 +42,12 @@ export function getReasoningEffort(model, rawValue) {
|
|
|
32
42
|
if (!ALLOWED_REASONING_EFFORTS.has(normalized)) {
|
|
33
43
|
throw new Error(`Invalid reasoning_effort: ${rawValue}. Allowed values: low, medium, high, xhigh.`);
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
const agent = getAgentForModel(model);
|
|
46
|
+
if (agent === 'gemini') {
|
|
47
|
+
throw new Error('reasoning_effort is only supported for Claude and Codex models.');
|
|
48
|
+
}
|
|
49
|
+
if (agent === 'claude' && !CLAUDE_REASONING_EFFORTS.has(normalized)) {
|
|
50
|
+
throw new Error('Claude reasoning_effort supports only low, medium, high.');
|
|
37
51
|
}
|
|
38
52
|
return normalized;
|
|
39
53
|
}
|
|
@@ -83,23 +97,18 @@ export function buildCliCommand(options) {
|
|
|
83
97
|
// Resolve model
|
|
84
98
|
const rawModel = options.model || '';
|
|
85
99
|
const resolvedModel = resolveModelAlias(rawModel);
|
|
86
|
-
|
|
100
|
+
const agent = getAgentForModel(resolvedModel);
|
|
101
|
+
// Special handling for ultra aliases: default to higher reasoning if not specified
|
|
87
102
|
let reasoningEffortArg = options.reasoning_effort;
|
|
88
|
-
if (
|
|
89
|
-
|
|
103
|
+
if (!reasoningEffortArg) {
|
|
104
|
+
if (rawModel === 'codex-ultra') {
|
|
105
|
+
reasoningEffortArg = 'xhigh';
|
|
106
|
+
}
|
|
107
|
+
else if (rawModel === 'claude-ultra') {
|
|
108
|
+
reasoningEffortArg = 'high';
|
|
109
|
+
}
|
|
90
110
|
}
|
|
91
111
|
const reasoningEffort = getReasoningEffort(resolvedModel, reasoningEffortArg);
|
|
92
|
-
// Determine agent
|
|
93
|
-
let agent;
|
|
94
|
-
if (resolvedModel.startsWith('gpt-')) {
|
|
95
|
-
agent = 'codex';
|
|
96
|
-
}
|
|
97
|
-
else if (resolvedModel.startsWith('gemini')) {
|
|
98
|
-
agent = 'gemini';
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
agent = 'claude';
|
|
102
|
-
}
|
|
103
112
|
// Build CLI path and args
|
|
104
113
|
let cliPath;
|
|
105
114
|
let args;
|
|
@@ -136,6 +145,9 @@ export function buildCliCommand(options) {
|
|
|
136
145
|
if (options.session_id && typeof options.session_id === 'string') {
|
|
137
146
|
args.push('-r', options.session_id, '--fork-session');
|
|
138
147
|
}
|
|
148
|
+
if (reasoningEffort) {
|
|
149
|
+
args.push('--effort', reasoningEffort);
|
|
150
|
+
}
|
|
139
151
|
args.push('-p', prompt);
|
|
140
152
|
if (resolvedModel) {
|
|
141
153
|
args.push('--model', resolvedModel);
|
package/dist/cli.js
CHANGED
|
@@ -40,7 +40,7 @@ Options:
|
|
|
40
40
|
--prompt Prompt string (mutually exclusive with --prompt_file)
|
|
41
41
|
--prompt_file Path to a file containing the prompt
|
|
42
42
|
--session_id Session ID to resume
|
|
43
|
-
--reasoning_effort Codex
|
|
43
|
+
--reasoning_effort Claude/Codex: Claude=low|medium|high, Codex=low|medium|high|xhigh
|
|
44
44
|
--help Show this help message
|
|
45
45
|
|
|
46
46
|
Raw CLI output goes to stdout. Use cli.run.parse to parse the output:
|
package/dist/server.js
CHANGED
|
@@ -116,7 +116,7 @@ export class ClaudeCodeServer {
|
|
|
116
116
|
**IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
|
|
117
117
|
|
|
118
118
|
**Supported models**:
|
|
119
|
-
"claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
|
|
119
|
+
"claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
|
|
120
120
|
|
|
121
121
|
**Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
|
|
122
122
|
|
|
@@ -144,11 +144,11 @@ export class ClaudeCodeServer {
|
|
|
144
144
|
},
|
|
145
145
|
model: {
|
|
146
146
|
type: 'string',
|
|
147
|
-
description: 'The model to use. Aliases: "claude-ultra", "codex-ultra" (auto
|
|
147
|
+
description: 'The model to use. Aliases: "claude-ultra" (auto high effort), "codex-ultra" (auto xhigh reasoning), "gemini-ultra". Standard: "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1", "gemini-2.5-pro", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", etc.',
|
|
148
148
|
},
|
|
149
149
|
reasoning_effort: {
|
|
150
150
|
type: 'string',
|
|
151
|
-
description: 'Codex
|
|
151
|
+
description: 'Reasoning control for Claude and Codex. Claude uses --effort with "low", "medium", "high". Codex uses model_reasoning_effort with "low", "medium", "high", "xhigh".',
|
|
152
152
|
},
|
|
153
153
|
session_id: {
|
|
154
154
|
type: 'string',
|
package/docs/concept.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# AI CLI MCP Server - Concept
|
|
2
|
+
|
|
3
|
+
## What is this?
|
|
4
|
+
|
|
5
|
+
AI CLI MCP Server (`ai-cli-mcp`) は、複数のAI CLIツール(Claude Code, Codex, Gemini)をMCPプロトコル経由でバックグラウンド実行するサーバーである。
|
|
6
|
+
|
|
7
|
+
## 解決する課題
|
|
8
|
+
|
|
9
|
+
### 1. 単一エージェントのボトルネック
|
|
10
|
+
|
|
11
|
+
CursorなどのAI IDE は内部で1つのAIエージェントを逐次実行する。複雑なマルチステップ作業(リファクタリング + テスト作成 + ドキュメント更新など)では、1つずつ順番に処理するしかなく時間がかかる。
|
|
12
|
+
|
|
13
|
+
### 2. モデル/プロバイダーのロックイン
|
|
14
|
+
|
|
15
|
+
IDE が採用するAIモデルに依存し、タスクごとに最適なモデルを選択できない。コード生成には Claude Opus、高速な軽作業には Haiku、といった使い分けができない。
|
|
16
|
+
|
|
17
|
+
### 3. AI CLI の非同期実行の欠如
|
|
18
|
+
|
|
19
|
+
Claude Code や Codex CLI は対話的に使うことを前提としており、バックグラウンドでの非同期プロセス管理をネイティブにはサポートしていない。
|
|
20
|
+
|
|
21
|
+
## Core Idea
|
|
22
|
+
|
|
23
|
+
**MCP を「AIエージェントのプロセスマネージャー」として使う。**
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
MCP Client (Cursor, Claude Code, etc.)
|
|
27
|
+
│
|
|
28
|
+
├─ run(prompt, model="opus") → PID 1234 (即座に返却)
|
|
29
|
+
├─ run(prompt, model="gpt-5.2-codex") → PID 1235
|
|
30
|
+
├─ run(prompt, model="gemini-2.5-pro") → PID 1236
|
|
31
|
+
│
|
|
32
|
+
├─ list_processes() → 実行状況一覧
|
|
33
|
+
├─ get_result(pid) → 個別結果取得
|
|
34
|
+
└─ wait(pids) → 全完了待ち
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **Fire-and-forget**: `run` は即座にPIDを返し、呼び出し側はブロックされない
|
|
38
|
+
- **Blocking wait**: `wait(pids)` は指定プロセスが全完了するまでブロックする。利用者がポーリングループを組む必要はない。`run` で複数タスクを投げ、自分の作業を進めた後、`wait` 一発で全結果を回収するのが基本フロー
|
|
39
|
+
- **マルチモデル**: プロンプトごとに異なるAIモデルを選択可能
|
|
40
|
+
- **プロセスライフサイクル管理**: 起動・監視・結果取得・強制終了を統一APIで提供
|
|
41
|
+
|
|
42
|
+
### 基本フロー
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
run(task1) → PID 1
|
|
46
|
+
run(task2) → PID 2
|
|
47
|
+
run(task3) → PID 3
|
|
48
|
+
↓
|
|
49
|
+
(呼び出し側は自分の作業を続行)
|
|
50
|
+
↓
|
|
51
|
+
wait([PID 1, 2, 3]) → ブロック → 全完了後に結果をまとめて返却
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Core Responsibility
|
|
55
|
+
|
|
56
|
+
このツールの責務は3つに集約される:
|
|
57
|
+
|
|
58
|
+
### 1. どのAI CLIからでも同じプロンプトで使える
|
|
59
|
+
|
|
60
|
+
このMCPサーバーは Claude Code / Gemini CLI / Codex CLI のいずれをホスト(呼び出し元)としても、同じツール名・同じ引数・同じプロンプトで動作する。ホスト側のAIがどのプロバイダーであっても、統一されたMCPインターフェースを通じて同一の体験を提供する。
|
|
61
|
+
|
|
62
|
+
### 2. CLI差異の完全な隠蔽
|
|
63
|
+
|
|
64
|
+
利用者(主にAIエージェント)は Claude Code / Codex / Gemini の個別仕様を一切知る必要がない。
|
|
65
|
+
|
|
66
|
+
- パーミッションフラグの違い(`--dangerously-skip-permissions` / `--full-auto` / `-y`)
|
|
67
|
+
- 出力形式の違い(Claude の JSON / Codex のログ / Gemini の出力)
|
|
68
|
+
- セッション管理の違い(`--session-id` / `--session` / `-s`)
|
|
69
|
+
- モデル名の指定方法の違い
|
|
70
|
+
|
|
71
|
+
これらはすべて内部で吸収される。利用者は「モデル名とプロンプトを渡すだけ」でよい。
|
|
72
|
+
|
|
73
|
+
### 3. AI-Friendly な返り値
|
|
74
|
+
|
|
75
|
+
返り値は人間ではなく **AIエージェントが消費する** ことを前提に設計する。
|
|
76
|
+
|
|
77
|
+
- 各CLIの生出力(JSON、ログ、テキスト)をパースし、構造化されたオブジェクトとして返す
|
|
78
|
+
- `session_id` や `exitCode` などのメタデータを統一フォーマットで付与する
|
|
79
|
+
- `verbose` フラグで必要に応じてツール使用履歴も提供する
|
|
80
|
+
- AIが次のアクションを判断しやすい、一貫した構造を維持する
|
|
81
|
+
|
|
82
|
+
## Design Principles
|
|
83
|
+
|
|
84
|
+
### Thin Wrapper, Not a Framework
|
|
85
|
+
|
|
86
|
+
各AI CLIの既存機能をそのまま活かし、MCPのインターフェースでラップするだけに留める。独自のプロンプト処理やフィルタリングは行わない。
|
|
87
|
+
|
|
88
|
+
### Immediate Return
|
|
89
|
+
|
|
90
|
+
`run` は常に即座にPIDを返す。重い処理のブロッキングを避け、呼び出し元が他の作業を並行して進められるようにする。
|
|
91
|
+
|
|
92
|
+
### CLI Agnostic
|
|
93
|
+
|
|
94
|
+
Claude / Codex / Gemini のCLI差異(フラグ、出力形式)を内部で吸収し、統一されたインターフェースを提供する。新しいCLIの追加も最小限の変更で可能な構造にする。
|
|
95
|
+
|
|
96
|
+
## Architecture Overview
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
src/
|
|
100
|
+
├── server.ts # MCP Server 本体 (ツールハンドラ + プロセス管理)
|
|
101
|
+
├── cli-builder.ts # モデル名 → CLI コマンド構築 (パス解決・引数組み立て)
|
|
102
|
+
├── cli-utils.ts # CLI バイナリ検出・デバッグログ
|
|
103
|
+
├── parsers.ts # 各CLI出力のパース (Claude JSON / Codex logs / Gemini)
|
|
104
|
+
├── cli.ts # スタンドアロン CLI エントリポイント
|
|
105
|
+
└── cli-parse.ts # パース単体テスト用エントリポイント
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Session Stacking
|
|
109
|
+
|
|
110
|
+
`session_id` を使ってコンテキストを積み重ね、効率的にタスクを実行する推奨パターン。初回の `run` で構築したコンテキストを後続タスクに引き継ぎ、再読み込みコストを削減する。
|
|
111
|
+
|
|
112
|
+
詳細は [Session Stacking](./session-stacking.md) を参照。
|
|
113
|
+
|
|
114
|
+
## Model Aliases (Ultra)
|
|
115
|
+
|
|
116
|
+
`claude-ultra` / `codex-ultra` / `gemini-ultra` というエイリアスを提供している。
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
claude-ultra → opus
|
|
120
|
+
codex-ultra → gpt-5.4 (+ reasoning_effort: high)
|
|
121
|
+
gemini-ultra → gemini-3.1-pro-preview
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**設計意図**: AI プロバイダーのモデル名は頻繁に変わる。利用者(特にAIエージェント)が個々のモデル名の変遷を追う必要がないよう、「そのプロバイダーの最強モデル」を指す安定したエイリアスを提供する。マッピング先はサーバー側で更新するだけで、利用者のプロンプトを変更する必要がない。
|
|
125
|
+
|
|
126
|
+
## Security Model
|
|
127
|
+
|
|
128
|
+
このツールは **信頼された環境でのみ使用する** ことを前提としている。
|
|
129
|
+
|
|
130
|
+
- Claude Code は `--dangerously-skip-permissions` で実行される(すべてのファイル操作・コマンド実行が無許可で行われる)
|
|
131
|
+
- Codex は `--full-auto` で実行される
|
|
132
|
+
- Gemini は `-y`(自動承認)で実行される
|
|
133
|
+
|
|
134
|
+
つまり、このMCPサーバーに接続できるクライアントは、ローカルマシン上で **任意のコード実行が可能** である。ネットワーク越しの不特定多数への公開や、信頼できないクライアントからのアクセスは想定していない。
|
|
135
|
+
|
|
136
|
+
## Constraints
|
|
137
|
+
|
|
138
|
+
| 制約 | 詳細 |
|
|
139
|
+
|---|---|
|
|
140
|
+
| インメモリプロセス管理 | プロセス情報はサーバーのメモリ上にのみ保持される。サーバー再起動で全プロセス情報が消失する |
|
|
141
|
+
| stdio トランスポートのみ | HTTP/WebSocket 等のリモートトランスポートは未サポート。ローカル実行が前提 |
|
|
142
|
+
| 単一マシン | 分散実行やリモートマシンへのプロセス委譲はサポートしない |
|
|
143
|
+
| CLI の事前セットアップ必須 | 各AI CLIのインストール・認証はユーザーが事前に完了させる必要がある |
|
|
144
|
+
|
|
145
|
+
## Key Technical Decisions
|
|
146
|
+
|
|
147
|
+
| 決定 | 理由 |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `node:child_process.spawn` でプロセス管理 | 軽量で直接的。外部依存なしにPIDベースの管理が可能 |
|
|
150
|
+
| `--dangerously-skip-permissions` (Claude) | MCP経由の自動実行には非対話モードが必須 |
|
|
151
|
+
| `--full-auto` (Codex) / `-y` (Gemini) | 同上。各CLIの自動承認モード |
|
|
152
|
+
| `session_id` サポート | コンテキストキャッシュにより、大規模コードベースの読み込みコストを複数タスクで共有 |
|
|
153
|
+
| 出力パーサーの分離 | CLI出力形式の変更に対して個別に対応可能 |
|
|
154
|
+
| npx 配布 | インストール不要でMCP設定に直接記述可能 |
|
package/docs/prd.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# AI CLI MCP Server - Product Requirements Document
|
|
2
|
+
|
|
3
|
+
## Product Overview
|
|
4
|
+
|
|
5
|
+
| 項目 | 内容 |
|
|
6
|
+
|---|---|
|
|
7
|
+
| プロダクト名 | AI CLI MCP Server (`ai-cli-mcp`) |
|
|
8
|
+
| npm パッケージ | [ai-cli-mcp](https://www.npmjs.com/package/ai-cli-mcp) |
|
|
9
|
+
| 現バージョン | 2.8.2 |
|
|
10
|
+
| ライセンス | MIT |
|
|
11
|
+
| ターゲットユーザー | MCP対応AI IDE / CLI を利用する開発者 |
|
|
12
|
+
|
|
13
|
+
## Problem Statement
|
|
14
|
+
|
|
15
|
+
AI支援開発において、以下の制約がユーザーの生産性を阻害している:
|
|
16
|
+
|
|
17
|
+
1. **逐次処理の制約**: IDEのAIエージェントは1タスクずつしか処理できず、並行作業ができない
|
|
18
|
+
2. **モデル選択の制約**: IDEが提供するモデルに限定され、タスクに最適なモデルを選べない
|
|
19
|
+
3. **環境の制約**: AI CLIツールの豊富な機能(ファイル操作、Git操作、Web検索等)をIDE内から活用できない
|
|
20
|
+
|
|
21
|
+
## Goals
|
|
22
|
+
|
|
23
|
+
- **ホスト非依存**: Claude Code / Gemini CLI / Codex CLI のどれをホスト(呼び出し元)として使っても、同じプロンプト・同じインターフェースで動作すること
|
|
24
|
+
- **CLI差異の隠蔽**: 利用者が呼び出し先の Claude Code / Codex / Gemini の個別仕様(フラグ、出力形式、セッション管理等)を意識せず、モデル名とプロンプトだけで使えること
|
|
25
|
+
- **AI-Friendly な返り値**: 各CLIの生出力をパースし、AIエージェントが次のアクションを判断しやすい構造化データとして返すこと
|
|
26
|
+
- **並行実行**: MCP対応の任意のクライアントから、複数のAI CLIエージェントを並行実行できること
|
|
27
|
+
- **最小セットアップ**: npx 一行で起動可能であること
|
|
28
|
+
|
|
29
|
+
## Non-Goals
|
|
30
|
+
|
|
31
|
+
- AI CLI ツール自体の機能拡張
|
|
32
|
+
- 独自のプロンプトエンジニアリングやチェーン処理
|
|
33
|
+
- Web UI やダッシュボードの提供
|
|
34
|
+
- AI CLI ツールのインストールや認証の自動化
|
|
35
|
+
- 人間向けのリッチなフォーマット出力(返り値はAI消費が前提)
|
|
36
|
+
|
|
37
|
+
## Functional Requirements
|
|
38
|
+
|
|
39
|
+
### FR-1: プロセス実行 (`run`)
|
|
40
|
+
|
|
41
|
+
- ユーザーがプロンプト(文字列 or ファイルパス)、作業ディレクトリ、モデル名を指定してAIエージェントを起動できる
|
|
42
|
+
- プロセスはバックグラウンドで実行され、即座にPIDが返却される
|
|
43
|
+
- モデル名から適切なCLI(Claude / Codex / Gemini)が自動選択される
|
|
44
|
+
- Ultra エイリアス(`claude-ultra`, `codex-ultra`, `gemini-ultra`)による簡易モデル指定をサポート
|
|
45
|
+
- `session_id` による前回セッションの継続をサポート(Claude, Gemini)
|
|
46
|
+
- `reasoning_effort` による推論深度の指定をサポート(Codex)
|
|
47
|
+
|
|
48
|
+
### FR-2: プロセス一覧 (`list_processes`)
|
|
49
|
+
|
|
50
|
+
- 実行中・完了・失敗の全プロセスをPID・エージェント種別・ステータスとともに一覧表示できる
|
|
51
|
+
|
|
52
|
+
### FR-3: 結果取得 (`get_result`)
|
|
53
|
+
|
|
54
|
+
- PIDを指定して、プロセスの出力(パース済み)とメタデータを取得できる
|
|
55
|
+
- `verbose` オプションでツール使用履歴等の詳細情報を取得できる
|
|
56
|
+
- `session_id` がある場合は結果に含まれる
|
|
57
|
+
|
|
58
|
+
### FR-4: 一括待機 (`wait`)
|
|
59
|
+
|
|
60
|
+
- 複数PIDを指定して、全プロセスの完了を待機できる
|
|
61
|
+
- 呼び出し側がポーリングする必要はなく、`wait` 自体がブロックして完了まで待つ設計
|
|
62
|
+
- `run` → (自分の作業) → `wait` で結果回収、が基本フロー
|
|
63
|
+
- タイムアウト指定が可能(デフォルト: 180秒)
|
|
64
|
+
|
|
65
|
+
### FR-5: プロセス終了 (`kill_process`)
|
|
66
|
+
|
|
67
|
+
- PIDを指定して実行中のプロセスをSIGTERMで終了できる
|
|
68
|
+
|
|
69
|
+
### FR-6: クリーンアップ (`cleanup_processes`)
|
|
70
|
+
|
|
71
|
+
- 完了・失敗したプロセスをプロセスリストから削除し、メモリを解放できる
|
|
72
|
+
|
|
73
|
+
## Non-Functional Requirements
|
|
74
|
+
|
|
75
|
+
### NFR-1: パフォーマンス
|
|
76
|
+
|
|
77
|
+
- `run` のレスポンスタイムはプロセスの実行時間に依存せず、即座に返却すること
|
|
78
|
+
- 複数プロセスの同時実行をサポートすること
|
|
79
|
+
|
|
80
|
+
### NFR-2: 互換性
|
|
81
|
+
|
|
82
|
+
- Node.js v20 以上で動作すること
|
|
83
|
+
- MCP プロトコル仕様に準拠すること(`@modelcontextprotocol/sdk` 使用)
|
|
84
|
+
- stdio トランスポートで動作すること
|
|
85
|
+
|
|
86
|
+
### NFR-3: 信頼性
|
|
87
|
+
|
|
88
|
+
- CLIプロセスのクラッシュを適切にハンドリングし、ステータスに反映すること
|
|
89
|
+
- プロセスの stdout / stderr を確実に収集すること
|
|
90
|
+
|
|
91
|
+
### NFR-4: 運用性
|
|
92
|
+
|
|
93
|
+
- `npx ai-cli-mcp@latest` のみで起動可能であること
|
|
94
|
+
- 環境変数によるCLIパスのカスタマイズが可能であること
|
|
95
|
+
- `MCP_CLAUDE_DEBUG` によるデバッグログ出力をサポートすること
|
|
96
|
+
|
|
97
|
+
## Supported Models
|
|
98
|
+
|
|
99
|
+
| Provider | Models |
|
|
100
|
+
|---|---|
|
|
101
|
+
| Claude | `sonnet`, `sonnet[1m]`, `opus`, `opusplan`, `haiku` |
|
|
102
|
+
| Codex | `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.2-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1`, `gpt-5` |
|
|
103
|
+
| Gemini | `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3.1-pro-preview`, `gemini-3-pro-preview`, `gemini-3-flash-preview` |
|
|
104
|
+
| Ultra aliases | `claude-ultra`, `codex-ultra`, `gemini-ultra` |
|
|
105
|
+
|
|
106
|
+
## User Scenarios
|
|
107
|
+
|
|
108
|
+
### Scenario 1: 並列タスク実行
|
|
109
|
+
|
|
110
|
+
1. ユーザーがMCPクライアント経由で3つの `run` を発行(リファクタリング / テスト作成 / ドキュメント更新)
|
|
111
|
+
2. それぞれ異なるモデルで即座に起動、PIDが返却される
|
|
112
|
+
3. ユーザーは別作業を進めつつ、`wait` で全完了を待機
|
|
113
|
+
4. 各結果をまとめて確認
|
|
114
|
+
|
|
115
|
+
### Scenario 2: Session Stacking(推奨パターン)
|
|
116
|
+
|
|
117
|
+
`session_id` によるセッション再開を活用し、コンテキストを積み重ねて効率的にタスクを実行する。
|
|
118
|
+
|
|
119
|
+
1. `opus` で大規模コードベースを読み込み、`session_id` を取得
|
|
120
|
+
2. 同じ `session_id` を後続の `run` に渡して複数タスクを並行起動
|
|
121
|
+
3. コンテキストの再読み込みコストなしに、共有コンテキスト上で実行される
|
|
122
|
+
4. さらに後続タスクの `session_id` を使って追加の深掘りも可能(段階的スタッキング)
|
|
123
|
+
|
|
124
|
+
ポーリング不要: 手順2の後は `wait` で全完了をブロッキング待機すればよい。
|
|
125
|
+
|
|
126
|
+
**注意**: 多段スタッキング(手順4)に完全対応しているのは Claude のみ。Codex / Gemini は1回のセッション再開は可能だが、再開後に新しい `session_id` が返らないため、それ以上の連鎖はできない。詳細は [Session Stacking](./session-stacking.md) を参照。
|
|
127
|
+
|
|
128
|
+
## Security Model
|
|
129
|
+
|
|
130
|
+
本プロダクトは **信頼されたローカル環境** での使用を前提とする。
|
|
131
|
+
|
|
132
|
+
- 各AI CLIは自動承認モード(`--dangerously-skip-permissions` / `--full-auto` / `-y`)で実行されるため、接続クライアントはローカルマシン上で任意のコード実行が可能になる
|
|
133
|
+
- ネットワーク越しの不特定多数への公開は想定しない
|
|
134
|
+
- セキュリティ境界は「MCPサーバーへの接続可否」で制御される
|
|
135
|
+
|
|
136
|
+
## Constraints
|
|
137
|
+
|
|
138
|
+
- プロセス情報はインメモリのみ。サーバー再起動で消失する(永続化なし)
|
|
139
|
+
- stdio トランスポートのみ対応。リモート接続は未サポート
|
|
140
|
+
- 単一マシンでの実行が前提。分散実行はサポートしない
|
|
141
|
+
- 各AI CLIの事前インストール・認証はユーザー責任
|
|
142
|
+
|
|
143
|
+
## Release History (Notable)
|
|
144
|
+
|
|
145
|
+
- **v1.x**: Claude Code MCP として Claude CLI のみサポート
|
|
146
|
+
- **v2.x**: `ai-cli-mcp` にリネーム、Codex / Gemini サポート追加、非同期実行モデルに移行
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Session Stacking
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
Session Stacking は、`session_id` を使ってコンテキストを積み重ね、効率的にタスクを実行する推奨パターンである。
|
|
6
|
+
|
|
7
|
+
最初の `run` で構築したコンテキスト(コードベースの理解等)を `session_id` 経由で後続タスクに引き継ぐことで、同じ情報の再読み込みを避ける。
|
|
8
|
+
|
|
9
|
+
## 基本フロー
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Step 1: コンテキスト構築
|
|
13
|
+
run("src/を全部読んで構造を理解して", model="opus")
|
|
14
|
+
→ 結果: { session_id: "abc-123", ... }
|
|
15
|
+
|
|
16
|
+
Step 2: コンテキストを再利用して並行タスク実行
|
|
17
|
+
run("utilsをリファクタして", session_id="abc-123", model="sonnet") → PID 1
|
|
18
|
+
run("READMEを更新して", session_id="abc-123", model="haiku") → PID 2
|
|
19
|
+
|
|
20
|
+
Step 3: 結果回収
|
|
21
|
+
wait([PID 1, PID 2]) → ブロッキング待機 → 全結果返却
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 利点
|
|
25
|
+
|
|
26
|
+
- **コスト削減**: 大規模コードベースの再読み込みを避けられる
|
|
27
|
+
- **コンテキスト共有**: 1回の理解をベースに複数の派生タスクを実行できる
|
|
28
|
+
- **段階的な深掘り**: 概要把握 → 詳細分析 → 実装、とセッションを積み重ねられる
|
|
29
|
+
|
|
30
|
+
## 多段スタッキング
|
|
31
|
+
|
|
32
|
+
Session Stacking の真価は、セッションを2段、3段と重ねて深掘りしていける点にある。
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Step 1: 全体理解
|
|
36
|
+
run("src/を全部読んで構造を理解して", model="opus")
|
|
37
|
+
→ session_id: "abc-123"
|
|
38
|
+
|
|
39
|
+
Step 2: 詳細分析(Step 1 のコンテキストを継承)
|
|
40
|
+
run("認証周りの問題点を洗い出して", session_id="abc-123", model="opus")
|
|
41
|
+
→ session_id: "def-456" ← 新しい session_id が返る
|
|
42
|
+
|
|
43
|
+
Step 3: 実装(Step 1 + Step 2 のコンテキストを継承)
|
|
44
|
+
run("洗い出した問題を修正して", session_id="def-456", model="sonnet")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
各ステップのコンテキストが累積されるため、後段のタスクほど深い理解の上で実行される。
|
|
48
|
+
|
|
49
|
+
## 各CLIの対応状況
|
|
50
|
+
|
|
51
|
+
| CLI | 1回のセッション再開 | 多段スタッキング | 備考 |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| Claude | OK | **OK** | 再開後に新しい `session_id` が返る。`--fork-session` でセッションをフォークするため、元のセッションも保持される |
|
|
54
|
+
| Codex | OK | **NG** | 再開後に新しい `session_id` が返らない。2段(初回 + 1回再開)まで |
|
|
55
|
+
| Gemini | OK | **NG** | 再開後に新しい `session_id` が返らない。2段(初回 + 1回再開)まで |
|
|
56
|
+
|
|
57
|
+
**完全な多段 Session Stacking ができるのは現時点では Claude のみ。** Codex / Gemini は1回のセッション再開には対応しているが、再開時に新しい `session_id` が発行されないため、それ以上の連鎖はできない。これは各CLIの制約であり、本ツール側の制限ではない。
|
|
58
|
+
|
|
59
|
+
## 内部実装
|
|
60
|
+
|
|
61
|
+
各CLIのセッション再開方法の違いは `cli-builder.ts` 内で吸収される。利用者は `session_id` パラメータを渡すだけでよい。
|
|
62
|
+
|
|
63
|
+
| CLI | 内部で実行されるフラグ |
|
|
64
|
+
|---|---|
|
|
65
|
+
| Claude | `-r <session_id> --fork-session` |
|
|
66
|
+
| Gemini | `-r <session_id>` |
|
|
67
|
+
| Codex | `exec resume <session_id>` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-cli-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"mcpName": "io.github.mkXultra/ai-cli-mcp",
|
|
5
5
|
"description": "MCP server for AI CLI tools (Claude, Codex, and Gemini) with background process management",
|
|
6
6
|
"author": "mkXultra",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"test:coverage": "npm run build && vitest --coverage --config vitest.config.unit.ts",
|
|
21
21
|
"test:watch": "vitest --watch",
|
|
22
22
|
"cli.run": "tsx src/cli.ts",
|
|
23
|
-
"cli.run.parse": "tsx src/cli-parse.ts"
|
|
23
|
+
"cli.run.parse": "tsx src/cli-parse.ts",
|
|
24
|
+
"prepare": "husky"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"@modelcontextprotocol/sdk": "^1.11.2",
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"@semantic-release/git": "^10.0.1",
|
|
34
35
|
"@types/node": "^22.15.17",
|
|
35
36
|
"@vitest/coverage-v8": "^2.1.8",
|
|
37
|
+
"husky": "^9.1.7",
|
|
36
38
|
"semantic-release": "^25.0.2",
|
|
37
39
|
"tsx": "^4.19.4",
|
|
38
40
|
"typescript": "^5.8.3",
|
|
@@ -36,8 +36,8 @@ describe('cli-builder', () => {
|
|
|
36
36
|
expect(resolveModelAlias('claude-ultra')).toBe('opus');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it('should resolve codex-ultra to gpt-5.
|
|
40
|
-
expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.
|
|
39
|
+
it('should resolve codex-ultra to gpt-5.4', () => {
|
|
40
|
+
expect(resolveModelAlias('codex-ultra')).toBe('gpt-5.4');
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('should resolve gemini-ultra to gemini-3.1-pro-preview', () => {
|
|
@@ -76,6 +76,8 @@ describe('cli-builder', () => {
|
|
|
76
76
|
expect(getReasoningEffort('gpt-5.2', 'medium')).toBe('medium');
|
|
77
77
|
expect(getReasoningEffort('gpt-5.2', 'high')).toBe('high');
|
|
78
78
|
expect(getReasoningEffort('gpt-5.2', 'xhigh')).toBe('xhigh');
|
|
79
|
+
expect(getReasoningEffort('sonnet', 'high')).toBe('high');
|
|
80
|
+
expect(getReasoningEffort('', 'low')).toBe('low');
|
|
79
81
|
});
|
|
80
82
|
|
|
81
83
|
it('should throw for invalid reasoning effort value', () => {
|
|
@@ -84,9 +86,15 @@ describe('cli-builder', () => {
|
|
|
84
86
|
);
|
|
85
87
|
});
|
|
86
88
|
|
|
87
|
-
it('should
|
|
88
|
-
expect(() => getReasoningEffort('sonnet', '
|
|
89
|
-
'reasoning_effort
|
|
89
|
+
it('should reject xhigh for claude models', () => {
|
|
90
|
+
expect(() => getReasoningEffort('sonnet', 'xhigh')).toThrow(
|
|
91
|
+
'Claude reasoning_effort supports only low, medium, high.'
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should throw for unsupported model families', () => {
|
|
96
|
+
expect(() => getReasoningEffort('gemini-2.5-pro', 'high')).toThrow(
|
|
97
|
+
'reasoning_effort is only supported for Claude and Codex models.'
|
|
90
98
|
);
|
|
91
99
|
});
|
|
92
100
|
});
|
|
@@ -224,6 +232,57 @@ describe('cli-builder', () => {
|
|
|
224
232
|
expect(cmd.resolvedModel).toBe('opus');
|
|
225
233
|
expect(cmd.args).toContain('opus');
|
|
226
234
|
});
|
|
235
|
+
|
|
236
|
+
it('should resolve claude-ultra and default to high effort', () => {
|
|
237
|
+
const cmd = buildCliCommand({
|
|
238
|
+
prompt: 'test',
|
|
239
|
+
workFolder: '/tmp',
|
|
240
|
+
model: 'claude-ultra',
|
|
241
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(cmd.args).toContain('--effort');
|
|
245
|
+
expect(cmd.args).toContain('high');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should build claude command with reasoning_effort using --effort', () => {
|
|
249
|
+
const cmd = buildCliCommand({
|
|
250
|
+
prompt: 'test',
|
|
251
|
+
workFolder: '/tmp',
|
|
252
|
+
model: 'sonnet',
|
|
253
|
+
reasoning_effort: 'medium',
|
|
254
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(cmd.args).toContain('--effort');
|
|
258
|
+
expect(cmd.args).toContain('medium');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should reject xhigh reasoning_effort for claude', () => {
|
|
262
|
+
expect(() =>
|
|
263
|
+
buildCliCommand({
|
|
264
|
+
prompt: 'test',
|
|
265
|
+
workFolder: '/tmp',
|
|
266
|
+
model: 'sonnet',
|
|
267
|
+
reasoning_effort: 'xhigh',
|
|
268
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
269
|
+
})
|
|
270
|
+
).toThrow('Claude reasoning_effort supports only low, medium, high.');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should allow overriding reasoning_effort for claude-ultra', () => {
|
|
274
|
+
const cmd = buildCliCommand({
|
|
275
|
+
prompt: 'test',
|
|
276
|
+
workFolder: '/tmp',
|
|
277
|
+
model: 'claude-ultra',
|
|
278
|
+
reasoning_effort: 'low',
|
|
279
|
+
cliPaths: DEFAULT_CLI_PATHS,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(cmd.args).toContain('--effort');
|
|
283
|
+
expect(cmd.args).toContain('low');
|
|
284
|
+
expect(cmd.args).not.toContain('high');
|
|
285
|
+
});
|
|
227
286
|
});
|
|
228
287
|
|
|
229
288
|
describe('codex agent', () => {
|
|
@@ -280,7 +339,7 @@ describe('cli-builder', () => {
|
|
|
280
339
|
});
|
|
281
340
|
|
|
282
341
|
expect(cmd.agent).toBe('codex');
|
|
283
|
-
expect(cmd.resolvedModel).toBe('gpt-5.
|
|
342
|
+
expect(cmd.resolvedModel).toBe('gpt-5.4');
|
|
284
343
|
expect(cmd.args).toContain('-c');
|
|
285
344
|
expect(cmd.args).toContain('model_reasoning_effort=xhigh');
|
|
286
345
|
});
|
|
@@ -301,7 +301,7 @@ describe('Argument Validation Tests', () => {
|
|
|
301
301
|
).rejects.toThrow(/reasoning_effort/i);
|
|
302
302
|
});
|
|
303
303
|
|
|
304
|
-
it('should reject reasoning_effort for
|
|
304
|
+
it('should reject reasoning_effort for unsupported model families', async () => {
|
|
305
305
|
await setupServer();
|
|
306
306
|
const handler = handlers.get('callTool')!;
|
|
307
307
|
|
|
@@ -312,7 +312,7 @@ describe('Argument Validation Tests', () => {
|
|
|
312
312
|
arguments: {
|
|
313
313
|
prompt: 'test',
|
|
314
314
|
workFolder: '/tmp',
|
|
315
|
-
model: '
|
|
315
|
+
model: 'gemini-2.5-pro',
|
|
316
316
|
reasoning_effort: 'low'
|
|
317
317
|
}
|
|
318
318
|
}
|
package/src/cli-builder.ts
CHANGED
|
@@ -4,11 +4,22 @@ import { resolve as pathResolve, isAbsolute } from 'node:path';
|
|
|
4
4
|
// Model alias mappings for user-friendly model names
|
|
5
5
|
export const MODEL_ALIASES: Record<string, string> = {
|
|
6
6
|
'claude-ultra': 'opus',
|
|
7
|
-
'codex-ultra': 'gpt-5.
|
|
7
|
+
'codex-ultra': 'gpt-5.4',
|
|
8
8
|
'gemini-ultra': 'gemini-3.1-pro-preview'
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const ALLOWED_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
|
|
12
|
+
const CLAUDE_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
13
|
+
|
|
14
|
+
function getAgentForModel(model: string): 'codex' | 'claude' | 'gemini' {
|
|
15
|
+
if (model.startsWith('gpt-')) {
|
|
16
|
+
return 'codex';
|
|
17
|
+
}
|
|
18
|
+
if (model.startsWith('gemini')) {
|
|
19
|
+
return 'gemini';
|
|
20
|
+
}
|
|
21
|
+
return 'claude';
|
|
22
|
+
}
|
|
12
23
|
|
|
13
24
|
/**
|
|
14
25
|
* Resolves model aliases to their full model names
|
|
@@ -38,9 +49,15 @@ export function getReasoningEffort(model: string, rawValue: unknown): string {
|
|
|
38
49
|
`Invalid reasoning_effort: ${rawValue}. Allowed values: low, medium, high, xhigh.`
|
|
39
50
|
);
|
|
40
51
|
}
|
|
41
|
-
|
|
52
|
+
const agent = getAgentForModel(model);
|
|
53
|
+
if (agent === 'gemini') {
|
|
54
|
+
throw new Error(
|
|
55
|
+
'reasoning_effort is only supported for Claude and Codex models.'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (agent === 'claude' && !CLAUDE_REASONING_EFFORTS.has(normalized)) {
|
|
42
59
|
throw new Error(
|
|
43
|
-
'reasoning_effort
|
|
60
|
+
'Claude reasoning_effort supports only low, medium, high.'
|
|
44
61
|
);
|
|
45
62
|
}
|
|
46
63
|
return normalized;
|
|
@@ -117,25 +134,20 @@ export function buildCliCommand(options: BuildCliCommandOptions): CliCommand {
|
|
|
117
134
|
// Resolve model
|
|
118
135
|
const rawModel = options.model || '';
|
|
119
136
|
const resolvedModel = resolveModelAlias(rawModel);
|
|
137
|
+
const agent = getAgentForModel(resolvedModel);
|
|
120
138
|
|
|
121
|
-
// Special handling for
|
|
139
|
+
// Special handling for ultra aliases: default to higher reasoning if not specified
|
|
122
140
|
let reasoningEffortArg: string | undefined = options.reasoning_effort;
|
|
123
|
-
if (
|
|
124
|
-
|
|
141
|
+
if (!reasoningEffortArg) {
|
|
142
|
+
if (rawModel === 'codex-ultra') {
|
|
143
|
+
reasoningEffortArg = 'xhigh';
|
|
144
|
+
} else if (rawModel === 'claude-ultra') {
|
|
145
|
+
reasoningEffortArg = 'high';
|
|
146
|
+
}
|
|
125
147
|
}
|
|
126
148
|
|
|
127
149
|
const reasoningEffort = getReasoningEffort(resolvedModel, reasoningEffortArg);
|
|
128
150
|
|
|
129
|
-
// Determine agent
|
|
130
|
-
let agent: 'codex' | 'claude' | 'gemini';
|
|
131
|
-
if (resolvedModel.startsWith('gpt-')) {
|
|
132
|
-
agent = 'codex';
|
|
133
|
-
} else if (resolvedModel.startsWith('gemini')) {
|
|
134
|
-
agent = 'gemini';
|
|
135
|
-
} else {
|
|
136
|
-
agent = 'claude';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
151
|
// Build CLI path and args
|
|
140
152
|
let cliPath: string;
|
|
141
153
|
let args: string[];
|
|
@@ -180,6 +192,10 @@ export function buildCliCommand(options: BuildCliCommandOptions): CliCommand {
|
|
|
180
192
|
args.push('-r', options.session_id, '--fork-session');
|
|
181
193
|
}
|
|
182
194
|
|
|
195
|
+
if (reasoningEffort) {
|
|
196
|
+
args.push('--effort', reasoningEffort);
|
|
197
|
+
}
|
|
198
|
+
|
|
183
199
|
args.push('-p', prompt);
|
|
184
200
|
if (resolvedModel) {
|
|
185
201
|
args.push('--model', resolvedModel);
|
package/src/cli.ts
CHANGED
|
@@ -40,7 +40,7 @@ Options:
|
|
|
40
40
|
--prompt Prompt string (mutually exclusive with --prompt_file)
|
|
41
41
|
--prompt_file Path to a file containing the prompt
|
|
42
42
|
--session_id Session ID to resume
|
|
43
|
-
--reasoning_effort Codex
|
|
43
|
+
--reasoning_effort Claude/Codex: Claude=low|medium|high, Codex=low|medium|high|xhigh
|
|
44
44
|
--help Show this help message
|
|
45
45
|
|
|
46
46
|
Raw CLI output goes to stdout. Use cli.run.parse to parse the output:
|
package/src/server.ts
CHANGED
|
@@ -162,7 +162,7 @@ export class ClaudeCodeServer {
|
|
|
162
162
|
**IMPORTANT**: This tool now returns immediately with a PID. Use other tools to check status and get results.
|
|
163
163
|
|
|
164
164
|
**Supported models**:
|
|
165
|
-
"claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
|
|
165
|
+
"claude-ultra", "codex-ultra", "gemini-ultra", "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2", "gpt-5.1", "gpt-5.1-codex", "gpt-5-codex", "gpt-5-codex-mini", "gpt-5", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview"
|
|
166
166
|
|
|
167
167
|
**Prompt input**: You must provide EITHER prompt (string) OR prompt_file (file path), but not both.
|
|
168
168
|
|
|
@@ -190,11 +190,11 @@ export class ClaudeCodeServer {
|
|
|
190
190
|
},
|
|
191
191
|
model: {
|
|
192
192
|
type: 'string',
|
|
193
|
-
description: 'The model to use. Aliases: "claude-ultra", "codex-ultra" (auto
|
|
193
|
+
description: 'The model to use. Aliases: "claude-ultra" (auto high effort), "codex-ultra" (auto xhigh reasoning), "gemini-ultra". Standard: "sonnet", "sonnet[1m]", "opus", "opusplan", "haiku", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex", "gpt-5.1-codex-mini", "gpt-5.1", "gemini-2.5-pro", "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", etc.',
|
|
194
194
|
},
|
|
195
195
|
reasoning_effort: {
|
|
196
196
|
type: 'string',
|
|
197
|
-
description: 'Codex
|
|
197
|
+
description: 'Reasoning control for Claude and Codex. Claude uses --effort with "low", "medium", "high". Codex uses model_reasoning_effort with "low", "medium", "high", "xhigh".',
|
|
198
198
|
},
|
|
199
199
|
session_id: {
|
|
200
200
|
type: 'string',
|