natureco-cli 2.23.29 → 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 (71) hide show
  1. package/README.md +94 -11
  2. package/bin/natureco.js +402 -4
  3. package/package.json +1 -1
  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/clickclack.js +130 -0
  11. package/src/commands/commitments.js +32 -0
  12. package/src/commands/completion.js +76 -0
  13. package/src/commands/configure.js +93 -0
  14. package/src/commands/crestodian.js +92 -0
  15. package/src/commands/daemon.js +60 -0
  16. package/src/commands/device-pair.js +248 -0
  17. package/src/commands/devices.js +110 -0
  18. package/src/commands/directory.js +47 -0
  19. package/src/commands/dns.js +58 -0
  20. package/src/commands/docs.js +43 -0
  21. package/src/commands/exec-policy.js +71 -0
  22. package/src/commands/gateway-server.js +1155 -24
  23. package/src/commands/health.js +18 -0
  24. package/src/commands/imessage.js +128 -14
  25. package/src/commands/infer.js +73 -0
  26. package/src/commands/irc.js +64 -15
  27. package/src/commands/mattermost.js +114 -12
  28. package/src/commands/memory-cmd.js +134 -1
  29. package/src/commands/message.js +9 -3
  30. package/src/commands/migrate.js +213 -2
  31. package/src/commands/node.js +98 -0
  32. package/src/commands/nodes.js +106 -0
  33. package/src/commands/oc-path.js +200 -0
  34. package/src/commands/onboard.js +70 -0
  35. package/src/commands/open-prose.js +67 -0
  36. package/src/commands/policy.js +176 -0
  37. package/src/commands/proxy.js +155 -0
  38. package/src/commands/qr.js +28 -0
  39. package/src/commands/sandbox.js +125 -0
  40. package/src/commands/secrets.js +118 -0
  41. package/src/commands/setup.js +113 -7
  42. package/src/commands/signal.js +447 -18
  43. package/src/commands/sms.js +123 -19
  44. package/src/commands/system.js +53 -0
  45. package/src/commands/terminal.js +21 -0
  46. package/src/commands/thread-ownership.js +157 -0
  47. package/src/commands/transcripts.js +72 -0
  48. package/src/commands/voice.js +82 -0
  49. package/src/commands/vydra.js +98 -0
  50. package/src/commands/workboard.js +207 -0
  51. package/src/tools/audio_understanding.js +154 -0
  52. package/src/tools/browser.js +112 -0
  53. package/src/tools/canvas.js +104 -0
  54. package/src/tools/document_extract.js +84 -0
  55. package/src/tools/duckduckgo.js +54 -0
  56. package/src/tools/exa_search.js +66 -0
  57. package/src/tools/firecrawl.js +104 -0
  58. package/src/tools/image_generation.js +99 -0
  59. package/src/tools/llm_task.js +118 -0
  60. package/src/tools/media_understanding.js +128 -0
  61. package/src/tools/music_generation.js +113 -0
  62. package/src/tools/parallel_search.js +77 -0
  63. package/src/tools/phone_control.js +80 -0
  64. package/src/tools/phone_control_enhanced.js +184 -0
  65. package/src/tools/searxng.js +61 -0
  66. package/src/tools/speech_to_text.js +135 -0
  67. package/src/tools/text_to_speech.js +105 -0
  68. package/src/tools/thread_ownership.js +88 -0
  69. package/src/tools/video_generation.js +72 -0
  70. package/src/tools/web_readability.js +104 -0
  71. package/src/utils/memory.js +200 -0
