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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "2.9.1",
3
+ "version": "2.10.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">v2.9.1</div>
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.9.1',
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.1...', 'green');
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
- // Send prompt to AI
604
- const { sendMessage } = require('../utils/api');
605
- const conversationId = `cron_${cronJob.name.replace(/\s+/g, '_')}`;
603
+ // Get provider config
604
+ const { getConfig } = require('../utils/config');
605
+ const cfg = getConfig();
606
606
 
607
- log('cron', `Sending prompt to AI...`, 'cyan');
608
- const response = await sendMessage(null, null, cronJob.prompt, conversationId);
609
- const reply = response?.reply || response?.message || '';
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
+ };