natureco-cli 2.23.30 → 2.23.32
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/bin/natureco.js +178 -167
- package/package.json +1 -1
- package/src/commands/acp.js +39 -0
- package/src/commands/admin-rpc.js +83 -0
- package/src/commands/agent.js +214 -23
- package/src/commands/agents.js +114 -30
- package/src/commands/approvals.js +172 -11
- package/src/commands/ask.js +1 -1
- package/src/commands/browser.js +815 -0
- package/src/commands/capability.js +195 -22
- package/src/commands/channels.js +422 -267
- package/src/commands/chat.js +5 -8
- package/src/commands/clawbot.js +19 -0
- package/src/commands/code.js +3 -2
- package/src/commands/commitments.js +125 -9
- package/src/commands/completion.js +40 -32
- package/src/commands/config.js +228 -30
- package/src/commands/configure.js +84 -67
- package/src/commands/cron.js +239 -19
- package/src/commands/daemon.js +34 -4
- package/src/commands/dashboard.js +47 -374
- package/src/commands/devices.js +53 -26
- package/src/commands/directory.js +146 -14
- package/src/commands/dns.js +148 -10
- package/src/commands/docs.js +119 -26
- package/src/commands/doctor.js +143 -492
- package/src/commands/exec-policy.js +57 -48
- package/src/commands/gateway.js +492 -249
- package/src/commands/health.js +141 -11
- package/src/commands/help.js +24 -25
- package/src/commands/hooks.js +141 -87
- package/src/commands/infer.js +1442 -41
- package/src/commands/logs.js +122 -99
- package/src/commands/mcp.js +121 -309
- package/src/commands/memory.js +128 -0
- package/src/commands/message.js +720 -140
- package/src/commands/models.js +39 -1
- package/src/commands/node.js +77 -77
- package/src/commands/nodes.js +278 -22
- package/src/commands/onboard.js +115 -56
- package/src/commands/pairing.js +108 -107
- package/src/commands/path.js +206 -0
- package/src/commands/plugins.js +35 -1
- package/src/commands/proxy.js +159 -8
- package/src/commands/qr.js +55 -13
- package/src/commands/reset.js +101 -94
- package/src/commands/secrets.js +104 -21
- package/src/commands/sessions.js +110 -51
- package/src/commands/setup.js +229 -649
- package/src/commands/skills.js +67 -1
- package/src/commands/status.js +101 -127
- package/src/commands/tasks.js +208 -100
- package/src/commands/terminal.js +130 -12
- package/src/commands/transcripts.js +24 -1
- package/src/commands/tui.js +41 -0
- package/src/commands/uninstall.js +73 -92
- package/src/commands/update.js +146 -91
- package/src/commands/web-fetch.js +34 -0
- package/src/commands/webhooks.js +58 -66
- package/src/commands/wiki.js +783 -0
- package/src/utils/agents-md.js +85 -0
- package/src/utils/api.js +40 -41
- package/src/utils/format.js +144 -0
- package/src/utils/headless.js +2 -1
- package/src/utils/parallel-tools.js +106 -0
- package/src/utils/sub-agent.js +148 -0
- package/src/utils/token-budget.js +304 -0
- package/src/utils/tool-runner.js +7 -5
- package/src/utils/web-fetch.js +107 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
|
|
6
|
+
const AGENTS_MD_FILENAMES = [
|
|
7
|
+
'AGENTS.md',
|
|
8
|
+
'.natureco/AGENTS.md',
|
|
9
|
+
'.natureco/INSTRUCTIONS.md',
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function isRoot(dir) {
|
|
13
|
+
const parsed = path.parse(dir);
|
|
14
|
+
return parsed.root === dir;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hasGit(dir) {
|
|
18
|
+
return fs.existsSync(path.join(dir, '.git'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function findAgentsMd(cwd) {
|
|
22
|
+
let current = path.resolve(cwd || process.cwd());
|
|
23
|
+
|
|
24
|
+
while (true) {
|
|
25
|
+
for (const relPath of AGENTS_MD_FILENAMES) {
|
|
26
|
+
const candidate = path.join(current, relPath);
|
|
27
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isRoot(current) || hasGit(current)) {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parent = path.dirname(current);
|
|
37
|
+
if (parent === current) break;
|
|
38
|
+
current = parent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadInstructions(cwd) {
|
|
45
|
+
const resolvedCwd = path.resolve(cwd || process.cwd());
|
|
46
|
+
|
|
47
|
+
if (cache.has(resolvedCwd)) {
|
|
48
|
+
return cache.get(resolvedCwd);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const filePath = findAgentsMd(resolvedCwd);
|
|
52
|
+
if (!filePath) {
|
|
53
|
+
cache.set(resolvedCwd, null);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
59
|
+
cache.set(resolvedCwd, content);
|
|
60
|
+
return content;
|
|
61
|
+
} catch {
|
|
62
|
+
cache.set(resolvedCwd, null);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function injectIntoPrompt(systemPrompt, cwd) {
|
|
68
|
+
const instructions = loadInstructions(cwd);
|
|
69
|
+
if (!instructions) {
|
|
70
|
+
return systemPrompt;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const header = '\n\n## Project Instructions (from AGENTS.md)\n\n';
|
|
74
|
+
return systemPrompt + header + instructions.trim();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function clearCache() {
|
|
78
|
+
cache.clear();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
loadInstructions,
|
|
83
|
+
injectIntoPrompt,
|
|
84
|
+
clearCache,
|
|
85
|
+
};
|
package/src/utils/api.js
CHANGED
|
@@ -8,6 +8,7 @@ const chalk = require('chalk');
|
|
|
8
8
|
const { getConfig } = require('./config');
|
|
9
9
|
const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
|
|
10
10
|
const { MCPClient } = require('./mcp-client');
|
|
11
|
+
const TB = require('./token-budget');
|
|
11
12
|
|
|
12
13
|
// Persistent conversation directory
|
|
13
14
|
const CONV_DIR = path.join(os.homedir(), '.natureco', 'conversations');
|
|
@@ -60,7 +61,7 @@ function saveConversation(convId, messages) {
|
|
|
60
61
|
try {
|
|
61
62
|
fs.mkdirSync(CONV_DIR, { recursive: true });
|
|
62
63
|
// Keep only last 10 messages
|
|
63
|
-
fs.writeFileSync(file, JSON.stringify(messages.slice(-
|
|
64
|
+
fs.writeFileSync(file, JSON.stringify(messages.slice(-(TB.load().conversationOnDisk)), null, 2));
|
|
64
65
|
} catch (e) {
|
|
65
66
|
// Silently fail
|
|
66
67
|
}
|
|
@@ -188,7 +189,7 @@ function normalizeMcpToolSchema(tool) {
|
|
|
188
189
|
function minimizeMcpTool(tool) {
|
|
189
190
|
return {
|
|
190
191
|
name: tool.name,
|
|
191
|
-
description: (tool.description
|
|
192
|
+
description: TB.capMcpDesc(tool.description),
|
|
192
193
|
inputSchema: {
|
|
193
194
|
type: tool.inputSchema?.type || 'object',
|
|
194
195
|
properties: Object.fromEntries(
|
|
@@ -265,9 +266,10 @@ async function executeMcpTool(toolName, toolArgs) {
|
|
|
265
266
|
if (textContents.length > 0) {
|
|
266
267
|
let output = textContents.join('\n');
|
|
267
268
|
|
|
268
|
-
// Truncate MCP result
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
// Truncate MCP result
|
|
270
|
+
const maxChars = TB.load().toolMaxChars;
|
|
271
|
+
if (output.length > maxChars) {
|
|
272
|
+
output = output.slice(0, maxChars) + '... (truncated)';
|
|
271
273
|
}
|
|
272
274
|
|
|
273
275
|
return {
|
|
@@ -281,8 +283,9 @@ async function executeMcpTool(toolName, toolArgs) {
|
|
|
281
283
|
let fallbackOutput = JSON.stringify(result, null, 2);
|
|
282
284
|
|
|
283
285
|
// Truncate fallback output too
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
const maxChars = TB.load().toolMaxChars;
|
|
287
|
+
if (fallbackOutput.length > maxChars) {
|
|
288
|
+
fallbackOutput = fallbackOutput.slice(0, maxChars) + '... (truncated)';
|
|
286
289
|
}
|
|
287
290
|
|
|
288
291
|
return {
|
|
@@ -467,6 +470,7 @@ async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
|
|
|
467
470
|
role: 'assistant',
|
|
468
471
|
content,
|
|
469
472
|
tool_calls: data.choices?.[0]?.message?.tool_calls || undefined,
|
|
473
|
+
usage: data.usage || undefined,
|
|
470
474
|
};
|
|
471
475
|
}
|
|
472
476
|
|
|
@@ -519,7 +523,8 @@ async function sendMessageAnthropic(providerConfig, messages, tools) {
|
|
|
519
523
|
return {
|
|
520
524
|
role: 'assistant',
|
|
521
525
|
content: content,
|
|
522
|
-
tool_calls: toolCalls.length > 0 ? toolCalls : undefined
|
|
526
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
527
|
+
usage: data.usage || undefined,
|
|
523
528
|
};
|
|
524
529
|
}
|
|
525
530
|
|
|
@@ -548,18 +553,22 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
548
553
|
const convId = conversationId || generateDefaultConvId();
|
|
549
554
|
const history = loadConversation(convId);
|
|
550
555
|
|
|
556
|
+
// Augment system prompt with project AGENTS.md instructions
|
|
557
|
+
const agentsMd = require('./agents-md');
|
|
558
|
+
const augmentedPrompt = agentsMd.injectIntoPrompt(systemPrompt || '', options?.cwd || process.cwd());
|
|
559
|
+
|
|
551
560
|
// Build messages
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
messages.push({ role: 'system', content:
|
|
561
|
+
let messages = [];
|
|
562
|
+
if (augmentedPrompt) {
|
|
563
|
+
messages.push({ role: 'system', content: augmentedPrompt });
|
|
555
564
|
}
|
|
556
565
|
messages.push(...history);
|
|
557
566
|
messages.push({ role: 'user', content: message });
|
|
558
567
|
|
|
559
|
-
// Get tool definitions (local + MCP)
|
|
560
|
-
const tools =
|
|
561
|
-
?
|
|
562
|
-
: formatToolsForOpenAI();
|
|
568
|
+
// Get tool definitions (local + MCP) — skip if noTools flag set (chat mode)
|
|
569
|
+
const tools = options.noTools
|
|
570
|
+
? []
|
|
571
|
+
: (providerConfig.isAnthropic ? formatToolsForAnthropic() : formatToolsForOpenAI());
|
|
563
572
|
|
|
564
573
|
debugLog('\n[Provider] Sending request...');
|
|
565
574
|
debugLog('[Provider] URL:', providerConfig.url);
|
|
@@ -572,7 +581,7 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
572
581
|
let iteration = 0;
|
|
573
582
|
const maxIterations = 10;
|
|
574
583
|
let finalResponse = null;
|
|
575
|
-
const stream = options.stream !== false &&
|
|
584
|
+
const stream = (options.stream ?? options.noStream === undefined) !== false &&
|
|
576
585
|
!providerConfig.url.includes('api.natureco.me');
|
|
577
586
|
|
|
578
587
|
while (iteration < maxIterations) {
|
|
@@ -604,6 +613,14 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
604
613
|
};
|
|
605
614
|
}
|
|
606
615
|
|
|
616
|
+
// Track token usage if available
|
|
617
|
+
if (assistantMessage.usage) {
|
|
618
|
+
TB.trackUsage(convId, {
|
|
619
|
+
input: assistantMessage.usage.prompt_tokens || assistantMessage.usage.input_tokens || 0,
|
|
620
|
+
output: assistantMessage.usage.completion_tokens || assistantMessage.usage.output_tokens || 0
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
607
624
|
debugLog('[Provider] Response type:', assistantMessage?.tool_calls ? 'tool_calls' : 'text');
|
|
608
625
|
|
|
609
626
|
// Add assistant message to history
|
|
@@ -681,6 +698,9 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
681
698
|
finalResponse = finalResponse || 'Max tool execution iterations reached.';
|
|
682
699
|
}
|
|
683
700
|
|
|
701
|
+
// Apply token budget trimming
|
|
702
|
+
messages = TB.trimMessages(messages);
|
|
703
|
+
|
|
684
704
|
// Save to conversation history (only user and final assistant message)
|
|
685
705
|
history.push({ role: 'user', content: message });
|
|
686
706
|
history.push({ role: 'assistant', content: finalResponse });
|
|
@@ -755,35 +775,14 @@ async function sendMessage(apiKey, botId, message, conversationId = null, chatSy
|
|
|
755
775
|
return sendMessageToProvider(apiKey, message, conversationId, prompt, options);
|
|
756
776
|
}
|
|
757
777
|
|
|
758
|
-
//
|
|
778
|
+
// Minimal base prompt (~200 token)
|
|
759
779
|
const toolDefs = getToolDefinitions();
|
|
760
780
|
const toolsDesc = toolDefs.map(t => t.name).join(', ');
|
|
761
|
-
let
|
|
762
|
-
Home: ${homeDir}
|
|
763
|
-
Tools: ${toolsDesc}`;
|
|
781
|
+
let systemPrompt = `Assistant. Tools: ${toolsDesc}. Home: ${homeDir}.`;
|
|
764
782
|
|
|
765
|
-
//
|
|
766
|
-
if (config.mcpEnabled !== false) {
|
|
767
|
-
baseSystemPrompt += `\nMCP: send number params as numbers, not strings.`;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Prepend botName
|
|
771
|
-
if (mem.botName) {
|
|
772
|
-
baseSystemPrompt = `Adın ${mem.botName}. Sen ${mem.botName}'sun. Adın sorulduğunda her zaman "${mem.botName}" de.\n\n` + baseSystemPrompt;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
let systemPrompt = baseSystemPrompt;
|
|
776
|
-
|
|
777
|
-
// Append chat.js system prompt (memory + skills + agents) — max 3000 chars
|
|
783
|
+
// Skill prompts only, max 500 chars
|
|
778
784
|
if (chatSystemPrompt) {
|
|
779
|
-
systemPrompt += '\n
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// Add MCP server names if enabled and loaded
|
|
783
|
-
const mcpTools = getMcpTools();
|
|
784
|
-
if (config.mcpEnabled !== false && mcpTools.length > 0) {
|
|
785
|
-
const mcpServerNames = Object.keys(mcpClients);
|
|
786
|
-
systemPrompt += `\n\nMCP SERVERS: ${mcpServerNames.join(', ')}`;
|
|
785
|
+
systemPrompt += '\n' + chatSystemPrompt.slice(0, TB.load().systemPromptMaxChars);
|
|
787
786
|
}
|
|
788
787
|
|
|
789
788
|
return sendMessageToProvider(apiKey, message, conversationId, systemPrompt, options);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const W = () => Math.min(process.stdout.columns || 100, 100);
|
|
4
|
+
|
|
5
|
+
function header(text) {
|
|
6
|
+
const w = W();
|
|
7
|
+
const line = chalk.dim('┌' + '─'.repeat(w - 2) + '┐');
|
|
8
|
+
const padding = Math.max(0, w - text.length - 4);
|
|
9
|
+
const leftPad = Math.floor(padding / 2);
|
|
10
|
+
const rightPad = padding - leftPad;
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log(line);
|
|
13
|
+
console.log(chalk.dim('│') + ' '.repeat(leftPad) + chalk.bold.cyan(text) + ' '.repeat(rightPad) + chalk.dim('│'));
|
|
14
|
+
console.log(chalk.dim('└' + '─'.repeat(w - 2) + '┘'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function section(text) {
|
|
18
|
+
const w = W();
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log(chalk.dim('▔').repeat(Math.min(w - 4, 48)));
|
|
21
|
+
console.log(chalk.bold.cyan(' ' + text));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function divider() {
|
|
25
|
+
const w = W();
|
|
26
|
+
console.log(chalk.dim('─').repeat(Math.min(w - 4, 48)));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function label(key, value, options = {}) {
|
|
30
|
+
const { indent = 2, keyWidth = 14, valueColor = 'white' } = options;
|
|
31
|
+
const pad = ' '.repeat(indent);
|
|
32
|
+
const coloredKey = chalk.dim(key.padEnd(keyWidth));
|
|
33
|
+
const coloredValue = chalk[valueColor] ? chalk[valueColor](value) : chalk.white(value);
|
|
34
|
+
console.log(pad + coloredKey + coloredValue);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function kv(key, value) {
|
|
38
|
+
if (value === undefined || value === null) value = chalk.dim('—');
|
|
39
|
+
const keyStr = chalk.dim(key.padEnd(14));
|
|
40
|
+
console.log(' ' + keyStr + chalk.white(value));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function badge(text, color = 'cyan') {
|
|
44
|
+
const c = chalk[color] || chalk.cyan;
|
|
45
|
+
return c.bold(' ' + text + ' ');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cmd(text) {
|
|
49
|
+
return chalk.cyan(text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function flag(text) {
|
|
53
|
+
return chalk.yellow(text);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function success(text) {
|
|
57
|
+
console.log(chalk.green(' ✓ ' + text));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function error(text) {
|
|
61
|
+
console.log(chalk.red(' ✗ ' + text));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function warning(text) {
|
|
65
|
+
console.log(chalk.yellow(' ⚠ ' + text));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function info(text) {
|
|
69
|
+
console.log(chalk.blue(' ℹ ' + text));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function list(items, options = {}) {
|
|
73
|
+
const { indent = 2, bullet = '•' } = options;
|
|
74
|
+
const pad = ' '.repeat(indent);
|
|
75
|
+
for (const item of items) {
|
|
76
|
+
if (typeof item === 'string') {
|
|
77
|
+
console.log(pad + chalk.dim(bullet + ' ') + chalk.white(item));
|
|
78
|
+
} else if (item.label && item.value) {
|
|
79
|
+
console.log(pad + chalk.dim(bullet + ' ') + chalk.white(item.label + ': ') + chalk.dim(item.value));
|
|
80
|
+
} else if (item.label) {
|
|
81
|
+
console.log(pad + chalk.dim(bullet + ' ') + chalk.white(item.label));
|
|
82
|
+
if (item.desc) console.log(pad + ' ' + chalk.dim(item.desc));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function table(headers, rows, options = {}) {
|
|
88
|
+
const { indent = 2, headerColor = 'bold.cyan' } = options;
|
|
89
|
+
const pad = ' '.repeat(indent);
|
|
90
|
+
|
|
91
|
+
if (rows.length === 0) {
|
|
92
|
+
console.log(pad + chalk.dim('(empty)'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const colCount = headers.length;
|
|
97
|
+
const colWidths = headers.map((h, i) => {
|
|
98
|
+
const maxData = rows.reduce((max, row) => Math.max(max, String(row[i] || '').length), 0);
|
|
99
|
+
return Math.max(h.length, maxData) + 2;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const totalWidth = colWidths.reduce((a, b) => a + b, 0) + colCount - 1;
|
|
103
|
+
if (totalWidth > W() - indent) {
|
|
104
|
+
const ratio = (W() - indent - colCount + 1) / (totalWidth - colCount + 1);
|
|
105
|
+
for (let i = 0; i < colWidths.length; i++) {
|
|
106
|
+
colWidths[i] = Math.max(3, Math.floor((colWidths[i] - 2) * ratio) + 2);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const hdr = headers.map((h, i) => {
|
|
111
|
+
const w = colWidths[i];
|
|
112
|
+
const text = h.length > w - 1 ? h.slice(0, w - 2) + '…' : h.padEnd(w);
|
|
113
|
+
return chalk.bold.cyan(text);
|
|
114
|
+
}).join(' ');
|
|
115
|
+
console.log(pad + chalk.dim('┌' + '─'.repeat(totalWidth) + '┐'));
|
|
116
|
+
console.log(pad + chalk.dim('│') + hdr + chalk.dim('│'));
|
|
117
|
+
console.log(pad + chalk.dim('├' + '─'.repeat(totalWidth) + '┤'));
|
|
118
|
+
|
|
119
|
+
for (const row of rows) {
|
|
120
|
+
const cells = row.map((cell, i) => {
|
|
121
|
+
const w = colWidths[i];
|
|
122
|
+
const text = String(cell || '');
|
|
123
|
+
return (text.length > w - 1 ? text.slice(0, w - 2) + '…' : text.padEnd(w));
|
|
124
|
+
}).join(' ');
|
|
125
|
+
console.log(pad + chalk.dim('│') + cells + chalk.dim('│'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log(pad + chalk.dim('└' + '─'.repeat(totalWidth) + '┘'));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function dot(enabled, label) {
|
|
132
|
+
const d = enabled ? chalk.green('●') : chalk.dim('○');
|
|
133
|
+
console.log(' ' + d + ' ' + chalk.white(label));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function meta(text) {
|
|
137
|
+
console.log(chalk.dim(' ' + text));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function json(obj) {
|
|
141
|
+
console.log(chalk.dim(' ') + chalk.white(JSON.stringify(obj, null, 2).replace(/\n/g, '\n ')));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { header, section, divider, label, kv, badge, cmd, flag, success, error, warning, info, list, table, dot, meta, json };
|
package/src/utils/headless.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { getProviderConfig } = require('./config');
|
|
9
|
+
const TB = require('./token-budget');
|
|
9
10
|
const { getToolDefinitions, executeTool } = require('./tool-runner');
|
|
10
11
|
|
|
11
12
|
// ── Proje indexing (code.js'den paylaşılan) ───────────────────────────────────
|
|
@@ -169,7 +170,7 @@ Dosyalar: ${projectIndex.files.slice(0, 25).join(', ')}`;
|
|
|
169
170
|
role: 'tool',
|
|
170
171
|
tool_call_id: assistantMsg.tool_calls?.find(tc => tc.function.name === toolCall.name)?.id || toolCall.id,
|
|
171
172
|
name: toolCall.name,
|
|
172
|
-
content: resultStr.slice(0,
|
|
173
|
+
content: resultStr.slice(0, TB.load().toolMaxChars),
|
|
173
174
|
});
|
|
174
175
|
}
|
|
175
176
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Parallel Tool Runner — run multiple independent tool/MCP calls in parallel
|
|
2
|
+
|
|
3
|
+
async function executeSingle(tool, options = {}) {
|
|
4
|
+
const { type, name, params } = tool;
|
|
5
|
+
const executeTool = options.executeTool || defaultExecuteTool;
|
|
6
|
+
|
|
7
|
+
if (type === 'mcp') {
|
|
8
|
+
return executeMcpCall(tool, options);
|
|
9
|
+
}
|
|
10
|
+
if (type === 'function') {
|
|
11
|
+
return executeTool(name, params);
|
|
12
|
+
}
|
|
13
|
+
throw new Error(`Unknown tool type: ${type}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function defaultExecuteTool(toolName, params) {
|
|
17
|
+
const { executeTool } = require('./tool-runner');
|
|
18
|
+
return executeTool(toolName, params);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function executeMcpCall(tool, options = {}) {
|
|
22
|
+
const { name, params } = tool;
|
|
23
|
+
const getClient = options.getMcpClient;
|
|
24
|
+
|
|
25
|
+
if (!getClient) {
|
|
26
|
+
return { success: false, error: 'No MCP client lookup provided (options.getMcpClient)' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const client = getClient(name);
|
|
30
|
+
if (!client) {
|
|
31
|
+
return { success: false, error: `MCP client not found for tool: ${name}` };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const result = await client.callTool(name, params);
|
|
36
|
+
if (result.content && result.content.length > 0) {
|
|
37
|
+
const textContents = result.content
|
|
38
|
+
.filter(c => c.type === 'text')
|
|
39
|
+
.map(c => c.text);
|
|
40
|
+
if (textContents.length > 0) {
|
|
41
|
+
return { success: true, output: textContents.join('\n') };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { success: true, output: JSON.stringify(result, null, 2) };
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return { success: false, error: err.message };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Run multiple independent tool calls in parallel
|
|
51
|
+
// tools: [{ name, params, type: 'mcp' | 'function' }]
|
|
52
|
+
async function runParallel(tools, options = {}) {
|
|
53
|
+
if (!tools || tools.length === 0) return [];
|
|
54
|
+
|
|
55
|
+
const results = await Promise.allSettled(
|
|
56
|
+
tools.map(t => executeSingle(t, options))
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return results.map((r, i) => ({
|
|
60
|
+
tool: tools[i].name,
|
|
61
|
+
status: r.status === 'fulfilled' ? 'success' : 'error',
|
|
62
|
+
result: r.status === 'fulfilled' ? r.value : r.reason.message,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Group tool calls by independence
|
|
67
|
+
// dependencyMap: { inputKeys?: string[], outputKeys?: string[] }
|
|
68
|
+
// Tools that don't share input/output keys are independent
|
|
69
|
+
function groupIndependent(tools, dependencyMap = {}) {
|
|
70
|
+
if (!tools || tools.length === 0) return [];
|
|
71
|
+
|
|
72
|
+
const maps = tools.map(t => ({
|
|
73
|
+
tool: t,
|
|
74
|
+
inputs: dependencyMap[t.name]?.inputKeys || Object.keys(t.params || {}),
|
|
75
|
+
outputs: dependencyMap[t.name]?.outputKeys || [],
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
const groups = [];
|
|
79
|
+
|
|
80
|
+
for (const item of maps) {
|
|
81
|
+
const allKeys = [...item.inputs, ...item.outputs];
|
|
82
|
+
let placed = false;
|
|
83
|
+
|
|
84
|
+
for (const group of groups) {
|
|
85
|
+
const overlap = allKeys.some(k => group.keys.includes(k));
|
|
86
|
+
if (!overlap) {
|
|
87
|
+
group.items.push(item.tool);
|
|
88
|
+
group.keys.push(...allKeys);
|
|
89
|
+
placed = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!placed) {
|
|
95
|
+
groups.push({ items: [item.tool], keys: allKeys });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return groups.map(g => g.items);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
executeSingle,
|
|
104
|
+
runParallel,
|
|
105
|
+
groupIndependent,
|
|
106
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { getProviderConfig } = require('./api');
|
|
5
|
+
|
|
6
|
+
const SUB_AGENTS_FILE = path.join(os.homedir(), '.natureco', 'sub-agents.json');
|
|
7
|
+
|
|
8
|
+
const SYSTEM_PROMPTS = {
|
|
9
|
+
explore: 'You are a research agent. Find information, explore codebases, search files. Be concise.',
|
|
10
|
+
general: 'You are a general-purpose implementation agent. Write code, fix bugs, refactor.',
|
|
11
|
+
review: 'You are a code review agent. Analyze code for bugs, security, performance, style.',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function ensureDir() {
|
|
15
|
+
const dir = path.dirname(SUB_AGENTS_FILE);
|
|
16
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function loadAgents() {
|
|
20
|
+
ensureDir();
|
|
21
|
+
if (!fs.existsSync(SUB_AGENTS_FILE)) return [];
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(fs.readFileSync(SUB_AGENTS_FILE, 'utf8'));
|
|
24
|
+
} catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function saveAgents(agents) {
|
|
30
|
+
ensureDir();
|
|
31
|
+
fs.writeFileSync(SUB_AGENTS_FILE, JSON.stringify(agents, null, 2), 'utf8');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function generateId() {
|
|
35
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function spawnSubAgent(type, task, options = {}) {
|
|
39
|
+
const validTypes = Object.keys(SYSTEM_PROMPTS);
|
|
40
|
+
if (!validTypes.includes(type)) {
|
|
41
|
+
throw new Error(`Invalid sub-agent type: ${type}. Valid: ${validTypes.join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(` [Sub-agent ${type}] Spawned for: ${task.slice(0, 80)}`);
|
|
45
|
+
|
|
46
|
+
const entry = {
|
|
47
|
+
id: generateId(),
|
|
48
|
+
type,
|
|
49
|
+
task,
|
|
50
|
+
status: 'running',
|
|
51
|
+
result: null,
|
|
52
|
+
startedAt: new Date().toISOString(),
|
|
53
|
+
completedAt: null,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const agents = loadAgents();
|
|
57
|
+
agents.push(entry);
|
|
58
|
+
saveAgents(agents);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const providerConfig = getProviderConfig();
|
|
62
|
+
if (!providerConfig) {
|
|
63
|
+
throw new Error('Provider not configured. Run: natureco configure');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const systemPrompt = options.systemPrompt || SYSTEM_PROMPTS[type];
|
|
67
|
+
|
|
68
|
+
const baseUrl = providerConfig.url.replace(/\/+$/, '');
|
|
69
|
+
const endpoint = `${baseUrl}/chat/completions`;
|
|
70
|
+
|
|
71
|
+
const maxTokens = options.maxTokens || 512;
|
|
72
|
+
|
|
73
|
+
const response = await fetch(endpoint, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'Authorization': `Bearer ${providerConfig.apiKey}`,
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
model: providerConfig.model,
|
|
81
|
+
messages: [
|
|
82
|
+
{ role: 'system', content: systemPrompt },
|
|
83
|
+
{ role: 'user', content: task },
|
|
84
|
+
],
|
|
85
|
+
temperature: 0.3,
|
|
86
|
+
max_tokens: maxTokens,
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
const errorText = await response.text();
|
|
92
|
+
throw new Error(`Provider API error: ${response.status} - ${errorText}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const data = await response.json();
|
|
96
|
+
const content = data.choices?.[0]?.message?.content || data.choices?.[0]?.text || '';
|
|
97
|
+
|
|
98
|
+
entry.status = 'completed';
|
|
99
|
+
entry.result = content;
|
|
100
|
+
entry.completedAt = new Date().toISOString();
|
|
101
|
+
saveAgents(loadAgents().map(a => a.id === entry.id ? entry : a));
|
|
102
|
+
|
|
103
|
+
const usage = data.usage || {};
|
|
104
|
+
const duration = new Date(entry.completedAt) - new Date(entry.startedAt);
|
|
105
|
+
|
|
106
|
+
return { result: content, usage, duration };
|
|
107
|
+
} catch (err) {
|
|
108
|
+
entry.status = 'failed';
|
|
109
|
+
entry.result = err.message;
|
|
110
|
+
entry.completedAt = new Date().toISOString();
|
|
111
|
+
saveAgents(loadAgents().map(a => a.id === entry.id ? entry : a));
|
|
112
|
+
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function spawnParallel(agents) {
|
|
118
|
+
const promises = agents.map(a =>
|
|
119
|
+
spawnSubAgent(a.type, a.task, a.options || {}).then(
|
|
120
|
+
result => ({ status: 'fulfilled', result }),
|
|
121
|
+
error => ({ status: 'rejected', reason: error.message })
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const results = await Promise.allSettled(promises);
|
|
126
|
+
|
|
127
|
+
const failed = results.filter(r => r.status === 'rejected');
|
|
128
|
+
return { results, failed };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getStatus() {
|
|
132
|
+
const agents = loadAgents();
|
|
133
|
+
const last20 = agents.slice(-20).reverse();
|
|
134
|
+
return {
|
|
135
|
+
total: agents.length,
|
|
136
|
+
running: agents.filter(a => a.status === 'running').length,
|
|
137
|
+
completed: agents.filter(a => a.status === 'completed').length,
|
|
138
|
+
failed: agents.filter(a => a.status === 'failed').length,
|
|
139
|
+
agents: last20,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
spawnSubAgent,
|
|
145
|
+
spawnParallel,
|
|
146
|
+
getStatus,
|
|
147
|
+
SYSTEM_PROMPTS,
|
|
148
|
+
};
|