sam-coder-cli 1.0.44 → 1.0.45

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
@@ -15,8 +15,42 @@ let OPENROUTER_API_KEY;
15
15
  let MODEL = 'deepseek/deepseek-chat-v3-0324:free';
16
16
  let API_BASE_URL = 'https://openrouter.ai/api/v1';
17
17
 
18
+ // Tool/Function definitions for the AI
19
+ // Knowledge management functions
20
+ const KNOWLEDGE_FILE = path.join(os.homedir(), '.sam-coder-knowledge.json');
21
+
22
+ async function loadKnowledge() {
23
+ try {
24
+ const data = await fs.readFile(KNOWLEDGE_FILE, 'utf8');
25
+ return JSON.parse(data);
26
+ } catch (error) {
27
+ // If file doesn't exist, return empty knowledge
28
+ return {};
29
+ }
30
+ }
31
+
32
+ async function saveKnowledge(knowledge) {
33
+ await fs.writeFile(KNOWLEDGE_FILE, JSON.stringify(knowledge, null, 2), 'utf8');
34
+ }
35
+
18
36
  // Tool/Function definitions for the AI
19
37
  const tools = [
38
+ {
39
+ type: 'function',
40
+ function: {
41
+ name: 'addKnowledge',
42
+ description: 'Add or update knowledge in the knowledge base',
43
+ parameters: {
44
+ type: 'object',
45
+ properties: {
46
+ key: { type: 'string', description: 'Key/identifier for the knowledge' },
47
+ value: { type: 'string', description: 'The knowledge content' },
48
+ description: { type: 'string', description: 'Description of what this knowledge is about' }
49
+ },
50
+ required: ['key', 'value', 'description']
51
+ }
52
+ }
53
+ },
20
54
  {
21
55
  type: 'function',
22
56
  function: {
@@ -190,12 +224,19 @@ const tools = [
190
224
  // System prompt for the AI Assistant when using tool calling
191
225
  const TOOL_CALLING_PROMPT = `You are a helpful AI assistant with agency capabilities. You can perform actions on the user's system using the provided tools.
192
226
 
227
+ KNOWLEDGE MANAGEMENT:
228
+ - You can store and retrieve knowledge using the 'addKnowledge' tool.
229
+ - Use this to remember user preferences, project details, or any other important information.
230
+ - The knowledge will persist across sessions.
231
+ - Example: If the user mentions they prefer Python for all projects, store this preference.
232
+
193
233
  TOOLS AVAILABLE:
194
- 1. readFile - Read the contents of a file
195
- 2. writeFile - Write content to a file
196
- 3. editFile - Edit specific parts of a file
197
- 4. runCommand - Execute a shell command
198
- 5. searchFiles - Search for files using a glob pattern
234
+ 1. addKnowledge - Add or update knowledge in the knowledge base
235
+ 2. readFile - Read the contents of a file
236
+ 3. writeFile - Write content to a file
237
+ 4. editFile - Edit specific parts of a file
238
+ 5. runCommand - Execute a shell command
239
+ 6. searchFiles - Search for files using a glob pattern
199
240
 
200
241
  ENVIRONMENT:
201
242
  - OS: ${process.platform}
@@ -355,6 +396,28 @@ const agentUtils = {
355
396
  }
356
397
  };
357
398
 
399
+ const toolHandlers = {
400
+ addKnowledge: async ({ key, value, description }) => {
401
+ try {
402
+ const knowledge = await loadKnowledge();
403
+ knowledge[key] = {
404
+ value,
405
+ description,
406
+ timestamp: new Date().toISOString()
407
+ };
408
+ await saveKnowledge(knowledge);
409
+ return { success: true, message: `Knowledge '${key}' added successfully` };
410
+ } catch (error) {
411
+ return { success: false, error: error.message };
412
+ }
413
+ },
414
+ readFile,
415
+ writeFile,
416
+ editFile,
417
+ runCommand,
418
+ searchFiles
419
+ };
420
+
358
421
  // Extract JSON from markdown code blocks
359
422
  function extractJsonFromMarkdown(text) {
360
423
  // Try to find a markdown code block with JSON content
@@ -428,7 +491,29 @@ async function callOpenRouter(messages, currentModel, useJson = false) {
428
491
  // Process a query with tool calling
429
492
  async function processQueryWithTools(query, conversation = [], currentModel) {
430
493
  const userMessage = { role: 'user', content: query };
431
- const messages = [...conversation, userMessage];
494
+ let messages = [...conversation, userMessage];
495
+
496
+ // Add system message with knowledge if this is the first message
497
+ if (conversation.length === 0) {
498
+ // Load knowledge and include it in the system prompt
499
+ const knowledge = await loadKnowledge();
500
+ let systemPrompt = TOOL_CALLING_PROMPT;
501
+
502
+ // Add knowledge to the system prompt if any exists
503
+ const knowledgeEntries = Object.entries(knowledge);
504
+ if (knowledgeEntries.length > 0) {
505
+ systemPrompt += '\n\nCURRENT KNOWLEDGE BASE:\n';
506
+ systemPrompt += knowledgeEntries.map(([key, { value, description }]) =>
507
+ `- ${key}: ${value} (${description})`
508
+ ).join('\n');
509
+ }
510
+
511
+ // Add system message at the beginning
512
+ messages = [
513
+ { role: 'system', content: systemPrompt },
514
+ ...messages
515
+ ];
516
+ }
432
517
 
433
518
  ui.startThinking();
434
519
 
@@ -471,12 +556,20 @@ async function processQueryWithTools(query, conversation = [], currentModel) {
471
556
  async function handleToolCalls(toolCalls, messages) {
472
557
  const results = [];
473
558
 
559
+ // Load knowledge at the start of processing tool calls
560
+ const knowledge = await loadKnowledge();
561
+
474
562
  for (const toolCall of toolCalls) {
475
563
  const functionName = toolCall.function.name;
476
564
  let args;
477
565
 
478
566
  try {
479
567
  args = JSON.parse(toolCall.function.arguments);
568
+
569
+ // If we have knowledge that might be relevant, include it in the context
570
+ if (knowledge.defaultLanguage && !args.language) {
571
+ args.language = knowledge.defaultLanguage.value;
572
+ }
480
573
  } catch (error) {
481
574
  console.error('āŒ Failed to parse tool arguments:', error);
482
575
  results.push({
@@ -647,18 +740,28 @@ async function chat(rl, useToolCalling, initialModel) {
647
740
  if (input.toLowerCase() === '/setup') {
648
741
  await runSetup(rl, true);
649
742
  console.log('\nSetup complete. Please restart the application to apply changes.');
650
- rl.close();
743
+ rl.prompt();
651
744
  return;
652
745
  }
653
-
654
- if (input.toLowerCase() === 'exit') {
655
- rl.close();
746
+
747
+ if (input.toLowerCase() === '/eraseknowledge') {
748
+ try {
749
+ await fs.unlink(KNOWLEDGE_FILE);
750
+ console.log('āœ… All knowledge has been erased.');
751
+ } catch (error) {
752
+ if (error.code === 'ENOENT') {
753
+ console.log('ā„¹ļø No knowledge to erase.');
754
+ } else {
755
+ console.error('āŒ Failed to erase knowledge:', error.message);
756
+ }
757
+ }
758
+ rl.prompt();
656
759
  return;
657
760
  }
658
761
 
659
762
  const result = useToolCalling
660
- ? await processQueryWithTools(input, conversation, currentModel)
661
- : await processQuery(input, conversation, currentModel);
763
+ ? await processQueryWithTools(query, conversation, currentModel)
764
+ : await processQuery(query, conversation, currentModel);
662
765
  ui.stopThinking();
663
766
  ui.showResponse(result.response);
664
767
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "1.0.44",
3
+ "version": "1.0.45",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {
@@ -24,6 +24,7 @@
24
24
  "ws": "^8.18.3"
25
25
  },
26
26
  "devDependencies": {
27
+ "@types/vscode": "^1.102.0",
27
28
  "eslint": "^7.27.0"
28
29
  },
29
30
  "engines": {
@@ -1,328 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const readline = require('readline');
4
- const path = require('path');
5
- const fs = require('fs').promises;
6
- const { exec } = require('child_process');
7
- const util = require('util');
8
- const execAsync = util.promisify(exec);
9
-
10
- // Configuration
11
- const MODEL = 'deepseek/deepseek-chat-v3-0324:free';
12
- const API_BASE_URL = 'https://openrouter.ai/api/v1';
13
-
14
- // System prompt matching the VS Code extension
15
- const SYSTEM_PROMPT = `You are a VS Code AI Assistant with agency capabilities. You can perform actions on the user's workspace.
16
-
17
- ENVIRONMENT CONTEXT:
18
- - OS: ${process.platform}
19
- - Working Directory: ${process.cwd()}
20
-
21
- When you need to perform actions, respond with JSON in the following format:
22
- \`\`\`json
23
- {
24
- "thoughts": "Your reasoning about what needs to be done",
25
- "actions": [
26
- {
27
- "type": "read|write|search|command|analyze|execute|stop",
28
- "data": { ... action specific data ... }
29
- }
30
- ]
31
- }
32
- \`\`\`
33
-
34
- Action types and their data:
35
- - read: { "path": "relative/or/absolute/path" }
36
- - write: { "path": "relative/or/absolute/path", "content": "file content" }
37
- - search: { "type": "files", "pattern": "glob pattern" } or { "type": "text", "text": "search text" }
38
- - command: { "command": "command string to execute in terminal" }
39
- - execute: { "language": "js|python|bash|...", "code": "code to execute" }
40
- - analyze: { "code": "code to analyze", "question": "what you want to analyze" }
41
- - browse: { "query": "search query", "numResults": 5 } (free web search using DuckDuckGo, optional numResults)
42
- - edit: {
43
- "path": "relative/or/absolute/path",
44
- "edits": {
45
- "operations": [
46
- { "type": "replace", "startLine": 10, "endLine": 15, "newText": "new code here" },
47
- { "type": "replace", "pattern": "oldFunction\\(\\)", "replacement": "newFunction()", "flags": "g" },
48
- { "type": "insert", "line": 20, "text": "new line of code here" },
49
- { "type": "insert", "position": "start", "text": "// Header comment" },
50
- { "type": "insert", "position": "end", "text": "// Footer comment" },
51
- { "type": "delete", "startLine": 25, "endLine": 30 }
52
- ]
53
- }
54
- } (edit specific parts of an existing file)
55
- - stop: {} (use this to indicate you're done with the task and no more actions are needed)
56
-
57
- By default, you will continue to take actions in a loop until you decide to stop with the 'stop' action type.
58
- Always wrap your JSON in markdown code blocks with the json language specifier.
59
- When executing code or commands that might be potentially harmful, explain what the code does before executing it.`;
60
-
61
- // Agent utilities
62
- const agentUtils = {
63
- async readFile(filePath) {
64
- try {
65
- const content = await fs.readFile(filePath, 'utf-8');
66
- return content;
67
- } catch (error) {
68
- throw new Error(`Failed to read file ${filePath}: ${error.message}`);
69
- }
70
- },
71
-
72
- async writeFile(filePath, content) {
73
- try {
74
- await fs.writeFile(filePath, content, 'utf-8');
75
- return `Successfully wrote to ${filePath}`;
76
- } catch (error) {
77
- throw new Error(`Failed to write to file ${filePath}: ${error.message}`);
78
- }
79
- },
80
-
81
- async runCommand(command) {
82
- try {
83
- const { stdout, stderr } = await execAsync(command, { cwd: process.cwd() });
84
- if (stderr) {
85
- console.error('Command stderr:', stderr);
86
- }
87
- return stdout || 'Command executed successfully (no output)';
88
- } catch (error) {
89
- throw new Error(`Command failed: ${error.message}`);
90
- }
91
- },
92
-
93
- async searchFiles(pattern) {
94
- try {
95
- const { stdout } = await execAsync(`find . -name "${pattern}"`, { cwd: process.cwd() });
96
- return stdout || 'No files found';
97
- } catch (error) {
98
- throw new Error(`Search failed: ${error.message}`);
99
- }
100
- }
101
- };
102
-
103
- // Extract JSON from markdown code blocks
104
- function extractJsonFromMarkdown(text) {
105
- // Try to find a markdown code block with JSON content
106
- const codeBlockRegex = /```json\s*([\s\S]*?)\s*```/;
107
- const match = text.match(codeBlockRegex);
108
-
109
- if (match) {
110
- try {
111
- return JSON.parse(match[1]);
112
- } catch (error) {
113
- console.error('Error parsing JSON from markdown:', error);
114
- }
115
- }
116
-
117
- // If no code block, try to parse the entire text as JSON
118
- try {
119
- return JSON.parse(text);
120
- } catch (error) {
121
- console.error('Error parsing JSON:', error);
122
- return null;
123
- }
124
- }
125
-
126
- // Call OpenRouter API
127
- async function callOpenRouter(messages) {
128
- const apiKey = process.env.OPENROUTER_API_KEY;
129
-
130
- if (!apiKey) {
131
- throw new Error('OPENROUTER_API_KEY environment variable is not set');
132
- }
133
-
134
- try {
135
- const response = await fetch(API_BASE_URL + '/chat/completions', {
136
- method: 'POST',
137
- headers: {
138
- 'Content-Type': 'application/json',
139
- 'Authorization': `Bearer ${apiKey}`,
140
- 'HTTP-Referer': 'https://github.com/yourusername/agi-cli'
141
- },
142
- body: JSON.stringify({
143
- model: MODEL,
144
- messages: messages
145
- })
146
- });
147
-
148
- if (!response.ok) {
149
- const error = await response.json();
150
- throw new Error(`API error: ${error.error?.message || response.statusText}`);
151
- }
152
-
153
- return await response.json();
154
- } catch (error) {
155
- console.error('API call failed:', error);
156
- throw new Error(`Failed to call OpenRouter API: ${error.message}`);
157
- }
158
- }
159
-
160
- // Process a query with action handling
161
- async function processQuery(query, conversation = []) {
162
- try {
163
- // Add user message to conversation
164
- const userMessage = { role: 'user', content: query };
165
- const messages = [...conversation, userMessage];
166
-
167
- // Add system message if this is the first message
168
- if (conversation.length === 0) {
169
- messages.unshift({
170
- role: 'system',
171
- content: SYSTEM_PROMPT
172
- });
173
- }
174
-
175
- let shouldContinue = true;
176
- let iteration = 0;
177
- const maxIterations = 10; // Prevent infinite loops
178
- let finalResponse = '';
179
-
180
- while (shouldContinue && iteration < maxIterations) {
181
- iteration++;
182
- console.log('šŸ¤– Thinking...');
183
-
184
- const response = await callOpenRouter(messages);
185
- const assistantMessage = response.choices[0].message;
186
-
187
- // Add assistant's message to the conversation
188
- messages.push(assistantMessage);
189
-
190
- // Check if the response contains actions
191
- const actionData = extractJsonFromMarkdown(assistantMessage.content);
192
-
193
- if (actionData && actionData.actions && Array.isArray(actionData.actions)) {
194
- console.log(`šŸ”§ Processing ${actionData.actions.length} actions...`);
195
- if (actionData.thoughts) {
196
- console.log(`šŸ’­ ${actionData.thoughts}`);
197
- }
198
-
199
- const actionResults = [];
200
-
201
- for (const action of actionData.actions) {
202
- console.log(`šŸ› ļø Executing action: ${action.type}`);
203
-
204
- // Handle stop action
205
- if (action.type === 'stop') {
206
- console.log('šŸ›‘ Stop action received, ending action processing');
207
- shouldContinue = false;
208
- finalResponse = 'Task completed successfully.';
209
- break;
210
- }
211
-
212
- try {
213
- let result;
214
-
215
- switch (action.type) {
216
- case 'read':
217
- result = await agentUtils.readFile(action.data.path);
218
- break;
219
-
220
- case 'write':
221
- result = await agentUtils.writeFile(action.data.path, action.data.content);
222
- break;
223
-
224
- case 'command':
225
- result = await agentUtils.runCommand(action.data.command);
226
- break;
227
-
228
- case 'search':
229
- if (action.data.type === 'files') {
230
- result = await agentUtils.searchFiles(action.data.pattern);
231
- } else {
232
- result = 'Text search not yet implemented';
233
- }
234
- break;
235
-
236
- default:
237
- result = `Action type '${action.type}' is not supported yet.`;
238
- }
239
-
240
- actionResults.push({
241
- type: action.type,
242
- success: true,
243
- result: result
244
- });
245
-
246
- console.log(`āœ… Action ${action.type} completed successfully`);
247
-
248
- } catch (error) {
249
- console.error(`āŒ Action ${action.type} failed:`, error);
250
- actionResults.push({
251
- type: action.type,
252
- success: false,
253
- error: error.message
254
- });
255
- }
256
- }
257
-
258
- // Add action results to the conversation
259
- messages.push({
260
- role: 'system',
261
- content: `Action results:\n\`\`\`json\n${JSON.stringify(actionResults, null, 2)}\n\`\`\`\n` +
262
- `Based on these results, determine what to do next. You can:\n` +
263
- `1. Continue with more actions by returning a new JSON with "actions" array\n` +
264
- `2. Stop the iteration by including an action with "type": "stop" if the task is completed\n` +
265
- `3. Provide a final response to the user with your findings`
266
- });
267
-
268
- } else {
269
- // No actions, this is a regular response
270
- shouldContinue = false;
271
- finalResponse = assistantMessage.content;
272
- }
273
- }
274
-
275
- // If we hit max iterations, add a note
276
- if (iteration >= maxIterations) {
277
- finalResponse += '\n\nāš ļø Reached maximum number of iterations. Stopping execution.';
278
- }
279
-
280
- return {
281
- response: finalResponse,
282
- conversation: messages
283
- };
284
-
285
- } catch (error) {
286
- console.error('Error processing query:', error);
287
- return {
288
- response: `Error: ${error.message}`,
289
- conversation
290
- };
291
- }
292
- }
293
-
294
- // Main chat loop
295
- async function chat() {
296
- const conversation = [];
297
- console.log('Welcome to AGI-CLI. Type your message, or "exit" to quit.');
298
-
299
- const rl = readline.createInterface({
300
- input: process.stdin,
301
- output: process.stdout,
302
- prompt: '> '
303
- });
304
-
305
- rl.prompt();
306
-
307
- rl.on('line', async (input) => {
308
- if (input.toLowerCase() === 'exit') {
309
- rl.close();
310
- return;
311
- }
312
-
313
- const result = await processQuery(input, conversation);
314
- console.log(result.response);
315
-
316
- // Update conversation with the full context
317
- conversation.length = 0; // Clear the array
318
- result.conversation.forEach(msg => conversation.push(msg));
319
-
320
- rl.prompt();
321
- }).on('close', () => {
322
- console.log('Goodbye!');
323
- process.exit(0);
324
- });
325
- }
326
-
327
- // Start the chat
328
- chat().catch(console.error);