@rainfall-devkit/sdk 0.1.8 → 0.2.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.
Files changed (58) hide show
  1. package/README.md +51 -0
  2. package/dist/chunk-7MRE4ZVI.mjs +662 -0
  3. package/dist/chunk-AQFC7YAX.mjs +27 -0
  4. package/dist/chunk-EI7SJH5K.mjs +85 -0
  5. package/dist/chunk-NTTAVKRT.mjs +89 -0
  6. package/dist/chunk-RVKW5KBT.mjs +269 -0
  7. package/dist/chunk-V5QWJVLC.mjs +662 -0
  8. package/dist/chunk-VDPKDC3R.mjs +869 -0
  9. package/dist/chunk-WOITG5TG.mjs +84 -0
  10. package/dist/chunk-XAHJQRBJ.mjs +269 -0
  11. package/dist/chunk-XEQ6U3JQ.mjs +269 -0
  12. package/dist/cli/index.js +3797 -632
  13. package/dist/cli/index.mjs +453 -36
  14. package/dist/config-7UT7GYSN.mjs +16 -0
  15. package/dist/config-DDTQQBN7.mjs +14 -0
  16. package/dist/config-MD45VGWD.mjs +14 -0
  17. package/dist/config-ZKNHII2A.mjs +8 -0
  18. package/dist/daemon/index.d.mts +168 -0
  19. package/dist/daemon/index.d.ts +168 -0
  20. package/dist/daemon/index.js +3182 -0
  21. package/dist/daemon/index.mjs +1548 -0
  22. package/dist/errors-BMPseAnM.d.mts +47 -0
  23. package/dist/errors-BMPseAnM.d.ts +47 -0
  24. package/dist/errors-CZdRoYyw.d.ts +332 -0
  25. package/dist/errors-Chjq1Mev.d.mts +332 -0
  26. package/dist/index.d.mts +249 -2
  27. package/dist/index.d.ts +249 -2
  28. package/dist/index.js +1247 -3
  29. package/dist/index.mjs +227 -2
  30. package/dist/listeners-B5Vy9Ao5.d.ts +372 -0
  31. package/dist/listeners-BbYIaNCs.d.mts +372 -0
  32. package/dist/listeners-CP2A9J_2.d.ts +372 -0
  33. package/dist/listeners-CTRSofnm.d.mts +372 -0
  34. package/dist/listeners-CYI-YwIF.d.mts +372 -0
  35. package/dist/listeners-DRwITBW_.d.mts +372 -0
  36. package/dist/listeners-DrMrvFT5.d.ts +372 -0
  37. package/dist/listeners-MNAnpZj-.d.mts +372 -0
  38. package/dist/listeners-PZI7iT85.d.ts +372 -0
  39. package/dist/listeners-QJeEtLbV.d.ts +372 -0
  40. package/dist/listeners-hp0Ib2Ox.d.ts +372 -0
  41. package/dist/listeners-jLwetUnx.d.mts +372 -0
  42. package/dist/mcp.d.mts +7 -2
  43. package/dist/mcp.d.ts +7 -2
  44. package/dist/mcp.js +92 -1
  45. package/dist/mcp.mjs +1 -1
  46. package/dist/sdk-4OvXPr8E.d.mts +1054 -0
  47. package/dist/sdk-4OvXPr8E.d.ts +1054 -0
  48. package/dist/sdk-CJ9g5lFo.d.mts +772 -0
  49. package/dist/sdk-CJ9g5lFo.d.ts +772 -0
  50. package/dist/sdk-CN1ezZrI.d.mts +1054 -0
  51. package/dist/sdk-CN1ezZrI.d.ts +1054 -0
  52. package/dist/sdk-DD1OeGRJ.d.mts +871 -0
  53. package/dist/sdk-DD1OeGRJ.d.ts +871 -0
  54. package/dist/sdk-Xw0BjsLd.d.mts +1054 -0
  55. package/dist/sdk-Xw0BjsLd.d.ts +1054 -0
  56. package/dist/types-GnRAfH-h.d.mts +489 -0
  57. package/dist/types-GnRAfH-h.d.ts +489 -0
  58. package/package.json +17 -5
package/README.md CHANGED
@@ -339,6 +339,57 @@ try {
339
339
  }
