closed-loop-cli 1.0.2 → 1.0.4

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.

Potentially problematic release.


This version of closed-loop-cli might be problematic. Click here for more details.

package/src/index.ts CHANGED
@@ -1,425 +1,356 @@
1
- #!/usr/bin/env node
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as readline from 'readline';
5
- import * as dotenv from 'dotenv';
6
- import { AutogenesisEngine } from './orchestrator/autogenesis';
7
- import { startDashboardServer } from './dashboard/server';
8
- import { startTelegramBot } from './orchestrator/telegram-bot';
9
- import { runTaskAgent } from './orchestrator/task-agent';
10
- import { Spinner } from './tools/tui-tools';
11
-
12
- // Phase 3 is initialized
13
-
14
- // Load environment variables
15
- dotenv.config();
16
-
17
- function printHeader() {
18
- const banner = `
19
- \x1b[38;5;99m ___ _ _ _
20
- / __\\ | ___ ___ ___ __| | | | ___ ___ _ __
21
- / / | |/ _ \\/ __|/ _ \\/ _\` | | |/ _ \\ / _ \\| '_ \\
22
- / /___| | (_) \\__ \\ __/ (_| | | | (_) | (_) | |_) |
23
- \\____/|_|\\___/|___/\\___|\\__,_| |_|\\___/ \\___/| .__/
24
- |_| \x1b[0m`;
25
-
26
- console.log(banner);
27
- console.log('\x1b[37mTips for getting started:\x1b[0m');
28
- console.log('\x1b[90m1. Ask questions, edit files, or run commands.\x1b[0m');
29
- console.log('\x1b[90m2. Be specific for the best results.\x1b[0m');
30
- console.log('\x1b[90m3. Type "exit" or "quit" to close the session.\x1b[0m\n');
31
-
32
- const endpoint = process.env.ANTHROPIC_BASE_URL || 'default';
33
- const model = process.env.ANTHROPIC_MODEL || 'mimo-v2.5-pro[1m]';
34
- const subagent = process.env.CLAUDE_CODE_SUBAGENT_MODEL || 'mimo-v2.5-pro';
35
-
36
- console.log(`\x1b[90mUsing: Endpoint: \x1b[38;5;86m${endpoint}\x1b[90m | Model: \x1b[38;5;153m${model}\x1b[90m | Subagent: \x1b[38;5;208m${subagent}\x1b[0m\n`);
37
- }
38
-
39
- class ClosedLoopTUI {
40
- private history: { role: 'user' | 'assistant'; content: string }[] = [];
41
- private input = '';
42
- private cursorIdx = 0;
43
- private isThinking = false;
44
- private effort: 'standard' | 'ultracode';
45
-
46
- constructor(effort: 'standard' | 'ultracode' = 'standard') {
47
- this.effort = effort;
48
- }
49
-
50
- start() {
51
- process.stdout.write('\x1b[?1049h'); // Enter alternate screen
52
- process.stdout.write('\x1b[?25h'); // Show cursor
53
- this.render();
54
-
55
- readline.emitKeypressEvents(process.stdin);
56
- if (process.stdin.isTTY) {
57
- process.stdin.setRawMode(true);
58
- }
59
-
60
- process.stdin.on('keypress', this.handleKeypress.bind(this));
61
- process.stdout.on('resize', () => {
62
- this.render();
63
- });
64
- }
65
-
66
- render() {
67
- const rows = process.stdout.rows || 24;
68
- const cols = process.stdout.columns || 80;
69
-
70
- // Clear screen and move cursor to 1,1
71
- process.stdout.write('\x1b[2J\x1b[H');
72
-
73
- // 1. Draw Banner & Info
74
- const banner = `
75
- \x1b[38;5;99m ___ _ _ _
76
- / __\\ | ___ ___ ___ __| | | | ___ ___ _ __
77
- / / | |/ _ \\/ __|/ _ \\/ _\` | | |/ _ \\ / _ \\| '_ \\
78
- / /___| | (_) \\__ \\ __/ (_| | | | (_) | (_) | |_) |
79
- \\____/|_|\\___/|___/\\___|\\__,_| |_|\\___/ \\___/| .__/
80
- |_| \x1b[0m`;
81
- console.log(banner);
82
-
83
- console.log('\x1b[37mTips for getting started:\x1b[0m');
84
- console.log('\x1b[90m1. Ask questions, edit files, or run commands. 2. Be specific. 3. Type "exit" to quit.\x1b[0m');
85
-
86
- const endpoint = process.env.ANTHROPIC_BASE_URL || 'default';
87
- const model = process.env.ANTHROPIC_MODEL || 'mimo-v2.5-pro';
88
- console.log(`\x1b[90mUsing: Endpoint: \x1b[38;5;86m${endpoint}\x1b[90m | Model: \x1b[38;5;153m${model}\x1b[0m\n`);
89
-
90
- // 2. Draw History in the middle
91
- const historyHeight = rows - 14;
92
- const historyLines: string[] = [];
93
-
94
- // Format history messages into lines
95
- for (const msg of this.history) {
96
- if (msg.role === 'user') {
97
- historyLines.push(`\x1b[38;5;99mUser ❯\x1b[0m ${msg.content}`);
98
- } else {
99
- const lines = msg.content.split('\n');
100
- for (const line of lines) {
101
- historyLines.push(`\x1b[38;5;86mAgent ❯\x1b[0m ${line}`);
102
- }
103
- }
104
- }
105
-
106
- // Slice history to fit historyHeight
107
- const startIdx = Math.max(0, historyLines.length - historyHeight);
108
- const visibleHistory = historyLines.slice(startIdx, startIdx + historyHeight);
109
-
110
- for (let i = 0; i < historyHeight; i++) {
111
- if (i < visibleHistory.length) {
112
- console.log(visibleHistory[i]);
113
- } else {
114
- console.log(''); // empty line
115
- }
116
- }
117
-
118
- // 3. Draw Bottom Input Box (3 lines)
119
- const border = '─'.repeat(cols - 2);
120
- console.log(`\x1b[38;5;99m╭${border}╮\x1b[0m`);
121
-
122
- const promptStr = this.isThinking ? ' \x1b[36m⠋ Thinking...\x1b[0m' : ` › ${this.input}`;
123
- const visiblePromptLen = this.isThinking ? 14 : 3 + this.input.length;
124
- const padding = ' '.repeat(Math.max(0, cols - 4 - visiblePromptLen));
125
- console.log(`\x1b[38;5;99m│\x1b[0m${promptStr}${padding}\x1b[38;5;99m│\x1b[0m`);
126
-
127
- console.log(`\x1b[38;5;99m╰${border}╯\x1b[0m`);
128
-
129
- // Position cursor
130
- if (!this.isThinking) {
131
- const cursorCol = 4 + this.cursorIdx;
132
- const cursorRow = rows - 1;
133
- process.stdout.write(`\x1b[${cursorRow};${cursorCol}H`);
134
- } else {
135
- process.stdout.write('\x1b[?25l');
136
- }
137
- }
138
-
139
- async handleKeypress(str: string, key: any) {
140
- if (this.isThinking) return;
141
-
142
- if (key.ctrl && key.name === 'c') {
143
- this.exit();
144
- }
145
-
146
- if (key.name === 'return') {
147
- const cmd = this.input.trim();
148
- if (!cmd) return;
149
-
150
- if (cmd.toLowerCase() === 'exit' || cmd.toLowerCase() === 'quit') {
151
- this.exit();
152
- }
153
-
154
- this.history.push({ role: 'user', content: cmd });
155
- this.input = '';
156
- this.cursorIdx = 0;
157
- this.isThinking = true;
158
- this.render();
159
-
160
- try {
161
- // Temporarily leave alternate screen buffer for execution
162
- process.stdout.write('\x1b[?1049l');
163
- process.stdout.write('\x1b[?25h');
164
- if (process.stdin.isTTY) {
165
- process.stdin.setRawMode(false);
166
- }
167
-
168
- console.log(`\n\x1b[35;1m=================== EXECUTION MODE ===================\x1b[0m`);
169
- console.log(`\x1b[90mRunning Task:\x1b[0m ${cmd}\n`);
170
-
171
- const response = await runTaskAgent(cmd, {
172
- role: 'coder_codeact' as any,
173
- effort: this.effort,
174
- history: this.history.slice(0, -1)
175
- });
176
-
177
- // Re-enter TUI and raw mode
178
- process.stdout.write('\x1b[?1049h');
179
- if (process.stdin.isTTY) {
180
- process.stdin.setRawMode(true);
181
- }
182
-
183
- this.history.push({ role: 'assistant', content: response.result });
184
- } catch (err: any) {
185
- // Re-enter TUI and raw mode
186
- process.stdout.write('\x1b[?1049h');
187
- if (process.stdin.isTTY) {
188
- process.stdin.setRawMode(true);
189
- }
190
- this.history.push({ role: 'assistant', content: `Error: ${err.message}` });
191
- }
192
-
193
- this.isThinking = false;
194
- process.stdout.write('\x1b[?25h');
195
- this.render();
196
- return;
197
- }
198
-
199
- if (key.name === 'backspace') {
200
- if (this.cursorIdx > 0) {
201
- this.input = this.input.slice(0, this.cursorIdx - 1) + this.input.slice(this.cursorIdx);
202
- this.cursorIdx--;
203
- this.render();
204
- }
205
- return;
206
- }
207
-
208
- if (key.name === 'left') {
209
- if (this.cursorIdx > 0) {
210
- this.cursorIdx--;
211
- this.render();
212
- }
213
- return;
214
- }
215
-
216
- if (key.name === 'right') {
217
- if (this.cursorIdx < this.input.length) {
218
- this.cursorIdx++;
219
- this.render();
220
- }
221
- return;
222
- }
223
-
224
- // Handle normal character inputs
225
- if (str && str.length === 1 && !key.ctrl && !key.meta) {
226
- this.input = this.input.slice(0, this.cursorIdx) + str + this.input.slice(this.cursorIdx);
227
- this.cursorIdx++;
228
- this.render();
229
- }
230
- }
231
-
232
- exit() {
233
- process.stdout.write('\x1b[?1049l');
234
- process.stdout.write('\x1b[?25h');
235
- console.log('Goodbye!');
236
- process.exit(0);
237
- }
238
- }
239
-
240
- async function startInteractiveCLI(effort: 'standard' | 'ultracode' = 'standard', codeactMode = false) {
241
- const tui = new ClosedLoopTUI(effort);
242
- tui.start();
243
- }
244
-
245
- function setupLogRedirection() {
246
- const logFile = fs.createWriteStream(path.join(process.cwd(), 'evolution.log'), { flags: 'a' });
247
- const originalLog = console.log;
248
- const originalError = console.error;
249
-
250
- console.log = function(...args) {
251
- originalLog.apply(console, args);
252
- logFile.write(args.join(' ') + '\n');
253
- };
254
- console.error = function(...args) {
255
- originalError.apply(console, args);
256
- logFile.write('ERROR: ' + args.join(' ') + '\n');
257
- };
258
- }
259
-
260
- async function main() {
261
- const args = process.argv.slice(2);
262
-
263
- // Parse codeact option
264
- let codeactMode = false;
265
- const codeactIdx = args.findIndex(arg => arg === '--codeact' || arg === '--openhands');
266
- if (codeactIdx !== -1) {
267
- codeactMode = true;
268
- args.splice(codeactIdx, 1);
269
- }
270
-
271
- // Parse effort option
272
- let effort: 'standard' | 'ultracode' = 'standard';
273
- const effortIdx = args.findIndex(arg => arg === '--effort' || arg === '-e');
274
- if (effortIdx !== -1 && effortIdx + 1 < args.length) {
275
- const val = args[effortIdx + 1].toLowerCase();
276
- if (val === 'ultracode' || val === 'max') {
277
- effort = 'ultracode';
278
- }
279
- // Remove --effort and its value from args
280
- args.splice(effortIdx, 2);
281
- } else {
282
- const envEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL || 'standard';
283
- if (envEffort === 'max' || envEffort === 'ultracode') {
284
- effort = 'ultracode';
285
- }
286
- }
287
-
288
- if (args.length > 0) {
289
- if (args[0] === '--telegram') {
290
- try {
291
- console.log(`\n\x1b[33m[Running Standalone Telegram ChatOps Mode]\x1b[0m`);
292
- startTelegramBot();
293
- return;
294
- } catch (err: any) {
295
- console.error('\n\x1b[31m[Telegram ChatOps Failed]:\x1b[0m', err.message);
296
- process.exit(1);
297
- }
298
- }
299
-
300
- if (args[0] === '--dashboard') {
301
- try {
302
- console.log(`\n\x1b[33m[Running Standalone Dashboard Monitor Mode]\x1b[0m`);
303
- startDashboardServer();
304
- return;
305
- } catch (err: any) {
306
- console.error('\n\x1b[31m[Dashboard Failed]:\x1b[0m', err.message);
307
- process.exit(1);
308
- }
309
- }
310
-
311
- const isContinuous = args.includes('--continuous') || args.includes('-c');
312
- if (isContinuous) {
313
- try {
314
- let cycles = 0; // infinite
315
- const cyclesIdx = args.indexOf('--cycles');
316
- if (cyclesIdx !== -1 && cyclesIdx + 1 < args.length) {
317
- cycles = parseInt(args[cyclesIdx + 1], 10) || 0;
318
- }
319
-
320
- let tokenBudget = 500000; // default 500k tokens
321
- const budgetIdx = args.indexOf('--budget-tokens');
322
- if (budgetIdx !== -1 && budgetIdx + 1 < args.length) {
323
- tokenBudget = parseInt(args[budgetIdx + 1], 10) || 500000;
324
- }
325
-
326
- console.log(`\n\x1b[33m[Running Continuous Self-Evolution Daemon Mode]\x1b[0m`);
327
- console.log(`- Cycle Limit: ${cycles === 0 ? 'Infinite' : cycles}`);
328
- console.log(`- Token Budget: ${tokenBudget} tokens\n`);
329
-
330
- setupLogRedirection();
331
- startDashboardServer();
332
- startTelegramBot();
333
-
334
- const engine = new AutogenesisEngine();
335
- engine.setEffort(effort);
336
- engine.codeactMode = codeactMode;
337
- await engine.runContinuousEvolution(cycles, tokenBudget);
338
- console.log('\n\x1b[32m[Continuous Self-Evolution Daemon Stopped Cleanly]\x1b[0m');
339
- process.exit(0);
340
- } catch (err: any) {
341
- console.error('\n\x1b[31m[Continuous Self-Evolution Daemon Failed]:\x1b[0m', err.message);
342
- process.exit(1);
343
- }
344
- }
345
-
346
- if (args[0] === '--refactor') {
347
- try {
348
- console.log(`\n\x1b[33m[Running Autonomous Refactoring Mode]\x1b[0m`);
349
- const engine = new AutogenesisEngine();
350
- engine.setEffort(effort);
351
- engine.codeactMode = codeactMode;
352
- await engine.runAutonomousRefactor();
353
- console.log('\n\x1b[32m[Autonomous Refactoring Completed]\x1b[0m');
354
- process.exit(0);
355
- } catch (err: any) {
356
- console.error('\n\x1b[31m[Autonomous Refactoring Failed]:\x1b[0m', err.message);
357
- process.exit(1);
358
- }
359
- }
360
-
361
- if (args[0] === '--optimize-prompt') {
362
- try {
363
- console.log(`\n\x1b[33m[Running Prompt Optimization Benchmarking Mode]\x1b[0m`);
364
- const engine = new AutogenesisEngine();
365
- engine.setEffort(effort);
366
- engine.codeactMode = codeactMode;
367
- await engine.runPromptOptimization();
368
- console.log('\n\x1b[32m[Prompt Optimization Completed]\x1b[0m');
369
- process.exit(0);
370
- } catch (err: any) {
371
- console.error('\n\x1b[31m[Prompt Optimization Failed]:\x1b[0m', err.message);
372
- process.exit(1);
373
- }
374
- }
375
-
376
- if (args[0] === '--dgm') {
377
- try {
378
- // Parse optional --generations N flag
379
- let maxGenerations = 5;
380
- const genIdx = args.indexOf('--generations');
381
- if (genIdx !== -1 && genIdx + 1 < args.length) {
382
- maxGenerations = parseInt(args[genIdx + 1], 10) || 5;
383
- args.splice(genIdx, 2);
384
- }
385
-
386
- // Task is everything after --dgm flag
387
- const dgmTask = args.slice(1).join(' ') || 'Improve the codebase using open-ended evolution';
388
- console.log(`\n\x1b[33m[Running DGM (Darwin Gödel Machine) Evolution Mode]\x1b[0m`);
389
- console.log(`Task: "${dgmTask}" | Generations: ${maxGenerations}\n`);
390
- const engine = new AutogenesisEngine();
391
- engine.setEffort(effort);
392
- engine.codeactMode = codeactMode;
393
- await engine.runDGMEvolution(dgmTask, maxGenerations);
394
- console.log('\n\x1b[32m[DGM Evolution Completed]\x1b[0m');
395
- process.exit(0);
396
- } catch (err: any) {
397
- console.error('\n\x1b[31m[DGM Evolution Failed]:\x1b[0m', err.message);
398
- process.exit(1);
399
- }
400
- }
401
-
402
- // Direct command execution
403
- const task = args.join(' ');
404
- try {
405
- console.log(`Running direct task: "${task}" (CodeAct=${codeactMode})`);
406
- const engine = new AutogenesisEngine();
407
- engine.setEffort(effort);
408
- engine.codeactMode = codeactMode;
409
- await engine.runEvolutionStep(task);
410
- console.log('\n\x1b[32m[Evolution Step Completed]\x1b[0m');
411
- process.exit(0);
412
- } catch (err: any) {
413
- console.error('\n\x1b[31m[Evolution Failed]:\x1b[0m', err.message);
414
- process.exit(1);
415
- }
416
- } else {
417
- // Start interactive session
418
- await startInteractiveCLI(effort, codeactMode);
419
- }
420
- }
421
-
422
- main().catch(err => {
423
- console.error('Fatal error:', err);
424
- process.exit(1);
425
- });
1
+ #!/usr/bin/env node
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as readline from 'readline';
5
+ import * as dotenv from 'dotenv';
6
+ import * as os from 'os';
7
+ import { startDashboardServer } from './dashboard/server';
8
+ import { startTelegramBot } from './orchestrator/telegram-bot';
9
+ import { runTaskAgent } from './orchestrator/task-agent';
10
+ import { Spinner } from './tools/tui-tools';
11
+
12
+ // Load environment variables
13
+ dotenv.config();
14
+
15
+ function printHeader() {
16
+ const banner = `
17
+ \x1b[38;5;99m ___ _ _ _
18
+ / __\\ | ___ ___ ___ __| | | | ___ ___ _ __
19
+ / / | |/ _ \\/ __|/ _ \\/ _\` | | |/ _ \\ / _ \\| '_ \\
20
+ / /___| | (_) \\__ \\ __/ (_| | | | (_) | (_) | |_) |
21
+ \\____/|_|\\___/|___/\\___|\\__,_| |_|\\___/ \\___/| .__/
22
+ |_| \x1b[0m`;
23
+
24
+ console.log(banner);
25
+ console.log('\x1b[37mTips for getting started:\x1b[0m');
26
+ console.log('\x1b[90m1. Ask questions, edit files, or run commands.\x1b[0m');
27
+ console.log('\x1b[90m2. Be specific for the best results.\x1b[0m');
28
+ console.log('\x1b[90m3. Type "exit" or "quit" to close the session.\x1b[0m\n');
29
+
30
+ const endpoint = process.env.ANTHROPIC_BASE_URL || 'default';
31
+ const model = process.env.ANTHROPIC_MODEL || 'mimo-v2.5-pro[1m]';
32
+ const subagent = process.env.CLAUDE_CODE_SUBAGENT_MODEL || 'mimo-v2.5-pro';
33
+
34
+ console.log(`\x1b[90mUsing: Endpoint: \x1b[38;5;86m${endpoint}\x1b[90m | Model: \x1b[38;5;153m${model}\x1b[90m | Subagent: \x1b[38;5;208m${subagent}\x1b[0m\n`);
35
+ }
36
+
37
+ class ClosedLoopTUI {
38
+ private history: { role: 'user' | 'assistant'; content: string }[] = [];
39
+ private input = '';
40
+ private cursorIdx = 0;
41
+ private isThinking = false;
42
+ private effort: 'standard' | 'ultracode';
43
+
44
+ constructor(effort: 'standard' | 'ultracode' = 'standard') {
45
+ this.effort = effort;
46
+ }
47
+
48
+ start() {
49
+ process.stdout.write('\x1b[?1049h'); // Enter alternate screen
50
+ process.stdout.write('\x1b[?25h'); // Show cursor
51
+ this.render();
52
+
53
+ readline.emitKeypressEvents(process.stdin);
54
+ if (process.stdin.isTTY) {
55
+ process.stdin.setRawMode(true);
56
+ }
57
+
58
+ process.stdin.on('keypress', this.handleKeypress.bind(this));
59
+ process.stdout.on('resize', () => {
60
+ this.render();
61
+ });
62
+ }
63
+
64
+ render() {
65
+ const rows = process.stdout.rows || 24;
66
+ const cols = process.stdout.columns || 80;
67
+
68
+ // Clear screen and move cursor to 1,1
69
+ process.stdout.write('\x1b[2J\x1b[H');
70
+
71
+ // 1. Draw Banner & Info
72
+ const banner = `
73
+ \x1b[38;5;99m ___ _ _ _
74
+ / __\\ | ___ ___ ___ __| | | | ___ ___ _ __
75
+ / / | |/ _ \\/ __|/ _ \\/ _\` | | |/ _ \\ / _ \\| '_ \\
76
+ / /___| | (_) \\__ \\ __/ (_| | | | (_) | (_) | |_) |
77
+ \\____/|_|\\___/|___/\\___|\\__,_| |_|\\___/ \\___/| .__/
78
+ |_| \x1b[0m`;
79
+ console.log(banner);
80
+
81
+ console.log('\x1b[37mTips for getting started:\x1b[0m');
82
+ console.log('\x1b[90m1. Ask questions, edit files, or run commands. 2. Be specific. 3. Type "exit" to quit.\x1b[0m');
83
+
84
+ const endpoint = process.env.ANTHROPIC_BASE_URL || 'default';
85
+ const model = process.env.ANTHROPIC_MODEL || 'mimo-v2.5-pro';
86
+ console.log(`\x1b[90mUsing: Endpoint: \x1b[38;5;86m${endpoint}\x1b[90m | Model: \x1b[38;5;153m${model}\x1b[0m\n`);
87
+
88
+ // 2. Draw History in the middle
89
+ const historyHeight = rows - 14;
90
+ const historyLines: string[] = [];
91
+
92
+ // Format history messages into lines
93
+ for (const msg of this.history) {
94
+ if (msg.role === 'user') {
95
+ historyLines.push(`\x1b[38;5;99mUser ❯\x1b[0m ${msg.content}`);
96
+ } else {
97
+ const lines = msg.content.split('\n');
98
+ for (const line of lines) {
99
+ historyLines.push(`\x1b[38;5;86mAgent ❯\x1b[0m ${line}`);
100
+ }
101
+ }
102
+ }
103
+
104
+ // Slice history to fit historyHeight
105
+ const startIdx = Math.max(0, historyLines.length - historyHeight);
106
+ const visibleHistory = historyLines.slice(startIdx, startIdx + historyHeight);
107
+
108
+ for (let i = 0; i < historyHeight; i++) {
109
+ if (i < visibleHistory.length) {
110
+ console.log(visibleHistory[i]);
111
+ } else {
112
+ console.log(''); // empty line
113
+ }
114
+ }
115
+
116
+ // 3. Draw Bottom Input Box (3 lines)
117
+ const border = '─'.repeat(cols - 2);
118
+ console.log(`\x1b[38;5;99m╭${border}╮\x1b[0m`);
119
+
120
+ const promptStr = this.isThinking ? ' \x1b[36m⠋ Thinking...\x1b[0m' : ` › ${this.input}`;
121
+ const visiblePromptLen = this.isThinking ? 14 : 3 + this.input.length;
122
+ const padding = ' '.repeat(Math.max(0, cols - 4 - visiblePromptLen));
123
+ console.log(`\x1b[38;5;99m│\x1b[0m${promptStr}${padding}\x1b[38;5;99m│\x1b[0m`);
124
+
125
+ console.log(`\x1b[38;5;99m╰${border}╯\x1b[0m`);
126
+
127
+ // Position cursor
128
+ if (!this.isThinking) {
129
+ const cursorCol = 4 + this.cursorIdx;
130
+ const cursorRow = rows - 1;
131
+ process.stdout.write(`\x1b[${cursorRow};${cursorCol}H`);
132
+ } else {
133
+ process.stdout.write('\x1b[?25l');
134
+ }
135
+ }
136
+
137
+ async handleKeypress(str: string, key: any) {
138
+ if (this.isThinking) return;
139
+
140
+ if (key.ctrl && key.name === 'c') {
141
+ this.exit();
142
+ }
143
+
144
+ if (key.name === 'return') {
145
+ const cmd = this.input.trim();
146
+ if (!cmd) return;
147
+
148
+ if (cmd.toLowerCase() === 'exit' || cmd.toLowerCase() === 'quit') {
149
+ this.exit();
150
+ }
151
+
152
+ this.history.push({ role: 'user', content: cmd });
153
+ this.input = '';
154
+ this.cursorIdx = 0;
155
+ this.isThinking = true;
156
+ this.render();
157
+
158
+ try {
159
+ // Temporarily leave alternate screen buffer for execution
160
+ process.stdout.write('\x1b[?1049l');
161
+ process.stdout.write('\x1b[?25h');
162
+ if (process.stdin.isTTY) {
163
+ process.stdin.setRawMode(false);
164
+ }
165
+
166
+ console.log(`\n\x1b[35;1m=================== EXECUTION MODE ===================\x1b[0m`);
167
+ console.log(`\x1b[90mRunning Task:\x1b[0m ${cmd}\n`);
168
+
169
+ const response = await runTaskAgent(cmd, {
170
+ role: 'coder_codeact' as any,
171
+ effort: this.effort,
172
+ history: this.history.slice(0, -1)
173
+ });
174
+
175
+ // Re-enter TUI and raw mode
176
+ process.stdout.write('\x1b[?1049h');
177
+ if (process.stdin.isTTY) {
178
+ process.stdin.setRawMode(true);
179
+ }
180
+
181
+ this.history.push({ role: 'assistant', content: response.result });
182
+ } catch (err: any) {
183
+ // Re-enter TUI and raw mode
184
+ process.stdout.write('\x1b[?1049h');
185
+ if (process.stdin.isTTY) {
186
+ process.stdin.setRawMode(true);
187
+ }
188
+ this.history.push({ role: 'assistant', content: `Error: ${err.message}` });
189
+ }
190
+
191
+ this.isThinking = false;
192
+ process.stdout.write('\x1b[?25h');
193
+ this.render();
194
+ return;
195
+ }
196
+
197
+ if (key.name === 'backspace') {
198
+ if (this.cursorIdx > 0) {
199
+ this.input = this.input.slice(0, this.cursorIdx - 1) + this.input.slice(this.cursorIdx);
200
+ this.cursorIdx--;
201
+ this.render();
202
+ }
203
+ return;
204
+ }
205
+
206
+ if (key.name === 'left') {
207
+ if (this.cursorIdx > 0) {
208
+ this.cursorIdx--;
209
+ this.render();
210
+ }
211
+ return;
212
+ }
213
+
214
+ if (key.name === 'right') {
215
+ if (this.cursorIdx < this.input.length) {
216
+ this.cursorIdx++;
217
+ this.render();
218
+ }
219
+ return;
220
+ }
221
+
222
+ // Handle normal character inputs
223
+ if (str && str.length === 1 && !key.ctrl && !key.meta) {
224
+ this.input = this.input.slice(0, this.cursorIdx) + str + this.input.slice(this.cursorIdx);
225
+ this.cursorIdx++;
226
+ this.render();
227
+ }
228
+ }
229
+
230
+ exit() {
231
+ process.stdout.write('\x1b[?1049l');
232
+ process.stdout.write('\x1b[?25h');
233
+ console.log('Goodbye!');
234
+ process.exit(0);
235
+ }
236
+ }
237
+
238
+ async function startInteractiveCLI(effort: 'standard' | 'ultracode' = 'standard', codeactMode = false) {
239
+ const tui = new ClosedLoopTUI(effort);
240
+ tui.start();
241
+ }
242
+
243
+ async function checkAndPromptAPIKey(): Promise<string> {
244
+ if (process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN) {
245
+ return process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || '';
246
+ }
247
+
248
+ const configPath = path.join(os.homedir(), '.closed-loop.json');
249
+ if (fs.existsSync(configPath)) {
250
+ try {
251
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
252
+ if (config.apiKey) {
253
+ process.env.ANTHROPIC_API_KEY = config.apiKey;
254
+ return config.apiKey;
255
+ }
256
+ } catch (e) {}
257
+ }
258
+
259
+ console.log('\n\x1b[33m🔑 Anthropic API key not found in environment or local .env file.\x1b[0m');
260
+ console.log('To run this assistant, please configure your API key below.');
261
+
262
+ const rl = readline.createInterface({
263
+ input: process.stdin,
264
+ output: process.stdout
265
+ });
266
+
267
+ const question = (query: string): Promise<string> => {
268
+ return new Promise(resolve => rl.question(query, resolve));
269
+ };
270
+
271
+ const key = await question('\x1b[35mEnter your Anthropic API Key:\x1b[0m ');
272
+ rl.close();
273
+
274
+ const trimmedKey = key.trim();
275
+ if (!trimmedKey) {
276
+ console.error('\x1b[31mError: API Key cannot be empty.\x1b[0m');
277
+ process.exit(1);
278
+ }
279
+
280
+ try {
281
+ fs.writeFileSync(configPath, JSON.stringify({ apiKey: trimmedKey }, null, 2), 'utf8');
282
+ console.log(`\x1b[32m✔ Saved API key to ${configPath}\x1b[0m\n`);
283
+ } catch (err: any) {
284
+ console.warn(`\x1b[33mWarning: Could not save API key to config: ${err.message}\x1b[0m`);
285
+ }
286
+
287
+ process.env.ANTHROPIC_API_KEY = trimmedKey;
288
+ return trimmedKey;
289
+ }
290
+
291
+ async function main() {
292
+ // Ensure API Key is configured before running any agent logic
293
+ await checkAndPromptAPIKey();
294
+
295
+ const args = process.argv.slice(2);
296
+
297
+ // Parse codeact option
298
+ let codeactMode = false;
299
+ const codeactIdx = args.findIndex(arg => arg === '--codeact' || arg === '--openhands');
300
+ if (codeactIdx !== -1) {
301
+ codeactMode = true;
302
+ args.splice(codeactIdx, 1);
303
+ }
304
+
305
+ // Parse effort option
306
+ let effort: 'standard' | 'ultracode' = 'standard';
307
+ const effortIdx = args.findIndex(arg => arg === '--effort' || arg === '-e');
308
+ if (effortIdx !== -1 && effortIdx + 1 < args.length) {
309
+ const val = args[effortIdx + 1].toLowerCase();
310
+ if (val === 'ultracode' || val === 'max') {
311
+ effort = 'ultracode';
312
+ }
313
+ // Remove --effort and its value from args
314
+ args.splice(effortIdx, 2);
315
+ } else {
316
+ const envEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL || 'standard';
317
+ if (envEffort === 'max' || envEffort === 'ultracode') {
318
+ effort = 'ultracode';
319
+ }
320
+ }
321
+
322
+ if (args.length > 0) {
323
+ if (args[0] === '--telegram') {
324
+ try {
325
+ console.log(`\n\x1b[33m[Running Standalone Telegram ChatOps Mode]\x1b[0m`);
326
+ startTelegramBot();
327
+ return;
328
+ } catch (err: any) {
329
+ console.error('\n\x1b[31m[Telegram ChatOps Failed]:\x1b[0m', err.message);
330
+ process.exit(1);
331
+ }
332
+ }
333
+
334
+ if (args[0] === '--dashboard') {
335
+ try {
336
+ console.log(`\n\x1b[33m[Running Standalone Dashboard Monitor Mode]\x1b[0m`);
337
+ startDashboardServer();
338
+ return;
339
+ } catch (err: any) {
340
+ console.error('\n\x1b[31m[Dashboard Failed]:\x1b[0m', err.message);
341
+ process.exit(1);
342
+ }
343
+ }
344
+
345
+ console.log(`Unknown option: ${args[0]}. Running interactive session...`);
346
+ await startInteractiveCLI(effort, codeactMode);
347
+ } else {
348
+ // Start interactive session
349
+ await startInteractiveCLI(effort, codeactMode);
350
+ }
351
+ }
352
+
353
+ main().catch(err => {
354
+ console.error('Fatal error:', err);
355
+ process.exit(1);
356
+ });