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