ai-cli-mcp 2.18.0 → 2.20.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.ja.md +37 -11
  3. package/README.md +44 -11
  4. package/dist/app/cli.js +2 -1
  5. package/dist/app/mcp.js +65 -13
  6. package/dist/cli-builder.js +13 -6
  7. package/dist/cli-process-service.js +81 -95
  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 +111 -8
  12. package/dist/process-service.js +5 -4
  13. package/package.json +26 -2
  14. package/server.json +3 -3
  15. package/.gemini/settings.json +0 -11
  16. package/.github/dependabot.yml +0 -28
  17. package/.github/pull_request_template.md +0 -28
  18. package/.github/workflows/ci.yml +0 -34
  19. package/.github/workflows/dependency-review.yml +0 -22
  20. package/.github/workflows/publish.yml +0 -89
  21. package/.github/workflows/test.yml +0 -20
  22. package/.github/workflows/watch-session-prs.yml +0 -276
  23. package/.husky/pre-commit +0 -1
  24. package/.mcp.json +0 -11
  25. package/.releaserc.json +0 -18
  26. package/.vscode/settings.json +0 -3
  27. package/CONTRIBUTING.md +0 -81
  28. package/dist/__tests__/app-cli.test.js +0 -392
  29. package/dist/__tests__/cli-bin-smoke.test.js +0 -101
  30. package/dist/__tests__/cli-builder.test.js +0 -442
  31. package/dist/__tests__/cli-process-service.test.js +0 -655
  32. package/dist/__tests__/cli-utils.test.js +0 -171
  33. package/dist/__tests__/e2e.test.js +0 -256
  34. package/dist/__tests__/edge-cases.test.js +0 -130
  35. package/dist/__tests__/error-cases.test.js +0 -292
  36. package/dist/__tests__/mcp-contract.test.js +0 -636
  37. package/dist/__tests__/mocks.js +0 -32
  38. package/dist/__tests__/model-alias.test.js +0 -36
  39. package/dist/__tests__/parsers.test.js +0 -500
  40. package/dist/__tests__/peek.test.js +0 -36
  41. package/dist/__tests__/process-management.test.js +0 -871
  42. package/dist/__tests__/server.test.js +0 -809
  43. package/dist/__tests__/setup.js +0 -11
  44. package/dist/__tests__/utils/claude-mock.js +0 -80
  45. package/dist/__tests__/utils/mcp-client.js +0 -121
  46. package/dist/__tests__/utils/opencode-mock.js +0 -91
  47. package/dist/__tests__/utils/persistent-mock.js +0 -28
  48. package/dist/__tests__/utils/test-helpers.js +0 -11
  49. package/dist/__tests__/validation.test.js +0 -308
  50. package/dist/__tests__/version-print.test.js +0 -65
  51. package/dist/__tests__/wait.test.js +0 -260
  52. package/docs/RELEASE_CHECKLIST.md +0 -65
  53. package/docs/cli-architecture.md +0 -275
  54. package/docs/concept.md +0 -154
  55. package/docs/development.md +0 -156
  56. package/docs/e2e-testing.md +0 -148
  57. package/docs/prd.md +0 -146
  58. package/docs/session-stacking.md +0 -67
  59. package/src/__tests__/app-cli.test.ts +0 -495
  60. package/src/__tests__/cli-bin-smoke.test.ts +0 -136
  61. package/src/__tests__/cli-builder.test.ts +0 -549
  62. package/src/__tests__/cli-process-service.test.ts +0 -759
  63. package/src/__tests__/cli-utils.test.ts +0 -200
  64. package/src/__tests__/e2e.test.ts +0 -311
  65. package/src/__tests__/edge-cases.test.ts +0 -176
  66. package/src/__tests__/error-cases.test.ts +0 -370
  67. package/src/__tests__/mcp-contract.test.ts +0 -755
  68. package/src/__tests__/mocks.ts +0 -35
  69. package/src/__tests__/model-alias.test.ts +0 -44
  70. package/src/__tests__/parsers.test.ts +0 -564
  71. package/src/__tests__/peek.test.ts +0 -44
  72. package/src/__tests__/process-management.test.ts +0 -1043
  73. package/src/__tests__/server.test.ts +0 -1020
  74. package/src/__tests__/setup.ts +0 -13
  75. package/src/__tests__/utils/claude-mock.ts +0 -87
  76. package/src/__tests__/utils/mcp-client.ts +0 -159
  77. package/src/__tests__/utils/opencode-mock.ts +0 -108
  78. package/src/__tests__/utils/persistent-mock.ts +0 -33
  79. package/src/__tests__/utils/test-helpers.ts +0 -13
  80. package/src/__tests__/validation.test.ts +0 -369
  81. package/src/__tests__/version-print.test.ts +0 -81
  82. package/src/__tests__/wait.test.ts +0 -302
  83. package/src/app/cli.ts +0 -424
  84. package/src/app/mcp.ts +0 -466
  85. package/src/bin/ai-cli-mcp.ts +0 -7
  86. package/src/bin/ai-cli.ts +0 -11
  87. package/src/cli-builder.ts +0 -274
  88. package/src/cli-parse.ts +0 -105
  89. package/src/cli-process-service.ts +0 -708
  90. package/src/cli-utils.ts +0 -258
  91. package/src/cli.ts +0 -124
  92. package/src/model-catalog.ts +0 -87
  93. package/src/parsers.ts +0 -840
  94. package/src/peek.ts +0 -95
  95. package/src/process-result.ts +0 -88
  96. package/src/process-service.ts +0 -367
  97. package/src/server.ts +0 -10
  98. package/tsconfig.json +0 -16
  99. package/vitest.config.e2e.ts +0 -27
  100. package/vitest.config.ts +0 -22
  101. 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
- }