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,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
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const { getConfig } = require('../utils/config');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
name: 'parallel_search',
|
|
5
|
+
description: 'Free web search using Parallel (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://api.duckduckgo.com/?q=${query}&format=json&no_html=1&skip_disambig=1`,
|
|
22
|
+
{ headers: { 'User-Agent': 'NatureCo-CLI/2.0' } }
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
return { success: false, error: `Parallel search error: ${response.status}` };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
const results = [];
|
|
31
|
+
|
|
32
|
+
if (data.AbstractText) {
|
|
33
|
+
results.push({
|
|
34
|
+
title: data.Heading || 'Summary',
|
|
35
|
+
snippet: data.AbstractText,
|
|
36
|
+
url: data.AbstractURL || ''
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (data.Results && Array.isArray(data.Results)) {
|
|
41
|
+
for (const r of data.Results) {
|
|
42
|
+
if (results.length >= maxResults) break;
|
|
43
|
+
if (r.Text && r.FirstURL) {
|
|
44
|
+
results.push({ title: r.Text.split(' - ')[0] || r.Text, snippet: r.Text, url: r.FirstURL });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (data.RelatedTopics && Array.isArray(data.RelatedTopics)) {
|
|
50
|
+
for (const r of data.RelatedTopics) {
|
|
51
|
+
if (results.length >= maxResults) break;
|
|
52
|
+
if (r.Text && r.FirstURL) {
|
|
53
|
+
results.push({ title: r.Text.split(' - ')[0] || r.Text, snippet: r.Text, url: r.FirstURL });
|
|
54
|
+
}
|
|
55
|
+
if (r.Topics && Array.isArray(r.Topics)) {
|
|
56
|
+
for (const t of r.Topics) {
|
|
57
|
+
if (results.length >= maxResults) break;
|
|
58
|
+
if (t.Text && t.FirstURL) {
|
|
59
|
+
results.push({ title: t.Text.split(' - ')[0] || t.Text, snippet: t.Text, url: t.FirstURL });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
success: true,
|
|
68
|
+
query: params.query,
|
|
69
|
+
results,
|
|
70
|
+
count: results.length,
|
|
71
|
+
source: 'parallel'
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return { success: false, error: error.message };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: 'phone_control',
|
|
3
|
+
description: 'Send notifications and control mobile devices via push services (Pushover, ntfy)',
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
action: { type: 'string', description: 'Action: notify (send push notification)', enum: ['notify'] },
|
|
8
|
+
title: { type: 'string', description: 'Notification title' },
|
|
9
|
+
message: { type: 'string', description: 'Notification message body' },
|
|
10
|
+
priority: { type: 'number', description: 'Priority: -2 (lowest) to 2 (emergency)', default: 0 },
|
|
11
|
+
url: { type: 'string', description: 'Optional URL to open on click' },
|
|
12
|
+
service: { type: 'string', description: 'Push service: pushover, ntfy (default: pushover)', enum: ['pushover', 'ntfy'] }
|
|
13
|
+
},
|
|
14
|
+
required: ['action', 'message']
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
async execute(params) {
|
|
18
|
+
try {
|
|
19
|
+
const { getConfig } = require('../utils/config');
|
|
20
|
+
const config = getConfig();
|
|
21
|
+
|
|
22
|
+
if (params.action === 'notify') {
|
|
23
|
+
const service = params.service || 'pushover';
|
|
24
|
+
|
|
25
|
+
if (service === 'pushover') {
|
|
26
|
+
const token = params.token || config.pushoverToken || process.env.PUSHOVER_TOKEN;
|
|
27
|
+
const user = params.user || config.pushoverUser || process.env.PUSHOVER_USER;
|
|
28
|
+
|
|
29
|
+
if (!token || !user) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: 'Pushover token ve user gerekli.\nKur: natureco config set pushoverToken <token>\nnatureco config set pushoverUser <user>'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const response = await fetch('https://api.pushover.net/1/messages.json', {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
token,
|
|
41
|
+
user,
|
|
42
|
+
title: params.title || 'NatureCo',
|
|
43
|
+
message: params.message,
|
|
44
|
+
priority: params.priority || 0,
|
|
45
|
+
url: params.url
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) throw new Error(`Pushover error ${response.status}`);
|
|
50
|
+
return { success: true, service: 'pushover', status: 'notification sent' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (service === 'ntfy') {
|
|
54
|
+
const server = params.server || config.ntfyServer || process.env.NTFY_SERVER || 'https://ntfy.sh';
|
|
55
|
+
const topic = params.topic || config.ntfyTopic || process.env.NTFY_TOPIC;
|
|
56
|
+
|
|
57
|
+
if (!topic) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: 'ntfy topic gerekli.\nKur: natureco config set ntfyTopic <topic>'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const response = await fetch(`${server}/${topic}`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
67
|
+
body: `[${params.title || 'NatureCo'}] ${params.message}`
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!response.ok) throw new Error(`ntfy error ${response.status}`);
|
|
71
|
+
return { success: true, service: 'ntfy', status: 'notification sent' };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { success: false, error: `Unknown action: ${params.action}` };
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return { success: false, error: error.message };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const { getConfig, saveConfig } = require('../utils/config');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
|
|
4
|
+
function checkAdb() {
|
|
5
|
+
try {
|
|
6
|
+
execSync('adb version', { stdio: 'pipe', encoding: 'utf8' });
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function adbCommand(args) {
|
|
14
|
+
return execSync(`adb ${args}`, { stdio: 'pipe', encoding: 'utf8', maxBuffer: 1024 * 1024 }).trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
name: 'phone_control_enhanced',
|
|
19
|
+
description: 'Full mobile device control — notifications, ADB commands, camera, screen recording, SMS, contacts. Supports Pushover, ntfy, and ADB.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
action: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Action',
|
|
26
|
+
enum: ['notify', 'arm', 'disarm', 'status', 'devices', 'camera.snap', 'camera.clip', 'screen.record', 'sms.send', 'sms.list', 'contacts.list', 'adb']
|
|
27
|
+
},
|
|
28
|
+
title: { type: 'string', description: 'Notification title' },
|
|
29
|
+
message: { type: 'string', description: 'Notification message or SMS text' },
|
|
30
|
+
service: { type: 'string', description: 'Push service: pushover, ntfy (default: pushover)', enum: ['pushover', 'ntfy'] },
|
|
31
|
+
priority: { type: 'number', description: 'Priority: -2 to 2' },
|
|
32
|
+
phoneNumber: { type: 'string', description: 'Phone number for SMS' },
|
|
33
|
+
duration: { type: 'number', description: 'Duration in seconds for recording' },
|
|
34
|
+
adbCommand: { type: 'string', description: 'Raw ADB command (for adb action)' },
|
|
35
|
+
group: { type: 'string', description: 'Group to arm: camera, screen, writes, all' }
|
|
36
|
+
},
|
|
37
|
+
required: ['action']
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async execute(params) {
|
|
41
|
+
try {
|
|
42
|
+
const config = getConfig();
|
|
43
|
+
|
|
44
|
+
if (params.action === 'notify') {
|
|
45
|
+
const service = params.service || 'pushover';
|
|
46
|
+
if (service === 'pushover') {
|
|
47
|
+
const token = config.pushoverToken || process.env.PUSHOVER_TOKEN;
|
|
48
|
+
const user = config.pushoverUser || process.env.PUSHOVER_USER;
|
|
49
|
+
if (!token || !user) return { success: false, error: 'Pushover token/user gerekli' };
|
|
50
|
+
const r = await fetch('https://api.pushover.net/1/messages.json', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({ token, user, title: params.title || 'NatureCo', message: params.message, priority: params.priority || 0 })
|
|
54
|
+
});
|
|
55
|
+
return { success: r.ok, service: 'pushover' };
|
|
56
|
+
}
|
|
57
|
+
if (service === 'ntfy') {
|
|
58
|
+
const topic = config.ntfyTopic || process.env.NTFY_TOPIC;
|
|
59
|
+
if (!topic) return { success: false, error: 'ntfy topic gerekli' };
|
|
60
|
+
const server = config.ntfyServer || process.env.NTFY_SERVER || 'https://ntfy.sh';
|
|
61
|
+
const r = await fetch(`${server}/${topic}`, {
|
|
62
|
+
method: 'POST', headers: { 'Content-Type': 'text/plain' },
|
|
63
|
+
body: `[${params.title || 'NatureCo'}] ${params.message}`
|
|
64
|
+
});
|
|
65
|
+
return { success: r.ok, service: 'ntfy' };
|
|
66
|
+
}
|
|
67
|
+
return { success: false, error: `Unknown service: ${service}` };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (params.action === 'arm') {
|
|
71
|
+
const group = params.group || 'all';
|
|
72
|
+
if (!config.phoneControl) config.phoneControl = {};
|
|
73
|
+
if (!config.phoneControl.armed) config.phoneControl.armed = {};
|
|
74
|
+
config.phoneControl.armed[group] = { armed: true, at: new Date().toISOString() };
|
|
75
|
+
saveConfig(config);
|
|
76
|
+
return { success: true, action: 'arm', group, status: 'armed' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (params.action === 'disarm') {
|
|
80
|
+
const group = params.group || 'all';
|
|
81
|
+
if (!config.phoneControl) config.phoneControl = {};
|
|
82
|
+
if (!config.phoneControl.armed) config.phoneControl.armed = {};
|
|
83
|
+
if (group === 'all') config.phoneControl.armed = {};
|
|
84
|
+
else delete config.phoneControl.armed[group];
|
|
85
|
+
saveConfig(config);
|
|
86
|
+
return { success: true, action: 'disarm', group, status: 'disarmed' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (params.action === 'status') {
|
|
90
|
+
const pc = config.phoneControl || {};
|
|
91
|
+
const armed = pc.armed || {};
|
|
92
|
+
const hasAdb = checkAdb();
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
action: 'status',
|
|
96
|
+
adbAvailable: hasAdb,
|
|
97
|
+
armed: Object.keys(armed).length > 0 ? armed : false,
|
|
98
|
+
pushoverConfigured: !!(config.pushoverToken || process.env.PUSHOVER_TOKEN),
|
|
99
|
+
ntfyConfigured: !!(config.ntfyTopic || process.env.NTFY_TOPIC)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (params.action === 'devices') {
|
|
104
|
+
if (!checkAdb()) return { success: false, error: 'ADB bulunamadı. Android SDK kurulumu gerekli.' };
|
|
105
|
+
const output = adbCommand('devices -l');
|
|
106
|
+
const lines = output.split('\n').filter(l => l.trim() && !l.startsWith('List'));
|
|
107
|
+
const devices = lines.map(l => {
|
|
108
|
+
const parts = l.trim().split(/\s+/);
|
|
109
|
+
return { id: parts[0], state: parts[1], info: parts.slice(2).join(' ') };
|
|
110
|
+
});
|
|
111
|
+
return { success: true, action: 'devices', devices, count: devices.length };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (params.action.startsWith('camera.')) {
|
|
115
|
+
if (!checkAdb()) return { success: false, error: 'ADB gerekli' };
|
|
116
|
+
if (params.action === 'camera.snap') {
|
|
117
|
+
const output = adbCommand('shell am start -a android.media.action.IMAGE_CAPTURE');
|
|
118
|
+
adbCommand('shell sleep 2');
|
|
119
|
+
adbCommand('shell input keyevent 27');
|
|
120
|
+
return { success: true, action: 'camera.snap', message: 'Camera capture triggered' };
|
|
121
|
+
}
|
|
122
|
+
if (params.action === 'camera.clip') {
|
|
123
|
+
const dur = params.duration || 10;
|
|
124
|
+
adbCommand(`shell am start -a android.media.action.VIDEO_CAPTURE --ei android.intent.extra.durationLimit ${dur}`);
|
|
125
|
+
adbCommand(`shell sleep ${dur + 2}`);
|
|
126
|
+
return { success: true, action: 'camera.clip', duration: dur };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (params.action === 'screen.record') {
|
|
131
|
+
if (!checkAdb()) return { success: false, error: 'ADB gerekli' };
|
|
132
|
+
const dur = params.duration || 30;
|
|
133
|
+
const outputFile = `/sdcard/screen_${Date.now()}.mp4`;
|
|
134
|
+
adbCommand(`shell screenrecord --time-limit ${dur} ${outputFile}`);
|
|
135
|
+
return { success: true, action: 'screen.record', outputFile, duration: dur };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (params.action === 'sms.send') {
|
|
139
|
+
if (!checkAdb()) {
|
|
140
|
+
if (!config.twilioSid) return { success: false, error: 'SMS için ADB veya Twilio gerekli' };
|
|
141
|
+
const sid = config.twilioSid || process.env.TWILIO_SID;
|
|
142
|
+
const token = config.twilioToken || process.env.TWILIO_TOKEN;
|
|
143
|
+
const from = config.twilioFrom || process.env.TWILIO_FROM;
|
|
144
|
+
if (!params.phoneNumber || !params.message) return { success: false, error: 'phoneNumber ve message gerekli' };
|
|
145
|
+
const r = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${sid}/Messages.json`, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: { 'Authorization': `Basic ${Buffer.from(`${sid}:${token}`).toString('base64')}`, 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
148
|
+
body: new URLSearchParams({ To: params.phoneNumber, From: from, Body: params.message })
|
|
149
|
+
});
|
|
150
|
+
return { success: r.ok, action: 'sms.send', service: 'twilio' };
|
|
151
|
+
}
|
|
152
|
+
const output = adbCommand(`shell service call isms 7 i32 0 s16 "com.android.mms" s16 "${params.phoneNumber}" s16 "null" s16 "${params.message}" s16 "null" s16 "null"`);
|
|
153
|
+
return { success: true, action: 'sms.send', output: output.substring(0, 500) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (params.action === 'sms.list') {
|
|
157
|
+
if (!checkAdb()) return { success: false, error: 'ADB gerekli' };
|
|
158
|
+
const output = adbCommand('shell content query --uri content://sms/inbox --projection address,body,date --limit 20');
|
|
159
|
+
return { success: true, action: 'sms.list', messages: output.split('\n').filter(Boolean).map(l => l.trim()) };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (params.action === 'contacts.list') {
|
|
163
|
+
if (!checkAdb()) return { success: false, error: 'ADB gerekli' };
|
|
164
|
+
const output = adbCommand('shell content query --uri content://contacts/phones --projection display_name,number --limit 50');
|
|
165
|
+
const contacts = output.split('\n').filter(Boolean).map(l => {
|
|
166
|
+
const m = l.match(/display_name=(.+?),.*?number=(.+?)(?:,|$)/);
|
|
167
|
+
return m ? { name: m[1], number: m[2] } : { raw: l.trim() };
|
|
168
|
+
});
|
|
169
|
+
return { success: true, action: 'contacts.list', contacts, count: contacts.length };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (params.action === 'adb') {
|
|
173
|
+
if (!checkAdb()) return { success: false, error: 'ADB gerekli' };
|
|
174
|
+
if (!params.adbCommand) return { success: false, error: 'adbCommand gerekli' };
|
|
175
|
+
const output = adbCommand(params.adbCommand);
|
|
176
|
+
return { success: true, action: 'adb', command: params.adbCommand, output: output.substring(0, 10000) };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { success: false, error: `Unknown action: ${params.action}` };
|
|
180
|
+
} catch (error) {
|
|
181
|
+
return { success: false, error: error.message };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { getConfig } = require('../utils/config');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
name: 'searxng_search',
|
|
5
|
+
description: 'Search the web using a SearXNG instance (self-hosted privacy search)',
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
query: { type: 'string', description: 'Search query' },
|
|
10
|
+
instanceUrl: { type: 'string', description: 'SearXNG instance URL (default: config.searxngInstanceUrl or https://search.sapti.me)' },
|
|
11
|
+
maxResults: { type: 'number', description: 'Maximum results (default: 5)', default: 5 },
|
|
12
|
+
categories: { type: 'string', description: 'Search categories: general, news, images, video, files (default: general)' }
|
|
13
|
+
},
|
|
14
|
+
required: ['query']
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
async execute(params) {
|
|
18
|
+
try {
|
|
19
|
+
const config = getConfig();
|
|
20
|
+
const instanceUrl = (params.instanceUrl || config.searxngInstanceUrl || 'https://search.sapti.me').replace(/\/+$/, '');
|
|
21
|
+
const maxResults = params.maxResults || 5;
|
|
22
|
+
|
|
23
|
+
const searchParams = new URLSearchParams({
|
|
24
|
+
q: params.query,
|
|
25
|
+
format: 'json',
|
|
26
|
+
language: 'en',
|
|
27
|
+
categories: params.categories || 'general',
|
|
28
|
+
pageno: 1
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const response = await fetch(`${instanceUrl}/search?${searchParams}`, {
|
|
32
|
+
headers: { 'User-Agent': 'NatureCo-CLI/2.0', 'Accept': 'application/json' }
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
return { success: false, error: `SearXNG error: ${response.status}` };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
|
|
41
|
+
const results = (data.results || []).slice(0, maxResults).map(r => ({
|
|
42
|
+
title: r.title,
|
|
43
|
+
snippet: r.content,
|
|
44
|
+
url: r.url,
|
|
45
|
+
engine: r.engine,
|
|
46
|
+
category: r.category
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
query: params.query,
|
|
52
|
+
instance: instanceUrl,
|
|
53
|
+
results,
|
|
54
|
+
count: results.length,
|
|
55
|
+
source: 'searxng'
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return { success: false, error: error.message };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const { getConfig } = require('../utils/config');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const PROVIDERS = {
|
|
6
|
+
deepgram: {
|
|
7
|
+
name: 'Deepgram',
|
|
8
|
+
async transcribe({ audioPath, audioUrl, apiKey, language, model }) {
|
|
9
|
+
const url = audioPath
|
|
10
|
+
? 'https://api.deepgram.com/v1/listen'
|
|
11
|
+
: `https://api.deepgram.com/v1/listen?url=${encodeURIComponent(audioUrl)}`;
|
|
12
|
+
|
|
13
|
+
const options = {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Authorization': `Token ${apiKey}` },
|
|
16
|
+
params: { model: model || 'nova-2', language: language || 'en', smart_format: 'true' }
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
if (audioPath) {
|
|
20
|
+
const audioFile = fs.readFileSync(audioPath);
|
|
21
|
+
options.body = audioFile;
|
|
22
|
+
options.headers['Content-Type'] = 'audio/wav';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const queryString = Object.entries(options.params).map(([k, v]) => `${k}=${v}`).join('&');
|
|
26
|
+
const response = await fetch(`${url}${audioPath ? '' : '&'}${queryString}`, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: options.headers,
|
|
29
|
+
body: options.body
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!response.ok) throw new Error(`Deepgram error ${response.status}`);
|
|
33
|
+
const data = await response.json();
|
|
34
|
+
return data.results?.channels?.[0]?.alternatives?.[0]?.transcript || '';
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
azure: {
|
|
38
|
+
name: 'Azure Speech',
|
|
39
|
+
async transcribe({ audioPath, apiKey, region, language }) {
|
|
40
|
+
const response = await fetch(
|
|
41
|
+
`https://${region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=${language || 'en-US'}`,
|
|
42
|
+
{
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'Ocp-Apim-Subscription-Key': apiKey,
|
|
46
|
+
'Content-Type': 'audio/wav; codecs=audio/pcm; samplerate=16000'
|
|
47
|
+
},
|
|
48
|
+
body: fs.readFileSync(audioPath)
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
if (!response.ok) throw new Error(`Azure error ${response.status}`);
|
|
52
|
+
const data = await response.json();
|
|
53
|
+
return data.DisplayText || '';
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
whisper: {
|
|
57
|
+
name: 'OpenAI Whisper',
|
|
58
|
+
async transcribe({ audioPath, apiKey, language }) {
|
|
59
|
+
const formData = new (require('form-data'))();
|
|
60
|
+
formData.append('file', fs.createReadStream(audioPath));
|
|
61
|
+
formData.append('model', 'whisper-1');
|
|
62
|
+
if (language) formData.append('language', language);
|
|
63
|
+
|
|
64
|
+
const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
67
|
+
body: formData
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!response.ok) throw new Error(`Whisper error ${response.status}`);
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
return data.text || '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
name: 'speech_to_text',
|
|
79
|
+
description: 'Transcribe audio to text using Deepgram, Azure Speech, or OpenAI Whisper',
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
audioPath: { type: 'string', description: 'Local audio file path' },
|
|
84
|
+
audioUrl: { type: 'string', description: 'Remote audio URL (if no local file)' },
|
|
85
|
+
provider: { type: 'string', description: 'STT provider: deepgram, azure, whisper (default: whisper)', enum: ['deepgram', 'azure', 'whisper'] },
|
|
86
|
+
language: { type: 'string', description: 'Language code (e.g. en, tr, fr)' }
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async execute(params) {
|
|
91
|
+
try {
|
|
92
|
+
const config = getConfig();
|
|
93
|
+
const provider = params.provider || config.sttProvider || 'whisper';
|
|
94
|
+
|
|
95
|
+
const providerConfig = PROVIDERS[provider];
|
|
96
|
+
if (!providerConfig) {
|
|
97
|
+
return { success: false, error: `Desteklenmeyen provider: ${provider}` };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!params.audioPath && !params.audioUrl) {
|
|
101
|
+
return { success: false, error: 'audioPath veya audioUrl gerekli' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (params.audioPath && !fs.existsSync(path.resolve(params.audioPath))) {
|
|
105
|
+
return { success: false, error: `Dosya bulunamadı: ${params.audioPath}` };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let apiKey;
|
|
109
|
+
if (provider === 'deepgram') apiKey = params.apiKey || config.deepgramApiKey || process.env.DEEPGRAM_API_KEY;
|
|
110
|
+
else if (provider === 'azure') apiKey = params.apiKey || config.azureSpeechKey || process.env.AZURE_SPEECH_KEY;
|
|
111
|
+
else apiKey = params.apiKey || config.openaiApiKey || process.env.OPENAI_API_KEY;
|
|
112
|
+
|
|
113
|
+
if (!apiKey) {
|
|
114
|
+
return { success: false, error: `${providerConfig.name} API key gerekli.` };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const transcript = await providerConfig.transcribe({
|
|
118
|
+
audioPath: params.audioPath,
|
|
119
|
+
audioUrl: params.audioUrl,
|
|
120
|
+
apiKey,
|
|
121
|
+
region: config.azureRegion || process.env.AZURE_REGION || 'eastus',
|
|
122
|
+
language: params.language
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
provider,
|
|
128
|
+
transcript: transcript || '(boş)',
|
|
129
|
+
wordCount: transcript ? transcript.split(/\s+/).length : 0
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return { success: false, error: error.message };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|