natureco-cli 2.8.3 → 2.9.1

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 CHANGED
@@ -106,11 +106,16 @@ program
106
106
  });
107
107
 
108
108
  program
109
- .command('cron [action] [params...]')
110
- .description('Manage cron jobs (list|add|remove|run|enable|disable|start|logs)')
111
- .action((action, params) => {
109
+ .command('cron <action>')
110
+ .option('--name <name>', 'Cron job adı')
111
+ .option('--schedule <schedule>', 'Cron schedule (örnek: "0 9 * * *")')
112
+ .option('--action <action>', 'Aksiyon: whatsapp veya telegram')
113
+ .option('--target <target>', 'Hedef numara veya chat ID')
114
+ .option('--prompt <prompt>', 'AI\'a gönderilecek prompt')
115
+ .description('Cron job yönetimi (add|list|remove)')
116
+ .action((action, options) => {
112
117
  const cronCmd = require('../src/commands/cron');
113
- cronCmd(action, ...(params || []));
118
+ cronCmd(action, options);
114
119
  });
115
120
 
116
121
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "2.8.3",
3
+ "version": "2.9.1",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "conf": "^10.2.0",
32
32
  "eventsource": "^2.0.2",
33
33
  "inquirer": "^8.2.7",
34
- "node-cron": "^2.0.3",
34
+ "node-cron": "^3.0.3",
35
35
  "node-telegram-bot-api": "^0.65.1",
36
36
  "ora": "^5.4.1",
37
37
  "pino": "^8.21.0",
@@ -1,383 +1,151 @@
1
- const inquirer = require('inquirer');
2
1
  const chalk = require('chalk');
3
- const cron = require('node-cron');
4
- const { getApiKey } = require('../utils/config');
5
- const { getBots, sendMessage } = require('../utils/api');
6
- const {
7
- loadCronJobs,
8
- addCronJob,
9
- removeCronJob,
10
- updateCronJob,
11
- getCronJob,
12
- parseCronSchedule,
13
- validateCronExpression,
14
- logCronOutput,
15
- getCronLog,
16
- } = require('../utils/cron');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
17
5
 
18
- async function cronCommand(action, ...args) {
19
- if (!action || action === 'list') {
20
- return listCronJobs();
21
- }
22
-
23
- if (action === 'add') {
24
- return addCronJobInteractive();
25
- }
26
-
27
- if (action === 'remove') {
28
- const id = args[0];
29
- if (!id) {
30
- console.log(chalk.red('\n❌ Job ID required\n'));
31
- console.log(chalk.gray('Usage: natureco cron remove <id>\n'));
32
- process.exit(1);
33
- }
34
- return removeCronJobCommand(id);
35
- }
36
-
37
- if (action === 'run') {
38
- const id = args[0];
39
- if (!id) {
40
- console.log(chalk.red('\n❌ Job ID required\n'));
41
- console.log(chalk.gray('Usage: natureco cron run <id>\n'));
42
- process.exit(1);
43
- }
44
- return runCronJobNow(id);
45
- }
46
-
47
- if (action === 'enable') {
48
- const id = args[0];
49
- if (!id) {
50
- console.log(chalk.red('\n❌ Job ID required\n'));
51
- console.log(chalk.gray('Usage: natureco cron enable <id>\n'));
52
- process.exit(1);
53
- }
54
- return toggleCronJob(id, true);
55
- }
56
-
57
- if (action === 'disable') {
58
- const id = args[0];
59
- if (!id) {
60
- console.log(chalk.red('\n❌ Job ID required\n'));
61
- console.log(chalk.gray('Usage: natureco cron disable <id>\n'));
62
- process.exit(1);
63
- }
64
- return toggleCronJob(id, false);
65
- }
66
-
67
- if (action === 'start') {
68
- return startCronDaemon();
69
- }
70
-
71
- if (action === 'logs') {
72
- const id = args[0];
73
- if (!id) {
74
- console.log(chalk.red('\n❌ Job ID required\n'));
75
- console.log(chalk.gray('Usage: natureco cron logs <id>\n'));
76
- process.exit(1);
6
+ const CRONS_FILE = path.join(os.homedir(), '.natureco', 'crons.json');
7
+
8
+ function loadCrons() {
9
+ try {
10
+ if (!fs.existsSync(CRONS_FILE)) {
11
+ return [];
77
12
  }
78
- return showCronLogs(id);
13
+ return JSON.parse(fs.readFileSync(CRONS_FILE, 'utf-8'));
14
+ } catch {
15
+ return [];
79
16
  }
80
-
81
- console.log(chalk.red(`\n❌ Unknown action: ${action}\n`));
82
- console.log(chalk.gray('Available actions: list, add, remove, run, enable, disable, start, logs\n'));
83
- process.exit(1);
84
17
  }
85
18
 
86
- function listCronJobs() {
87
- const jobs = loadCronJobs();
88
-
89
- if (jobs.length === 0) {
90
- console.log(chalk.gray('\nNo cron jobs found.\n'));
91
- console.log(chalk.gray('Create one with: natureco cron add\n'));
92
- return;
19
+ function saveCrons(crons) {
20
+ const dir = path.dirname(CRONS_FILE);
21
+ if (!fs.existsSync(dir)) {
22
+ fs.mkdirSync(dir, { recursive: true });
93
23
  }
94
-
95
- console.log(chalk.yellow('\nCron Jobs:\n'));
96
-
97
- jobs.forEach(job => {
98
- const status = job.enabled ? chalk.green('✓ enabled') : chalk.gray('✗ disabled');
99
- console.log(` ${chalk.cyan(job.id)} ${status}`);
100
- console.log(chalk.white(` ${job.name}`));
101
- console.log(chalk.gray(` Bot: ${job.botName} | Schedule: ${job.schedule}`));
102
- console.log(chalk.gray(` Output: ${job.outputType === 'file' ? 'log file' : 'terminal'}`));
103
- console.log('');
104
- });
24
+ fs.writeFileSync(CRONS_FILE, JSON.stringify(crons, null, 2), 'utf-8');
105
25
  }
106
26
 
107
- async function addCronJobInteractive() {
108
- const apiKey = getApiKey();
109
-
110
- if (!apiKey) {
111
- console.log(chalk.red('\n❌ Not logged in. Run "natureco login" first.\n'));
112
- process.exit(1);
113
- }
114
-
115
- let botList;
116
- try {
117
- botList = await getBots(apiKey);
118
- } catch (err) {
119
- console.log(chalk.red(`\n❌ Error: ${err.message}\n`));
27
+ async function cron(action, options) {
28
+ if (!action || !['add', 'list', 'remove'].includes(action)) {
29
+ console.log(chalk.red('\n❌ Geçersiz aksiyon\n'));
30
+ console.log(chalk.gray('Kullanım:'));
31
+ console.log(chalk.cyan(' natureco cron add --name <name> --schedule <cron> --action <channel> --target <target> --prompt <prompt>'));
32
+ console.log(chalk.cyan(' natureco cron list'));
33
+ console.log(chalk.cyan(' natureco cron remove --name <name>'));
34
+ console.log(chalk.gray('\nÖrnek:'));
35
+ console.log(chalk.cyan(' natureco cron add --name "bitcoin-fiyat" --schedule "0 9 * * *" --action "whatsapp" --target "+905422842631" --prompt "Bugünkü Bitcoin fiyatını öğren ve kısaca bildir"\n'));
120
36
  process.exit(1);
121
37
  }
122
38
 
123
- if (!botList || !botList.bots || botList.bots.length === 0) {
124
- console.log(chalk.gray('No bots found. Create one at https://developers.natureco.me\n'));
125
- process.exit(1);
126
- }
127
-
128
- process.stdin.resume();
129
-
130
- const answers = await inquirer.prompt([
131
- {
132
- type: 'input',
133
- name: 'name',
134
- message: 'Job name:',
135
- validate: (val) => val.trim() !== '' || 'Name cannot be empty',
136
- },
137
- {
138
- type: 'input',
139
- name: 'message',
140
- message: 'Message to send to bot:',
141
- validate: (val) => val.trim() !== '' || 'Message cannot be empty',
142
- },
143
- {
144
- type: 'list',
145
- name: 'botId',
146
- message: 'Select bot:',
147
- choices: botList.bots.map(b => ({ name: b.name, value: b.id })),
148
- },
149
- {
150
- type: 'list',
151
- name: 'scheduleType',
152
- message: 'Schedule type:',
153
- choices: [
154
- { name: 'Daily at specific time', value: 'daily' },
155
- { name: 'Every X hours', value: 'hours' },
156
- { name: 'Custom cron expression', value: 'custom' },
157
- ],
158
- },
159
- ]);
160
-
161
- let schedule;
162
-
163
- if (answers.scheduleType === 'daily') {
164
- const timeAnswer = await inquirer.prompt([
165
- {
166
- type: 'input',
167
- name: 'time',
168
- message: 'Time (HH:MM):',
169
- default: '09:00',
170
- validate: (val) => {
171
- const match = val.match(/^(\d{1,2}):(\d{2})$/);
172
- if (!match) return 'Invalid time format. Use HH:MM';
173
- const hour = parseInt(match[1]);
174
- const minute = parseInt(match[2]);
175
- if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
176
- return 'Invalid time';
177
- }
178
- return true;
179
- },
180
- },
181
- ]);
182
- schedule = `daily at ${timeAnswer.time}`;
183
- } else if (answers.scheduleType === 'hours') {
184
- const hoursAnswer = await inquirer.prompt([
185
- {
186
- type: 'input',
187
- name: 'hours',
188
- message: 'Every X hours:',
189
- default: '1',
190
- validate: (val) => {
191
- const num = parseInt(val);
192
- if (isNaN(num) || num < 1 || num > 24) {
193
- return 'Enter a number between 1 and 24';
194
- }
195
- return true;
196
- },
197
- },
198
- ]);
199
- schedule = `every ${hoursAnswer.hours} hours`;
200
- } else {
201
- const cronAnswer = await inquirer.prompt([
202
- {
203
- type: 'input',
204
- name: 'cron',
205
- message: 'Cron expression:',
206
- default: '0 9 * * *',
207
- validate: (val) => {
208
- const parsed = parseCronSchedule(val);
209
- if (!validateCronExpression(parsed)) {
210
- return 'Invalid cron expression';
211
- }
212
- return true;
213
- },
214
- },
215
- ]);
216
- schedule = cronAnswer.cron;
217
- }
218
-
219
- const outputAnswer = await inquirer.prompt([
220
- {
221
- type: 'list',
222
- name: 'outputType',
223
- message: 'Output destination:',
224
- choices: [
225
- { name: 'Save to log file', value: 'file' },
226
- { name: 'Print to terminal', value: 'terminal' },
227
- ],
228
- default: 'file',
229
- },
230
- ]);
231
-
232
- const bot = botList.bots.find(b => b.id === answers.botId);
233
-
234
- const job = addCronJob({
235
- name: answers.name,
236
- message: answers.message,
237
- botId: answers.botId,
238
- botName: bot.name,
239
- schedule,
240
- outputType: outputAnswer.outputType,
241
- });
242
-
243
- console.log(chalk.green(`\n✅ Cron job created: ${job.id}\n`));
244
- console.log(chalk.gray('Start the cron daemon with: natureco cron start\n'));
245
- }
246
-
247
- function removeCronJobCommand(id) {
248
- const removed = removeCronJob(id);
249
- if (removed) {
250
- console.log(chalk.green(`\n✅ Cron job removed: ${id}\n`));
251
- } else {
252
- console.log(chalk.red(`\n❌ Cron job not found: ${id}\n`));
253
- process.exit(1);
39
+ if (action === 'add') {
40
+ await addCron(options);
41
+ } else if (action === 'list') {
42
+ listCrons();
43
+ } else if (action === 'remove') {
44
+ removeCron(options);
254
45
  }
255
46
  }
256
47
 
257
- async function runCronJobNow(id) {
258
- const job = getCronJob(id);
259
- if (!job) {
260
- console.log(chalk.red(`\n❌ Cron job not found: ${id}\n`));
48
+ async function addCron(options) {
49
+ const { name, schedule, action, target, prompt } = options;
50
+
51
+ if (!name || !schedule || !action || !target || !prompt) {
52
+ console.log(chalk.red('\n❌ Eksik parametre\n'));
53
+ console.log(chalk.gray('Gerekli parametreler: --name, --schedule, --action, --target, --prompt\n'));
261
54
  process.exit(1);
262
55
  }
263
56
 
264
- const apiKey = getApiKey();
265
- if (!apiKey) {
266
- console.log(chalk.red('\n❌ Not logged in. Run "natureco login" first.\n'));
57
+ if (!['whatsapp', 'telegram'].includes(action)) {
58
+ console.log(chalk.red('\n❌ Geçersiz action. Sadece "whatsapp" veya "telegram" kullanılabilir\n'));
267
59
  process.exit(1);
268
60
  }
269
61
 
270
- console.log(chalk.yellow(`\n⏳ Running job: ${job.name}...\n`));
271
-
62
+ // Validate cron expression
272
63
  try {
273
- const response = await sendMessage(apiKey, job.botId, job.message, null, '');
274
- const reply = response.reply || response.message || 'No response';
275
-
276
- const output = `Job: ${job.name}\nBot: ${job.botName}\nMessage: ${job.message}\nReply: ${reply}`;
277
-
278
- if (job.outputType === 'file') {
279
- logCronOutput(job.id, output);
280
- console.log(chalk.green('✅ Job completed. Output saved to log file.\n'));
281
- } else {
282
- console.log(chalk.green('✅ Job completed:\n'));
283
- console.log(chalk.white(reply));
284
- console.log('');
64
+ const nodeCron = require('node-cron');
65
+ if (!nodeCron.validate(schedule)) {
66
+ console.log(chalk.red('\n❌ Geçersiz cron ifadesi\n'));
67
+ console.log(chalk.gray('Örnek: "0 9 * * *" (her gün saat 09:00)\n'));
68
+ process.exit(1);
285
69
  }
286
70
  } catch (err) {
287
- console.log(chalk.red(`\n❌ Error: ${err.message}\n`));
71
+ console.log(chalk.red('\n❌ node-cron yüklü değil\n'));
72
+ console.log(chalk.yellow('Yüklemek için:'), chalk.cyan('npm install -g node-cron\n'));
288
73
  process.exit(1);
289
74
  }
290
- }
291
-
292
- function toggleCronJob(id, enabled) {
293
- const updated = updateCronJob(id, { enabled });
294
- if (updated) {
295
- const status = enabled ? 'enabled' : 'disabled';
296
- console.log(chalk.green(`\n✅ Cron job ${status}: ${id}\n`));
297
- } else {
298
- console.log(chalk.red(`\n❌ Cron job not found: ${id}\n`));
75
+
76
+ const crons = loadCrons();
77
+
78
+ // Check if name already exists
79
+ if (crons.find(c => c.name === name)) {
80
+ console.log(chalk.red('\n❌ Bu isimde bir cron zaten var\n'));
81
+ console.log(chalk.yellow('Önce silin:'), chalk.cyan(`natureco cron remove --name "${name}"\n`));
299
82
  process.exit(1);
300
83
  }
84
+
85
+ const newCron = {
86
+ name,
87
+ schedule,
88
+ action,
89
+ target,
90
+ prompt,
91
+ createdAt: new Date().toISOString(),
92
+ enabled: true
93
+ };
94
+
95
+ crons.push(newCron);
96
+ saveCrons(crons);
97
+
98
+ console.log(chalk.green('\n✅ Cron eklendi!\n'));
99
+ console.log(chalk.cyan('İsim:'), chalk.white(name));
100
+ console.log(chalk.cyan('Zamanlama:'), chalk.white(schedule));
101
+ console.log(chalk.cyan('Kanal:'), chalk.white(action));
102
+ console.log(chalk.cyan('Hedef:'), chalk.white(target));
103
+ console.log(chalk.cyan('Prompt:'), chalk.white(prompt));
104
+ console.log(chalk.gray('\nCron\'lar gateway başlatıldığında aktif olur.'));
105
+ console.log(chalk.gray('Gateway çalışıyorsa yeniden başlatın: natureco gateway stop && natureco gateway start\n'));
301
106
  }
302
107
 
303
- async function startCronDaemon() {
304
- const apiKey = getApiKey();
305
- if (!apiKey) {
306
- console.log(chalk.red('\n❌ Not logged in. Run "natureco login" first.\n'));
307
- process.exit(1);
308
- }
309
-
310
- console.log(chalk.green('🚀 Cron daemon started\n'));
311
- console.log(chalk.gray('Press Ctrl+C to stop\n'));
108
+ function listCrons() {
109
+ const crons = loadCrons();
312
110
 
313
- const jobs = loadCronJobs();
314
- const tasks = [];
315
-
316
- jobs.forEach(job => {
317
- if (!job.enabled) return;
318
-
319
- const cronExpression = parseCronSchedule(job.schedule);
320
-
321
- if (!validateCronExpression(cronExpression)) {
322
- console.log(chalk.red(`❌ Invalid cron expression for job ${job.id}: ${job.schedule}`));
323
- return;
324
- }
325
-
326
- const task = cron.schedule(cronExpression, async () => {
327
- console.log(chalk.yellow(`⏳ Running job: ${job.name}`));
328
-
329
- try {
330
- const response = await sendMessage(apiKey, job.botId, job.message, null, '');
331
- const reply = response.reply || response.message || 'No response';
332
-
333
- const output = `Job: ${job.name}\nBot: ${job.botName}\nMessage: ${job.message}\nReply: ${reply}`;
334
-
335
- if (job.outputType === 'file') {
336
- logCronOutput(job.id, output);
337
- console.log(chalk.green(`✅ Job completed: ${job.name} (saved to log)`));
338
- } else {
339
- console.log(chalk.green(`✅ Job completed: ${job.name}`));
340
- console.log(chalk.white(reply));
341
- }
342
- } catch (err) {
343
- console.log(chalk.red(`❌ Job failed: ${job.name} - ${err.message}`));
344
- }
345
- });
346
-
347
- tasks.push(task);
348
- console.log(chalk.cyan(`✓ Scheduled: ${job.name} (${job.schedule})`));
349
- });
350
-
351
- if (tasks.length === 0) {
352
- console.log(chalk.gray('No enabled jobs found.\n'));
353
- process.exit(0);
111
+ if (crons.length === 0) {
112
+ console.log(chalk.gray('\n⚪ Kayıtlı cron yok\n'));
113
+ return;
354
114
  }
355
115
 
356
- console.log('');
116
+ console.log(chalk.green(`\n📅 Kayıtlı Cron'lar (${crons.length})\n`));
357
117
 
358
- // Keep process alive
359
- process.on('SIGINT', () => {
360
- console.log(chalk.gray('\n\n👋 Stopping cron daemon...\n'));
361
- tasks.forEach(task => task.stop());
362
- process.exit(0);
118
+ crons.forEach((c, i) => {
119
+ console.log(chalk.cyan(`${i + 1}. ${c.name}`));
120
+ console.log(chalk.gray(` Zamanlama: ${c.schedule}`));
121
+ console.log(chalk.gray(` Kanal: ${c.action} → ${c.target}`));
122
+ console.log(chalk.gray(` Prompt: ${c.prompt.substring(0, 60)}${c.prompt.length > 60 ? '...' : ''}`));
123
+ console.log(chalk.gray(` Durum: ${c.enabled ? '✅ Aktif' : '❌ Pasif'}`));
124
+ console.log('');
363
125
  });
364
126
  }
365
127
 
366
- function showCronLogs(id) {
367
- const job = getCronJob(id);
368
- if (!job) {
369
- console.log(chalk.red(`\n❌ Cron job not found: ${id}\n`));
128
+ function removeCron(options) {
129
+ const { name } = options;
130
+
131
+ if (!name) {
132
+ console.log(chalk.red('\n❌ --name parametresi gerekli\n'));
370
133
  process.exit(1);
371
134
  }
372
135
 
373
- const logs = getCronLog(id);
374
- if (!logs) {
375
- console.log(chalk.gray(`\nNo logs found for job: ${job.name}\n`));
376
- return;
136
+ const crons = loadCrons();
137
+ const index = crons.findIndex(c => c.name === name);
138
+
139
+ if (index === -1) {
140
+ console.log(chalk.red('\n❌ Bu isimde bir cron bulunamadı\n'));
141
+ process.exit(1);
377
142
  }
378
143
 
379
- console.log(chalk.yellow(`\nLogs for job: ${job.name}\n`));
380
- console.log(logs);
144
+ crons.splice(index, 1);
145
+ saveCrons(crons);
146
+
147
+ console.log(chalk.green('\n✅ Cron silindi!\n'));
148
+ console.log(chalk.gray('Gateway çalışıyorsa yeniden başlatın: natureco gateway stop && natureco gateway start\n'));
381
149
  }
382
150
 
383
- module.exports = cronCommand;
151
+ module.exports = cron;
@@ -211,7 +211,7 @@ body::before{
211
211
  <div class="header-bot-name" id="header-bot-name">Nature Bot</div>
212
212
  <div class="header-bot-model" id="header-bot-model">NatureCo</div>
213
213
  </div>
214
- <div class="version-badge" id="version-badge">v2.8.3</div>
214
+ <div class="version-badge" id="version-badge">v2.9.1</div>
215
215
  </div>
216
216
  <div class="messages" id="messages"></div>
217
217
  <div class="input-area">
@@ -341,7 +341,7 @@ function dashboard(action) {
341
341
  apiKey: cfg.apiKey,
342
342
  defaultBot: cfg.defaultBot,
343
343
  defaultBotId: cfg.defaultBotId,
344
- version: 'v2.8.3',
344
+ version: 'v2.9.1',
345
345
  bots: cfg.bots || [],
346
346
  telegramToken: cfg.telegramToken || null,
347
347
  whatsappConnected: cfg.whatsappConnected || false,
@@ -138,7 +138,7 @@ async function startGateway() {
138
138
 
139
139
  async function runGatewayWorker() {
140
140
  // This runs in the background
141
- log('gateway', 'Starting NatureCo Gateway v2.8.3...', 'green');
141
+ log('gateway', 'Starting NatureCo Gateway v2.9.1...', 'green');
142
142
 
143
143
  // Load config
144
144
  const { getConfig } = require('../utils/config');
@@ -179,6 +179,9 @@ async function runGatewayWorker() {
179
179
  // Start HTTP server for message sending
180
180
  startHttpServer();
181
181
 
182
+ // Load and start cron jobs
183
+ startCronJobs(config);
184
+
182
185
  // Health check every 60 seconds
183
186
  setInterval(() => {
184
187
  log('gateway', 'health check: OK', 'gray');
@@ -571,6 +574,87 @@ function startHttpServer() {
571
574
  });
572
575
  }
573
576
 
577
+ function startCronJobs(config) {
578
+ try {
579
+ const nodeCron = require('node-cron');
580
+ const CRONS_FILE = path.join(os.homedir(), '.natureco', 'crons.json');
581
+
582
+ if (!fs.existsSync(CRONS_FILE)) {
583
+ log('cron', 'No cron jobs configured', 'gray');
584
+ return;
585
+ }
586
+
587
+ const crons = JSON.parse(fs.readFileSync(CRONS_FILE, 'utf-8'));
588
+ const enabledCrons = crons.filter(c => c.enabled);
589
+
590
+ if (enabledCrons.length === 0) {
591
+ log('cron', 'No enabled cron jobs', 'gray');
592
+ return;
593
+ }
594
+
595
+ log('cron', `Loading ${enabledCrons.length} cron job(s)...`, 'cyan');
596
+
597
+ enabledCrons.forEach(cronJob => {
598
+ try {
599
+ nodeCron.schedule(cronJob.schedule, async () => {
600
+ log('cron', `Triggered: ${cronJob.name}`, 'yellow');
601
+
602
+ try {
603
+ // Send prompt to AI
604
+ const { sendMessage } = require('../utils/api');
605
+ const conversationId = `cron_${cronJob.name.replace(/\s+/g, '_')}`;
606
+
607
+ log('cron', `Sending prompt to AI...`, 'cyan');
608
+ const response = await sendMessage(null, null, cronJob.prompt, conversationId);
609
+ const reply = response?.reply || response?.message || '';
610
+
611
+ if (!reply) {
612
+ log('cron', `No response from AI`, 'red');
613
+ return;
614
+ }
615
+
616
+ log('cron', `AI response received (${reply.length} chars)`, 'green');
617
+
618
+ // Send to target channel
619
+ if (cronJob.action === 'whatsapp') {
620
+ if (!global.whatsappSock) {
621
+ log('cron', `WhatsApp not connected, skipping`, 'red');
622
+ return;
623
+ }
624
+
625
+ const normalizedTarget = cronJob.target.replace(/[^\d]/g, '');
626
+ const jid = `${normalizedTarget}@s.whatsapp.net`;
627
+
628
+ await global.whatsappSock.sendMessage(jid, { text: reply });
629
+ log('cron', `Sent to WhatsApp: ${cronJob.target}`, 'green');
630
+
631
+ } else if (cronJob.action === 'telegram') {
632
+ if (!global.telegramBot) {
633
+ log('cron', `Telegram not connected, skipping`, 'red');
634
+ return;
635
+ }
636
+
637
+ await global.telegramBot.sendMessage(cronJob.target, reply);
638
+ log('cron', `Sent to Telegram: ${cronJob.target}`, 'green');
639
+ }
640
+
641
+ } catch (err) {
642
+ log('cron', `Error executing ${cronJob.name}: ${err.message}`, 'red');
643
+ }
644
+ });
645
+
646
+ log('cron', `Scheduled: ${cronJob.name} (${cronJob.schedule})`, 'green');
647
+
648
+ } catch (err) {
649
+ log('cron', `Failed to schedule ${cronJob.name}: ${err.message}`, 'red');
650
+ }
651
+ });
652
+
653
+ } catch (err) {
654
+ log('cron', `Failed to load cron jobs: ${err.message}`, 'red');
655
+ }
656
+ }
657
+
574
658
  function stopGateway() {
575
659
  if (!fs.existsSync(PID_FILE)) {
576
660
  console.log(chalk.gray('\n⚠️ Gateway not running\n'));