natureco-cli 2.23.28 → 2.23.30

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 (96) hide show
  1. package/README.md +94 -11
  2. package/bin/natureco.js +470 -10
  3. package/package.json +10 -6
  4. package/src/commands/admin-rpc.js +219 -0
  5. package/src/commands/agent.js +89 -0
  6. package/src/commands/approvals.js +53 -0
  7. package/src/commands/backup.js +124 -0
  8. package/src/commands/bonjour.js +167 -0
  9. package/src/commands/capability.js +64 -0
  10. package/src/commands/channels.js +94 -4
  11. package/src/commands/chat.js +11 -25
  12. package/src/commands/clickclack.js +130 -0
  13. package/src/commands/commitments.js +32 -0
  14. package/src/commands/completion.js +76 -0
  15. package/src/commands/config.js +111 -68
  16. package/src/commands/configure.js +93 -0
  17. package/src/commands/crestodian.js +92 -0
  18. package/src/commands/daemon.js +60 -0
  19. package/src/commands/device-pair.js +248 -0
  20. package/src/commands/devices.js +110 -0
  21. package/src/commands/directory.js +47 -0
  22. package/src/commands/dns.js +58 -0
  23. package/src/commands/docs.js +43 -0
  24. package/src/commands/doctor.js +121 -16
  25. package/src/commands/exec-policy.js +71 -0
  26. package/src/commands/gateway-server.js +1175 -30
  27. package/src/commands/gateway.js +11 -20
  28. package/src/commands/health.js +18 -0
  29. package/src/commands/help.js +6 -0
  30. package/src/commands/imessage.js +169 -0
  31. package/src/commands/infer.js +73 -0
  32. package/src/commands/irc.js +119 -0
  33. package/src/commands/mattermost.js +164 -0
  34. package/src/commands/memory-cmd.js +134 -1
  35. package/src/commands/message.js +30 -4
  36. package/src/commands/migrate.js +213 -2
  37. package/src/commands/models.js +584 -216
  38. package/src/commands/node.js +98 -0
  39. package/src/commands/nodes.js +106 -0
  40. package/src/commands/oc-path.js +200 -0
  41. package/src/commands/onboard.js +70 -0
  42. package/src/commands/open-prose.js +67 -0
  43. package/src/commands/plugins.js +415 -172
  44. package/src/commands/policy.js +176 -0
  45. package/src/commands/proxy.js +155 -0
  46. package/src/commands/qr.js +28 -0
  47. package/src/commands/sandbox.js +125 -0
  48. package/src/commands/secrets.js +118 -0
  49. package/src/commands/security.js +149 -1
  50. package/src/commands/setup.js +114 -10
  51. package/src/commands/signal.js +495 -0
  52. package/src/commands/skills.js +20 -29
  53. package/src/commands/sms.js +168 -0
  54. package/src/commands/system.js +53 -0
  55. package/src/commands/tasks.js +328 -79
  56. package/src/commands/terminal.js +21 -0
  57. package/src/commands/thread-ownership.js +157 -0
  58. package/src/commands/transcripts.js +72 -0
  59. package/src/commands/voice.js +82 -0
  60. package/src/commands/vydra.js +98 -0
  61. package/src/commands/webhooks.js +79 -0
  62. package/src/commands/whatsapp.js +7 -21
  63. package/src/commands/workboard.js +207 -0
  64. package/src/tools/audio_understanding.js +154 -0
  65. package/src/tools/bash.js +63 -29
  66. package/src/tools/browser.js +112 -0
  67. package/src/tools/canvas.js +104 -0
  68. package/src/tools/document_extract.js +84 -0
  69. package/src/tools/duckduckgo.js +54 -0
  70. package/src/tools/exa_search.js +66 -0
  71. package/src/tools/firecrawl.js +104 -0
  72. package/src/tools/image_generation.js +99 -0
  73. package/src/tools/llm_task.js +118 -0
  74. package/src/tools/media_understanding.js +128 -0
  75. package/src/tools/music_generation.js +113 -0
  76. package/src/tools/parallel_search.js +77 -0
  77. package/src/tools/phone_control.js +80 -0
  78. package/src/tools/phone_control_enhanced.js +184 -0
  79. package/src/tools/searxng.js +61 -0
  80. package/src/tools/speech_to_text.js +135 -0
  81. package/src/tools/text_to_speech.js +105 -0
  82. package/src/tools/thread_ownership.js +88 -0
  83. package/src/tools/video_generation.js +72 -0
  84. package/src/tools/web_readability.js +104 -0
  85. package/src/utils/api.js +3 -20
  86. package/src/utils/approvals.js +297 -0
  87. package/src/utils/background.js +223 -66
  88. package/src/utils/baileys.js +21 -0
  89. package/src/utils/config.js +141 -10
  90. package/src/utils/errors.js +148 -0
  91. package/src/utils/inquirer-wrapper.js +1 -2
  92. package/src/utils/memory.js +200 -0
  93. package/src/utils/path-utils.js +13 -13
  94. package/src/utils/plugin-registry.js +238 -0
  95. package/src/utils/secrets.js +177 -0
  96. package/src/utils/skills.js +10 -23
