@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/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,23 +17,40 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
23
33
  AuthenticationError: () => AuthenticationError,
34
+ EdgeNodeSecurity: () => EdgeNodeSecurity,
24
35
  NetworkError: () => NetworkError,
25
36
  NotFoundError: () => NotFoundError,
26
37
  Rainfall: () => Rainfall,
27
38
  RainfallClient: () => RainfallClient,
39
+ RainfallDaemonContext: () => RainfallDaemonContext,
28
40
  RainfallError: () => RainfallError,
41
+ RainfallListenerRegistry: () => RainfallListenerRegistry,
42
+ RainfallNetworkedExecutor: () => RainfallNetworkedExecutor,
29
43
  RateLimitError: () => RateLimitError,
44
+ SecureEdgeClient: () => SecureEdgeClient,
30
45
  ServerError: () => ServerError,
31
46
  TimeoutError: () => TimeoutError,
32
47
  ToolNotFoundError: () => ToolNotFoundError,
33
48
  VERSION: () => VERSION,
34
- ValidationError: () => ValidationError
49
+ ValidationError: () => ValidationError,
50
+ createCronWorkflow: () => createCronWorkflow,
51
+ createEdgeNodeSecurity: () => createEdgeNodeSecurity,
52
+ createFileWatcherWorkflow: () => createFileWatcherWorkflow,
53
+ createSecureEdgeClient: () => createSecureEdgeClient
35
54
  });
36
55
  module.exports = __toCommonJS(index_exports);
37
56
 
@@ -348,6 +367,49 @@ var RainfallClient = class {
348
367
  sleep(ms) {
349
368
  return new Promise((resolve) => setTimeout(resolve, ms));
350
369
  }
370
+ /**
371
+ * OpenAI-compatible chat completions with tool support
372
+ */
373
+ async chatCompletions(params) {
374
+ const { subscriber_id, ...body } = params;
375
+ if (body.stream) {
376
+ const url = `${this.baseUrl}/olympic/subscribers/${subscriber_id}/v1/chat/completions`;
377
+ const response = await fetch(url, {
378
+ method: "POST",
379
+ headers: {
380
+ "x-api-key": this.apiKey,
381
+ "Content-Type": "application/json",
382
+ "Accept": "text/event-stream"
383
+ },
384
+ body: JSON.stringify(body)
385
+ });
386
+ if (!response.ok) {
387
+ const error = await response.text();
388
+ throw new RainfallError(`Chat completions failed: ${error}`, "CHAT_ERROR");
389
+ }
390
+ if (!response.body) {
391
+ throw new RainfallError("No response body", "CHAT_ERROR");
392
+ }
393
+ return response.body;
394
+ }
395
+ return this.request(
396
+ `/olympic/subscribers/${subscriber_id}/v1/chat/completions`,
397
+ {
398
+ method: "POST",
399
+ body
400
+ }
401
+ );
402
+ }
403
+ /**
404
+ * List available models (OpenAI-compatible format)
405
+ */
406
+ async listModels(subscriberId) {
407
+ const sid = subscriberId || this.subscriberId || await this.ensureSubscriberId();
408
+ const result = await this.request(
409
+ `/olympic/subscribers/${sid}/v1/models`
410
+ );
411
+ return result.data || [];
412
+ }
351
413
  };
352
414
 
353
415
  // src/namespaces/integrations.ts
@@ -517,7 +579,12 @@ function createAI(client) {
517
579
  chat: (params) => client.executeTool("xai-chat-completions", params),
518
580
  complete: (params) => client.executeTool("fim", params),
519
581
  classify: (params) => client.executeTool("jina-document-classifier", params),
520
- segment: (params) => client.executeTool("jina-text-segmenter", params)
582
+ segment: (params) => client.executeTool("jina-text-segmenter", params),
583
+ /**
584
+ * OpenAI-compatible chat completions with full tool support
585
+ * This is the recommended method for multi-turn conversations with tools
586
+ */
587
+ chatCompletions: (params) => client.chatCompletions(params)
521
588
  };
522
589
  }
523
590
 
@@ -797,22 +864,1199 @@ var Rainfall = class {
797
864
  getRateLimitInfo() {
798
865
  return this.client.getRateLimitInfo();
799
866
  }
867
+ /**
868
+ * OpenAI-compatible chat completions with tool support
869
+ *
870
+ * @example
871
+ * ```typescript
872
+ * // Simple chat
873
+ * const response = await rainfall.chatCompletions({
874
+ * subscriber_id: 'my-subscriber',
875
+ * messages: [{ role: 'user', content: 'Hello!' }],
876
+ * model: 'llama-3.3-70b-versatile'
877
+ * });
878
+ *
879
+ * // With tools
880
+ * const response = await rainfall.chatCompletions({
881
+ * subscriber_id: 'my-subscriber',
882
+ * messages: [{ role: 'user', content: 'Search for AI news' }],
883
+ * tools: [{ type: 'function', function: { name: 'web-search' } }],
884
+ * enable_stacked: true
885
+ * });
886
+ *
887
+ * // Streaming
888
+ * const stream = await rainfall.chatCompletions({
889
+ * subscriber_id: 'my-subscriber',
890
+ * messages: [{ role: 'user', content: 'Tell me a story' }],
891
+ * stream: true
892
+ * });
893
+ * ```
894
+ */
895
+ async chatCompletions(params) {
896
+ return this.client.chatCompletions(params);
897
+ }
898
+ /**
899
+ * List available models (OpenAI-compatible format)
900
+ *
901
+ * @example
902
+ * ```typescript
903
+ * const models = await rainfall.listModels();
904
+ * console.log(models); // [{ id: 'llama-3.3-70b-versatile', ... }]
905
+ * ```
906
+ */
907
+ async listModels(subscriberId) {
908
+ return this.client.listModels(subscriberId);
909
+ }
800
910
  };
801
911
 
