natureco-cli 1.1.6 → 2.0.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": "1.1.6",
3
+ "version": "2.0.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">v1.1.6</div>
214
+ <div class="version-badge" id="version-badge">v2.0.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: 'v1.1.6',
344
+ version: 'v2.0.0',
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,221 @@
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');
29
- const config = getConfig();
34
+ /**
35
+ * Send message to Groq with tool support
36
+ */
37
+ async function sendMessageToGroq(apiKey, message, conversationId = null, systemPrompt = null) {
38
+ const groqApiKey = getGroqApiKey();
30
39
 
31
- // Backend otomatik ilk botu seçsin, bot_id gönderme
32
- const body = {
33
- message,
34
- user_id: 'cli-user'
35
- };
40
+ if (!groqApiKey) {
41
+ throw new Error('Groq API key not found. Set it with: natureco config set groqApiKey gsk_xxx');
42
+ }
43
+
44
+ // Get or create conversation history
45
+ const convId = conversationId || `conv_${Date.now()}`;
46
+ if (!conversationHistory.has(convId)) {
47
+ conversationHistory.set(convId, []);
48
+ }
49
+ const history = conversationHistory.get(convId);
50
+
51
+ // Add system prompt if provided
52
+ const messages = [];
53
+ if (systemPrompt) {
54
+ messages.push({ role: 'system', content: systemPrompt });
55
+ }
56
+
57
+ // Add conversation history
58
+ messages.push(...history);
59
+
60
+ // Add user message
61
+ messages.push({ role: 'user', content: message });
62
+
63
+ // Get tool definitions
64
+ const tools = formatToolsForGroq();
65
+
66
+ console.log('\n[Groq] Sending request...');
67
+ console.log('[Groq] Messages:', messages.length);
68
+ console.log('[Groq] Tools:', tools.length);
69
+
70
+ // Tool execution loop (max 10 iterations)
71
+ let iteration = 0;
72
+ const maxIterations = 10;
73
+ let finalResponse = null;
74
+
75
+ while (iteration < maxIterations) {
76
+ 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
+ }
101
+
102
+ const data = await response.json();
103
+ const assistantMessage = data.choices[0].message;
104
+
105
+ console.log('[Groq] Response type:', assistantMessage.tool_calls ? 'tool_calls' : 'text');
106
+
107
+ // Add assistant message to history
108
+ messages.push(assistantMessage);
109
+
110
+ // Check for tool calls
111
+ if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
112
+ console.log(`[Groq] Tool calls: ${assistantMessage.tool_calls.length}`);
113
+
114
+ // Execute tools locally
115
+ const toolCalls = assistantMessage.tool_calls.map(tc => ({
116
+ id: tc.id,
117
+ name: tc.function.name,
118
+ input: JSON.parse(tc.function.arguments)
119
+ }));
36
120
 
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;
121
+ const toolResults = await executeToolCalls(toolCalls);
122
+
123
+ // Add tool results to messages
124
+ for (const result of toolResults) {
125
+ const toolCall = assistantMessage.tool_calls.find(tc => tc.id === result.id);
126
+
127
+ messages.push({
128
+ role: 'tool',
129
+ tool_call_id: result.id,
130
+ name: result.name,
131
+ content: result.result.success
132
+ ? (result.result.output || JSON.stringify(result.result))
133
+ : `Error: ${result.result.error}`
134
+ });
135
+ }
136
+
137
+ // Continue loop to get final response
138
+ continue;
45
139
  }
140
+
141
+ // No tool calls, we have final response
142
+ finalResponse = assistantMessage.content;
143
+ break;
46
144
  }
47
145
 
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;
146
+ if (iteration >= maxIterations) {
147
+ console.log('\n[Groq] Max iterations reached');
148
+ finalResponse = finalResponse || 'Max tool execution iterations reached.';
68
149
  }
69
- if (data.message_id && !data.conversation_id) {
70
- data.conversation_id = data.message_id;
150
+
151
+ // Save to conversation history (only user and final assistant message)
152
+ history.push({ role: 'user', content: message });
153
+ history.push({ role: 'assistant', content: finalResponse });
154
+
155
+ // Keep history limited to last 20 messages
156
+ if (history.length > 20) {
157
+ conversationHistory.set(convId, history.slice(-20));
71
158
  }
72
159
 
73
- return data;
160
+ return {
161
+ reply: finalResponse,
162
+ conversation_id: convId,
163
+ message_id: `msg_${Date.now()}`,
164
+ success: true
165
+ };
74
166
  }
75
167
 
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
- }
168
+ /**
169
+ * Clear conversation history
170
+ */
171
+ function clearConversation(conversationId) {
172
+ if (conversationId) {
173
+ conversationHistory.delete(conversationId);
174
+ } else {
175
+ conversationHistory.clear();
86
176
  }
87
- return { reply: 'API şu an meşgul, lütfen tekrar deneyin.' };
88
177
  }
89
178
 
90
- async function sendMessage(...args) {
91
- return callWithRetry(() => _sendMessage(...args));
179
+ /**
180
+ * Legacy function for compatibility
181
+ */
182
+ async function sendMessage(apiKey, botId, message, conversationId = null, skillPrompts = '') {
183
+ // System prompt for terminal assistant
184
+ 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
+
186
+ return sendMessageToGroq(apiKey, message, conversationId, systemPrompt);
92
187
  }
93
188
 
189
+ /**
190
+ * Validate API key (not used in v2.0.0, kept for compatibility)
191
+ */
94
192
  async function validateApiKey(apiKey) {
95
- try {
96
- const result = await getBots(apiKey);
97
- return true;
98
- } catch (err) {
99
- return false;
100
- }
193
+ const groqApiKey = getGroqApiKey();
194
+ return groqApiKey !== null;
195
+ }
196
+
197
+ /**
198
+ * Get bots (not used in v2.0.0, kept for compatibility)
199
+ */
200
+ async function getBots(apiKey) {
201
+ return {
202
+ bots: [
203
+ {
204
+ id: 'groq-direct',
205
+ name: 'Groq Direct',
206
+ ai_provider: 'groq',
207
+ model: 'llama-3.3-70b-versatile'
208
+ }
209
+ ]
210
+ };
101
211
  }
102
212
 
103
213
  module.exports = {
104
- getBots,
105
214
  sendMessage,
215
+ sendMessageToGroq,
106
216
  validateApiKey,
107
- _sendMessage, // Export for tool execution loop
217
+ getBots,
218
+ clearConversation,
219
+ getGroqApiKey,
220
+ _sendMessage: sendMessage, // Alias for compatibility
108
221
  };