natureco-cli 1.1.5 → 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.5",
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.5</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.5',
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,111 +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
17
3
 
18
- return response.json();
19
- }
4
+ const { getConfig } = require('./config');
5
+ const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
20
6
 
21
- async function getBots(apiKey) {
22
- return request('/api/v1/bots', {
23
- headers: { Authorization: `Bearer ${apiKey}` },
24
- });
25
- }
7
+ // Conversation history for multi-turn chat
8
+ const conversationHistory = new Map();
26
9
 
27
- async function _sendMessage(apiKey, botId, message, conversationId = null, skillPrompts = '', toolDefinitions = null) {
28
- const { getConfig } = require('./config');
10
+ /**
11
+ * Get Groq API key from config
12
+ */
13
+ function getGroqApiKey() {
29
14
  const config = getConfig();
15
+ return config.groqApiKey || null;
16
+ }
17
+
18
+ /**
19
+ * Format tool definitions for Groq API
20
+ */
21
+ function formatToolsForGroq() {
22
+ const tools = getToolDefinitions();
30
23
 
31
- // bot_id boş veya undefined ise config'den oku
32
- const finalBotId = botId || config.defaultBotId;
24
+ return tools.map(tool => ({
25
+ type: 'function',
26
+ function: {
27
+ name: tool.name,
28
+ description: tool.description,
29
+ parameters: tool.inputSchema
30
+ }
31
+ }));
32
+ }
33
+
34
+ /**
35
+ * Send message to Groq with tool support
36
+ */
37
+ async function sendMessageToGroq(apiKey, message, conversationId = null, systemPrompt = null) {
38
+ const groqApiKey = getGroqApiKey();
33
39
 
34
- const body = {
35
- message,
36
- user_id: 'cli-user',
37
- bot_id: finalBotId
38
- };
40
+ if (!groqApiKey) {
41
+ throw new Error('Groq API key not found. Set it with: natureco config set groqApiKey gsk_xxx');
42
+ }
39
43
 
40
- // Custom AI provider varsa ekle
41
- if (config.aiProvider && config.aiApiKey) {
42
- body.custom_provider = config.aiProvider;
43
- body.custom_api_key = config.aiApiKey;
44
-
45
- // Model varsa ekle
46
- if (config.aiModel) {
47
- body.model = config.aiModel;
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}`);
48
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
+ }));
120
+
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;
139
+ }
140
+
141
+ // No tool calls, we have final response
142
+ finalResponse = assistantMessage.content;
143
+ break;
49
144
  }
50
145
 
51
- // Debug: Request body'yi logla
52
- console.log('\n[DEBUG] API Request Body:', JSON.stringify(body, null, 2));
53
-
54
- const data = await request('/api/agent/message', {
55
- method: 'POST',
56
- headers: {
57
- Authorization: `Bearer ${apiKey}`,
58
- 'X-User-ID': 'cli-user'
59
- },
60
- body: JSON.stringify(body),
61
- });
62
-
63
- // Debug: API response'u logla
64
- console.log('[DEBUG] API Response:', JSON.stringify(data, null, 2), '\n');
65
-
66
- // Backend response formatını CLI formatına çevir
67
- // Backend: { response: "...", message_id: "...", bot_id: "..." }
68
- // CLI beklediği: { reply: "...", conversation_id: "..." }
69
- if (data.response && !data.reply) {
70
- data.reply = data.response;
146
+ if (iteration >= maxIterations) {
147
+ console.log('\n[Groq] Max iterations reached');
148
+ finalResponse = finalResponse || 'Max tool execution iterations reached.';
71
149
  }
72
- if (data.message_id && !data.conversation_id) {
73
- 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));
74
158
  }
75
159
 
76
- return data;
160
+ return {
161
+ reply: finalResponse,
162
+ conversation_id: convId,
163
+ message_id: `msg_${Date.now()}`,
164
+ success: true
165
+ };
77
166
  }
78
167
 
79
- async function callWithRetry(fn, maxRetries = 3, delay = 2000) {
80
- for (let i = 0; i < maxRetries; i++) {
81
- const result = await fn();
82
- const reply = result?.reply || result?.message || '';
83
- if (reply && !reply.includes('Şu an yanıt veremiyorum') && !reply.includes('cannot respond')) {
84
- return result;
85
- }
86
- if (i < maxRetries - 1) {
87
- await new Promise(r => setTimeout(r, delay));
88
- }
168
+ /**
169
+ * Clear conversation history
170
+ */
171
+ function clearConversation(conversationId) {
172
+ if (conversationId) {
173
+ conversationHistory.delete(conversationId);
174
+ } else {
175
+ conversationHistory.clear();
89
176
  }
90
- return { reply: 'API şu an meşgul, lütfen tekrar deneyin.' };
91
177
  }
92
178
 
93
- async function sendMessage(...args) {
94
- 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);
95
187
  }
96
188
 
189
+ /**
190
+ * Validate API key (not used in v2.0.0, kept for compatibility)
191
+ */
97
192
  async function validateApiKey(apiKey) {
98
- try {
99
- const result = await getBots(apiKey);
100
- return true;
101
- } catch (err) {
102
- return false;
103
- }
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
+ };
104
211
  }
105
212
 
106
213
  module.exports = {
107
- getBots,
108
214
  sendMessage,
215
+ sendMessageToGroq,
109
216
  validateApiKey,
110
- _sendMessage, // Export for tool execution loop
217
+ getBots,
218
+ clearConversation,
219
+ getGroqApiKey,
220
+ _sendMessage: sendMessage, // Alias for compatibility
111
221
  };