natureco-cli 1.0.61 → 1.1.1

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/README.md CHANGED
@@ -9,6 +9,7 @@ Terminal-native AI agent CLI — chat with your bots, automate workflows, and co
9
9
  ## ✨ Features
10
10
 
11
11
  - **🤖 Multi-Bot Chat** — Interactive conversations with AI bots, support for multi-word bot names, auto-selection when no default
12
+ - **🛠️ Local Tool Execution** — Bash commands, file operations, HTTP requests — bots can execute tools locally with automatic retry loop
12
13
  - **🔌 Multi-Platform Integration** — Telegram, Discord, Slack, WhatsApp (QR code auth with Baileys)
13
14
  - **🎯 Skill System** — Extend capabilities with NatureHub and ClawHub skills
14
15
  - **🔧 MCP Support** — Model Context Protocol servers for filesystem, GitHub, databases
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "1.0.61",
3
+ "version": "1.1.1",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -2,7 +2,7 @@ const inquirer = require('inquirer');
2
2
  const chalk = require('chalk');
3
3
  const readline = require('readline');
4
4
  const { getApiKey } = require('../utils/config');
5
- const { getBots, sendMessage } = require('../utils/api');
5
+ const { getBots, sendMessage, _sendMessage } = require('../utils/api');
6
6
  const { getSkillPrompts, getSkills } = require('../utils/skills');
7
7
  const { getAgentsPrompt } = require('../utils/agents');
8
8
  const { addToHistory, getCommandHistory, clearHistory } = require('../utils/history');
@@ -11,6 +11,8 @@ const { getCommands, getCommandContent } = require('../utils/commands');
11
11
  const { runHooks } = require('../utils/hooks');
12
12
  const { createSession, loadSession, getLatestSession, addMessageToSession } = require('../utils/sessions');
13
13
  const { addBackgroundTask, updateBackgroundTask } = require('../utils/background');
14
+ const { getToolDefinitions, executeToolCalls } = require('../utils/tool-runner');
15
+ const { extractToolCalls } = require('../utils/tool-adapter');
14
16
 
