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.
Files changed (111) hide show
  1. package/README.md +94 -11
  2. package/bin/natureco.js +495 -94
  3. package/package.json +1 -1
  4. package/src/commands/acp.js +39 -0
  5. package/src/commands/admin-rpc.js +302 -0
  6. package/src/commands/agent.js +280 -0
  7. package/src/commands/agents.js +114 -30
  8. package/src/commands/approvals.js +214 -0
  9. package/src/commands/backup.js +124 -0
  10. package/src/commands/bonjour.js +167 -0
  11. package/src/commands/browser.js +815 -0
  12. package/src/commands/capability.js +237 -0
  13. package/src/commands/channels.js +422 -267
  14. package/src/commands/chat.js +5 -8
  15. package/src/commands/clawbot.js +19 -0
  16. package/src/commands/clickclack.js +130 -0
  17. package/src/commands/code.js +3 -2
  18. package/src/commands/commitments.js +148 -0
  19. package/src/commands/completion.js +84 -0
  20. package/src/commands/config.js +219 -30
  21. package/src/commands/configure.js +110 -0
  22. package/src/commands/crestodian.js +92 -0
  23. package/src/commands/cron.js +239 -19
  24. package/src/commands/daemon.js +90 -0
  25. package/src/commands/dashboard.js +47 -374
  26. package/src/commands/device-pair.js +248 -0
  27. package/src/commands/devices.js +137 -0
  28. package/src/commands/directory.js +179 -0
  29. package/src/commands/dns.js +196 -0
  30. package/src/commands/docs.js +136 -0
  31. package/src/commands/doctor.js +143 -492
  32. package/src/commands/exec-policy.js +80 -0
  33. package/src/commands/gateway-server.js +1155 -24
  34. package/src/commands/gateway.js +492 -249
  35. package/src/commands/health.js +148 -0
  36. package/src/commands/help.js +24 -25
  37. package/src/commands/hooks.js +141 -87
  38. package/src/commands/imessage.js +128 -14
  39. package/src/commands/infer.js +1474 -0
  40. package/src/commands/irc.js +64 -15
  41. package/src/commands/logs.js +122 -99
  42. package/src/commands/mattermost.js +114 -12
  43. package/src/commands/mcp.js +121 -309
  44. package/src/commands/memory-cmd.js +134 -1
  45. package/src/commands/memory.js +128 -0
  46. package/src/commands/message.js +720 -134
  47. package/src/commands/migrate.js +213 -2
  48. package/src/commands/models.js +39 -1
  49. package/src/commands/node.js +98 -0
  50. package/src/commands/nodes.js +362 -0
  51. package/src/commands/oc-path.js +200 -0
  52. package/src/commands/onboard.js +129 -0
  53. package/src/commands/open-prose.js +67 -0
  54. package/src/commands/pairing.js +108 -107
  55. package/src/commands/path.js +206 -0
  56. package/src/commands/plugins.js +35 -1
  57. package/src/commands/policy.js +176 -0
  58. package/src/commands/proxy.js +306 -0
  59. package/src/commands/qr.js +70 -0
  60. package/src/commands/reset.js +101 -94
  61. package/src/commands/sandbox.js +125 -0
  62. package/src/commands/secrets.js +201 -0
  63. package/src/commands/sessions.js +110 -51
  64. package/src/commands/setup.js +102 -543
  65. package/src/commands/signal.js +447 -18
  66. package/src/commands/skills.js +67 -1
  67. package/src/commands/sms.js +123 -19
  68. package/src/commands/status.js +101 -127
  69. package/src/commands/system.js +53 -0
  70. package/src/commands/tasks.js +208 -100
  71. package/src/commands/terminal.js +139 -0
  72. package/src/commands/thread-ownership.js +157 -0
  73. package/src/commands/transcripts.js +95 -0
  74. package/src/commands/tui.js +41 -0
  75. package/src/commands/uninstall.js +73 -92
  76. package/src/commands/update.js +146 -91
  77. package/src/commands/voice.js +82 -0
  78. package/src/commands/vydra.js +98 -0
  79. package/src/commands/webhooks.js +58 -66
  80. package/src/commands/wiki.js +783 -0
  81. package/src/commands/workboard.js +207 -0
  82. package/src/tools/audio_understanding.js +154 -0
  83. package/src/tools/browser.js +112 -0
  84. package/src/tools/canvas.js +104 -0
  85. package/src/tools/document_extract.js +84 -0
  86. package/src/tools/duckduckgo.js +54 -0
  87. package/src/tools/exa_search.js +66 -0
  88. package/src/tools/firecrawl.js +104 -0
  89. package/src/tools/image_generation.js +99 -0
  90. package/src/tools/llm_task.js +118 -0
  91. package/src/tools/media_understanding.js +128 -0
  92. package/src/tools/music_generation.js +113 -0
  93. package/src/tools/parallel_search.js +77 -0
  94. package/src/tools/phone_control.js +80 -0
  95. package/src/tools/phone_control_enhanced.js +184 -0
  96. package/src/tools/searxng.js +61 -0
  97. package/src/tools/speech_to_text.js +135 -0
  98. package/src/tools/text_to_speech.js +105 -0
  99. package/src/tools/thread_ownership.js +88 -0
  100. package/src/tools/video_generation.js +72 -0
  101. package/src/tools/web_readability.js +104 -0
  102. package/src/utils/agents-md.js +85 -0
  103. package/src/utils/api.js +39 -40
  104. package/src/utils/format.js +144 -0
  105. package/src/utils/headless.js +2 -1
  106. package/src/utils/memory.js +200 -0
  107. package/src/utils/parallel-tools.js +106 -0
  108. package/src/utils/sub-agent.js +148 -0
  109. package/src/utils/token-budget.js +304 -0
  110. package/src/utils/tool-runner.js +7 -5
  111. 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(/&amp;/g, '&')
56
+ .replace(/&lt;/g, '<')
57
+ .replace(/&gt;/g, '>')
58
+ .replace(/&quot;/g, '"')
59
+ .replace(/&#x27;/g, "'")
60
+ .replace(/&#x2F;/g, '/')
61
+ .replace(/&#\d+;/g, ' ')
62
+ // Normalize whitespace
63
+ .replace(/&nbsp;/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(/&nbsp;/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(-10), null, 2));
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 || '').slice(0, 100),
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 to max 1500 characters
269
- if (output.length > 1500) {
270
- output = output.slice(0, 1500) + '... (truncated)';
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
- if (fallbackOutput.length > 1500) {
285
- fallbackOutput = fallbackOutput.slice(0, 1500) + '... (truncated)';
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 (systemPrompt) {
554
- messages.push({ role: 'system', content: systemPrompt });
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 = providerConfig.isAnthropic
561
- ? formatToolsForAnthropic()
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
- // Base system prompt — kısa (~3000 token max)
778
+ // Minimal base prompt (~200 token)
759
779
  const toolDefs = getToolDefinitions();
760
780
  const toolsDesc = toolDefs.map(t => t.name).join(', ');
761
- let baseSystemPrompt = `Terminal assistant. Use tools when needed.
762
- Home: ${homeDir}
763
- Tools: ${toolsDesc}`;
781
+ let systemPrompt = `Assistant. Tools: ${toolsDesc}. Home: ${homeDir}.`;
764
782
 
765
- // MCP sayı parametresi uyarısı sadece MCP aktifse
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\n' + chatSystemPrompt.slice(0, 3000);
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 };
@@ -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, 3000),
173
+ content: resultStr.slice(0, TB.load().toolMaxChars),
173
174
  });
174
175
  }
175
176
  }