sam-coder-cli 1.0.68 → 2.0.0

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/bin/agi-cli.js CHANGED
@@ -12,6 +12,9 @@ const execAsync = util.promisify(exec);
12
12
  // Import AGI Animation module
13
13
  const { runAGIAnimation } = require('./agi-animation.js');
14
14
 
15
+ // Import Brainstorm Core module
16
+ const brainstormCore = require('./core');
17
+
15
18
  // Configuration
16
19
  const CONFIG_PATH = path.join(os.homedir(), '.sam-coder-config.json');
17
20
  let OPENROUTER_API_KEY;
@@ -67,43 +70,51 @@ const tools = [
67
70
  items: {
68
71
  type: 'object',
69
72
  properties: {
70
- type: {
71
- type: 'string',
72
- enum: ['replace', 'insert', 'delete'],
73
+ type: {
74
+ type: 'string',
75
+ enum: ['replace', 'insert', 'delete', 'search_replace'],
73
76
  description: 'Type of edit operation'
74
77
  },
75
- startLine: {
76
- type: 'number',
77
- description: 'Starting line number (1-based)'
78
+ old_string: {
79
+ type: 'string',
80
+ description: 'Exact string to search for and replace (for search_replace operations)'
81
+ },
82
+ new_string: {
83
+ type: 'string',
84
+ description: 'String to replace old_string with (for search_replace operations)'
78
85
  },
79
- endLine: {
80
- type: 'number',
81
- description: 'Ending line number (1-based, inclusive)'
86
+ startLine: {
87
+ type: 'number',
88
+ description: 'Starting line number (1-based)'
82
89
  },
83
- newText: {
84
- type: 'string',
85
- description: 'New text to insert or replace with'
90
+ endLine: {
91
+ type: 'number',
92
+ description: 'Ending line number (1-based, inclusive)'
86
93
  },
87
- pattern: {
88
- type: 'string',
89
- description: 'Pattern to search for (for replace operations)'
94
+ newText: {
95
+ type: 'string',
96
+ description: 'New text to insert or replace with'
90
97
  },
91
- replacement: {
92
- type: 'string',
93
- description: 'Replacement text (for pattern-based replace)'
98
+ pattern: {
99
+ type: 'string',
100
+ description: 'Pattern to search for (for replace operations)'
94
101
  },
95
- flags: {
96
- type: 'string',
97
- description: 'Regex flags (e.g., "g" for global)'
102
+ replacement: {
103
+ type: 'string',
104
+ description: 'Replacement text (for pattern-based replace)'
98
105
  },
99
- position: {
100
- type: 'string',
106
+ flags: {
107
+ type: 'string',
108
+ description: 'Regex flags (e.g., "g" for global)'
109
+ },
110
+ position: {
111
+ type: 'string',
101
112
  enum: ['start', 'end'],
102
- description: 'Where to insert (only for insert operations)'
113
+ description: 'Where to insert (only for insert operations)'
103
114
  },
104
- line: {
105
- type: 'number',
106
- description: 'Line number to insert at (for line-based insert)'
115
+ line: {
116
+ type: 'number',
117
+ description: 'Line number to insert at (for line-based insert)'
107
118
  }
108
119
  },
109
120
  required: ['type'],
@@ -149,6 +160,14 @@ const tools = [
149
160
  endLine: { type: 'number' }
150
161
  },
151
162
  required: ['startLine', 'endLine']
163
+ },
164
+ {
165
+ properties: {
166
+ type: { const: 'search_replace' },
167
+ old_string: { type: 'string' },
168
+ new_string: { type: 'string' }
169
+ },
170
+ required: ['old_string', 'new_string']
152
171
  }
153
172
  ]
154
173
  }
@@ -214,6 +233,48 @@ INSTRUCTIONS:
214
233
 
215
234
  Always think step by step and explain your reasoning before taking actions that could affect the system.`;
216
235
 
236
+ // System prompt for the AI Assistant when using Engineer Mode
237
+ const ENGINEER_PROMPT = `You are a Senior Software Engineer with 15+ years of experience. You have deep expertise in:
238
+ - Software architecture and design patterns
239
+ - Clean code principles and best practices
240
+ - Test-driven development
241
+ - Performance optimization
242
+ - Security best practices
243
+ - Code review and mentoring
244
+
245
+ TOOLS AVAILABLE:
246
+ 1. readFile - Read the contents of a file
247
+ 2. writeFile - Write content to a file
248
+ 3. editFile - Edit specific parts of a file (use search_replace with old_string/new_string for precise edits)
249
+ 4. runCommand - Execute a shell command
250
+ 5. searchFiles - Search for files using a glob pattern
251
+
252
+ ENGINEER PRINCIPLES:
253
+ 1. **Code Quality First**: Write clean, maintainable, well-documented code
254
+ 2. **Think Before Acting**: Analyze the problem thoroughly before making changes
255
+ 3. **Small, Focused Changes**: Make incremental changes that are easy to review
256
+ 4. **Test Your Work**: Verify changes work as expected before moving on
257
+ 5. **Explain Your Reasoning**: Document why you made certain technical decisions
258
+
259
+ WHEN EDITING FILES:
260
+ - ALWAYS use editFile with search_replace operations
261
+ - Use { "type": "search_replace", "old_string": "exact text to find", "new_string": "replacement text" }
262
+ - The old_string must match EXACTLY including whitespace
263
+ - Make focused, minimal changes
264
+
265
+ WORKFLOW:
266
+ 1. Read and understand the existing code
267
+ 2. Identify the minimal changes needed
268
+ 3. Make changes using precise search_replace operations
269
+ 4. Verify the changes compile/run correctly
270
+ 5. Summarize what was done
271
+
272
+ ENVIRONMENT:
273
+ - OS: ${process.platform}
274
+ - Current directory: ${process.cwd()}
275
+
276
+ You are autonomous - continue working until the task is complete. Use the 'stop' action when finished.`;
277
+
217
278
  // System prompt for the AI Assistant when using legacy function calling (JSON actions)
218
279
  const FUNCTION_CALLING_PROMPT = `You are an autonomous AI agent with advanced problem-solving capabilities. You operate through strategic action sequences to accomplish complex tasks on the user's system. Think like an expert developer and system administrator combined.
219
280
 
@@ -430,9 +491,10 @@ const agentUtils = {
430
491
  },
431
492
 
432
493
  async writeFile(input, maybeContent) {
494
+ let filePath;
433
495
  try {
434
- const filePath = typeof input === 'string' ? input : input?.path;
435
- const content = typeof input === 'string' ? maybeContent : input?.content;
496
+ filePath = typeof input === 'string' ? input : input?.path;
497
+ const content = typeof input === 'string' ? maybeContent : (input?.content ?? input?.contents ?? input?.data);
436
498
  if (!filePath) throw new Error('writeFile: missing path');
437
499
  if (typeof content !== 'string') throw new Error('writeFile: missing content');
438
500
  const dir = path.dirname(filePath);
@@ -440,7 +502,8 @@ const agentUtils = {
440
502
  await fs.writeFile(filePath, content, 'utf-8');
441
503
  return `Successfully wrote to ${filePath}`;
442
504
  } catch (error) {
443
- throw new Error(`Failed to write to file ${filePath}: ${error.message}`);
505
+ const ctx = filePath || (typeof input === 'object' ? input?.path : undefined) || 'unknown path';
506
+ throw new Error(`Failed to write to file ${ctx}: ${error.message}`);
444
507
  }
445
508
  },
446
509
 
@@ -453,7 +516,7 @@ const agentUtils = {
453
516
  // Read the current file content
454
517
  let content = await fs.readFile(targetPath, 'utf-8');
455
518
  const lines = content.split('\n');
456
-
519
+
457
520
  // Process each edit operation
458
521
  for (const op of edits.operations) {
459
522
  switch (op.type) {
@@ -476,7 +539,7 @@ const agentUtils = {
476
539
  lines.push(...content.split('\n'));
477
540
  }
478
541
  break;
479
-
542
+
480
543
  case 'insert':
481
544
  if (op.position === 'start') {
482
545
  lines.unshift(...op.text.split('\n'));
@@ -490,23 +553,44 @@ const agentUtils = {
490
553
  lines.splice(op.line - 1, 0, ...insertLines);
491
554
  }
492
555
  break;
493
-
556
+
494
557
  case 'delete':
495
558
  if (op.startLine < 1 || op.endLine > lines.length) {
496
559
  throw new Error(`Line numbers out of range (1-${lines.length})`);
497
560
  }
498
561
  lines.splice(op.startLine - 1, op.endLine - op.startLine + 1);
499
562
  break;
500
-
563
+
564
+ case 'search_replace':
565
+ // Claude Code style: exact string search and replace
566
+ if (op.old_string === undefined || op.new_string === undefined) {
567
+ throw new Error('search_replace requires old_string and new_string');
568
+ }
569
+ content = lines.join('\n');
570
+ if (!content.includes(op.old_string)) {
571
+ throw new Error(`Could not find exact match for old_string: "${op.old_string.substring(0, 50)}${op.old_string.length > 50 ? '...' : ''}"`);
572
+ }
573
+ // Count occurrences
574
+ const occurrences = content.split(op.old_string).length - 1;
575
+ if (occurrences > 1) {
576
+ console.log(`Warning: Found ${occurrences} occurrences of old_string. Replacing first occurrence only.`);
577
+ }
578
+ // Replace first occurrence (to be safe and predictable like Claude Code)
579
+ content = content.replace(op.old_string, op.new_string);
580
+ // Update lines array
581
+ lines.length = 0;
582
+ lines.push(...content.split('\n'));
583
+ break;
584
+
501
585
  default:
502
586
  throw new Error(`Unknown operation type: ${op.type}`);
503
587
  }
504
588
  }
505
-
589
+
506
590
  // Write the modified content back to the file
507
591
  await fs.writeFile(targetPath, lines.join('\n'), 'utf-8');
508
592
  return `Successfully edited ${targetPath}`;
509
-
593
+
510
594
  } catch (error) {
511
595
  throw new Error(`Failed to edit file ${typeof inputPathOrObj === 'string' ? inputPathOrObj : inputPathOrObj?.path}: ${error.message}`);
512
596
  }
@@ -514,9 +598,54 @@ const agentUtils = {
514
598
 
515
599
  async runCommand(input) {
516
600
  try {
517
- const command = typeof input === 'string' ? input : input?.command;
518
- if (!command) throw new Error('runCommand: missing command');
519
- const { stdout, stderr } = await execAsync(command, { cwd: process.cwd() });
601
+ const isObj = typeof input === 'object' && input !== null;
602
+ let command = !isObj ? input : (input.command ?? null);
603
+ const cmd = isObj ? (input.cmd ?? input.program ?? null) : null;
604
+ const args = isObj ? (input.args ?? input.params ?? null) : null;
605
+ const script = isObj ? (input.script ?? null) : null;
606
+ const shell = isObj ? (input.shell ?? (input.powershell ? 'powershell.exe' : (input.bash ? 'bash' : undefined))) : undefined;
607
+ const cwdRaw = isObj ? input.cwd : undefined;
608
+ const envRaw = isObj ? input.env : undefined;
609
+ const timeout = isObj && typeof input.timeout === 'number' ? input.timeout : undefined;
610
+
611
+ const quoteArg = (a) => {
612
+ if (a == null) return '';
613
+ const s = String(a);
614
+ return /\s|["']/g.test(s) ? '"' + s.replace(/"/g, '\\"') + '"' : s;
615
+ };
616
+
617
+ // Build command from arrays or fields if not provided directly
618
+ if (!command && cmd) {
619
+ if (Array.isArray(cmd)) {
620
+ command = cmd.map(quoteArg).join(' ');
621
+ } else if (typeof cmd === 'string') {
622
+ command = cmd;
623
+ }
624
+ }
625
+ if (Array.isArray(command)) {
626
+ command = command.map(quoteArg).join(' ');
627
+ }
628
+ if (command && Array.isArray(args) && args.length) {
629
+ command = `${command} ${args.map(quoteArg).join(' ')}`;
630
+ }
631
+ if (script && !command) {
632
+ // If only a script is provided, run it directly under the selected shell
633
+ command = String(script);
634
+ }
635
+
636
+ if (!command || typeof command !== 'string' || command.trim().length === 0) {
637
+ throw new Error('runCommand: missing command');
638
+ }
639
+
640
+ // Resolve cwd
641
+ let cwd = process.cwd();
642
+ if (typeof cwdRaw === 'string' && cwdRaw.trim().length > 0) {
643
+ cwd = path.isAbsolute(cwdRaw) ? cwdRaw : path.join(process.cwd(), cwdRaw);
644
+ }
645
+ // Merge env
646
+ const env = envRaw && typeof envRaw === 'object' ? { ...process.env, ...envRaw } : undefined;
647
+
648
+ const { stdout, stderr } = await execAsync(command, { cwd, env, timeout, shell });
520
649
  if (stderr) {
521
650
  console.error('Command stderr:', stderr);
522
651
  }
@@ -614,7 +743,7 @@ function extractJsonFromMarkdown(text) {
614
743
  // Try to find a markdown code block with JSON content (case insensitive)
615
744
  const codeBlockRegex = /```(?:json|JSON)\s*([\s\S]*?)\s*```/i;
616
745
  const match = text.match(codeBlockRegex);
617
-
746
+
618
747
  if (match && match[1]) {
619
748
  try {
620
749
  const jsonStr = match[1].trim();
@@ -626,7 +755,7 @@ function extractJsonFromMarkdown(text) {
626
755
  // ignore
627
756
  }
628
757
  }
629
-
758
+
630
759
  // If no code block found, look for JSON-like patterns in the text
631
760
  const jsonPatterns = [
632
761
  // Look for objects that start with { and end with }
@@ -634,7 +763,7 @@ function extractJsonFromMarkdown(text) {
634
763
  // Look for arrays that start with [ and end with ]
635
764
  /\[[\s\S]*?\]/g
636
765
  ];
637
-
766
+
638
767
  for (const pattern of jsonPatterns) {
639
768
  const matches = text.match(pattern);
640
769
  if (matches) {
@@ -652,7 +781,7 @@ function extractJsonFromMarkdown(text) {
652
781
  }
653
782
  }
654
783
  }
655
-
784
+
656
785
  // If still no valid JSON found, try to parse the entire text as JSON
657
786
  try {
658
787
  const trimmed = text.trim();
@@ -662,7 +791,7 @@ function extractJsonFromMarkdown(text) {
662
791
  } catch (error) {
663
792
  // Last resort failed
664
793
  }
665
-
794
+
666
795
  return null;
667
796
  }
668
797
 
@@ -928,24 +1057,24 @@ function extractArgsJson(text) {
928
1057
  // Prefer fenced code block
929
1058
  const fence = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
930
1059
  if (fence && fence[1]) {
931
- try { return JSON.parse(fence[1].trim()); } catch (_) {}
1060
+ try { return JSON.parse(fence[1].trim()); } catch (_) { }
932
1061
  }
933
1062
  // Try straightforward parse
934
1063
  const trimmed = text.trim();
935
1064
  if ((trimmed.startsWith('{') && trimmed.includes('}')) || (trimmed.startsWith('[') && trimmed.includes(']'))) {
936
- try { return JSON.parse(trimmed); } catch (_) {}
1065
+ try { return JSON.parse(trimmed); } catch (_) { }
937
1066
  }
938
1067
  // Fallback: find first {...} minimally
939
1068
  const braceRegex = /\{[\s\S]*?\}/g;
940
1069
  const m = braceRegex.exec(text);
941
1070
  if (m && m[0]) {
942
- try { return JSON.parse(m[0]); } catch (_) {}
1071
+ try { return JSON.parse(m[0]); } catch (_) { }
943
1072
  }
944
1073
  // Fallback: find first [...] minimally
945
1074
  const arrRegex = /\[[\s\S]*?\]/g;
946
1075
  const a = arrRegex.exec(text);
947
1076
  if (a && a[0]) {
948
- try { return JSON.parse(a[0]); } catch (_) {}
1077
+ try { return JSON.parse(a[0]); } catch (_) { }
949
1078
  }
950
1079
  return {};
951
1080
  }
@@ -1115,11 +1244,11 @@ async function processQueryWithTools(query, conversation = [], currentModel) {
1115
1244
 
1116
1245
  async function handleToolCalls(toolCalls, messages) {
1117
1246
  const results = [];
1118
-
1247
+
1119
1248
  for (const toolCall of toolCalls) {
1120
1249
  const functionName = toolCall.function.name;
1121
1250
  let args;
1122
-
1251
+
1123
1252
  try {
1124
1253
  args = JSON.parse(toolCall.function.arguments);
1125
1254
  } catch (error) {
@@ -1132,14 +1261,14 @@ async function handleToolCalls(toolCalls, messages) {
1132
1261
  });
1133
1262
  continue;
1134
1263
  }
1135
-
1264
+
1136
1265
  console.log(`🔧 Executing ${functionName} with args:`, args);
1137
-
1266
+
1138
1267
  try {
1139
1268
  if (!agentUtils[functionName]) {
1140
1269
  throw new Error(`Tool '${functionName}' not found`);
1141
1270
  }
1142
-
1271
+
1143
1272
  let result;
1144
1273
  if (Array.isArray(args)) {
1145
1274
  result = await agentUtils[functionName](...args);
@@ -1151,9 +1280,9 @@ async function handleToolCalls(toolCalls, messages) {
1151
1280
  result = await agentUtils[functionName]();
1152
1281
  }
1153
1282
  console.log('✅ Tool executed successfully');
1154
-
1283
+
1155
1284
  const resultContent = typeof result === 'string' ? result : JSON.stringify(result);
1156
-
1285
+
1157
1286
  results.push({
1158
1287
  tool_call_id: toolCall.id,
1159
1288
  role: 'tool',
@@ -1162,25 +1291,25 @@ async function handleToolCalls(toolCalls, messages) {
1162
1291
  });
1163
1292
  } catch (error) {
1164
1293
  console.error('❌ Tool execution failed:', error);
1165
-
1294
+
1166
1295
  results.push({
1167
1296
  tool_call_id: toolCall.id,
1168
1297
  role: 'tool',
1169
1298
  name: functionName,
1170
- content: JSON.stringify({
1299
+ content: JSON.stringify({
1171
1300
  error: error.message,
1172
1301
  stack: process.env.DEBUG ? error.stack : undefined
1173
1302
  })
1174
1303
  });
1175
1304
  }
1176
1305
  }
1177
-
1306
+
1178
1307
  return results;
1179
1308
  }
1180
1309
 
1181
1310
  async function executeAction(action) {
1182
1311
  const { type, data } = action;
1183
-
1312
+
1184
1313
  switch (type) {
1185
1314
  case 'read':
1186
1315
  return await agentUtils.readFile(data.path);
@@ -1219,16 +1348,16 @@ async function executeAction(action) {
1219
1348
  async function processQuery(query, conversation = [], currentModel) {
1220
1349
  try {
1221
1350
  const userMessage = { role: 'user', content: query };
1222
-
1351
+
1223
1352
  // Use existing conversation (which should already have system prompt from chat function)
1224
1353
  let messages = [...conversation, userMessage];
1225
-
1354
+
1226
1355
  let actionCount = 0;
1227
1356
  const MAX_ACTIONS = 10;
1228
1357
  let finalResponse = '';
1229
1358
 
1230
1359
  ui.startThinking();
1231
-
1360
+
1232
1361
  while (actionCount < MAX_ACTIONS) {
1233
1362
  const responseObj = await callOpenRouter(messages, currentModel, true);
1234
1363
  const assistantMessage = responseObj.choices[0].message;
@@ -1248,55 +1377,55 @@ async function processQuery(query, conversation = [], currentModel) {
1248
1377
  }
1249
1378
  normalizeToolCallsFromMessage(assistantMessage);
1250
1379
  messages.push(assistantMessage);
1251
-
1380
+
1252
1381
  const actionData = extractJsonFromMarkdown(assistantMessage.content);
1253
-
1382
+
1254
1383
  // If no valid action found, treat as final response
1255
1384
  if (!actionData || !actionData.type) {
1256
1385
  finalResponse = assistantMessage.content;
1257
1386
  break;
1258
1387
  }
1259
-
1388
+
1260
1389
  // If stop action, break with the reasoning or content
1261
1390
  if (actionData.type === 'stop') {
1262
1391
  finalResponse = actionData.reasoning || assistantMessage.content;
1263
1392
  break;
1264
1393
  }
1265
-
1394
+
1266
1395
  actionCount++;
1267
1396
  console.log(`🔧 Executing action ${actionCount}: ${actionData.type}`);
1268
1397
  if (actionData.reasoning) {
1269
1398
  console.log(`📝 Reasoning: ${actionData.reasoning}`);
1270
1399
  }
1271
-
1400
+
1272
1401
  try {
1273
1402
  const result = await executeAction(actionData);
1274
1403
  console.log('✅ Action executed successfully');
1275
-
1404
+
1276
1405
  // Add action result to conversation
1277
1406
  const resultMessage = {
1278
- role: 'user',
1407
+ role: 'user',
1279
1408
  content: `Action result (${actionData.type}): ${result}`
1280
1409
  };
1281
1410
  messages.push(resultMessage);
1282
-
1411
+
1283
1412
  } catch (error) {
1284
1413
  console.error('❌ Action execution failed:', error.message);
1285
-
1414
+
1286
1415
  // Add error result to conversation
1287
1416
  const errorMessage = {
1288
- role: 'user',
1417
+ role: 'user',
1289
1418
  content: `Action failed (${actionData.type}): ${error.message}`
1290
1419
  };
1291
1420
  messages.push(errorMessage);
1292
1421
  }
1293
1422
  }
1294
-
1423
+
1295
1424
  // If we hit max actions, get a final response
1296
1425
  if (actionCount >= MAX_ACTIONS && !finalResponse) {
1297
1426
  const finalMsg = { role: 'user', content: 'Please provide a final summary of what was accomplished.' };
1298
1427
  messages.push(finalMsg);
1299
-
1428
+
1300
1429
  const finalResponseObj = await callOpenRouter(messages, currentModel, true);
1301
1430
  const finalAssistantMessage = finalResponseObj.choices[0].message;
1302
1431
  if (finalAssistantMessage && typeof finalAssistantMessage.content === 'string') {
@@ -1326,22 +1455,27 @@ async function processQuery(query, conversation = [], currentModel) {
1326
1455
  }
1327
1456
  }
1328
1457
 
1329
- async function chat(rl, useToolCalling, initialModel) {
1458
+ async function chat(rl, mode, initialModel) {
1330
1459
  let currentModel = initialModel;
1331
1460
  const conversation = [];
1332
-
1333
- // Initialize conversation with appropriate system prompt
1334
- if (useToolCalling) {
1461
+
1462
+ // Initialize conversation with appropriate system prompt based on mode
1463
+ if (mode === 'engineer') {
1464
+ conversation.push({ role: 'system', content: ENGINEER_PROMPT });
1465
+ } else if (mode === 'tool') {
1335
1466
  conversation.push({ role: 'system', content: TOOL_CALLING_PROMPT });
1336
1467
  } else {
1337
1468
  conversation.push({ role: 'system', content: FUNCTION_CALLING_PROMPT });
1338
1469
  }
1339
-
1470
+
1471
+ // Determine if we should use tool calling API (both 'tool' and 'engineer' modes use tools)
1472
+ const useToolCalling = (mode === 'tool' || mode === 'engineer');
1473
+
1340
1474
  console.log('Type your message, or "exit" to quit.');
1341
-
1475
+
1342
1476
  rl.setPrompt('> ');
1343
1477
  rl.prompt();
1344
-
1478
+
1345
1479
  rl.on('line', async (input) => {
1346
1480
  if (input.toLowerCase().startsWith('/model')) {
1347
1481
  const newModel = input.split(' ')[1];
@@ -1394,11 +1528,99 @@ async function chat(rl, useToolCalling, initialModel) {
1394
1528
  return;
1395
1529
  }
1396
1530
 
1531
+ // Brainstorm command: /brainstorm <project-name> <description>
1532
+ if (input.toLowerCase().startsWith('/brainstorm')) {
1533
+ const parts = input.substring('/brainstorm'.length).trim();
1534
+ if (!parts) {
1535
+ console.log('Usage: /brainstorm <project-name> "<description>"');
1536
+ console.log('Example: /brainstorm "My Project" "A cool project that does things"');
1537
+ rl.prompt();
1538
+ return;
1539
+ }
1540
+
1541
+ // Parse project name and description
1542
+ const match = parts.match(/^"?([^"]+)"?\s+"?([^"]+)"?$/) || parts.match(/^(\S+)\s+(.+)$/);
1543
+ if (!match) {
1544
+ console.log('Usage: /brainstorm <project-name> "<description>"');
1545
+ rl.prompt();
1546
+ return;
1547
+ }
1548
+
1549
+ const projectName = match[1].trim();
1550
+ const projectDescription = match[2].trim();
1551
+ const outputDir = path.join(process.cwd(), projectName.replace(/\s+/g, '-').toLowerCase());
1552
+
1553
+ try {
1554
+ ui.showInfo(`Creating brainstorm session for "${projectName}"...`);
1555
+ const session = await brainstormCore.quickStart(
1556
+ projectName,
1557
+ projectDescription,
1558
+ outputDir,
1559
+ ['CLAUDE-1'] // Single agent by default
1560
+ );
1561
+
1562
+ ui.showSuccess(`✅ Brainstorm session created!`);
1563
+ console.log(` Session ID: ${session.id}`);
1564
+ console.log(` Output: ${outputDir}`);
1565
+ console.log(` Files generated: ${Object.keys(session.fileVersions).length}`);
1566
+ console.log(`\n Generated files:`);
1567
+ Object.keys(session.fileVersions).forEach(f => console.log(` - ${f}`));
1568
+ console.log(`\n Use "/finish <summary>" when done to complete the session.`);
1569
+
1570
+ // Store current session path for /finish command
1571
+ global.currentSessionDir = outputDir;
1572
+ } catch (error) {
1573
+ ui.showError(`Failed to create brainstorm: ${error.message}`);
1574
+ }
1575
+
1576
+ rl.prompt();
1577
+ return;
1578
+ }
1579
+
1580
+ // Finish command: /finish <summary>
1581
+ if (input.toLowerCase().startsWith('/finish')) {
1582
+ const summary = input.substring('/finish'.length).trim();
1583
+
1584
+ if (!global.currentSessionDir) {
1585
+ console.log('No active brainstorm session. Use /brainstorm first.');
1586
+ rl.prompt();
1587
+ return;
1588
+ }
1589
+
1590
+ if (!summary) {
1591
+ console.log('Usage: /finish "<summary of what was accomplished>"');
1592
+ rl.prompt();
1593
+ return;
1594
+ }
1595
+
1596
+ try {
1597
+ ui.showInfo('Completing brainstorm session...');
1598
+ const result = await brainstormCore.finishBrainstorm({
1599
+ sessionDir: global.currentSessionDir,
1600
+ summary,
1601
+ actor: 'CLAUDE-1'
1602
+ });
1603
+
1604
+ if (result.success) {
1605
+ ui.showSuccess(`✅ Session completed!`);
1606
+ console.log(` Summary: ${summary}`);
1607
+ global.currentSessionDir = null;
1608
+ } else {
1609
+ ui.showError(`Failed: ${result.errors.join(', ')}`);
1610
+ }
1611
+ } catch (error) {
1612
+ ui.showError(`Failed to finish session: ${error.message}`);
1613
+ }
1614
+
1615
+ rl.prompt();
1616
+ return;
1617
+ }
1618
+
1397
1619
  if (input.toLowerCase() === 'exit') {
1398
1620
  rl.close();
1399
1621
  return;
1400
1622
  }
1401
-
1623
+
1402
1624
  // Direct Harmony tool-call execution from user input (bypass model)
1403
1625
  try {
1404
1626
  const seg = parseSegmentedTranscript(input);
@@ -1429,16 +1651,16 @@ async function chat(rl, useToolCalling, initialModel) {
1429
1651
  } catch (e) {
1430
1652
  // Fall through to normal processing if parsing/execution fails
1431
1653
  }
1432
-
1433
- const result = useToolCalling
1654
+
1655
+ const result = useToolCalling
1434
1656
  ? await processQueryWithTools(input, conversation, currentModel)
1435
1657
  : await processQuery(input, conversation, currentModel);
1436
1658
  ui.stopThinking();
1437
1659
  ui.showResponse(result.response);
1438
-
1660
+
1439
1661
  conversation.length = 0;
1440
1662
  result.conversation.forEach(msg => conversation.push(msg));
1441
-
1663
+
1442
1664
  rl.prompt();
1443
1665
  }).on('close', () => {
1444
1666
  ui.showResponse('Goodbye!');
@@ -1448,8 +1670,15 @@ async function chat(rl, useToolCalling, initialModel) {
1448
1670
 
1449
1671
  function askForMode(rl) {
1450
1672
  return new Promise((resolve) => {
1451
- rl.question('Select mode (1 for tool calling, 2 for function calling): ', (answer) => {
1452
- resolve(answer.trim() === '1');
1673
+ rl.question('Select mode (1 for tool calling, 2 for function calling, 3 for engineer): ', (answer) => {
1674
+ const mode = answer.trim();
1675
+ if (mode === '3') {
1676
+ resolve('engineer');
1677
+ } else if (mode === '2') {
1678
+ resolve('function');
1679
+ } else {
1680
+ resolve('tool');
1681
+ }
1453
1682
  });
1454
1683
  });
1455
1684
  }
@@ -1532,11 +1761,11 @@ async function start() {
1532
1761
 
1533
1762
  // Check if animation should be shown (can be disabled via config)
1534
1763
  const showAnimation = config.showAnimation !== false; // Default to true
1535
-
1764
+
1536
1765
  if (showAnimation && !process.env.SKIP_ANIMATION) {
1537
1766
  // Run the epic AGI awakening animation
1538
1767
  await runAGIAnimation();
1539
-
1768
+
1540
1769
  // Small pause after animation
1541
1770
  await new Promise(resolve => setTimeout(resolve, 500));
1542
1771
  }
@@ -1545,11 +1774,13 @@ async function start() {
1545
1774
  console.log('Select Mode:');
1546
1775
  console.log('1. Tool Calling (for models that support it)');
1547
1776
  console.log('2. Function Calling (legacy)');
1548
-
1549
- const useToolCalling = await askForMode(rl);
1550
- ui.showResponse(`\nStarting in ${useToolCalling ? 'Tool Calling' : 'Function Calling'} mode...\n`);
1551
-
1552
- await chat(rl, useToolCalling, MODEL);
1777
+ console.log('3. Engineer Mode (autonomous engineering with Claude Code style editing)');
1778
+
1779
+ const selectedMode = await askForMode(rl);
1780
+ const modeNames = { 'tool': 'Tool Calling', 'function': 'Function Calling', 'engineer': 'Engineer' };
1781
+ ui.showResponse(`\nStarting in ${modeNames[selectedMode]} mode...\n`);
1782
+
1783
+ await chat(rl, selectedMode, MODEL);
1553
1784
  } catch (error) {
1554
1785
  ui.showError(error);
1555
1786
  rl.close();