ai-cli-mcp 2.19.0 → 2.20.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.ja.md +34 -8
  3. package/README.md +41 -8
  4. package/dist/app/cli.js +1 -0
  5. package/dist/app/mcp.js +64 -12
  6. package/dist/cli-builder.js +13 -6
  7. package/dist/cli-process-service.js +76 -91
  8. package/dist/cli-utils.js +6 -0
  9. package/dist/cli.js +1 -1
  10. package/dist/model-catalog.js +3 -2
  11. package/dist/parsers.js +8 -2
  12. package/package.json +27 -3
  13. package/server.json +3 -3
  14. package/.gemini/settings.json +0 -11
  15. package/.github/dependabot.yml +0 -28
  16. package/.github/pull_request_template.md +0 -28
  17. package/.github/workflows/ci.yml +0 -34
  18. package/.github/workflows/dependency-review.yml +0 -22
  19. package/.github/workflows/publish.yml +0 -89
  20. package/.github/workflows/test.yml +0 -20
  21. package/.github/workflows/watch-session-prs.yml +0 -276
  22. package/.husky/pre-commit +0 -1
  23. package/.mcp.json +0 -11
  24. package/.releaserc.json +0 -18
  25. package/.vscode/settings.json +0 -3
  26. package/CONTRIBUTING.md +0 -81
  27. package/dist/__tests__/app-cli.test.js +0 -392
  28. package/dist/__tests__/cli-bin-smoke.test.js +0 -101
  29. package/dist/__tests__/cli-builder.test.js +0 -442
  30. package/dist/__tests__/cli-process-service.test.js +0 -655
  31. package/dist/__tests__/cli-utils.test.js +0 -171
  32. package/dist/__tests__/e2e.test.js +0 -256
  33. package/dist/__tests__/edge-cases.test.js +0 -130
  34. package/dist/__tests__/error-cases.test.js +0 -292
  35. package/dist/__tests__/mcp-contract.test.js +0 -636
  36. package/dist/__tests__/mocks.js +0 -32
  37. package/dist/__tests__/model-alias.test.js +0 -36
  38. package/dist/__tests__/parsers.test.js +0 -646
  39. package/dist/__tests__/peek.test.js +0 -36
  40. package/dist/__tests__/process-management.test.js +0 -949
  41. package/dist/__tests__/server.test.js +0 -809
  42. package/dist/__tests__/setup.js +0 -11
  43. package/dist/__tests__/utils/claude-mock.js +0 -80
  44. package/dist/__tests__/utils/mcp-client.js +0 -121
  45. package/dist/__tests__/utils/opencode-mock.js +0 -91
  46. package/dist/__tests__/utils/persistent-mock.js +0 -28
  47. package/dist/__tests__/utils/test-helpers.js +0 -11
  48. package/dist/__tests__/validation.test.js +0 -308
  49. package/dist/__tests__/version-print.test.js +0 -65
  50. package/dist/__tests__/wait.test.js +0 -260
  51. package/docs/RELEASE_CHECKLIST.md +0 -65
  52. package/docs/cli-architecture.md +0 -275
  53. package/docs/concept.md +0 -154
  54. package/docs/development.md +0 -156
  55. package/docs/e2e-testing.md +0 -148
  56. package/docs/prd.md +0 -146
  57. package/docs/session-stacking.md +0 -67
  58. package/src/__tests__/app-cli.test.ts +0 -495
  59. package/src/__tests__/cli-bin-smoke.test.ts +0 -136
  60. package/src/__tests__/cli-builder.test.ts +0 -549
  61. package/src/__tests__/cli-process-service.test.ts +0 -759
  62. package/src/__tests__/cli-utils.test.ts +0 -200
  63. package/src/__tests__/e2e.test.ts +0 -311
  64. package/src/__tests__/edge-cases.test.ts +0 -176
  65. package/src/__tests__/error-cases.test.ts +0 -370
  66. package/src/__tests__/mcp-contract.test.ts +0 -755
  67. package/src/__tests__/mocks.ts +0 -35
  68. package/src/__tests__/model-alias.test.ts +0 -44
  69. package/src/__tests__/parsers.test.ts +0 -730
  70. package/src/__tests__/peek.test.ts +0 -44
  71. package/src/__tests__/process-management.test.ts +0 -1129
  72. package/src/__tests__/server.test.ts +0 -1020
  73. package/src/__tests__/setup.ts +0 -13
  74. package/src/__tests__/utils/claude-mock.ts +0 -87
  75. package/src/__tests__/utils/mcp-client.ts +0 -159
  76. package/src/__tests__/utils/opencode-mock.ts +0 -108
  77. package/src/__tests__/utils/persistent-mock.ts +0 -33
  78. package/src/__tests__/utils/test-helpers.ts +0 -13
  79. package/src/__tests__/validation.test.ts +0 -369
  80. package/src/__tests__/version-print.test.ts +0 -81
  81. package/src/__tests__/wait.test.ts +0 -302
  82. package/src/app/cli.ts +0 -424
  83. package/src/app/mcp.ts +0 -466
  84. package/src/bin/ai-cli-mcp.ts +0 -7
  85. package/src/bin/ai-cli.ts +0 -11
  86. package/src/cli-builder.ts +0 -274
  87. package/src/cli-parse.ts +0 -105
  88. package/src/cli-process-service.ts +0 -709
  89. package/src/cli-utils.ts +0 -258
  90. package/src/cli.ts +0 -124
  91. package/src/model-catalog.ts +0 -87
  92. package/src/parsers.ts +0 -965
  93. package/src/peek.ts +0 -95
  94. package/src/process-result.ts +0 -88
  95. package/src/process-service.ts +0 -368
  96. package/src/server.ts +0 -10
  97. package/tsconfig.json +0 -16
  98. package/vitest.config.e2e.ts +0 -27
  99. package/vitest.config.ts +0 -22
  100. package/vitest.config.unit.ts +0 -28