340
340
  ```
341
341
 
342
+ ## Daemon
343
+
344
+ Run a local daemon with WebSocket (MCP) and OpenAI-compatible API endpoints:
345
+
346
+ ```bash
347
+ # Start the daemon
348
+ rainfall daemon start
349
+
350
+ # Start with custom ports
351
+ rainfall daemon start --port 8765 --openai-port 8787
352
+
353
+ # Check status
354
+ rainfall daemon status
355
+
356
+ # Stop the daemon
357
+ rainfall daemon stop
358
+ ```
359
+
360
+ ### Using the Daemon
361
+
362
+ **WebSocket (MCP) Endpoint:** `ws://localhost:8765`
363
+
364
+ **OpenAI-compatible API:** `http://localhost:8787/v1/chat/completions`
365
+
366
+ ```bash
367
+ # Test the OpenAI-compatible endpoint
368
+ curl http://localhost:8787/v1/chat/completions \
369
+ -H "Content-Type: application/json" \
370
+ -d '{
371
+ "model": "rainfall",
372
+ "messages": [{"role": "user", "content": "hello"}]
373
+ }'
374
+ ```
375
+
376
+ ### Programmatic Usage
377
+
378
+ ```typescript
379
+ import { RainfallDaemon } from '@rainfall-devkit/sdk/daemon';
380
+
381
+ const daemon = new RainfallDaemon({
382
+ port: 8765, // WebSocket/MCP port
383
+ openaiPort: 8787, // OpenAI-compatible API port
384
+ });
385
+
386
+ await daemon.start();
387
+ // Daemon is running with all Rainfall tools loaded
388
+
389
+ // Later...
390
+ await daemon.stop();
391
+ ```
392
+
342
393
  ## MCP Server
343
394
 
344
395
  Use Rainfall with Claude, Cursor, and other MCP-compatible assistants:
