sam-coder-cli 1.0.69 → 2.0.1

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.
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fetch = require('node-fetch');
4
+ const readline = require('readline');
5
+ const path = require('path');
6
+ const { CLIAgentUtils } = require('../src/cliAgentUtils');
7
+
8
+ // Initialize CLIAgentUtils
9
+ const agentUtils = new CLIAgentUtils();
10
+
11
+ // OpenRouter configuration
12
+ const OPENROUTER_API_KEY = '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';
13
+ const MODEL = 'deepseek/deepseek-chat-v3-0324:free';
14
+ const API_BASE_URL = 'https://openrouter.ai/api/v1';
15
+
16
+ // System prompt matching the VS Code extension
17
+ const SYSTEM_PROMPT = `You are a VS Code AI Assistant with agency capabilities. You can perform actions on the user's workspace.
18
+
19
+ ENVIRONMENT CONTEXT:
20
+ - OS: ${process.platform}
21
+ - Working Directory: ${process.cwd()}
22
+
23
+ When you need to perform actions, respond with JSON in the following format:
24
+ \`\`\`json
25
+ {
26
+ "thoughts": "Your reasoning about what needs to be done",
27
+ "actions": [
28
+ {
29
+ "type": "read|write|search|command|analyze|execute|stop",
30
+ "data": { ... action specific data ... }
31
+ }
32
+ ]
33
+ }
34
+ \`\`\`
35
+
36
+ Action types and their data:
37
+ - read: { "path": "relative/or/absolute/path" }
38
+ - write: { "path": "relative/or/absolute/path", "content": "file content" }
39
+ - search: { "type": "files", "pattern": "glob pattern" } or { "type": "text", "text": "search text" }
40
+ - command: { "command": "command string to execute in terminal" }
41
+ - execute: { "language": "js|python|bash|...", "code": "code to execute" }
42
+ - analyze: { "code": "code to analyze", "question": "what you want to analyze" }
43
+ - browse: { "query": "search query", "numResults": 5 } (free web search using DuckDuckGo, optional numResults)
44
+ - edit: {
45
+ "path": "relative/or/absolute/path",
46
+ "edits": {
47
+ "operations": [
48
+ { "type": "replace", "startLine": 10, "endLine": 15, "newText": "new code here" },
49
+ { "type": "replace", "pattern": "oldFunction\\(\\)", "replacement": "newFunction()", "flags": "g" },
50
+ { "type": "insert", "line": 20, "text": "new line of code here" },
51
+ { "type": "insert", "position": "start", "text": "// Header comment" },
52
+ { "type": "insert", "position": "end", "text": "// Footer comment" },
53
+ { "type": "delete", "startLine": 25, "endLine": 30 }
54
+ ]
55
+ }
56
+ } (edit specific parts of an existing file)
57
+ - stop: {} (use this to indicate you're done with the task and no more actions are needed)
58
+
59
+ By default, you will continue to take actions in a loop until you decide to stop with the 'stop' action type.
60
+ Always wrap your JSON in markdown code blocks with the json language specifier.
61
+ When executing code or commands that might be potentially harmful, explain what the code does before executing it.`;
62
+
63
+ async function callOpenRouter(messages, toolCalls = null) {
64
+ const body = {
65
+ model: MODEL,
66
+ messages: messages.map(msg => ({
67
+ role: msg.role,
68
+ content: msg.content || '',
69
+ ...(msg.name && { name: msg.name })
70
+ })),
71
+ ...(!toolCalls && { tools }),
72
+ ...(toolCalls && { tool_calls: toolCalls })
73
+ };
74
+
75
+ // Remove any undefined values
76
+ const cleanBody = JSON.parse(JSON.stringify(body));
77
+
78
+ try {
79
+ console.log('Sending request to OpenRouter API...');
80
+ console.log('Model:', MODEL);
81
+ console.log('Headers:', {
82
+ 'Content-Type': 'application/json',
83
+ 'Authorization': 'Bearer ' + (OPENROUTER_API_KEY ? '***' + OPENROUTER_API_KEY.slice(-4) : 'undefined'),
84
+ 'HTTP-Referer': 'https://github.com/yourusername/agi-cli',
85
+ 'X-Title': 'AGI-CLI'
86
+ });
87
+
88
+ console.log('Request body:', JSON.stringify(cleanBody, null, 2));
89
+
90
+ const response = await fetch(`${API_BASE_URL}/chat/completions`, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ 'Authorization': `Bearer ${OPENROUTER_API_KEY}`,
95
+ 'HTTP-Referer': 'https://github.com/yourusername/agi-cli',
96
+ 'X-Title': 'AGI-CLI'
97
+ },
98
+ body: JSON.stringify(cleanBody)
99
+ });
100
+
101
+ console.log('Response status:', response.status, response.statusText);
102
+
103
+ if (!response.ok) {
104
+ let errorText;
105
+ try {
106
+ const errorData = await response.text();
107
+ console.error('Error response body:', errorData);
108
+ const parsedError = JSON.parse(errorData);
109
+ errorText = parsedError.error?.message || JSON.stringify(parsedError);
110
+ } catch (e) {
111
+ errorText = await response.text();
112
+ }
113
+ throw new Error(`API request failed with status ${response.status}: ${errorText}`);
114
+ }
115
+
116
+ const responseData = await response.json();
117
+ console.log('API Response:', JSON.stringify(responseData, null, 2));
118
+ return responseData;
119
+ } catch (error) {
120
+ console.error('Error in callOpenRouter:', error);
121
+ throw new Error(`Failed to call OpenRouter API: ${error.message}`);
122
+ }
123
+ }
124
+
125
+ async function handleToolCalls(toolCalls, messages) {
126
+ const results = [];
127
+
128
+ for (const toolCall of toolCalls) {
129
+ const { name, arguments: args } = toolCall.function;
130
+ let result;
131
+
132
+ try {
133
+ switch (name) {
134
+ case 'readFile':
135
+ result = await agentUtils.readFile(args.path);
136
+ break;
137
+ case 'writeFile':
138
+ await agentUtils.writeFile(args.path, args.content);
139
+
140
+ results.push({
141
+ tool_call_id: toolCall.id,
142
+ role: 'tool',
143
+ name: functionName,
144
+ content: JSON.stringify(result)
145
+ });
146
+ } catch (error) {
147
+ console.error('❌ Tool execution failed:', error);
148
+
149
+ results.push({
150
+ tool_call_id: toolCall.id,
151
+ role: 'tool',
152
+ name: functionName,
153
+ content: JSON.stringify({ error: error.message })
154
+ });
155
+ }
156
+ }
157
+
158
+ return results;
159
+ }
160
+
161
+ async function extractJsonFromMarkdown(text) {
162
+ // Try to find a markdown code block with JSON content
163
+ const codeBlockRegex = /```json\n([\s\S]*?)\n```/g;
164
+ const match = codeBlockRegex.exec(text);
165
+
166
+ if (match) {
167
+ try {
168
+ return JSON.parse(match[1]);
169
+ } catch (error) {
170
+ console.error('Error parsing JSON from markdown:', error);
171
+ }
172
+ }
173
+
174
+ // If no code block, try to parse the entire text as JSON
175
+ try {
176
+ return JSON.parse(text);
177
+ } catch (error) {
178
+ console.error('Error parsing JSON:', error);
179
+ return null;
180
+ }
181
+ }
182
+
183
+ async function processQuery(query, conversation = []) {
184
+ try {
185
+ // Add user message to conversation
186
+ const userMessage = { role: 'user', content: query };
187
+ const messages = [...conversation, userMessage];
188
+
189
+ // Add system message if this is the first message
190
+ if (conversation.length === 0) {
191
+ messages.unshift({
192
+ role: 'system',
193
+ content: SYSTEM_PROMPT
194
+ });
195
+ }
196
+
197
+ let shouldContinue = true;
198
+ let iteration = 0;
199
+ const maxIterations = 10; // Prevent infinite loops
200
+ let finalResponse = '';
201
+
202
+ while (shouldContinue && iteration < maxIterations) {
203
+ iteration++;
204
+ console.log('🤖 Thinking...');
205
+
206
+ const response = await callOpenRouter(messages);
207
+ const assistantMessage = response.choices[0].message;
208
+
209
+ // Add assistant's message to the conversation
210
+ messages.push(assistantMessage);
211
+
212
+ // Check if the response contains actions
213
+ const actionData = await extractJsonFromMarkdown(assistantMessage.content);
214
+
215
+ if (actionData && actionData.actions && Array.isArray(actionData.actions)) {
216
+ console.log(`🔧 Processing ${actionData.actions.length} actions...`);
217
+ if (actionData.thoughts) {
218
+ console.log(`💭 ${actionData.thoughts}`);
219
+ }
220
+
221
+ const actionResults = [];
222
+
223
+ for (const action of actionData.actions) {
224
+ console.log(`🛠️ Executing action: ${action.type}`);
225
+
226
+ // Handle stop action
227
+ if (action.type === 'stop') {
228
+ console.log('🛑 Stop action received, ending action processing');
229
+ shouldContinue = false;
230
+ finalResponse = 'Task completed successfully.';
231
+ break;
232
+ }
233
+
234
+ try {
235
+ let result;
236
+
237
+ switch (action.type) {
238
+ case 'read':
239
+ result = await agentUtils.readFile(action.data.path);
240
+ break;
241
+
242
+ case 'write':
243
+ await agentUtils.writeFile(action.data.path, action.data.content);
244
+ result = `Successfully wrote to ${action.data.path}`;
245
+ break;
246
+
247
+ case 'command':
248
+ result = await agentUtils.runCommand(action.data.command);
249
+ break;
250
+
251
+ case 'search':
252
+ if (action.data.type === 'files') {
253
+ result = await agentUtils.searchFiles(action.data.pattern);
254
+ } else {
255
+ result = 'Text search not yet implemented';
256
+ }
257
+ break;
258
+
259
+ default:
260
+ result = `Action type '${action.type}' is not supported yet.`;
261
+ }
262
+
263
+ actionResults.push({
264
+ type: action.type,
265
+ success: true,
266
+ result: result
267
+ });
268
+
269
+ console.log(`✅ Action ${action.type} completed successfully`);
270
+
271
+ } catch (error) {
272
+ console.error(`❌ Action ${action.type} failed:`, error);
273
+ actionResults.push({
274
+ type: action.type,
275
+ success: false,
276
+ error: error.message
277
+ });
278
+ }
279
+ }
280
+
281
+ // Add action results to the conversation
282
+ messages.push({
283
+ role: 'system',
284
+ content: `Action results:\n\`\`\`json\n${JSON.stringify(actionResults, null, 2)}\n\`\`\`\n` +
285
+ `Based on these results, determine what to do next. You can:\n` +
286
+ `1. Continue with more actions by returning a new JSON with "actions" array\n` +
287
+ `2. Stop the iteration by including an action with "type": "stop" if the task is completed\n` +
288
+ `3. Provide a final response to the user with your findings`
289
+ });
290
+
291
+ } else {
292
+ // No actions, this is a regular response
293
+ shouldContinue = false;
294
+ finalResponse = assistantMessage.content;
295
+ }
296
+ }
297
+
298
+ // If we hit max iterations, add a note
299
+ if (iteration >= maxIterations) {
300
+ finalResponse += '\n\n⚠️ Reached maximum number of iterations. Stopping execution.';
301
+ }
302
+
303
+ return {
304
+ response: finalResponse,
305
+ conversation: messages
306
+ };
307
+
308
+ } catch (error) {
309
+ console.error('Error processing query:', error);
310
+ return {
311
+ response: `Error: ${error.message}`,
312
+ conversation
313
+ };
314
+ }
315
+ }
316
+
317
+ async function chat() {
318
+ const conversation = [];
319
+ console.log('Welcome to AGI-CLI. Type your message, or "exit" to quit.');
320
+
321
+ const rl = readline.createInterface({
322
+ input: process.stdin,
323
+ output: process.stdout,
324
+ prompt: '> '
325
+ });
326
+
327
+ rl.prompt();
328
+
329
+ rl.on('line', async (line) => {
330
+ const input = line.trim();
331
+
332
+ if (input.toLowerCase() === 'exit') {
333
+ rl.close();
334
+ return;
335
+ }
336
+
337
+ if (!input) {
338
+ rl.prompt();
339
+ return;
340
+ }
341
+
342
+ const result = await processQuery(input, conversation);
343
+ console.log(result.response);
344
+ rl.prompt();
345
+ }).on('close', () => {
346
+ console.log('Goodbye!');
347
+ process.exit(0);
348
+ });
349
+ }
350
+
351
+ // Start the chat
352
+ chat();
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Brainstorm Module - Start a New Brainstorm Session
3
+ *
4
+ * Initializes a new brainstorm session and generates all project coordination files.
5
+ * Ported from Python to JavaScript.
6
+ */
7
+
8
+ const fs = require('fs').promises;
9
+ const path = require('path');
10
+ const { ProjectConfig, BrainstormSession } = require('./models');
11
+ const templates = require('./templates');
12
+
13
+ /**
14
+ * File templates mapping
15
+ */
16
+ const FILE_TEMPLATES = {
17
+ 'CLAUDE.md': templates.generateClaudeMd,
18
+ 'PROJECT_STATUS.md': templates.generateProjectStatusMd,
19
+ 'PROGRESS_REPORT.md': templates.generateProgressReportMd,
20
+ 'GETTING_STARTED.md': templates.generateGettingStartedMd,
21
+ 'SELF_IMPROVEMENT.md': templates.generateSelfImprovementMd,
22
+ 'CEO-UPDATES-TODAY.md': templates.generateCeoUpdatesMd,
23
+ 'CODE_OF_CONDUCT_OF_AI_WORKERS.md': templates.generateCodeOfConductMd
24
+ };
25
+
26
+ const MULTI_AGENT_TEMPLATES = {
27
+ 'SECONDARY-AI-CHAT.md': templates.generateSecondaryAiChatMd
28
+ };
29
+
30
+ /**
31
+ * Start a new brainstorm session
32
+ *
33
+ * @param {Object} options
34
+ * @param {ProjectConfig|Object} options.config - Project configuration
35
+ * @param {string} options.outputDir - Directory where files will be generated
36
+ * @param {string[]} options.agents - List of agent identities (e.g., ["CLAUDE-1", "CLAUDE-2"])
37
+ * @returns {Promise<BrainstormSession>}
38
+ */
39
+ async function startBrainstorm({ config, outputDir, agents = ['CLAUDE-1'] }) {
40
+ // Convert to ProjectConfig if needed
41
+ const project = config instanceof ProjectConfig ? config : new ProjectConfig(config);
42
+
43
+ // Create output directory
44
+ await fs.mkdir(outputDir, { recursive: true });
45
+
46
+ // Create new session
47
+ const session = BrainstormSession.create({
48
+ project,
49
+ outputDirectory: outputDir,
50
+ agents
51
+ });
52
+
53
+ const generatedFiles = [];
54
+
55
+ // Generate standard files
56
+ for (const [fileName, templateFn] of Object.entries(FILE_TEMPLATES)) {
57
+ const content = templateFn({ project, agents, session });
58
+ const filePath = path.join(outputDir, fileName);
59
+ await fs.writeFile(filePath, content, 'utf-8');
60
+ generatedFiles.push(fileName);
61
+ session.fileVersions[fileName] = 1;
62
+ }
63
+
64
+ // Generate multi-agent files if more than one agent
65
+ if (agents.length > 1) {
66
+ for (const [fileName, templateFn] of Object.entries(MULTI_AGENT_TEMPLATES)) {
67
+ const content = templateFn({ project, agents, session });
68
+ const filePath = path.join(outputDir, fileName);
69
+ await fs.writeFile(filePath, content, 'utf-8');
70
+ generatedFiles.push(fileName);
71
+ session.fileVersions[fileName] = 1;
72
+ }
73
+ }
74
+
75
+ // Create directory structure if defined
76
+ if (project.directoryStructure && Object.keys(project.directoryStructure).length > 0) {
77
+ await createDirectoryStructure(outputDir, project.directoryStructure);
78
+ }
79
+
80
+ // Add event for file generation
81
+ session.addEvent({
82
+ eventType: 'UPDATED',
83
+ actor: 'BRAINSTORM',
84
+ description: `Generated ${generatedFiles.length} project files`,
85
+ affectedFiles: generatedFiles
86
+ });
87
+
88
+ // Save session state
89
+ await session.save();
90
+
91
+ return session;
92
+ }
93
+
94
+ /**
95
+ * Create directory structure recursively
96
+ */
97
+ async function createDirectoryStructure(baseDir, structure, currentPath = null) {
98
+ const current = currentPath || baseDir;
99
+
100
+ for (const [name, children] of Object.entries(structure)) {
101
+ const itemPath = path.join(current, name);
102
+
103
+ if (typeof children === 'object' && children !== null && Object.keys(children).length > 0) {
104
+ // Directory with children
105
+ await fs.mkdir(itemPath, { recursive: true });
106
+ await createDirectoryStructure(baseDir, children, itemPath);
107
+ } else if (children === null || (typeof children === 'object' && Object.keys(children).length === 0)) {
108
+ // Empty directory
109
+ await fs.mkdir(itemPath, { recursive: true });
110
+ } else {
111
+ // File placeholder
112
+ await fs.mkdir(path.dirname(itemPath), { recursive: true });
113
+ try {
114
+ await fs.access(itemPath);
115
+ } catch {
116
+ await fs.writeFile(itemPath, '', 'utf-8');
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Load an existing session from a directory
124
+ *
125
+ * @param {string} sessionDir - Directory containing SESSION.json
126
+ * @returns {Promise<BrainstormSession|null>}
127
+ */
128
+ async function getSession(sessionDir) {
129
+ const sessionFile = path.join(sessionDir, 'SESSION.json');
130
+
131
+ try {
132
+ await fs.access(sessionFile);
133
+ return await BrainstormSession.load(sessionFile);
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * List all files in a brainstorm session with their versions
141
+ *
142
+ * @param {BrainstormSession} session
143
+ * @returns {Array<Object>}
144
+ */
145
+ async function listFiles(session) {
146
+ const files = [];
147
+
148
+ for (const [fileName, version] of Object.entries(session.fileVersions)) {
149
+ const filePath = path.join(session.outputDirectory, fileName);
150
+ let exists = false;
151
+
152
+ try {
153
+ await fs.access(filePath);
154
+ exists = true;
155
+ } catch { }
156
+
157
+ files.push({
158
+ name: fileName,
159
+ path: filePath,
160
+ version,
161
+ exists
162
+ });
163
+ }
164
+
165
+ return files;
166
+ }
167
+
168
+ /**
169
+ * Quick start function for creating a brainstorm session
170
+ *
171
+ * @param {string} name - Project name
172
+ * @param {string} description - Project description
173
+ * @param {string} outputDir - Output directory
174
+ * @param {string[]} agents - List of agent identities
175
+ * @param {Object} options - Additional ProjectConfig fields
176
+ * @returns {Promise<BrainstormSession>}
177
+ */
178
+ async function quickStart(name, description, outputDir, agents = ['CLAUDE-1'], options = {}) {
179
+ const config = new ProjectConfig({
180
+ name,
181
+ description,
182
+ ...options
183
+ });
184
+
185
+ return startBrainstorm({
186
+ config,
187
+ outputDir,
188
+ agents
189
+ });
190
+ }
191
+
192
+ module.exports = {
193
+ startBrainstorm,
194
+ getSession,
195
+ listFiles,
196
+ quickStart,
197
+ createDirectoryStructure
198
+ };