natureco-cli 1.1.6 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "1.1.6",
3
+ "version": "2.0.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">v1.1.6</div>
214
+ <div class="version-badge" id="version-badge">v2.0.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: 'v1.1.6',
344
+ version: 'v2.0.1',
345
345
  bots: cfg.bots || [],
346
346
  telegramToken: cfg.telegramToken || null,
347
347
  whatsappConnected: cfg.whatsappConnected || false,
@@ -0,0 +1,115 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * List Directory Tool
6
+ * Lists files and directories in a given path
7
+ */
8
+
9
+ module.exports = {
10
+ name: 'list_dir',
11
+ description: 'List files and directories in a given path. Returns file names, sizes, and types.',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ path: {
16
+ type: 'string',
17
+ description: 'Directory path to list (relative or absolute). Use "." for current directory.'
18
+ }
19
+ },
20
+ required: ['path']
21
+ },
22
+
23
+ async execute(params) {
24
+ try {
25
+ const dirPath = params.path || '.';
26
+ const absolutePath = path.resolve(dirPath);
27
+
28
+ // Check if directory exists
29
+ if (!fs.existsSync(absolutePath)) {
30
+ return {
31
+ success: false,
32
+ error: `Directory not found: ${dirPath}`
33
+ };
34
+ }
35
+
36
+ // Check if it's a directory
37
+ const stats = fs.statSync(absolutePath);
38
+ if (!stats.isDirectory()) {
39
+ return {
40
+ success: false,
41
+ error: `Not a directory: ${dirPath}`
42
+ };
43
+ }
44
+
45
+ // Read directory
46
+ const entries = fs.readdirSync(absolutePath, { withFileTypes: true });
47
+
48
+ // Format entries
49
+ const items = entries.map(entry => {
50
+ const itemPath = path.join(absolutePath, entry.name);
51
+ let size = 0;
52
+ let type = 'unknown';
53
+
54
+ try {
55
+ const itemStats = fs.statSync(itemPath);
56
+ size = itemStats.size;
57
+
58
+ if (entry.isDirectory()) {
59
+ type = 'directory';
60
+ } else if (entry.isFile()) {
61
+ type = 'file';
62
+ } else if (entry.isSymbolicLink()) {
63
+ type = 'symlink';
64
+ }
65
+ } catch (err) {
66
+ // Ignore stat errors
67
+ }
68
+
69
+ return {
70
+ name: entry.name,
71
+ type: type,
72
+ size: size
73
+ };
74
+ });
75
+
76
+ // Sort: directories first, then files
77
+ items.sort((a, b) => {
78
+ if (a.type === 'directory' && b.type !== 'directory') return -1;
79
+ if (a.type !== 'directory' && b.type === 'directory') return 1;
80
+ return a.name.localeCompare(b.name);
81
+ });
82
+
83
+ // Format output
84
+ let output = `Directory: ${absolutePath}\n`;
85
+ output += `Total items: ${items.length}\n\n`;
86
+
87
+ items.forEach(item => {
88
+ const typeIcon = item.type === 'directory' ? '📁' : '📄';
89
+ const sizeStr = item.type === 'file' ? ` (${formatSize(item.size)})` : '';
90
+ output += `${typeIcon} ${item.name}${sizeStr}\n`;
91
+ });
92
+
93
+ return {
94
+ success: true,
95
+ output: output,
96
+ items: items,
97
+ path: absolutePath
98
+ };
99
+
100
+ } catch (error) {
101
+ return {
102
+ success: false,
103
+ error: error.message
104
+ };
105
+ }
106
+ }
107
+ };
108
+
109
+ function formatSize(bytes) {
110
+ if (bytes === 0) return '0 B';
111
+ const k = 1024;
112
+ const sizes = ['B', 'KB', 'MB', 'GB'];
113
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
114
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
115
+ }
package/src/utils/api.js CHANGED
@@ -1,108 +1,226 @@
1
- const API_BASE = 'https://api.natureco.me';
2
-
3
- async function request(endpoint, options = {}) {
4
- const url = `${API_BASE}${endpoint}`;
5
- const response = await fetch(url, {
6
- ...options,
7
- headers: {
8
- 'Content-Type': 'application/json',
9
- ...options.headers,
10
- },
11
- });
12
-
13
- if (!response.ok) {
14
- const text = await response.text();
15
- throw new Error(`API Error: ${response.status} - ${text}`);
16
- }
1
+ // NatureCo CLI v2.0.0 - Direct Groq Integration with Local Tool Execution
2
+ // Bypass NatureCo backend, connect directly to Groq API
3
+
4
+ const { getConfig } = require('./config');
5
+ const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
6
+
7
+ // Conversation history for multi-turn chat
8
+ const conversationHistory = new Map();
17
9
 
18
- return response.json();
10
+ /**
11
+ * Get Groq API key from config
12
+ */
13
+ function getGroqApiKey() {
14
+ const config = getConfig();
15
+ return config.groqApiKey || null;
19
16
  }
20
17
 
21
- async function getBots(apiKey) {
22
- return request('/api/v1/bots', {
23
- headers: { Authorization: `Bearer ${apiKey}` },
24
- });
18
+ /**
19
+ * Format tool definitions for Groq API
20
+ */
21
+ function formatToolsForGroq() {
22
+ const tools = getToolDefinitions();
23
+
24
+ return tools.map(tool => ({
25
+ type: 'function',
26
+ function: {
27
+ name: tool.name,
28
+ description: tool.description,
29
+ parameters: tool.inputSchema
30
+ }
31
+ }));
25
32
  }
26
33
 
27
- async function _sendMessage(apiKey, botId, message, conversationId = null, skillPrompts = '', toolDefinitions = null) {
28
- const { getConfig } = require('./config');
34
+ /**
35
+ * Send message to Groq with tool support
36
+ */
37
+ async function sendMessageToGroq(apiKey, message, conversationId = null, systemPrompt = null) {
29
38
  const config = getConfig();
39
+ const groqApiKey = config.groqApiKey;
30
40
 
31
- // Backend otomatik ilk botu seçsin, bot_id gönderme
32
- const body = {
33
- message,
34
- user_id: 'cli-user'
35
- };
41
+ if (!groqApiKey) {
42
+ throw new Error('Groq API key not found. Set it with: natureco config set groqApiKey gsk_xxx');
43
+ }
44
+
45
+ // Get model from config (default: llama-3.1-8b-instant)
46
+ const model = config.groqModel || 'llama-3.1-8b-instant';
47
+
48
+ // Get or create conversation history
49
+ const convId = conversationId || `conv_${Date.now()}`;
50
+ if (!conversationHistory.has(convId)) {
51
+ conversationHistory.set(convId, []);
52
+ }
53
+ const history = conversationHistory.get(convId);
54
+
55
+ // Add system prompt if provided
56
+ const messages = [];
57
+ if (systemPrompt) {
58
+ messages.push({ role: 'system', content: systemPrompt });
59
+ }
60
+
61
+ // Add conversation history
62
+ messages.push(...history);
63
+
64
+ // Add user message
65
+ messages.push({ role: 'user', content: message });
66
+
67
+ // Get tool definitions
68
+ const tools = formatToolsForGroq();
69
+
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);
74
+
75
+ // Tool execution loop (max 10 iterations)
76
+ let iteration = 0;
77
+ const maxIterations = 10;
78
+ let finalResponse = null;
79
+
80
+ while (iteration < maxIterations) {
81
+ 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
+ }
106
+
107
+ const data = await response.json();
108
+ const assistantMessage = data.choices[0].message;
109
+
110
+ console.log('[Groq] Response type:', assistantMessage.tool_calls ? 'tool_calls' : 'text');
111
+
112
+ // Add assistant message to history
113
+ messages.push(assistantMessage);
114
+
115
+ // Check for tool calls
116
+ if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
117
+ console.log(`[Groq] Tool calls: ${assistantMessage.tool_calls.length}`);
118
+
119
+ // Execute tools locally
120
+ const toolCalls = assistantMessage.tool_calls.map(tc => ({
121
+ id: tc.id,
122
+ name: tc.function.name,
123
+ input: JSON.parse(tc.function.arguments)
124
+ }));
36
125
 
37
- // Custom AI provider varsa ekle
38
- if (config.aiProvider && config.aiApiKey) {
39
- body.custom_provider = config.aiProvider;
40
- body.custom_api_key = config.aiApiKey;
41
-
42
- // Model varsa ekle
43
- if (config.aiModel) {
44
- body.model = config.aiModel;
126
+ const toolResults = await executeToolCalls(toolCalls);
127
+
128
+ // Add tool results to messages
129
+ for (const result of toolResults) {
130
+ const toolCall = assistantMessage.tool_calls.find(tc => tc.id === result.id);
131
+
132
+ messages.push({
133
+ role: 'tool',
134
+ tool_call_id: result.id,
135
+ name: result.name,
136
+ content: result.result.success
137
+ ? (result.result.output || JSON.stringify(result.result))
138
+ : `Error: ${result.result.error}`
139
+ });
140
+ }
141
+
142
+ // Continue loop to get final response
143
+ continue;
45
144
  }
145
+
146
+ // No tool calls, we have final response
147
+ finalResponse = assistantMessage.content;
148
+ break;
46
149
  }
47
150
 
48
- // Debug: Request body'yi logla
49
- console.log('\n[DEBUG] API Request Body:', JSON.stringify(body, null, 2));
50
-
51
- const data = await request('/api/agent/message', {
52
- method: 'POST',
53
- headers: {
54
- Authorization: `Bearer ${apiKey}`,
55
- 'X-User-ID': 'cli-user'
56
- },
57
- body: JSON.stringify(body),
58
- });
59
-
60
- // Debug: API response'u logla
61
- console.log('[DEBUG] API Response:', JSON.stringify(data, null, 2), '\n');
62
-
63
- // Backend response formatını CLI formatına çevir
64
- // Backend: { response: "...", message_id: "...", bot_id: "..." }
65
- // CLI beklediği: { reply: "...", conversation_id: "..." }
66
- if (data.response && !data.reply) {
67
- data.reply = data.response;
151
+ if (iteration >= maxIterations) {
152
+ console.log('\n[Groq] Max iterations reached');
153
+ finalResponse = finalResponse || 'Max tool execution iterations reached.';
68
154
  }
69
- if (data.message_id && !data.conversation_id) {
70
- data.conversation_id = data.message_id;
155
+
156
+ // Save to conversation history (only user and final assistant message)
157
+ history.push({ role: 'user', content: message });
158
+ history.push({ role: 'assistant', content: finalResponse });
159
+
160
+ // Keep history limited to last 20 messages
161
+ if (history.length > 20) {
162
+ conversationHistory.set(convId, history.slice(-20));
71
163
  }
72
164
 
73
- return data;
165
+ return {
166
+ reply: finalResponse,
167
+ conversation_id: convId,
168
+ message_id: `msg_${Date.now()}`,
169
+ success: true
170
+ };
74
171
  }
75
172
 
76
- async function callWithRetry(fn, maxRetries = 3, delay = 2000) {
77
- for (let i = 0; i < maxRetries; i++) {
78
- const result = await fn();
79
- const reply = result?.reply || result?.message || '';
80
- if (reply && !reply.includes('Şu an yanıt veremiyorum') && !reply.includes('cannot respond')) {
81
- return result;
82
- }
83
- if (i < maxRetries - 1) {
84
- await new Promise(r => setTimeout(r, delay));
85
- }
173
+ /**
174
+ * Clear conversation history
175
+ */
176
+ function clearConversation(conversationId) {
177
+ if (conversationId) {
178
+ conversationHistory.delete(conversationId);
179
+ } else {
180
+ conversationHistory.clear();
86
181
  }
87
- return { reply: 'API şu an meşgul, lütfen tekrar deneyin.' };
88
182
  }
89
183
 
90
- async function sendMessage(...args) {
91
- return callWithRetry(() => _sendMessage(...args));
184
+ /**
185
+ * Legacy function for compatibility
186
+ */
187
+ async function sendMessage(apiKey, botId, message, conversationId = null, skillPrompts = '') {
188
+ // 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.";
190
+
191
+ return sendMessageToGroq(apiKey, message, conversationId, systemPrompt);
92
192
  }
93
193
 
194
+ /**
195
+ * Validate API key (not used in v2.0.0, kept for compatibility)
196
+ */
94
197
  async function validateApiKey(apiKey) {
95
- try {
96
- const result = await getBots(apiKey);
97
- return true;
98
- } catch (err) {
99
- return false;
100
- }
198
+ const groqApiKey = getGroqApiKey();
199
+ return groqApiKey !== null;
200
+ }
201
+
202
+ /**
203
+ * Get bots (not used in v2.0.0, kept for compatibility)
204
+ */
205
+ async function getBots(apiKey) {
206
+ return {
207
+ bots: [
208
+ {
209
+ id: 'groq-direct',
210
+ name: 'Groq Direct',
211
+ ai_provider: 'groq',
212
+ model: 'llama-3.3-70b-versatile'
213
+ }
214
+ ]
215
+ };
101
216
  }
102
217
 
103
218
  module.exports = {
104
- getBots,
105
219
  sendMessage,
220
+ sendMessageToGroq,
106
221
  validateApiKey,
107
- _sendMessage, // Export for tool execution loop
222
+ getBots,
223
+ clearConversation,
224
+ getGroqApiKey,
225
+ _sendMessage: sendMessage, // Alias for compatibility
108
226
  };