@bamptee/aia-code 2.0.13 → 2.0.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamptee/aia-code",
3
- "version": "2.0.13",
3
+ "version": "2.0.15",
4
4
  "description": "AI Architecture Assistant - orchestrate AI-assisted development workflows via CLI tools (Claude, Codex, Gemini)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,10 +12,14 @@ export async function generate(prompt, model, { verbose = false, apply = false,
12
12
  }
13
13
  if (apply) {
14
14
  args.push('--allowedTools', 'Edit,Write,Bash,Read,Glob,Grep');
15
+ // Use stream-json for real-time output visibility
16
+ args.push('--output-format', 'stream-json');
17
+ // Include partial messages for token-by-token streaming
18
+ args.push('--include-partial-messages');
15
19
  }
16
20
  if (verbose || apply) {
17
21
  args.push('--verbose');
18
22
  }
19
23
 
20
- return runCli('claude', args, { stdin: prompt, verbose: verbose || apply, apply, onData, cwd });
24
+ return runCli('claude', args, { stdin: prompt, verbose: verbose || apply, apply, onData, cwd, streamJson: apply });
21
25
  }
@@ -3,24 +3,124 @@ import { Readable } from 'node:stream';
3
3
  import chalk from 'chalk';
4
4
 
5
5
  const DEFAULT_IDLE_TIMEOUT_MS = 180_000;
6
- const AGENT_IDLE_TIMEOUT_MS = 600_000;
6
+ const AGENT_IDLE_TIMEOUT_MS = 1_800_000; // 30 minutes pour éviter les timeouts sur les tâches complexes
7
7
 