package/src/cli-utils.ts DELETED
@@ -1,258 +0,0 @@
1
- import { accessSync, constants } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
- import * as path from 'path';
5
-
6
- const debugMode = process.env.MCP_CLAUDE_DEBUG === 'true';
7
-
8
- export function debugLog(message?: any, ...optionalParams: any[]): void {
9
- if (debugMode) {
10
- console.error(message, ...optionalParams);
11
- }
12
- }
13
-
14
- export interface CliBinaryStatus {
15
- configuredCommand: string;
16
- resolvedPath: string | null;
17
- available: boolean;
18
- lookup: 'env' | 'local' | 'path';
19
- error?: string;
20
- }
21
-
22
- export type CliBinaryName = 'claude' | 'codex' | 'gemini' | 'forge' | 'opencode';
23
-
24
- export interface CliPaths {
25
- claude: string;
26
- codex: string;
27
- gemini: string;
28
- forge: string;
29
- opencode: string;
30
- }
31
-
32
- export interface CliDoctorStatus {
33
- claude: CliBinaryStatus;
34
- codex: CliBinaryStatus;
35
- gemini: CliBinaryStatus;
36
- forge: CliBinaryStatus;
37
- opencode: CliBinaryStatus;
38
- }
39
-
40
- function getPathDelimiter(): string {
41
- return process.platform === 'win32' ? ';' : ':';
42
- }
43
-
44
- function getPathExtensions(): string[] {
45
- if (process.platform !== 'win32') {
46
- return [''];
47
- }
48
-
49
- const rawPathext = process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM';
50
- return ['', ...rawPathext.split(';').filter(Boolean)];
51
- }
52
-
53
- function findExecutableOnPath(commandName: string): string | null {
54
- const rawPath = process.env.PATH || '';
55
- if (!rawPath) {
56
- return null;
57
- }
58
-
59
- const pathEntries = rawPath.split(getPathDelimiter()).filter(Boolean);
60
- const extensions = getPathExtensions();
61
-
62
- for (const entry of pathEntries) {
63
- for (const extension of extensions) {
64
- const candidate = join(entry, `${commandName}${extension}`);
65
- if (isExecutableFile(candidate)) {
66
- return candidate;
67
- }
68
- }
69
- }
70
-
71
- return null;
72
- }
73
-
74
- function validateCustomCliName(envVarName: string, customCliName: string): string | null {
75
- if (path.isAbsolute(customCliName)) {
76
- return null;
77
- }
78
-
79
- if (
80
- customCliName.startsWith('./') ||
81
- customCliName.startsWith('../') ||
82
- customCliName.includes('/')
83
- ) {
84
- return `Invalid ${envVarName}: Relative paths are not allowed. Use either a simple name (e.g., '${customCliName.split('/').pop() || 'cli'}') or an absolute path (e.g., '/tmp/${customCliName.split('/').pop() || 'cli'}-test')`;
85
- }
86
-
87
- return null;
88
- }
89
-
90
- function inspectCliBinary(options: {
91
- envVarName: string;
92
- customCliName: string | undefined;
93
- defaultCliName: string;
94
- localInstallPath?: string;
95
- }): CliBinaryStatus {
96
- const configuredCommand = options.customCliName || options.defaultCliName;
97
-
98
- if (options.customCliName) {
99
- const validationError = validateCustomCliName(options.envVarName, options.customCliName);
100
- if (validationError) {
101
- return {
102
- configuredCommand,
103
- resolvedPath: null,
104
- available: false,
105
- lookup: 'env',
106
- error: validationError,
107
- };
108
- }
109
-
110
- if (path.isAbsolute(options.customCliName)) {
111
- return {
112
- configuredCommand,
113
- resolvedPath: options.customCliName,
114
- available: isExecutableFile(options.customCliName),
115
- lookup: 'env',
116
- };
117
- }
118
-
119
- const resolvedPath = findExecutableOnPath(configuredCommand);
120
- return {
121
- configuredCommand,
122
- resolvedPath,
123
- available: resolvedPath !== null,
124
- lookup: 'env',
125
- };
126
- }
127
-
128
- if (options.localInstallPath && isExecutableFile(options.localInstallPath)) {
129
- return {
130
- configuredCommand,
131
- resolvedPath: options.localInstallPath,
132
- available: true,
133
- lookup: 'local',
134
- };
135
- }
136
-
137
- const resolvedPath = findExecutableOnPath(configuredCommand);
138
- return {
139
- configuredCommand,
140
- resolvedPath,
141
- available: resolvedPath !== null,
142
- lookup: 'path',
143
- };
144
- }
145
-
146
- function getCliCommandOrThrow(status: CliBinaryStatus): string {
147
- if (status.error) {
148
- throw new Error(status.error);
149
- }
150
-
151
- if (status.lookup === 'env' && !path.isAbsolute(status.configuredCommand)) {
152
- return status.configuredCommand;
153
- }
154
-
155
- return status.resolvedPath || status.configuredCommand;
156
- }
157
-
158
- function isExecutableFile(filePath: string): boolean {
159
- try {
160
- accessSync(filePath, constants.X_OK);
161
- return true;
162
- } catch {
163
- return false;
164
- }
165
- }
166
-
167
- function getCliBinaryConfig(name: CliBinaryName): {
168
- envVarName: string;
169
- customCliName: string | undefined;
170
- defaultCliName: string;
171
- localInstallPath?: string;
172
- } {
173
- if (name === 'claude') {
174
- return {
175
- envVarName: 'CLAUDE_CLI_NAME',
176
- customCliName: process.env.CLAUDE_CLI_NAME,
177
- defaultCliName: 'claude',
178
- localInstallPath: join(homedir(), '.claude', 'local', 'claude'),
179
- };
180
- }
181
-
182
- if (name === 'codex') {
183
- return {
184
- envVarName: 'CODEX_CLI_NAME',
185
- customCliName: process.env.CODEX_CLI_NAME,
186
- defaultCliName: 'codex',
187
- localInstallPath: join(homedir(), '.codex', 'local', 'codex'),
188
- };
189
- }
190
-
191
- if (name === 'forge') {
192
- return {
193
- envVarName: 'FORGE_CLI_NAME',
194
- customCliName: process.env.FORGE_CLI_NAME,
195
- defaultCliName: 'forge',
196
- localInstallPath: join(homedir(), '.forge', 'local', 'forge'),
197
- };
198
- }
199
-
200
- if (name === 'opencode') {
201
- return {
202
- envVarName: 'OPENCODE_CLI_NAME',
203
- customCliName: process.env.OPENCODE_CLI_NAME,
204
- defaultCliName: 'opencode',
205
- };
206
- }
207
-
208
- return {
209
- envVarName: 'GEMINI_CLI_NAME',
210
- customCliName: process.env.GEMINI_CLI_NAME,
211
- defaultCliName: 'gemini',
212
- localInstallPath: join(homedir(), '.gemini', 'local', 'gemini'),
213
- };
214
- }
215
-
216
- function getCliBinaryStatus(name: CliBinaryName): CliBinaryStatus {
217
- return inspectCliBinary(getCliBinaryConfig(name));
218
- }
219
-
220
- export function getCliDoctorStatus(): CliDoctorStatus {
221
- return {
222
- claude: getCliBinaryStatus('claude'),
223
- codex: getCliBinaryStatus('codex'),
224
- gemini: getCliBinaryStatus('gemini'),
225
- forge: getCliBinaryStatus('forge'),
226
- opencode: getCliBinaryStatus('opencode'),
227
- };
228
- }
229
-
230
- export function findGeminiCli(): string {
231
- debugLog('[Debug] Attempting to find Gemini CLI...');
232
- const status = getCliBinaryStatus('gemini');
233
- return getCliCommandOrThrow(status);
234
- }
235
-
236
- export function findCodexCli(): string {
237
- debugLog('[Debug] Attempting to find Codex CLI...');
238
- const status = getCliBinaryStatus('codex');
239
- return getCliCommandOrThrow(status);
240
- }
241
-
242
- export function findForgeCli(): string {
243
- debugLog('[Debug] Attempting to find Forge CLI...');
244
- const status = getCliBinaryStatus('forge');
245
- return getCliCommandOrThrow(status);
246
- }
247
-
248
- export function findOpencodeCli(): string {
249
- debugLog('[Debug] Attempting to find OpenCode CLI...');
250
- const status = getCliBinaryStatus('opencode');
251
- return getCliCommandOrThrow(status);
252
- }
253
-
254
- export function findClaudeCli(): string {
255
- debugLog('[Debug] Attempting to find Claude CLI...');
256
- const status = getCliBinaryStatus('claude');
257
- return getCliCommandOrThrow(status);
258
- }
package/src/cli.ts DELETED
@@ -1,124 +0,0 @@
1
- #!/usr/bin/env node
2
- import { spawn } from 'node:child_process';
3
- import { buildCliCommand } from './cli-builder.js';
4
- import { findClaudeCli, findCodexCli, findForgeCli, findGeminiCli, findOpencodeCli } from './cli-utils.js';
5
-
6
- /**
7
- * Minimal argv parser. No external dependencies.
8
- * Supports: --key value, --key=value
9
- */
10
- function parseArgs(argv: string[]): Record<string, string> {
11
- const result: Record<string, string> = {};
12
- for (let i = 0; i < argv.length; i++) {
13
- const arg = argv[i];
14
- if (!arg.startsWith('--')) continue;
15
-
16
- const eqIdx = arg.indexOf('=');
17
- if (eqIdx !== -1) {
18
- // --key=value
19
- result[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
20
- } else {
21
- // --key value
22
- const key = arg.slice(2);
23
- const next = argv[i + 1];
24
- if (next !== undefined && !next.startsWith('--')) {
25
- result[key] = next;
26
- i++;
27
- } else {
28
- result[key] = '';
29
- }
30
- }
31
- }
32
- return result;
33
- }
34
-
35
- const USAGE = `Usage: npm run -s cli.run -- --model <model> --workFolder <path> --prompt "..." [options]
36
-
37
- Options:
38
- --model Model name or alias (e.g. sonnet, opus, gpt-5.2-codex, gemini-2.5-pro, forge, opencode, oc-openai/gpt-5.4)
39
- --workFolder Working directory (absolute path)
40
- --prompt Prompt string (mutually exclusive with --prompt_file)
41
- --prompt_file Path to a file containing the prompt
42
- --session_id Session ID to resume, including OpenCode in-place resumes
43
- --reasoning_effort Claude/Codex only: Claude=low|medium|high, Codex=low|medium|high|xhigh; unsupported for Gemini, Forge, and OpenCode
44
- --help Show this help message
45
-
46
- Raw CLI output goes to stdout. Use cli.run.parse to parse the output:
47
- npm run -s cli.run -- ... > raw.txt
48
- npm run -s cli.run.parse -- --agent claude < raw.txt
49
- npm run -s cli.run.parse -- --agent opencode < raw.txt
50
- `;
51
-
52
- async function main(): Promise<void> {
53
- const args = parseArgs(process.argv.slice(2));
54
-
55
- if ('help' in args) {
56
- process.stdout.write(USAGE);
57
- process.exit(0);
58
- }
59
-
60
- if (!args.workFolder) {
61
- process.stderr.write('Error: --workFolder is required\n\n');
62
- process.stderr.write(USAGE);
63
- process.exit(1);
64
- }
65
-
66
- if (!args.prompt && !args.prompt_file) {
67
- process.stderr.write('Error: --prompt or --prompt_file is required\n\n');
68
- process.stderr.write(USAGE);
69
- process.exit(1);
70
- }
71
-
72
- // Resolve CLI paths
73
- const cliPaths = {
74
- claude: findClaudeCli(),
75
- codex: findCodexCli(),
76
- gemini: findGeminiCli(),
77
- forge: findForgeCli(),
78
- opencode: findOpencodeCli(),
79
- };
80
-
81
- // Build command
82
- let cmd;
83
- try {
84
- cmd = buildCliCommand({
85
- prompt: args.prompt || undefined,
86
- prompt_file: args.prompt_file || undefined,
87
- workFolder: args.workFolder,
88
- model: args.model || undefined,
89
- session_id: args.session_id || undefined,
90
- reasoning_effort: args.reasoning_effort || undefined,
91
- cliPaths,
92
- });
93
- } catch (error: any) {
94
- process.stderr.write(`Error: ${error.message}\n`);
95
- process.exit(1);
96
- }
97
-
98
- // Log agent info to stderr (does not pollute stdout)
99
- process.stderr.write(`[cli.run] agent=${cmd.agent} model=${cmd.resolvedModel || '(default)'}\n`);
100
-
101
- // Spawn foreground process — raw output passthrough
102
- const child = spawn(cmd.cliPath, cmd.args, {
103
- cwd: cmd.cwd,
104
- stdio: 'inherit',
105
- detached: false,
106
- });
107
-
108
- const exitCode = await new Promise<number>((resolve) => {
109
- child.on('close', (code) => {
110
- resolve(code ?? 1);
111
- });
112
- child.on('error', (err) => {
113
- process.stderr.write(`Process error: ${err.message}\n`);
114
- resolve(1);
115
- });
116
- });
117
-
118
- process.exit(exitCode);
119
- }
120
-
121
- main().catch((err) => {
122
- process.stderr.write(`Fatal error: ${err.message}\n`);
123
- process.exit(1);
124
- });
@@ -1,87 +0,0 @@
1
- export const CLAUDE_MODELS = ['sonnet', 'sonnet[1m]', 'opus', 'opusplan', 'haiku'] as const;
2
- export const CODEX_MODELS = [
3
- 'gpt-5.4',
4
- 'gpt-5.3-codex',
5
- 'gpt-5.2-codex',
6
- 'gpt-5.1-codex-mini',
7
- 'gpt-5.1-codex-max',
8
- 'gpt-5.2',
9
- 'gpt-5.1',
10
- 'gpt-5.1-codex',
11
- 'gpt-5-codex',
12
- 'gpt-5-codex-mini',
13
- 'gpt-5',
14
- ] as const;
15
- export const GEMINI_MODELS = [
16
- 'gemini-2.5-pro',
17
- 'gemini-2.5-flash',
18
- 'gemini-3.1-pro-preview',
19
- 'gemini-3-pro-preview',
20
- 'gemini-3-flash-preview',
21
- ] as const;
22
- export const FORGE_MODELS = ['forge'] as const;
23
- export const OPENCODE_MODELS = ['opencode'] as const;
24
-
25
- export const MODEL_ALIASES: Record<string, string> = {
26
- 'claude-ultra': 'opus',
27
- 'codex-ultra': 'gpt-5.4',
28
- 'gemini-ultra': 'gemini-3.1-pro-preview',
29
- };
30
-
31
- export const MODEL_ALIAS_DETAILS = [
32
- { name: 'claude-ultra', resolvesTo: 'opus', agent: 'claude', defaultReasoningEffort: 'high' },
33
- { name: 'codex-ultra', resolvesTo: 'gpt-5.4', agent: 'codex', defaultReasoningEffort: 'xhigh' },
34
- { name: 'gemini-ultra', resolvesTo: 'gemini-3.1-pro-preview', agent: 'gemini' },
35
- ] as const;
36
-
37
- export interface DynamicModelBackendDescription {
38
- explicitPrefix: string;
39
- explicitPattern: string;
40
- discoveryCommand: string;
41
- modelsAreDynamic: boolean;
42
- }
43
-
44
- export function getSupportedModelsDescription(): string {
45
- return [
46
- '"claude-ultra", "codex-ultra", "gemini-ultra"',
47
- ...CLAUDE_MODELS.map((model) => `"${model}"`),
48
- ...CODEX_MODELS.map((model) => `"${model}"`),
49
- ...GEMINI_MODELS.map((model) => `"${model}"`),
50
- ...FORGE_MODELS.map((model) => `"${model}"`),
51
- ...OPENCODE_MODELS.map((model) => `"${model}"`),
52
- '"oc-<provider/model>"',
53
- ].join(', ');
54
- }
55
-
56
- export function getModelParameterDescription(): string {
57
- return `The model to use. Aliases: "claude-ultra" (auto high effort), "codex-ultra" (auto xhigh reasoning), "gemini-ultra". Standard: ${[...CLAUDE_MODELS, ...CODEX_MODELS, ...GEMINI_MODELS, ...FORGE_MODELS, ...OPENCODE_MODELS].map((model) => `"${model}"`).join(', ')}. OpenCode also accepts explicit dynamic models using "oc-<provider/model>". "forge" is a provider key, not a Forge model family selector.`;
58
- }
59
-
60
- export function getModelsPayload(): {
61
- aliases: ReadonlyArray<(typeof MODEL_ALIAS_DETAILS)[number]>;
62
- claude: ReadonlyArray<string>;
63
- codex: ReadonlyArray<string>;
64
- gemini: ReadonlyArray<string>;
65
- forge: ReadonlyArray<string>;
66
- opencode: ReadonlyArray<string>;
67
- dynamicModelBackends: {
68
- opencode: DynamicModelBackendDescription;
69
- };
70
- } {
71
- return {
72
- aliases: MODEL_ALIAS_DETAILS,
73
- claude: CLAUDE_MODELS,
74
- codex: CODEX_MODELS,
75
- gemini: GEMINI_MODELS,
76
- forge: FORGE_MODELS,
77
- opencode: OPENCODE_MODELS,
78
- dynamicModelBackends: {
79
- opencode: {
80
- explicitPrefix: 'oc-',
81
- explicitPattern: 'oc-<provider/model>',
82
- discoveryCommand: 'opencode models',
83
- modelsAreDynamic: true,
84
- },
85
- },
86
- };
87
- }