natureco-cli 2.0.1 → 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 +166 -67
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.0
|
|
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
|
|
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,18 +53,116 @@ function formatToolsForGroq() {
|
|
|
32
53
|
}
|
|
33
54
|
|
|
34
55
|
/**
|
|
35
|
-
*
|
|
56
|
+
* Format tool definitions for Anthropic API
|
|
36
57
|
*/
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const groqApiKey = config.groqApiKey;
|
|
58
|
+
function formatToolsForAnthropic() {
|
|
59
|
+
const tools = getToolDefinitions();
|
|
40
60
|
|
|
41
|
-
|
|
42
|
-
|
|
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}`);
|
|
43
93
|
}
|
|
44
94
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
);
|
|
165
|
+
}
|
|
47
166
|
|
|
48
167
|
// Get or create conversation history
|
|
49
168
|
const convId = conversationId || `conv_${Date.now()}`;
|
|
@@ -52,25 +171,25 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
|
|
|
52
171
|
}
|
|
53
172
|
const history = conversationHistory.get(convId);
|
|
54
173
|
|
|
55
|
-
//
|
|
174
|
+
// Build messages
|
|
56
175
|
const messages = [];
|
|
57
176
|
if (systemPrompt) {
|
|
58
177
|
messages.push({ role: 'system', content: systemPrompt });
|
|
59
178
|
}
|
|
60
|
-
|
|
61
|
-
// Add conversation history
|
|
62
179
|
messages.push(...history);
|
|
63
|
-
|
|
64
|
-
// Add user message
|
|
65
180
|
messages.push({ role: 'user', content: message });
|
|
66
181
|
|
|
67
182
|
// Get tool definitions
|
|
68
|
-
const tools =
|
|
183
|
+
const tools = providerConfig.isAnthropic
|
|
184
|
+
? formatToolsForAnthropic()
|
|
185
|
+
: formatToolsForOpenAI();
|
|
69
186
|
|
|
70
|
-
console.log('\n[
|
|
71
|
-
console.log('[
|
|
72
|
-
console.log('[
|
|
73
|
-
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);
|
|
74
193
|
|
|
75
194
|
// Tool execution loop (max 10 iterations)
|
|
76
195
|
let iteration = 0;
|
|
@@ -79,42 +198,21 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
|
|
|
79
198
|
|
|
80
199
|
while (iteration < maxIterations) {
|
|
81
200
|
iteration++;
|
|
82
|
-
console.log(`\n[
|
|
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
|
-
}
|
|
201
|
+
console.log(`\n[Provider] Iteration ${iteration}/${maxIterations}`);
|
|
106
202
|
|
|
107
|
-
|
|
108
|
-
const assistantMessage =
|
|
203
|
+
// Call provider API
|
|
204
|
+
const assistantMessage = providerConfig.isAnthropic
|
|
205
|
+
? await sendMessageAnthropic(providerConfig, messages, tools)
|
|
206
|
+
: await sendMessageOpenAICompatible(providerConfig, messages, tools);
|
|
109
207
|
|
|
110
|
-
console.log('[
|
|
208
|
+
console.log('[Provider] Response type:', assistantMessage.tool_calls ? 'tool_calls' : 'text');
|
|
111
209
|
|
|
112
210
|
// Add assistant message to history
|
|
113
211
|
messages.push(assistantMessage);
|
|
114
212
|
|
|
115
213
|
// Check for tool calls
|
|
116
214
|
if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
|
|
117
|
-
console.log(`[
|
|
215
|
+
console.log(`[Provider] Tool calls: ${assistantMessage.tool_calls.length}`);
|
|
118
216
|
|
|
119
217
|
// Execute tools locally
|
|
120
218
|
const toolCalls = assistantMessage.tool_calls.map(tc => ({
|
|
@@ -127,8 +225,6 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
|
|
|
127
225
|
|
|
128
226
|
// Add tool results to messages
|
|
129
227
|
for (const result of toolResults) {
|
|
130
|
-
const toolCall = assistantMessage.tool_calls.find(tc => tc.id === result.id);
|
|
131
|
-
|
|
132
228
|
messages.push({
|
|
133
229
|
role: 'tool',
|
|
134
230
|
tool_call_id: result.id,
|
|
@@ -149,7 +245,7 @@ async function sendMessageToGroq(apiKey, message, conversationId = null, systemP
|
|
|
149
245
|
}
|
|
150
246
|
|
|
151
247
|
if (iteration >= maxIterations) {
|
|
152
|
-
console.log('\n[
|
|
248
|
+
console.log('\n[Provider] Max iterations reached');
|
|
153
249
|
finalResponse = finalResponse || 'Max tool execution iterations reached.';
|
|
154
250
|
}
|
|
155
251
|
|
|
@@ -188,28 +284,31 @@ async function sendMessage(apiKey, botId, message, conversationId = null, skillP
|
|
|
188
284
|
// System prompt for terminal assistant
|
|
189
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.";
|
|
190
286
|
|
|
191
|
-
return
|
|
287
|
+
return sendMessageToProvider(apiKey, message, conversationId, systemPrompt);
|
|
192
288
|
}
|
|
193
289
|
|
|
194
290
|
/**
|
|
195
|
-
* Validate API key (not used in v2.
|
|
291
|
+
* Validate API key (not used in v2.x, kept for compatibility)
|
|
196
292
|
*/
|
|
197
293
|
async function validateApiKey(apiKey) {
|
|
198
|
-
const
|
|
199
|
-
return
|
|
294
|
+
const providerConfig = getProviderConfig();
|
|
295
|
+
return providerConfig !== null;
|
|
200
296
|
}
|
|
201
297
|
|
|
202
298
|
/**
|
|
203
|
-
* Get bots (not used in v2.
|
|
299
|
+
* Get bots (not used in v2.x, kept for compatibility)
|
|
204
300
|
*/
|
|
205
301
|
async function getBots(apiKey) {
|
|
302
|
+
const providerConfig = getProviderConfig();
|
|
303
|
+
const providerName = providerConfig?.isAnthropic ? 'Anthropic' : 'OpenAI-compatible';
|
|
304
|
+
|
|
206
305
|
return {
|
|
207
306
|
bots: [
|
|
208
307
|
{
|
|
209
|
-
id: '
|
|
210
|
-
name:
|
|
211
|
-
ai_provider:
|
|
212
|
-
model: '
|
|
308
|
+
id: 'universal-provider',
|
|
309
|
+
name: `Universal Provider (${providerName})`,
|
|
310
|
+
ai_provider: providerName,
|
|
311
|
+
model: providerConfig?.model || 'unknown'
|
|
213
312
|
}
|
|
214
313
|
]
|
|
215
314
|
};
|
|
@@ -217,10 +316,10 @@ async function getBots(apiKey) {
|
|
|
217
316
|
|
|
218
317
|
module.exports = {
|
|
219
318
|
sendMessage,
|
|
220
|
-
|
|
319
|
+
sendMessageToProvider,
|
|
221
320
|
validateApiKey,
|
|
222
321
|
getBots,
|
|
223
322
|
clearConversation,
|
|
224
|
-
|
|
323
|
+
getProviderConfig,
|
|
225
324
|
_sendMessage: sendMessage, // Alias for compatibility
|
|
226
325
|
};
|