@@ -0,0 +1,662 @@
1
+ // src/services/networked.ts
2
+ var RainfallNetworkedExecutor = class {
3
+ rainfall;
4
+ options;
5
+ edgeNodeId;
6
+ jobCallbacks = /* @__PURE__ */ new Map();
7
+ resultPollingInterval;
8
+ constructor(rainfall, options = {}) {
9
+ this.rainfall = rainfall;
10
+ this.options = {
11
+ wsPort: 8765,
12
+ httpPort: 8787,
13
+ hostname: process.env.HOSTNAME || "local-daemon",
14
+ capabilities: {
15
+ localExec: true,
16
+ fileWatch: true,
17
+ passiveListen: true
18
+ },
19
+ ...options
20
+ };
21
+ }
22
+ /**
23
+ * Register this edge node with the Rainfall backend
24
+ */
25
+ async registerEdgeNode() {
26
+ const capabilities = this.buildCapabilitiesList();
27
+ try {
28
+ const result = await this.rainfall.executeTool("register-edge-node", {
29
+ hostname: this.options.hostname,
30
+ capabilities,
31
+ wsPort: this.options.wsPort,
32
+ httpPort: this.options.httpPort,
33
+ version: "0.1.0"
34
+ });
35
+ this.edgeNodeId = result.edgeNodeId;
36
+ console.log(`\u{1F310} Edge node registered with Rainfall as ${this.edgeNodeId}`);
37
+ return this.edgeNodeId;
38
+ } catch (error) {
39
+ this.edgeNodeId = `edge-${this.options.hostname}-${Date.now()}`;
40
+ console.log(`\u{1F310} Edge node running in local mode (ID: ${this.edgeNodeId})`);
41
+ return this.edgeNodeId;
42
+ }
43
+ }
44
+ /**
45
+ * Unregister this edge node on shutdown
46
+ */
47
+ async unregisterEdgeNode() {
48
+ if (!this.edgeNodeId) return;
49
+ try {
50
+ await this.rainfall.executeTool("unregister-edge-node", {
51
+ edgeNodeId: this.edgeNodeId
52
+ });
53
+ console.log(`\u{1F310} Edge node ${this.edgeNodeId} unregistered`);
54
+ } catch {
55
+ }
56
+ if (this.resultPollingInterval) {
57
+ clearInterval(this.resultPollingInterval);
58
+ }
59
+ }
60
+ /**
61
+ * Queue a tool execution for distributed processing
62
+ * Non-blocking - returns immediately with a job ID
63
+ */
64
+ async queueToolExecution(toolId, params, options = {}) {
65
+ const executionMode = options.executionMode || "any";
66
+ try {
67
+ const result = await this.rainfall.executeTool("queue-job", {
68
+ toolId,
69
+ params,
70
+ executionMode,
71
+ requesterEdgeNodeId: this.edgeNodeId
72
+ });
73
+ if (options.callback) {
74
+ this.jobCallbacks.set(result.jobId, options.callback);
75
+ this.startResultPolling();
76
+ }
77
+ return result.jobId;
78
+ } catch (error) {
79
+ if (executionMode === "local-only" || executionMode === "any") {
80
+ try {
81
+ const result = await this.rainfall.executeTool(toolId, params);
82
+ if (options.callback) {
83
+ options.callback(result);
84
+ }
85
+ return `local-${Date.now()}`;
86
+ } catch (execError) {
87
+ if (options.callback) {
88
+ options.callback(null, String(execError));
89
+ }
90
+ throw execError;
91
+ }
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ /**
97
+ * Get status of a queued job
98
+ */
99
+ async getJobStatus(jobId) {
100
+ try {
101
+ const result = await this.rainfall.executeTool("get-job-status", {
102
+ jobId
103
+ });
104
+ return result.job;
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+ /**
110
+ * Subscribe to job results via polling (WebSocket fallback)
111
+ * In the future, this will use WebSocket push from ApresMoi
112
+ */
113
+ async subscribeToResults(callback) {
114
+ console.log("\u{1F4E1} Subscribed to job results via Rainfall (polling mode)");
115
+ this.onResultReceived = callback;
116
+ }
117
+ onResultReceived;
118
+ /**
119
+ * Start polling for job results (fallback until WebSocket push is ready)
120
+ */
121
+ startResultPolling() {
122
+ if (this.resultPollingInterval) return;
123
+ this.resultPollingInterval = setInterval(async () => {
124
+ for (const [jobId, callback] of this.jobCallbacks) {
125
+ try {
126
+ const job = await this.getJobStatus(jobId);
127
+ if (job?.status === "completed" || job?.status === "failed") {
128
+ callback(job.result, job.error);
129
+ this.jobCallbacks.delete(jobId);
130
+ if (this.onResultReceived) {
131
+ this.onResultReceived(jobId, job.result, job.error);
132
+ }
133
+ }
134
+ } catch {
135
+ }
136
+ }
137
+ if (this.jobCallbacks.size === 0 && this.resultPollingInterval) {
138
+ clearInterval(this.resultPollingInterval);
139
+ this.resultPollingInterval = void 0;
140
+ }
141
+ }, 2e3);
142
+ }
143
+ /**
144
+ * Claim a job for execution on this edge node
145
+ */
146
+ async claimJob() {
147
+ try {
148
+ const result = await this.rainfall.executeTool("claim-job", {
149
+ edgeNodeId: this.edgeNodeId,
150
+ capabilities: this.buildCapabilitiesList()
151
+ });
152
+ return result.job;
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * Submit job result after execution
159
+ */
160
+ async submitJobResult(jobId, result, error) {
161
+ try {
162
+ await this.rainfall.executeTool("submit-job-result", {
163
+ jobId,
164
+ edgeNodeId: this.edgeNodeId,
165
+ result,
166
+ error
167
+ });
168
+ } catch {
169
+ }
170
+ }
171
+ /**
172
+ * Get this edge node's ID
173
+ */
174
+ getEdgeNodeId() {
175
+ return this.edgeNodeId;
176
+ }
177
+ /**
178
+ * Build capabilities list from options
179
+ */
180
+ buildCapabilitiesList() {
181
+ const caps = this.options.capabilities || {};
182
+ const list = [];
183
+ if (caps.localExec) list.push("local-exec");
184
+ if (caps.fileWatch) list.push("file-watch");
185
+ if (caps.passiveListen) list.push("passive-listen");
186
+ if (caps.browser) list.push("browser");
187
+ if (caps.custom) list.push(...caps.custom);
188
+ return list;
189
+ }
190
+ };
191
+
192
+ // src/services/context.ts
193
+ var RainfallDaemonContext = class {
194
+ rainfall;
195
+ options;
196
+ localMemories = /* @__PURE__ */ new Map();
197
+ sessions = /* @__PURE__ */ new Map();
198
+ executionHistory = [];
199
+ currentSessionId;
200
+ constructor(rainfall, options = {}) {
201
+ this.rainfall = rainfall;
202
+ this.options = {
203
+ maxLocalMemories: 1e3,
204
+ maxMessageHistory: 100,
205
+ maxExecutionHistory: 500,
206
+ sessionTtl: 24 * 60 * 60 * 1e3,
207
+ // 24 hours
208
+ ...options
209
+ };
210
+ }
211
+ /**
212
+ * Initialize the context - load recent memories from cloud
213
+ */
214
+ async initialize() {
215
+ try {
216
+ const recentMemories = await this.rainfall.memory.recall({
217
+ query: "daemon:context",
218
+ topK: this.options.maxLocalMemories
219
+ });
220
+ for (const memory of recentMemories) {
221
+ this.localMemories.set(memory.id, {
222
+ id: memory.id,
223
+ content: memory.content,
224
+ keywords: memory.keywords || [],
225
+ timestamp: memory.timestamp,
226
+ source: memory.source,
227
+ metadata: memory.metadata
228
+ });
229
+ }
230
+ console.log(`\u{1F9E0} Loaded ${this.localMemories.size} memories into context`);
231
+ } catch (error) {
232
+ console.warn("\u26A0\uFE0F Could not sync memories:", error instanceof Error ? error.message : error);
233
+ }
234
+ }
235
+ /**
236
+ * Create or get a session
237
+ */
238
+ getSession(sessionId) {
239
+ if (sessionId && this.sessions.has(sessionId)) {
240
+ const session = this.sessions.get(sessionId);
241
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
242
+ return session;
243
+ }
244
+ const newSession = {
245
+ id: sessionId || `session-${Date.now()}`,
246
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
247
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
248
+ variables: {},
249
+ messageHistory: []
250
+ };
251
+ this.sessions.set(newSession.id, newSession);
252
+ this.currentSessionId = newSession.id;
253
+ return newSession;
254
+ }
255
+ /**
256
+ * Get the current active session
257
+ */
258
+ getCurrentSession() {
259
+ if (this.currentSessionId) {
260
+ return this.sessions.get(this.currentSessionId);
261
+ }
262
+ return void 0;
263
+ }
264
+ /**
265
+ * Set the current active session
266
+ */
267
+ setCurrentSession(sessionId) {
268
+ if (this.sessions.has(sessionId)) {
269
+ this.currentSessionId = sessionId;
270
+ }
271
+ }
272
+ /**
273
+ * Add a message to the current session history
274
+ */
275
+ addMessage(role, content) {
276
+ const session = this.getCurrentSession();
277
+ if (!session) return;
278
+ session.messageHistory.push({
279
+ role,
280
+ content,
281
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
282
+ });
283
+ if (session.messageHistory.length > this.options.maxMessageHistory) {
284
+ session.messageHistory = session.messageHistory.slice(-this.options.maxMessageHistory);
285
+ }
286
+ }
287
+ /**
288
+ * Store a memory (local + cloud sync)
289
+ */
290
+ async storeMemory(content, options = {}) {
291
+ const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2)}`;
292
+ const entry = {
293
+ id,
294
+ content,
295
+ keywords: options.keywords || [],
296
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
297
+ source: options.source || "daemon",
298
+ metadata: options.metadata
299
+ };
300
+ this.localMemories.set(id, entry);
301
+ try {
302
+ await this.rainfall.memory.create({
303
+ content,
304
+ keywords: [...options.keywords || [], "daemon:context"],
305
+ metadata: {
306
+ ...options.metadata,
307
+ daemonMemoryId: id,
308
+ source: options.source || "daemon"
309
+ }
310
+ });
311
+ } catch (error) {
312
+ console.warn("\u26A0\uFE0F Could not sync memory to cloud:", error instanceof Error ? error.message : error);
313
+ }
314
+ this.trimLocalMemories();
315
+ return id;
316
+ }
317
+ /**
318
+ * Recall memories by query
319
+ */
320
+ async recallMemories(query, topK = 5) {
321
+ const localResults = Array.from(this.localMemories.values()).filter(
322
+ (m) => m.content.toLowerCase().includes(query.toLowerCase()) || m.keywords.some((k) => k.toLowerCase().includes(query.toLowerCase()))
323
+ ).slice(0, topK);
324
+ try {
325
+ const cloudResults = await this.rainfall.memory.recall({ query, topK });
326
+ const seen = new Set(localResults.map((r) => r.id));
327
+ for (const mem of cloudResults) {
328
+ if (!seen.has(mem.id)) {
329
+ localResults.push({
330
+ id: mem.id,
331
+ content: mem.content,
332
+ keywords: mem.keywords || [],
333
+ timestamp: mem.timestamp,
334
+ source: mem.source,
335
+ metadata: mem.metadata
336
+ });
337
+ }
338
+ }
339
+ } catch {
340
+ }
341
+ return localResults.slice(0, topK);
342
+ }
343
+ /**
344
+ * Set a session variable
345
+ */
346
+ setVariable(key, value) {
347
+ const session = this.getCurrentSession();
348
+ if (session) {
349
+ session.variables[key] = value;
350
+ }
351
+ }
352
+ /**
353
+ * Get a session variable
354
+ */
355
+ getVariable(key) {
356
+ const session = this.getCurrentSession();
357
+ return session?.variables[key];
358
+ }
359
+ /**
360
+ * Record a tool execution
361
+ */
362
+ recordExecution(toolId, params, result, options = { duration: 0 }) {
363
+ const record = {
364
+ id: `exec-${Date.now()}-${Math.random().toString(36).slice(2)}`,
365
+ toolId,
366
+ params,
367
+ result,
368
+ error: options.error,
369
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
370
+ duration: options.duration,
371
+ edgeNodeId: options.edgeNodeId
372
+ };
373
+ this.executionHistory.push(record);
374
+ if (this.executionHistory.length > this.options.maxExecutionHistory) {
375
+ this.executionHistory = this.executionHistory.slice(-this.options.maxExecutionHistory);
376
+ }
377
+ }
378
+ /**
379
+ * Get recent execution history
380
+ */
381
+ getExecutionHistory(limit = 10) {
382
+ return this.executionHistory.slice(-limit).reverse();
383
+ }
384
+ /**
385
+ * Get execution statistics
386
+ */
387
+ getExecutionStats() {
388
+ const stats = {
389
+ total: this.executionHistory.length,
390
+ successful: 0,
391
+ failed: 0,
392
+ averageDuration: 0,
393
+ byTool: {}
394
+ };
395
+ let totalDuration = 0;
396
+ for (const exec of this.executionHistory) {
397
+ if (exec.error) {
398
+ stats.failed++;
399
+ } else {
400
+ stats.successful++;
401
+ }
402
+ totalDuration += exec.duration;
403
+ stats.byTool[exec.toolId] = (stats.byTool[exec.toolId] || 0) + 1;
404
+ }
405
+ stats.averageDuration = stats.total > 0 ? totalDuration / stats.total : 0;
406
+ return stats;
407
+ }
408
+ /**
409
+ * Clear old sessions based on TTL
410
+ */
411
+ cleanupSessions() {
412
+ const now = Date.now();
413
+ const ttl = this.options.sessionTtl;
414
+ for (const [id, session] of this.sessions) {
415
+ const lastActivity = new Date(session.lastActivity).getTime();
416
+ if (now - lastActivity > ttl) {
417
+ this.sessions.delete(id);
418
+ if (this.currentSessionId === id) {
419
+ this.currentSessionId = void 0;
420
+ }
421
+ }
422
+ }
423
+ }
424
+ /**
425
+ * Get context summary for debugging
426
+ */
427
+ getStatus() {
428
+ return {
429
+ memoriesCached: this.localMemories.size,
430
+ activeSessions: this.sessions.size,
431
+ currentSession: this.currentSessionId,
432
+ executionHistorySize: this.executionHistory.length
433
+ };
434
+ }
435
+ trimLocalMemories() {
436
+ if (this.localMemories.size <= this.options.maxLocalMemories) return;
437
+ const entries = Array.from(this.localMemories.entries()).sort((a, b) => new Date(a[1].timestamp).getTime() - new Date(b[1].timestamp).getTime());
438
+ const toRemove = entries.slice(0, entries.length - this.options.maxLocalMemories);
439
+ for (const [id] of toRemove) {
440
+ this.localMemories.delete(id);
441
+ }
442
+ }
443
+ };
444
+
445
+ // src/services/listeners.ts
446
+ var RainfallListenerRegistry = class {
447
+ rainfall;
448
+ context;
449
+ executor;
450
+ watchers = /* @__PURE__ */ new Map();
451
+ cronIntervals = /* @__PURE__ */ new Map();
452
+ eventHistory = [];
453
+ maxEventHistory = 100;
454
+ constructor(rainfall, context, executor) {
455
+ this.rainfall = rainfall;
456
+ this.context = context;
457
+ this.executor = executor;
458
+ }
459
+ /**
460
+ * Register a file watcher
461
+ * Note: Actual file watching requires fs.watch or chokidar
462
+ * This is the registry - actual watching is done by the daemon
463
+ */
464
+ async registerFileWatcher(config) {
465
+ console.log(`\u{1F441}\uFE0F Registering file watcher: ${config.name} (${config.watchPath})`);
466
+ const existing = Array.from(this.watchers.keys());
467
+ if (existing.includes(config.id)) {
468
+ await this.unregisterFileWatcher(config.id);
469
+ }
470
+ this.watchers.set(config.id, {
471
+ stop: () => {
472
+ console.log(`\u{1F441}\uFE0F Stopped file watcher: ${config.name}`);
473
+ }
474
+ });
475
+ await this.context.storeMemory(`File watcher registered: ${config.name}`, {
476
+ keywords: ["listener", "file-watcher", config.name],
477
+ metadata: { config }
478
+ });
479
+ }
480
+ /**
481
+ * Unregister a file watcher
482
+ */
483
+ async unregisterFileWatcher(id) {
484
+ const watcher = this.watchers.get(id);
485
+ if (watcher) {
486
+ watcher.stop();
487
+ this.watchers.delete(id);
488
+ }
489
+ }
490
+ /**
491
+ * Register a cron trigger
492
+ */
493
+ async registerCronTrigger(config) {
494
+ console.log(`\u23F0 Registering cron trigger: ${config.name} (${config.cron})`);
495
+ if (this.cronIntervals.has(config.id)) {
496
+ clearInterval(this.cronIntervals.get(config.id));
497
+ this.cronIntervals.delete(config.id);
498
+ }
499
+ const interval = this.parseCronToMs(config.cron);
500
+ if (interval) {
501
+ const intervalId = setInterval(async () => {
502
+ await this.handleCronTick(config);
503
+ }, interval);
504
+ this.cronIntervals.set(config.id, intervalId);
505
+ }
506
+ await this.context.storeMemory(`Cron trigger registered: ${config.name}`, {
507
+ keywords: ["listener", "cron", config.name],
508
+ metadata: { config }
509
+ });
510
+ }
511
+ /**
512
+ * Unregister a cron trigger
513
+ */
514
+ unregisterCronTrigger(id) {
515
+ const interval = this.cronIntervals.get(id);
516
+ if (interval) {
517
+ clearInterval(interval);
518
+ this.cronIntervals.delete(id);
519
+ }
520
+ }
521
+ /**
522
+ * Handle a file event
523
+ */
524
+ async handleFileEvent(watcherId, eventType, filePath) {
525
+ const event = {
526
+ id: `evt-${Date.now()}`,
527
+ type: "file",
528
+ source: watcherId,
529
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
530
+ data: { eventType, filePath }
531
+ };
532
+ this.recordEvent(event);
533
+ console.log(`\u{1F4C1} File event: ${eventType} ${filePath}`);
534
+ }
535
+ /**
536
+ * Handle a cron tick
537
+ */
538
+ async handleCronTick(config) {
539
+ const event = {
540
+ id: `evt-${Date.now()}`,
541
+ type: "cron",
542
+ source: config.id,
543
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
544
+ data: { cron: config.cron }
545
+ };
546
+ this.recordEvent(event);
547
+ console.log(`\u23F0 Cron tick: ${config.name}`);
548
+ for (const step of config.workflow) {
549
+ try {
550
+ await this.executor.queueToolExecution(step.toolId, {
551
+ ...step.params,
552
+ _event: event
553
+ });
554
+ } catch (error) {
555
+ console.error(`\u274C Workflow step failed: ${step.toolId}`, error);
556
+ }
557
+ }
558
+ }
559
+ /**
560
+ * Trigger a manual event (for testing or programmatic triggers)
561
+ */
562
+ async triggerManual(name, data = {}) {
563
+ const event = {
564
+ id: `evt-${Date.now()}`,
565
+ type: "manual",
566
+ source: name,
567
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
568
+ data
569
+ };
570
+ this.recordEvent(event);
571
+ console.log(`\u{1F446} Manual trigger: ${name}`);
572
+ await this.context.storeMemory(`Manual trigger fired: ${name}`, {
573
+ keywords: ["trigger", "manual", name],
574
+ metadata: { event }
575
+ });
576
+ }
577
+ /**
578
+ * Get recent events
579
+ */
580
+ getRecentEvents(limit = 10) {
581
+ return this.eventHistory.slice(-limit).reverse();
582
+ }
583
+ /**
584
+ * Get active listeners status
585
+ */
586
+ getStatus() {
587
+ return {
588
+ fileWatchers: this.watchers.size,
589
+ cronTriggers: this.cronIntervals.size,
590
+ recentEvents: this.eventHistory.length
591
+ };
592
+ }
593
+ /**
594
+ * Stop all listeners
595
+ */
596
+ async stopAll() {
597
+ for (const [id] of this.watchers) {
598
+ await this.unregisterFileWatcher(id);
599
+ }
600
+ for (const [id] of this.cronIntervals) {
601
+ this.unregisterCronTrigger(id);
602
+ }
603
+ console.log("\u{1F6D1} All listeners stopped");
604
+ }
605
+ recordEvent(event) {
606
+ this.eventHistory.push(event);
607
+ if (this.eventHistory.length > this.maxEventHistory) {
608
+ this.eventHistory = this.eventHistory.slice(-this.maxEventHistory);
609
+ }
610
+ }
611
+ /**
612
+ * Simple cron parser - converts basic cron expressions to milliseconds
613
+ * Supports: @hourly, @daily, @weekly, and simple intervals like every N minutes
614
+ */
615
+ parseCronToMs(cron) {
616
+ switch (cron) {
617
+ case "@hourly":
618
+ return 60 * 60 * 1e3;
619
+ case "@daily":
620
+ return 24 * 60 * 60 * 1e3;
621
+ case "@weekly":
622
+ return 7 * 24 * 60 * 60 * 1e3;
623
+ case "@minutely":
624
+ return 60 * 1e3;
625
+ }
626
+ const match = cron.match(/^\*\/(\d+)\s/);
627
+ if (match) {
628
+ const minutes = parseInt(match[1], 10);
629
+ if (minutes > 0 && minutes <= 60) {
630
+ return minutes * 60 * 1e3;
631
+ }
632
+ }
633
+ console.warn(`\u26A0\uFE0F Unrecognized cron pattern "${cron}", using 1 minute interval`);
634
+ return 60 * 1e3;
635
+ }
636
+ };
637
+ function createFileWatcherWorkflow(name, watchPath, options) {
638
+ return {
639
+ id: `fw-${Date.now()}-${Math.random().toString(36).slice(2)}`,
640
+ name,
641
+ watchPath,
642
+ pattern: options.pattern,
643
+ events: options.events || ["create"],
644
+ workflow: options.workflow
645
+ };
646
+ }
647
+ function createCronWorkflow(name, cron, workflow) {
648
+ return {
649
+ id: `cron-${Date.now()}-${Math.random().toString(36).slice(2)}`,
650
+ name,
651
+ cron,
652
+ workflow
653
+ };
654
+ }
655
+
656
+ export {
657
+ RainfallNetworkedExecutor,
658
+ RainfallDaemonContext,
659
+ RainfallListenerRegistry,
660
+ createFileWatcherWorkflow,
661
+ createCronWorkflow
662
+ };