natureco-cli 2.23.27 → 2.23.29
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/bin/natureco.js +68 -6
- package/package.json +10 -6
- package/src/commands/channels.js +94 -4
- package/src/commands/chat.js +11 -25
- package/src/commands/code.js +12 -11
- package/src/commands/config.js +111 -68
- package/src/commands/doctor.js +121 -16
- package/src/commands/gateway-server.js +35 -21
- package/src/commands/gateway.js +11 -20
- package/src/commands/help.js +6 -0
- package/src/commands/imessage.js +55 -0
- package/src/commands/irc.js +70 -0
- package/src/commands/mattermost.js +62 -0
- package/src/commands/message.js +24 -4
- package/src/commands/models.js +584 -216
- package/src/commands/plugins.js +415 -172
- package/src/commands/security.js +149 -1
- package/src/commands/setup.js +1 -3
- package/src/commands/signal.js +66 -0
- package/src/commands/skills.js +20 -29
- package/src/commands/sms.js +64 -0
- package/src/commands/tasks.js +328 -79
- package/src/commands/webhooks.js +79 -0
- package/src/commands/whatsapp.js +7 -21
- package/src/tools/bash.js +63 -29
- 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/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
package/src/commands/doctor.js
CHANGED
|
@@ -2,9 +2,12 @@ const chalk = require('chalk');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const net = require('net');
|
|
5
|
-
const { getConfig, saveConfig, CONFIG_FILE, CONFIG_DIR } = require('../utils/config');
|
|
5
|
+
const { getConfig, saveConfig, CONFIG_FILE, CONFIG_DIR, listBackups } = require('../utils/config');
|
|
6
6
|
const { getBots } = require('../utils/api');
|
|
7
7
|
const { getSkills } = require('../utils/skills');
|
|
8
|
+
const { NatureCoError, ApiError, ConfigError, handleError } = require('../utils/errors');
|
|
9
|
+
const { listSecretRefs, resolveSecretRef } = require('../utils/secrets');
|
|
10
|
+
const { loadApprovals, resolveEffectivePolicy } = require('../utils/approvals');
|
|
8
11
|
|
|
9
12
|
async function doctor(options) {
|
|
10
13
|
const shouldFix = options && (options.includes('--fix') || options.includes('-f'));
|
|
@@ -258,27 +261,40 @@ async function doctor(options) {
|
|
|
258
261
|
results.push({ status: 'warning', message: 'Tavily API key yok', fix: 'natureco config set tavilyApiKey tvly_xxx' });
|
|
259
262
|
}
|
|
260
263
|
|
|
261
|
-
// 15. Provider erişim kontrolü
|
|
264
|
+
// 15. Provider erişim kontrolü (gerçek API doğrulama)
|
|
262
265
|
total++;
|
|
263
266
|
criticalTotal++;
|
|
264
267
|
if (config && config.providerUrl) {
|
|
265
268
|
try {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
const response = await fetch(config.providerUrl, {
|
|
270
|
+
method: 'POST',
|
|
271
|
+
headers: {
|
|
272
|
+
'Content-Type': 'application/json',
|
|
273
|
+
...(config.providerApiKey ? { 'Authorization': `Bearer ${config.providerApiKey}` } : {}),
|
|
274
|
+
},
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
model: config.providerModel || 'llama-3.3-70b-versatile',
|
|
277
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
278
|
+
max_tokens: 1,
|
|
279
|
+
}),
|
|
280
|
+
signal: AbortSignal.timeout(15000),
|
|
272
281
|
});
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
282
|
+
|
|
283
|
+
if (response.ok) {
|
|
284
|
+
console.log(chalk.green('✅ Provider:'), chalk.white('API doğrulandı'));
|
|
285
|
+
passed++;
|
|
286
|
+
criticalPassed++;
|
|
287
|
+
results.push({ status: 'ok', message: 'Provider API doğrulandı' });
|
|
288
|
+
} else if (response.status === 401 || response.status === 403) {
|
|
289
|
+
console.log(chalk.red('❌ Provider:'), chalk.white('API key geçersiz'));
|
|
290
|
+
results.push({ status: 'error', message: 'API key reddedildi', fix: 'natureco config set providerApiKey <key>' });
|
|
291
|
+
} else {
|
|
292
|
+
console.log(chalk.yellow('⚠️ Provider:'), chalk.white(`HTTP ${response.status} — beklenmeyen yanıt`));
|
|
293
|
+
results.push({ status: 'warning', message: `Provider HTTP ${response.status} döndü` });
|
|
294
|
+
}
|
|
279
295
|
} catch (err) {
|
|
280
|
-
console.log(chalk.red('Bağlantı hatası: ' + err.message));
|
|
281
|
-
results.push({ status: 'error', message: 'Provider
|
|
296
|
+
console.log(chalk.red('❌ Provider:'), chalk.white('Bağlantı hatası: ' + err.message));
|
|
297
|
+
results.push({ status: 'error', message: 'Provider bağlantı hatası' });
|
|
282
298
|
}
|
|
283
299
|
} else {
|
|
284
300
|
console.log(chalk.gray('⚪ Provider Erişim:'), chalk.white('Provider URL ayarlanmamış'));
|
|
@@ -321,6 +337,95 @@ async function doctor(options) {
|
|
|
321
337
|
results.push({ status: 'info', message: 'Versiyon kontrol edilemedi' });
|
|
322
338
|
}
|
|
323
339
|
|
|
340
|
+
// 17. Exec approvals kontrolü
|
|
341
|
+
total++;
|
|
342
|
+
const approvals = loadApprovals();
|
|
343
|
+
const policy = resolveEffectivePolicy(approvals);
|
|
344
|
+
const allowlistCount = (approvals.allowlist || []).length;
|
|
345
|
+
if (policy !== 'deny' || allowlistCount > 0) {
|
|
346
|
+
passed++;
|
|
347
|
+
results.push({ status: 'ok', message: `Güvenlik: ${policy} (${allowlistCount} allowlist)` });
|
|
348
|
+
console.log(chalk.green('✅ Exec Güvenlik:'), chalk.white(`${policy} — ${allowlistCount} allowlist kuralı`));
|
|
349
|
+
} else {
|
|
350
|
+
results.push({ status: 'warning', message: 'Exec güvenlik deny modunda', fix: 'natureco security policy set allowlist' });
|
|
351
|
+
console.log(chalk.yellow('⚠️ Exec Güvenlik:'), chalk.white('deny modunda, komut çalıştırma engelli'));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 18. Exec approvals allowlist durumu (opsiyonel detay)
|
|
355
|
+
if (allowlistCount > 0) {
|
|
356
|
+
const expired = (approvals.allowlist || []).filter(e => e.expires && new Date(e.expires) < new Date()).length;
|
|
357
|
+
if (expired > 0) {
|
|
358
|
+
console.log(chalk.yellow(` ⚠ ${expired} allowlist kuralı süresi dolmuş`));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 19. Config yedekleri kontrolü
|
|
363
|
+
total++;
|
|
364
|
+
try {
|
|
365
|
+
const backupDir = path.join(CONFIG_DIR, 'backups');
|
|
366
|
+
if (fs.existsSync(backupDir)) {
|
|
367
|
+
const backups = fs.readdirSync(backupDir).filter(f => f.startsWith('config-') && f.endsWith('.json'));
|
|
368
|
+
if (backups.length > 0) {
|
|
369
|
+
passed++;
|
|
370
|
+
results.push({ status: 'ok', message: `${backups.length} config yedeği mevcut` });
|
|
371
|
+
console.log(chalk.green('✅ Config Yedek:'), chalk.white(`${backups.length} yedek`));
|
|
372
|
+
|
|
373
|
+
if (backups.length >= 10) {
|
|
374
|
+
console.log(chalk.yellow(' ⚠ Maksimum yedek sayısına ulaşıldı (10)'));
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
results.push({ status: 'info', message: 'Config yedeği yok' });
|
|
378
|
+
console.log(chalk.gray('⚪ Config Yedek:'), chalk.white('Yedek bulunamadı'));
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
results.push({ status: 'info', message: 'Config yedeği yok' });
|
|
382
|
+
console.log(chalk.gray('⚪ Config Yedek:'), chalk.white('Yedek klasörü yok'));
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
results.push({ status: 'info', message: 'Config yedekleri kontrol edilemedi' });
|
|
386
|
+
console.log(chalk.gray('⚪ Config Yedek:'), chalk.white('Kontrol edilemedi'));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 20. Secret referansları kontrolü
|
|
390
|
+
total++;
|
|
391
|
+
try {
|
|
392
|
+
const secretRefs = listSecretRefs(config || {});
|
|
393
|
+
if (secretRefs.length > 0) {
|
|
394
|
+
const unresolved = secretRefs.filter(ref => {
|
|
395
|
+
try {
|
|
396
|
+
return resolveSecretRef(ref) === null;
|
|
397
|
+
} catch {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (unresolved.length === 0) {
|
|
403
|
+
passed++;
|
|
404
|
+
results.push({ status: 'ok', message: `${secretRefs.length} secret referansı çözüldü` });
|
|
405
|
+
console.log(chalk.green('✅ Secrets:'), chalk.white(`${secretRefs.length} referans çözüldü`));
|
|
406
|
+
} else {
|
|
407
|
+
results.push({ status: 'warning', message: `${unresolved.length}/${secretRefs.length} secret çözülemedi`, fix: '.env dosyası oluşturun' });
|
|
408
|
+
console.log(chalk.yellow('⚠️ Secrets:'), chalk.white(`${unresolved.length}/${secretRefs.length} referans çözülemedi`));
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
results.push({ status: 'info', message: 'Secret referansı yok' });
|
|
412
|
+
console.log(chalk.gray('⚪ Secrets:'), chalk.white('Referans yok'));
|
|
413
|
+
}
|
|
414
|
+
} catch {
|
|
415
|
+
results.push({ status: 'info', message: 'Secrets kontrol edilemedi' });
|
|
416
|
+
console.log(chalk.gray('⚪ Secrets:'), chalk.white('Kontrol edilemedi'));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// 21. .env dosyası varlığı (secret ref varsa)
|
|
420
|
+
if (config) {
|
|
421
|
+
const envFile = path.join(CONFIG_DIR, '.env');
|
|
422
|
+
if (fs.existsSync(envFile)) {
|
|
423
|
+
console.log(chalk.gray(' 📄 .env dosyası:'), chalk.white('Mevcut'));
|
|
424
|
+
} else {
|
|
425
|
+
console.log(chalk.gray(' 📄 .env dosyası:'), chalk.white('Yok'));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
324
429
|
// Özet
|
|
325
430
|
console.log('');
|
|
326
431
|
console.log(chalk.gray(' ' + '─'.repeat(48)));
|
|
@@ -4,13 +4,12 @@ const path = require('path');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { spawn, execSync } = require('child_process');
|
|
6
6
|
const pino = require('pino');
|
|
7
|
+
const { loadBaileys } = require('../utils/baileys');
|
|
8
|
+
const { ApiError } = require('../utils/errors');
|
|
7
9
|
|
|
8
10
|
const PID_FILE = path.join(os.homedir(), '.natureco', 'gateway.pid');
|
|
9
11
|
const LOG_FILE = path.join(os.homedir(), '.natureco', 'gateway.log');
|
|
10
12
|
|
|
11
|
-
// WhatsApp imports
|
|
12
|
-
let makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, Browsers;
|
|
13
|
-
|
|
14
13
|
// Silent logger for Baileys
|
|
15
14
|
const silentLogger = {
|
|
16
15
|
level: 'silent',
|
|
@@ -23,18 +22,6 @@ const silentLogger = {
|
|
|
23
22
|
child: () => silentLogger
|
|
24
23
|
};
|
|
25
24
|
|
|
26
|
-
// Lazy load Baileys
|
|
27
|
-
function loadBaileys() {
|
|
28
|
-
if (!makeWASocket) {
|
|
29
|
-
const baileys = require('@whiskeysockets/baileys');
|
|
30
|
-
makeWASocket = baileys.default;
|
|
31
|
-
useMultiFileAuthState = baileys.useMultiFileAuthState;
|
|
32
|
-
DisconnectReason = baileys.DisconnectReason;
|
|
33
|
-
fetchLatestBaileysVersion = baileys.fetchLatestBaileysVersion;
|
|
34
|
-
Browsers = baileys.Browsers;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
25
|
// Create Baileys logger — real pino in worker mode, silent otherwise
|
|
39
26
|
let _baileysLogger = null;
|
|
40
27
|
function getBaileysLogger() {
|
|
@@ -151,7 +138,8 @@ async function startGateway() {
|
|
|
151
138
|
|
|
152
139
|
async function runGatewayWorker() {
|
|
153
140
|
// This runs in the background
|
|
154
|
-
|
|
141
|
+
const pkg = require('../../package.json');
|
|
142
|
+
log('gateway', `Starting NatureCo Gateway v${pkg.version}...`, 'green');
|
|
155
143
|
|
|
156
144
|
// Load config
|
|
157
145
|
const { getConfig } = require('../utils/config');
|
|
@@ -188,6 +176,14 @@ async function runGatewayWorker() {
|
|
|
188
176
|
} else {
|
|
189
177
|
log('telegram', 'not configured, skipping', 'gray');
|
|
190
178
|
}
|
|
179
|
+
|
|
180
|
+
// Log new channel statuses
|
|
181
|
+
if (config.signalBotId) log('signal', `configured (${config.signalAccount || config.signalHttpUrl})`, 'gray');
|
|
182
|
+
if (config.ircBotId) log('irc', `configured (${config.ircNick} @ ${config.ircHost})`, 'gray');
|
|
183
|
+
if (config.mattermostBotId) log('mattermost', `configured (${config.mattermostBaseUrl})`, 'gray');
|
|
184
|
+
if (config.imessageBotId) log('imessage', 'configured (macOS only)', 'gray');
|
|
185
|
+
if (config.smsBotId) log('sms', `configured (${config.smsFromNumber})`, 'gray');
|
|
186
|
+
if (config.webhooks?.length) log('webhooks', `${config.webhooks.length} route(s) configured`, 'gray');
|
|
191
187
|
|
|
192
188
|
// Start HTTP server for message sending
|
|
193
189
|
startHttpServer();
|
|
@@ -196,8 +192,14 @@ async function runGatewayWorker() {
|
|
|
196
192
|
startCronJobs(config);
|
|
197
193
|
|
|
198
194
|
// Health check every 60 seconds
|
|
199
|
-
setInterval(() => {
|
|
200
|
-
|
|
195
|
+
setInterval(async () => {
|
|
196
|
+
const alive = fs.existsSync(PID_FILE) && (() => { try { process.kill(process.pid, 0); return true; } catch { return false; } })();
|
|
197
|
+
const wsOk = global.gatewayWs && global.gatewayWs.readyState === 1;
|
|
198
|
+
if (alive && wsOk) {
|
|
199
|
+
log('gateway', 'health check: OK', 'gray');
|
|
200
|
+
} else {
|
|
201
|
+
log('gateway', `health check: WARN (alive=${alive}, ws=${wsOk})`, 'yellow');
|
|
202
|
+
}
|
|
201
203
|
}, 60000);
|
|
202
204
|
|
|
203
205
|
log('gateway', 'Gateway running in background', 'green');
|
|
@@ -222,7 +224,7 @@ async function runGatewayWorker() {
|
|
|
222
224
|
|
|
223
225
|
async function startWhatsAppProvider(sessionDir, config) {
|
|
224
226
|
try {
|
|
225
|
-
loadBaileys();
|
|
227
|
+
const { makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, Browsers } = loadBaileys();
|
|
226
228
|
|
|
227
229
|
const { state, saveCreds } = await useMultiFileAuthState(sessionDir);
|
|
228
230
|
const { version } = await fetchLatestBaileysVersion();
|
|
@@ -563,6 +565,18 @@ function startHttpServer() {
|
|
|
563
565
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
564
566
|
res.end(JSON.stringify({ success: true, channel: 'telegram', target }));
|
|
565
567
|
|
|
568
|
+
} else if (channel === 'signal') {
|
|
569
|
+
log('http', `Signal message send requested (${target}) — requires signal-cli REST', 'yellow');
|
|
570
|
+
res.writeHead(501, { 'Content-Type': 'application/json' });
|
|
571
|
+
res.end(JSON.stringify({ error: 'Signal outbound via HTTP not yet implemented' }));
|
|
572
|
+
} else if (channel === 'irc') {
|
|
573
|
+
log('http', `IRC message send requested (${target})`, 'yellow');
|
|
574
|
+
res.writeHead(501, { 'Content-Type': 'application/json' });
|
|
575
|
+
res.end(JSON.stringify({ error: 'IRC outbound via HTTP not yet implemented' }));
|
|
576
|
+
} else if (channel === 'sms') {
|
|
577
|
+
log('http', `SMS message send requested (${target})`, 'yellow');
|
|
578
|
+
res.writeHead(501, { 'Content-Type': 'application/json' });
|
|
579
|
+
res.end(JSON.stringify({ error: 'SMS outbound via HTTP not yet implemented' }));
|
|
566
580
|
} else {
|
|
567
581
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
568
582
|
res.end(JSON.stringify({ error: 'Invalid channel. Use "whatsapp" or "telegram"' }));
|
|
@@ -650,7 +664,7 @@ function startCronJobs(config) {
|
|
|
650
664
|
|
|
651
665
|
if (!response.ok) {
|
|
652
666
|
const errorText = await response.text();
|
|
653
|
-
throw new
|
|
667
|
+
throw new ApiError(`Anthropic API error: ${response.status} - ${errorText}`, response.status);
|
|
654
668
|
}
|
|
655
669
|
|
|
656
670
|
const data = await response.json();
|
|
@@ -674,7 +688,7 @@ function startCronJobs(config) {
|
|
|
674
688
|
|
|
675
689
|
if (!response.ok) {
|
|
676
690
|
const errorText = await response.text();
|
|
677
|
-
throw new
|
|
691
|
+
throw new ApiError(`Provider API error: ${response.status} - ${errorText}`, response.status);
|
|
678
692
|
}
|
|
679
693
|
|
|
680
694
|
const data = await response.json();
|
package/src/commands/gateway.js
CHANGED
|
@@ -6,23 +6,11 @@ const packageJson = require('../../package.json');
|
|
|
6
6
|
const { getConfig, CONFIG_FILE } = require('../utils/config');
|
|
7
7
|
const { getSkills } = require('../utils/skills');
|
|
8
8
|
const { getMcpServers } = require('../utils/mcp');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
let makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, Browsers;
|
|
9
|
+
const { loadBaileys } = require('../utils/baileys');
|
|
10
|
+
const { NatureCoError, GatewayError, handleError } = require('../utils/errors');
|
|
12
11
|
const pino = require('pino');
|
|
13
12
|
const logger = pino({ level: 'silent' });
|
|
14
13
|
|
|
15
|
-
// Lazy load Baileys
|
|
16
|
-
function loadBaileys() {
|
|
17
|
-
if (!makeWASocket) {
|
|
18
|
-
const baileys = require('@whiskeysockets/baileys');
|
|
19
|
-
makeWASocket = baileys.default;
|
|
20
|
-
useMultiFileAuthState = baileys.useMultiFileAuthState;
|
|
21
|
-
fetchLatestBaileysVersion = baileys.fetchLatestBaileysVersion;
|
|
22
|
-
Browsers = baileys.Browsers;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
14
|
async function gateway(action) {
|
|
27
15
|
if (action === 'start') {
|
|
28
16
|
return startGateway();
|
|
@@ -139,7 +127,7 @@ async function startGateway() {
|
|
|
139
127
|
|
|
140
128
|
async function startWhatsAppProvider(sessionDir, config) {
|
|
141
129
|
try {
|
|
142
|
-
loadBaileys();
|
|
130
|
+
const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, Browsers } = loadBaileys();
|
|
143
131
|
|
|
144
132
|
const { state, saveCreds } = await useMultiFileAuthState(sessionDir);
|
|
145
133
|
const { version } = await fetchLatestBaileysVersion();
|
|
@@ -224,8 +212,9 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
224
212
|
await sock.sendMessage(msg.key.remoteJid, { text: reply.slice(0, 4000) });
|
|
225
213
|
console.log(chalk.cyan('[whatsapp]'), chalk.green(`!code tamamlandı (${result.iterations} iterasyon)`));
|
|
226
214
|
} catch (err) {
|
|
227
|
-
|
|
228
|
-
|
|
215
|
+
const msg = err instanceof NatureCoError ? err.message : err?.message ?? 'Unknown error';
|
|
216
|
+
console.log(chalk.red('[whatsapp/code]'), chalk.gray(`hata: ${msg}`));
|
|
217
|
+
await sock.sendMessage(msg.key.remoteJid, { text: `❌ Hata: ${msg}` });
|
|
229
218
|
}
|
|
230
219
|
continue;
|
|
231
220
|
}
|
|
@@ -235,14 +224,15 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
235
224
|
console.log(chalk.cyan('[whatsapp]'), chalk.gray('Sending reply...'));
|
|
236
225
|
|
|
237
226
|
const response = await sendMessage(config.apiKey, config.whatsappBotId, messageText, null, '');
|
|
238
|
-
const reply = response?.reply
|
|
227
|
+
const reply = response?.reply ?? response?.message ?? '';
|
|
239
228
|
|
|
240
229
|
if (reply) {
|
|
241
230
|
await sock.sendMessage(msg.key.remoteJid, { text: reply });
|
|
242
231
|
console.log(chalk.cyan('[whatsapp]'), chalk.green(`Reply sent (${reply.length} chars)`));
|
|
243
232
|
}
|
|
244
233
|
} catch (err) {
|
|
245
|
-
|
|
234
|
+
const msg = err instanceof NatureCoError ? err.message : err?.message ?? 'Unknown error';
|
|
235
|
+
console.log(chalk.red('[whatsapp]'), chalk.gray(`error: ${msg}`));
|
|
246
236
|
}
|
|
247
237
|
}
|
|
248
238
|
}
|
|
@@ -251,7 +241,8 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
251
241
|
sock.ev.on('creds.update', saveCreds);
|
|
252
242
|
|
|
253
243
|
} catch (err) {
|
|
254
|
-
|
|
244
|
+
const msg = err instanceof NatureCoError ? err.message : err?.message ?? 'Unknown error';
|
|
245
|
+
console.log(chalk.red('[whatsapp]'), chalk.gray(`failed to start: ${msg}`));
|
|
255
246
|
}
|
|
256
247
|
}
|
|
257
248
|
|
package/src/commands/help.js
CHANGED
|
@@ -40,6 +40,12 @@ function help() {
|
|
|
40
40
|
printCmd('natureco whatsapp connect', 'WhatsApp QR kod ile bağla');
|
|
41
41
|
printCmd('natureco discord connect', 'Discord bot bağla');
|
|
42
42
|
printCmd('natureco slack connect', 'Slack workspace bağla');
|
|
43
|
+
printCmd('natureco signal connect', 'Signal REST API bağla');
|
|
44
|
+
printCmd('natureco irc connect', 'IRC sunucusuna bağlan');
|
|
45
|
+
printCmd('natureco mattermost connect', 'Mattermost bot bağla');
|
|
46
|
+
printCmd('natureco imessage connect', 'iMessage bridge bağla');
|
|
47
|
+
printCmd('natureco sms connect', 'Twilio SMS bağla');
|
|
48
|
+
printCmd('natureco webhooks connect', 'Webhook ekle');
|
|
43
49
|
|
|
44
50
|
// ── Gateway & Dashboard ──────────────────────────────────────
|
|
45
51
|
console.log(chalk.cyan.bold('\n Gateway & Dashboard\n'));
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require('../utils/inquirer-wrapper');
|
|
3
|
+
const { getConfig, saveConfig } = require('../utils/config');
|
|
4
|
+
|
|
5
|
+
async function imessage(action) {
|
|
6
|
+
if (!action || action === 'connect') return connectImessage();
|
|
7
|
+
if (action === 'disconnect') return disconnectImessage();
|
|
8
|
+
if (action === 'status') return statusImessage();
|
|
9
|
+
console.log(chalk.red('\n❌ Unknown action\n'));
|
|
10
|
+
console.log(chalk.gray('Available actions: connect, disconnect, status\n'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function connectImessage() {
|
|
15
|
+
const config = getConfig();
|
|
16
|
+
if (!config.providerUrl) { console.log(chalk.red('\n❌ Setup yapılmamış. Önce "natureco setup" çalıştırın.\n')); process.exit(1); }
|
|
17
|
+
console.log(chalk.yellow('\n⏳ iMessage bağlantısı hazırlanıyor...\n'));
|
|
18
|
+
console.log(chalk.gray('iMessage sadece macOS\'te çalışır. Windows/Linux\'ta kullanılamaz.'));
|
|
19
|
+
console.log(chalk.gray('Gereken: imsg bridge: https://github.com/mbilker/imsg\n'));
|
|
20
|
+
const answers = await inquirer.prompt([
|
|
21
|
+
{ type: 'input', name: 'cliPath', message: 'imsg CLI yolu (opsiyonel, boş bırakın PATH\'ten bulsun):' },
|
|
22
|
+
{ type: 'list', name: 'dmPolicy', message: 'DM politikası:', choices: [{ name: 'Pairing (önerilen)', value: 'pairing' }, { name: 'Allowlist', value: 'allowlist' }, { name: 'Open', value: 'open' }, { name: 'Disabled', value: 'disabled' }], default: 'pairing' },
|
|
23
|
+
]);
|
|
24
|
+
const botId = `imessage_${Date.now()}`;
|
|
25
|
+
config.imessageCliPath = answers.cliPath.trim() || '';
|
|
26
|
+
config.imessageDmPolicy = answers.dmPolicy;
|
|
27
|
+
config.imessageBotId = botId;
|
|
28
|
+
saveConfig(config);
|
|
29
|
+
console.log(chalk.green('\n✅ iMessage bağlantısı kaydedildi!\n'));
|
|
30
|
+
console.log(chalk.cyan('Bot ID:'), chalk.white(botId));
|
|
31
|
+
if (config.imessageCliPath) console.log(chalk.cyan('CLI Yolu:'), chalk.white(config.imessageCliPath));
|
|
32
|
+
console.log(chalk.gray('\nNot: iMessage sadece macOS\'te çalışır.'));
|
|
33
|
+
console.log(chalk.gray('Gateway ile başlatmak için: natureco gateway start\n'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function disconnectImessage() {
|
|
37
|
+
const config = getConfig();
|
|
38
|
+
if (!config.imessageBotId) { console.log(chalk.gray('\n⚠️ No iMessage connection found\n')); return; }
|
|
39
|
+
const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: 'iMessage bağlantısını kaldırmak istediğinize emin misiniz?', default: false }]);
|
|
40
|
+
if (!confirm) { console.log(chalk.gray('\nCancelled\n')); return; }
|
|
41
|
+
delete config.imessageCliPath; delete config.imessageDmPolicy; delete config.imessageBotId;
|
|
42
|
+
saveConfig(config);
|
|
43
|
+
console.log(chalk.green('\n✅ iMessage disconnected\n'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function statusImessage() {
|
|
47
|
+
const config = getConfig();
|
|
48
|
+
if (!config.imessageBotId) { console.log(chalk.gray('\n⚠️ iMessage not connected\n')); console.log(chalk.gray('Connect with: natureco imessage connect\n')); return; }
|
|
49
|
+
console.log(chalk.green('\n✅ iMessage connected\n'));
|
|
50
|
+
console.log(chalk.cyan('Bot ID:'), chalk.white(config.imessageBotId));
|
|
51
|
+
if (config.imessageCliPath) console.log(chalk.cyan('CLI Yolu:'), chalk.white(config.imessageCliPath));
|
|
52
|
+
console.log(chalk.gray('\nDisconnect with: natureco imessage disconnect\n'));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = imessage;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require('../utils/inquirer-wrapper');
|
|
3
|
+
const { getConfig, saveConfig } = require('../utils/config');
|
|
4
|
+
|
|
5
|
+
async function irc(action) {
|
|
6
|
+
if (!action || action === 'connect') return connectIrc();
|
|
7
|
+
if (action === 'disconnect') return disconnectIrc();
|
|
8
|
+
if (action === 'status') return statusIrc();
|
|
9
|
+
console.log(chalk.red('\n❌ Unknown action\n'));
|
|
10
|
+
console.log(chalk.gray('Available actions: connect, disconnect, status\n'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function connectIrc() {
|
|
15
|
+
const config = getConfig();
|
|
16
|
+
if (!config.providerUrl) { console.log(chalk.red('\n❌ Setup yapılmamış. Önce "natureco setup" çalıştırın.\n')); process.exit(1); }
|
|
17
|
+
console.log(chalk.yellow('\n⏳ IRC bağlantısı hazırlanıyor...\n'));
|
|
18
|
+
const answers = await inquirer.prompt([
|
|
19
|
+
{ type: 'input', name: 'host', message: 'IRC sunucusu (örn: irc.libera.chat):', validate: v => v.trim() ? true : 'Gerekli' },
|
|
20
|
+
{ type: 'input', name: 'port', message: 'Port:', default: '6697' },
|
|
21
|
+
{ type: 'confirm', name: 'tls', message: 'TLS kullanılsın mı?', default: true },
|
|
22
|
+
{ type: 'input', name: 'nick', message: 'Nick:', validate: v => v.trim() ? true : 'Gerekli' },
|
|
23
|
+
{ type: 'input', name: 'username', message: 'Kullanıcı adı (opsiyonel):' },
|
|
24
|
+
{ type: 'password', name: 'password', message: 'Parola (opsiyonel):' },
|
|
25
|
+
{ type: 'input', name: 'channels', message: 'Kanallar (virgülle ayırın, örn: #natureco,#bots):' },
|
|
26
|
+
{ type: 'list', name: 'dmPolicy', message: 'DM politikası:', choices: [{ name: 'Pairing (önerilen)', value: 'pairing' }, { name: 'Allowlist', value: 'allowlist' }, { name: 'Open', value: 'open' }, { name: 'Disabled', value: 'disabled' }], default: 'pairing' },
|
|
27
|
+
]);
|
|
28
|
+
const botId = `irc_${Date.now()}`;
|
|
29
|
+
config.ircHost = answers.host.trim();
|
|
30
|
+
config.ircPort = parseInt(answers.port) || 6697;
|
|
31
|
+
config.ircTls = answers.tls;
|
|
32
|
+
config.ircNick = answers.nick.trim();
|
|
33
|
+
config.ircUsername = answers.username.trim() || answers.nick.trim();
|
|
34
|
+
config.ircPassword = answers.password || '';
|
|
35
|
+
config.ircChannels = answers.channels ? answers.channels.split(',').map(c => c.trim()).filter(Boolean) : [];
|
|
36
|
+
config.ircDmPolicy = answers.dmPolicy;
|
|
37
|
+
config.ircBotId = botId;
|
|
38
|
+
saveConfig(config);
|
|
39
|
+
console.log(chalk.green('\n✅ IRC bağlantısı kaydedildi!\n'));
|
|
40
|
+
console.log(chalk.cyan('Bot ID:'), chalk.white(botId));
|
|
41
|
+
console.log(chalk.cyan('Sunucu:'), chalk.white(`${answers.host.trim()}:${answers.port}`));
|
|
42
|
+
console.log(chalk.cyan('Nick:'), chalk.white(answers.nick.trim()));
|
|
43
|
+
if (config.ircChannels.length) console.log(chalk.cyan('Kanallar:'), chalk.white(config.ircChannels.join(', ')));
|
|
44
|
+
console.log(chalk.gray('\nGateway ile başlatmak için: natureco gateway start\n'));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function disconnectIrc() {
|
|
48
|
+
const config = getConfig();
|
|
49
|
+
if (!config.ircBotId) { console.log(chalk.gray('\n⚠️ No IRC connection found\n')); return; }
|
|
50
|
+
const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: 'IRC bağlantısını kaldırmak istediğinize emin misiniz?', default: false }]);
|
|
51
|
+
if (!confirm) { console.log(chalk.gray('\nCancelled\n')); return; }
|
|
52
|
+
delete config.ircHost; delete config.ircPort; delete config.ircTls; delete config.ircNick;
|
|
53
|
+
delete config.ircUsername; delete config.ircPassword; delete config.ircChannels;
|
|
54
|
+
delete config.ircDmPolicy; delete config.ircBotId;
|
|
55
|
+
saveConfig(config);
|
|
56
|
+
console.log(chalk.green('\n✅ IRC disconnected\n'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function statusIrc() {
|
|
60
|
+
const config = getConfig();
|
|
61
|
+
if (!config.ircBotId) { console.log(chalk.gray('\n⚠️ IRC not connected\n')); console.log(chalk.gray('Connect with: natureco irc connect\n')); return; }
|
|
62
|
+
console.log(chalk.green('\n✅ IRC connected\n'));
|
|
63
|
+
console.log(chalk.cyan('Bot ID:'), chalk.white(config.ircBotId));
|
|
64
|
+
console.log(chalk.cyan('Sunucu:'), chalk.white(`${config.ircHost}:${config.ircPort}`));
|
|
65
|
+
console.log(chalk.cyan('Nick:'), chalk.white(config.ircNick));
|
|
66
|
+
if (config.ircChannels?.length) console.log(chalk.cyan('Kanallar:'), chalk.white(config.ircChannels.join(', ')));
|
|
67
|
+
console.log(chalk.gray('\nDisconnect with: natureco irc disconnect\n'));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = irc;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require('../utils/inquirer-wrapper');
|
|
3
|
+
const { getConfig, saveConfig } = require('../utils/config');
|
|
4
|
+
|
|
5
|
+
async function mattermost(action) {
|
|
6
|
+
if (!action || action === 'connect') return connectMattermost();
|
|
7
|
+
if (action === 'disconnect') return disconnectMattermost();
|
|
8
|
+
if (action === 'status') return statusMattermost();
|
|
9
|
+
console.log(chalk.red('\n❌ Unknown action\n'));
|
|
10
|
+
console.log(chalk.gray('Available actions: connect, disconnect, status\n'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function connectMattermost() {
|
|
15
|
+
const config = getConfig();
|
|
16
|
+
if (!config.providerUrl) { console.log(chalk.red('\n❌ Setup yapılmamış. Önce "natureco setup" çalıştırın.\n')); process.exit(1); }
|
|
17
|
+
console.log(chalk.yellow('\n⏳ Mattermost bağlantısı hazırlanıyor...\n'));
|
|
18
|
+
console.log(chalk.gray('Mattermost bot token almak için:'));
|
|
19
|
+
console.log(chalk.gray('1. Mattermost > System Console > Bot Accounts'));
|
|
20
|
+
console.log(chalk.gray('2. Bot oluşturun veya mevcut botu kullanın'));
|
|
21
|
+
console.log(chalk.gray('3. Access Token oluşturun\n'));
|
|
22
|
+
const answers = await inquirer.prompt([
|
|
23
|
+
{ type: 'input', name: 'baseUrl', message: 'Mattermost sunucu URL (örn: https://mattermost.example.com):', validate: v => v.trim() ? true : 'Gerekli' },
|
|
24
|
+
{ type: 'input', name: 'token', message: 'Bot token:', validate: v => v.trim() ? true : 'Gerekli' },
|
|
25
|
+
{ type: 'list', name: 'dmPolicy', message: 'DM politikası:', choices: [{ name: 'Pairing (önerilen)', value: 'pairing' }, { name: 'Allowlist', value: 'allowlist' }, { name: 'Open', value: 'open' }, { name: 'Disabled', value: 'disabled' }], default: 'pairing' },
|
|
26
|
+
]);
|
|
27
|
+
const botId = `mattermost_${Date.now()}`;
|
|
28
|
+
config.mattermostBaseUrl = answers.baseUrl.trim().replace(/\/$/, '');
|
|
29
|
+
config.mattermostToken = answers.token.trim();
|
|
30
|
+
config.mattermostDmPolicy = answers.dmPolicy;
|
|
31
|
+
config.mattermostBotId = botId;
|
|
32
|
+
saveConfig(config);
|
|
33
|
+
console.log(chalk.green('\n✅ Mattermost bağlantısı kaydedildi!\n'));
|
|
34
|
+
console.log(chalk.cyan('Bot ID:'), chalk.white(botId));
|
|
35
|
+
console.log(chalk.cyan('Sunucu:'), chalk.white(config.mattermostBaseUrl));
|
|
36
|
+
console.log(chalk.cyan('Token:'), chalk.white(answers.token.slice(0, 20) + '...'));
|
|
37
|
+
console.log(chalk.gray('\nGateway ile başlatmak için: natureco gateway start\n'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function disconnectMattermost() {
|
|
41
|
+
const config = getConfig();
|
|
42
|
+
if (!config.mattermostBotId) { console.log(chalk.gray('\n⚠️ No Mattermost connection found\n')); return; }
|
|
43
|
+
const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: 'Mattermost bağlantısını kaldırmak istediğinize emin misiniz?', default: false }]);
|
|
44
|
+
if (!confirm) { console.log(chalk.gray('\nCancelled\n')); return; }
|
|
45
|
+
delete config.mattermostBaseUrl; delete config.mattermostToken;
|
|
46
|
+
delete config.mattermostDmPolicy; delete config.mattermostBotId;
|
|
47
|
+
saveConfig(config);
|
|
48
|
+
console.log(chalk.green('\n✅ Mattermost disconnected\n'));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function statusMattermost() {
|
|
52
|
+
const config = getConfig();
|
|
53
|
+
if (!config.mattermostBotId) { console.log(chalk.gray('\n⚠️ Mattermost not connected\n')); console.log(chalk.gray('Connect with: natureco mattermost connect\n')); return; }
|
|
54
|
+
console.log(chalk.green('\n✅ Mattermost connected\n'));
|
|
55
|
+
console.log(chalk.cyan('Bot ID:'), chalk.white(config.mattermostBotId));
|
|
56
|
+
console.log(chalk.cyan('Sunucu:'), chalk.white(config.mattermostBaseUrl));
|
|
57
|
+
console.log(chalk.cyan('Token:'), chalk.white((config.mattermostToken || '').slice(0, 20) + '...'));
|
|
58
|
+
console.log(chalk.cyan('DM Politikası:'), chalk.white(config.mattermostDmPolicy || 'pairing'));
|
|
59
|
+
console.log(chalk.gray('\nDisconnect with: natureco mattermost disconnect\n'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = mattermost;
|
package/src/commands/message.js
CHANGED
|
@@ -3,6 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { getConfig } = require('../utils/config');
|
|
6
|
+
const { ApiError } = require('../utils/errors');
|
|
6
7
|
|
|
7
8
|
const PID_FILE = path.join(os.homedir(), '.natureco', 'gateway.pid');
|
|
8
9
|
const GATEWAY_HTTP_URL = 'http://127.0.0.1:3847/send';
|
|
@@ -32,8 +33,8 @@ async function message(args) {
|
|
|
32
33
|
process.exit(1);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
if (!['whatsapp', 'telegram'].includes(channel)) {
|
|
36
|
-
console.log(chalk.red('\n❌ Geçersiz kanal.
|
|
36
|
+
if (!['whatsapp', 'telegram', 'signal', 'irc', 'sms'].includes(channel)) {
|
|
37
|
+
console.log(chalk.red('\n❌ Geçersiz kanal. Kullanılabilir: whatsapp, telegram, signal, irc, sms\n'));
|
|
37
38
|
process.exit(1);
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -68,12 +69,31 @@ async function message(args) {
|
|
|
68
69
|
process.exit(1);
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
if (channel === 'signal' && !config.signalBotId) {
|
|
73
|
+
console.log(chalk.red('\n❌ Signal bağlı değil\n'));
|
|
74
|
+
console.log(chalk.yellow('Önce Signal\'ı bağlayın:'), chalk.cyan('natureco signal connect\n'));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (channel === 'irc' && !config.ircBotId) {
|
|
79
|
+
console.log(chalk.red('\n❌ IRC bağlı değil\n'));
|
|
80
|
+
console.log(chalk.yellow('Önce IRC\'yi bağlayın:'), chalk.cyan('natureco irc connect\n'));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (channel === 'sms' && !config.smsBotId) {
|
|
85
|
+
console.log(chalk.red('\n❌ SMS bağlı değil\n'));
|
|
86
|
+
console.log(chalk.yellow('Önce SMS\'i bağlayın:'), chalk.cyan('natureco sms connect\n'));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
71
90
|
// Send message via HTTP endpoint
|
|
72
91
|
await sendMessageViaHttp(channel, target, messageText);
|
|
73
92
|
}
|
|
74
93
|
|
|
75
94
|
async function sendMessageViaHttp(channel, target, messageText) {
|
|
76
|
-
|
|
95
|
+
const channelNames = { whatsapp: 'WhatsApp', telegram: 'Telegram', signal: 'Signal', irc: 'IRC', sms: 'SMS' };
|
|
96
|
+
console.log(chalk.yellow(`\n⏳ ${channelNames[channel] || channel} mesajı gönderiliyor...\n`));
|
|
77
97
|
|
|
78
98
|
try {
|
|
79
99
|
const response = await fetch(GATEWAY_HTTP_URL, {
|
|
@@ -85,7 +105,7 @@ async function sendMessageViaHttp(channel, target, messageText) {
|
|
|
85
105
|
const data = await response.json();
|
|
86
106
|
|
|
87
107
|
if (!response.ok) {
|
|
88
|
-
throw new
|
|
108
|
+
throw new ApiError(data.error || `HTTP ${response.status}`, response.status);
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
console.log(chalk.green('✅ Mesaj gönderildi!\n'));
|