natureco-cli 2.0.0 → 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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.0</div>
214
+ <div class="version-badge" id="version-badge">v2.1.0</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.0',
344
+ version: 'v2.1.0',
345
345
  bots: cfg.bots || [],
346
346
  telegramToken: cfg.telegramToken || null,
347
347
  whatsappConnected: cfg.whatsappConnected || false,
package/src/tools/bash.js CHANGED
@@ -19,7 +19,8 @@ module.exports = {
19
19
  const output = execSync(params.command, {
20
20
  encoding: 'utf-8',
21
21
  maxBuffer: 10 * 1024 * 1024, // 10MB
22
- timeout: 30000 // 30 seconds
22
+ timeout: 30000, // 30 seconds
23
+ shell: true // Use shell for command execution
23
24
  });
24
25
 
25
26
  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,16 @@ 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());
26
29
  const absolutePath = path.resolve(dirPath);
27
30
 
28
31
  // Check if directory exists
29
32
  if (!fs.existsSync(absolutePath)) {
30
33
  return {
31
34
  success: false,
32
- error: `Directory not found: ${dirPath}`
35
+ error: `Directory not found: ${params.path}`
33
36
  };
34
37
  }
35
38
 
@@ -38,7 +41,7 @@ module.exports = {
38
41
  if (!stats.isDirectory()) {
39
42
  return {
40
43
  success: false,
41
- error: `Not a directory: ${dirPath}`
44
+ error: `Not a directory: ${params.path}`
42
45
  };
43
46
  }
44
47
 
@@ -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,13 +53,115 @@ 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 groqApiKey = getGroqApiKey();
58
+ function formatToolsForAnthropic() {
59
+ const tools = getToolDefinitions();
39
60
 
40
- if (!groqApiKey) {
41
- 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}`);
93
+ }
94
+
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
+ );
42
165
  }
43
166
 
44
167
  // Get or create conversation history
@@ -48,24 +171,25 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
48
171
  }
49
172
  const history = conversationHistory.get(convId);
50
173
 
51
- // Add system prompt if provided
174
+ // Build messages
52
175
  const messages = [];
53
176
  if (systemPrompt) {
54
177
  messages.push({ role: 'system', content: systemPrompt });
55
178
  }
56
-
57
- // Add conversation history
58
179
  messages.push(...history);
59
-
60
- // Add user message
61
180
  messages.push({ role: 'user', content: message });
62
181
 
63
182
  // Get tool definitions
64
- const tools = formatToolsForGroq();
183
+ const tools = providerConfig.isAnthropic
184
+ ? formatToolsForAnthropic()
185
+ : formatToolsForOpenAI();
65
186
 
66
- console.log('\n[Groq] Sending request...');
67
- console.log('[Groq] Messages:', messages.length);
68
- 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);
69
193
 
70
194
  // Tool execution loop (max 10 iterations)
71
195
  let iteration = 0;
@@ -74,42 +198,21 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
74
198
 
75
199
  while (iteration < maxIterations) {
76
200
  iteration++;
77
- console.log(`\n[Groq] Iteration ${iteration}/${maxIterations}`);
78
-
79
- // Call Groq API
80
- const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
81
- method: 'POST',
82
- headers: {
83
- 'Authorization': `Bearer ${groqApiKey}`,
84
- 'Content-Type': 'application/json',
85
- },
86
- body: JSON.stringify({
87
- model: 'llama-3.3-70b-versatile',
88
- messages: messages,
89
- tools: tools,
90
- tool_choice: 'auto',
91
- temperature: 0.7,
92
- max_tokens: 2000,
93
- }),
94
- });
95
-
96
- if (!response.ok) {
97
- const errorText = await response.text();
98
- console.error('[Groq] Error:', errorText);
99
- throw new Error(`Groq API error: ${response.status} - ${errorText}`);
100
- }
201
+ console.log(`\n[Provider] Iteration ${iteration}/${maxIterations}`);
101
202
 
102
- const data = await response.json();
103
- 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);
104
207
 
105
- console.log('[Groq] Response type:', assistantMessage.tool_calls ? 'tool_calls' : 'text');
208
+ console.log('[Provider] Response type:', assistantMessage.tool_calls ? 'tool_calls' : 'text');
106
209
 
107
210
  // Add assistant message to history
108
211
  messages.push(assistantMessage);
109
212
 
110
213
  // Check for tool calls
111
214
  if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
112
- console.log(`[Groq] Tool calls: ${assistantMessage.tool_calls.length}`);
215
+ console.log(`[Provider] Tool calls: ${assistantMessage.tool_calls.length}`);
113
216
 
114
217
  // Execute tools locally
115
218
  const toolCalls = assistantMessage.tool_calls.map(tc => ({
@@ -122,8 +225,6 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
122
225
 
123
226
  // Add tool results to messages
124
227
  for (const result of toolResults) {
125
- const toolCall = assistantMessage.tool_calls.find(tc => tc.id === result.id);
126
-
127
228
  messages.push({
128
229
  role: 'tool',
129
230
  tool_call_id: result.id,
@@ -144,7 +245,7 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
144
245
  }
145
246
 
146
247
  if (iteration >= maxIterations) {
147
- console.log('\n[Groq] Max iterations reached');
248
+ console.log('\n[Provider] Max iterations reached');
148
249
  finalResponse = finalResponse || 'Max tool execution iterations reached.';
149
250
  }
150
251
 
@@ -183,28 +284,31 @@ async function sendMessage(apiKey, botId, message, conversationId = null, skillP
183
284
  // System prompt for terminal assistant
184
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.";
185
286
 
186
- return sendMessageToGroq(apiKey, message, conversationId, systemPrompt);
287
+ return sendMessageToProvider(apiKey, message, conversationId, systemPrompt);
187
288
  }
188
289
 
189
290
  /**
190
- * Validate API key (not used in v2.0.0, kept for compatibility)
291
+ * Validate API key (not used in v2.x, kept for compatibility)
191
292
  */
192
293
  async function validateApiKey(apiKey) {
193
- const groqApiKey = getGroqApiKey();
194
- return groqApiKey !== null;
294
+ const providerConfig = getProviderConfig();
295
+ return providerConfig !== null;
195
296
  }
196
297
 
197
298
  /**
198
- * Get bots (not used in v2.0.0, kept for compatibility)
299
+ * Get bots (not used in v2.x, kept for compatibility)
199
300
  */
200
301
  async function getBots(apiKey) {
302
+ const providerConfig = getProviderConfig();
303
+ const providerName = providerConfig?.isAnthropic ? 'Anthropic' : 'OpenAI-compatible';
304
+
201
305
  return {
202
306
  bots: [
203
307
  {
204
- id: 'groq-direct',
205
- name: 'Groq Direct',
206
- ai_provider: 'groq',
207
- model: 'llama-3.3-70b-versatile'
308
+ id: 'universal-provider',
309
+ name: `Universal Provider (${providerName})`,
310
+ ai_provider: providerName,
311
+ model: providerConfig?.model || 'unknown'
208
312
  }
209
313
  ]
210
314
  };
@@ -212,10 +316,10 @@ async function getBots(apiKey) {
212
316
 
213
317
  module.exports = {
214
318
  sendMessage,
215
- sendMessageToGroq,
319
+ sendMessageToProvider,
216
320
  validateApiKey,
217
321
  getBots,
218
322
  clearConversation,
219
- getGroqApiKey,
323
+ getProviderConfig,
220
324
  _sendMessage: sendMessage, // Alias for compatibility
221
325
  };