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.
- package/CHANGELOG.md +26 -0
- package/README.ja.md +34 -8
- package/README.md +41 -8
- package/dist/app/cli.js +1 -0
- package/dist/app/mcp.js +64 -12
- package/dist/cli-builder.js +13 -6
- package/dist/cli-process-service.js +76 -91
- package/dist/cli-utils.js +6 -0
- package/dist/cli.js +1 -1
- package/dist/model-catalog.js +3 -2
- package/dist/parsers.js +8 -2
- package/package.json +27 -3
- package/server.json +3 -3
- package/.gemini/settings.json +0 -11
- package/.github/dependabot.yml +0 -28
- package/.github/pull_request_template.md +0 -28
- package/.github/workflows/ci.yml +0 -34
- package/.github/workflows/dependency-review.yml +0 -22
- package/.github/workflows/publish.yml +0 -89
- package/.github/workflows/test.yml +0 -20
- package/.github/workflows/watch-session-prs.yml +0 -276
- package/.husky/pre-commit +0 -1
- package/.mcp.json +0 -11
- package/.releaserc.json +0 -18
- package/.vscode/settings.json +0 -3
- package/CONTRIBUTING.md +0 -81
- package/dist/__tests__/app-cli.test.js +0 -392
- package/dist/__tests__/cli-bin-smoke.test.js +0 -101
- package/dist/__tests__/cli-builder.test.js +0 -442
- package/dist/__tests__/cli-process-service.test.js +0 -655
- package/dist/__tests__/cli-utils.test.js +0 -171
- package/dist/__tests__/e2e.test.js +0 -256
- package/dist/__tests__/edge-cases.test.js +0 -130
- package/dist/__tests__/error-cases.test.js +0 -292
- package/dist/__tests__/mcp-contract.test.js +0 -636
- package/dist/__tests__/mocks.js +0 -32
- package/dist/__tests__/model-alias.test.js +0 -36
- package/dist/__tests__/parsers.test.js +0 -646
- package/dist/__tests__/peek.test.js +0 -36
- package/dist/__tests__/process-management.test.js +0 -949
- package/dist/__tests__/server.test.js +0 -809
- package/dist/__tests__/setup.js +0 -11
- package/dist/__tests__/utils/claude-mock.js +0 -80
- package/dist/__tests__/utils/mcp-client.js +0 -121
- package/dist/__tests__/utils/opencode-mock.js +0 -91
- package/dist/__tests__/utils/persistent-mock.js +0 -28
- package/dist/__tests__/utils/test-helpers.js +0 -11
- package/dist/__tests__/validation.test.js +0 -308
- package/dist/__tests__/version-print.test.js +0 -65
- package/dist/__tests__/wait.test.js +0 -260
- package/docs/RELEASE_CHECKLIST.md +0 -65
- package/docs/cli-architecture.md +0 -275
- package/docs/concept.md +0 -154
- package/docs/development.md +0 -156
- package/docs/e2e-testing.md +0 -148
- package/docs/prd.md +0 -146
- package/docs/session-stacking.md +0 -67
- package/src/__tests__/app-cli.test.ts +0 -495
- package/src/__tests__/cli-bin-smoke.test.ts +0 -136
- package/src/__tests__/cli-builder.test.ts +0 -549
- package/src/__tests__/cli-process-service.test.ts +0 -759
- package/src/__tests__/cli-utils.test.ts +0 -200
- package/src/__tests__/e2e.test.ts +0 -311
- package/src/__tests__/edge-cases.test.ts +0 -176
- package/src/__tests__/error-cases.test.ts +0 -370
- package/src/__tests__/mcp-contract.test.ts +0 -755
- package/src/__tests__/mocks.ts +0 -35
- package/src/__tests__/model-alias.test.ts +0 -44
- package/src/__tests__/parsers.test.ts +0 -730
- package/src/__tests__/peek.test.ts +0 -44
- package/src/__tests__/process-management.test.ts +0 -1129
- package/src/__tests__/server.test.ts +0 -1020
- package/src/__tests__/setup.ts +0 -13
- package/src/__tests__/utils/claude-mock.ts +0 -87
- package/src/__tests__/utils/mcp-client.ts +0 -159
- package/src/__tests__/utils/opencode-mock.ts +0 -108
- package/src/__tests__/utils/persistent-mock.ts +0 -33
- package/src/__tests__/utils/test-helpers.ts +0 -13
- package/src/__tests__/validation.test.ts +0 -369
- package/src/__tests__/version-print.test.ts +0 -81
- package/src/__tests__/wait.test.ts +0 -302
- package/src/app/cli.ts +0 -424
- package/src/app/mcp.ts +0 -466
- package/src/bin/ai-cli-mcp.ts +0 -7
- package/src/bin/ai-cli.ts +0 -11
- package/src/cli-builder.ts +0 -274
- package/src/cli-parse.ts +0 -105
- package/src/cli-process-service.ts +0 -709
- package/src/cli-utils.ts +0 -258
- package/src/cli.ts +0 -124
- package/src/model-catalog.ts +0 -87
- package/src/parsers.ts +0 -965
- package/src/peek.ts +0 -95
- package/src/process-result.ts +0 -88
- package/src/process-service.ts +0 -368
- package/src/server.ts +0 -10
- package/tsconfig.json +0 -16
- package/vitest.config.e2e.ts +0 -27
- package/vitest.config.ts +0 -22
- package/vitest.config.unit.ts +0 -28
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
|
-
import { EventEmitter } from 'node:events';
|
|
6
|
-
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
|
-
|
|
9
|
-
// Mock dependencies
|
|
10
|
-
vi.mock('node:child_process');
|
|
11
|
-
vi.mock('node:fs');
|
|
12
|
-
vi.mock('node:os');
|
|
13
|
-
vi.mock('node:path', () => ({
|
|
14
|
-
resolve: vi.fn((path) => path),
|
|
15
|
-
join: vi.fn((...args) => args.join('/')),
|
|
16
|
-
isAbsolute: vi.fn((path) => path.startsWith('/'))
|
|
17
|
-
}));
|
|
18
|
-
vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
|
|
19
|
-
Server: vi.fn().mockImplementation(function(this: any) {
|
|
20
|
-
this.setRequestHandler = vi.fn();
|
|
21
|
-
this.connect = vi.fn();
|
|
22
|
-
this.close = vi.fn();
|
|
23
|
-
this.onerror = undefined;
|
|
24
|
-
return this;
|
|
25
|
-
}),
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
vi.mock('@modelcontextprotocol/sdk/types.js', () => ({
|
|
29
|
-
ListToolsRequestSchema: { name: 'listTools' },
|
|
30
|
-
CallToolRequestSchema: { name: 'callTool' },
|
|
31
|
-
ErrorCode: {
|
|
32
|
-
InternalError: 'InternalError',
|
|
33
|
-
MethodNotFound: 'MethodNotFound',
|
|
34
|
-
InvalidParams: 'InvalidParams'
|
|
35
|
-
},
|
|
36
|
-
McpError: class extends Error {
|
|
37
|
-
code: any;
|
|
38
|
-
constructor(code: any, message: string) {
|
|
39
|
-
super(message);
|
|
40
|
-
this.code = code;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
|
-
const mockExistsSync = vi.mocked(existsSync);
|
|
46
|
-
const mockSpawn = vi.mocked(spawn);
|
|
47
|
-
const mockHomedir = vi.mocked(homedir);
|
|
48
|
-
|
|
49
|
-
describe('Error Handling Tests', () => {
|
|
50
|
-
let consoleErrorSpy: any;
|
|
51
|
-
let originalEnv: any;
|
|
52
|
-
let errorHandler: any = null;
|
|
53
|
-
|
|
54
|
-
function setupServerMock() {
|
|
55
|
-
errorHandler = null;
|
|
56
|
-
vi.mocked(Server).mockImplementation(function(this: any) {
|
|
57
|
-
this.setRequestHandler = vi.fn();
|
|
58
|
-
this.connect = vi.fn();
|
|
59
|
-
this.close = vi.fn();
|
|
60
|
-
Object.defineProperty(this, 'onerror', {
|
|
61
|
-
get() { return errorHandler; },
|
|
62
|
-
set(handler) { errorHandler = handler; },
|
|
63
|
-
enumerable: true,
|
|
64
|
-
configurable: true
|
|
65
|
-
});
|
|
66
|
-
return this;
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
beforeEach(() => {
|
|
71
|
-
vi.clearAllMocks();
|
|
72
|
-
vi.resetModules();
|
|
73
|
-
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
74
|
-
originalEnv = { ...process.env };
|
|
75
|
-
process.env = { ...originalEnv };
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
afterEach(() => {
|
|
79
|
-
consoleErrorSpy.mockRestore();
|
|
80
|
-
process.env = originalEnv;
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('CallToolRequest Error Cases', () => {
|
|
84
|
-
it('should throw error for unknown tool name', async () => {
|
|
85
|
-
mockHomedir.mockReturnValue('/home/user');
|
|
86
|
-
mockExistsSync.mockReturnValue(true);
|
|
87
|
-
|
|
88
|
-
// Set up Server mock before importing the module
|
|
89
|
-
setupServerMock();
|
|
90
|
-
|
|
91
|
-
const module = await import('../server.js');
|
|
92
|
-
// @ts-ignore
|
|
93
|
-
const { ClaudeCodeServer } = module;
|
|
94
|
-
|
|
95
|
-
const server = new ClaudeCodeServer();
|
|
96
|
-
const mockServerInstance = vi.mocked(Server).mock.results[0].value;
|
|
97
|
-
|
|
98
|
-
const callToolCall = mockServerInstance.setRequestHandler.mock.calls.find(
|
|
99
|
-
(call: any[]) => call[0].name === 'callTool'
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
const handler = callToolCall[1];
|
|
103
|
-
|
|
104
|
-
await expect(
|
|
105
|
-
handler({
|
|
106
|
-
params: {
|
|
107
|
-
name: 'unknown_tool',
|
|
108
|
-
arguments: {}
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
).rejects.toThrow('Tool unknown_tool not found');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should handle timeout errors', async () => {
|
|
115
|
-
mockHomedir.mockReturnValue('/home/user');
|
|
116
|
-
mockExistsSync.mockReturnValue(true);
|
|
117
|
-
setupServerMock();
|
|
118
|
-
|
|
119
|
-
const module = await import('../server.js');
|
|
120
|
-
// @ts-ignore
|
|
121
|
-
const { ClaudeCodeServer } = module;
|
|
122
|
-
const { McpError } = await import('@modelcontextprotocol/sdk/types.js');
|
|
123
|
-
|
|
124
|
-
const server = new ClaudeCodeServer();
|
|
125
|
-
const mockServerInstance = vi.mocked(Server).mock.results[0].value;
|
|
126
|
-
|
|
127
|
-
// Find the callTool handler
|
|
128
|
-
let callToolHandler: any;
|
|
129
|
-
for (const call of mockServerInstance.setRequestHandler.mock.calls) {
|
|
130
|
-
if (call[0].name === 'callTool') {
|
|
131
|
-
callToolHandler = call[1];
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Mock spawn to return process without PID
|
|
137
|
-
mockSpawn.mockImplementation(() => {
|
|
138
|
-
const mockProcess = new EventEmitter() as any;
|
|
139
|
-
mockProcess.stdout = new EventEmitter();
|
|
140
|
-
mockProcess.stderr = new EventEmitter();
|
|
141
|
-
|
|
142
|
-
mockProcess.stdout.on = vi.fn();
|
|
143
|
-
mockProcess.stderr.on = vi.fn();
|
|
144
|
-
mockProcess.pid = undefined; // No PID to simulate process start failure
|
|
145
|
-
|
|
146
|
-
return mockProcess;
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Call handler
|
|
150
|
-
await expect(callToolHandler({
|
|
151
|
-
params: {
|
|
152
|
-
name: 'run',
|
|
153
|
-
arguments: {
|
|
154
|
-
prompt: 'test',
|
|
155
|
-
workFolder: '/tmp'
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
})).rejects.toThrow('Failed to start claude CLI process');
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should handle invalid argument types', async () => {
|
|
162
|
-
mockHomedir.mockReturnValue('/home/user');
|
|
163
|
-
mockExistsSync.mockReturnValue(true);
|
|
164
|
-
setupServerMock();
|
|
165
|
-
const module = await import('../server.js');
|
|
166
|
-
// @ts-ignore
|
|
167
|
-
const { ClaudeCodeServer } = module;
|
|
168
|
-
|
|
169
|
-
const server = new ClaudeCodeServer();
|
|
170
|
-
const mockServerInstance = vi.mocked(Server).mock.results[0].value;
|
|
171
|
-
|
|
172
|
-
const callToolCall = mockServerInstance.setRequestHandler.mock.calls.find(
|
|
173
|
-
(call: any[]) => call[0].name === 'callTool'
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const handler = callToolCall[1];
|
|
177
|
-
|
|
178
|
-
await expect(
|
|
179
|
-
handler({
|
|
180
|
-
params: {
|
|
181
|
-
name: 'run',
|
|
182
|
-
arguments: 'invalid-should-be-object'
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
).rejects.toThrow();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should include CLI error details in error message', async () => {
|
|
189
|
-
mockHomedir.mockReturnValue('/home/user');
|
|
190
|
-
mockExistsSync.mockReturnValue(true);
|
|
191
|
-
setupServerMock();
|
|
192
|
-
|
|
193
|
-
const module = await import('../server.js');
|
|
194
|
-
// @ts-ignore
|
|
195
|
-
const { ClaudeCodeServer } = module;
|
|
196
|
-
|
|
197
|
-
const server = new ClaudeCodeServer();
|
|
198
|
-
const mockServerInstance = vi.mocked(Server).mock.results[0].value;
|
|
199
|
-
|
|
200
|
-
const callToolCall = mockServerInstance.setRequestHandler.mock.calls.find(
|
|
201
|
-
(call: any[]) => call[0].name === 'callTool'
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
const handler = callToolCall[1];
|
|
205
|
-
|
|
206
|
-
// Create a simple mock process
|
|
207
|
-
mockSpawn.mockImplementation(() => {
|
|
208
|
-
const mockProcess = Object.create(EventEmitter.prototype);
|
|
209
|
-
EventEmitter.call(mockProcess);
|
|
210
|
-
mockProcess.stdout = Object.create(EventEmitter.prototype);
|
|
211
|
-
EventEmitter.call(mockProcess.stdout);
|
|
212
|
-
mockProcess.stderr = Object.create(EventEmitter.prototype);
|
|
213
|
-
EventEmitter.call(mockProcess.stderr);
|
|
214
|
-
|
|
215
|
-
mockProcess.stdout.on = vi.fn((event, callback) => {
|
|
216
|
-
if (event === 'data') {
|
|
217
|
-
// Send some stdout data
|
|
218
|
-
process.nextTick(() => callback('stdout content'));
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
mockProcess.stderr.on = vi.fn((event, callback) => {
|
|
223
|
-
if (event === 'data') {
|
|
224
|
-
// Send some stderr data
|
|
225
|
-
process.nextTick(() => callback('stderr content'));
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Emit error/close event after data is sent
|
|
230
|
-
setTimeout(() => {
|
|
231
|
-
mockProcess.emit('close', 1);
|
|
232
|
-
}, 1);
|
|
233
|
-
|
|
234
|
-
return mockProcess;
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
await expect(
|
|
238
|
-
handler({
|
|
239
|
-
params: {
|
|
240
|
-
name: 'run',
|
|
241
|
-
arguments: {
|
|
242
|
-
prompt: 'test',
|
|
243
|
-
workFolder: '/tmp'
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
).rejects.toThrow();
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe('Process Spawn Error Cases', () => {
|
|
252
|
-
it('should handle spawn ENOENT error', async () => {
|
|
253
|
-
const module = await import('../server.js');
|
|
254
|
-
// @ts-ignore
|
|
255
|
-
const { spawnAsync } = module;
|
|
256
|
-
|
|
257
|
-
const mockProcess = new EventEmitter() as any;
|
|
258
|
-
mockProcess.stdout = new EventEmitter();
|
|
259
|
-
mockProcess.stderr = new EventEmitter();
|
|
260
|
-
mockProcess.stdout.on = vi.fn();
|
|
261
|
-
mockProcess.stderr.on = vi.fn();
|
|
262
|
-
|
|
263
|
-
mockSpawn.mockReturnValue(mockProcess);
|
|
264
|
-
|
|
265
|
-
const promise = spawnAsync('nonexistent-command', []);
|
|
266
|
-
|
|
267
|
-
// Simulate ENOENT error
|
|
268
|
-
setTimeout(() => {
|
|
269
|
-
const error: any = new Error('spawn ENOENT');
|
|
270
|
-
error.code = 'ENOENT';
|
|
271
|
-
error.path = 'nonexistent-command';
|
|
272
|
-
error.syscall = 'spawn';
|
|
273
|
-
mockProcess.emit('error', error);
|
|
274
|
-
}, 10);
|
|
275
|
-
|
|
276
|
-
await expect(promise).rejects.toThrow('Spawn error');
|
|
277
|
-
await expect(promise).rejects.toThrow('nonexistent-command');
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it('should handle generic spawn errors', async () => {
|
|
281
|
-
const module = await import('../server.js');
|
|
282
|
-
// @ts-ignore
|
|
283
|
-
const { spawnAsync } = module;
|
|
284
|
-
|
|
285
|
-
const mockProcess = new EventEmitter() as any;
|
|
286
|
-
mockProcess.stdout = new EventEmitter();
|
|
287
|
-
mockProcess.stderr = new EventEmitter();
|
|
288
|
-
mockProcess.stdout.on = vi.fn();
|
|
289
|
-
mockProcess.stderr.on = vi.fn();
|
|
290
|
-
|
|
291
|
-
mockSpawn.mockReturnValue(mockProcess);
|
|
292
|
-
|
|
293
|
-
const promise = spawnAsync('test', []);
|
|
294
|
-
|
|
295
|
-
// Simulate generic error
|
|
296
|
-
setTimeout(() => {
|
|
297
|
-
mockProcess.emit('error', new Error('Generic spawn error'));
|
|
298
|
-
}, 10);
|
|
299
|
-
|
|
300
|
-
await expect(promise).rejects.toThrow('Generic spawn error');
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('should accumulate stderr output before error', async () => {
|
|
304
|
-
const module = await import('../server.js');
|
|
305
|
-
// @ts-ignore
|
|
306
|
-
const { spawnAsync } = module;
|
|
307
|
-
|
|
308
|
-
const mockProcess = new EventEmitter() as any;
|
|
309
|
-
mockProcess.stdout = new EventEmitter();
|
|
310
|
-
mockProcess.stderr = new EventEmitter();
|
|
311
|
-
let stderrHandler: any;
|
|
312
|
-
|
|
313
|
-
mockProcess.stdout.on = vi.fn();
|
|
314
|
-
mockProcess.stderr.on = vi.fn((event, handler) => {
|
|
315
|
-
if (event === 'data') stderrHandler = handler;
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
mockSpawn.mockReturnValue(mockProcess);
|
|
319
|
-
|
|
320
|
-
const promise = spawnAsync('test', []);
|
|
321
|
-
|
|
322
|
-
// Simulate stderr data then error
|
|
323
|
-
setTimeout(() => {
|
|
324
|
-
stderrHandler('error line 1\n');
|
|
325
|
-
stderrHandler('error line 2\n');
|
|
326
|
-
mockProcess.emit('error', new Error('Command failed'));
|
|
327
|
-
}, 10);
|
|
328
|
-
|
|
329
|
-
await expect(promise).rejects.toThrow('error line 1\nerror line 2');
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
describe('Server Initialization Errors', () => {
|
|
334
|
-
it('should handle CLI path not found gracefully', async () => {
|
|
335
|
-
// Mock no CLI found anywhere
|
|
336
|
-
mockHomedir.mockReturnValue('/home/user');
|
|
337
|
-
mockExistsSync.mockReturnValue(false);
|
|
338
|
-
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
339
|
-
setupServerMock();
|
|
340
|
-
|
|
341
|
-
const module = await import('../server.js');
|
|
342
|
-
// @ts-ignore
|
|
343
|
-
const { ClaudeCodeServer } = module;
|
|
344
|
-
|
|
345
|
-
const server = new ClaudeCodeServer();
|
|
346
|
-
|
|
347
|
-
expect(server).toBeDefined();
|
|
348
|
-
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
349
|
-
|
|
350
|
-
consoleWarnSpy.mockRestore();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it('should handle server connection errors', async () => {
|
|
354
|
-
mockHomedir.mockReturnValue('/home/user');
|
|
355
|
-
mockExistsSync.mockReturnValue(true);
|
|
356
|
-
setupServerMock();
|
|
357
|
-
const module = await import('../server.js');
|
|
358
|
-
// @ts-ignore
|
|
359
|
-
const { ClaudeCodeServer } = module;
|
|
360
|
-
|
|
361
|
-
const server = new ClaudeCodeServer();
|
|
362
|
-
|
|
363
|
-
// Mock connection failure
|
|
364
|
-
const mockServerInstance = vi.mocked(Server).mock.results[0].value;
|
|
365
|
-
mockServerInstance.connect.mockRejectedValue(new Error('Connection failed'));
|
|
366
|
-
|
|
367
|
-
await expect(server.run()).rejects.toThrow('Connection failed');
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
});
|