@leejungkiin/awkit 1.7.4 → 1.7.6

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.
@@ -3,11 +3,12 @@
3
3
  * Orchestrates tasks across Gemini Flash Medium, agy (Claude Opus), and Codex.
4
4
  */
5
5
 
6
- const { execSync } = require('child_process');
6
+ const { execSync, spawnSync } = require('child_process');
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const os = require('os');
9
10
 
10
- const AGY_PATH = '/Users/trungkientn/.local/bin/agy';
11
+ const AGY_PATH = path.join(os.homedir(), '.local', 'bin', 'agy');
11
12
 
12
13
  const C = {
13
14
  reset: '\x1b[0m',
@@ -25,6 +26,15 @@ const warn = (msg) => log(`${C.yellow}⚠${C.reset} ${msg}`);
25
26
  const err = (msg) => log(`${C.red}✖${C.reset} ${msg}`);
26
27
  const info = (msg) => log(`${C.cyan}ℹ${C.reset} ${msg}`);
27
28
 
29
+ // Load configurations from .project-identity
30
+ let config = {};
31
+ try {
32
+ const configPath = path.join(__dirname, '..', '.project-identity');
33
+ if (fs.existsSync(configPath)) {
34
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
35
+ }
36
+ } catch (_) {}
37
+
28
38
  function findAgy() {
29
39
  if (fs.existsSync(AGY_PATH)) {
30
40
  return AGY_PATH;
@@ -36,9 +46,135 @@ function findAgy() {
36
46
  }
37
47
  }
38
48
 
49
+ function findClaudePath() {
50
+ if (config.automation?.multiAgent?.runners?.claude) {
51
+ const p = config.automation.multiAgent.runners.claude;
52
+ if (fs.existsSync(p)) return p;
53
+ }
54
+ const homeClaude = path.join(os.homedir(), '.claude', 'local', 'claude');
55
+ if (fs.existsSync(homeClaude)) return homeClaude;
56
+
57
+ try {
58
+ const shell = process.env.SHELL || '/bin/zsh';
59
+ const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which claude"' : 'bash -lc "which claude"';
60
+ const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
61
+ if (detected && fs.existsSync(detected)) return detected;
62
+ } catch (_) {}
63
+
64
+ return 'claude';
65
+ }
66
+
67
+ function findCodexPath() {
68
+ if (config.automation?.multiAgent?.runners?.codex) {
69
+ const p = config.automation.multiAgent.runners.codex;
70
+ if (fs.existsSync(p)) return p;
71
+ }
72
+ try {
73
+ const shell = process.env.SHELL || '/bin/zsh';
74
+ const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which codex"' : 'bash -lc "which codex"';
75
+ const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
76
+ if (detected && fs.existsSync(detected)) return detected;
77
+ } catch (_) {}
78
+ return 'codex';
79
+ }
80
+
81
+ function findQwenPath() {
82
+ if (config.automation?.multiAgent?.runners?.qwen) {
83
+ const p = config.automation.multiAgent.runners.qwen;
84
+ if (fs.existsSync(p)) return p;
85
+ }
86
+ try {
87
+ const shell = process.env.SHELL || '/bin/zsh';
88
+ const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which qwen"' : 'bash -lc "which qwen"';
89
+ const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
90
+ if (detected && fs.existsSync(detected)) return detected;
91
+ } catch (_) {}
92
+ return 'qwen';
93
+ }
94
+
95
+ function readGlobalConfig() {
96
+ const defaultConf = {
97
+ audioAlerts: true,
98
+ runners: {
99
+ claude: true,
100
+ codex: true,
101
+ qwen: true
102
+ }
103
+ };
104
+ try {
105
+ const globalConfigPath = path.join(os.homedir(), '.awkit_config.json');
106
+ if (fs.existsSync(globalConfigPath)) {
107
+ const parsed = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
108
+ return {
109
+ ...defaultConf,
110
+ ...parsed,
111
+ runners: {
112
+ ...defaultConf.runners,
113
+ ...(parsed.runners || {})
114
+ }
115
+ };
116
+ }
117
+ } catch (_) {}
118
+ return defaultConf;
119
+ }
120
+
121
+ function isRunnerEnabled(runnerName) {
122
+ const localVal = config.automation?.multiAgent?.runnersEnabled?.[runnerName];
123
+ if (localVal !== undefined) return localVal;
124
+
125
+ const globalConfig = readGlobalConfig();
126
+ const globalVal = globalConfig.runners?.[runnerName];
127
+ if (globalVal !== undefined) return globalVal;
128
+
129
+ return true;
130
+ }
131
+
132
+ function isRunnerAvailable(runnerName) {
133
+ if (!isRunnerEnabled(runnerName)) return false;
134
+ let p;
135
+ if (runnerName === 'claude') p = findClaudePath();
136
+ else if (runnerName === 'codex') p = findCodexPath();
137
+ else if (runnerName === 'qwen') p = findQwenPath();
138
+ else return false;
139
+
140
+ if (fs.existsSync(p)) return true;
141
+ try {
142
+ execSync(`which ${p} || command -v ${p}`, { stdio: 'ignore' });
143
+ return true;
144
+ } catch (_) {
145
+ return false;
146
+ }
147
+ }
148
+
149
+
150
+ function playAudioAlert(message = "Quy trình đã hoàn thành") {
151
+ // 1. Read global configuration
152
+ let globalAudio = true;
153
+ try {
154
+ const globalConfigPath = path.join(os.homedir(), '.awkit_config.json');
155
+ if (fs.existsSync(globalConfigPath)) {
156
+ const globalConfig = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
157
+ if (globalConfig.audioAlerts !== undefined) {
158
+ globalAudio = globalConfig.audioAlerts;
159
+ }
160
+ }
161
+ } catch (_) {}
162
+
163
+ // 2. Read local project identity config
164
+ let localAudio = config.automation?.multiAgent?.audioAlerts;
165
+
166
+ // Local configuration takes priority. If not defined, fallback to global config.
167
+ const audioEnabled = localAudio !== undefined ? localAudio : globalAudio;
168
+
169
+ if (audioEnabled) {
170
+ try {
171
+ execSync(`afplay /System/Library/Sounds/Glass.aiff && say "${message}"`, { stdio: 'ignore' });
172
+ } catch (_) {}
173
+ }
174
+ }
175
+
39
176
  function runPlanStep(featureName) {
40
- const agyBin = findAgy();
41
- info(`Khởi chạy Lập kế hoạch Kiến trúc (Opus 4.6 via agy CLI)...`);
177
+ info(`Khởi chạy Lập kế hoạch Kiến trúc...`);
42
178
 
43
179
  // Ensure docs and docs/specs directories exist
44
180
  fs.mkdirSync(path.join(process.cwd(), 'docs', 'specs'), { recursive: true });
@@ -46,72 +182,178 @@ function runPlanStep(featureName) {
46
182
  const specFile = `docs/specs/${featureName.toLowerCase().replace(/\s+/g, '_')}_spec.md`;
47
183
  const prompt = `Thực hiện lập kế hoạch chi tiết cho tính năng '${featureName}'. Tạo PRD tại docs/PRD.md và Spec chi tiết tại ${specFile}. Hãy vẽ kèm sơ đồ Mermaid thể hiện luồng xử lý. Hãy phản hồi ngắn gọn bằng JSON tóm tắt các files đã tạo và mô tả kế hoạch.`;
48
184
 
49
- const cmd = `"${agyBin}" --model claude-opus --print "${prompt.replace(/"/g, '\\"')}" < /dev/null`;
185
+ let planSuccess = false;
50
186
 
51
- try {
52
- log(`${C.gray}$ ${cmd}${C.reset}`);
53
- const output = execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
54
- log(output);
55
- ok(`Hoàn thành lập kế hoạch kiến trúc.`);
56
- ok(`PRD đã lưu tại docs/PRD.md`);
57
- ok(`Spec đã lưu tại ${specFile}`);
58
- } catch (e) {
59
- err(`Lỗi lập kế hoạch: ${e.message}`);
60
- process.exit(1);
187
+ if (isRunnerAvailable('claude')) {
188
+ const claudePath = findClaudePath();
189
+ try {
190
+ info(`Calling Claude CLI (${claudePath}) for Architect Planning...`);
191
+ log(`$ ${claudePath} -p "..." --print --no-session-persistence --permission-mode bypassPermissions`);
192
+ const result = spawnSync(claudePath, [
193
+ '-p', prompt,
194
+ '--print',
195
+ '--no-session-persistence',
196
+ '--permission-mode', 'bypassPermissions'
197
+ ], { encoding: 'utf8', timeout: 30000 }); // 30s timeout for Claude
198
+
199
+ if (result.status === 0) {
200
+ log(result.stdout);
201
+ planSuccess = true;
202
+ } else {
203
+ warn(`Claude planning failed: ${result.stderr || result.stdout}`);
204
+ }
205
+ } catch (e) {
206
+ warn(`Claude planning failed or timed out: ${e.message}. Falling back to agy (Gemini)...`);
207
+ }
208
+ } else {
209
+ info(`Claude CLI is disabled or not available. Falling back directly to agy (Gemini)...`);
210
+ }
211
+
212
+ if (!planSuccess) {
213
+ const agyBin = findAgy();
214
+ try {
215
+ log(`$ ${agyBin} --model gemini-3.5-flash --print "..."`);
216
+ const result = spawnSync(agyBin, [
217
+ '--model', 'gemini-3.5-flash',
218
+ '--print', prompt
219
+ ], { encoding: 'utf8', timeout: 60000 });
220
+
221
+ if (result.status === 0) {
222
+ log(result.stdout);
223
+ } else {
224
+ err(`Lỗi lập kế hoạch: ${result.stderr || result.stdout}`);
225
+ process.exit(1);
226
+ }
227
+ } catch (e) {
228
+ err(`Lỗi lập kế hoạch: ${e.message}`);
229
+ process.exit(1);
230
+ }
61
231
  }
232
+
233
+ ok(`Hoàn thành lập kế hoạch kiến trúc.`);
234
+ ok(`PRD đã lưu tại docs/PRD.md`);
235
+ ok(`Spec đã lưu tại ${specFile}`);
236
+ playAudioAlert("Kế hoạch kiến trúc đã hoàn thành");
62
237
  }
63
238
 
64
239
  function runUiStep(featureName) {
65
- info(`Khởi chạy Thiết kế Shell & GUI Assets (Codex CLI)...`);
66
-
67
240
  // Ensure assets/ui directory exists
68
241
  fs.mkdirSync(path.join(process.cwd(), 'assets', 'ui'), { recursive: true });
69
242
 
70
243
  // Step A: Prompt to generate layout demo preview HTML/CSS or mockup code
71
244
  const promptPreview = `Tạo UI Demo Preview dưới dạng HTML/CSS tĩnh hoặc SwiftUI shell cho tính năng '${featureName}' dựa trên tài liệu docs/PRD.md và spec hiện có. Lưu tệp demo vào assets/ui/demo_preview.html.`;
72
- const cmdPreview = `codex exec "${promptPreview.replace(/"/g, '\\"')}" --sandbox read-only < /dev/null`;
73
245
 
74
- try {
75
- log(`${C.gray}$ ${cmdPreview}${C.reset}`);
76
- const outPreview = execSync(cmdPreview, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
77
- log(outPreview);
78
- ok(`Đã sinh UI Demo Preview tại assets/ui/demo_preview.html`);
79
- } catch (e) {
80
- err(`Lỗi sinh UI Demo Preview: ${e.message}`);
81
- process.exit(1);
82
- }
246
+ if (isRunnerAvailable('codex')) {
247
+ info(`Khởi chạy Thiết kế Shell & GUI Assets (Codex CLI)...`);
248
+ const cmdPreview = `codex exec "${promptPreview.replace(/"/g, '\\"')}" -s read-only --skip-git-repo-check < /dev/null`;
249
+ try {
250
+ log(`${C.gray}$ ${cmdPreview}${C.reset}`);
251
+ const outPreview = execSync(cmdPreview, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
252
+ log(outPreview);
253
+ ok(`Đã sinh UI Demo Preview tại assets/ui/demo_preview.html`);
254
+ } catch (e) {
255
+ err(`Lỗi sinh UI Demo Preview: ${e.message}`);
256
+ process.exit(1);
257
+ }
83
258
 
84
- // Step B: Prompt to generate spritesheet/image assets
85
- const promptAssets = `Tạo GUI assets (nút bấm, icons, pet companion, egg) dưới dạng spritesheet hoặc dạng assets lẻ dựa trên thiết kế của UI Demo Preview. Lưu tệp assets vào thư mục assets/ui/.`;
86
- const cmdAssets = `codex exec "${promptAssets.replace(/"/g, '\\"')}" --sandbox read-only < /dev/null`;
259
+ // Step B: Prompt to generate spritesheet/image assets
260
+ const promptAssets = `Tạo GUI assets (nút bấm, icons, pet companion, egg) dưới dạng spritesheet hoặc dạng assets lẻ dựa trên thiết kế của UI Demo Preview. Lưu tệp assets vào thư mục assets/ui/.`;
261
+ const cmdAssets = `codex exec "${promptAssets.replace(/"/g, '\\"')}" -s read-only --skip-git-repo-check < /dev/null`;
262
+ try {
263
+ log(`${C.gray}$ ${cmdAssets}${C.reset}`);
264
+ const outAssets = execSync(cmdAssets, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
265
+ log(outAssets);
266
+ ok(`Đã sinh GUI assets thành công vào assets/ui/`);
267
+ } catch (e) {
268
+ err(`Lỗi sinh GUI assets: ${e.message}`);
269
+ process.exit(1);
270
+ }
271
+ } else {
272
+ info(`Codex CLI is disabled or not available. Falling back to agy (Gemini)...`);
273
+ const agyBin = findAgy();
274
+ try {
275
+ log(`$ ${agyBin} --model gemini-3.5-flash --print "..."`);
276
+ const outPreview = spawnSync(agyBin, [
277
+ '--model', 'gemini-3.5-flash',
278
+ '--print', promptPreview
279
+ ], { encoding: 'utf8', timeout: 60000 });
87
280
 
88
- try {
89
- log(`${C.gray}$ ${cmdAssets}${C.reset}`);
90
- const outAssets = execSync(cmdAssets, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
91
- log(outAssets);
92
- ok(`Đã sinh GUI assets thành công vào assets/ui/`);
93
- } catch (e) {
94
- err(`Lỗi sinh GUI assets: ${e.message}`);
95
- process.exit(1);
281
+ if (outPreview.status === 0) {
282
+ let code = outPreview.stdout;
283
+ const match = code.match(/```html\s*([\s\S]*?)```/i) || code.match(/```xml\s*([\s\S]*?)```/i);
284
+ if (match) {
285
+ code = match[1];
286
+ }
287
+ fs.writeFileSync(path.join(process.cwd(), 'assets', 'ui', 'demo_preview.html'), code, 'utf8');
288
+ ok(`Đã sinh UI Demo Preview tại assets/ui/demo_preview.html`);
289
+ } else {
290
+ err(`Gemini UI Design failed: ${outPreview.stderr || outPreview.stdout}`);
291
+ process.exit(1);
292
+ }
293
+ } catch (e) {
294
+ err(`Lỗi sinh UI Demo Preview qua Gemini: ${e.message}`);
295
+ process.exit(1);
296
+ }
96
297
  }
298
+
299
+ playAudioAlert("Thiết kế giao diện đã hoàn thành");
97
300
  }
98
301
 
99
302
  function runCodeStep(featureName) {
100
- const agyBin = findAgy();
101
- info(`Khởi chạy Thực thi Viết Code (Gemini via agy CLI)...`);
303
+ const isCostOptimized = config.automation?.multiAgent?.routingMode === 'cost-optimized';
102
304
 
103
- const promptCode = `Hãy thực hiện viết code cho tính năng '${featureName}' tuân thủ kế hoạch chi tiết trong implementation_plan.md và các spec giao diện trong docs/specs/. Sau đó tự động gọi critic subagent để review code.`;
104
- const cmdCode = `"${agyBin}" --model gemini-2.5-flash --print "${promptCode.replace(/"/g, '\\"')}" < /dev/null`;
305
+ if (isCostOptimized && isRunnerAvailable('qwen')) {
306
+ info(`Khởi chạy Thực thi Viết Code (Qwen Coder CLI)...`);
307
+ const promptCode = `Hãy thực hiện viết code cho tính năng '${featureName}' tuân thủ kế hoạch chi tiết trong implementation_plan.md và các spec giao diện trong docs/specs/. Sau đó tự động gọi critic subagent để review code.`;
308
+
309
+ try {
310
+ const qwenScript = path.join(__dirname, 'qwen-exec.js');
311
+ log(`$ node ${qwenScript} --prompt "..."`);
312
+ const result = spawnSync('node', [
313
+ qwenScript,
314
+ '--prompt', promptCode
315
+ ], { encoding: 'utf8', timeout: 180000 });
105
316
 
106
- try {
107
- log(`${C.gray}$ ${cmdCode}${C.reset}`);
108
- const output = execSync(cmdCode, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
109
- log(output);
110
- ok(`Đã viết code hoàn tất quá trình review từ critic subagent.`);
111
- } catch (e) {
112
- err(`Lỗi thực thi viết code: ${e.message}`);
113
- process.exit(1);
317
+ if (result.status === 0) {
318
+ log(result.stdout);
319
+ ok(`Đã viết code hoàn tất quá trình review từ critic subagent.`);
320
+ } else {
321
+ err(`Lỗi thực thi viết code Qwen: ${result.stderr || result.stdout}`);
322
+ process.exit(1);
323
+ }
324
+ } catch (e) {
325
+ err(`Lỗi thực thi viết code Qwen: ${e.message}`);
326
+ process.exit(1);
327
+ }
328
+ } else {
329
+ const agyBin = findAgy();
330
+ if (isCostOptimized && !isRunnerAvailable('qwen')) {
331
+ info(`Qwen Coder CLI is disabled or not available. Falling back to agy (Gemini)...`);
332
+ }
333
+ info(`Khởi chạy Thực thi Viết Code (Gemini via agy CLI)...`);
334
+ const promptCode = `Hãy thực hiện viết code cho tính năng '${featureName}' tuân thủ kế hoạch chi tiết trong implementation_plan.md và các spec giao diện trong docs/specs/. Sau đó tự động gọi critic subagent để review code.`;
335
+
336
+ try {
337
+ log(`$ ${agyBin} --model gemini-3.5-flash --print "..."`);
338
+ const result = spawnSync(agyBin, [
339
+ '--model', 'gemini-3.5-flash',
340
+ '--print', promptCode
341
+ ], { encoding: 'utf8', timeout: 180000 });
342
+
343
+ if (result.status === 0) {
344
+ log(result.stdout);
345
+ ok(`Đã viết code và hoàn tất quá trình review từ critic subagent.`);
346
+ } else {
347
+ err(`Lỗi thực thi viết code: ${result.stderr || result.stdout}`);
348
+ process.exit(1);
349
+ }
350
+ } catch (e) {
351
+ err(`Lỗi thực thi viết code: ${e.message}`);
352
+ process.exit(1);
353
+ }
114
354
  }
355
+
356
+ playAudioAlert("Viết mã nguồn đã hoàn thành");
115
357
  }
116
358
 
117
359
  function cmdPipeline(args) {
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wrapper script to run Qwen CLI non-interactively within AWKit.
5
+ * Handles temporary files, sandbox options, and fallback protocols.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { execSync } = require('child_process');
12
+
13
+ const args = process.argv.slice(2);
14
+ let prompt = '';
15
+ let promptFile = '';
16
+ let outputFile = '';
17
+ let sandbox = 'read-only';
18
+
19
+ for (let i = 0; i < args.length; i++) {
20
+ if (args[i] === '--prompt' && args[i + 1]) {
21
+ prompt = args[i + 1];
22
+ i++;
23
+ } else if (args[i] === '--prompt-file' && args[i + 1]) {
24
+ promptFile = args[i + 1];
25
+ i++;
26
+ } else if (args[i] === '--output' && args[i + 1]) {
27
+ outputFile = args[i + 1];
28
+ i++;
29
+ } else if (args[i] === '--sandbox' && args[i + 1]) {
30
+ sandbox = args[i + 1];
31
+ i++;
32
+ }
33
+ }
34
+
35
+ // Read prompt from file if specified
36
+ if (promptFile && fs.existsSync(promptFile)) {
37
+ prompt = fs.readFileSync(promptFile, 'utf8');
38
+ }
39
+
40
+ if (!prompt) {
41
+ console.error('Error: Prompt is required. Use --prompt or --prompt-file');
42
+ process.exit(1);
43
+ }
44
+
45
+ // Load configurations from .project-identity
46
+ let routingMode = 'cost-optimized';
47
+ let config = {};
48
+
49
+ try {
50
+ const identityPath = path.join(__dirname, '..', '.project-identity');
51
+ if (fs.existsSync(identityPath)) {
52
+ config = JSON.parse(fs.readFileSync(identityPath, 'utf8'));
53
+ if (config.automation?.multiAgent?.routingMode) {
54
+ routingMode = config.automation.multiAgent.routingMode;
55
+ }
56
+ }
57
+ } catch (e) {
58
+ console.warn('⚠️ Could not load config from .project-identity, using defaults.');
59
+ }
60
+
61
+ // Dynamic Qwen CLI Path Detection
62
+ function findQwenPath() {
63
+ // 1. Check config path
64
+ if (config.automation?.multiAgent?.runners?.qwen) {
65
+ const p = config.automation.multiAgent.runners.qwen;
66
+ if (fs.existsSync(p)) return p;
67
+ }
68
+ // 2. Try interactive shell which (resolves aliases/nvm)
69
+ try {
70
+ const shell = process.env.SHELL || '/bin/zsh';
71
+ const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which qwen"' : 'bash -lc "which qwen"';
72
+ const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
73
+ if (detected && fs.existsSync(detected)) return detected;
74
+ } catch (_) {}
75
+ // 3. Basic command check
76
+ try {
77
+ const detected = execSync('which qwen || command -v qwen', { encoding: 'utf8' }).trim();
78
+ if (detected) return detected;
79
+ } catch (_) {}
80
+
81
+ return 'qwen';
82
+ }
83
+
84
+ const qwenPath = findQwenPath();
85
+
86
+ // Verify Qwen CLI existence
87
+ let qwenAvailable = false;
88
+ try {
89
+ execSync(`[ -f "${qwenPath}" ] || command -v qwen`, { stdio: 'ignore' });
90
+ qwenAvailable = true;
91
+ } catch (e) {
92
+ console.warn(`⚠️ Qwen CLI not found at ${qwenPath}`);
93
+ }
94
+
95
+ // Temporary file to pipe inputs securely
96
+ const tempInputFile = path.join(__dirname, `temp_qwen_input_${Date.now()}.txt`);
97
+ fs.writeFileSync(tempInputFile, prompt, 'utf8');
98
+
99
+ let success = false;
100
+ let finalOutput = '';
101
+
102
+ if (qwenAvailable) {
103
+ console.log('🤖 Running code generation via Qwen CLI...');
104
+ try {
105
+ const cmd = `"${qwenPath}" --max-session-turns 1 -s ${sandbox} < "${tempInputFile}"`;
106
+ finalOutput = execSync(cmd, { encoding: 'utf8', timeout: 120000 });
107
+ success = true;
108
+ } catch (e) {
109
+ console.warn('⚠️ Qwen execution failed. Fallback triggered.');
110
+ console.error(e.message);
111
+ }
112
+ }
113
+
114
+ // Fallback to Gemini CLI (agy) if Qwen failed or unavailable
115
+ if (!success) {
116
+ console.log('📡 Falling back to Gemini CLI (agy)...');
117
+ try {
118
+ const agyBin = 'agy';
119
+ const cmd = `"${agyBin}" --model gemini-3.5-flash --print "${prompt.replace(/"/g, '\\"')}" < /dev/null`;
120
+ finalOutput = execSync(cmd, { encoding: 'utf8', timeout: 60000 });
121
+ success = true;
122
+ } catch (e) {
123
+ console.error('❌ Fallback to Gemini CLI also failed:', e.message);
124
+ }
125
+ }
126
+
127
+ // Clean up temporary file
128
+ try {
129
+ if (fs.existsSync(tempInputFile)) {
130
+ fs.unlinkSync(tempInputFile);
131
+ }
132
+ } catch (_) {}
133
+
134
+ // Output results
135
+ if (success) {
136
+ if (outputFile) {
137
+ fs.mkdirSync(path.dirname(outputFile), { recursive: true });
138
+ fs.writeFileSync(outputFile, finalOutput, 'utf8');
139
+ console.log(`✅ Output written to ${outputFile}`);
140
+ } else {
141
+ process.stdout.write(finalOutput);
142
+ }
143
+ process.exit(0);
144
+ } else {
145
+ console.error('❌ Failed to execute code step with both Qwen and fallback Gemini.');
146
+ process.exit(1);
147
+ }
@@ -45,3 +45,27 @@ When writing `brain/<projectId>/plan_prompt.md`, ensure it contains:
45
45
  4. NeuralMemory relevant constraints.
46
46
  5. Symphony tasks.
47
47
  6. The target format instructions for `implementation_plan.md`.
48
+
49
+ ## ⚙️ Configuration & Control Techniques
50
+
51
+ ### 1. Check Runner Status
52
+ Verify if the Claude planner runner is enabled globally:
53
+ ```bash
54
+ awkit config claude
55
+ ```
56
+
57
+ ### 2. Enable/Disable Runner
58
+ To temporarily disable Claude planner (forcing fallback to Gemini standard models) or enable it:
59
+ ```bash
60
+ awkit config claude off
61
+ awkit config claude on
62
+ ```
63
+
64
+ ### 3. Check All Runners
65
+ ```bash
66
+ awkit config runners
67
+ ```
68
+
69
+ ### 4. Active Routing & Fallback
70
+ If `claude` is disabled or not installed on the system, the pipeline automatically routes Gate 2 architect planning to **Gemini Flash** via `agy` CLI using `gemini-3.5-flash`.
71
+
@@ -130,6 +130,30 @@ Parallel: gemini-conductor (different role, can coexist)
130
130
  Independent of: NeuralMemory
131
131
  ```
132
132
 
133
+ ## ⚙️ Configuration & Control Techniques
134
+
135
+ ### 1. Check Runner Status
136
+ Verify if the Codex runner is enabled globally:
137
+ ```bash
138
+ awkit config codex
139
+ ```
140
+
141
+ ### 2. Enable/Disable Runner
142
+ To temporarily disable Codex runner (forcing fallback to Gemini standard models) or enable it:
143
+ ```bash
144
+ awkit config codex off
145
+ awkit config codex on
146
+ ```
147
+
148
+ ### 3. Check All Runners
149
+ ```bash
150
+ awkit config runners
151
+ ```
152
+
153
+ ### 4. Active Routing & Fallback
154
+ If `codex` is disabled or unavailable on the system, the pipeline automatically routes UI design tasks (Gate 2.5 shell design) to **Gemini Flash** via `agy` CLI using `gemini-3.5-flash` and performs automatic HTML code extraction to save code to `assets/ui/demo_preview.html`.
155
+
133
156
  ---
134
157
 
135
- *codex-conductor v1.1 — Modular Router Architecture*
158
+ *codex-conductor v1.2 — Modular Router Architecture*
159
+