natureco-cli 2.8.2 → 2.9.0

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,12 @@ 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)')
109
+ .command('cron <action> [params...]')
110
+ .description('Manage scheduled tasks (add|list|remove)')
111
111
  .action((action, params) => {
112
112
  const cronCmd = require('../src/commands/cron');
113
- cronCmd(action, ...(params || []));
113
+ const args = process.argv.slice(3); // Get all args after 'cron'
114
+ cronCmd(args);
114
115
  });
115
116
 
116
117
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "2.8.2",
3
+ "version": "2.9.0",
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,175 @@
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`));
120
- process.exit(1);
121
- }
122
-
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'));
27
+ async function cron(args) {
28
+ const action = args[0];
29
+
30
+ if (!action || !['add', 'list', 'remove'].includes(action)) {
31
+ console.log(chalk.red('\n❌ Geçersiz aksiyon\n'));
32
+ console.log(chalk.gray('Kullanım:'));
33
+ console.log(chalk.cyan(' natureco cron add --name <name> --schedule <cron> --action <channel> --target <target> --prompt <prompt>'));
34
+ console.log(chalk.cyan(' natureco cron list'));
35
+ console.log(chalk.cyan(' natureco cron remove --name <name>'));
36
+ console.log(chalk.gray('\nÖrnek:'));
37
+ 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'));
125
38
  process.exit(1);
126
39
  }
127
40
 
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;
41
+ if (action === 'add') {
42
+ await addCron(args);
43
+ } else if (action === 'list') {
44
+ listCrons();
45
+ } else if (action === 'remove') {
46
+ removeCron(args);
217
47
  }
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
48
  }
246
49
 
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`));
50
+ async function addCron(args) {
51
+ const nameIndex = args.indexOf('--name');
52
+ const scheduleIndex = args.indexOf('--schedule');
53
+ const actionIndex = args.indexOf('--action');
54
+ const targetIndex = args.indexOf('--target');
55
+ const promptIndex = args.indexOf('--prompt');
56
+
57
+ if (nameIndex === -1 || scheduleIndex === -1 || actionIndex === -1 || targetIndex === -1 || promptIndex === -1) {
58
+ console.log(chalk.red('\n❌ Eksik parametre\n'));
59
+ console.log(chalk.gray('Gerekli parametreler: --name, --schedule, --action, --target, --prompt\n'));
253
60
  process.exit(1);
254
61
  }
255
- }
256
-
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`));
62
+
63
+ const name = args[nameIndex + 1];
64
+ const schedule = args[scheduleIndex + 1];
65
+ const action = args[actionIndex + 1];
66
+ const target = args[targetIndex + 1];
67
+ const prompt = args[promptIndex + 1];
68
+
69
+ if (!name || !schedule || !action || !target || !prompt) {
70
+ console.log(chalk.red('\n❌ Parametreler boş olamaz\n'));
261
71
  process.exit(1);
262
72
  }
263
73
 
264
- const apiKey = getApiKey();
265
- if (!apiKey) {
266
- console.log(chalk.red('\n❌ Not logged in. Run "natureco login" first.\n'));
74
+ if (!['whatsapp', 'telegram'].includes(action)) {
75
+ console.log(chalk.red('\n❌ Geçersiz action. Sadece "whatsapp" veya "telegram" kullanılabilir\n'));
267
76
  process.exit(1);
268
77
  }
269
78
 
270
- console.log(chalk.yellow(`\n⏳ Running job: ${job.name}...\n`));
271
-
79
+ // Validate cron expression
272
80
  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('');
81
+ const nodeCron = require('node-cron');
82
+ if (!nodeCron.validate(schedule)) {
83
+ console.log(chalk.red('\n❌ Geçersiz cron ifadesi\n'));
84
+ console.log(chalk.gray('Örnek: "0 9 * * *" (her gün saat 09:00)\n'));
85
+ process.exit(1);
285
86
  }
286
87
  } catch (err) {
287
- console.log(chalk.red(`\n❌ Error: ${err.message}\n`));
88
+ console.log(chalk.red('\n❌ node-cron yüklü değil\n'));
89
+ console.log(chalk.yellow('Yüklemek için:'), chalk.cyan('npm install -g node-cron\n'));
288
90
  process.exit(1);
289
91
  }
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`));
92
+
93
+ const crons = loadCrons();
94
+
95
+ // Check if name already exists
96
+ if (crons.find(c => c.name === name)) {
97
+ console.log(chalk.red('\n❌ Bu isimde bir cron zaten var\n'));
98
+ console.log(chalk.yellow('Önce silin:'), chalk.cyan(`natureco cron remove --name "${name}"\n`));
299
99
  process.exit(1);
300
100
  }
101
+
102
+ const newCron = {
103
+ name,
104
+ schedule,
105
+ action,
106
+ target,
107
+ prompt,
108
+ createdAt: new Date().toISOString(),
109
+ enabled: true
110
+ };
111
+
112
+ crons.push(newCron);
113
+ saveCrons(crons);
114
+
115
+ console.log(chalk.green('\n✅ Cron eklendi!\n'));
116
+ console.log(chalk.cyan('İsim:'), chalk.white(name));
117
+ console.log(chalk.cyan('Zamanlama:'), chalk.white(schedule));
118
+ console.log(chalk.cyan('Kanal:'), chalk.white(action));
119
+ console.log(chalk.cyan('Hedef:'), chalk.white(target));
120
+ console.log(chalk.cyan('Prompt:'), chalk.white(prompt));
121
+ console.log(chalk.gray('\nCron\'lar gateway başlatıldığında aktif olur.'));
122
+ console.log(chalk.gray('Gateway çalışıyorsa yeniden başlatın: natureco gateway stop && natureco gateway start\n'));
301
123
  }
