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 +1 -1
- package/src/commands/dashboard.js +2 -2
- package/src/tools/bash.js +2 -1
- package/src/tools/list_dir.js +6 -3
- package/src/tools/read_file.js +24 -2
- package/src/utils/api.js +167 -63
package/package.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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 {
|
package/src/tools/list_dir.js
CHANGED
|
@@ -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
|
-
|
|
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: ${
|
|
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: ${
|
|
44
|
+
error: `Not a directory: ${params.path}`
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
|
package/src/tools/read_file.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
2
|
-
//
|
|
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
|
|
11
|
+
* Get provider configuration from config
|
|
12
12
|
*/
|
|
13
|
-
function
|
|
13
|
+
function getProviderConfig() {
|
|
14
14
|
const config = getConfig();
|
|
15
|
-
|
|
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
|
|
40
|
+
* Format tool definitions for OpenAI-compatible APIs
|
|
20
41
|
*/
|
|
21
|
-
function
|
|
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
|
-
*
|
|
56
|
+
* Format tool definitions for Anthropic API
|
|
36
57
|
*/
|
|
37
|
-
|
|
38
|
-
const
|
|
58
|
+
function formatToolsForAnthropic() {
|
|
59
|
+
const tools = getToolDefinitions();
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
//
|
|
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 =
|
|
183
|
+
const tools = providerConfig.isAnthropic
|
|
184
|
+
? formatToolsForAnthropic()
|
|
185
|
+
: formatToolsForOpenAI();
|
|
65
186
|
|
|
66
|
-
console.log('\n[
|
|
67
|
-
console.log('[
|
|
68
|
-
console.log('[
|
|
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[
|
|
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
|
-
|
|
103
|
-
const assistantMessage =
|
|
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('[
|
|
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(`[
|
|
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[
|
|
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
|
|
287
|
+
return sendMessageToProvider(apiKey, message, conversationId, systemPrompt);
|
|
187
288
|
}
|
|
188
289
|
|
|
189
290
|
/**
|
|
190
|
-
* Validate API key (not used in v2.
|
|
291
|
+
* Validate API key (not used in v2.x, kept for compatibility)
|
|
191
292
|
*/
|
|
192
293
|
async function validateApiKey(apiKey) {
|
|
193
|
-
const
|
|
194
|
-
return
|
|
294
|
+
const providerConfig = getProviderConfig();
|
|
295
|
+
return providerConfig !== null;
|
|
195
296
|
}
|
|
196
297
|
|
|
197
298
|
/**
|
|
198
|
-
* Get bots (not used in v2.
|
|
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: '
|
|
205
|
-
name:
|
|
206
|
-
ai_provider:
|
|
207
|
-
model: '
|
|
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
|
-
|
|
319
|
+
sendMessageToProvider,
|
|
216
320
|
validateApiKey,
|
|
217
321
|
getBots,
|
|
218
322
|
clearConversation,
|
|
219
|
-
|
|
323
|
+
getProviderConfig,
|
|
220
324
|
_sendMessage: sendMessage, // Alias for compatibility
|
|
221
325
|
};
|