ai-cli-mcp 2.14.1 → 2.16.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/dependabot.yml +28 -0
- package/.github/workflows/ci.yml +4 -1
- package/.github/workflows/dependency-review.yml +22 -0
- package/CHANGELOG.md +14 -0
- package/README.ja.md +83 -6
- package/README.md +83 -7
- package/dist/__tests__/app-cli.test.js +80 -5
- package/dist/__tests__/cli-bin-smoke.test.js +43 -0
- package/dist/__tests__/cli-builder.test.js +93 -15
- package/dist/__tests__/cli-process-service.test.js +162 -0
- package/dist/__tests__/cli-utils.test.js +31 -0
- package/dist/__tests__/e2e.test.js +79 -52
- package/dist/__tests__/mcp-contract.test.js +162 -0
- package/dist/__tests__/parsers.test.js +224 -1
- package/dist/__tests__/peek.test.js +35 -0
- package/dist/__tests__/process-management.test.js +160 -1
- package/dist/__tests__/server.test.js +39 -9
- package/dist/__tests__/utils/opencode-mock.js +91 -0
- package/dist/__tests__/validation.test.js +40 -2
- package/dist/app/cli.js +47 -5
- package/dist/app/mcp.js +53 -4
- package/dist/cli-builder.js +67 -28
- package/dist/cli-parse.js +11 -5
- package/dist/cli-process-service.js +241 -20
- package/dist/cli-utils.js +14 -23
- package/dist/cli.js +6 -4
- package/dist/model-catalog.js +13 -1
- package/dist/parsers.js +242 -28
- package/dist/peek.js +56 -0
- package/dist/process-result.js +9 -2
- package/dist/process-service.js +103 -17
- package/dist/server.js +1 -2
- package/package.json +9 -6
- package/src/__tests__/app-cli.test.ts +95 -4
- package/src/__tests__/cli-bin-smoke.test.ts +62 -1
- package/src/__tests__/cli-builder.test.ts +111 -15
- package/src/__tests__/cli-process-service.test.ts +180 -0
- package/src/__tests__/cli-utils.test.ts +34 -0
- package/src/__tests__/e2e.test.ts +87 -55
- package/src/__tests__/mcp-contract.test.ts +188 -0
- package/src/__tests__/parsers.test.ts +260 -1
- package/src/__tests__/peek.test.ts +43 -0
- package/src/__tests__/process-management.test.ts +185 -1
- package/src/__tests__/server.test.ts +49 -13
- package/src/__tests__/utils/opencode-mock.ts +108 -0
- package/src/__tests__/validation.test.ts +48 -2
- package/src/app/cli.ts +52 -4
- package/src/app/mcp.ts +54 -4
- package/src/cli-builder.ts +91 -32
- package/src/cli-parse.ts +11 -5
- package/src/cli-process-service.ts +304 -17
- package/src/cli-utils.ts +37 -33
- package/src/cli.ts +6 -4
- package/src/model-catalog.ts +24 -1
- package/src/parsers.ts +299 -33
- package/src/peek.ts +88 -0
- package/src/process-result.ts +11 -2
- package/src/process-service.ts +134 -15
- package/src/server.ts +2 -2
- package/vitest.config.unit.ts +2 -3
package/src/process-service.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
2
|
import { buildCliCommand, type BuildCliCommandOptions } from './cli-builder.js';
|
|
3
|
-
import { parseClaudeOutput, parseCodexOutput, parseForgeOutput, parseGeminiOutput } from './parsers.js';
|
|
3
|
+
import { parseClaudeOutput, parseCodexOutput, parseForgeOutput, parseGeminiOutput, parseOpenCodeOutput, PeekMessageExtractor } from './parsers.js';
|
|
4
|
+
import {
|
|
5
|
+
appendPeekMessages,
|
|
6
|
+
buildNotFoundPeekProcess,
|
|
7
|
+
observedDurationSec,
|
|
8
|
+
validatePeekPids,
|
|
9
|
+
validatePeekTimeSec,
|
|
10
|
+
type PeekProcessResult,
|
|
11
|
+
type PeekResponse,
|
|
12
|
+
} from './peek.js';
|
|
4
13
|
import { buildProcessResult } from './process-result.js';
|
|
5
14
|
|
|
6
|
-
export type AgentType = 'claude' | 'codex' | 'gemini' | 'forge';
|
|
15
|
+
export type AgentType = 'claude' | 'codex' | 'gemini' | 'forge' | 'opencode';
|
|
7
16
|
export type ProcessStatus = 'running' | 'completed' | 'failed';
|
|
8
17
|
|
|
9
18
|
interface TrackedProcess {
|
|
@@ -37,6 +46,31 @@ interface ProcessServiceOptions {
|
|
|
37
46
|
cliPaths: BuildCliCommandOptions['cliPaths'];
|
|
38
47
|
}
|
|
39
48
|
|
|
49
|
+
function parseAgentOutput(agent: AgentType, stdout: string, stderr: string): any {
|
|
50
|
+
if (agent === 'codex') {
|
|
51
|
+
return parseCodexOutput(`${stdout || ''}\n${stderr || ''}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!stdout) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (agent === 'claude') {
|
|
59
|
+
return parseClaudeOutput(stdout);
|
|
60
|
+
}
|
|
61
|
+
if (agent === 'gemini') {
|
|
62
|
+
return parseGeminiOutput(stdout);
|
|
63
|
+
}
|
|
64
|
+
if (agent === 'forge') {
|
|
65
|
+
return parseForgeOutput(stdout);
|
|
66
|
+
}
|
|
67
|
+
if (agent === 'opencode') {
|
|
68
|
+
return parseOpenCodeOutput(stdout);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
40
74
|
export class ProcessService {
|
|
41
75
|
private readonly processManager = new Map<number, TrackedProcess>();
|
|
42
76
|
private readonly cliPaths: BuildCliCommandOptions['cliPaths'];
|
|
@@ -136,19 +170,7 @@ export class ProcessService {
|
|
|
136
170
|
throw new Error(`Process with PID ${pid} not found`);
|
|
137
171
|
}
|
|
138
172
|
|
|
139
|
-
|
|
140
|
-
if (process.toolType === 'codex') {
|
|
141
|
-
const combinedOutput = (process.stdout || '') + '\n' + (process.stderr || '');
|
|
142
|
-
agentOutput = parseCodexOutput(combinedOutput);
|
|
143
|
-
} else if (process.stdout) {
|
|
144
|
-
if (process.toolType === 'claude') {
|
|
145
|
-
agentOutput = parseClaudeOutput(process.stdout);
|
|
146
|
-
} else if (process.toolType === 'gemini') {
|
|
147
|
-
agentOutput = parseGeminiOutput(process.stdout);
|
|
148
|
-
} else if (process.toolType === 'forge') {
|
|
149
|
-
agentOutput = parseForgeOutput(process.stdout);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
173
|
+
const agentOutput = parseAgentOutput(process.toolType, process.stdout, process.stderr);
|
|
152
174
|
|
|
153
175
|
return buildProcessResult({
|
|
154
176
|
pid,
|
|
@@ -204,6 +226,103 @@ export class ProcessService {
|
|
|
204
226
|
}
|
|
205
227
|
}
|
|
206
228
|
|
|
229
|
+
async peekProcesses(pids: number[], peekTimeSec = 10): Promise<PeekResponse> {
|
|
230
|
+
const targetPids = validatePeekPids(pids);
|
|
231
|
+
const targetPeekTimeSec = validatePeekTimeSec(peekTimeSec);
|
|
232
|
+
const processes: PeekProcessResult[] = [];
|
|
233
|
+
const observers: Array<{
|
|
234
|
+
entry: TrackedProcess;
|
|
235
|
+
result: PeekProcessResult;
|
|
236
|
+
stdoutExtractor: PeekMessageExtractor;
|
|
237
|
+
stderrExtractor: PeekMessageExtractor;
|
|
238
|
+
onStdout: (data: Buffer | string) => void;
|
|
239
|
+
onStderr: (data: Buffer | string) => void;
|
|
240
|
+
}> = [];
|
|
241
|
+
|
|
242
|
+
for (const pid of targetPids) {
|
|
243
|
+
const entry = this.processManager.get(pid);
|
|
244
|
+
if (!entry) {
|
|
245
|
+
processes.push(buildNotFoundPeekProcess(pid));
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const result: PeekProcessResult = {
|
|
250
|
+
pid,
|
|
251
|
+
agent: entry.toolType,
|
|
252
|
+
status: entry.status,
|
|
253
|
+
messages: [],
|
|
254
|
+
truncated: false,
|
|
255
|
+
error: null,
|
|
256
|
+
};
|
|
257
|
+
processes.push(result);
|
|
258
|
+
|
|
259
|
+
const stdoutExtractor = new PeekMessageExtractor(entry.toolType);
|
|
260
|
+
const stderrExtractor = new PeekMessageExtractor(entry.toolType);
|
|
261
|
+
const onStdout = (data: Buffer | string) => {
|
|
262
|
+
appendPeekMessages(result, stdoutExtractor.push(data.toString(), new Date().toISOString()));
|
|
263
|
+
};
|
|
264
|
+
const onStderr = (data: Buffer | string) => {
|
|
265
|
+
appendPeekMessages(result, stderrExtractor.push(data.toString(), new Date().toISOString()));
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
if (entry.status === 'running') {
|
|
269
|
+
entry.process.stdout?.on('data', onStdout);
|
|
270
|
+
entry.process.stderr?.on('data', onStderr);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
observers.push({ entry, result, stdoutExtractor, stderrExtractor, onStdout, onStderr });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const startedAt = new Date();
|
|
277
|
+
const startedAtMs = Date.now();
|
|
278
|
+
const runningObservers = observers.filter((observer) => observer.entry.status === 'running');
|
|
279
|
+
const terminalPromise = Promise.all(runningObservers.map((observer) => this.waitForProcessTerminal(observer.entry)));
|
|
280
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
281
|
+
const timeoutPromise = new Promise<void>((resolve) => {
|
|
282
|
+
timeoutHandle = setTimeout(resolve, targetPeekTimeSec * 1000);
|
|
283
|
+
timeoutHandle.unref?.();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
await Promise.race([terminalPromise, timeoutPromise]);
|
|
288
|
+
} finally {
|
|
289
|
+
if (timeoutHandle) {
|
|
290
|
+
clearTimeout(timeoutHandle);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const flushTs = new Date().toISOString();
|
|
294
|
+
for (const observer of observers) {
|
|
295
|
+
observer.entry.process.stdout?.off('data', observer.onStdout);
|
|
296
|
+
observer.entry.process.stderr?.off('data', observer.onStderr);
|
|
297
|
+
appendPeekMessages(observer.result, observer.stdoutExtractor.flush(flushTs));
|
|
298
|
+
appendPeekMessages(observer.result, observer.stderrExtractor.flush(flushTs));
|
|
299
|
+
observer.result.status = observer.entry.status;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
peek_started_at: startedAt.toISOString(),
|
|
305
|
+
observed_duration_sec: observedDurationSec(startedAtMs),
|
|
306
|
+
processes,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private waitForProcessTerminal(processEntry: TrackedProcess): Promise<void> {
|
|
311
|
+
if (processEntry.status !== 'running') {
|
|
312
|
+
return Promise.resolve();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return new Promise<void>((resolve) => {
|
|
316
|
+
const done = () => {
|
|
317
|
+
processEntry.process.off('close', done);
|
|
318
|
+
processEntry.process.off('error', done);
|
|
319
|
+
resolve();
|
|
320
|
+
};
|
|
321
|
+
processEntry.process.once('close', done);
|
|
322
|
+
processEntry.process.once('error', done);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
207
326
|
killProcess(pid: number): { pid: number; status: string; message: string } {
|
|
208
327
|
const processEntry = this.processManager.get(pid);
|
|
209
328
|
if (!processEntry) {
|
package/src/server.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export { debugLog, findClaudeCli, findCodexCli, findForgeCli, findGeminiCli } from './cli-utils.js';
|
|
1
|
+
import { debugLog, findClaudeCli, findCodexCli, findForgeCli, findGeminiCli, findOpencodeCli } from './cli-utils.js';
|
|
2
|
+
export { debugLog, findClaudeCli, findCodexCli, findForgeCli, findGeminiCli, findOpencodeCli } from './cli-utils.js';
|
|
3
3
|
export { resolveModelAlias } from './cli-builder.js';
|
|
4
4
|
export { ClaudeCodeServer, runMcpServer, spawnAsync } from './app/mcp.js';
|
|
5
5
|
|
package/vitest.config.unit.ts
CHANGED
|
@@ -17,13 +17,12 @@ export default defineConfig({
|
|
|
17
17
|
},
|
|
18
18
|
exclude: [
|
|
19
19
|
'node_modules/**',
|
|
20
|
+
'dist/**',
|
|
20
21
|
'src/__tests__/e2e.test.ts',
|
|
21
22
|
'src/__tests__/edge-cases.test.ts',
|
|
22
|
-
'dist/__tests__/e2e.test.js',
|
|
23
|
-
'dist/__tests__/edge-cases.test.js',
|
|
24
23
|
],
|
|
25
24
|
mockReset: true,
|
|
26
25
|
clearMocks: true,
|
|
27
26
|
restoreMocks: true,
|
|
28
27
|
},
|
|
29
|
-
});
|
|
28
|
+
});
|