15
17
  async function chat(botName, options = {}) {
16
18
  const apiKey = getApiKey();
@@ -466,7 +468,11 @@ ${lastCodeBlock}
466
468
  const loadingInterval = startLoadingAnimation();
467
469
 
468
470
  try {
469
- const response = await sendMessage(apiKey, bot.id, userMessage, conversationId, systemPrompt);
471
+ // Get tool definitions
472
+ const toolDefinitions = getToolDefinitions();
473
+
474
+ // Initial API call with tools
475
+ let response = await _sendMessage(apiKey, bot.id, userMessage, conversationId, systemPrompt, toolDefinitions);
470
476
 
471
477
  stopLoadingAnimation(loadingInterval);
472
478
 
@@ -474,6 +480,50 @@ ${lastCodeBlock}
474
480
  conversationId = response.conversation_id;
475
481
  }
476
482
 
483
+ // Tool execution loop
484
+ let maxIterations = 5;
485
+ let iteration = 0;
486
+
487
+ while (iteration < maxIterations) {
488
+ // Check for tool calls
489
+ const toolCalls = extractToolCalls(response);
490
+
491
+ if (!toolCalls || toolCalls.length === 0) {
492
+ // No more tool calls, break loop
493
+ break;
494
+ }
495
+
496
+ console.log(chalk.yellow(`\n🔧 Executing ${toolCalls.length} tool(s)...\n`));
497
+
498
+ // Execute tools
499
+ const toolResults = await executeToolCalls(toolCalls);
500
+
501
+ // Format results as message
502
+ const toolResultMessage = toolResults.map(tr => {
503
+ const resultStr = tr.result.success
504
+ ? (tr.result.output || JSON.stringify(tr.result))
505
+ : `Error: ${tr.result.error}`;
506
+ return `Tool: ${tr.name}\nResult: ${resultStr}`;
507
+ }).join('\n\n');
508
+
509
+ console.log(chalk.blue('\n📤 Sending tool results back to bot...\n'));
510
+
511
+ // Send tool results back to API
512
+ const loadingInterval2 = startLoadingAnimation();
513
+ response = await _sendMessage(apiKey, bot.id, toolResultMessage, conversationId, systemPrompt, toolDefinitions);
514
+ stopLoadingAnimation(loadingInterval2);
515
+
516
+ if (response.conversation_id) {
517
+ conversationId = response.conversation_id;
518
+ }
519
+
520
+ iteration++;
521
+ }
522
+
523
+ if (iteration >= maxIterations) {
524
+ console.log(chalk.yellow('\n⚠️ Max tool iterations reached\n'));
525
+ }
526
+
477
527
  let botReply = response.reply || response.message || 'No response';
478
528
 
479
529
  // Run post-message hooks
@@ -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.0.61</div>
214
+ <div class="version-badge" id="version-badge">v1.1.1</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.0.61',
344
+ version: 'v1.1.1',
345
345
  bots: cfg.bots || [],
346
346
  telegramToken: cfg.telegramToken || null,
347
347
  whatsappConnected: cfg.whatsappConnected || false,
@@ -0,0 +1,37 @@
1
+ const { execSync } = require('child_process');
2
+
3
+ module.exports = {
4
+ name: 'bash',
5
+ description: 'Execute bash commands in the terminal',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ command: {
10
+ type: 'string',
11
+ description: 'The bash command to execute'
12
+ }
13
+ },
14
+ required: ['command']
15
+ },
16
+
17
+ async execute(params) {
18
+ try {
19
+ const output = execSync(params.command, {
20
+ encoding: 'utf-8',
21
+ maxBuffer: 10 * 1024 * 1024, // 10MB
22
+ timeout: 30000 // 30 seconds
23
+ });
24
+
25
+ return {
26
+ success: true,
27
+ output: output.trim()
28
+ };
29
+ } catch (error) {
30
+ return {
31
+ success: false,
32
+ error: error.message,
33
+ stderr: error.stderr?.toString() || ''
34
+ };
35
+ }
36
+ }
37
+ };
@@ -0,0 +1,67 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ module.exports = {
5
+ name: 'filesystem',
6
+ description: 'List files and directories',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ path: {
11
+ type: 'string',
12
+ description: 'Directory path to list'
13
+ },
14
+ recursive: {
15
+ type: 'boolean',
16
+ description: 'List recursively',
17
+ default: false
18
+ }
19
+ },
20
+ required: ['path']
21
+ },
22
+
23
+ async execute(params) {
24
+ try {
25
+ const targetPath = path.resolve(params.path);
26
+
27
+ if (!fs.existsSync(targetPath)) {
28
+ return {
29
+ success: false,
30
+ error: 'Path does not exist'
31
+ };
32
+ }
33
+
34
+ const stats = fs.statSync(targetPath);
35
+
36
+ if (!stats.isDirectory()) {
37
+ return {
38
+ success: false,
39
+ error: 'Path is not a directory'
40
+ };
41
+ }
42
+
43
+ const files = fs.readdirSync(targetPath);
44
+ const items = files.map(file => {
45
+ const filePath = path.join(targetPath, file);
46
+ const fileStats = fs.statSync(filePath);
47
+ return {
48
+ name: file,
49
+ type: fileStats.isDirectory() ? 'directory' : 'file',
50
+ size: fileStats.size,
51
+ modified: fileStats.mtime
52
+ };
53
+ });
54
+
55
+ return {
56
+ success: true,
57
+ path: targetPath,
58
+ items
59
+ };
60
+ } catch (error) {
61
+ return {
62
+ success: false,
63
+ error: error.message
64
+ };
65
+ }
66
+ }
67
+ };
@@ -0,0 +1,56 @@
1
+ module.exports = {
2
+ name: 'http',
3
+ description: 'Make HTTP requests',
4
+ inputSchema: {
5
+ type: 'object',
6
+ properties: {
7
+ url: {
8
+ type: 'string',
9
+ description: 'URL to request'
10
+ },
11
+ method: {
12
+ type: 'string',
13
+ description: 'HTTP method (GET, POST, etc.)',
14
+ default: 'GET'
15
+ },
16
+ headers: {
17
+ type: 'object',
18
+ description: 'Request headers'
19
+ },
20
+ body: {
21
+ type: 'string',
22
+ description: 'Request body'
23
+ }
24
+ },
25
+ required: ['url']
26
+ },
27
+
28
+ async execute(params) {
29
+ try {
30
+ const options = {
31
+ method: params.method || 'GET',
32
+ headers: params.headers || {}
33
+ };
34
+
35
+ if (params.body) {
36
+ options.body = params.body;
37
+ }
38
+
39
+ const response = await fetch(params.url, options);
40
+ const text = await response.text();
41
+
42
+ return {
43
+ success: true,
44
+ status: response.status,
45
+ statusText: response.statusText,
46
+ headers: Object.fromEntries(response.headers.entries()),
47
+ body: text
48
+ };
49
+ } catch (error) {
50
+ return {
51
+ success: false,
52
+ error: error.message
53
+ };
54
+ }
55
+ }
56
+ };
@@ -0,0 +1,53 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ module.exports = {
5
+ name: 'read_file',
6
+ description: 'Read content from a file',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ path: {
11
+ type: 'string',
12
+ description: 'File path to read'
13
+ }
14
+ },
15
+ required: ['path']
16
+ },
17
+
18
+ async execute(params) {
19
+ try {
20
+ const filePath = path.resolve(params.path);
21
+
22
+ if (!fs.existsSync(filePath)) {
23
+ return {
24
+ success: false,
25
+ error: 'File does not exist'
26
+ };
27
+ }
28
+
29
+ const stats = fs.statSync(filePath);
30
+
31
+ if (!stats.isFile()) {
32
+ return {
33
+ success: false,
34
+ error: 'Path is not a file'
35
+ };
36
+ }
37
+
38
+ const content = fs.readFileSync(filePath, 'utf-8');
39
+
40
+ return {
41
+ success: true,
42
+ path: filePath,
43
+ content,
44
+ size: stats.size
45
+ };
46
+ } catch (error) {
47
+ return {
48
+ success: false,
49
+ error: error.message
50
+ };
51
+ }
52
+ }
53
+ };
@@ -0,0 +1,48 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ module.exports = {
5
+ name: 'write_file',
6
+ description: 'Write content to a file',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ path: {
11
+ type: 'string',
12
+ description: 'File path to write'
13
+ },
14
+ content: {
15
+ type: 'string',
16
+ description: 'Content to write'
17
+ }
18
+ },
19
+ required: ['path', 'content']
20
+ },
21
+
22
+ async execute(params) {
23
+ try {
24
+ const filePath = path.resolve(params.path);
25
+ const dir = path.dirname(filePath);
26
+
27
+ // Create directory if it doesn't exist
28
+ if (!fs.existsSync(dir)) {
29
+ fs.mkdirSync(dir, { recursive: true });
30
+ }
31
+
32
+ fs.writeFileSync(filePath, params.content, 'utf-8');
33
+
34
+ const stats = fs.statSync(filePath);
35
+
36
+ return {
37
+ success: true,
38
+ path: filePath,
39
+ size: stats.size
40
+ };
41
+ } catch (error) {
42
+ return {
43
+ success: false,
44
+ error: error.message
45
+ };
46
+ }
47
+ }
48
+ };
package/src/utils/api.js CHANGED
@@ -24,13 +24,16 @@ async function getBots(apiKey) {
24
24
  });
