natureco-cli 2.9.1 โ 2.10.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
|
@@ -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.10.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.10.0',
|
|
345
345
|
bots: cfg.bots || [],
|
|
346
346
|
telegramToken: cfg.telegramToken || null,
|
|
347
347
|
whatsappConnected: cfg.whatsappConnected || false,
|
|
@@ -138,7 +138,7 @@ async function startGateway() {
|
|
|
138
138
|
|
|
139
139
|
async function runGatewayWorker() {
|
|
140
140
|
// This runs in the background
|
|
141
|
-
log('gateway', 'Starting NatureCo Gateway v2.9.
|
|
141
|
+
log('gateway', 'Starting NatureCo Gateway v2.9.2...', 'green');
|
|
142
142
|
|
|
143
143
|
// Load config
|
|
144
144
|
const { getConfig } = require('../utils/config');
|
|
@@ -600,13 +600,70 @@ function startCronJobs(config) {
|
|
|
600
600
|
log('cron', `Triggered: ${cronJob.name}`, 'yellow');
|
|
601
601
|
|
|
602
602
|
try {
|
|
603
|
-
//
|
|
604
|
-
const {
|
|
605
|
-
const
|
|
603
|
+
// Get provider config
|
|
604
|
+
const { getConfig } = require('../utils/config');
|
|
605
|
+
const cfg = getConfig();
|
|
606
606
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
607
|
+
if (!cfg.providerUrl || !cfg.providerApiKey) {
|
|
608
|
+
log('cron', 'Provider not configured', 'red');
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const isAnthropic = cfg.providerUrl.includes('anthropic.com');
|
|
613
|
+
|
|
614
|
+
log('cron', `Sending prompt to AI (no tools)...`, 'cyan');
|
|
615
|
+
|
|
616
|
+
// Make direct API call without tools
|
|
617
|
+
let reply = '';
|
|
618
|
+
|
|
619
|
+
if (isAnthropic) {
|
|
620
|
+
// Anthropic API
|
|
621
|
+
const response = await fetch(`${cfg.providerUrl}/v1/messages`, {
|
|
622
|
+
method: 'POST',
|
|
623
|
+
headers: {
|
|
624
|
+
'x-api-key': cfg.providerApiKey,
|
|
625
|
+
'anthropic-version': '2023-06-01',
|
|
626
|
+
'Content-Type': 'application/json',
|
|
627
|
+
},
|
|
628
|
+
body: JSON.stringify({
|
|
629
|
+
model: cfg.providerModel || 'claude-3-5-sonnet-20241022',
|
|
630
|
+
max_tokens: 1000,
|
|
631
|
+
messages: [{ role: 'user', content: cronJob.prompt }]
|
|
632
|
+
}),
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
if (!response.ok) {
|
|
636
|
+
const errorText = await response.text();
|
|
637
|
+
throw new Error(`Anthropic API error: ${response.status} - ${errorText}`);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const data = await response.json();
|
|
641
|
+
reply = data.content.find(c => c.type === 'text')?.text || '';
|
|
642
|
+
|
|
643
|
+
} else {
|
|
644
|
+
// OpenAI-compatible API
|
|
645
|
+
const response = await fetch(`${cfg.providerUrl}/chat/completions`, {
|
|
646
|
+
method: 'POST',
|
|
647
|
+
headers: {
|
|
648
|
+
'Authorization': `Bearer ${cfg.providerApiKey}`,
|
|
649
|
+
'Content-Type': 'application/json',
|
|
650
|
+
},
|
|
651
|
+
body: JSON.stringify({
|
|
652
|
+
model: cfg.providerModel || 'llama-3.1-8b-instant',
|
|
653
|
+
messages: [{ role: 'user', content: cronJob.prompt }],
|
|
654
|
+
temperature: 0.7,
|
|
655
|
+
max_tokens: 1000,
|
|
656
|
+
}),
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
if (!response.ok) {
|
|
660
|
+
const errorText = await response.text();
|
|
661
|
+
throw new Error(`Provider API error: ${response.status} - ${errorText}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const data = await response.json();
|
|
665
|
+
reply = data.choices[0].message.content;
|
|
666
|
+
}
|
|
610
667
|
|
|
611
668
|
if (!reply) {
|
|
612
669
|
log('cron', `No response from AI`, 'red');
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { EventEmitter } = require('events');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MCP Client - JSON-RPC over stdio
|
|
6
|
+
* Manages communication with MCP servers via stdin/stdout
|
|
7
|
+
*/
|
|
8
|
+
class MCPClient extends EventEmitter {
|
|
9
|
+
constructor(command, args = [], env = {}) {
|
|
10
|
+
super();
|
|
11
|
+
this.command = command;
|
|
12
|
+
this.args = args;
|
|
13
|
+
this.env = { ...process.env, ...env };
|
|
14
|
+
this.process = null;
|
|
15
|
+
this.messageId = 0;
|
|
16
|
+
this.pendingRequests = new Map();
|
|
17
|
+
this.buffer = '';
|
|
18
|
+
this.isInitialized = false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Start MCP server process
|
|
23
|
+
*/
|
|
24
|
+
async start() {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
try {
|
|
27
|
+
this.process = spawn(this.command, this.args, {
|
|
28
|
+
env: this.env,
|
|
29
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Handle stdout (JSON-RPC responses)
|
|
33
|
+
this.process.stdout.on('data', (data) => {
|
|
34
|
+
this.handleData(data);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Handle stderr (logs)
|
|
38
|
+
this.process.stderr.on('data', (data) => {
|
|
39
|
+
const message = data.toString().trim();
|
|
40
|
+
if (message) {
|
|
41
|
+
this.emit('log', message);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle process exit
|
|
46
|
+
this.process.on('exit', (code) => {
|
|
47
|
+
this.emit('exit', code);
|
|
48
|
+
this.cleanup();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Handle process error
|
|
52
|
+
this.process.on('error', (err) => {
|
|
53
|
+
reject(err);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Initialize connection
|
|
57
|
+
this.initialize()
|
|
58
|
+
.then(() => {
|
|
59
|
+
this.isInitialized = true;
|
|
60
|
+
resolve();
|
|
61
|
+
})
|
|
62
|
+
.catch(reject);
|
|
63
|
+
|
|
64
|
+
} catch (err) {
|
|
65
|
+
reject(err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle incoming data from stdout
|
|
72
|
+
*/
|
|
73
|
+
handleData(data) {
|
|
74
|
+
this.buffer += data.toString();
|
|
75
|
+
|
|
76
|
+
// Process complete JSON-RPC messages (newline-delimited)
|
|
77
|
+
const lines = this.buffer.split('\n');
|
|
78
|
+
this.buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
79
|
+
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
if (!line.trim()) continue;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const message = JSON.parse(line);
|
|
85
|
+
this.handleMessage(message);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
this.emit('error', new Error(`Failed to parse JSON-RPC message: ${err.message}`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Handle parsed JSON-RPC message
|
|
94
|
+
*/
|
|
95
|
+
handleMessage(message) {
|
|
96
|
+
// Response to our request
|
|
97
|
+
if (message.id !== undefined && this.pendingRequests.has(message.id)) {
|
|
98
|
+
const { resolve, reject } = this.pendingRequests.get(message.id);
|
|
99
|
+
this.pendingRequests.delete(message.id);
|
|
100
|
+
|
|
101
|
+
if (message.error) {
|
|
102
|
+
reject(new Error(message.error.message || 'MCP request failed'));
|
|
103
|
+
} else {
|
|
104
|
+
resolve(message.result);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Notification from server
|
|
108
|
+
else if (message.method) {
|
|
109
|
+
this.emit('notification', message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Send JSON-RPC request
|
|
115
|
+
*/
|
|
116
|
+
async sendRequest(method, params = {}) {
|
|
117
|
+
if (!this.process || this.process.killed) {
|
|
118
|
+
throw new Error('MCP server not running');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const id = ++this.messageId;
|
|
122
|
+
const request = {
|
|
123
|
+
jsonrpc: '2.0',
|
|
124
|
+
id,
|
|
125
|
+
method,
|
|
126
|
+
params,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
131
|
+
|
|
132
|
+
// Timeout after 30 seconds
|
|
133
|
+
const timeout = setTimeout(() => {
|
|
134
|
+
if (this.pendingRequests.has(id)) {
|
|
135
|
+
this.pendingRequests.delete(id);
|
|
136
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
137
|
+
}
|
|
138
|
+
}, 30000);
|
|
139
|
+
|
|
140
|
+
// Clear timeout on response
|
|
141
|
+
const originalResolve = resolve;
|
|
142
|
+
const originalReject = reject;
|
|
143
|
+
|
|
144
|
+
this.pendingRequests.set(id, {
|
|
145
|
+
resolve: (result) => {
|
|
146
|
+
clearTimeout(timeout);
|
|
147
|
+
originalResolve(result);
|
|
148
|
+
},
|
|
149
|
+
reject: (err) => {
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
originalReject(err);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Send request
|
|
156
|
+
const message = JSON.stringify(request) + '\n';
|
|
157
|
+
this.process.stdin.write(message);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Initialize MCP connection
|
|
163
|
+
*/
|
|
164
|
+
async initialize() {
|
|
165
|
+
const result = await this.sendRequest('initialize', {
|
|
166
|
+
protocolVersion: '2024-11-05',
|
|
167
|
+
capabilities: {
|
|
168
|
+
roots: {
|
|
169
|
+
listChanged: false
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
clientInfo: {
|
|
173
|
+
name: 'natureco-cli',
|
|
174
|
+
version: '2.10.0',
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Send initialized notification
|
|
179
|
+
const notification = {
|
|
180
|
+
jsonrpc: '2.0',
|
|
181
|
+
method: 'notifications/initialized',
|
|
182
|
+
};
|
|
183
|
+
this.process.stdin.write(JSON.stringify(notification) + '\n');
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* List available tools
|
|
190
|
+
*/
|
|
191
|
+
async listTools() {
|
|
192
|
+
if (!this.isInitialized) {
|
|
193
|
+
throw new Error('MCP client not initialized');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result = await this.sendRequest('tools/list', {});
|
|
197
|
+
return result.tools || [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Call a tool
|
|
202
|
+
*/
|
|
203
|
+
async callTool(toolName, args = {}) {
|
|
204
|
+
if (!this.isInitialized) {
|
|
205
|
+
throw new Error('MCP client not initialized');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const result = await this.sendRequest('tools/call', {
|
|
209
|
+
name: toolName,
|
|
210
|
+
arguments: args,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Stop MCP server
|
|
218
|
+
*/
|
|
219
|
+
stop() {
|
|
220
|
+
if (this.process && !this.process.killed) {
|
|
221
|
+
this.process.kill();
|
|
222
|
+
}
|
|
223
|
+
this.cleanup();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Cleanup resources
|
|
228
|
+
*/
|
|
229
|
+
cleanup() {
|
|
230
|
+
this.pendingRequests.clear();
|
|
231
|
+
this.buffer = '';
|
|
232
|
+
this.isInitialized = false;
|
|
233
|
+
this.process = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Create and start MCP client
|
|
239
|
+
*/
|
|
240
|
+
async function createMCPClient(command, args = [], env = {}) {
|
|
241
|
+
const client = new MCPClient(command, args, env);
|
|
242
|
+
await client.start();
|
|
243
|
+
return client;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Test function - Start filesystem MCP server and list tools
|
|
248
|
+
*/
|
|
249
|
+
async function test() {
|
|
250
|
+
console.log('๐งช Testing MCP Client...\n');
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
// Start filesystem MCP server
|
|
254
|
+
console.log('Starting filesystem MCP server...');
|
|
255
|
+
const client = await createMCPClient('npx', [
|
|
256
|
+
'-y',
|
|
257
|
+
'@modelcontextprotocol/server-filesystem',
|
|
258
|
+
process.cwd(),
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
console.log('โ
MCP server started\n');
|
|
262
|
+
|
|
263
|
+
// Listen to logs
|
|
264
|
+
client.on('log', (message) => {
|
|
265
|
+
console.log('[MCP Log]', message);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// List tools
|
|
269
|
+
console.log('Listing tools...');
|
|
270
|
+
const tools = await client.listTools();
|
|
271
|
+
console.log(`โ
Found ${tools.length} tools:\n`);
|
|
272
|
+
|
|
273
|
+
tools.forEach((tool) => {
|
|
274
|
+
console.log(` - ${tool.name}`);
|
|
275
|
+
console.log(` ${tool.description}`);
|
|
276
|
+
console.log('');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Test tool call - read directory
|
|
280
|
+
if (tools.find(t => t.name === 'read_file')) {
|
|
281
|
+
console.log('Testing read_file tool...');
|
|
282
|
+
try {
|
|
283
|
+
const result = await client.callTool('read_file', {
|
|
284
|
+
path: 'package.json',
|
|
285
|
+
});
|
|
286
|
+
console.log('โ
Tool call successful');
|
|
287
|
+
console.log('Result:', JSON.stringify(result, null, 2).substring(0, 200) + '...\n');
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.log('โ Tool call failed:', err.message, '\n');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Stop server
|
|
294
|
+
console.log('Stopping MCP server...');
|
|
295
|
+
client.stop();
|
|
296
|
+
console.log('โ
Test completed\n');
|
|
297
|
+
|
|
298
|
+
process.exit(0);
|
|
299
|
+
|
|
300
|
+
} catch (err) {
|
|
301
|
+
console.error('โ Test failed:', err.message);
|
|
302
|
+
console.error(err.stack);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
MCPClient,
|
|
309
|
+
createMCPClient,
|
|
310
|
+
test,
|
|
311
|
+
};
|