@@ -0,0 +1,54 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ module.exports = {
4
+ name: 'duckduckgo_search',
5
+ description: 'Search the web using DuckDuckGo (no API key required)',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ query: { type: 'string', description: 'Search query' },
10
+ maxResults: { type: 'number', description: 'Maximum results (default: 5)', default: 5 }
11
+ },
12
+ required: ['query']
13
+ },
14
+
15
+ async execute(params) {
16
+ try {
17
+ const query = encodeURIComponent(params.query);
18
+ const maxResults = params.maxResults || 5;
19
+
20
+ const response = await fetch(
21
+ `https://html.duckduckgo.com/html/?q=${query}`,
22
+ { headers: { 'User-Agent': 'NatureCo-CLI/2.0' } }
23
+ );
24
+
25
+ if (!response.ok) {
26
+ return { success: false, error: `DuckDuckGo error: ${response.status}` };
27
+ }
28
+
29
+ const html = await response.text();
30
+
31
+ // Extract result snippets from DuckDuckGo HTML
32
+ const results = [];
33
+ const resultRegex = /<a rel="nofollow" class="result__a" href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
34
+ let match;
35
+ while ((match = resultRegex.exec(html)) !== null && results.length < maxResults) {
36
+ results.push({
37
+ title: match[2].replace(/<[^>]+>/g, '').trim(),
38
+ snippet: match[3].replace(/<[^>]+>/g, '').trim(),
39
+ url: match[1]
40
+ });
41
+ }
42
+
43
+ return {
44
+ success: true,
45
+ query: params.query,
46
+ results,
47
+ count: results.length,
48
+ source: 'duckduckgo'
49
+ };
50
+ } catch (error) {
51
+ return { success: false, error: error.message };
52
+ }
53
+ }
54
+ };
@@ -0,0 +1,66 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ module.exports = {
4
+ name: 'exa_search',
5
+ description: 'Search the web using Exa (exa.ai) — AI-powered search with content extraction',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ query: { type: 'string', description: 'Search query' },
10
+ maxResults: { type: 'number', description: 'Maximum results (default: 5)', default: 5 },
11
+ type: { type: 'string', description: 'Search type: keyword, neural, auto (default: auto)', enum: ['keyword', 'neural', 'auto'] },
12
+ includeText: { type: 'array', items: { type: 'string' }, description: 'Keywords that must appear' },
13
+ excludeText: { type: 'array', items: { type: 'string' }, description: 'Keywords to exclude' }
14
+ },
15
+ required: ['query']
16
+ },
17
+
18
+ async execute(params) {
19
+ try {
20
+ const config = getConfig();
21
+ const apiKey = config.exaApiKey || process.env.EXA_API_KEY;
22
+
23
+ if (!apiKey) {
24
+ return {
25
+ success: false,
26
+ error: 'Exa API key gerekli. Kur: natureco config set exaApiKey <key>\nKey al: https://dashboard.exa.ai/api-keys'
27
+ };
28
+ }
29
+
30
+ const response = await fetch('https://api.exa.ai/search', {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
33
+ body: JSON.stringify({
34
+ query: params.query,
35
+ type: params.type || 'auto',
36
+ numResults: params.maxResults || 5,
37
+ includeTerms: params.includeText,
38
+ excludeTerms: params.excludeText
39
+ })
40
+ });
41
+
42
+ if (!response.ok) {
43
+ const err = await response.text().catch(() => '');
44
+ return { success: false, error: `Exa error ${response.status}: ${err}` };
45
+ }
46
+
47
+ const data = await response.json();
48
+
49
+ return {
50
+ success: true,
51
+ query: params.query,
52
+ results: (data.results || []).map(r => ({
53
+ title: r.title,
54
+ snippet: r.text,
55
+ url: r.url,
56
+ score: r.score,
57
+ publishedDate: r.publishedDate
58
+ })),
59
+ count: data.results?.length || 0,
60
+ source: 'exa'
61
+ };
62
+ } catch (error) {
63
+ return { success: false, error: error.message };
64
+ }
65
+ }
66
+ };
@@ -0,0 +1,104 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ module.exports = {
4
+ name: 'firecrawl',
5
+ description: 'Scrape and crawl web pages using Firecrawl — extracts markdown content from any URL',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ url: { type: 'string', description: 'URL to scrape or crawl' },
10
+ mode: { type: 'string', description: 'Mode: scrape (single page) or crawl (entire site, max 10 pages)', enum: ['scrape', 'crawl'], default: 'scrape' },
11
+ maxPages: { type: 'number', description: 'Max pages to crawl (default: 5, max: 10)', default: 5 },
12
+ formats: { type: 'array', items: { type: 'string' }, description: 'Output formats: markdown, html, rawHtml, screenshot (default: markdown)' }
13
+ },
14
+ required: ['url']
15
+ },
16
+
17
+ async execute(params) {
18
+ try {
19
+ const config = getConfig();
20
+ const apiKey = config.firecrawlApiKey || process.env.FIRECRAWL_API_KEY;
21
+
22
+ if (!apiKey) {
23
+ return {
24
+ success: false,
25
+ error: 'Firecrawl API key gerekli. Kur: natureco config set firecrawlApiKey <key>\nKey al: https://www.firecrawl.dev'
26
+ };
27
+ }
28
+
29
+ const formats = params.formats || ['markdown'];
30
+ const maxPages = Math.min(params.maxPages || 5, 10);
31
+
32
+ if (params.mode === 'crawl') {
33
+ // Crawl mode
34
+ const crawlResponse = await fetch('https://api.firecrawl.dev/v1/crawl', {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
37
+ body: JSON.stringify({
38
+ url: params.url,
39
+ limit: maxPages,
40
+ scrapeOptions: { formats }
41
+ })
42
+ });
43
+
44
+ if (!crawlResponse.ok) {
45
+ return { success: false, error: `Firecrawl crawl error: ${crawlResponse.status}` };
46
+ }
47
+
48
+ const { id } = await crawlResponse.json();
49
+
50
+ // Poll for results
51
+ for (let i = 0; i < 30; i++) {
52
+ await new Promise(r => setTimeout(r, 2000));
53
+ const statusResponse = await fetch(`https://api.firecrawl.dev/v1/crawl/${id}`, {
54
+ headers: { 'Authorization': `Bearer ${apiKey}` }
55
+ });
56
+ if (!statusResponse.ok) continue;
57
+
58
+ const statusData = await statusResponse.json();
59
+ if (statusData.status === 'completed') {
60
+ return {
61
+ success: true,
62
+ url: params.url,
63
+ mode: 'crawl',
64
+ pages: (statusData.data || []).map(p => ({
65
+ url: p.url || p.metadata?.url,
66
+ content: p.markdown || '',
67
+ title: p.metadata?.title
68
+ })),
69
+ count: statusData.data?.length || 0,
70
+ source: 'firecrawl'
71
+ };
72
+ }
73
+ }
74
+
75
+ return { success: false, error: 'Firecrawl crawl timed out' };
76
+ }
77
+
78
+ // Scrape mode
79
+ const response = await fetch('https://api.firecrawl.dev/v1/scrape', {
80
+ method: 'POST',
81
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
82
+ body: JSON.stringify({ url: params.url, formats })
83
+ });
84
+
85
+ if (!response.ok) {
86
+ return { success: false, error: `Firecrawl scrape error: ${response.status}` };
87
+ }
88
+
89
+ const data = await response.json();
90
+
91
+ return {
92
+ success: true,
93
+ url: params.url,
94
+ mode: 'scrape',
95
+ content: data.data?.markdown || data.data?.content || '',
96
+ title: data.data?.metadata?.title,
97
+ description: data.data?.metadata?.description,
98
+ source: 'firecrawl'
99
+ };
100
+ } catch (error) {
101
+ return { success: false, error: error.message };
102
+ }
103
+ }
104
+ };
@@ -0,0 +1,99 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ const PROVIDERS = {
4
+ openai: {
5
+ name: 'OpenAI DALL-E',
6
+ async generate({ prompt, size, n, apiKey }) {
7
+ const response = await fetch('https://api.openai.com/v1/images/generations', {
8
+ method: 'POST',
9
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
10
+ body: JSON.stringify({ prompt, n: n || 1, size: size || '1024x1024' })
11
+ });
12
+ if (!response.ok) throw new Error(`OpenAI error ${response.status}: ${await response.text()}`);
13
+ return (await response.json()).data;
14
+ }
15
+ },
16
+ fal: {
17
+ name: 'FAL.ai',
18
+ async generate({ prompt, apiKey, model }) {
19
+ const response = await fetch(`https://fal.run/fal-ai/${model || 'fast-sdxl'}`, {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Key ${apiKey}` },
22
+ body: JSON.stringify({ prompt })
23
+ });
24
+ if (!response.ok) throw new Error(`FAL error ${response.status}: ${await response.text()}`);
25
+ return [{ url: (await response.json()).images?.[0]?.url || (await response.json()).image?.url }];
26
+ }
27
+ },
28
+ together: {
29
+ name: 'Together AI',
30
+ async generate({ prompt, apiKey, model }) {
31
+ const response = await fetch('https://api.together.xyz/v1/images/generations', {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
34
+ body: JSON.stringify({ model: model || 'black-forest-labs/FLUX.1-schnell', prompt, steps: 4, n: 1 })
35
+ });
36
+ if (!response.ok) throw new Error(`Together error ${response.status}: ${await response.text()}`);
37
+ const data = await response.json();
38
+ return data.data?.map(d => ({ url: d.url })) || [{ url: data.output?.[0] }];
39
+ }
40
+ }
41
+ };
42
+
43
+ module.exports = {
44
+ name: 'image_generation',
45
+ description: 'Generate images using AI. Supports OpenAI DALL-E, FAL.ai, and Together AI providers.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ prompt: { type: 'string', description: 'Text description of the image to generate' },
50
+ provider: { type: 'string', description: 'Image provider: openai, fal, together (default: openai)', enum: ['openai', 'fal', 'together'] },
51
+ model: { type: 'string', description: 'Model override (e.g. fast-sdxl for FAL, FLUX.1-schnell for Together)' },
52
+ size: { type: 'string', description: 'Image size for DALL-E: 1024x1024, 1792x1024, 1024x1792 (default: 1024x1024)' },
53
+ n: { type: 'number', description: 'Number of images to generate (default: 1, max: 4)' }
54
+ },
55
+ required: ['prompt']
56
+ },
57
+
58
+ async execute(params) {
59
+ try {
60
+ const config = getConfig();
61
+ const provider = params.provider || config.imageProvider || 'openai';
62
+
63
+ const providerConfig = PROVIDERS[provider];
64
+ if (!providerConfig) {
65
+ return { success: false, error: `Desteklenmeyen provider: ${provider}. Kullanılabilir: ${Object.keys(PROVIDERS).join(', ')}` };
66
+ }
67
+
68
+ let apiKey;
69
+ if (provider === 'openai') apiKey = params.apiKey || config.openaiApiKey || process.env.OPENAI_API_KEY;
70
+ else if (provider === 'fal') apiKey = params.apiKey || config.falApiKey || process.env.FAL_KEY;
71
+ else if (provider === 'together') apiKey = params.apiKey || config.togetherApiKey || process.env.TOGETHER_API_KEY;
72
+
73
+ if (!apiKey) {
74
+ return {
75
+ success: false,
76
+ error: `${providerConfig.name} API key gerekli.\nKur: natureco config set ${provider}ApiKey <key>`
77
+ };
78
+ }
79
+
80
+ const images = await providerConfig.generate({
81
+ prompt: params.prompt,
82
+ apiKey,
83
+ model: params.model,
84
+ size: params.size,
85
+ n: params.n
86
+ });
87
+
88
+ return {
89
+ success: true,
90
+ prompt: params.prompt,
91
+ provider: provider,
92
+ images: images.filter(i => i.url).map(i => ({ url: i.url, revisedPrompt: i.revised_prompt })),
93
+ count: images.filter(i => i.url).length
94
+ };
95
+ } catch (error) {
96
+ return { success: false, error: error.message };
97
+ }
98
+ }
99
+ };
@@ -0,0 +1,118 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ function stripCodeFences(s) {
4
+ const m = s.trim().match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
5
+ if (m) return (m[1] || '').trim();
6
+ return s.trim();
7
+ }
8
+
9
+ function validateAgainstSchema(value, schema) {
10
+ if (!schema || typeof schema !== 'object') return { ok: true };
11
+ if (schema.type === 'object' && schema.properties) {
12
+ if (typeof value !== 'object' || Array.isArray(value)) return { ok: false, errors: [{ text: 'expected object' }] };
13
+ const errors = [];
14
+ for (const [key, prop] of Object.entries(schema.properties)) {
15
+ if (prop.type && value[key] !== undefined) {
16
+ const actual = typeof value[key];
17
+ if (actual !== prop.type && !(prop.type === 'number' && actual === 'integer')) {
18
+ errors.push({ text: `${key}: expected ${prop.type}, got ${actual}` });
19
+ }
20
+ }
21
+ if (prop.required && value[key] === undefined) {
22
+ errors.push({ text: `${key}: required but missing` });
23
+ }
24
+ }
25
+ return errors.length ? { ok: false, errors } : { ok: true };
26
+ }
27
+ return { ok: true };
28
+ }
29
+
30
+ module.exports = {
31
+ name: 'llm_task',
32
+ description: 'Run a generic JSON-only LLM task and return schema-validated JSON. No tool calls, no commentary.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ prompt: { type: 'string', description: 'Task instruction for the LLM' },
37
+ input: { type: 'object', description: 'Optional input payload for the task' },
38
+ schema: { type: 'object', description: 'Optional JSON Schema to validate the returned JSON' },
39
+ provider: { type: 'string', description: 'Provider override (e.g. openai, anthropic)' },
40
+ model: { type: 'string', description: 'Model override' },
41
+ temperature: { type: 'number', description: 'Temperature override' },
42
+ maxTokens: { type: 'number', description: 'Max tokens override' }
43
+ },
44
+ required: ['prompt']
45
+ },
46
+
47
+ async execute(params) {
48
+ try {
49
+ const config = getConfig();
50
+ const provider = params.provider || config.provider || 'openai';
51
+ const model = params.model || config.model || 'gpt-4o';
52
+
53
+ let apiKey;
54
+ if (provider === 'openai') apiKey = params.apiKey || config.openaiApiKey || process.env.OPENAI_API_KEY;
55
+ else if (provider === 'anthropic') apiKey = params.apiKey || config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
56
+ else if (provider === 'groq') apiKey = params.apiKey || config.groqApiKey || process.env.GROQ_API_KEY;
57
+
58
+ if (!apiKey) {
59
+ return { success: false, error: `API key required for ${provider}. Set: natureco config set ${provider}ApiKey <key>` };
60
+ }
61
+
62
+ const systemPrompt = [
63
+ 'You are a JSON-only function.',
64
+ 'Return ONLY a valid JSON value.',
65
+ 'Do not wrap in markdown fences.',
66
+ 'Do not include commentary.',
67
+ 'Do not call tools.'
68
+ ].join(' ');
69
+
70
+ const inputJson = JSON.stringify(params.input ?? null, null, 2);
71
+ const fullPrompt = `${systemPrompt}\n\nTASK:\n${params.prompt}\n\nINPUT:\n${inputJson}\n`;
72
+
73
+ const baseUrl = provider === 'openai' ? 'https://api.openai.com/v1' : `https://api.${provider}.com/v1`;
74
+ const requestBody = {
75
+ model,
76
+ messages: [{ role: 'user', content: fullPrompt }],
77
+ temperature: params.temperature ?? 0.1,
78
+ max_tokens: params.maxTokens ?? 4096
79
+ };
80
+
81
+ const response = await fetch(`${baseUrl}/chat/completions`, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
84
+ body: JSON.stringify(requestBody)
85
+ });
86
+
87
+ if (!response.ok) {
88
+ return { success: false, error: `${provider} error ${response.status}: ${await response.text()}` };
89
+ }
90
+
91
+ const data = await response.json();
92
+ const text = data.choices?.[0]?.message?.content?.trim();
93
+ if (!text) return { success: false, error: 'LLM returned empty output' };
94
+
95
+ const raw = stripCodeFences(text);
96
+ let parsed;
97
+ try { parsed = JSON.parse(raw); }
98
+ catch { return { success: false, error: 'LLM returned invalid JSON', raw: text }; }
99
+
100
+ const schema = params.schema;
101
+ if (schema && typeof schema === 'object') {
102
+ const validation = validateAgainstSchema(parsed, schema);
103
+ if (!validation.ok) {
104
+ return {
105
+ success: false,
106
+ error: `JSON did not match schema: ${validation.errors.map(e => e.text).join('; ')}`,
107
+ raw: text,
108
+ parsed
109
+ };
110
+ }
111
+ }
112
+
113
+ return { success: true, data: parsed, provider, model };
114
+ } catch (error) {
115
+ return { success: false, error: error.message };
116
+ }
117
+ }
118
+ };
@@ -0,0 +1,128 @@
1
+ const { getConfig } = require('../utils/config');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ module.exports = {
6
+ name: 'media_understanding',
7
+ description: 'Analyze images and media using AI vision models (OpenAI, Anthropic, Gemini, Groq)',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ imagePath: { type: 'string', description: 'Local image file path' },
12
+ imageUrl: { type: 'string', description: 'Remote image URL (if no local file)' },
13
+ prompt: { type: 'string', description: 'Analysis prompt (default: "Describe this image in detail")' },
14
+ provider: { type: 'string', description: 'Vision provider: openai, anthropic, groq (default: openai)', enum: ['openai', 'anthropic', 'groq'] },
15
+ model: { type: 'string', description: 'Model override' }
16
+ }
17
+ },
18
+
19
+ async execute(params) {
20
+ try {
21
+ const config = getConfig();
22
+ const prompt = params.prompt || 'Describe this image in detail';
23
+ const provider = params.provider || config.visionProvider || 'openai';
24
+
25
+ if (!params.imagePath && !params.imageUrl) {
26
+ return { success: false, error: 'imagePath veya imageUrl gerekli' };
27
+ }
28
+
29
+ let imageBase64 = '';
30
+ let mediaType = 'image/jpeg';
31
+
32
+ if (params.imagePath) {
33
+ const resolvedPath = path.resolve(params.imagePath.replace(/^~/, require('os').homedir()));
34
+ if (!fs.existsSync(resolvedPath)) {
35
+ return { success: false, error: `Dosya bulunamadı: ${resolvedPath}` };
36
+ }
37
+ const ext = path.extname(resolvedPath).toLowerCase();
38
+ const mediaMap = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp' };
39
+ mediaType = mediaMap[ext] || 'image/jpeg';
40
+ imageBase64 = fs.readFileSync(resolvedPath).toString('base64');
41
+
42
+ // Check file size
43
+ const stats = fs.statSync(resolvedPath);
44
+ if (stats.size > 20 * 1024 * 1024) {
45
+ return { success: false, error: 'Dosya çok büyük (max 20MB)' };
46
+ }
47
+ }
48
+
49
+ const dataUrl = params.imageUrl || `data:${mediaType};base64,${imageBase64}`;
50
+
51
+ if (provider === 'openai') {
52
+ const apiKey = params.apiKey || config.openaiApiKey || process.env.OPENAI_API_KEY;
53
+ if (!apiKey) return { success: false, error: 'OpenAI API key gerekli' };
54
+
55
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
56
+ method: 'POST',
57
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
58
+ body: JSON.stringify({
59
+ model: params.model || 'gpt-4o',
60
+ messages: [{
61
+ role: 'user',
62
+ content: [
63
+ { type: 'text', text: prompt },
64
+ { type: 'image_url', image_url: { url: dataUrl, detail: 'high' } }
65
+ ]
66
+ }],
67
+ max_tokens: 1000
68
+ })
69
+ });
70
+
71
+ if (!response.ok) throw new Error(`OpenAI error ${response.status}`);
72
+ const data = await response.json();
73
+ return { success: true, provider: 'openai', analysis: data.choices?.[0]?.message?.content || '' };
74
+ }
75
+
76
+ if (provider === 'anthropic') {
77
+ const apiKey = params.apiKey || config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
78
+ if (!apiKey) return { success: false, error: 'Anthropic API key gerekli' };
79
+
80
+ const content = [{ type: 'text', text: prompt }];
81
+ if (params.imagePath) {
82
+ content.push({ type: 'image', source: { type: 'base64', media_type: mediaType, data: imageBase64 } });
83
+ } else {
84
+ content.push({ type: 'image', source: { type: 'url', url: params.imageUrl } });
85
+ }
86
+
87
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
90
+ body: JSON.stringify({ model: params.model || 'claude-3-5-sonnet-20241022', max_tokens: 1000, messages: [{ role: 'user', content }] })
91
+ });
92
+
93
+ if (!response.ok) throw new Error(`Anthropic error ${response.status}`);
94
+ const data = await response.json();
95
+ return { success: true, provider: 'anthropic', analysis: data.content?.[0]?.text || '' };
96
+ }
97
+
98
+ if (provider === 'groq') {
99
+ const apiKey = params.apiKey || config.providerApiKey || process.env.GROQ_API_KEY;
100
+ if (!apiKey) return { success: false, error: 'Groq API key gerekli' };
101
+
102
+ const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
103
+ method: 'POST',
104
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
105
+ body: JSON.stringify({
106
+ model: params.model || 'llama-3.2-90b-vision-preview',
107
+ messages: [{
108
+ role: 'user',
109
+ content: [
110
+ { type: 'text', text: prompt },
111
+ { type: 'image_url', image_url: { url: params.imagePath ? `data:${mediaType};base64,${imageBase64}` : params.imageUrl } }
112
+ ]
113
+ }],
114
+ max_tokens: 1000
115
+ })
116
+ });
117
+
118
+ if (!response.ok) throw new Error(`Groq error ${response.status}`);
119
+ const data = await response.json();
120
+ return { success: true, provider: 'groq', analysis: data.choices?.[0]?.message?.content || '' };
121
+ }
122
+
123
+ return { success: false, error: `Bilinmeyen provider: ${provider}` };
124
+ } catch (error) {
125
+ return { success: false, error: error.message };
126
+ }
127
+ }
128
+ };
@@ -0,0 +1,113 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ const PROVIDERS = {
4
+ suno: {
5
+ name: 'Suno AI (via API)',
6
+ async generate({ prompt, apiKey, model, duration, style }) {
7
+ const response = await fetch('https://api.sunoa.ai/v1/music/generate', {
8
+ method: 'POST',
9
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
10
+ body: JSON.stringify({
11
+ prompt,
12
+ model: model || 'chirp-v3',
13
+ duration: duration || 30,
14
+ style: style || '',
15
+ make_instrumental: false
16
+ })
17
+ });
18
+ if (!response.ok) throw new Error(`Suno error ${response.status}: ${await response.text()}`);
19
+ return (await response.json()).data?.clips || [];
20
+ }
21
+ },
22
+ udio: {
23
+ name: 'Udio AI (via API)',
24
+ async generate({ prompt, apiKey, model, duration }) {
25
+ const response = await fetch('https://api.udio.ai/v1/music/generate', {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
28
+ body: JSON.stringify({
29
+ prompt,
30
+ model: model || 'udio-v1',
31
+ duration: duration || 30
32
+ })
33
+ });
34
+ if (!response.ok) throw new Error(`Udio error ${response.status}: ${await response.text()}`);
35
+ const data = await response.json();
36
+ return data.results || data.songs || [];
37
+ }
38
+ },
39
+ elevenlabs: {
40
+ name: 'ElevenLabs Sound Effects',
41
+ async generate({ prompt, apiKey, duration }) {
42
+ const response = await fetch('https://api.elevenlabs.io/v1/sound-generation', {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json', 'xi-api-key': apiKey },
45
+ body: JSON.stringify({
46
+ text: prompt,
47
+ duration_seconds: duration || 10
48
+ })
49
+ });
50
+ if (!response.ok) throw new Error(`ElevenLabs error ${response.status}: ${await response.text()}`);
51
+ const buffer = await response.arrayBuffer();
52
+ return [{ url: URL.createObjectURL(new Blob([buffer], { type: 'audio/mpeg' })), provider: 'elevenlabs' }];
53
+ }
54
+ }
55
+ };
56
+
57
+ module.exports = {
58
+ name: 'music_generation',
59
+ description: 'Generate music, songs, and sound effects using AI. Supports Suno, Udio, and ElevenLabs.',
60
+ inputSchema: {
61
+ type: 'object',
62
+ properties: {
63
+ prompt: { type: 'string', description: 'Description of the music/song to generate' },
64
+ provider: { type: 'string', description: 'Music provider: suno, udio, elevenlabs (default: suno)', enum: ['suno', 'udio', 'elevenlabs'] },
65
+ model: { type: 'string', description: 'Model override' },
66
+ duration: { type: 'number', description: 'Duration in seconds (default: 30)' },
67
+ style: { type: 'string', description: 'Style/genre hint (e.g. "pop", "jazz", "electronic")' }
68
+ },
69
+ required: ['prompt']
70
+ },
71
+
72
+ async execute(params) {
73
+ try {
74
+ const config = getConfig();
75
+ const provider = params.provider || config.musicProvider || 'suno';
76
+
77
+ const providerConfig = PROVIDERS[provider];
78
+ if (!providerConfig) {
79
+ return { success: false, error: `Unsupported provider: ${provider}. Available: ${Object.keys(PROVIDERS).join(', ')}` };
80
+ }
81
+
82
+ let apiKey;
83
+ if (provider === 'suno') apiKey = params.apiKey || config.sunoApiKey || process.env.SUNO_API_KEY;
84
+ else if (provider === 'udio') apiKey = params.apiKey || config.udioApiKey || process.env.UDIO_API_KEY;
85
+ else if (provider === 'elevenlabs') apiKey = params.apiKey || config.elevenlabsApiKey || process.env.ELEVENLABS_API_KEY;
86
+
87
+ if (!apiKey) {
88
+ return {
89
+ success: false,
90
+ error: `${providerConfig.name} API key required.\nSet: natureco config set ${provider}ApiKey <key>`
91
+ };
92
+ }
93
+
94
+ const music = await providerConfig.generate({
95
+ prompt: params.prompt,
96
+ apiKey,
97
+ model: params.model,
98
+ duration: params.duration,
99
+ style: params.style
100
+ });
101
+
102
+ return {
103
+ success: true,
104
+ prompt: params.prompt,
105
+ provider,
106
+ music: Array.isArray(music) ? music : [music],
107
+ count: Array.isArray(music) ? music.length : 1
108
+ };
109
+ } catch (error) {
110
+ return { success: false, error: error.message };
111
+ }
112
+ }
113
+ };