@@ -0,0 +1,105 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ module.exports = {
4
+ name: 'text_to_speech',
5
+ description: 'Convert text to speech using ElevenLabs, OpenAI TTS, or local CLI TTS',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ text: { type: 'string', description: 'Text to convert to speech' },
10
+ provider: { type: 'string', description: 'TTS provider: elevenlabs, openai, local (default: elevenlabs)', enum: ['elevenlabs', 'openai', 'local'] },
11
+ voice: { type: 'string', description: 'Voice ID or name (ElevenLabs: Rachel, Adam, etc; OpenAI: alloy, echo, fable, nova, shimmer)' },
12
+ outputPath: { type: 'string', description: 'Save audio to file path (optional)' }
13
+ },
14
+ required: ['text']
15
+ },
16
+
17
+ async execute(params) {
18
+ try {
19
+ const config = getConfig();
20
+ const provider = params.provider || config.ttsProvider || 'elevenlabs';
21
+ const outputPath = params.outputPath;
22
+
23
+ if (provider === 'elevenlabs') {
24
+ const apiKey = params.apiKey || config.elevenlabsApiKey || process.env.ELEVENLABS_API_KEY;
25
+ if (!apiKey) {
26
+ return { success: false, error: 'ElevenLabs API key gerekli.\nKur: natureco config set elevenlabsApiKey <key>' };
27
+ }
28
+
29
+ const voice = params.voice || config.elevenlabsVoice || '21m00Tcm4TlvDq8ikWAM';
30
+ const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voice}`, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json', 'xi-api-key': apiKey },
33
+ body: JSON.stringify({
34
+ text: params.text,
35
+ model_id: 'eleven_monolingual_v1',
36
+ voice_settings: { stability: 0.5, similarity_boost: 0.5 }
37
+ })
38
+ });
39
+
40
+ if (!response.ok) throw new Error(`ElevenLabs error ${response.status}`);
41
+
42
+ const audioBuffer = await response.arrayBuffer();
43
+ if (outputPath) {
44
+ require('fs').writeFileSync(outputPath, Buffer.from(audioBuffer));
45
+ return { success: true, provider: 'elevenlabs', outputPath, size: audioBuffer.byteLength };
46
+ }
47
+
48
+ return {
49
+ success: true,
50
+ provider: 'elevenlabs',
51
+ audioData: Buffer.from(audioBuffer).toString('base64'),
52
+ format: 'mp3',
53
+ message: 'Audio generated. Use outputPath to save to file.'
54
+ };
55
+ }
56
+
57
+ if (provider === 'openai') {
58
+ const apiKey = params.apiKey || config.openaiApiKey || process.env.OPENAI_API_KEY;
59
+ if (!apiKey) {
60
+ return { success: false, error: 'OpenAI API key gerekli.' };
61
+ }
62
+
63
+ const voice = params.voice || 'alloy';
64
+ const response = await fetch('https://api.openai.com/v1/audio/speech', {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
67
+ body: JSON.stringify({ model: 'tts-1', input: params.text, voice })
68
+ });
69
+
70
+ if (!response.ok) throw new Error(`OpenAI TTS error ${response.status}`);
71
+
72
+ const audioBuffer = await response.arrayBuffer();
73
+ if (outputPath) {
74
+ require('fs').writeFileSync(outputPath, Buffer.from(audioBuffer));
75
+ return { success: true, provider: 'openai', outputPath, size: audioBuffer.byteLength };
76
+ }
77
+
78
+ return {
79
+ success: true, provider: 'openai',
80
+ audioData: Buffer.from(audioBuffer).toString('base64'), format: 'mp3'
81
+ };
82
+ }
83
+
84
+ if (provider === 'local') {
85
+ const { execSync } = require('child_process');
86
+ try {
87
+ execSync('which say || which espeak || which festival', { stdio: 'ignore' });
88
+ } catch {
89
+ return { success: false, error: 'Local TTS aracı bulunamadı. macOS: "say", Linux: "espeak"' };
90
+ }
91
+
92
+ const cmd = process.platform === 'darwin'
93
+ ? `say "${params.text.replace(/"/g, '\\"')}"${outputPath ? ` -o "${outputPath}"` : ''}`
94
+ : `espeak "${params.text.replace(/"/g, '\\"')}"${outputPath ? ` -w "${outputPath}"` : ''}`;
95
+
96
+ execSync(cmd, { timeout: 30000 });
97
+ return { success: true, provider: 'local', outputPath: outputPath || 'played via speakers', message: 'Text spoken via local TTS' };
98
+ }
99
+
100
+ return { success: false, error: `Unknown provider: ${provider}` };
101
+ } catch (error) {
102
+ return { success: false, error: error.message };
103
+ }
104
+ }
105
+ };
@@ -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
+ };
package/src/utils/api.js CHANGED
@@ -317,8 +317,6 @@ function encodeToolResult(toolResult) {
317
317
  content = toolResult;
318
318
  } else if (toolResult.output) {
319
319
  content = toolResult.output;
320
- /* DEAD CODE */ } else if (toolResult.data) {
321
- content = JSON.stringify(toolResult.data); /* DEAD CODE */
322
320
  } else if (toolResult.success !== undefined) {
323
321
  // Handle { success: true/false, output/error: ... } format
324
322
  content = toolResult.success ? (toolResult.output || JSON.stringify(toolResult)) : (toolResult.error || 'Unknown error');
@@ -429,9 +427,7 @@ function formatToolsForAnthropic() {
429
427
  */
430
428
  async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
431
429
  const baseUrl = providerConfig.url.replace(/\/+$/, '');
432
- const endpoint = baseUrl.includes('api.natureco.me')
433
- ? 'https://api.natureco.me/api/v1/chat/completions'
434
- : `${baseUrl}/chat/completions`;
430
+ const endpoint = `${baseUrl}/chat/completions`;
435
431
  const requestBody = {
436
432
  model: providerConfig.model,
437
433
  messages: messages,
@@ -859,16 +855,8 @@ async function getBots(apiKey) {
859
855
  }
860
856
  } catch (e) {
861
857
  debugLog('[getBots] NatureCo API error:', e.message);
858
+ return { bots: [], error: e.message };
862
859
  }
863
- // Fallback — API'den bot gelmezse universal provider döndür
864
- return {
865
- bots: [{
866
- id: 'universal-provider',
867
- name: 'Universal Provider (NatureCo)',
868
- ai_provider: 'natureco',
869
- model: 'natureco-default',
870
- }]
871
- };
872
860
  }
873
861
 
874
862
  // Diğer provider'lar — universal provider döndür
@@ -889,9 +877,6 @@ async function getBots(apiKey) {
889
877
  // ── Streaming Support ────────────────────────────────────────────────────────────
890
878
 
891
879
  async function streamProviderCompletion(providerConfig, messages, tools) {
892
- if (providerConfig.url.includes('api.natureco.me')) {
893
- return sendMessageOpenAICompatible(providerConfig, messages, tools);
894
- }
895
880
  if (providerConfig.isAnthropic) {
896
881
  return streamAnthropicCompletion(providerConfig, messages);
897
882
  }
@@ -900,9 +885,7 @@ async function streamProviderCompletion(providerConfig, messages, tools) {
900
885
 
901
886
  async function streamOpenAICompletion(providerConfig, messages, tools) {
902
887
  const baseUrl = providerConfig.url.replace(/\/+$/, '');
903
- const endpoint = baseUrl.includes('api.natureco.me')
904
- ? 'https://api.natureco.me/api/v1/chat/completions'
905
- : `${baseUrl}/chat/completions`;
888
+ const endpoint = `${baseUrl}/chat/completions`;
906
889
 
907
890
  const requestBody = {
908
891
  model: providerConfig.model,