@bamptee/aia-code 2.0.13 → 2.0.14

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.14",
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,17 +3,105 @@ 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
+ // Remove CLAUDECODE from env to avoid conflicts
101
+ const { CLAUDECODE: _, ...cleanEnv } = process.env;
14
102
  const child = spawn(command, args, {
15
103
  stdio: ['pipe', 'pipe', 'pipe'],
16
- env: { ...process.env, FORCE_COLOR: '0' },
104
+ env: { ...cleanEnv, FORCE_COLOR: '0' },
17
105
  cwd,
18
106
  });
19
107
 
@@ -21,6 +109,9 @@ export function runCli(command, args, { stdin: stdinData, verbose = false, apply
21
109
  let stderr = '';
22
110
  let settled = false;
23
111
  let gotFirstOutput = false;
112
+ let jsonBuffer = ''; // Buffer for incomplete JSON lines
113
+ let finalResult = ''; // Store the final result from stream-json
114
+ let parserState = {}; // State for stream parser
24
115
 
25
116
  function resetTimer() {
26
117
  clearTimeout(timer);
@@ -47,10 +138,32 @@ export function runCli(command, args, { stdin: stdinData, verbose = false, apply
47
138
  console.error(chalk.gray('[AI] First stdout received — agent is running'));
48
139
  }
49
140
  const text = data.toString();
50
- if (verbose) process.stdout.write(text);
51
- chunks.push(text);
52
- if (onData) onData({ type: 'stdout', text });
53
141
  resetTimer();
142
+
143
+ if (streamJson) {
144
+ // Parse stream-json format line by line
145
+ jsonBuffer += text;
146
+ const lines = jsonBuffer.split('\n');
147
+ jsonBuffer = lines.pop() || ''; // Keep incomplete line in buffer
148
+
149
+ for (const line of lines) {
150
+ if (!line.trim()) continue;
151
+ const { result, state } = parseStreamJsonEvent(line, onData, parserState);
152
+ parserState = state;
153
+ if (result !== null) {
154
+ finalResult = result;
155
+ }
156
+ if (verbose) {
157
+ // In verbose mode, also show raw JSON for debugging
158
+ process.stdout.write(chalk.gray(line + '\n'));
159
+ }
160
+ }
161
+ } else {
162
+ // Original behavior for non-stream-json
163
+ if (verbose) process.stdout.write(text);
164
+ chunks.push(text);
165
+ if (onData) onData({ type: 'stdout', text });
166
+ }
54
167
  });
55
168
 
56
169
  child.stderr.on('data', (data) => {
@@ -76,10 +189,19 @@ export function runCli(command, args, { stdin: stdinData, verbose = false, apply
76
189
  });
77
190
 
78
191
  child.on('close', (code) => {
192
+ // Process any remaining data in the buffer
193
+ if (streamJson && jsonBuffer.trim()) {
194
+ const { result } = parseStreamJsonEvent(jsonBuffer, onData, parserState);
195
+ if (result !== null) {
196
+ finalResult = result;
197
+ }
198
+ }
199
+
79
200
  if (code !== 0) {
80
201
  finish(new Error(`${command} exited with code ${code}:\n${stderr.trim()}`));
81
202
  } else {
82
- finish(null, chunks.join(''));
203
+ // For stream-json, return the extracted result; otherwise return chunks
204
+ finish(null, streamJson ? finalResult : chunks.join(''));
83
205
  }
84
206
  });
85
207
 
@@ -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
  }