25
25
  }
26
26
 
27
- async function _sendMessage(apiKey, botId, message, conversationId = null, skillPrompts = '') {
27
+ async function _sendMessage(apiKey, botId, message, conversationId = null, skillPrompts = '', toolDefinitions = null) {
28
28
  const { getConfig } = require('./config');
29
29
  const config = getConfig();
30
30
 
31
31
  // agent_id boş veya undefined ise config'den oku
32
32
  const agentId = botId || config.defaultBotId;
33
33
 
34
+ // Terminal asistan prompt'u ekle
35
+ const terminalPrompt = 'Sen bir terminal asistanısın. Kullanıcı dosya listeleme, komut çalıştırma, dizin görüntüleme gibi istekler yaptığında MUTLAKA bash veya filesystem tool\'larını kullan. Asla \'şu komutu çalıştırın\' deme — kendin çalıştır ve sonucu göster.\n\n';
36
+
34
37
  // system_prompt'u basitçe karaktere göre kes
35
38
  const shortPrompt = skillPrompts ? skillPrompts.slice(0, 600) : '';
36
39
 
@@ -40,7 +43,7 @@ async function _sendMessage(apiKey, botId, message, conversationId = null, skill
40
43
  conversation_id: conversationId,
41
44
  platform: 'cli',
42
45
  user_id: 'cli-user',
43
- system_prompt: shortPrompt
46
+ system_prompt: terminalPrompt + shortPrompt
44
47
  };
45
48
 
46
49
  // Custom AI provider varsa ekle
@@ -54,6 +57,11 @@ async function _sendMessage(apiKey, botId, message, conversationId = null, skill
54
57
  }
55
58
  }
56
59
 
60
+ // Tool definitions ekle
61
+ if (toolDefinitions && toolDefinitions.length > 0) {
62
+ body.tools = toolDefinitions;
63
+ }
64
+
57
65
  const data = await request('/api/agent/chat', {
58
66
  method: 'POST',
59
67
  headers: {
@@ -97,4 +105,5 @@ module.exports = {
97
105
  getBots,
98
106
  sendMessage,
99
107
  validateApiKey,
108
+ _sendMessage, // Export for tool execution loop
100
109
  };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Tool Adapter - Normalize different provider formats
3
+ */
4
+
5
+ /**
6
+ * Extract tool calls from API response
7
+ * Supports: Anthropic, OpenAI, Groq, Gemini
8
+ */
9
+ function extractToolCalls(response, provider) {
10
+ if (!response) return [];
11
+
12
+ // Anthropic format
13
+ if (response.content && Array.isArray(response.content)) {
14
+ return response.content
15
+ .filter(block => block.type === 'tool_use')
16
+ .map(block => ({
17
+ id: block.id,
18
+ name: block.name,
19
+ input: block.input
20
+ }));
21
+ }
22
+
23
+ // OpenAI/Groq format
24
+ if (response.tool_calls && Array.isArray(response.tool_calls)) {
25
+ return response.tool_calls.map(call => ({
26
+ id: call.id,
27
+ name: call.function.name,
28
+ input: JSON.parse(call.function.arguments)
29
+ }));
30
+ }
31
+
32
+ // Gemini format
33
+ if (response.functionCall) {
34
+ return [{
35
+ id: 'gemini_' + Date.now(),
36
+ name: response.functionCall.name,
37
+ input: response.functionCall.args
38
+ }];
39
+ }
40
+
41
+ return [];
42
+ }
43
+
44
+ /**
45
+ * Format tool result for API request
46
+ */
47
+ function formatToolResult(toolCallId, toolName, result, provider) {
48
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
49
+
50
+ // Anthropic format
51
+ if (provider === 'anthropic') {
52
+ return {
53
+ type: 'tool_result',
54
+ tool_use_id: toolCallId,
55
+ content: resultStr
56
+ };
57
+ }
58
+
59
+ // OpenAI/Groq format
60
+ if (provider === 'openai' || provider === 'groq') {
61
+ return {
62
+ role: 'tool',
63
+ tool_call_id: toolCallId,
64
+ name: toolName,
65
+ content: resultStr
66
+ };
67
+ }
68
+
69
+ // Gemini format
70
+ if (provider === 'gemini') {
71
+ return {
72
+ functionResponse: {
73
+ name: toolName,
74
+ response: result
75
+ }
76
+ };
77
+ }
78
+
79
+ // Default format
80
+ return {
81
+ tool_call_id: toolCallId,
82
+ name: toolName,
83
+ result: resultStr
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Convert tool definitions to provider format
89
+ */
90
+ function formatToolDefinitions(tools, provider) {
91
+ // Anthropic format
92
+ if (provider === 'anthropic') {
93
+ return tools.map(tool => ({
94
+ name: tool.name,
95
+ description: tool.description,
96
+ input_schema: tool.inputSchema
97
+ }));
98
+ }
99
+
100
+ // OpenAI/Groq format
101
+ if (provider === 'openai' || provider === 'groq') {
102
+ return tools.map(tool => ({
103
+ type: 'function',
104
+ function: {
105
+ name: tool.name,
106
+ description: tool.description,
107
+ parameters: tool.inputSchema
108
+ }
109
+ }));
110
+ }
111
+
112
+ // Gemini format
113
+ if (provider === 'gemini') {
114
+ return tools.map(tool => ({
115
+ name: tool.name,
116
+ description: tool.description,
117
+ parameters: tool.inputSchema
118
+ }));
119
+ }
120
+
121
+ // Default format (Anthropic-like)
122
+ return tools.map(tool => ({
123
+ name: tool.name,
124
+ description: tool.description,
125
+ input_schema: tool.inputSchema
126
+ }));
127
+ }
128
+
129
+ module.exports = {
130
+ extractToolCalls,
131
+ formatToolResult,
132
+ formatToolDefinitions
133
+ };
@@ -0,0 +1,93 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * Tool Runner - Execute tools locally
7
+ */
8
+
9
+ // Load all tools from src/tools/
10
+ function loadTools() {
11
+ const toolsDir = path.join(__dirname, '..', 'tools');
12
+ const tools = {};
13
+
14
+ if (!fs.existsSync(toolsDir)) {
15
+ return tools;
16
+ }
17
+
18
+ const files = fs.readdirSync(toolsDir).filter(f => f.endsWith('.js'));
19
+
20
+ for (const file of files) {
21
+ try {
22
+ const tool = require(path.join(toolsDir, file));
23
+ if (tool.name && tool.execute) {
24
+ tools[tool.name] = tool;
25
+ }
26
+ } catch (err) {
27
+ console.error(chalk.red(`Failed to load tool ${file}:`, err.message));
28
+ }
29
+ }
30
+
31
+ return tools;
32
+ }
33
+
34
+ // Get tool definitions for API
35
+ function getToolDefinitions() {
36
+ const tools = loadTools();
37
+ return Object.values(tools).map(tool => ({
38
+ name: tool.name,
39
+ description: tool.description,
40
+ inputSchema: tool.inputSchema
41
+ }));
42
+ }
43
+
44
+ // Execute a single tool
45
+ async function executeTool(toolName, params) {
46
+ const tools = loadTools();
47
+ const tool = tools[toolName];
48
+
49
+ if (!tool) {
50
+ return {
51
+ success: false,
52
+ error: `Tool '${toolName}' not found`
53
+ };
54
+ }
55
+
56
+ console.log(chalk.blue(`\n🔧 Executing tool: ${toolName}`));
57
+ console.log(chalk.gray(` Params: ${JSON.stringify(params)}`));
58
+
59
+ try {
60
+ const result = await tool.execute(params);
61
+ console.log(chalk.green(` ✓ Success`));
62
+ return result;
63
+ } catch (error) {
64
+ console.log(chalk.red(` ✗ Error: ${error.message}`));
65
+ return {
66
+ success: false,
67
+ error: error.message
68
+ };
69
+ }
70
+ }
71
+
72
+ // Execute multiple tool calls
73
+ async function executeToolCalls(toolCalls) {
74
+ const results = [];
75
+
76
+ for (const call of toolCalls) {
77
+ const result = await executeTool(call.name, call.input);
78
+ results.push({
79
+ id: call.id,
80
+ name: call.name,
81
+ result
82
+ });
83
+ }
84
+
85
+ return results;
86
+ }
87
+
88
+ module.exports = {
89
+ loadTools,
90
+ getToolDefinitions,
91
+ executeTool,
92
+ executeToolCalls
93
+ };