beast-agent 1.9.0 → 2.0.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.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +130 -130
  3. package/bin/beast-agent.js +137 -137
  4. package/package.json +1 -1
  5. package/scripts/fix-electron.js +138 -138
  6. package/scripts/release.js +137 -131
  7. package/scripts/swap-electron.js +40 -40
  8. package/src/agent/agentdefs.js +122 -122
  9. package/src/agent/bots.js +580 -580
  10. package/src/agent/bus.js +389 -389
  11. package/src/agent/computeruse.js +200 -200
  12. package/src/agent/config.js +284 -284
  13. package/src/agent/discord.js +332 -332
  14. package/src/agent/engine.js +75 -10
  15. package/src/agent/kb.js +123 -123
  16. package/src/agent/llm.js +430 -430
  17. package/src/agent/logger.js +90 -90
  18. package/src/agent/mcp.js +427 -427
  19. package/src/agent/mem0.js +605 -605
  20. package/src/agent/memory.js +427 -427
  21. package/src/agent/mqueue.js +124 -124
  22. package/src/agent/pdf.js +20 -20
  23. package/src/agent/research.js +133 -133
  24. package/src/agent/scripts/news.py +113 -113
  25. package/src/agent/scripts/stealthsearch.py +30 -30
  26. package/src/agent/scripts/websearch.py +225 -225
  27. package/src/agent/searxng.js +325 -325
  28. package/src/agent/seeds/brainstorming/SKILL.md +90 -90
  29. package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
  30. package/src/agent/seeds/executing-plans/SKILL.md +60 -60
  31. package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
  32. package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
  33. package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
  34. package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
  35. package/src/agent/seeds/writing-plans/SKILL.md +162 -162
  36. package/src/agent/seeds/writing-skills/SKILL.md +229 -229
  37. package/src/agent/skills.js +652 -652
  38. package/src/agent/store.js +378 -378
  39. package/src/agent/telegram.js +155 -155
  40. package/src/agent/tokens.js +39 -39
  41. package/src/agent/usage.js +125 -125
  42. package/src/agent/watchers.js +312 -312
  43. package/src/agent/watext.js +80 -80
  44. package/src/agent/whatsapp.js +555 -555
  45. package/src/cron.js +255 -255
  46. package/src/main.js +348 -6
  47. package/src/preload.js +9 -0
  48. package/src/renderer/browserPreload.js +73 -73
  49. package/src/renderer/i18n.js +4 -0
  50. package/src/renderer/index.html +448 -409
  51. package/src/renderer/renderer.js +824 -41
  52. package/src/renderer/style.css +264 -5