8
- export function runCli(command, args, { stdin: stdinData, verbose = false, apply = false, idleTimeoutMs, onData, cwd } = {}) {
8
+ /**
9
+ * Parse stream-json events from Claude CLI and extract human-readable output
10
+ */
11
+ function parseStreamJsonEvent(line, onData, state = {}) {
12
+ try {
13
+ const event = JSON.parse(line);
14
+
15
+ // Extract readable content based on event type
16
+ if (event.type === 'system' && event.subtype === 'init') {
17
+ const msg = `[init] Model: ${event.model}, Tools: ${event.tools?.length || 0}\n`;
18
+ if (onData) onData({ type: 'stdout', text: msg });
19
+ return { result: null, state };
20
+ }
21
+
22
+ // Stream events (partial messages) - token by token streaming
23
+ if (event.type === 'stream_event' && event.event) {
24
+ const streamEvent = event.event;
25
+
26
+ // Content block delta - actual text tokens
27
+ if (streamEvent.type === 'content_block_delta' && streamEvent.delta) {
28
+ if (streamEvent.delta.type === 'text_delta' && streamEvent.delta.text) {
29
+ if (onData) onData({ type: 'stdout', text: streamEvent.delta.text });
30
+ }
31
+ // Tool use input delta
32
+ if (streamEvent.delta.type === 'input_json_delta' && streamEvent.delta.partial_json) {
33
+ // Tool input streaming - don't output raw JSON
34
+ }
35
+ }
36
+
37
+ // Content block start - new block starting
38
+ if (streamEvent.type === 'content_block_start' && streamEvent.content_block) {
39
+ if (streamEvent.content_block.type === 'tool_use') {
40
+ const toolMsg = `\n[tool] ${streamEvent.content_block.name}\n`;
41
+ if (onData) onData({ type: 'stdout', text: toolMsg });
42
+ }
43
+ }
44
+
45
+ // Message start
46
+ if (streamEvent.type === 'message_start') {
47
+ // New message starting - could add indicator
48
+ }
49
+
50
+ // Message stop
51
+ if (streamEvent.type === 'message_stop') {
52
+ if (onData) onData({ type: 'stdout', text: '\n' });
53
+ }
54
+
55
+ return { result: null, state };
56
+ }
57
+
58
+ // Complete assistant message (non-streaming)
59
+ if (event.type === 'assistant' && event.message?.content) {
60
+ for (const block of event.message.content) {
61
+ if (block.type === 'text' && block.text) {
62
+ if (onData) onData({ type: 'stdout', text: block.text });
63
+ } else if (block.type === 'tool_use') {
64
+ const toolMsg = `\n[tool] ${block.name}\n`;
65
+ if (onData) onData({ type: 'stdout', text: toolMsg });
66
+ }
67
+ }
68
+ return { result: null, state };
69
+ }
70
+
71
+ // User message (tool results)
72
+ if (event.type === 'user' && event.message?.content) {
73
+ for (const block of event.message.content) {
74
+ if (block.type === 'tool_result') {
75
+ const status = block.is_error ? '✗' : '✓';
76
+ const resultMsg = `\n[${status}] Tool completed\n`;
77
+ if (onData) onData({ type: 'stdout', text: resultMsg });
78
+ }
79
+ }
80
+ return { result: null, state };
81
+ }
82
+
83
+ // Final result
84
+ if (event.type === 'result') {
85
+ return { result: event.result || '', state };
86
+ }
87
+
88
+ return { result: null, state };
89
+ } catch {
90
+ // Not valid JSON, return as-is
91
+ return { result: null, state };
92
+ }
93
+ }
94
+
95
+ export function runCli(command, args, { stdin: stdinData, verbose = false, apply = false, idleTimeoutMs, onData, cwd, streamJson = false } = {}) {
9
96
  if (!idleTimeoutMs) {
10
97
  idleTimeoutMs = apply ? AGENT_IDLE_TIMEOUT_MS : DEFAULT_IDLE_TIMEOUT_MS;
11
98
  }
12
99
  return new Promise((resolve, reject) => {
13
- const { CLAUDECODE, ...cleanEnv } = process.env;
100
+ // Log the full command for debugging (allows manual replay)
101
+ const shellCmd = [command, ...args].map(a => /[\s"']/.test(a) ? `'${a.replace(/'/g, "'\\''")}'` : a).join(' ');
102
+ console.error(chalk.gray(`[AI] Run: ${shellCmd}`));
103
+ if (stdinData) {
104
+ console.error(chalk.gray(`[AI] stdin: ${stdinData.length > 200 ? stdinData.slice(0, 200) + '...' : stdinData} (${(stdinData.length / 1024).toFixed(1)}KB)`));
105
+ }
106
+
107
+ // Remove CLAUDECODE from env to avoid conflicts
108
+ const { CLAUDECODE: _, ...cleanEnv } = process.env;
14
109
  const child = spawn(command, args, {
15
110
  stdio: ['pipe', 'pipe', 'pipe'],
16
- env: { ...process.env, FORCE_COLOR: '0' },
111
+ env: { ...cleanEnv, FORCE_COLOR: '0' },
17
112
  cwd,
18
113
  });
19
114
 
115
+ console.error(chalk.gray(`[AI] PID: ${child.pid}`));
116
+
20
117
  const chunks = [];
21
118
  let stderr = '';
22
119
  let settled = false;
23
120
  let gotFirstOutput = false;
121
+ let jsonBuffer = ''; // Buffer for incomplete JSON lines
122
+ let finalResult = ''; // Store the final result from stream-json
123
+ let parserState = {}; // State for stream parser
24
124
 
25
125
  function resetTimer() {
26
126
  clearTimeout(timer);
@@ -47,10 +147,32 @@ export function runCli(command, args, { stdin: stdinData, verbose = false, apply
47
147
  console.error(chalk.gray('[AI] First stdout received — agent is running'));
48
148
  }
49
149
  const text = data.toString();
50
- if (verbose) process.stdout.write(text);
51
- chunks.push(text);
52
- if (onData) onData({ type: 'stdout', text });
53
150
  resetTimer();
151
+
152
+ if (streamJson) {
153
+ // Parse stream-json format line by line
154
+ jsonBuffer += text;
155
+ const lines = jsonBuffer.split('\n');
156
+ jsonBuffer = lines.pop() || ''; // Keep incomplete line in buffer
157
+
158
+ for (const line of lines) {
159
+ if (!line.trim()) continue;
160
+ const { result, state } = parseStreamJsonEvent(line, onData, parserState);
161
+ parserState = state;
162
+ if (result !== null) {
163
+ finalResult = result;
164
+ }
165
+ if (verbose) {
166
+ // In verbose mode, also show raw JSON for debugging
167
+ process.stdout.write(chalk.gray(line + '\n'));
168
+ }
169
+ }
170
+ } else {
171
+ // Original behavior for non-stream-json
172
+ if (verbose) process.stdout.write(text);
173
+ chunks.push(text);
174
+ if (onData) onData({ type: 'stdout', text });
175
+ }
54
176
  });
55
177
 
56
178
  child.stderr.on('data', (data) => {
@@ -75,11 +197,21 @@ export function runCli(command, args, { stdin: stdinData, verbose = false, apply
75
197
  }
76
198
  });
77
199
 
78
- child.on('close', (code) => {
200
+ child.on('close', (code, signal) => {
201
+ console.error(chalk.gray(`[AI] Process exited: code=${code} signal=${signal || 'none'}`));
202
+ // Process any remaining data in the buffer
203
+ if (streamJson && jsonBuffer.trim()) {
204
+ const { result } = parseStreamJsonEvent(jsonBuffer, onData, parserState);
205
+ if (result !== null) {
206
+ finalResult = result;
207
+ }
208
+ }
209
+
79
210
  if (code !== 0) {
80
211
  finish(new Error(`${command} exited with code ${code}:\n${stderr.trim()}`));
81
212
  } else {
82
- finish(null, chunks.join(''));
213
+ // For stream-json, return the extracted result; otherwise return chunks
214
+ finish(null, streamJson ? finalResult : chunks.join(''));
83
215
  }
84
216
  });
85
217
 
@@ -1,14 +1,16 @@
1
1
  import { runCli } from './cli-runner.js';
2
2
 
3
- export async function generate(prompt, model, { verbose = false, apply = false, onData } = {}) {
3
+ export async function generate(prompt, model, { verbose = false, apply = false, onData, cwd } = {}) {
4
4
  const args = [];
5
5
  if (model) {
6
6
  args.push('-m', model);
7
7
  }
8
8
  if (apply) {
9
9
  args.push('--sandbox', 'false');
10
+ // Use stream-json for real-time output visibility
11
+ args.push('--output-format', 'stream-json');
10
12
  }
11
13
  args.push('-');
12
14
 
13
- return runCli('gemini', args, { stdin: prompt, verbose: verbose || apply, apply, onData });
15
+ return runCli('gemini', args, { stdin: prompt, verbose: verbose || apply, apply, onData, cwd, streamJson: apply });
14
16
  }
@@ -1,6 +1,6 @@
1
1
  import { runCli } from './cli-runner.js';
2
2
 
3
- export async function generate(prompt, model, { verbose = false, apply = false, onData } = {}) {
3
+ export async function generate(prompt, model, { verbose = false, apply = false, onData, cwd } = {}) {
4
4
  const args = ['exec'];
5
5
  if (model) {
6
6
  args.push('-c', `model="${model}"`);
@@ -10,5 +10,6 @@ export async function generate(prompt, model, { verbose = false, apply = false,
10
10
  }
11
11
  args.push('-');
12
12
 
13
- return runCli('codex', args, { stdin: prompt, verbose: verbose || apply, apply, onData });
13
+ // Note: Codex CLI does not support stream-json output format
14
+ return runCli('codex', args, { stdin: prompt, verbose: verbose || apply, apply, onData, cwd });
14
15
  }