natureco-cli 1.0.61 → 1.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/README.md +1 -0
- package/package.json +1 -1
- package/src/commands/chat.js +52 -2
- package/src/commands/dashboard.js +2 -2
- package/src/tools/bash.js +37 -0
- package/src/tools/filesystem.js +67 -0
- package/src/tools/http.js +56 -0
- package/src/tools/read_file.js +53 -0
- package/src/tools/write_file.js +48 -0
- package/src/utils/api.js +7 -1
- package/src/utils/tool-adapter.js +133 -0
- package/src/utils/tool-runner.js +93 -0
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
package/src/commands/chat.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
214
|
+
<div class="version-badge" id="version-badge">v1.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: 'v1.0
|
|
344
|
+
version: 'v1.1.0',
|
|
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,7 +24,7 @@ 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
|
|
|
@@ -54,6 +54,11 @@ async function _sendMessage(apiKey, botId, message, conversationId = null, skill
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Tool definitions ekle
|
|
58
|
+
if (toolDefinitions && toolDefinitions.length > 0) {
|
|
59
|
+
body.tools = toolDefinitions;
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
const data = await request('/api/agent/chat', {
|
|
58
63
|
method: 'POST',
|
|
59
64
|
headers: {
|
|
@@ -97,4 +102,5 @@ module.exports = {
|
|
|
97
102
|
getBots,
|
|
98
103
|
sendMessage,
|
|
99
104
|
validateApiKey,
|
|
105
|
+
_sendMessage, // Export for tool execution loop
|
|
100
106
|
};
|
|
@@ -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
|
+
};
|