natureco-cli 2.0.1 → 2.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -211,7 +211,7 @@ body::before{
211
211
  <div class="header-bot-name" id="header-bot-name">Nature Bot</div>
212
212
  <div class="header-bot-model" id="header-bot-model">NatureCo</div>
213
213
  </div>
214
- <div class="version-badge" id="version-badge">v2.0.1</div>
214
+ <div class="version-badge" id="version-badge">v2.1.1</div>
215
215
  </div>
216
216
  <div class="messages" id="messages"></div>
217
217
  <div class="input-area">
@@ -341,7 +341,7 @@ function dashboard(action) {
341
341
  apiKey: cfg.apiKey,
342
342
  defaultBot: cfg.defaultBot,
343
343
  defaultBotId: cfg.defaultBotId,
344
- version: 'v2.0.1',
344
+ version: 'v2.1.1',
345
345
  bots: cfg.bots || [],
346
346
  telegramToken: cfg.telegramToken || null,
347
347
  whatsappConnected: cfg.whatsappConnected || false,
package/src/tools/bash.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const { execSync } = require('child_process');
2
+ const os = require('os');
2
3
 
3
4
  module.exports = {
4
5
  name: 'bash',
@@ -16,10 +17,15 @@ module.exports = {
16
17
 
17
18
  async execute(params) {
18
19
  try {
19
- const output = execSync(params.command, {
20
+ // Replace /home with actual home directory
21
+ let command = params.command;
22
+ command = command.replace(/\/home(?=\s|\/|$)/g, os.homedir());
23
+
24
+ const output = execSync(command, {
20
25
  encoding: 'utf-8',
21
26
  maxBuffer: 10 * 1024 * 1024, // 10MB
22
- timeout: 30000 // 30 seconds
27
+ timeout: 30000, // 30 seconds
28
+ shell: true // Use shell for command execution
23
29
  });
24
30
 
25
31
  return {
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const os = require('os');
3
4
 
4
5
  /**
5
6
  * List Directory Tool
@@ -22,14 +23,22 @@ module.exports = {
22
23
 
23
24
  async execute(params) {
24
25
  try {
25
- const dirPath = params.path || '.';
26
+ // Expand ~ to home directory
27
+ let dirPath = params.path || '.';
28
+ dirPath = dirPath.replace(/^~/, os.homedir());
29
+
30
+ // Fix /home path - replace with actual home directory
31
+ if (dirPath === '/home' || dirPath === 'home') {
32
+ dirPath = os.homedir();
33
+ }
34
+
26
35
  const absolutePath = path.resolve(dirPath);
27
36
 
28
37
  // Check if directory exists
29
38
  if (!fs.existsSync(absolutePath)) {
30
39
  return {
31
40
  success: false,
32
- error: `Directory not found: ${dirPath}`
41
+ error: `Directory not found: ${params.path}`
33
42
  };
34
43
  }
35
44
 
@@ -38,7 +47,7 @@ module.exports = {
38
47
  if (!stats.isDirectory()) {
39
48
  return {
40
49
  success: false,
41
- error: `Not a directory: ${dirPath}`
50
+ error: `Not a directory: ${params.path}`
42
51
  };
43
52
  }
44
53
 
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const os = require('os');
3
4
 
4
5
  module.exports = {
5
6
  name: 'read_file',
@@ -17,7 +18,9 @@ module.exports = {
17
18
 
18
19
  async execute(params) {
19
20
  try {
20
- const filePath = path.resolve(params.path);
21
+ // Expand ~ to home directory
22
+ let filePath = params.path.replace(/^~/, os.homedir());
23
+ filePath = path.resolve(filePath);
21
24
 
22
25
  if (!fs.existsSync(filePath)) {
23
26
  return {
@@ -35,13 +38,32 @@ module.exports = {
35
38
  };
36
39
  }
37
40
 
41
+ // Check file size - if > 1MB, read only first 50KB
42
+ if (stats.size > 1024 * 1024) {
43
+ const fd = fs.openSync(filePath, 'r');
44
+ const buf = Buffer.alloc(50000);
45
+ fs.readSync(fd, buf, 0, 50000, 0);
46
+ fs.closeSync(fd);
47
+
48
+ const content = '[Büyük dosya - ilk ~50KB gösteriliyor]\n' + buf.toString('utf8');
49
+
50
+ return {
51
+ success: true,
52
+ path: filePath,
53
+ content,
54
+ size: stats.size,
55
+ truncated: true
56
+ };
57
+ }
58
+
38
59
  const content = fs.readFileSync(filePath, 'utf-8');
39
60
 
40
61
  return {
41
62
  success: true,
42
63
  path: filePath,
43
64
  content,
44
- size: stats.size
65
+ size: stats.size,
66
+ truncated: false
45
67
  };
46
68
  } catch (error) {
47
69
  return {
package/src/utils/api.js CHANGED
@@ -1,5 +1,5 @@
1
- // NatureCo CLI v2.0.0 - Direct Groq Integration with Local Tool Execution
2
- // Bypass NatureCo backend, connect directly to Groq API
1
+ // NatureCo CLI v2.1.0 - Universal LLM Provider Support
2
+ // Supports: OpenAI, Groq, Together, Fireworks, Perplexity, Mistral, DeepSeek, OpenRouter, Ollama, LM Studio, Anthropic
3
3
 
4
4
  const { getConfig } = require('./config');
5
5
  const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
@@ -8,17 +8,38 @@ const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
8
8
  const conversationHistory = new Map();
9
9
 
10
10
  /**
11
- * Get Groq API key from config
11
+ * Get provider configuration from config
12
12
  */
13
- function getGroqApiKey() {
13
+ function getProviderConfig() {
14
14
  const config = getConfig();
15
- return config.groqApiKey || null;
15
+
16
+ // Universal provider config (v2.1.0+)
17
+ if (config.providerUrl && config.providerApiKey) {
18
+ return {
19
+ url: config.providerUrl,
20
+ apiKey: config.providerApiKey,
21
+ model: config.providerModel || 'llama-3.1-8b-instant',
22
+ isAnthropic: config.providerUrl.includes('anthropic.com')
23
+ };
24
+ }
25
+
26
+ // Legacy Groq config (v2.0.x)
27
+ if (config.groqApiKey) {
28
+ return {
29
+ url: 'https://api.groq.com/openai/v1',
30
+ apiKey: config.groqApiKey,
31
+ model: config.groqModel || 'llama-3.1-8b-instant',
32
+ isAnthropic: false
33
+ };
34
+ }
35
+
36
+ return null;
16
37
  }
17
38
 
18
39
  /**
19
- * Format tool definitions for Groq API
40
+ * Format tool definitions for OpenAI-compatible APIs
20
41
  */
21
- function formatToolsForGroq() {
42
+ function formatToolsForOpenAI() {
22
43
  const tools = getToolDefinitions();
23
44
 
24
45
  return tools.map(tool => ({
@@ -32,18 +53,116 @@ function formatToolsForGroq() {
32
53
  }
33
54
 
34
55
  /**
35
- * Send message to Groq with tool support
56
+ * Format tool definitions for Anthropic API
36
57
  */
37
- async function sendMessageToGroq(apiKey, message, conversationId = null, systemPrompt = null) {
38
- const config = getConfig();
39
- const groqApiKey = config.groqApiKey;
58
+ function formatToolsForAnthropic() {
59
+ const tools = getToolDefinitions();
40
60
 
41
- if (!groqApiKey) {
42
- throw new Error('Groq API key not found. Set it with: natureco config set groqApiKey gsk_xxx');
61
+ return tools.map(tool => ({
62
+ name: tool.name,
63
+ description: tool.description,
64
+ input_schema: tool.inputSchema
65
+ }));
66
+ }
67
+
68
+ /**
69
+ * Send message to OpenAI-compatible provider (Groq, OpenAI, Together, etc.)
70
+ */
71
+ async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
72
+ const endpoint = `${providerConfig.url}/chat/completions`;
73
+
74
+ const response = await fetch(endpoint, {
75
+ method: 'POST',
76
+ headers: {
77
+ 'Authorization': `Bearer ${providerConfig.apiKey}`,
78
+ 'Content-Type': 'application/json',
79
+ },
80
+ body: JSON.stringify({
81
+ model: providerConfig.model,
82
+ messages: messages,
83
+ tools: tools,
84
+ tool_choice: 'auto',
85
+ temperature: 0.7,
86
+ max_tokens: 2000,
87
+ }),
88
+ });
89
+
90
+ if (!response.ok) {
91
+ const errorText = await response.text();
92
+ throw new Error(`Provider API error: ${response.status} - ${errorText}`);
43
93
  }
44
94
 
45
- // Get model from config (default: llama-3.1-8b-instant)
46
- const model = config.groqModel || 'llama-3.1-8b-instant';
95
+ const data = await response.json();
96
+ return data.choices[0].message;
97
+ }
98
+
99
+ /**
100
+ * Send message to Anthropic API
101
+ */
102
+ async function sendMessageAnthropic(providerConfig, messages, tools) {
103
+ const endpoint = `${providerConfig.url}/v1/messages`;
104
+
105
+ // Anthropic requires system message separate
106
+ const systemMessage = messages.find(m => m.role === 'system');
107
+ const userMessages = messages.filter(m => m.role !== 'system');
108
+
109
+ const response = await fetch(endpoint, {
110
+ method: 'POST',
111
+ headers: {
112
+ 'x-api-key': providerConfig.apiKey,
113
+ 'anthropic-version': '2023-06-01',
114
+ 'Content-Type': 'application/json',
115
+ },
116
+ body: JSON.stringify({
117
+ model: providerConfig.model,
118
+ max_tokens: 2000,
119
+ system: systemMessage?.content || '',
120
+ messages: userMessages,
121
+ tools: tools,
122
+ }),
123
+ });
124
+
125
+ if (!response.ok) {
126
+ const errorText = await response.text();
127
+ throw new Error(`Anthropic API error: ${response.status} - ${errorText}`);
128
+ }
129
+
130
+ const data = await response.json();
131
+
132
+ // Convert Anthropic response to OpenAI format
133
+ const content = data.content.find(c => c.type === 'text')?.text || '';
134
+ const toolCalls = data.content
135
+ .filter(c => c.type === 'tool_use')
136
+ .map(c => ({
137
+ id: c.id,
138
+ type: 'function',
139
+ function: {
140
+ name: c.name,
141
+ arguments: JSON.stringify(c.input)
142
+ }
143
+ }));
144
+
145
+ return {
146
+ role: 'assistant',
147
+ content: content,
148
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Send message with tool support (universal)
154
+ */
155
+ async function sendMessageToProvider(apiKey, message, conversationId = null, systemPrompt = null) {
156
+ const providerConfig = getProviderConfig();
157
+
158
+ if (!providerConfig) {
159
+ throw new Error(
160
+ 'Provider not configured. Set with:\n' +
161
+ ' natureco config set providerUrl https://api.groq.com/openai/v1\n' +
162
+ ' natureco config set providerApiKey gsk_xxx\n' +
163
+ ' natureco config set providerModel llama-3.1-8b-instant'
164
+ );
165
+ }
47
166
 
48
167
  // Get or create conversation history
49
168
  const convId = conversationId || `conv_${Date.now()}`;
@@ -52,25 +171,25 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
52
171
  }
53
172
  const history = conversationHistory.get(convId);
54
173
 
55
- // Add system prompt if provided
174
+ // Build messages
56
175
  const messages = [];
57
176
  if (systemPrompt) {
58
177
  messages.push({ role: 'system', content: systemPrompt });
59
178
  }
60
-
61
- // Add conversation history
62
179
  messages.push(...history);
63
-
64
- // Add user message
65
180
  messages.push({ role: 'user', content: message });
66
181
 
67
182
  // Get tool definitions
68
- const tools = formatToolsForGroq();
183
+ const tools = providerConfig.isAnthropic
184
+ ? formatToolsForAnthropic()
185
+ : formatToolsForOpenAI();
69
186
 
70
- console.log('\n[Groq] Sending request...');
71
- console.log('[Groq] Model:', model);
72
- console.log('[Groq] Messages:', messages.length);
73
- console.log('[Groq] Tools:', tools.length);
187
+ console.log('\n[Provider] Sending request...');
188
+ console.log('[Provider] URL:', providerConfig.url);
189
+ console.log('[Provider] Model:', providerConfig.model);
190
+ console.log('[Provider] Type:', providerConfig.isAnthropic ? 'Anthropic' : 'OpenAI-compatible');
191
+ console.log('[Provider] Messages:', messages.length);
192
+ console.log('[Provider] Tools:', tools.length);
74
193
 
75
194
  // Tool execution loop (max 10 iterations)
76
195
  let iteration = 0;
@@ -79,42 +198,21 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
79
198
 
80
199
  while (iteration < maxIterations) {
81
200
  iteration++;
82
- console.log(`\n[Groq] Iteration ${iteration}/${maxIterations}`);
83
-
84
- // Call Groq API
85
- const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
86
- method: 'POST',
87
- headers: {
88
- 'Authorization': `Bearer ${groqApiKey}`,
89
- 'Content-Type': 'application/json',
90
- },
91
- body: JSON.stringify({
92
- model: model,
93
- messages: messages,
94
- tools: tools,
95
- tool_choice: 'auto',
96
- temperature: 0.7,
97
- max_tokens: 2000,
98
- }),
99
- });
100
-
101
- if (!response.ok) {
102
- const errorText = await response.text();
103
- console.error('[Groq] Error:', errorText);
104
- throw new Error(`Groq API error: ${response.status} - ${errorText}`);
105
- }
201
+ console.log(`\n[Provider] Iteration ${iteration}/${maxIterations}`);
106
202
 
107
- const data = await response.json();
108
- const assistantMessage = data.choices[0].message;
203
+ // Call provider API
204
+ const assistantMessage = providerConfig.isAnthropic
205
+ ? await sendMessageAnthropic(providerConfig, messages, tools)
206
+ : await sendMessageOpenAICompatible(providerConfig, messages, tools);
109
207
 
110
- console.log('[Groq] Response type:', assistantMessage.tool_calls ? 'tool_calls' : 'text');
208
+ console.log('[Provider] Response type:', assistantMessage.tool_calls ? 'tool_calls' : 'text');
111
209
 
112
210
  // Add assistant message to history
113
211
  messages.push(assistantMessage);
114
212
 
115
213
  // Check for tool calls
116
214
  if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
117
- console.log(`[Groq] Tool calls: ${assistantMessage.tool_calls.length}`);
215
+ console.log(`[Provider] Tool calls: ${assistantMessage.tool_calls.length}`);
118
216
 
119
217
  // Execute tools locally
120
218
  const toolCalls = assistantMessage.tool_calls.map(tc => ({
@@ -127,8 +225,6 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
127
225
 
128
226
  // Add tool results to messages
129
227
  for (const result of toolResults) {
130
- const toolCall = assistantMessage.tool_calls.find(tc => tc.id === result.id);
131
-
132
228
  messages.push({
133
229
  role: 'tool',
134
230
  tool_call_id: result.id,
@@ -149,7 +245,7 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
149
245
  }
150
246
 
151
247
  if (iteration >= maxIterations) {
152
- console.log('\n[Groq] Max iterations reached');
248
+ console.log('\n[Provider] Max iterations reached');
153
249
  finalResponse = finalResponse || 'Max tool execution iterations reached.';
154
250
  }
155
251
 
@@ -186,30 +282,33 @@ function clearConversation(conversationId) {
186
282
  */
187
283
  async function sendMessage(apiKey, botId, message, conversationId = null, skillPrompts = '') {
188
284
  // System prompt for terminal assistant
189
- const systemPrompt = "You are a terminal assistant. When users ask for file listing, command execution, or directory viewing, you MUST use the available tools (bash, read_file, write_file, list_dir). Never say 'run this command' - execute it yourself using tools and show the result.";
285
+ const systemPrompt = "You are a terminal assistant. When users ask for file listing, command execution, or directory viewing, you MUST use the available tools (bash, read_file, write_file, list_dir). Never say 'run this command' - execute it yourself using tools and show the result.\n\nIMPORTANT: For the user's home directory, always use the actual home path (e.g., /Users/username on Mac, /home/username on Linux), NOT /home. The tools automatically handle this conversion.";
190
286
 
191
- return sendMessageToGroq(apiKey, message, conversationId, systemPrompt);
287
+ return sendMessageToProvider(apiKey, message, conversationId, systemPrompt);
192
288
  }
193
289
 
194
290
  /**
195
- * Validate API key (not used in v2.0.0, kept for compatibility)
291
+ * Validate API key (not used in v2.x, kept for compatibility)
196
292
  */
197
293
  async function validateApiKey(apiKey) {
198
- const groqApiKey = getGroqApiKey();
199
- return groqApiKey !== null;
294
+ const providerConfig = getProviderConfig();
295
+ return providerConfig !== null;
200
296
  }
201
297
 
202
298
  /**
203
- * Get bots (not used in v2.0.0, kept for compatibility)
299
+ * Get bots (not used in v2.x, kept for compatibility)
204
300
  */
205
301
  async function getBots(apiKey) {
302
+ const providerConfig = getProviderConfig();
303
+ const providerName = providerConfig?.isAnthropic ? 'Anthropic' : 'OpenAI-compatible';
304
+
206
305
  return {
207
306
  bots: [
208
307
  {
209
- id: 'groq-direct',
210
- name: 'Groq Direct',
211
- ai_provider: 'groq',
212
- model: 'llama-3.3-70b-versatile'
308
+ id: 'universal-provider',
309
+ name: `Universal Provider (${providerName})`,
310
+ ai_provider: providerName,
311
+ model: providerConfig?.model || 'unknown'
213
312
  }
214
313
  ]
215
314
  };
@@ -217,10 +316,10 @@ async function getBots(apiKey) {
217
316
 
218
317
  module.exports = {
219
318
  sendMessage,
220
- sendMessageToGroq,
319
+ sendMessageToProvider,
221
320
  validateApiKey,
222
321
  getBots,
223
322
  clearConversation,
224
- getGroqApiKey,
323
+ getProviderConfig,
225
324
  _sendMessage: sendMessage, // Alias for compatibility
226
325
  };