natureco-cli 2.23.29 → 2.23.31
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 +94 -11
- package/bin/natureco.js +495 -94
- package/package.json +1 -1
- package/src/commands/acp.js +39 -0
- package/src/commands/admin-rpc.js +302 -0
- package/src/commands/agent.js +280 -0
- package/src/commands/agents.js +114 -30
- package/src/commands/approvals.js +214 -0
- package/src/commands/backup.js +124 -0
- package/src/commands/bonjour.js +167 -0
- package/src/commands/browser.js +815 -0
- package/src/commands/capability.js +237 -0
- package/src/commands/channels.js +422 -267
- package/src/commands/chat.js +5 -8
- package/src/commands/clawbot.js +19 -0
- package/src/commands/clickclack.js +130 -0
- package/src/commands/code.js +3 -2
- package/src/commands/commitments.js +148 -0
- package/src/commands/completion.js +84 -0
- package/src/commands/config.js +219 -30
- package/src/commands/configure.js +110 -0
- package/src/commands/crestodian.js +92 -0
- package/src/commands/cron.js +239 -19
- package/src/commands/daemon.js +90 -0
- package/src/commands/dashboard.js +47 -374
- package/src/commands/device-pair.js +248 -0
- package/src/commands/devices.js +137 -0
- package/src/commands/directory.js +179 -0
- package/src/commands/dns.js +196 -0
- package/src/commands/docs.js +136 -0
- package/src/commands/doctor.js +143 -492
- package/src/commands/exec-policy.js +80 -0
- package/src/commands/gateway-server.js +1155 -24
- package/src/commands/gateway.js +492 -249
- package/src/commands/health.js +148 -0
- package/src/commands/help.js +24 -25
- package/src/commands/hooks.js +141 -87
- package/src/commands/imessage.js +128 -14
- package/src/commands/infer.js +1474 -0
- package/src/commands/irc.js +64 -15
- package/src/commands/logs.js +122 -99
- package/src/commands/mattermost.js +114 -12
- package/src/commands/mcp.js +121 -309
- package/src/commands/memory-cmd.js +134 -1
- package/src/commands/memory.js +128 -0
- package/src/commands/message.js +720 -134
- package/src/commands/migrate.js +213 -2
- package/src/commands/models.js +39 -1
- package/src/commands/node.js +98 -0
- package/src/commands/nodes.js +362 -0
- package/src/commands/oc-path.js +200 -0
- package/src/commands/onboard.js +129 -0
- package/src/commands/open-prose.js +67 -0
- package/src/commands/pairing.js +108 -107
- package/src/commands/path.js +206 -0
- package/src/commands/plugins.js +35 -1
- package/src/commands/policy.js +176 -0
- package/src/commands/proxy.js +306 -0
- package/src/commands/qr.js +70 -0
- package/src/commands/reset.js +101 -94
- package/src/commands/sandbox.js +125 -0
- package/src/commands/secrets.js +201 -0
- package/src/commands/sessions.js +110 -51
- package/src/commands/setup.js +102 -543
- package/src/commands/signal.js +447 -18
- package/src/commands/skills.js +67 -1
- package/src/commands/sms.js +123 -19
- package/src/commands/status.js +101 -127
- package/src/commands/system.js +53 -0
- package/src/commands/tasks.js +208 -100
- package/src/commands/terminal.js +139 -0
- package/src/commands/thread-ownership.js +157 -0
- package/src/commands/transcripts.js +95 -0
- package/src/commands/tui.js +41 -0
- package/src/commands/uninstall.js +73 -92
- package/src/commands/update.js +146 -91
- package/src/commands/voice.js +82 -0
- package/src/commands/vydra.js +98 -0
- package/src/commands/webhooks.js +58 -66
- package/src/commands/wiki.js +783 -0
- package/src/commands/workboard.js +207 -0
- package/src/tools/audio_understanding.js +154 -0
- package/src/tools/browser.js +112 -0
- package/src/tools/canvas.js +104 -0
- package/src/tools/document_extract.js +84 -0
- package/src/tools/duckduckgo.js +54 -0
- package/src/tools/exa_search.js +66 -0
- package/src/tools/firecrawl.js +104 -0
- package/src/tools/image_generation.js +99 -0
- package/src/tools/llm_task.js +118 -0
- package/src/tools/media_understanding.js +128 -0
- package/src/tools/music_generation.js +113 -0
- package/src/tools/parallel_search.js +77 -0
- package/src/tools/phone_control.js +80 -0
- package/src/tools/phone_control_enhanced.js +184 -0
- package/src/tools/searxng.js +61 -0
- package/src/tools/speech_to_text.js +135 -0
- package/src/tools/text_to_speech.js +105 -0
- package/src/tools/thread_ownership.js +88 -0
- package/src/tools/video_generation.js +72 -0
- package/src/tools/web_readability.js +104 -0
- package/src/utils/agents-md.js +85 -0
- package/src/utils/api.js +39 -40
- package/src/utils/format.js +144 -0
- package/src/utils/headless.js +2 -1
- package/src/utils/memory.js +200 -0
- 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,88 @@
|
|
|
1
|
+
const { getConfig, saveConfig } = require('../utils/config');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
name: 'thread_ownership',
|
|
5
|
+
description: 'Manage message thread ownership — assign threads to specific agents/bots',
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
action: { type: 'string', description: 'Action: assign, release, status, list', enum: ['assign', 'release', 'status', 'list'] },
|
|
10
|
+
threadId: { type: 'string', description: 'Thread/channel/conversation ID' },
|
|
11
|
+
agentName: { type: 'string', description: 'Agent name to assign (for assign action)' },
|
|
12
|
+
channel: { type: 'string', description: 'Channel type: telegram, whatsapp, signal, irc, mattermost, discord, slack' }
|
|
13
|
+
},
|
|
14
|
+
required: ['action']
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
async execute(params) {
|
|
18
|
+
try {
|
|
19
|
+
const config = getConfig();
|
|
20
|
+
const ownership = config.threadOwnership || {};
|
|
21
|
+
|
|
22
|
+
if (params.action === 'list') {
|
|
23
|
+
const entries = Object.entries(ownership);
|
|
24
|
+
if (entries.length === 0) {
|
|
25
|
+
return { success: true, action: 'list', message: 'Atanmış thread yok.', threads: [] };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
action: 'list',
|
|
30
|
+
threads: entries.map(([id, agent]) => ({
|
|
31
|
+
threadId: id,
|
|
32
|
+
assignedAgent: agent
|
|
33
|
+
})),
|
|
34
|
+
count: entries.length
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (params.action === 'status') {
|
|
39
|
+
if (!params.threadId) {
|
|
40
|
+
return { success: false, error: 'threadId gerekli' };
|
|
41
|
+
}
|
|
42
|
+
const assigned = ownership[params.threadId];
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
action: 'status',
|
|
46
|
+
threadId: params.threadId,
|
|
47
|
+
assignedAgent: assigned || null,
|
|
48
|
+
isAssigned: !!assigned
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (params.action === 'assign') {
|
|
53
|
+
if (!params.threadId || !params.agentName) {
|
|
54
|
+
return { success: false, error: 'threadId ve agentName gerekli' };
|
|
55
|
+
}
|
|
56
|
+
ownership[params.threadId] = params.agentName;
|
|
57
|
+
config.threadOwnership = ownership;
|
|
58
|
+
saveConfig(config);
|
|
59
|
+
return {
|
|
60
|
+
success: true,
|
|
61
|
+
action: 'assign',
|
|
62
|
+
threadId: params.threadId,
|
|
63
|
+
agentName: params.agentName,
|
|
64
|
+
message: `Thread ${params.threadId} → ${params.agentName}`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (params.action === 'release') {
|
|
69
|
+
if (!params.threadId) {
|
|
70
|
+
return { success: false, error: 'threadId gerekli' };
|
|
71
|
+
}
|
|
72
|
+
delete ownership[params.threadId];
|
|
73
|
+
config.threadOwnership = ownership;
|
|
74
|
+
saveConfig(config);
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
action: 'release',
|
|
78
|
+
threadId: params.threadId,
|
|
79
|
+
message: `Thread ${params.threadId} serbest bırakıldı`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { success: false, error: `Bilinmeyen aksiyon: ${params.action}` };
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return { success: false, error: error.message };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { getConfig } = require('../utils/config');
|
|
2
|
+
|
|
3
|
+
const PROVIDERS = {
|
|
4
|
+
runway: {
|
|
5
|
+
name: 'RunwayML',
|
|
6
|
+
async generate({ prompt, apiKey, model }) {
|
|
7
|
+
const response = await fetch('https://api.runwayml.com/v1/text_to_video', {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
10
|
+
body: JSON.stringify({
|
|
11
|
+
model: model || 'gen3a_turbo',
|
|
12
|
+
prompt_text: prompt,
|
|
13
|
+
prompt_image: null,
|
|
14
|
+
duration: 5
|
|
15
|
+
})
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) throw new Error(`Runway error ${response.status}: ${await response.text()}`);
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
return [{ url: data.video?.url || data.url, id: data.id }];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
name: 'video_generation',
|
|
26
|
+
description: 'Generate videos using AI. Supports RunwayML provider.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
prompt: { type: 'string', description: 'Text description of the video to generate' },
|
|
31
|
+
provider: { type: 'string', description: 'Video provider: runway (default: runway)', enum: ['runway'] },
|
|
32
|
+
model: { type: 'string', description: 'Model override (default: gen3a_turbo)' }
|
|
33
|
+
},
|
|
34
|
+
required: ['prompt']
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
async execute(params) {
|
|
38
|
+
try {
|
|
39
|
+
const config = getConfig();
|
|
40
|
+
const provider = params.provider || 'runway';
|
|
41
|
+
|
|
42
|
+
const providerConfig = PROVIDERS[provider];
|
|
43
|
+
if (!providerConfig) {
|
|
44
|
+
return { success: false, error: `Desteklenmeyen provider: ${provider}` };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const apiKey = params.apiKey || config.runwayApiKey || process.env.RUNWAY_API_KEY;
|
|
48
|
+
if (!apiKey) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: `${providerConfig.name} API key gerekli.\nKur: natureco config set runwayApiKey <key>`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const videos = await providerConfig.generate({
|
|
56
|
+
prompt: params.prompt,
|
|
57
|
+
apiKey,
|
|
58
|
+
model: params.model
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
prompt: params.prompt,
|
|
64
|
+
provider,
|
|
65
|
+
videos,
|
|
66
|
+
count: videos.length
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return { success: false, error: error.message };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const { getConfig } = require('../utils/config');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
name: 'web_readability',
|
|
5
|
+
description: 'Extract readable content from any web page (Mozilla Readability algorithm, no API key needed)',
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
url: { type: 'string', description: 'URL to extract readable content from' },
|
|
10
|
+
maxChars: { type: 'number', description: 'Maximum characters to return (default: 10000)', default: 10000 }
|
|
11
|
+
},
|
|
12
|
+
required: ['url']
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
async execute(params) {
|
|
16
|
+
try {
|
|
17
|
+
const maxChars = params.maxChars || 10000;
|
|
18
|
+
|
|
19
|
+
const response = await fetch(params.url, {
|
|
20
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NatureCo-CLI/2.0; +https://natureco.me)' },
|
|
21
|
+
signal: AbortSignal.timeout(15000)
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
return { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const html = await response.text();
|
|
29
|
+
const contentType = response.headers.get('content-type') || '';
|
|
30
|
+
const isHtml = contentType.includes('text/html') || contentType.includes('text/plain') || html.trim().startsWith('<');
|
|
31
|
+
|
|
32
|
+
if (!isHtml) {
|
|
33
|
+
return { success: false, error: 'URL does not contain HTML content' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Extract title
|
|
37
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
38
|
+
const title = titleMatch ? titleMatch[1].trim() : '';
|
|
39
|
+
|
|
40
|
+
// Extract meta description
|
|
41
|
+
const descMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i);
|
|
42
|
+
const description = descMatch ? descMatch[1] : '';
|
|
43
|
+
|
|
44
|
+
// Strip HTML tags for readable text
|
|
45
|
+
let text = html
|
|
46
|
+
// Remove scripts and styles
|
|
47
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ' ')
|
|
48
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, ' ')
|
|
49
|
+
.replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, ' ')
|
|
50
|
+
.replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, ' ')
|
|
51
|
+
.replace(/<header[^>]*>[\s\S]*?<\/header>/gi, ' ')
|
|
52
|
+
// Remove all HTML tags
|
|
53
|
+
.replace(/<[^>]+>/g, ' ')
|
|
54
|
+
// Decode HTML entities
|
|
55
|
+
.replace(/&/g, '&')
|
|
56
|
+
.replace(/</g, '<')
|
|
57
|
+
.replace(/>/g, '>')
|
|
58
|
+
.replace(/"/g, '"')
|
|
59
|
+
.replace(/'/g, "'")
|
|
60
|
+
.replace(///g, '/')
|
|
61
|
+
.replace(/&#\d+;/g, ' ')
|
|
62
|
+
// Normalize whitespace
|
|
63
|
+
.replace(/ /g, ' ')
|
|
64
|
+
.replace(/\s+/g, ' ')
|
|
65
|
+
.trim();
|
|
66
|
+
|
|
67
|
+
// Extract meaningful paragraphs
|
|
68
|
+
const paragraphs = [];
|
|
69
|
+
const pRegex = /<p[^>]*>([\s\S]*?)<\/p>/gi;
|
|
70
|
+
let pMatch;
|
|
71
|
+
while ((pMatch = pRegex.exec(html)) !== null) {
|
|
72
|
+
const pText = pMatch[1]
|
|
73
|
+
.replace(/<[^>]+>/g, '')
|
|
74
|
+
.replace(/ /g, ' ')
|
|
75
|
+
.replace(/\s+/g, ' ')
|
|
76
|
+
.trim();
|
|
77
|
+
if (pText.length > 20) paragraphs.push(pText);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Prefer paragraph extraction, fall back to full text
|
|
81
|
+
const content = paragraphs.length > 0
|
|
82
|
+
? paragraphs.join('\n\n')
|
|
83
|
+
: text;
|
|
84
|
+
|
|
85
|
+
const truncated = content.length > maxChars
|
|
86
|
+
? content.slice(0, maxChars) + '...'
|
|
87
|
+
: content;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
url: params.url,
|
|
92
|
+
title,
|
|
93
|
+
description,
|
|
94
|
+
content: truncated,
|
|
95
|
+
wordCount: content.split(/\s+/).length,
|
|
96
|
+
totalChars: content.length,
|
|
97
|
+
truncated: content.length > maxChars,
|
|
98
|
+
source: 'readability'
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return { success: false, error: error.message };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
@@ -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
561
|
const messages = [];
|
|
553
|
-
if (
|
|
554
|
-
messages.push({ role: 'system', content:
|
|
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
|
}
|