302
124
 
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
- }
125
+ function listCrons() {
126
+ const crons = loadCrons();
309
127
 
310
- console.log(chalk.green('🚀 Cron daemon started\n'));
311
- console.log(chalk.gray('Press Ctrl+C to stop\n'));
128
+ if (crons.length === 0) {
129
+ console.log(chalk.gray('\n⚪ Kayıtlı cron yok\n'));
130
+ return;
131
+ }
312
132
 
313
- const jobs = loadCronJobs();
314
- const tasks = [];
133
+ console.log(chalk.green(`\n📅 Kayıtlı Cron'lar (${crons.length})\n`));
315
134
 
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})`));
135
+ crons.forEach((c, i) => {
136
+ console.log(chalk.cyan(`${i + 1}. ${c.name}`));
137
+ console.log(chalk.gray(` Zamanlama: ${c.schedule}`));
138
+ console.log(chalk.gray(` Kanal: ${c.action} ${c.target}`));
139
+ console.log(chalk.gray(` Prompt: ${c.prompt.substring(0, 60)}${c.prompt.length > 60 ? '...' : ''}`));
140
+ console.log(chalk.gray(` Durum: ${c.enabled ? '✅ Aktif' : '❌ Pasif'}`));
141
+ console.log('');
349
142
  });
143
+ }
144
+
145
+ function removeCron(args) {
146
+ const nameIndex = args.indexOf('--name');
350
147
 
351
- if (tasks.length === 0) {
352
- console.log(chalk.gray('No enabled jobs found.\n'));
353
- process.exit(0);
148
+ if (nameIndex === -1) {
149
+ console.log(chalk.red('\n❌ --name parametresi gerekli\n'));
150
+ process.exit(1);
354
151
  }
355
152
 
356
- console.log('');
153
+ const name = args[nameIndex + 1];
357
154
 
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);
363
- });
364
- }
365
-
366
- function showCronLogs(id) {
367
- const job = getCronJob(id);
368
- if (!job) {
369
- console.log(chalk.red(`\n❌ Cron job not found: ${id}\n`));
155
+ if (!name) {
156
+ console.log(chalk.red('\n❌ İsim boş olamaz\n'));
370
157
  process.exit(1);
371
158
  }
372
159
 
373
- const logs = getCronLog(id);
374
- if (!logs) {
375
- console.log(chalk.gray(`\nNo logs found for job: ${job.name}\n`));
376
- return;
160
+ const crons = loadCrons();
161
+ const index = crons.findIndex(c => c.name === name);
162
+
163
+ if (index === -1) {
164
+ console.log(chalk.red('\n❌ Bu isimde bir cron bulunamadı\n'));
165
+ process.exit(1);
377
166
  }
378
167
 
379
- console.log(chalk.yellow(`\nLogs for job: ${job.name}\n`));
380
- console.log(logs);
168
+ crons.splice(index, 1);
169
+ saveCrons(crons);
170
+
171
+ console.log(chalk.green('\n✅ Cron silindi!\n'));
172
+ console.log(chalk.gray('Gateway çalışıyorsa yeniden başlatın: natureco gateway stop && natureco gateway start\n'));
381
173
  }
382
174
 
383
- module.exports = cronCommand;
175
+ 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.2</div>
214
+ <div class="version-badge" id="version-badge">v2.9.0</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.2',
344
+ version: 'v2.9.0',
345
345
  bots: cfg.bots || [],
346
346
  telegramToken: cfg.telegramToken || null,
347
347
  whatsappConnected: cfg.whatsappConnected || false,
@@ -9,7 +9,18 @@ const LOG_FILE = path.join(os.homedir(), '.natureco', 'gateway.log');
9
9
 
10
10
  // WhatsApp imports
11
11
  let makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, Browsers;
12
- const pino = require('pino');
12
+
13
+ // Silent logger for Baileys
14
+ const silentLogger = {
15
+ level: 'silent',
16
+ trace: () => {},
17
+ debug: () => {},
18
+ info: () => {},
19
+ warn: () => {},
20
+ error: () => {},
21
+ fatal: () => {},
22
+ child: () => silentLogger
23
+ };
13
24
 
14
25
  // Lazy load Baileys
15
26
  function loadBaileys() {
@@ -127,7 +138,7 @@ async function startGateway() {
127
138
 
128
139
  async function runGatewayWorker() {
129
140
  // This runs in the background
130
- log('gateway', 'Starting NatureCo Gateway v2.8.2...', 'green');
141
+ log('gateway', 'Starting NatureCo Gateway v2.9.0...', 'green');
131
142
 
132
143
  // Load config
133
144
  const { getConfig } = require('../utils/config');
@@ -168,6 +179,9 @@ async function runGatewayWorker() {
168
179
  // Start HTTP server for message sending
169
180
  startHttpServer();
170
181
 
182
+ // Load and start cron jobs
183
+ startCronJobs(config);
184
+
171
185
  // Health check every 60 seconds
172
186
  setInterval(() => {
173
187
  log('gateway', 'health check: OK', 'gray');
@@ -207,7 +221,7 @@ async function startWhatsAppProvider(sessionDir, config) {
207
221
  version,
208
222
  auth: state,
209
223
  printQRInTerminal: false,
210
- logger: pino({ level: 'silent' }),
224
+ logger: silentLogger,
211
225
  browser: Browsers.ubuntu('Chrome'),
212
226
  connectTimeoutMs: 60000,
213
227
  defaultQueryTimeoutMs: 60000,
@@ -560,6 +574,87 @@ function startHttpServer() {
560
574
  });
561
575
  }
562
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
+
563
658
  function stopGateway() {
564
659
  if (!fs.existsSync(PID_FILE)) {
565
660
  console.log(chalk.gray('\n⚠️ Gateway not running\n'));