912
+ // src/services/networked.ts
913
+ var RainfallNetworkedExecutor = class {
914
+ rainfall;
915
+ options;
916
+ edgeNodeId;
917
+ jobCallbacks = /* @__PURE__ */ new Map();
918
+ resultPollingInterval;
919
+ constructor(rainfall, options = {}) {
920
+ this.rainfall = rainfall;
921
+ this.options = {
922
+ wsPort: 8765,
923
+ httpPort: 8787,
924
+ hostname: process.env.HOSTNAME || "local-daemon",
925
+ capabilities: {
926
+ localExec: true,
927
+ fileWatch: true,
928
+ passiveListen: true
929
+ },
930
+ ...options
931
+ };
932
+ }
933
+ /**
934
+ * Register this edge node with the Rainfall backend
935
+ */
936
+ async registerEdgeNode() {
937
+ const capabilities = this.buildCapabilitiesList();
938
+ try {
939
+ const result = await this.rainfall.executeTool("register-edge-node", {
940
+ hostname: this.options.hostname,
941
+ capabilities,
942
+ wsPort: this.options.wsPort,
943
+ httpPort: this.options.httpPort,
944
+ version: "0.1.0"
945
+ });
946
+ this.edgeNodeId = result.edgeNodeId;
947
+ console.log(`\u{1F310} Edge node registered with Rainfall as ${this.edgeNodeId}`);
948
+ return this.edgeNodeId;
949
+ } catch (error) {
950
+ this.edgeNodeId = `edge-${this.options.hostname}-${Date.now()}`;
951
+ console.log(`\u{1F310} Edge node running in local mode (ID: ${this.edgeNodeId})`);
952
+ return this.edgeNodeId;
953
+ }
954
+ }
955
+ /**
956
+ * Unregister this edge node on shutdown
957
+ */
958
+ async unregisterEdgeNode() {
959
+ if (!this.edgeNodeId) return;
960
+ try {
961
+ await this.rainfall.executeTool("unregister-edge-node", {
962
+ edgeNodeId: this.edgeNodeId
963
+ });
964
+ console.log(`\u{1F310} Edge node ${this.edgeNodeId} unregistered`);
965
+ } catch {
966
+ }
967
+ if (this.resultPollingInterval) {
968
+ clearInterval(this.resultPollingInterval);
969
+ }
970
+ }
971
+ /**
972
+ * Queue a tool execution for distributed processing
973
+ * Non-blocking - returns immediately with a job ID
974
+ */
975
+ async queueToolExecution(toolId, params, options = {}) {
976
+ const executionMode = options.executionMode || "any";
977
+ try {
978
+ const result = await this.rainfall.executeTool("queue-job", {
979
+ toolId,
980
+ params,
981
+ executionMode,
982
+ requesterEdgeNodeId: this.edgeNodeId
983
+ });
984
+ if (options.callback) {
985
+ this.jobCallbacks.set(result.jobId, options.callback);
986
+ this.startResultPolling();
987
+ }
988
+ return result.jobId;
989
+ } catch (error) {
990
+ if (executionMode === "local-only" || executionMode === "any") {
991
+ try {
992
+ const result = await this.rainfall.executeTool(toolId, params);
993
+ if (options.callback) {
994
+ options.callback(result);
995
+ }
996
+ return `local-${Date.now()}`;
997
+ } catch (execError) {
998
+ if (options.callback) {
999
+ options.callback(null, String(execError));
1000
+ }
1001
+ throw execError;
1002
+ }
1003
+ }
1004
+ throw error;
1005
+ }
1006
+ }
1007
+ /**
1008
+ * Get status of a queued job
1009
+ */
1010
+ async getJobStatus(jobId) {
1011
+ try {
1012
+ const result = await this.rainfall.executeTool("get-job-status", {
1013
+ jobId
1014
+ });
1015
+ return result.job;
1016
+ } catch {
1017
+ return null;
1018
+ }
1019
+ }
1020
+ /**
1021
+ * Subscribe to job results via polling (WebSocket fallback)
1022
+ * In the future, this will use WebSocket push from ApresMoi
1023
+ */
1024
+ async subscribeToResults(callback) {
1025
+ console.log("\u{1F4E1} Subscribed to job results via Rainfall (polling mode)");
1026
+ this.onResultReceived = callback;
1027
+ }
1028
+ onResultReceived;
1029
+ /**
1030
+ * Start polling for job results (fallback until WebSocket push is ready)
1031
+ */
1032
+ startResultPolling() {
1033
+ if (this.resultPollingInterval) return;
1034
+ this.resultPollingInterval = setInterval(async () => {
1035
+ for (const [jobId, callback] of this.jobCallbacks) {
1036
+ try {
1037
+ const job = await this.getJobStatus(jobId);
1038
+ if (job?.status === "completed" || job?.status === "failed") {
1039
+ callback(job.result, job.error);
1040
+ this.jobCallbacks.delete(jobId);
1041
+ if (this.onResultReceived) {
1042
+ this.onResultReceived(jobId, job.result, job.error);
1043
+ }
1044
+ }
1045
+ } catch {
1046
+ }
1047
+ }
1048
+ if (this.jobCallbacks.size === 0 && this.resultPollingInterval) {
1049
+ clearInterval(this.resultPollingInterval);
1050
+ this.resultPollingInterval = void 0;
1051
+ }
1052
+ }, 2e3);
1053
+ }
1054
+ /**
1055
+ * Claim a job for execution on this edge node
1056
+ */
1057
+ async claimJob() {
1058
+ try {
1059
+ const result = await this.rainfall.executeTool("claim-job", {
1060
+ edgeNodeId: this.edgeNodeId,
1061
+ capabilities: this.buildCapabilitiesList()
1062
+ });
1063
+ return result.job;
1064
+ } catch {
1065
+ return null;
1066
+ }
1067
+ }
1068
+ /**
1069
+ * Submit job result after execution
1070
+ */
1071
+ async submitJobResult(jobId, result, error) {
1072
+ try {
1073
+ await this.rainfall.executeTool("submit-job-result", {
1074
+ jobId,
1075
+ edgeNodeId: this.edgeNodeId,
1076
+ result,
1077
+ error
1078
+ });
1079
+ } catch {
1080
+ }
1081
+ }
1082
+ /**
1083
+ * Get this edge node's ID
1084
+ */
1085
+ getEdgeNodeId() {
1086
+ return this.edgeNodeId;
1087
+ }
1088
+ /**
1089
+ * Build capabilities list from options
1090
+ */
1091
+ buildCapabilitiesList() {
1092
+ const caps = this.options.capabilities || {};
1093
+ const list = [];
1094
+ if (caps.localExec) list.push("local-exec");
1095
+ if (caps.fileWatch) list.push("file-watch");
1096
+ if (caps.passiveListen) list.push("passive-listen");
1097
+ if (caps.browser) list.push("browser");
1098
+ if (caps.custom) list.push(...caps.custom);
1099
+ return list;
1100
+ }
1101
+ };
1102
+
1103
+ // src/services/context.ts
1104
+ var RainfallDaemonContext = class {
1105
+ rainfall;
1106
+ options;
1107
+ localMemories = /* @__PURE__ */ new Map();
1108
+ sessions = /* @__PURE__ */ new Map();
1109
+ executionHistory = [];
1110
+ currentSessionId;
1111
+ constructor(rainfall, options = {}) {
1112
+ this.rainfall = rainfall;
1113
+ this.options = {
1114
+ maxLocalMemories: 1e3,
1115
+ maxMessageHistory: 100,
1116
+ maxExecutionHistory: 500,
1117
+ sessionTtl: 24 * 60 * 60 * 1e3,
1118
+ // 24 hours
1119
+ ...options
1120
+ };
1121
+ }
1122
+ /**
1123
+ * Initialize the context - load recent memories from cloud
1124
+ */
1125
+ async initialize() {
1126
+ try {
1127
+ const recentMemories = await this.rainfall.memory.recall({
1128
+ query: "daemon:context",
1129
+ topK: this.options.maxLocalMemories
1130
+ });
1131
+ for (const memory of recentMemories) {
1132
+ this.localMemories.set(memory.id, {
1133
+ id: memory.id,
1134
+ content: memory.content,
1135
+ keywords: memory.keywords || [],
1136
+ timestamp: memory.timestamp,
1137
+ source: memory.source,
1138
+ metadata: memory.metadata
1139
+ });
1140
+ }
1141
+ console.log(`\u{1F9E0} Loaded ${this.localMemories.size} memories into context`);
1142
+ } catch (error) {
1143
+ console.warn("\u26A0\uFE0F Could not sync memories:", error instanceof Error ? error.message : error);
1144
+ }
1145
+ }
1146
+ /**
1147
+ * Create or get a session
1148
+ */
1149
+ getSession(sessionId) {
1150
+ if (sessionId && this.sessions.has(sessionId)) {
1151
+ const session = this.sessions.get(sessionId);
1152
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1153
+ return session;
1154
+ }
1155
+ const newSession = {
1156
+ id: sessionId || `session-${Date.now()}`,
1157
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1158
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
1159
+ variables: {},
1160
+ messageHistory: []
1161
+ };
1162
+ this.sessions.set(newSession.id, newSession);
1163
+ this.currentSessionId = newSession.id;
1164
+ return newSession;
1165
+ }
1166
+ /**
1167
+ * Get the current active session
1168
+ */
1169
+ getCurrentSession() {
1170
+ if (this.currentSessionId) {
1171
+ return this.sessions.get(this.currentSessionId);
1172
+ }
1173
+ return void 0;
1174
+ }
1175
+ /**
1176
+ * Set the current active session
1177
+ */
1178
+ setCurrentSession(sessionId) {
1179
+ if (this.sessions.has(sessionId)) {
1180
+ this.currentSessionId = sessionId;
1181
+ }
1182
+ }
1183
+ /**
1184
+ * Add a message to the current session history
1185
+ */
1186
+ addMessage(role, content) {
1187
+ const session = this.getCurrentSession();
1188
+ if (!session) return;
1189
+ session.messageHistory.push({
1190
+ role,
1191
+ content,
1192
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1193
+ });
1194
+ if (session.messageHistory.length > this.options.maxMessageHistory) {
1195
+ session.messageHistory = session.messageHistory.slice(-this.options.maxMessageHistory);
1196
+ }
1197
+ }
1198
+ /**
1199
+ * Store a memory (local + cloud sync)
1200
+ */
1201
+ async storeMemory(content, options = {}) {
1202
+ const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1203
+ const entry = {
1204
+ id,
1205
+ content,
1206
+ keywords: options.keywords || [],
1207
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1208
+ source: options.source || "daemon",
1209
+ metadata: options.metadata
1210
+ };
1211
+ this.localMemories.set(id, entry);
1212
+ try {
1213
+ await this.rainfall.memory.create({
1214
+ content,
1215
+ keywords: [...options.keywords || [], "daemon:context"],
1216
+ metadata: {
1217
+ ...options.metadata,
1218
+ daemonMemoryId: id,
1219
+ source: options.source || "daemon"
1220
+ }
1221
+ });
1222
+ } catch (error) {
1223
+ console.warn("\u26A0\uFE0F Could not sync memory to cloud:", error instanceof Error ? error.message : error);
1224
+ }
1225
+ this.trimLocalMemories();
1226
+ return id;
1227
+ }
1228
+ /**
1229
+ * Recall memories by query
1230
+ */
1231
+ async recallMemories(query, topK = 5) {
1232
+ const localResults = Array.from(this.localMemories.values()).filter(
1233
+ (m) => m.content.toLowerCase().includes(query.toLowerCase()) || m.keywords.some((k) => k.toLowerCase().includes(query.toLowerCase()))
1234
+ ).slice(0, topK);
1235
+ try {
1236
+ const cloudResults = await this.rainfall.memory.recall({ query, topK });
1237
+ const seen = new Set(localResults.map((r) => r.id));
1238
+ for (const mem of cloudResults) {
1239
+ if (!seen.has(mem.id)) {
1240
+ localResults.push({
1241
+ id: mem.id,
1242
+ content: mem.content,
1243
+ keywords: mem.keywords || [],
1244
+ timestamp: mem.timestamp,
1245
+ source: mem.source,
1246
+ metadata: mem.metadata
1247
+ });
1248
+ }
1249
+ }
1250
+ } catch {
1251
+ }
1252
+ return localResults.slice(0, topK);
1253
+ }
1254
+ /**
1255
+ * Set a session variable
1256
+ */
1257
+ setVariable(key, value) {
1258
+ const session = this.getCurrentSession();
1259
+ if (session) {
1260
+ session.variables[key] = value;
1261
+ }
1262
+ }
1263
+ /**
1264
+ * Get a session variable
1265
+ */
1266
+ getVariable(key) {
1267
+ const session = this.getCurrentSession();
1268
+ return session?.variables[key];
1269
+ }
1270
+ /**
1271
+ * Record a tool execution
1272
+ */
1273
+ recordExecution(toolId, params, result, options = { duration: 0 }) {
1274
+ const record = {
1275
+ id: `exec-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1276
+ toolId,
1277
+ params,
1278
+ result,
1279
+ error: options.error,
1280
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1281
+ duration: options.duration,
1282
+ edgeNodeId: options.edgeNodeId
1283
+ };
1284
+ this.executionHistory.push(record);
1285
+ if (this.executionHistory.length > this.options.maxExecutionHistory) {
1286
+ this.executionHistory = this.executionHistory.slice(-this.options.maxExecutionHistory);
1287
+ }
1288
+ }
1289
+ /**
1290
+ * Get recent execution history
1291
+ */
1292
+ getExecutionHistory(limit = 10) {
1293
+ return this.executionHistory.slice(-limit).reverse();
1294
+ }
1295
+ /**
1296
+ * Get execution statistics
1297
+ */
1298
+ getExecutionStats() {
1299
+ const stats = {
1300
+ total: this.executionHistory.length,
1301
+ successful: 0,
1302
+ failed: 0,
1303
+ averageDuration: 0,
1304
+ byTool: {}
1305
+ };
1306
+ let totalDuration = 0;
1307
+ for (const exec of this.executionHistory) {
1308
+ if (exec.error) {
1309
+ stats.failed++;
1310
+ } else {
1311
+ stats.successful++;
1312
+ }
1313
+ totalDuration += exec.duration;
1314
+ stats.byTool[exec.toolId] = (stats.byTool[exec.toolId] || 0) + 1;
1315
+ }
1316
+ stats.averageDuration = stats.total > 0 ? totalDuration / stats.total : 0;
1317
+ return stats;
1318
+ }
1319
+ /**
1320
+ * Clear old sessions based on TTL
1321
+ */
1322
+ cleanupSessions() {
1323
+ const now = Date.now();
1324
+ const ttl = this.options.sessionTtl;
1325
+ for (const [id, session] of this.sessions) {
1326
+ const lastActivity = new Date(session.lastActivity).getTime();
1327
+ if (now - lastActivity > ttl) {
1328
+ this.sessions.delete(id);
1329
+ if (this.currentSessionId === id) {
1330
+ this.currentSessionId = void 0;
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+ /**
1336
+ * Get context summary for debugging
1337
+ */
1338
+ getStatus() {
1339
+ return {
1340
+ memoriesCached: this.localMemories.size,
1341
+ activeSessions: this.sessions.size,
1342
+ currentSession: this.currentSessionId,
1343
+ executionHistorySize: this.executionHistory.length
1344
+ };
1345
+ }
1346
+ trimLocalMemories() {
1347
+ if (this.localMemories.size <= this.options.maxLocalMemories) return;
1348
+ const entries = Array.from(this.localMemories.entries()).sort((a, b) => new Date(a[1].timestamp).getTime() - new Date(b[1].timestamp).getTime());
1349
+ const toRemove = entries.slice(0, entries.length - this.options.maxLocalMemories);
1350
+ for (const [id] of toRemove) {
1351
+ this.localMemories.delete(id);
1352
+ }
1353
+ }
1354
+ };
1355
+
1356
+ // src/services/listeners.ts
1357
+ var RainfallListenerRegistry = class {
1358
+ rainfall;
1359
+ context;
1360
+ executor;
1361
+ watchers = /* @__PURE__ */ new Map();
1362
+ cronIntervals = /* @__PURE__ */ new Map();
1363
+ eventHistory = [];
1364
+ maxEventHistory = 100;
1365
+ constructor(rainfall, context, executor) {
1366
+ this.rainfall = rainfall;
1367
+ this.context = context;
1368
+ this.executor = executor;
1369
+ }
1370
+ /**
1371
+ * Register a file watcher
1372
+ * Note: Actual file watching requires fs.watch or chokidar
1373
+ * This is the registry - actual watching is done by the daemon
1374
+ */
1375
+ async registerFileWatcher(config) {
1376
+ console.log(`\u{1F441}\uFE0F Registering file watcher: ${config.name} (${config.watchPath})`);
1377
+ const existing = Array.from(this.watchers.keys());
1378
+ if (existing.includes(config.id)) {
1379
+ await this.unregisterFileWatcher(config.id);
1380
+ }
1381
+ this.watchers.set(config.id, {
1382
+ stop: () => {
1383
+ console.log(`\u{1F441}\uFE0F Stopped file watcher: ${config.name}`);
1384
+ }
1385
+ });
1386
+ await this.context.storeMemory(`File watcher registered: ${config.name}`, {
1387
+ keywords: ["listener", "file-watcher", config.name],
1388
+ metadata: { config }
1389
+ });
1390
+ }
1391
+ /**
1392
+ * Unregister a file watcher
1393
+ */
1394
+ async unregisterFileWatcher(id) {
1395
+ const watcher = this.watchers.get(id);
1396
+ if (watcher) {
1397
+ watcher.stop();
1398
+ this.watchers.delete(id);
1399
+ }
1400
+ }
1401
+ /**
1402
+ * Register a cron trigger
1403
+ */
1404
+ async registerCronTrigger(config) {
1405
+ console.log(`\u23F0 Registering cron trigger: ${config.name} (${config.cron})`);
1406
+ if (this.cronIntervals.has(config.id)) {
1407
+ clearInterval(this.cronIntervals.get(config.id));
1408
+ this.cronIntervals.delete(config.id);
1409
+ }
1410
+ const interval = this.parseCronToMs(config.cron);
1411
+ if (interval) {
1412
+ const intervalId = setInterval(async () => {
1413
+ await this.handleCronTick(config);
1414
+ }, interval);
1415
+ this.cronIntervals.set(config.id, intervalId);
1416
+ }
1417
+ await this.context.storeMemory(`Cron trigger registered: ${config.name}`, {
1418
+ keywords: ["listener", "cron", config.name],
1419
+ metadata: { config }
1420
+ });
1421
+ }
1422
+ /**
1423
+ * Unregister a cron trigger
1424
+ */
1425
+ unregisterCronTrigger(id) {
1426
+ const interval = this.cronIntervals.get(id);
1427
+ if (interval) {
1428
+ clearInterval(interval);
1429
+ this.cronIntervals.delete(id);
1430
+ }
1431
+ }
1432
+ /**
1433
+ * Handle a file event
1434
+ */
1435
+ async handleFileEvent(watcherId, eventType, filePath) {
1436
+ const event = {
1437
+ id: `evt-${Date.now()}`,
1438
+ type: "file",
1439
+ source: watcherId,
1440
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1441
+ data: { eventType, filePath }
1442
+ };
1443
+ this.recordEvent(event);
1444
+ console.log(`\u{1F4C1} File event: ${eventType} ${filePath}`);
1445
+ }
1446
+ /**
1447
+ * Handle a cron tick
1448
+ */
1449
+ async handleCronTick(config) {
1450
+ const event = {
1451
+ id: `evt-${Date.now()}`,
1452
+ type: "cron",
1453
+ source: config.id,
1454
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1455
+ data: { cron: config.cron }
1456
+ };
1457
+ this.recordEvent(event);
1458
+ console.log(`\u23F0 Cron tick: ${config.name}`);
1459
+ for (const step of config.workflow) {
1460
+ try {
1461
+ await this.executor.queueToolExecution(step.toolId, {
1462
+ ...step.params,
1463
+ _event: event
1464
+ });
1465
+ } catch (error) {
1466
+ console.error(`\u274C Workflow step failed: ${step.toolId}`, error);
1467
+ }
1468
+ }
1469
+ }
1470
+ /**
1471
+ * Trigger a manual event (for testing or programmatic triggers)
1472
+ */
1473
+ async triggerManual(name, data = {}) {
1474
+ const event = {
1475
+ id: `evt-${Date.now()}`,
1476
+ type: "manual",
1477
+ source: name,
1478
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1479
+ data
1480
+ };
1481
+ this.recordEvent(event);
1482
+ console.log(`\u{1F446} Manual trigger: ${name}`);
1483
+ await this.context.storeMemory(`Manual trigger fired: ${name}`, {
1484
+ keywords: ["trigger", "manual", name],
1485
+ metadata: { event }
1486
+ });
1487
+ }
1488
+ /**
1489
+ * Get recent events
1490
+ */
1491
+ getRecentEvents(limit = 10) {
1492
+ return this.eventHistory.slice(-limit).reverse();
1493
+ }
1494
+ /**
1495
+ * Get active listeners status
1496
+ */
1497
+ getStatus() {
1498
+ return {
1499
+ fileWatchers: this.watchers.size,
1500
+ cronTriggers: this.cronIntervals.size,
1501
+ recentEvents: this.eventHistory.length
1502
+ };
1503
+ }
1504
+ /**
1505
+ * Stop all listeners
1506
+ */
1507
+ async stopAll() {
1508
+ for (const [id] of this.watchers) {
1509
+ await this.unregisterFileWatcher(id);
1510
+ }
1511
+ for (const [id] of this.cronIntervals) {
1512
+ this.unregisterCronTrigger(id);
1513
+ }
1514
+ console.log("\u{1F6D1} All listeners stopped");
1515
+ }
1516
+ recordEvent(event) {
1517
+ this.eventHistory.push(event);
1518
+ if (this.eventHistory.length > this.maxEventHistory) {
1519
+ this.eventHistory = this.eventHistory.slice(-this.maxEventHistory);
1520
+ }
1521
+ }
1522
+ /**
1523
+ * Simple cron parser - converts basic cron expressions to milliseconds
1524
+ * Supports: @hourly, @daily, @weekly, and simple intervals like every N minutes
1525
+ */
1526
+ parseCronToMs(cron) {
1527
+ switch (cron) {
1528
+ case "@hourly":
1529
+ return 60 * 60 * 1e3;
1530
+ case "@daily":
1531
+ return 24 * 60 * 60 * 1e3;
1532
+ case "@weekly":
1533
+ return 7 * 24 * 60 * 60 * 1e3;
1534
+ case "@minutely":
1535
+ return 60 * 1e3;
1536
+ }
1537
+ const match = cron.match(/^\*\/(\d+)\s/);
1538
+ if (match) {
1539
+ const minutes = parseInt(match[1], 10);
1540
+ if (minutes > 0 && minutes <= 60) {
1541
+ return minutes * 60 * 1e3;
1542
+ }
1543
+ }
1544
+ console.warn(`\u26A0\uFE0F Unrecognized cron pattern "${cron}", using 1 minute interval`);
1545
+ return 60 * 1e3;
1546
+ }
1547
+ };
1548
+ function createFileWatcherWorkflow(name, watchPath, options) {
1549
+ return {
1550
+ id: `fw-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1551
+ name,
1552
+ watchPath,
1553
+ pattern: options.pattern,
1554
+ events: options.events || ["create"],
1555
+ workflow: options.workflow
1556
+ };
1557
+ }
1558
+ function createCronWorkflow(name, cron, workflow) {
1559
+ return {
1560
+ id: `cron-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1561
+ name,
1562
+ cron,
1563
+ workflow
1564
+ };
1565
+ }
1566
+
1567
+ // src/security/edge-node.ts
1568
+ var sodium = __toESM(require("libsodium-wrappers"));
1569
+ var EdgeNodeSecurity = class {
1570
+ sodiumReady;
1571
+ backendSecret;
1572
+ keyPair;
1573
+ constructor(options = {}) {
1574
+ this.sodiumReady = sodium.ready;
1575
+ this.backendSecret = options.backendSecret;
1576
+ this.keyPair = options.keyPair;
1577
+ }
1578
+ /**
1579
+ * Initialize libsodium
1580
+ */
1581
+ async initialize() {
1582
+ await this.sodiumReady;
1583
+ }
1584
+ // ============================================================================
1585
+ // JWT Token Management
1586
+ // ============================================================================
1587
+ /**
1588
+ * Generate a JWT token for an edge node
1589
+ * Note: In production, this is done by the backend. This is for testing.
1590
+ */
1591
+ generateJWT(edgeNodeId, subscriberId, expiresInDays = 30) {
1592
+ if (!this.backendSecret) {
1593
+ throw new Error("Backend secret not configured");
1594
+ }
1595
+ const now = Math.floor(Date.now() / 1e3);
1596
+ const exp = now + expiresInDays * 24 * 60 * 60;
1597
+ const jti = this.generateTokenId();
1598
+ const payload = {
1599
+ sub: edgeNodeId,
1600
+ iss: "rainfall-backend",
1601
+ iat: now,
1602
+ exp,
1603
+ jti,
1604
+ scope: ["edge:heartbeat", "edge:claim", "edge:submit", "edge:queue"]
1605
+ };
1606
+ const header = { alg: "HS256", typ: "JWT" };
1607
+ const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
1608
+ const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
1609
+ const signature = this.hmacSha256(
1610
+ `${encodedHeader}.${encodedPayload}`,
1611
+ this.backendSecret
1612
+ );
1613
+ const encodedSignature = this.base64UrlEncode(signature);
1614
+ return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
1615
+ }
1616
+ /**
1617
+ * Validate a JWT token
1618
+ */
1619
+ validateJWT(token) {
1620
+ const parts = token.split(".");
1621
+ if (parts.length !== 3) {
1622
+ throw new Error("Invalid JWT format");
1623
+ }
1624
+ const [encodedHeader, encodedPayload, encodedSignature] = parts;
1625
+ if (this.backendSecret) {
1626
+ const expectedSignature = this.hmacSha256(
1627
+ `${encodedHeader}.${encodedPayload}`,
1628
+ this.backendSecret
1629
+ );
1630
+ const expectedEncoded = this.base64UrlEncode(expectedSignature);
1631
+ if (!this.timingSafeEqual(encodedSignature, expectedEncoded)) {
1632
+ throw new Error("Invalid JWT signature");
1633
+ }
1634
+ }
1635
+ const payload = JSON.parse(this.base64UrlDecode(encodedPayload));
1636
+ const now = Math.floor(Date.now() / 1e3);
1637
+ if (payload.exp < now) {
1638
+ throw new Error("JWT token expired");
1639
+ }
1640
+ if (payload.iss !== "rainfall-backend") {
1641
+ throw new Error("Invalid JWT issuer");
1642
+ }
1643
+ return {
1644
+ edgeNodeId: payload.sub,
1645
+ subscriberId: payload.sub,
1646
+ // Same as edge node ID for now
1647
+ scopes: payload.scope,
1648
+ expiresAt: payload.exp
1649
+ };
1650
+ }
1651
+ /**
1652
+ * Extract bearer token from Authorization header
1653
+ */
1654
+ extractBearerToken(authHeader) {
1655
+ if (!authHeader) return null;
1656
+ const match = authHeader.match(/^Bearer\s+(.+)$/i);
1657
+ return match ? match[1] : null;
1658
+ }
1659
+ // ============================================================================
1660
+ // ACL Enforcement
1661
+ // ============================================================================
1662
+ /**
1663
+ * Check if an edge node is allowed to perform an action on a job
1664
+ * Rule: Edge nodes can only access jobs for their own subscriber
1665
+ */
1666
+ checkACL(check) {
1667
+ if (check.subscriberId !== check.jobSubscriberId) {
1668
+ return {
1669
+ allowed: false,
1670
+ reason: `Edge node ${check.edgeNodeId} cannot access jobs from subscriber ${check.jobSubscriberId}`
1671
+ };
1672
+ }
1673
+ const allowedActions = ["heartbeat", "claim", "submit", "queue"];
1674
+ if (!allowedActions.includes(check.action)) {
1675
+ return {
1676
+ allowed: false,
1677
+ reason: `Unknown action: ${check.action}`
1678
+ };
1679
+ }
1680
+ return { allowed: true };
1681
+ }
1682
+ /**
1683
+ * Middleware-style ACL check for job operations
1684
+ */
1685
+ requireSameSubscriber(edgeNodeSubscriberId, jobSubscriberId, operation) {
1686
+ const result = this.checkACL({
1687
+ edgeNodeId: edgeNodeSubscriberId,
1688
+ subscriberId: edgeNodeSubscriberId,
1689
+ jobSubscriberId,
1690
+ action: operation
1691
+ });
1692
+ if (!result.allowed) {
1693
+ throw new Error(result.reason);
1694
+ }
1695
+ }
1696
+ // ============================================================================
1697
+ // Encryption (Libsodium)
1698
+ // ============================================================================
1699
+ /**
1700
+ * Generate a new Ed25519 key pair for an edge node
1701
+ */
1702
+ async generateKeyPair() {
1703
+ await this.sodiumReady;
1704
+ const keyPair = sodium.crypto_box_keypair();
1705
+ return {
1706
+ publicKey: this.bytesToBase64(keyPair.publicKey),
1707
+ privateKey: this.bytesToBase64(keyPair.privateKey)
1708
+ };
1709
+ }
1710
+ /**
1711
+ * Encrypt job parameters for a target edge node using its public key
1712
+ */
1713
+ async encryptForEdgeNode(plaintext, targetPublicKeyBase64) {
1714
+ await this.sodiumReady;
1715
+ if (!this.keyPair) {
1716
+ throw new Error("Local key pair not configured");
1717
+ }
1718
+ const targetPublicKey = this.base64ToBytes(targetPublicKeyBase64);
1719
+ const ephemeralKeyPair = sodium.crypto_box_keypair();
1720
+ const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
1721
+ const message = new TextEncoder().encode(plaintext);
1722
+ const ciphertext = sodium.crypto_box_easy(
1723
+ message,
1724
+ nonce,
1725
+ targetPublicKey,
1726
+ ephemeralKeyPair.privateKey
1727
+ );
1728
+ return {
1729
+ ciphertext: this.bytesToBase64(ciphertext),
1730
+ nonce: this.bytesToBase64(nonce),
1731
+ ephemeralPublicKey: this.bytesToBase64(ephemeralKeyPair.publicKey)
1732
+ };
1733
+ }
1734
+ /**
1735
+ * Decrypt job parameters received from the backend
1736
+ */
1737
+ async decryptFromBackend(encrypted) {
1738
+ await this.sodiumReady;
1739
+ if (!this.keyPair) {
1740
+ throw new Error("Local key pair not configured");
1741
+ }
1742
+ const privateKey = this.base64ToBytes(this.keyPair.privateKey);
1743
+ const ephemeralPublicKey = this.base64ToBytes(encrypted.ephemeralPublicKey);
1744
+ const nonce = this.base64ToBytes(encrypted.nonce);
1745
+ const ciphertext = this.base64ToBytes(encrypted.ciphertext);
1746
+ const decrypted = sodium.crypto_box_open_easy(
1747
+ ciphertext,
1748
+ nonce,
1749
+ ephemeralPublicKey,
1750
+ privateKey
1751
+ );
1752
+ if (!decrypted) {
1753
+ throw new Error("Decryption failed - invalid ciphertext or keys");
1754
+ }
1755
+ return new TextDecoder().decode(decrypted);
1756
+ }
1757
+ /**
1758
+ * Encrypt job parameters for local storage (using secretbox)
1759
+ */
1760
+ async encryptLocal(plaintext, key) {
1761
+ await this.sodiumReady;
1762
+ const keyBytes = this.deriveKey(key);
1763
+ const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
1764
+ const message = new TextEncoder().encode(plaintext);
1765
+ const ciphertext = sodium.crypto_secretbox_easy(message, nonce, keyBytes);
1766
+ return {
1767
+ ciphertext: this.bytesToBase64(ciphertext),
1768
+ nonce: this.bytesToBase64(nonce)
1769
+ };
1770
+ }
1771
+ /**
1772
+ * Decrypt locally stored job parameters
1773
+ */
1774
+ async decryptLocal(encrypted, key) {
1775
+ await this.sodiumReady;
1776
+ const keyBytes = this.deriveKey(key);
1777
+ const nonce = this.base64ToBytes(encrypted.nonce);
1778
+ const ciphertext = this.base64ToBytes(encrypted.ciphertext);
1779
+ const decrypted = sodium.crypto_secretbox_open_easy(ciphertext, nonce, keyBytes);
1780
+ if (!decrypted) {
1781
+ throw new Error("Local decryption failed");
1782
+ }
1783
+ return new TextDecoder().decode(decrypted);
1784
+ }
1785
+ // ============================================================================
1786
+ // Utility Methods
1787
+ // ============================================================================
1788
+ generateTokenId() {
1789
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
1790
+ }
1791
+ base64UrlEncode(str) {
1792
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1793
+ }
1794
+ base64UrlDecode(str) {
1795
+ const padding = "=".repeat((4 - str.length % 4) % 4);
1796
+ const base64 = str.replace(/-/g, "+").replace(/_/g, "/") + padding;
1797
+ return atob(base64);
1798
+ }
1799
+ hmacSha256(message, secret) {
1800
+ const key = new TextEncoder().encode(secret);
1801
+ const msg = new TextEncoder().encode(message);
1802
+ const hash = sodium.crypto_auth(msg, key);
1803
+ return this.bytesToBase64(hash);
1804
+ }
1805
+ timingSafeEqual(a, b) {
1806
+ if (a.length !== b.length) return false;
1807
+ let result = 0;
1808
+ for (let i = 0; i < a.length; i++) {
1809
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
1810
+ }
1811
+ return result === 0;
1812
+ }
1813
+ bytesToBase64(bytes) {
1814
+ const binString = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
1815
+ return btoa(binString);
1816
+ }
1817
+ base64ToBytes(base64) {
1818
+ const binString = atob(base64);
1819
+ return Uint8Array.from(binString, (m) => m.charCodeAt(0));
1820
+ }
1821
+ deriveKey(password) {
1822
+ const passwordBytes = new TextEncoder().encode(password);
1823
+ return sodium.crypto_generichash(32, passwordBytes, null);
1824
+ }
1825
+ };
1826
+ async function createEdgeNodeSecurity(options = {}) {
1827
+ const security = new EdgeNodeSecurity(options);
1828
+ await security.initialize();
1829
+ return security;
1830
+ }
1831
+
1832
+ // src/security/edge-client.ts
1833
+ var import_fs = require("fs");
1834
+ var import_path = require("path");
1835
+ var SecureEdgeClient = class {
1836
+ client;
1837
+ security;
1838
+ edgeNodeId;
1839
+ edgeNodeSecret;
1840
+ keysPath;
1841
+ jwtPayload;
1842
+ keyPair;
1843
+ constructor(config) {
1844
+ this.client = config.client;
1845
+ this.edgeNodeId = config.edgeNodeId;
1846
+ this.edgeNodeSecret = config.edgeNodeSecret;
1847
+ this.keysPath = config.keysPath;
1848
+ this.security = new EdgeNodeSecurity({
1849
+ backendSecret: config.backendSecret
1850
+ });
1851
+ }
1852
+ /**
1853
+ * Initialize the secure client
1854
+ */
1855
+ async initialize() {
1856
+ await this.security.initialize();
1857
+ await this.loadKeyPair();
1858
+ this.jwtPayload = this.security.validateJWT(this.edgeNodeSecret);
1859
+ if (this.jwtPayload.edgeNodeId !== this.edgeNodeId) {
1860
+ throw new Error("JWT edge node ID mismatch");
1861
+ }
1862
+ }
1863
+ /**
1864
+ * Load key pair from disk
1865
+ */
1866
+ async loadKeyPair() {
1867
+ const publicKeyPath = (0, import_path.join)(this.keysPath, "edge-node.pub");
1868
+ const privateKeyPath = (0, import_path.join)(this.keysPath, "edge-node.key");
1869
+ if (!(0, import_fs.existsSync)(publicKeyPath) || !(0, import_fs.existsSync)(privateKeyPath)) {
1870
+ throw new Error("Key pair not found. Run: rainfall edge generate-keys");
1871
+ }
1872
+ this.keyPair = {
1873
+ publicKey: (0, import_fs.readFileSync)(publicKeyPath, "utf-8"),
1874
+ privateKey: (0, import_fs.readFileSync)(privateKeyPath, "utf-8")
1875
+ };
1876
+ }
1877
+ /**
1878
+ * Get public key for sharing with backend
1879
+ */
1880
+ getPublicKey() {
1881
+ if (!this.keyPair) {
1882
+ throw new Error("Key pair not loaded");
1883
+ }
1884
+ return this.keyPair.publicKey;
1885
+ }
1886
+ /**
1887
+ * Send heartbeat with authentication
1888
+ */
1889
+ async heartbeat() {
1890
+ this.requireAuth();
1891
+ return this.client.request("/edge/heartbeat", {
1892
+ method: "POST",
1893
+ body: {
1894
+ edgeNodeId: this.edgeNodeId,
1895
+ timestamp: Date.now()
1896
+ },
1897
+ headers: {
1898
+ "Authorization": `Bearer ${this.edgeNodeSecret}`
1899
+ }
1900
+ });
1901
+ }
1902
+ /**
1903
+ * Claim a job from the queue
1904
+ */
1905
+ async claimJob() {
1906
+ this.requireAuth();
1907
+ const job = await this.client.request("/edge/claim-job", {
1908
+ method: "POST",
1909
+ body: {
1910
+ edgeNodeId: this.edgeNodeId,
1911
+ subscriberId: this.jwtPayload.subscriberId
1912
+ },
1913
+ headers: {
1914
+ "Authorization": `Bearer ${this.edgeNodeSecret}`
1915
+ }
1916
+ });
1917
+ if (job && job.encrypted && job.params) {
1918
+ const decrypted = await this.decryptJobParams(job.params);
1919
+ return { ...job, params: decrypted };
1920
+ }
1921
+ return job;
1922
+ }
1923
+ /**
1924
+ * Submit job result
1925
+ */
1926
+ async submitJobResult(result) {
1927
+ this.requireAuth();
1928
+ let encryptedOutput;
1929
+ if (result.output) {
1930
+ encryptedOutput = await this.encryptJobResult(result.output);
1931
+ }
1932
+ await this.client.request("/edge/submit-job-result", {
1933
+ method: "POST",
1934
+ body: {
1935
+ edgeNodeId: this.edgeNodeId,
1936
+ subscriberId: this.jwtPayload.subscriberId,
1937
+ result: {
1938
+ ...result,
1939
+ output: encryptedOutput,
1940
+ encrypted: !!encryptedOutput
1941
+ }
1942
+ },
1943
+ headers: {
1944
+ "Authorization": `Bearer ${this.edgeNodeSecret}`
1945
+ }
1946
+ });
1947
+ }
1948
+ /**
1949
+ * Queue a job for processing
1950
+ */
1951
+ async queueJob(type, params, targetPublicKey) {
1952
+ this.requireAuth();
1953
+ let encryptedParams;
1954
+ let encrypted = false;
1955
+ if (targetPublicKey) {
1956
+ encryptedParams = await this.encryptJobParamsForTarget(
1957
+ JSON.stringify(params),
1958
+ targetPublicKey
1959
+ );
1960
+ encrypted = true;
1961
+ }
1962
+ return this.client.request("/edge/queue-job", {
1963
+ method: "POST",
1964
+ body: {
1965
+ edgeNodeId: this.edgeNodeId,
1966
+ subscriberId: this.jwtPayload.subscriberId,
1967
+ job: {
1968
+ type,
1969
+ params: encryptedParams || JSON.stringify(params),
1970
+ encrypted
1971
+ }
1972
+ },
1973
+ headers: {
1974
+ "Authorization": `Bearer ${this.edgeNodeSecret}`
1975
+ }
1976
+ });
1977
+ }
1978
+ /**
1979
+ * Decrypt job params received from backend
1980
+ */
1981
+ async decryptJobParams(encryptedParams) {
1982
+ const encrypted = JSON.parse(encryptedParams);
1983
+ return this.security.decryptFromBackend(encrypted);
1984
+ }
1985
+ /**
1986
+ * Encrypt job result for sending to backend
1987
+ */
1988
+ async encryptJobResult(output) {
1989
+ const encrypted = await this.security.encryptLocal(output, this.keyPair.privateKey);
1990
+ return JSON.stringify(encrypted);
1991
+ }
1992
+ /**
1993
+ * Encrypt job params for a specific target edge node
1994
+ */
1995
+ async encryptJobParamsForTarget(params, targetPublicKey) {
1996
+ const encrypted = await this.security.encryptForEdgeNode(params, targetPublicKey);
1997
+ return JSON.stringify(encrypted);
1998
+ }
1999
+ /**
2000
+ * Check if client is authenticated
2001
+ */
2002
+ requireAuth() {
2003
+ if (!this.jwtPayload) {
2004
+ throw new Error("Client not authenticated. Call initialize() first.");
2005
+ }
2006
+ const now = Math.floor(Date.now() / 1e3);
2007
+ if (this.jwtPayload.expiresAt < now) {
2008
+ throw new Error("JWT token expired. Re-register edge node.");
2009
+ }
2010
+ }
2011
+ /**
2012
+ * Get current authentication status
2013
+ */
2014
+ getAuthStatus() {
2015
+ if (!this.jwtPayload) {
2016
+ return { authenticated: false };
2017
+ }
2018
+ const now = Math.floor(Date.now() / 1e3);
2019
+ return {
2020
+ authenticated: this.jwtPayload.expiresAt > now,
2021
+ edgeNodeId: this.jwtPayload.edgeNodeId,
2022
+ subscriberId: this.jwtPayload.subscriberId,
2023
+ expiresAt: this.jwtPayload.expiresAt,
2024
+ scopes: this.jwtPayload.scopes
2025
+ };
2026
+ }
2027
+ };
2028
+ async function createSecureEdgeClient(client, options) {
2029
+ const secureClient = new SecureEdgeClient({
2030
+ client,
2031
+ ...options
2032
+ });
2033
+ await secureClient.initialize();
2034
+ return secureClient;
2035
+ }
2036
+
802
2037
  // src/index.ts
803
2038
  var VERSION = "0.1.0";
804
2039
  // Annotate the CommonJS export names for ESM import in node:
805
2040
  0 && (module.exports = {
806
2041
  AuthenticationError,
2042
+ EdgeNodeSecurity,
807
2043
  NetworkError,
808
2044
  NotFoundError,
809
2045
  Rainfall,
810
2046
  RainfallClient,
2047
+ RainfallDaemonContext,
811
2048
  RainfallError,
2049
+ RainfallListenerRegistry,
2050
+ RainfallNetworkedExecutor,
812
2051
  RateLimitError,
2052
+ SecureEdgeClient,
813
2053
  ServerError,
814
2054
  TimeoutError,
815
2055
  ToolNotFoundError,
816
2056
  VERSION,
817
- ValidationError
2057
+ ValidationError,
2058
+ createCronWorkflow,
2059
+ createEdgeNodeSecurity,
2060
+ createFileWatcherWorkflow,
2061
+ createSecureEdgeClient
818
2062
  });