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.
- package/README.md +94 -11
- package/bin/natureco.js +470 -10
- package/package.json +10 -6
- package/src/commands/admin-rpc.js +219 -0
- package/src/commands/agent.js +89 -0
- package/src/commands/approvals.js +53 -0
- package/src/commands/backup.js +124 -0
- package/src/commands/bonjour.js +167 -0
- package/src/commands/capability.js +64 -0
- package/src/commands/channels.js +94 -4
- package/src/commands/chat.js +11 -25
- package/src/commands/clickclack.js +130 -0
- package/src/commands/commitments.js +32 -0
- package/src/commands/completion.js +76 -0
- package/src/commands/config.js +111 -68
- package/src/commands/configure.js +93 -0
- package/src/commands/crestodian.js +92 -0
- package/src/commands/daemon.js +60 -0
- package/src/commands/device-pair.js +248 -0
- package/src/commands/devices.js +110 -0
- package/src/commands/directory.js +47 -0
- package/src/commands/dns.js +58 -0
- package/src/commands/docs.js +43 -0
- package/src/commands/doctor.js +121 -16
- package/src/commands/exec-policy.js +71 -0
- package/src/commands/gateway-server.js +1175 -30
- package/src/commands/gateway.js +11 -20
- package/src/commands/health.js +18 -0
- package/src/commands/help.js +6 -0
- package/src/commands/imessage.js +169 -0
- package/src/commands/infer.js +73 -0
- package/src/commands/irc.js +119 -0
- package/src/commands/mattermost.js +164 -0
- package/src/commands/memory-cmd.js +134 -1
- package/src/commands/message.js +30 -4
- package/src/commands/migrate.js +213 -2
- package/src/commands/models.js +584 -216
- package/src/commands/node.js +98 -0
- package/src/commands/nodes.js +106 -0
- package/src/commands/oc-path.js +200 -0
- package/src/commands/onboard.js +70 -0
- package/src/commands/open-prose.js +67 -0
- package/src/commands/plugins.js +415 -172
- package/src/commands/policy.js +176 -0
- package/src/commands/proxy.js +155 -0
- package/src/commands/qr.js +28 -0
- package/src/commands/sandbox.js +125 -0
- package/src/commands/secrets.js +118 -0
- package/src/commands/security.js +149 -1
- package/src/commands/setup.js +114 -10
- package/src/commands/signal.js +495 -0
- package/src/commands/skills.js +20 -29
- package/src/commands/sms.js +168 -0
- package/src/commands/system.js +53 -0
- package/src/commands/tasks.js +328 -79
- package/src/commands/terminal.js +21 -0
- package/src/commands/thread-ownership.js +157 -0
- package/src/commands/transcripts.js +72 -0
- package/src/commands/voice.js +82 -0
- package/src/commands/vydra.js +98 -0
- package/src/commands/webhooks.js +79 -0
- package/src/commands/whatsapp.js +7 -21
- package/src/commands/workboard.js +207 -0
- package/src/tools/audio_understanding.js +154 -0
- package/src/tools/bash.js +63 -29
- 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/api.js +3 -20
- package/src/utils/approvals.js +297 -0
- package/src/utils/background.js +223 -66
- package/src/utils/baileys.js +21 -0
- package/src/utils/config.js +141 -10
- package/src/utils/errors.js +148 -0
- package/src/utils/inquirer-wrapper.js +1 -2
- package/src/utils/memory.js +200 -0
- package/src/utils/path-utils.js +13 -13
- package/src/utils/plugin-registry.js +238 -0
- package/src/utils/secrets.js +177 -0
- package/src/utils/skills.js +10 -23
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'document_extract',
|
|
7
|
+
description: 'Extract text content from local documents (PDF, DOCX, TXT, CSV, JSON, MD, RTF, ODT)',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
path: { type: 'string', description: 'File path to extract text from' },
|
|
12
|
+
maxChars: { type: 'number', description: 'Maximum characters to return (default: 50000)', default: 50000 }
|
|
13
|
+
},
|
|
14
|
+
required: ['path']
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
async execute(params) {
|
|
18
|
+
try {
|
|
19
|
+
const filePath = path.resolve(params.path.replace(/^~/, require('os').homedir()));
|
|
20
|
+
const maxChars = params.maxChars || 50000;
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(filePath)) {
|
|
23
|
+
return { success: false, error: `Dosya bulunamadı: ${filePath}` };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const stats = fs.statSync(filePath);
|
|
27
|
+
if (stats.size > 50 * 1024 * 1024) {
|
|
28
|
+
return { success: false, error: 'Dosya çok büyük (max 50MB)' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
32
|
+
let text = '';
|
|
33
|
+
|
|
34
|
+
if (ext === '.txt' || ext === '.csv' || ext === '.json' || ext === '.md' || ext === '.xml' || ext === '.yaml' || ext === '.yml') {
|
|
35
|
+
text = fs.readFileSync(filePath, 'utf-8');
|
|
36
|
+
} else if (ext === '.pdf') {
|
|
37
|
+
try {
|
|
38
|
+
text = execSync(`pdftotext "${filePath}" -`, { encoding: 'utf-8', timeout: 30000, stdio: ['pipe', 'pipe', 'ignore'] });
|
|
39
|
+
} catch {
|
|
40
|
+
// Fallback: read raw PDF extract
|
|
41
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
42
|
+
const matches = raw.match(/\((.*?)\)/g);
|
|
43
|
+
text = (matches || []).map(m => m.slice(1, -1)).filter(s => s.length > 3).join(' ');
|
|
44
|
+
}
|
|
45
|
+
} else if (ext === '.docx') {
|
|
46
|
+
try {
|
|
47
|
+
const zip = require('../utils/zip-reader');
|
|
48
|
+
text = await zip.readText(filePath);
|
|
49
|
+
} catch {
|
|
50
|
+
return { success: false, error: 'DOCX okuma hatası. npm install adm-zip gerekli olabilir.' };
|
|
51
|
+
}
|
|
52
|
+
} else if (ext === '.rtf' || ext === '.odt') {
|
|
53
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
54
|
+
text = raw.replace(/<[^>]+>/g, ' ').replace(/\\[a-z]+|\{[^}]*\}|[{}]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
55
|
+
} else {
|
|
56
|
+
// Fallback: read as text
|
|
57
|
+
text = fs.readFileSync(filePath, 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cleaned = text
|
|
61
|
+
.replace(/\r\n/g, '\n')
|
|
62
|
+
.replace(/\x00/g, '')
|
|
63
|
+
.trim();
|
|
64
|
+
|
|
65
|
+
const truncated = cleaned.length > maxChars
|
|
66
|
+
? cleaned.slice(0, maxChars) + '...'
|
|
67
|
+
: cleaned;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
path: filePath,
|
|
72
|
+
extension: ext,
|
|
73
|
+
fileSize: stats.size,
|
|
74
|
+
content: truncated,
|
|
75
|
+
wordCount: cleaned.split(/\s+/).length,
|
|
76
|
+
totalChars: cleaned.length,
|
|
77
|
+
truncated: cleaned.length > maxChars,
|
|
78
|
+
source: 'document_extract'
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return { success: false, error: error.message };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
@@ -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
|
+
};
|