ai-cli-mcp 2.6.0 → 2.7.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.
@@ -0,0 +1,190 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve as pathResolve, isAbsolute } from 'node:path';
3
+
4
+ // Model alias mappings for user-friendly model names
5
+ export const MODEL_ALIASES: Record<string, string> = {
6
+ 'claude-ultra': 'opus',
7
+ 'codex-ultra': 'gpt-5.3-codex',
8
+ 'gemini-ultra': 'gemini-3-pro-preview'
9
+ };
10
+
11
+ export const ALLOWED_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
12
+
13
+ /**
14
+ * Resolves model aliases to their full model names
15
+ * @param model - The model name or alias to resolve
16
+ * @returns The full model name, or the original value if no alias exists
17
+ */
18
+ export function resolveModelAlias(model: string): string {
19
+ return MODEL_ALIASES[model] || model;
20
+ }
21
+
22
+ /**
23
+ * Validates and normalizes reasoning effort parameter.
24
+ * @returns normalized reasoning effort string, or '' if not applicable
25
+ * @throws Error for invalid values (plain Error, not MCP-specific)
26
+ */
27
+ export function getReasoningEffort(model: string, rawValue: unknown): string {
28
+ if (typeof rawValue !== 'string') {
29
+ return '';
30
+ }
31
+ const trimmed = rawValue.trim();
32
+ if (!trimmed) {
33
+ return '';
34
+ }
35
+ const normalized = trimmed.toLowerCase();
36
+ if (!ALLOWED_REASONING_EFFORTS.has(normalized)) {
37
+ throw new Error(
38
+ `Invalid reasoning_effort: ${rawValue}. Allowed values: low, medium, high, xhigh.`
39
+ );
40
+ }
41
+ if (!model.startsWith('gpt-')) {
42
+ throw new Error(
43
+ 'reasoning_effort is only supported for Codex models (gpt-*).'
44
+ );
45
+ }
46
+ return normalized;
47
+ }
48
+
49
+ export interface CliCommand {
50
+ cliPath: string;
51
+ args: string[];
52
+ cwd: string;
53
+ agent: 'claude' | 'codex' | 'gemini';
54
+ prompt: string;
55
+ resolvedModel: string;
56
+ }
57
+
58
+ export interface BuildCliCommandOptions {
59
+ prompt?: string;
60
+ prompt_file?: string;
61
+ workFolder: string;
62
+ model?: string;
63
+ session_id?: string;
64
+ reasoning_effort?: string;
65
+ cliPaths: { claude: string; codex: string; gemini: string };
66
+ }
67
+
68
+ /**
69
+ * Build a CLI command from the given options.
70
+ * This is a pure function (aside from filesystem reads for prompt_file / workFolder validation).
71
+ * @throws Error on validation failures
72
+ */
73
+ export function buildCliCommand(options: BuildCliCommandOptions): CliCommand {
74
+ // Validate workFolder
75
+ if (!options.workFolder || typeof options.workFolder !== 'string') {
76
+ throw new Error('Missing or invalid required parameter: workFolder');
77
+ }
78
+
79
+ // Validate prompt / prompt_file
80
+ const hasPrompt = !!options.prompt && typeof options.prompt === 'string' && options.prompt.trim() !== '';
81
+ const hasPromptFile = !!options.prompt_file && typeof options.prompt_file === 'string' && options.prompt_file.trim() !== '';
82
+
83
+ if (!hasPrompt && !hasPromptFile) {
84
+ throw new Error('Either prompt or prompt_file must be provided');
85
+ }
86
+
87
+ if (hasPrompt && hasPromptFile) {
88
+ throw new Error('Cannot specify both prompt and prompt_file. Please use only one.');
89
+ }
90
+
91
+ // Determine prompt
92
+ let prompt: string;
93
+ if (hasPrompt) {
94
+ prompt = options.prompt!;
95
+ } else {
96
+ const promptFilePath = isAbsolute(options.prompt_file!)
97
+ ? options.prompt_file!
98
+ : pathResolve(options.workFolder, options.prompt_file!);
99
+
100
+ if (!existsSync(promptFilePath)) {
101
+ throw new Error(`Prompt file does not exist: ${promptFilePath}`);
102
+ }
103
+
104
+ try {
105
+ prompt = readFileSync(promptFilePath, 'utf-8');
106
+ } catch (error: any) {
107
+ throw new Error(`Failed to read prompt file: ${error.message}`);
108
+ }
109
+ }
110
+
111
+ // Resolve workFolder
112
+ const cwd = pathResolve(options.workFolder);
113
+ if (!existsSync(cwd)) {
114
+ throw new Error(`Working folder does not exist: ${options.workFolder}`);
115
+ }
116
+
117
+ // Resolve model
118
+ const rawModel = options.model || '';
119
+ const resolvedModel = resolveModelAlias(rawModel);
120
+
121
+ // Special handling for codex-ultra: default to high reasoning effort if not specified
122
+ let reasoningEffortArg: string | undefined = options.reasoning_effort;
123
+ if (rawModel === 'codex-ultra' && !reasoningEffortArg) {
124
+ reasoningEffortArg = 'xhigh';
125
+ }
126
+
127
+ const reasoningEffort = getReasoningEffort(resolvedModel, reasoningEffortArg);
128
+
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
+ // Build CLI path and args
140
+ let cliPath: string;
141
+ let args: string[];
142
+
143
+ if (agent === 'codex') {
144
+ cliPath = options.cliPaths.codex;
145
+
146
+ if (options.session_id && typeof options.session_id === 'string') {
147
+ args = ['exec', 'resume', options.session_id];
148
+ } else {
149
+ args = ['exec'];
150
+ }
151
+
152
+ if (reasoningEffort) {
153
+ args.push('-c', `model_reasoning_effort=${reasoningEffort}`);
154
+ }
155
+ if (resolvedModel) {
156
+ args.push('--model', resolvedModel);
157
+ }
158
+
159
+ args.push('--full-auto', '--json', prompt);
160
+
161
+ } else if (agent === 'gemini') {
162
+ cliPath = options.cliPaths.gemini;
163
+ args = ['-y', '--output-format', 'json'];
164
+
165
+ if (options.session_id && typeof options.session_id === 'string') {
166
+ args.push('-r', options.session_id);
167
+ }
168
+
169
+ if (resolvedModel) {
170
+ args.push('--model', resolvedModel);
171
+ }
172
+
173
+ args.push(prompt);
174
+
175
+ } else {
176
+ cliPath = options.cliPaths.claude;
177
+ args = ['--dangerously-skip-permissions', '--output-format', 'stream-json', '--verbose'];
178
+
179
+ if (options.session_id && typeof options.session_id === 'string') {
180
+ args.push('-r', options.session_id);
181
+ }
182
+
183
+ args.push('-p', prompt);
184
+ if (resolvedModel) {
185
+ args.push('--model', resolvedModel);
186
+ }
187
+ }
188
+
189
+ return { cliPath, args, cwd, agent, prompt, resolvedModel };
190
+ }
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { parseClaudeOutput, parseCodexOutput, parseGeminiOutput } from './parsers.js';
3
+
4
+ const AGENTS = ['claude', 'codex', 'gemini'] as const;
5
+ type Agent = typeof AGENTS[number];
6
+
7
+ const USAGE = `Usage: npm run -s cli.run.parse -- --agent <claude|codex|gemini>
8
+
9
+ Reads raw CLI output from stdin and outputs parsed JSON to stdout.
10
+
11
+ Options:
12
+ --agent Agent type: claude, codex, or gemini (required)
13
+ --help Show this help message
14
+
15
+ Examples:
16
+ npm run -s cli.run -- --model sonnet --workFolder /tmp --prompt "hi" > raw.txt
17
+ npm run -s cli.run.parse -- --agent claude < raw.txt
18
+
19
+ # Or pipe directly
20
+ npm run -s cli.run -- --model sonnet --workFolder /tmp --prompt "hi" | npm run -s cli.run.parse -- --agent claude
21
+ `;
22
+
23
+ function parseArgs(argv: string[]): Record<string, string> {
24
+ const result: Record<string, string> = {};
25
+ for (let i = 0; i < argv.length; i++) {
26
+ const arg = argv[i];
27
+ if (!arg.startsWith('--')) continue;
28
+
29
+ const eqIdx = arg.indexOf('=');
30
+ if (eqIdx !== -1) {
31
+ result[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
32
+ } else {
33
+ const key = arg.slice(2);
34
+ const next = argv[i + 1];
35
+ if (next !== undefined && !next.startsWith('--')) {
36
+ result[key] = next;
37
+ i++;
38
+ } else {
39
+ result[key] = '';
40
+ }
41
+ }
42
+ }
43
+ return result;
44
+ }
45
+
46
+ function readStdin(): Promise<string> {
47
+ return new Promise((resolve, reject) => {
48
+ const chunks: Buffer[] = [];
49
+ process.stdin.on('data', (chunk) => chunks.push(chunk));
50
+ process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
51
+ process.stdin.on('error', reject);
52
+ });
53
+ }
54
+
55
+ async function main(): Promise<void> {
56
+ const args = parseArgs(process.argv.slice(2));
57
+
58
+ if ('help' in args) {
59
+ process.stdout.write(USAGE);
60
+ process.exit(0);
61
+ }
62
+
63
+ const agent = args.agent as Agent;
64
+ if (!agent || !AGENTS.includes(agent)) {
65
+ process.stderr.write(`Error: --agent is required (claude, codex, or gemini)\n\n`);
66
+ process.stderr.write(USAGE);
67
+ process.exit(1);
68
+ }
69
+
70
+ const input = await readStdin();
71
+
72
+ if (!input.trim()) {
73
+ process.stderr.write('Error: no input received from stdin\n');
74
+ process.exit(1);
75
+ }
76
+
77
+ let parsed: any = null;
78
+ switch (agent) {
79
+ case 'claude':
80
+ parsed = parseClaudeOutput(input);
81
+ break;
82
+ case 'codex':
83
+ parsed = parseCodexOutput(input);
84
+ break;
85
+ case 'gemini':
86
+ parsed = parseGeminiOutput(input);
87
+ break;
88
+ }
89
+
90
+ process.stdout.write(JSON.stringify(parsed, null, 2) + '\n');
91
+ }
92
+
93
+ main().catch((err) => {
94
+ process.stderr.write(`Fatal error: ${err.message}\n`);
95
+ process.exit(1);
96
+ });
package/src/cli.ts ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { buildCliCommand } from './cli-builder.js';
4
+ import { findClaudeCli, findCodexCli, findGeminiCli } from './server.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)
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
43
+ --reasoning_effort Codex only: low, medium, high, xhigh
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
+ `;
50
+
51
+ async function main(): Promise<void> {
52
+ const args = parseArgs(process.argv.slice(2));
53
+
54
+ if ('help' in args) {
55
+ process.stdout.write(USAGE);
56
+ process.exit(0);
57
+ }
58
+
59
+ if (!args.workFolder) {
60
+ process.stderr.write('Error: --workFolder is required\n\n');
61
+ process.stderr.write(USAGE);
62
+ process.exit(1);
63
+ }
64
+
65
+ if (!args.prompt && !args.prompt_file) {
66
+ process.stderr.write('Error: --prompt or --prompt_file is required\n\n');
67
+ process.stderr.write(USAGE);
68
+ process.exit(1);
69
+ }
70
+
71
+ // Resolve CLI paths
72
+ const cliPaths = {
73
+ claude: findClaudeCli(),
74
+ codex: findCodexCli(),
75
+ gemini: findGeminiCli(),
76
+ };
77
+
78
+ // Build command
79
+ let cmd;
80
+ try {
81
+ cmd = buildCliCommand({
82
+ prompt: args.prompt || undefined,
83
+ prompt_file: args.prompt_file || undefined,
84
+ workFolder: args.workFolder,
85
+ model: args.model || undefined,
86
+ session_id: args.session_id || undefined,
87
+ reasoning_effort: args.reasoning_effort || undefined,
88
+ cliPaths,
89
+ });
90
+ } catch (error: any) {
91
+ process.stderr.write(`Error: ${error.message}\n`);
92
+ process.exit(1);
93
+ }
94
+
95
+ // Log agent info to stderr (does not pollute stdout)
96
+ process.stderr.write(`[cli.run] agent=${cmd.agent} model=${cmd.resolvedModel || '(default)'}\n`);
97
+
98
+ // Spawn foreground process — raw output passthrough
99
+ const child = spawn(cmd.cliPath, cmd.args, {
100
+ cwd: cmd.cwd,
101
+ stdio: 'inherit',
102
+ detached: false,
103
+ });
104
+
105
+ const exitCode = await new Promise<number>((resolve) => {
106
+ child.on('close', (code) => {
107
+ resolve(code ?? 1);
108
+ });
109
+ child.on('error', (err) => {
110
+ process.stderr.write(`Process error: ${err.message}\n`);
111
+ resolve(1);
112
+ });
113
+ });
114
+
115
+ process.exit(exitCode);
116
+ }
117
+
118
+ main().catch((err) => {
119
+ process.stderr.write(`Fatal error: ${err.message}\n`);
120
+ process.exit(1);
121
+ });
package/src/server.ts CHANGED
@@ -9,48 +9,16 @@ import {
9
9
  type ServerResult,
10
10
  } from '@modelcontextprotocol/sdk/types.js';
11
11
  import { spawn, ChildProcess } from 'node:child_process';
12
- import { existsSync, readFileSync } from 'node:fs';
12
+ import { existsSync } from 'node:fs';
13
13
  import { homedir } from 'node:os';
14
- import { join, resolve as pathResolve } from 'node:path';
14
+ import { join } from 'node:path';
15
15
  import * as path from 'path';
16
16
  import { parseCodexOutput, parseClaudeOutput, parseGeminiOutput } from './parsers.js';
17
+ import { buildCliCommand } from './cli-builder.js';
17
18
 
18
19
  // Server version - update this when releasing new versions
19
20
  const SERVER_VERSION = "2.2.0";
20
21
 
21
- // Model alias mappings for user-friendly model names
22
- const MODEL_ALIASES: Record<string, string> = {
23
- 'claude-ultra': 'opus',
24
- 'codex-ultra': 'gpt-5.3-codex',
25
- 'gemini-ultra': 'gemini-3-pro-preview'
26
- };
27
-
28
- const ALLOWED_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
29
-
30
- function getReasoningEffort(model: string, rawValue: unknown): string {
31
- if (typeof rawValue !== 'string') {
32
- return '';
33
- }
34
- const trimmed = rawValue.trim();
35
- if (!trimmed) {
36
- return '';
37
- }
38
- const normalized = trimmed.toLowerCase();
39
- if (!ALLOWED_REASONING_EFFORTS.has(normalized)) {
40
- throw new McpError(
41
- ErrorCode.InvalidParams,
42
- `Invalid reasoning_effort: ${rawValue}. Allowed values: low, medium, high, xhigh.`
43
- );
44
- }
45
- if (!model.startsWith('gpt-')) {
46
- throw new McpError(
47
- ErrorCode.InvalidParams,
48
- 'reasoning_effort is only supported for Codex models (gpt-*).'
49
- );
50
- }
51
- return normalized;
52
- }
53
-
54
22
  // Define debugMode globally using const
55
23
  const debugMode = process.env.MCP_CLAUDE_DEBUG === 'true';
56
24
 
@@ -226,36 +194,8 @@ export function findClaudeCli(): string {
226
194
  return cliName;
227
195
  }
228
196
 
229
- /**
230
- * Interface for Claude Code tool arguments
231
- */
232
- interface ClaudeCodeArgs {
233
- prompt?: string;
234
- prompt_file?: string;
235
- workFolder: string;
236
- model?: string;
237
- session_id?: string;
238
- }
239
-
240
- /**
241
- * Interface for Codex tool arguments
242
- */
243
- interface CodexArgs {
244
- prompt?: string;
245
- prompt_file?: string;
246
- workFolder: string;
247
- model?: string; // Codex model id (e.g., gpt-5.2-codex)
248
- reasoning_effort?: string;
249
- }
250
-
251
- /**
252
- * Resolves model aliases to their full model names
253
- * @param model - The model name or alias to resolve
254
- * @returns The full model name, or the original value if no alias exists
255
- */
256
- export function resolveModelAlias(model: string): string {
257
- return MODEL_ALIASES[model] || model;
258
- }
197
+ // Re-export resolveModelAlias for backward compatibility
198
+ export { resolveModelAlias } from './cli-builder.js';
259
199
 
260
200
  // Ensure spawnAsync is defined correctly *before* the class
261
201
  export async function spawnAsync(command: string, args: string[], options?: { timeout?: number, cwd?: string }): Promise<{ stdout: string; stderr: string }> {
@@ -512,135 +452,33 @@ export class ClaudeCodeServer {
512
452
  * Handle run tool - starts Claude or Codex process and returns PID immediately
513
453
  */
514
454
  private async handleRun(toolArguments: any): Promise<ServerResult> {
515
- // Validate workFolder is required
516
- if (!toolArguments.workFolder || typeof toolArguments.workFolder !== 'string') {
517
- throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: workFolder');
518
- }
519
-
520
- // Validate that either prompt or prompt_file is provided
521
- const hasPrompt = toolArguments.prompt && typeof toolArguments.prompt === 'string' && toolArguments.prompt.trim() !== '';
522
- const hasPromptFile = toolArguments.prompt_file && typeof toolArguments.prompt_file === 'string' && toolArguments.prompt_file.trim() !== '';
523
-
524
- if (!hasPrompt && !hasPromptFile) {
525
- throw new McpError(ErrorCode.InvalidParams, 'Either prompt or prompt_file must be provided');
526
- }
527
-
528
- if (hasPrompt && hasPromptFile) {
529
- throw new McpError(ErrorCode.InvalidParams, 'Cannot specify both prompt and prompt_file. Please use only one.');
530
- }
531
-
532
- // Determine the prompt to use
533
- let prompt: string;
534
- if (hasPrompt) {
535
- prompt = toolArguments.prompt;
536
- } else {
537
- // Read prompt from file
538
- const promptFilePath = path.isAbsolute(toolArguments.prompt_file)
539
- ? toolArguments.prompt_file
540
- : pathResolve(toolArguments.workFolder, toolArguments.prompt_file);
541
-
542
- if (!existsSync(promptFilePath)) {
543
- throw new McpError(ErrorCode.InvalidParams, `Prompt file does not exist: ${promptFilePath}`);
544
- }
545
-
546
- try {
547
- prompt = readFileSync(promptFilePath, 'utf-8');
548
- } catch (error: any) {
549
- throw new McpError(ErrorCode.InvalidParams, `Failed to read prompt file: ${error.message}`);
550
- }
551
- }
552
-
553
- // Determine working directory
554
- const resolvedCwd = pathResolve(toolArguments.workFolder);
555
- if (!existsSync(resolvedCwd)) {
556
- throw new McpError(ErrorCode.InvalidParams, `Working folder does not exist: ${toolArguments.workFolder}`);
557
- }
558
- const effectiveCwd = resolvedCwd;
559
-
560
455
  // Print version on first use
561
456
  if (isFirstToolUse) {
562
457
  console.error(`ai_cli_mcp v${SERVER_VERSION} started at ${serverStartupTime}`);
563
458
  isFirstToolUse = false;
564
459
  }
565
460
 
566
- // Determine which agent to use based on model name
567
- const rawModel = toolArguments.model || '';
568
- const resolvedModel = resolveModelAlias(rawModel);
569
-
570
- // Special handling for codex-ultra: default to high reasoning effort if not specified
571
- let reasoningEffortArg = toolArguments.reasoning_effort;
572
- if (rawModel === 'codex-ultra' && !reasoningEffortArg) {
573
- reasoningEffortArg = 'xhigh';
574
- }
575
-
576
- const reasoningEffort = getReasoningEffort(resolvedModel, reasoningEffortArg);
577
- let agent: 'codex' | 'claude' | 'gemini';
578
-
579
- if (resolvedModel.startsWith('gpt-')) {
580
- agent = 'codex';
581
- } else if (resolvedModel.startsWith('gemini')) {
582
- agent = 'gemini';
583
- } else {
584
- agent = 'claude';
461
+ // Build CLI command (validation + args assembly)
462
+ let cmd;
463
+ try {
464
+ cmd = buildCliCommand({
465
+ prompt: toolArguments.prompt,
466
+ prompt_file: toolArguments.prompt_file,
467
+ workFolder: toolArguments.workFolder,
468
+ model: toolArguments.model,
469
+ session_id: toolArguments.session_id,
470
+ reasoning_effort: toolArguments.reasoning_effort,
471
+ cliPaths: {
472
+ claude: this.claudeCliPath,
473
+ codex: this.codexCliPath,
474
+ gemini: this.geminiCliPath,
475
+ },
476
+ });
477
+ } catch (error: any) {
478
+ throw new McpError(ErrorCode.InvalidParams, error.message);
585
479
  }
586
480
 
587
- let cliPath: string;
588
- let processArgs: string[];
589
-
590
- if (agent === 'codex') {
591
- // Handle Codex
592
- cliPath = this.codexCliPath;
593
-
594
- // Use 'exec resume' if session_id is provided, otherwise use 'exec'
595
- if (toolArguments.session_id && typeof toolArguments.session_id === 'string') {
596
- processArgs = ['exec', 'resume', toolArguments.session_id];
597
- } else {
598
- processArgs = ['exec'];
599
- }
600
-
601
- // Handle Codex models.
602
- if (reasoningEffort) {
603
- processArgs.push('-c', `model_reasoning_effort=${reasoningEffort}`);
604
- }
605
- if (resolvedModel) {
606
- processArgs.push('--model', resolvedModel);
607
- }
608
-
609
- processArgs.push('--full-auto', '--json', prompt);
610
-
611
- } else if (agent === 'gemini') {
612
- // Handle Gemini
613
- cliPath = this.geminiCliPath;
614
- processArgs = ['-y', '--output-format', 'json'];
615
-
616
- // Add session_id if provided
617
- if (toolArguments.session_id && typeof toolArguments.session_id === 'string') {
618
- processArgs.push('-r', toolArguments.session_id);
619
- }
620
-
621
- // Add model if specified
622
- if (resolvedModel) {
623
- processArgs.push('--model', resolvedModel);
624
- }
625
-
626
- // Add prompt as positional argument
627
- processArgs.push(prompt);
628
-
629
- } else {
630
- // Handle Claude (default)
631
- cliPath = this.claudeCliPath;
632
- processArgs = ['--dangerously-skip-permissions', '--output-format', 'stream-json', '--verbose'];
633
-
634
- // Add session_id if provided (Claude only)
635
- if (toolArguments.session_id && typeof toolArguments.session_id === 'string') {
636
- processArgs.push('-r', toolArguments.session_id);
637
- }
638
-
639
- processArgs.push('-p', prompt);
640
- if (resolvedModel) {
641
- processArgs.push('--model', resolvedModel);
642
- }
643
- }
481
+ const { cliPath, args: processArgs, cwd: effectiveCwd, agent, prompt } = cmd;
644
482
 
645
483
  // Spawn process without waiting
646
484
  const childProcess = spawn(cliPath, processArgs, {