package/src/cron.js CHANGED
@@ -1,255 +1,255 @@
1
- 'use strict';
2
-
3
- /* Beast cron: zamanlanmış görevler (%APPDATA%\beast\cron.json).
4
- 5 alanlı cron ifadeleri: dakika saat ayGunu ay haftaGunu
5
- Desteklenen sözdizimi: * , - / sayılar. Saat geldiğinde onFire tetiklenir. */
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
- const { beastRoot } = require('./agent/memory');
10
-
11
- function file() {
12
- return path.join(beastRoot(), 'cron.json');
13
- }
14
-
15
- let jobs = [];
16
- let timer = null;
17
- let onFire = null;
18
-
19
- function uid() {
20
- return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
21
- }
22
-
23
- function load() {
24
- try {
25
- const raw = JSON.parse(fs.readFileSync(file(), 'utf8'));
26
- jobs = Array.isArray(raw.jobs) ? raw.jobs : [];
27
- } catch {
28
- jobs = [];
29
- }
30
- }
31
-
32
- function save() {
33
- try {
34
- fs.mkdirSync(beastRoot(), { recursive: true });
35
- fs.writeFileSync(file(), JSON.stringify({ jobs }, null, 2));
36
- } catch {}
37
- }
38
-
39
- /* ---------- cron ayrıştırıcı ---------- */
40
-
41
- function parseField(field, min, max) {
42
- const allowed = new Set();
43
- for (const partRaw of String(field).split(',')) {
44
- const part = partRaw.trim();
45
- if (!part) return null;
46
- const [rangeRaw, stepRaw] = part.split('/');
47
- const step = stepRaw === undefined ? 1 : Number(stepRaw);
48
- if (!Number.isInteger(step) || step < 1) return null;
49
- let lo = min;
50
- let hi = max;
51
- if (rangeRaw !== '*') {
52
- const m = rangeRaw.match(/^(\d+)(?:-(\d+))?$/);
53
- if (!m) return null;
54
- lo = Number(m[1]);
55
- hi = m[2] === undefined ? lo : Number(m[2]);
56
- if (lo < min || hi > max || lo > hi) return null;
57
- }
58
- for (let v = lo; v <= hi; v += step) allowed.add(v);
59
- }
60
- return allowed;
61
- }
62
-
63
- /* {min,h,dow,mon} alan eşleşmesini Date üzerinde kontrol eder */
64
- function cronMatches(fields, d) {
65
- return (
66
- fields.min.has(d.getMinutes()) &&
67
- fields.hour.has(d.getHours()) &&
68
- fields.dom.has(d.getDate()) &&
69
- fields.mon.has(d.getMonth() + 1) &&
70
- (fields.dow.has(d.getDay()) || (fields.dow.has(7) && d.getDay() === 0))
71
- );
72
- }
73
-
74
- function parseCron(expr) {
75
- const parts = String(expr || '').trim().split(/\s+/);
76
- if (parts.length !== 5) return null;
77
- const fields = {
78
- min: parseField(parts[0], 0, 59),
79
- hour: parseField(parts[1], 0, 23),
80
- dom: parseField(parts[2], 1, 31),
81
- mon: parseField(parts[3], 1, 12),
82
- dow: parseField(parts[4], 0, 7),
83
- };
84
- if (Object.values(fields).some((f) => !f)) return null;
85
- return fields;
86
- }
87
-
88
- /* Verilen zamandan sonraki ilk uyarın anı (ms) — en fazla 400 gün tarar */
89
- function nextRunFrom(expr, fromDate) {
90
- const fields = parseCron(expr);
91
- if (!fields) return null;
92
- const d = new Date(fromDate.getTime());
93
- d.setSeconds(0, 0);
94
- d.setMinutes(d.getMinutes() + 1);
95
- for (let i = 0; i < 400 * 24 * 60; i++) {
96
- if (cronMatches(fields, d)) return d.getTime();
97
- d.setMinutes(d.getMinutes() + 1);
98
- }
99
- return null;
100
- }
101
-
102
- function isValidSchedule(expr) {
103
- return nextRunFrom(expr, new Date()) !== null;
104
- }
105
-
106
- /* Tekrarlı hatırlatma ("her sabah 09:00" tarzı): when'in saat/dakikası +
107
- repeat frekansından 5 alanlı cron üretir. repeat için presetler YA DA
108
- doğrudan geçerli bir cron ifadesi kabul edilir. Saf fonksiyon. */
109
- function reminderSchedule(when, repeat) {
110
- const r = String(repeat || '').trim().toLowerCase();
111
- if (!r) return { ok: false, error: 'repeat gerekli: daily/weekly/monthly/weekdays veya 5 alanlı cron' };
112
- /* doğrudan cron verilmişse (5 boşlukla ayrılmış alan) aynen doğrula */
113
- if (/^(\S+\s+){4}\S+$/.test(r)) {
114
- if (isValidSchedule(r)) return { ok: true, schedule: r };
115
- return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
116
- }
117
- const d = new Date(String(when || '').trim());
118
- if (isNaN(d.getTime())) return { ok: false, error: 'tekrarlı hatırlatma için geçerli when (saat bilgisiyle) gerekli' };
119
- const map = {
120
- daily: `${d.getMinutes()} ${d.getHours()} * * *`,
121
- weekdays: `${d.getMinutes()} ${d.getHours()} * * 1-5`,
122
- weekly: `${d.getMinutes()} ${d.getHours()} * * ${d.getDay()}`,
123
- monthly: `${d.getMinutes()} ${d.getHours()} ${d.getDate()} * *`,
124
- };
125
- const schedule = map[r];
126
- if (!schedule) {
127
- return { ok: false, error: 'bilinmeyen repeat — daily/weekly/monthly/weekdays kullan ya da 5 alanlı cron ver' };
128
- }
129
- if (!isValidSchedule(schedule)) return { ok: false, error: 'oluşan takvim hiç tetiklenmiyor — when/repeat uyumsuz' };
130
- return { ok: true, schedule };
131
- }
132
-
133
- /* ---------- CRUD ---------- */
134
-
135
- function list() {
136
- return jobs.map((j) => ({ ...j }));
137
- }
138
-
139
- function add({ name, schedule, prompt, once, sessionId }) {
140
- const n = String(name || '').trim().slice(0, 80) || 'Görev';
141
- const s = String(schedule || '').trim();
142
- const p = String(prompt || '').trim().slice(0, 4000);
143
- if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
144
- if (!p) return { ok: false, error: 'görev metni boş olamaz' };
145
- const job = {
146
- id: uid(),
147
- name: n,
148
- schedule: s,
149
- prompt: p,
150
- sessionId: sessionId || null,
151
- once: !!once,
152
- enabled: true,
153
- createdAt: new Date().toISOString(),
154
- lastRunAt: null,
155
- nextRunAt: nextRunFrom(s, new Date()),
156
- };
157
- jobs.push(job);
158
- save();
159
- return { ok: true, job: { ...job } };
160
- }
161
-
162
- function update(id, patch) {
163
- const job = jobs.find((j) => j.id === id);
164
- if (!job) return { ok: false, error: 'görev bulunamadı' };
165
- if (patch.sessionId !== undefined) job.sessionId = patch.sessionId;
166
- if (patch.name !== undefined) job.name = String(patch.name).trim().slice(0, 80) || job.name;
167
- if (patch.prompt !== undefined) {
168
- const p = String(patch.prompt).trim().slice(0, 4000);
169
- if (!p) return { ok: false, error: 'görev metni boş olamaz' };
170
- job.prompt = p;
171
- }
172
- if (patch.schedule !== undefined) {
173
- const s = String(patch.schedule).trim();
174
- if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi' };
175
- job.schedule = s;
176
- job.nextRunAt = nextRunFrom(s, new Date());
177
- }
178
- save();
179
- return { ok: true, job: { ...job } };
180
- }
181
-
182
- function remove(id) {
183
- const before = jobs.length;
184
- jobs = jobs.filter((j) => j.id !== id);
185
- save();
186
- return { ok: jobs.length < before };
187
- }
188
-
189
- /* Tüm görevleri bellekten ve diskten sil; zamanlayıcıyı durdur.
190
- "hepsini sil" / "/cron clear" için kullanılır. */
191
- function clearAll() {
192
- const count = jobs.length;
193
- jobs = [];
194
- if (timer) { clearInterval(timer); timer = null; }
195
- save();
196
- return { ok: true, count };
197
- }
198
-
199
- function toggle(id) {
200
- const job = jobs.find((j) => j.id === id);
201
- if (!job) return { ok: false, error: 'görev bulunamadı' };
202
- job.enabled = !job.enabled;
203
- job.nextRunAt = job.enabled ? nextRunFrom(job.schedule, new Date()) : null;
204
- save();
205
- return { ok: true, job: { ...job } };
206
- }
207
-
208
- function runNow(id) {
209
- const job = jobs.find((j) => j.id === id);
210
- if (!job) return { ok: false, error: 'görev bulunamadı' };
211
- fire(job);
212
- return { ok: true };
213
- }
214
-
215
- /* ---------- zamanlayıcı ---------- */
216
-
217
- function fire(job) {
218
- job.lastRunAt = new Date().toISOString();
219
- if (!job.once) job.nextRunAt = nextRunFrom(job.schedule, new Date());
220
- if (job.once) {
221
- // tek seferlik (hatırlatma): uyarınca kendini sil
222
- jobs = jobs.filter((j) => j.id !== job.id);
223
- }
224
- save();
225
- if (onFire) onFire({ ...job });
226
- }
227
-
228
- function tick() {
229
- const now = Date.now();
230
- for (const job of jobs.slice()) {
231
- if (!job.enabled || !job.nextRunAt) continue;
232
- if (new Date(job.nextRunAt).getTime() <= now) fire(job);
233
- }
234
- }
235
-
236
- function init(hooks) {
237
- onFire = (hooks && hooks.onFire) || null;
238
- load();
239
- for (const job of jobs) {
240
- if (job.enabled && (!job.nextRunAt || new Date(job.nextRunAt).getTime() <= Date.now())) {
241
- job.nextRunAt = nextRunFrom(job.schedule, new Date());
242
- }
243
- }
244
- save();
245
- if (timer) clearInterval(timer);
246
- timer = setInterval(tick, 20 * 1000);
247
- }
248
-
249
- /* /stop: zamanlayıcıyı durdur (init ile geri başlar) */
250
- function stop() {
251
- if (timer) clearInterval(timer);
252
- timer = null;
253
- }
254
-
255
- module.exports = { init, stop, list, add, update, remove, clearAll, toggle, runNow, parseCron, nextRunFrom, isValidSchedule, reminderSchedule };
1
+ 'use strict';
2
+
3
+ /* Beast cron: zamanlanmış görevler (%APPDATA%\beast\cron.json).
4
+ 5 alanlı cron ifadeleri: dakika saat ayGunu ay haftaGunu
5
+ Desteklenen sözdizimi: * , - / sayılar. Saat geldiğinde onFire tetiklenir. */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { beastRoot } = require('./agent/memory');
10
+
11
+ function file() {
12
+ return path.join(beastRoot(), 'cron.json');
13
+ }
14
+
15
+ let jobs = [];
16
+ let timer = null;
17
+ let onFire = null;
18
+
19
+ function uid() {
20
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
21
+ }
22
+
23
+ function load() {
24
+ try {
25
+ const raw = JSON.parse(fs.readFileSync(file(), 'utf8'));
26
+ jobs = Array.isArray(raw.jobs) ? raw.jobs : [];
27
+ } catch {
28
+ jobs = [];
29
+ }
30
+ }
31
+
32
+ function save() {
33
+ try {
34
+ fs.mkdirSync(beastRoot(), { recursive: true });
35
+ fs.writeFileSync(file(), JSON.stringify({ jobs }, null, 2));
36
+ } catch {}
37
+ }
38
+
39
+ /* ---------- cron ayrıştırıcı ---------- */
40
+
41
+ function parseField(field, min, max) {
42
+ const allowed = new Set();
43
+ for (const partRaw of String(field).split(',')) {
44
+ const part = partRaw.trim();
45
+ if (!part) return null;
46
+ const [rangeRaw, stepRaw] = part.split('/');
47
+ const step = stepRaw === undefined ? 1 : Number(stepRaw);
48
+ if (!Number.isInteger(step) || step < 1) return null;
49
+ let lo = min;
50
+ let hi = max;
51
+ if (rangeRaw !== '*') {
52
+ const m = rangeRaw.match(/^(\d+)(?:-(\d+))?$/);
53
+ if (!m) return null;
54
+ lo = Number(m[1]);
55
+ hi = m[2] === undefined ? lo : Number(m[2]);
56
+ if (lo < min || hi > max || lo > hi) return null;
57
+ }
58
+ for (let v = lo; v <= hi; v += step) allowed.add(v);
59
+ }
60
+ return allowed;
61
+ }
62
+
63
+ /* {min,h,dow,mon} alan eşleşmesini Date üzerinde kontrol eder */
64
+ function cronMatches(fields, d) {
65
+ return (
66
+ fields.min.has(d.getMinutes()) &&
67
+ fields.hour.has(d.getHours()) &&
68
+ fields.dom.has(d.getDate()) &&
69
+ fields.mon.has(d.getMonth() + 1) &&
70
+ (fields.dow.has(d.getDay()) || (fields.dow.has(7) && d.getDay() === 0))
71
+ );
72
+ }
73
+
74
+ function parseCron(expr) {
75
+ const parts = String(expr || '').trim().split(/\s+/);
76
+ if (parts.length !== 5) return null;
77
+ const fields = {
78
+ min: parseField(parts[0], 0, 59),
79
+ hour: parseField(parts[1], 0, 23),
80
+ dom: parseField(parts[2], 1, 31),
81
+ mon: parseField(parts[3], 1, 12),
82
+ dow: parseField(parts[4], 0, 7),
83
+ };
84
+ if (Object.values(fields).some((f) => !f)) return null;
85
+ return fields;
86
+ }
87
+
88
+ /* Verilen zamandan sonraki ilk uyarın anı (ms) — en fazla 400 gün tarar */
89
+ function nextRunFrom(expr, fromDate) {
90
+ const fields = parseCron(expr);
91
+ if (!fields) return null;
92
+ const d = new Date(fromDate.getTime());
93
+ d.setSeconds(0, 0);
94
+ d.setMinutes(d.getMinutes() + 1);
95
+ for (let i = 0; i < 400 * 24 * 60; i++) {
96
+ if (cronMatches(fields, d)) return d.getTime();
97
+ d.setMinutes(d.getMinutes() + 1);
98
+ }
99
+ return null;
100
+ }
101
+
102
+ function isValidSchedule(expr) {
103
+ return nextRunFrom(expr, new Date()) !== null;
104
+ }
105
+
106
+ /* Tekrarlı hatırlatma ("her sabah 09:00" tarzı): when'in saat/dakikası +
107
+ repeat frekansından 5 alanlı cron üretir. repeat için presetler YA DA
108
+ doğrudan geçerli bir cron ifadesi kabul edilir. Saf fonksiyon. */
109
+ function reminderSchedule(when, repeat) {
110
+ const r = String(repeat || '').trim().toLowerCase();
111
+ if (!r) return { ok: false, error: 'repeat gerekli: daily/weekly/monthly/weekdays veya 5 alanlı cron' };
112
+ /* doğrudan cron verilmişse (5 boşlukla ayrılmış alan) aynen doğrula */
113
+ if (/^(\S+\s+){4}\S+$/.test(r)) {
114
+ if (isValidSchedule(r)) return { ok: true, schedule: r };
115
+ return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
116
+ }
117
+ const d = new Date(String(when || '').trim());
118
+ if (isNaN(d.getTime())) return { ok: false, error: 'tekrarlı hatırlatma için geçerli when (saat bilgisiyle) gerekli' };
119
+ const map = {
120
+ daily: `${d.getMinutes()} ${d.getHours()} * * *`,
121
+ weekdays: `${d.getMinutes()} ${d.getHours()} * * 1-5`,
122
+ weekly: `${d.getMinutes()} ${d.getHours()} * * ${d.getDay()}`,
123
+ monthly: `${d.getMinutes()} ${d.getHours()} ${d.getDate()} * *`,
124
+ };
125
+ const schedule = map[r];
126
+ if (!schedule) {
127
+ return { ok: false, error: 'bilinmeyen repeat — daily/weekly/monthly/weekdays kullan ya da 5 alanlı cron ver' };
128
+ }
129
+ if (!isValidSchedule(schedule)) return { ok: false, error: 'oluşan takvim hiç tetiklenmiyor — when/repeat uyumsuz' };
130
+ return { ok: true, schedule };
131
+ }
132
+
133
+ /* ---------- CRUD ---------- */
134
+
135
+ function list() {
136
+ return jobs.map((j) => ({ ...j }));
137
+ }
138
+
139
+ function add({ name, schedule, prompt, once, sessionId }) {
140
+ const n = String(name || '').trim().slice(0, 80) || 'Görev';
141
+ const s = String(schedule || '').trim();
142
+ const p = String(prompt || '').trim().slice(0, 4000);
143
+ if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
144
+ if (!p) return { ok: false, error: 'görev metni boş olamaz' };
145
+ const job = {
146
+ id: uid(),
147
+ name: n,
148
+ schedule: s,
149
+ prompt: p,
150
+ sessionId: sessionId || null,
151
+ once: !!once,
152
+ enabled: true,
153
+ createdAt: new Date().toISOString(),
154
+ lastRunAt: null,
155
+ nextRunAt: nextRunFrom(s, new Date()),
156
+ };
157
+ jobs.push(job);
158
+ save();
159
+ return { ok: true, job: { ...job } };
160
+ }
161
+
162
+ function update(id, patch) {
163
+ const job = jobs.find((j) => j.id === id);
164
+ if (!job) return { ok: false, error: 'görev bulunamadı' };
165
+ if (patch.sessionId !== undefined) job.sessionId = patch.sessionId;
166
+ if (patch.name !== undefined) job.name = String(patch.name).trim().slice(0, 80) || job.name;
167
+ if (patch.prompt !== undefined) {
168
+ const p = String(patch.prompt).trim().slice(0, 4000);
169
+ if (!p) return { ok: false, error: 'görev metni boş olamaz' };
170
+ job.prompt = p;
171
+ }
172
+ if (patch.schedule !== undefined) {
173
+ const s = String(patch.schedule).trim();
174
+ if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi' };
175
+ job.schedule = s;
176
+ job.nextRunAt = nextRunFrom(s, new Date());
177
+ }
178
+ save();
179
+ return { ok: true, job: { ...job } };
180
+ }
181
+
182
+ function remove(id) {
183
+ const before = jobs.length;
184
+ jobs = jobs.filter((j) => j.id !== id);
185
+ save();
186
+ return { ok: jobs.length < before };
187
+ }
188
+
189
+ /* Tüm görevleri bellekten ve diskten sil; zamanlayıcıyı durdur.
190
+ "hepsini sil" / "/cron clear" için kullanılır. */
191
+ function clearAll() {
192
+ const count = jobs.length;
193
+ jobs = [];
194
+ if (timer) { clearInterval(timer); timer = null; }
195
+ save();
196
+ return { ok: true, count };
197
+ }
198
+
199
+ function toggle(id) {
200
+ const job = jobs.find((j) => j.id === id);
201
+ if (!job) return { ok: false, error: 'görev bulunamadı' };
202
+ job.enabled = !job.enabled;
203
+ job.nextRunAt = job.enabled ? nextRunFrom(job.schedule, new Date()) : null;
204
+ save();
205
+ return { ok: true, job: { ...job } };
206
+ }
207
+
208
+ function runNow(id) {
209
+ const job = jobs.find((j) => j.id === id);
210
+ if (!job) return { ok: false, error: 'görev bulunamadı' };
211
+ fire(job);
212
+ return { ok: true };
213
+ }
214
+
215
+ /* ---------- zamanlayıcı ---------- */
216
+
217
+ function fire(job) {
218
+ job.lastRunAt = new Date().toISOString();
219
+ if (!job.once) job.nextRunAt = nextRunFrom(job.schedule, new Date());
220
+ if (job.once) {
221
+ // tek seferlik (hatırlatma): uyarınca kendini sil
222
+ jobs = jobs.filter((j) => j.id !== job.id);
223
+ }
224
+ save();
225
+ if (onFire) onFire({ ...job });
226
+ }
227
+
228
+ function tick() {
229
+ const now = Date.now();
230
+ for (const job of jobs.slice()) {
231
+ if (!job.enabled || !job.nextRunAt) continue;
232
+ if (new Date(job.nextRunAt).getTime() <= now) fire(job);
233
+ }
234
+ }
235
+
236
+ function init(hooks) {
237
+ onFire = (hooks && hooks.onFire) || null;
238
+ load();
239
+ for (const job of jobs) {
240
+ if (job.enabled && (!job.nextRunAt || new Date(job.nextRunAt).getTime() <= Date.now())) {
241
+ job.nextRunAt = nextRunFrom(job.schedule, new Date());
242
+ }
243
+ }
244
+ save();
245
+ if (timer) clearInterval(timer);
246
+ timer = setInterval(tick, 20 * 1000);
247
+ }
248
+
249
+ /* /stop: zamanlayıcıyı durdur (init ile geri başlar) */
250
+ function stop() {
251
+ if (timer) clearInterval(timer);
252
+ timer = null;
253
+ }
254
+
255
+ module.exports = { init, stop, list, add, update, remove, clearAll, toggle, runNow, parseCron, nextRunFrom, isValidSchedule, reminderSchedule };