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.
@@ -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
- // Extract base URL for health check
267
- const baseUrl = config.providerUrl.replace(/\/v1.*$/, '');
268
-
269
- const response = await fetch(baseUrl, {
270
- method: 'GET',
271
- signal: AbortSignal.timeout(5000),
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
- // Any response (even 404) means provider is reachable
275
- console.log(chalk.green('✅ Provider Erişim:'), chalk.white('Provider erişilebilir'));
276
- passed++;
277
- criticalPassed++;
278
- results.push({ status: 'ok', message: 'Provider erişilebilir' });
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 erişilemiyor' });
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
- log('gateway', 'Starting NatureCo Gateway v2.11.3...', 'green');
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
- log('gateway', 'health check: OK', 'gray');
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 Error(`Anthropic API error: ${response.status} - ${errorText}`);
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 Error(`Provider API error: ${response.status} - ${errorText}`);
691
+ throw new ApiError(`Provider API error: ${response.status} - ${errorText}`, response.status);
678
692
  }
679
693
 
680
694
  const data = await response.json();
@@ -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
- // WhatsApp imports
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
- console.log(chalk.red('[whatsapp/code]'), chalk.gray(`hata: ${err.message}`));
228
- await sock.sendMessage(msg.key.remoteJid, { text: `❌ Hata: ${err.message}` });
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 || response?.message || '';
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
- console.log(chalk.red('[whatsapp]'), chalk.gray(`error: ${err.message}`));
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
- console.log(chalk.red('[whatsapp]'), chalk.gray(`failed to start: ${err.message}`));
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
 
@@ -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;
@@ -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. Sadece "whatsapp" veya "telegram" kullanılabilir\n'));
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
- console.log(chalk.yellow(`\n⏳ ${channel === 'whatsapp' ? 'WhatsApp' : 'Telegram'} mesajı gönderiliyor...\n`));
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 Error(data.error || `HTTP ${response.status}`);
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'));