@rainfall-devkit/sdk 0.2.2 → 0.2.4

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 (39) hide show
  1. package/dist/chunk-2FYYTIJQ.mjs +993 -0
  2. package/dist/chunk-6FXRLPLR.mjs +436 -0
  3. package/dist/chunk-CC4O7GSQ.mjs +978 -0
  4. package/dist/chunk-CQ5TV7CQ.mjs +989 -0
  5. package/dist/chunk-GPKQUVAV.mjs +987 -0
  6. package/dist/chunk-LJQEO3CY.mjs +150 -0
  7. package/dist/chunk-S7MOQCV4.mjs +137 -0
  8. package/dist/chunk-XHPFY5MH.mjs +132 -0
  9. package/dist/cli/index.js +1128 -49
  10. package/dist/cli/index.mjs +370 -30
  11. package/dist/daemon/index.d.mts +3 -3
  12. package/dist/daemon/index.d.ts +3 -3
  13. package/dist/daemon/index.js +416 -130
  14. package/dist/daemon/index.mjs +2 -1
  15. package/dist/display-KKJPO6UA.mjs +14 -0
  16. package/dist/errors-CY6HW2I5.mjs +24 -0
  17. package/dist/index.d.mts +66 -4
  18. package/dist/index.d.ts +66 -4
  19. package/dist/index.js +896 -113
  20. package/dist/index.mjs +18 -6
  21. package/dist/listeners-BBNBsJCk.d.ts +372 -0
  22. package/dist/listeners-BCEypw1u.d.ts +372 -0
  23. package/dist/listeners-BGdrWpkP.d.mts +372 -0
  24. package/dist/listeners-CMUKjEkb.d.mts +372 -0
  25. package/dist/listeners-CadPNUHd.d.ts +372 -0
  26. package/dist/listeners-Ckdj6D8T.d.mts +372 -0
  27. package/dist/mcp.d.mts +2 -2
  28. package/dist/mcp.d.ts +2 -2
  29. package/dist/mcp.js +410 -102
  30. package/dist/mcp.mjs +4 -2
  31. package/dist/param-parser-JVKB5FQK.mjs +12 -0
  32. package/dist/param-parser-PAKCNDBX.mjs +136 -0
  33. package/dist/sdk-BUVNdBc7.d.mts +1167 -0
  34. package/dist/sdk-BUVNdBc7.d.ts +1167 -0
  35. package/dist/sdk-Cl5Qzt4I.d.mts +1165 -0
  36. package/dist/sdk-Cl5Qzt4I.d.ts +1165 -0
  37. package/dist/sdk-DQKNbBce.d.mts +1162 -0
  38. package/dist/sdk-DQKNbBce.d.ts +1162 -0
  39. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -41,6 +41,19 @@ var init_cjs_shims = __esm({
41
41
  });
42
42
 
43
43
  // src/errors.ts
44
+ var errors_exports = {};
45
+ __export(errors_exports, {
46
+ AuthenticationError: () => AuthenticationError,
47
+ NetworkError: () => NetworkError,
48
+ NotFoundError: () => NotFoundError,
49
+ RainfallError: () => RainfallError,
50
+ RateLimitError: () => RateLimitError,
51
+ ServerError: () => ServerError,
52
+ TimeoutError: () => TimeoutError,
53
+ ToolNotFoundError: () => ToolNotFoundError,
54
+ ValidationError: () => ValidationError,
55
+ parseErrorResponse: () => parseErrorResponse
56
+ });
44
57
  function parseErrorResponse(response, data) {
45
58
  const statusCode = response.status;
46
59
  if (statusCode === 429) {
@@ -89,7 +102,7 @@ function parseErrorResponse(response, data) {
89
102
  );
90
103
  }
91
104
  }
92
- var RainfallError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError, TimeoutError, NetworkError;
105
+ var RainfallError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError, TimeoutError, NetworkError, ToolNotFoundError;
93
106
  var init_errors = __esm({
94
107
  "src/errors.ts"() {
95
108
  "use strict";
@@ -175,6 +188,179 @@ var init_errors = __esm({
175
188
  Object.setPrototypeOf(this, _NetworkError.prototype);
176
189
  }
177
190
  };
191
+ ToolNotFoundError = class _ToolNotFoundError extends RainfallError {
192
+ constructor(toolId) {
193
+ super(`Tool '${toolId}' not found`, "TOOL_NOT_FOUND", 404, { toolId });
194
+ this.name = "ToolNotFoundError";
195
+ Object.setPrototypeOf(this, _ToolNotFoundError.prototype);
196
+ }
197
+ };
198
+ }
199
+ });
200
+
201
+ // src/validation.ts
202
+ async function fetchToolSchema(client, toolId) {
203
+ const cached = schemaCache.get(toolId);
204
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
205
+ return cached.schema;
206
+ }
207
+ const response = await client.request(
208
+ `/olympic/subscribers/me/nodes/${toolId}/params`
209
+ );
210
+ if (!response.success || !response.params) {
211
+ throw new ValidationError(`Failed to fetch schema for tool '${toolId}'`);
212
+ }
213
+ schemaCache.set(toolId, { schema: response.params, timestamp: Date.now() });
214
+ return response.params;
215
+ }
216
+ function validateParams(schema, params, toolId) {
217
+ const errors = [];
218
+ const parameters = schema.parameters || {};
219
+ for (const [key, paramSchema] of Object.entries(parameters)) {
220
+ if (paramSchema.optional !== true && !(key in (params || {}))) {
221
+ errors.push({
222
+ path: key,
223
+ message: `Missing required parameter '${key}'`,
224
+ expected: paramSchema.type
225
+ });
226
+ }
227
+ }
228
+ if (params) {
229
+ for (const [key, value] of Object.entries(params)) {
230
+ const paramSchema = parameters[key];
231
+ if (!paramSchema) {
232
+ errors.push({
233
+ path: key,
234
+ message: `Unknown parameter '${key}'`,
235
+ received: value
236
+ });
237
+ continue;
238
+ }
239
+ const typeError = validateType(key, value, paramSchema);
240
+ if (typeError) {
241
+ errors.push(typeError);
242
+ }
243
+ }
244
+ }
245
+ return {
246
+ valid: errors.length === 0,
247
+ errors
248
+ };
249
+ }
250
+ function validateType(path, value, schema) {
251
+ if (value === null || value === void 0) {
252
+ if (schema.optional === true) {
253
+ return null;
254
+ }
255
+ return {
256
+ path,
257
+ message: `Parameter '${path}' is required but received ${value}`,
258
+ received: value,
259
+ expected: schema.type
260
+ };
261
+ }
262
+ const expectedType = schema.type;
263
+ const actualType = getJsType(value);
264
+ switch (expectedType) {
265
+ case "string":
266
+ if (typeof value !== "string") {
267
+ return {
268
+ path,
269
+ message: `Parameter '${path}' must be a string, received ${actualType}`,
270
+ received: value,
271
+ expected: "string"
272
+ };
273
+ }
274
+ break;
275
+ case "number":
276
+ if (typeof value !== "number" || isNaN(value)) {
277
+ return {
278
+ path,
279
+ message: `Parameter '${path}' must be a number, received ${actualType}`,
280
+ received: value,
281
+ expected: "number"
282
+ };
283
+ }
284
+ break;
285
+ case "boolean":
286
+ if (typeof value !== "boolean") {
287
+ return {
288
+ path,
289
+ message: `Parameter '${path}' must be a boolean, received ${actualType}`,
290
+ received: value,
291
+ expected: "boolean"
292
+ };
293
+ }
294
+ break;
295
+ case "array":
296
+ if (!Array.isArray(value)) {
297
+ return {
298
+ path,
299
+ message: `Parameter '${path}' must be an array, received ${actualType}`,
300
+ received: value,
301
+ expected: "array"
302
+ };
303
+ }
304
+ if (schema.items) {
305
+ for (let i = 0; i < value.length; i++) {
306
+ const itemError = validateType(`${path}[${i}]`, value[i], schema.items);
307
+ if (itemError) {
308
+ return itemError;
309
+ }
310
+ }
311
+ }
312
+ break;
313
+ case "object":
314
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
315
+ return {
316
+ path,
317
+ message: `Parameter '${path}' must be an object, received ${actualType}`,
318
+ received: value,
319
+ expected: "object"
320
+ };
321
+ }
322
+ if (schema.properties) {
323
+ const objValue = value;
324
+ for (const [propKey, propSchema] of Object.entries(schema.properties)) {
325
+ if (objValue[propKey] !== void 0) {
326
+ const propError = validateType(`${path}.${propKey}`, objValue[propKey], propSchema);
327
+ if (propError) {
328
+ return propError;
329
+ }
330
+ }
331
+ }
332
+ }
333
+ break;
334
+ default:
335
+ break;
336
+ }
337
+ return null;
338
+ }
339
+ function getJsType(value) {
340
+ if (value === null) return "null";
341
+ if (Array.isArray(value)) return "array";
342
+ return typeof value;
343
+ }
344
+ function formatValidationErrors(result) {
345
+ if (result.valid) return "No validation errors";
346
+ const lines = result.errors.map((err) => {
347
+ let line = ` - ${err.message}`;
348
+ if (err.received !== void 0) {
349
+ line += ` (received: ${JSON.stringify(err.received).slice(0, 50)})`;
350
+ }
351
+ return line;
352
+ });
353
+ return `Validation failed with ${result.errors.length} error(s):
354
+ ${lines.join("\n")}`;
355
+ }
356
+ var schemaCache, CACHE_TTL_MS;
357
+ var init_validation = __esm({
358
+ "src/validation.ts"() {
359
+ "use strict";
360
+ init_cjs_shims();
361
+ init_errors();
362
+ schemaCache = /* @__PURE__ */ new Map();
363
+ CACHE_TTL_MS = 5 * 60 * 1e3;
178
364
  }
179
365
  });
180
366
 
@@ -185,6 +371,7 @@ var init_client = __esm({
185
371
  "use strict";
186
372
  init_cjs_shims();
187
373
  init_errors();
374
+ init_validation();
188
375
  DEFAULT_BASE_URL = "https://olympic-api.pragma-digital.org/v1";
189
376
  DEFAULT_TIMEOUT = 3e4;
190
377
  DEFAULT_RETRIES = 3;
@@ -195,6 +382,7 @@ var init_client = __esm({
195
382
  defaultTimeout;
196
383
  defaultRetries;
197
384
  defaultRetryDelay;
385
+ disableValidation;
198
386
  lastRateLimitInfo;
199
387
  subscriberId;
200
388
  constructor(config) {
@@ -203,6 +391,7 @@ var init_client = __esm({
203
391
  this.defaultTimeout = config.timeout || DEFAULT_TIMEOUT;
204
392
  this.defaultRetries = config.retries ?? DEFAULT_RETRIES;
205
393
  this.defaultRetryDelay = config.retryDelay || DEFAULT_RETRY_DELAY;
394
+ this.disableValidation = config.disableValidation ?? false;
206
395
  }
207
396
  /**
208
397
  * Get the last rate limit info from the API
@@ -285,15 +474,72 @@ var init_client = __esm({
285
474
  }
286
475
  /**
287
476
  * Execute a tool/node by ID
477
+ *
478
+ * @param toolId - The ID of the tool/node to execute
479
+ * @param params - Parameters to pass to the tool
480
+ * @param options - Request options including skipValidation to bypass param validation
288
481
  */
289
482
  async executeTool(toolId, params, options) {
483
+ if (!this.disableValidation && !options?.skipValidation) {
484
+ const validation = await this.validateToolParams(toolId, params);
485
+ if (!validation.valid) {
486
+ const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
487
+ throw new ValidationError2(
488
+ `Parameter validation failed for tool '${toolId}': ${formatValidationErrors(validation)}`,
489
+ { toolId, errors: validation.errors }
490
+ );
491
+ }
492
+ }
290
493
  const subscriberId = await this.ensureSubscriberId();
494
+ const body = params || {};
495
+ if (options?.targetEdge) {
496
+ body._targetEdge = options.targetEdge;
497
+ }
291
498
  const response = await this.request(`/olympic/subscribers/${subscriberId}/nodes/${toolId}`, {
292
499
  method: "POST",
293
- body: params || {}
500
+ body
294
501
  }, options);
502
+ if (response.success === false) {
503
+ const errorMessage = typeof response.error === "string" ? response.error : JSON.stringify(response.error);
504
+ throw new RainfallError(
505
+ `Tool execution failed: ${errorMessage}`,
506
+ "TOOL_EXECUTION_ERROR",
507
+ 400,
508
+ { toolId, error: response.error }
509
+ );
510
+ }
295
511
  return response.result;
296
512
  }
513
+ /**
514
+ * Validate parameters for a tool without executing it
515
+ * Fetches the tool schema and validates the provided params
516
+ *
517
+ * @param toolId - The ID of the tool to validate params for
518
+ * @param params - Parameters to validate
519
+ * @returns Validation result with detailed error information
520
+ *
521
+ * @example
522
+ * ```typescript
523
+ * const result = await client.validateToolParams('finviz-quotes', { tickers: ['AAPL'] });
524
+ * if (!result.valid) {
525
+ * console.log('Validation errors:', result.errors);
526
+ * }
527
+ * ```
528
+ */
529
+ async validateToolParams(toolId, params) {
530
+ try {
531
+ const schema = await fetchToolSchema(this, toolId);
532
+ return validateParams(schema, params, toolId);
533
+ } catch (error) {
534
+ if (error instanceof RainfallError && error.statusCode === 404) {
535
+ return {
536
+ valid: false,
537
+ errors: [{ path: toolId, message: `Tool '${toolId}' not found` }]
538
+ };
539
+ }
540
+ return { valid: true, errors: [] };
541
+ }
542
+ }
297
543
  /**
298
544
  * List all available tools
299
545
  */
@@ -316,11 +562,20 @@ var init_client = __esm({
316
562
  }
317
563
  /**
318
564
  * Get tool schema/parameters
565
+ *
566
+ * @param toolId - The ID of the tool to get schema for
567
+ * @returns Tool schema including parameters and output definitions
319
568
  */
320
569
  async getToolSchema(toolId) {
321
- const subscriberId = await this.ensureSubscriberId();
322
- const response = await this.request(`/olympic/subscribers/${subscriberId}/nodes/${toolId}/params`);
323
- return response.params;
570
+ const schema = await fetchToolSchema(this, toolId);
571
+ return {
572
+ name: schema.name,
573
+ description: schema.description,
574
+ category: schema.category,
575
+ parameters: schema.parameters,
576
+ output: schema.output,
577
+ metadata: schema.metadata || {}
578
+ };
324
579
  }
325
580
  /**
326
581
  * Get subscriber info
@@ -898,9 +1153,40 @@ var init_sdk = __esm({
898
1153
  }
899
1154
  /**
900
1155
  * Execute any tool by ID (low-level access)
1156
+ *
1157
+ * @param toolId - The ID of the tool to execute
1158
+ * @param params - Parameters to pass to the tool
1159
+ * @param options - Execution options including skipValidation to bypass param validation
1160
+ *
1161
+ * @example
1162
+ * ```typescript
1163
+ * // Execute with validation (default)
1164
+ * const result = await rainfall.executeTool('finviz-quotes', { tickers: ['AAPL'] });
1165
+ *
1166
+ * // Execute without validation
1167
+ * const result = await rainfall.executeTool('finviz-quotes', { tickers: ['AAPL'] }, { skipValidation: true });
1168
+ * ```
901
1169
  */
902
- async executeTool(toolId, params) {
903
- return this.client.executeTool(toolId, params);
1170
+ async executeTool(toolId, params, options) {
1171
+ return this.client.executeTool(toolId, params, options);
1172
+ }
1173
+ /**
1174
+ * Validate parameters for a tool without executing it
1175
+ *
1176
+ * @param toolId - The ID of the tool to validate params for
1177
+ * @param params - Parameters to validate
1178
+ * @returns Validation result with detailed error information
1179
+ *
1180
+ * @example
1181
+ * ```typescript
1182
+ * const result = await rainfall.validateToolParams('finviz-quotes', { tickers: ['AAPL'] });
1183
+ * if (!result.valid) {
1184
+ * console.log('Validation errors:', result.errors);
1185
+ * }
1186
+ * ```
1187
+ */
1188
+ async validateToolParams(toolId, params) {
1189
+ return this.client.validateToolParams(toolId, params);
904
1190
  }
905
1191
  /**
906
1192
  * Get current subscriber info and usage
@@ -1058,6 +1344,291 @@ var init_config = __esm({
1058
1344
  }
1059
1345
  });
1060
1346
 
1347
+ // src/cli/core/display.ts
1348
+ var display_exports = {};
1349
+ __export(display_exports, {
1350
+ detectImageData: () => detectImageData,
1351
+ detectImageSupport: () => detectImageSupport,
1352
+ displayImage: () => displayImage,
1353
+ formatAsTable: () => formatAsTable,
1354
+ formatResult: () => formatResult
1355
+ });
1356
+ function detectImageSupport() {
1357
+ if (process.env.TERM_PROGRAM === "iTerm.app") {
1358
+ return { supported: true, command: "imgcat" };
1359
+ }
1360
+ if (process.env.KITTY_WINDOW_ID) {
1361
+ return { supported: true, command: "kitty +kitten icat" };
1362
+ }
1363
+ try {
1364
+ return { supported: true, command: "xan" };
1365
+ } catch {
1366
+ }
1367
+ try {
1368
+ return { supported: true, command: "catimg" };
1369
+ } catch {
1370
+ }
1371
+ return { supported: false };
1372
+ }
1373
+ async function displayImage(imageData, options = {}) {
1374
+ const imageCommand = options.imageCommand || detectImageSupport().command;
1375
+ if (!imageCommand) {
1376
+ const { writeFileSync: writeFileSync3 } = await import("fs");
1377
+ const { tmpdir } = await import("os");
1378
+ const { join: join3 } = await import("path");
1379
+ const tempPath = join3(tmpdir(), `rainfall-image-${Date.now()}.png`);
1380
+ const buffer = typeof imageData === "string" ? Buffer.from(imageData, "base64") : imageData;
1381
+ writeFileSync3(tempPath, buffer);
1382
+ console.log(`Image saved to: ${tempPath}`);
1383
+ return;
1384
+ }
1385
+ return new Promise((resolve, reject) => {
1386
+ const buffer = typeof imageData === "string" ? Buffer.from(imageData, "base64") : imageData;
1387
+ const child = (0, import_child_process.spawn)(imageCommand, [], {
1388
+ stdio: ["pipe", "inherit", "inherit"],
1389
+ shell: true
1390
+ });
1391
+ child.stdin.write(buffer);
1392
+ child.stdin.end();
1393
+ child.on("close", (code) => {
1394
+ if (code === 0) {
1395
+ resolve();
1396
+ } else {
1397
+ reject(new Error(`Image display failed with code ${code}`));
1398
+ }
1399
+ });
1400
+ child.on("error", reject);
1401
+ });
1402
+ }
1403
+ function formatAsTable(data, columns) {
1404
+ if (!Array.isArray(data) || data.length === 0) {
1405
+ return "No data";
1406
+ }
1407
+ const cols = columns || Object.keys(data[0]);
1408
+ const widths = {};
1409
+ for (const col of cols) {
1410
+ widths[col] = Math.max(
1411
+ col.length,
1412
+ ...data.map((row) => {
1413
+ const val = row?.[col];
1414
+ return String(val ?? "").slice(0, 50).length;
1415
+ })
1416
+ );
1417
+ }
1418
+ const lines = [];
1419
+ const header = cols.map((col) => col.padEnd(widths[col])).join(" ");
1420
+ lines.push(header);
1421
+ lines.push(cols.map((col) => "-".repeat(widths[col])).join(" "));
1422
+ for (const row of data) {
1423
+ const line = cols.map((col) => {
1424
+ const val = row?.[col];
1425
+ const str = String(val ?? "").slice(0, 50);
1426
+ return str.padEnd(widths[col]);
1427
+ }).join(" ");
1428
+ lines.push(line);
1429
+ }
1430
+ return lines.join("\n");
1431
+ }
1432
+ async function formatResult(result, options = {}) {
1433
+ const mode = options.mode || "pretty";
1434
+ switch (mode) {
1435
+ case "raw":
1436
+ return JSON.stringify(result);
1437
+ case "pretty":
1438
+ return JSON.stringify(result, null, 2);
1439
+ case "table":
1440
+ if (Array.isArray(result)) {
1441
+ return formatAsTable(result, options.columns);
1442
+ }
1443
+ return JSON.stringify(result, null, 2);
1444
+ case "terminal":
1445
+ if (typeof result === "string") {
1446
+ return result;
1447
+ }
1448
+ if (Array.isArray(result) && result.every((r) => typeof r === "string")) {
1449
+ return result.join("\n");
1450
+ }
1451
+ return JSON.stringify(result);
1452
+ default:
1453
+ return JSON.stringify(result, null, 2);
1454
+ }
1455
+ }
1456
+ function detectImageData(result) {
1457
+ if (!result || typeof result !== "object") {
1458
+ return { hasImage: false };
1459
+ }
1460
+ const obj = result;
1461
+ const imageFields = ["image", "imageData", "imageBase64", "png", "jpeg", "data"];
1462
+ for (const field of imageFields) {
1463
+ if (obj[field] && typeof obj[field] === "string") {
1464
+ const value = obj[field];
1465
+ if (value.startsWith("data:image/") || value.length > 100) {
1466
+ return {
1467
+ hasImage: true,
1468
+ imageData: value.startsWith("data:image/") ? value.split(",")[1] : value
1469
+ };
1470
+ }
1471
+ }
1472
+ }
1473
+ if (obj.url && typeof obj.url === "string" && (obj.url.endsWith(".png") || obj.url.endsWith(".jpg") || obj.url.endsWith(".jpeg"))) {
1474
+ return { hasImage: true, imagePath: obj.url };
1475
+ }
1476
+ return { hasImage: false };
1477
+ }
1478
+ var import_child_process;
1479
+ var init_display = __esm({
1480
+ "src/cli/core/display.ts"() {
1481
+ "use strict";
1482
+ init_cjs_shims();
1483
+ import_child_process = require("child_process");
1484
+ }
1485
+ });
1486
+
1487
+ // src/cli/core/param-parser.ts
1488
+ var param_parser_exports = {};
1489
+ __export(param_parser_exports, {
1490
+ formatValueForDisplay: () => formatValueForDisplay,
1491
+ generateParamExample: () => generateParamExample,
1492
+ parseCliArgs: () => parseCliArgs,
1493
+ parseValue: () => parseValue
1494
+ });
1495
+ function parseValue(value, schema, options = {}) {
1496
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1497
+ if (!schema) {
1498
+ try {
1499
+ return JSON.parse(value);
1500
+ } catch {
1501
+ return value;
1502
+ }
1503
+ }
1504
+ const expectedType = schema.type;
1505
+ switch (expectedType) {
1506
+ case "array":
1507
+ return parseArrayValue(value, schema, opts);
1508
+ case "number":
1509
+ return parseNumberValue(value, opts);
1510
+ case "boolean":
1511
+ return parseBooleanValue(value, opts);
1512
+ case "string":
1513
+ return value;
1514
+ case "object":
1515
+ try {
1516
+ return JSON.parse(value);
1517
+ } catch {
1518
+ return value;
1519
+ }
1520
+ default:
1521
+ try {
1522
+ return JSON.parse(value);
1523
+ } catch {
1524
+ return value;
1525
+ }
1526
+ }
1527
+ }
1528
+ function parseArrayValue(value, schema, options) {
1529
+ if (value.startsWith("[") && value.endsWith("]")) {
1530
+ try {
1531
+ return JSON.parse(value);
1532
+ } catch {
1533
+ }
1534
+ }
1535
+ if (options.arrayInterpolation && value.includes(options.arraySeparator)) {
1536
+ const items = value.split(options.arraySeparator).map((s) => s.trim()).filter(Boolean);
1537
+ if (schema.items) {
1538
+ return items.map((item) => parseValue(item, schema.items, { ...options, arrayInterpolation: false }));
1539
+ }
1540
+ return items;
1541
+ }
1542
+ if (schema.items) {
1543
+ return [parseValue(value, schema.items, { ...options, arrayInterpolation: false })];
1544
+ }
1545
+ return [value];
1546
+ }
1547
+ function parseNumberValue(value, options) {
1548
+ if (!options.numberParsing) {
1549
+ return value;
1550
+ }
1551
+ const num = Number(value);
1552
+ if (!isNaN(num) && isFinite(num)) {
1553
+ return num;
1554
+ }
1555
+ return value;
1556
+ }
1557
+ function parseBooleanValue(value, options) {
1558
+ if (!options.booleanParsing) {
1559
+ return value;
1560
+ }
1561
+ const lower = value.toLowerCase();
1562
+ if (lower === "true" || lower === "yes" || lower === "1" || lower === "on") {
1563
+ return true;
1564
+ }
1565
+ if (lower === "false" || lower === "no" || lower === "0" || lower === "off") {
1566
+ return false;
1567
+ }
1568
+ return value;
1569
+ }
1570
+ function parseCliArgs(args, schema, options = {}) {
1571
+ const params = {};
1572
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1573
+ const parameters = schema?.parameters || {};
1574
+ for (let i = 0; i < args.length; i++) {
1575
+ const arg = args[i];
1576
+ if (arg.startsWith("--")) {
1577
+ const key = arg.slice(2);
1578
+ const value = args[++i];
1579
+ if (value === void 0) {
1580
+ params[key] = true;
1581
+ continue;
1582
+ }
1583
+ const paramSchema = parameters[key];
1584
+ params[key] = parseValue(value, paramSchema, opts);
1585
+ }
1586
+ }
1587
+ return params;
1588
+ }
1589
+ function formatValueForDisplay(value) {
1590
+ if (value === null) return "null";
1591
+ if (value === void 0) return "undefined";
1592
+ if (typeof value === "string") return value;
1593
+ if (typeof value === "number") return String(value);
1594
+ if (typeof value === "boolean") return String(value);
1595
+ if (Array.isArray(value)) {
1596
+ return value.map(formatValueForDisplay).join(",");
1597
+ }
1598
+ return JSON.stringify(value);
1599
+ }
1600
+ function generateParamExample(key, schema) {
1601
+ const type = schema.type || "string";
1602
+ switch (type) {
1603
+ case "array":
1604
+ if (schema.items?.type === "string") {
1605
+ return `--${key} item1,item2,item3`;
1606
+ }
1607
+ return `--${key} '["item1", "item2"]'`;
1608
+ case "number":
1609
+ return `--${key} 42`;
1610
+ case "boolean":
1611
+ return `--${key} true`;
1612
+ case "object":
1613
+ return `--${key} '{"key": "value"}'`;
1614
+ default:
1615
+ return `--${key} "value"`;
1616
+ }
1617
+ }
1618
+ var DEFAULT_OPTIONS;
1619
+ var init_param_parser = __esm({
1620
+ "src/cli/core/param-parser.ts"() {
1621
+ "use strict";
1622
+ init_cjs_shims();
1623
+ DEFAULT_OPTIONS = {
1624
+ arrayInterpolation: true,
1625
+ numberParsing: true,
1626
+ booleanParsing: true,
1627
+ arraySeparator: ","
1628
+ };
1629
+ }
1630
+ });
1631
+
1061
1632
  // src/services/networked.ts
1062
1633
  var RainfallNetworkedExecutor;
1063
1634
  var init_networked = __esm({
@@ -1717,7 +2288,7 @@ var init_listeners = __esm({
1717
2288
  });
1718
2289
 
1719
2290
  // src/services/mcp-proxy.ts
1720
- var import_ws, import_client2, import_stdio, import_streamableHttp, import_types, MCPProxyHub;
2291
+ var import_ws, import_client2, import_stdio, import_streamableHttp, import_types2, MCPProxyHub;
1721
2292
  var init_mcp_proxy = __esm({
1722
2293
  "src/services/mcp-proxy.ts"() {
1723
2294
  "use strict";
@@ -1726,7 +2297,7 @@ var init_mcp_proxy = __esm({
1726
2297
  import_client2 = require("@modelcontextprotocol/sdk/client/index.js");
1727
2298
  import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
1728
2299
  import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
1729
- import_types = require("@modelcontextprotocol/sdk/types.js");
2300
+ import_types2 = require("@modelcontextprotocol/sdk/types.js");
1730
2301
  MCPProxyHub = class {
1731
2302
  clients = /* @__PURE__ */ new Map();
1732
2303
  options;
@@ -1845,7 +2416,7 @@ var init_mcp_proxy = __esm({
1845
2416
  method: "tools/list",
1846
2417
  params: {}
1847
2418
  },
1848
- import_types.ListToolsResultSchema
2419
+ import_types2.ListToolsResultSchema
1849
2420
  );
1850
2421
  const tools = toolsResult.tools.map((tool) => ({
1851
2422
  name: tool.name,
@@ -1959,7 +2530,7 @@ var init_mcp_proxy = __esm({
1959
2530
  arguments: args
1960
2531
  }
1961
2532
  },
1962
- import_types.CallToolResultSchema
2533
+ import_types2.CallToolResultSchema
1963
2534
  ),
1964
2535
  new Promise(
1965
2536
  (_, reject) => setTimeout(() => reject(new Error(`Tool call timeout after ${timeout}ms`)), timeout)
@@ -1969,7 +2540,7 @@ var init_mcp_proxy = __esm({
1969
2540
  return this.formatToolResult(result);
1970
2541
  } catch (error) {
1971
2542
  this.log(`[${requestId}] Failed:`, error instanceof Error ? error.message : error);
1972
- if (error instanceof import_types.McpError) {
2543
+ if (error instanceof import_types2.McpError) {
1973
2544
  throw new Error(`MCP Error (${toolName}): ${error.message} (code: ${error.code})`);
1974
2545
  }
1975
2546
  throw error;
@@ -2053,7 +2624,7 @@ var init_mcp_proxy = __esm({
2053
2624
  method: "tools/list",
2054
2625
  params: {}
2055
2626
  },
2056
- import_types.ListToolsResultSchema
2627
+ import_types2.ListToolsResultSchema
2057
2628
  );
2058
2629
  client.tools = toolsResult.tools.map((tool) => ({
2059
2630
  name: tool.name,
@@ -2084,7 +2655,7 @@ var init_mcp_proxy = __esm({
2084
2655
  method: "resources/list",
2085
2656
  params: {}
2086
2657
  },
2087
- import_types.ListResourcesResultSchema
2658
+ import_types2.ListResourcesResultSchema
2088
2659
  );
2089
2660
  results.push({
2090
2661
  clientName: name,
@@ -2110,7 +2681,7 @@ var init_mcp_proxy = __esm({
2110
2681
  method: "resources/read",
2111
2682
  params: { uri }
2112
2683
  },
2113
- import_types.ReadResourceResultSchema
2684
+ import_types2.ReadResourceResultSchema
2114
2685
  );
2115
2686
  return result;
2116
2687
  } else {
@@ -2121,7 +2692,7 @@ var init_mcp_proxy = __esm({
2121
2692
  method: "resources/read",
2122
2693
  params: { uri }
2123
2694
  },
2124
- import_types.ReadResourceResultSchema
2695
+ import_types2.ReadResourceResultSchema
2125
2696
  );
2126
2697
  return { clientName: name, ...result };
2127
2698
  } catch {
@@ -2145,7 +2716,7 @@ var init_mcp_proxy = __esm({
2145
2716
  method: "prompts/list",
2146
2717
  params: {}
2147
2718
  },
2148
- import_types.ListPromptsResultSchema
2719
+ import_types2.ListPromptsResultSchema
2149
2720
  );
2150
2721
  results.push({
2151
2722
  clientName: name,
@@ -2171,7 +2742,7 @@ var init_mcp_proxy = __esm({
2171
2742
  method: "prompts/get",
2172
2743
  params: { name, arguments: args }
2173
2744
  },
2174
- import_types.GetPromptResultSchema
2745
+ import_types2.GetPromptResultSchema
2175
2746
  );
2176
2747
  return result;
2177
2748
  } else {
@@ -2182,7 +2753,7 @@ var init_mcp_proxy = __esm({
2182
2753
  method: "prompts/get",
2183
2754
  params: { name, arguments: args }
2184
2755
  },
2185
- import_types.GetPromptResultSchema
2756
+ import_types2.GetPromptResultSchema
2186
2757
  );
2187
2758
  return { clientName: cName, ...result };
2188
2759
  } catch {
@@ -2204,7 +2775,7 @@ var init_mcp_proxy = __esm({
2204
2775
  method: "tools/list",
2205
2776
  params: {}
2206
2777
  },
2207
- import_types.ListToolsResultSchema
2778
+ import_types2.ListToolsResultSchema
2208
2779
  );
2209
2780
  results.set(name, {
2210
2781
  status: "healthy",
@@ -3274,7 +3845,7 @@ var import_path2 = require("path");
3274
3845
  var import_url = require("url");
3275
3846
  init_sdk();
3276
3847
  init_config();
3277
- var import_child_process = require("child_process");
3848
+ var import_child_process2 = require("child_process");
3278
3849
 
3279
3850
  // src/security/edge-node.ts
3280
3851
  init_cjs_shims();
@@ -3542,6 +4113,179 @@ async function createEdgeNodeSecurity(options = {}) {
3542
4113
  return security;
3543
4114
  }
3544
4115
 
4116
+ // src/cli/index.ts
4117
+ init_display();
4118
+
4119
+ // src/cli/handlers/_registry.ts
4120
+ init_cjs_shims();
4121
+
4122
+ // src/cli/core/types.ts
4123
+ init_cjs_shims();
4124
+ var ToolHandlerRegistry = class {
4125
+ handlers = [];
4126
+ register(handler) {
4127
+ this.handlers.push(handler);
4128
+ }
4129
+ findHandler(toolId) {
4130
+ return this.handlers.find((h) => {
4131
+ if (typeof h.toolId === "string") {
4132
+ return h.toolId === toolId;
4133
+ }
4134
+ return h.toolId.test(toolId);
4135
+ });
4136
+ }
4137
+ getAllHandlers() {
4138
+ return [...this.handlers];
4139
+ }
4140
+ };
4141
+ var globalHandlerRegistry = new ToolHandlerRegistry();
4142
+
4143
+ // src/cli/handlers/_registry.ts
4144
+ var imageGenerationHandler = {
4145
+ toolId: /image-generation|generate-image/,
4146
+ async display(context) {
4147
+ const { detectImageData: detectImageData2, displayImage: displayImage2 } = await Promise.resolve().then(() => (init_display(), display_exports));
4148
+ const { result, flags } = context;
4149
+ const imageInfo = detectImageData2(result);
4150
+ if (imageInfo.hasImage && !flags.raw) {
4151
+ try {
4152
+ if (imageInfo.imageData) {
4153
+ await displayImage2(imageInfo.imageData);
4154
+ return true;
4155
+ }
4156
+ } catch (error) {
4157
+ console.warn("Failed to display image:", error instanceof Error ? error.message : error);
4158
+ }
4159
+ }
4160
+ return false;
4161
+ }
4162
+ };
4163
+ var finvizQuotesHandler = {
4164
+ toolId: "finviz-quotes",
4165
+ async preflight(context) {
4166
+ const { parseValue: parseValue2 } = await Promise.resolve().then(() => (init_param_parser(), param_parser_exports));
4167
+ const params = { ...context.params };
4168
+ if (params.tickers && typeof params.tickers === "string") {
4169
+ params.tickers = parseValue2(params.tickers, { type: "array", items: { type: "string" } });
4170
+ }
4171
+ return { params };
4172
+ },
4173
+ async display(context) {
4174
+ const { result, flags } = context;
4175
+ if (flags.raw) {
4176
+ return false;
4177
+ }
4178
+ const obj = result;
4179
+ const quotes = obj?.quotes;
4180
+ if (Array.isArray(quotes) && quotes.length > 0) {
4181
+ const { formatAsTable: formatAsTable2 } = await Promise.resolve().then(() => (init_display(), display_exports));
4182
+ const tableData = quotes.map((q) => {
4183
+ const quote = q;
4184
+ const data = quote.data || {};
4185
+ return {
4186
+ Ticker: quote.ticker || data.Ticker || "-",
4187
+ Price: data.Price || data.Close || "-",
4188
+ Change: data.Change || "-",
4189
+ Volume: data.Volume || "-",
4190
+ "Market Cap": data.MarketCap || "-"
4191
+ };
4192
+ });
4193
+ console.log(formatAsTable2(tableData));
4194
+ const summary = obj?.summary;
4195
+ if (summary && typeof summary === "string") {
4196
+ console.log(`
4197
+ ${summary}`);
4198
+ }
4199
+ return true;
4200
+ }
4201
+ return false;
4202
+ }
4203
+ };
4204
+ var csvQueryHandler = {
4205
+ toolId: /query-csv|csv-query/,
4206
+ async display(context) {
4207
+ const { result, flags } = context;
4208
+ if (flags.raw) {
4209
+ return false;
4210
+ }
4211
+ if (Array.isArray(result) && result.length > 0) {
4212
+ const { formatAsTable: formatAsTable2 } = await Promise.resolve().then(() => (init_display(), display_exports));
4213
+ console.log(formatAsTable2(result));
4214
+ return true;
4215
+ }
4216
+ return false;
4217
+ }
4218
+ };
4219
+ var webSearchHandler = {
4220
+ toolId: /web-search|exa-web-search|perplexity/,
4221
+ async display(context) {
4222
+ const { result, flags } = context;
4223
+ if (flags.raw) {
4224
+ return false;
4225
+ }
4226
+ const obj = result;
4227
+ if (obj.results && typeof obj.results === "string") {
4228
+ console.log(obj.results);
4229
+ return true;
4230
+ }
4231
+ if (obj.answer || obj.summary) {
4232
+ console.log(obj.answer || obj.summary);
4233
+ if (obj.sources && Array.isArray(obj.sources)) {
4234
+ console.log("\n--- Sources ---");
4235
+ obj.sources.forEach((source, i) => {
4236
+ if (typeof source === "string") {
4237
+ console.log(` ${i + 1}. ${source}`);
4238
+ } else if (source && typeof source === "object") {
4239
+ const s = source;
4240
+ console.log(` ${i + 1}. ${s.title || s.url || JSON.stringify(source)}`);
4241
+ }
4242
+ });
4243
+ }
4244
+ return true;
4245
+ }
4246
+ return false;
4247
+ }
4248
+ };
4249
+ var memoryRecallHandler = {
4250
+ toolId: /memory-recall|recall/,
4251
+ async display(context) {
4252
+ const { result, flags } = context;
4253
+ if (flags.raw) {
4254
+ return false;
4255
+ }
4256
+ if (Array.isArray(result)) {
4257
+ if (result.length === 0) {
4258
+ console.log("No memories found.");
4259
+ return true;
4260
+ }
4261
+ console.log(`Found ${result.length} memory(s):
4262
+ `);
4263
+ result.forEach((mem, i) => {
4264
+ const memory = mem;
4265
+ console.log(`\u2500`.repeat(60));
4266
+ console.log(` ${i + 1}. ${memory.content || memory.text || JSON.stringify(memory).slice(0, 100)}`);
4267
+ if (memory.similarity) {
4268
+ console.log(` Similarity: ${(Number(memory.similarity) * 100).toFixed(1)}%`);
4269
+ }
4270
+ if (memory.keywords && Array.isArray(memory.keywords)) {
4271
+ console.log(` Keywords: ${memory.keywords.join(", ")}`);
4272
+ }
4273
+ console.log();
4274
+ });
4275
+ return true;
4276
+ }
4277
+ return false;
4278
+ }
4279
+ };
4280
+ function registerBuiltInHandlers(registry = globalHandlerRegistry) {
4281
+ registry.register(imageGenerationHandler);
4282
+ registry.register(finvizQuotesHandler);
4283
+ registry.register(csvQueryHandler);
4284
+ registry.register(webSearchHandler);
4285
+ registry.register(memoryRecallHandler);
4286
+ }
4287
+ registerBuiltInHandlers();
4288
+
3545
4289
  // src/cli/index.ts
3546
4290
  function printHelp() {
3547
4291
  console.log(`
@@ -3576,6 +4320,7 @@ Commands:
3576
4320
  config llm Show LLM configuration
3577
4321
 
3578
4322
  edge generate-keys Generate key pair for edge node encryption
4323
+ edge register <proc-node-id> Register a proc node for edge execution
3579
4324
  edge status Show edge node security status
3580
4325
 
3581
4326
  version Show version information
@@ -3593,7 +4338,12 @@ Options for 'run':
3593
4338
  --params, -p <json> Tool parameters as JSON
3594
4339
  --file, -f <path> Read parameters from file
3595
4340
  --raw Output raw JSON
4341
+ --table Output as table (if applicable)
4342
+ --terminal Output for terminal consumption (minimal formatting)
3596
4343
  --<key> <value> Pass individual parameters (e.g., --query "AI news")
4344
+ Arrays: --tickers AAPL,GOOGL (comma-separated)
4345
+ Numbers: --count 42
4346
+ Booleans: --enabled true
3597
4347
 
3598
4348
  Options for 'daemon start':
3599
4349
  --port <port> WebSocket port (default: 8765)
@@ -3609,6 +4359,7 @@ Examples:
3609
4359
  rainfall tools describe github-create-issue
3610
4360
  rainfall run exa-web-search -p '{"query": "AI news"}'
3611
4361
  rainfall run exa-web-search --query "AI news"
4362
+ rainfall run finviz-quotes --tickers AAPL,GOOGL,MSFT
3612
4363
  rainfall run github-create-issue --owner facebook --repo react --title "Bug"
3613
4364
  rainfall run article-summarize -f ./article.json
3614
4365
  rainfall daemon start
@@ -3626,6 +4377,24 @@ function getRainfall() {
3626
4377
  baseUrl: config.baseUrl
3627
4378
  });
3628
4379
  }
4380
+ async function fetchAllNodeIds(rainfall) {
4381
+ try {
4382
+ const client = rainfall.getClient();
4383
+ const subscriberId = await client.ensureSubscriberId();
4384
+ const result = await client.request(
4385
+ `/olympic/subscribers/${subscriberId}/nodes/_utils/node-list`
4386
+ );
4387
+ if (result.keys && Array.isArray(result.keys)) {
4388
+ return result.keys;
4389
+ }
4390
+ if (result.nodes && Array.isArray(result.nodes)) {
4391
+ return result.nodes.map((n) => n.id);
4392
+ }
4393
+ return [];
4394
+ } catch {
4395
+ return [];
4396
+ }
4397
+ }
3629
4398
  async function authLogin(args) {
3630
4399
  const apiKey = args[0] || process.env.RAINFALL_API_KEY;
3631
4400
  if (!apiKey) {
@@ -3712,6 +4481,100 @@ function formatSchema(obj, indent = 0) {
3712
4481
  }
3713
4482
  return lines.join("\n");
3714
4483
  }
4484
+ function levenshteinDistance(a, b) {
4485
+ const matrix = [];
4486
+ for (let i = 0; i <= b.length; i++) {
4487
+ matrix[i] = [i];
4488
+ }
4489
+ for (let j = 0; j <= a.length; j++) {
4490
+ matrix[0][j] = j;
4491
+ }
4492
+ for (let i = 1; i <= b.length; i++) {
4493
+ for (let j = 1; j <= a.length; j++) {
4494
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
4495
+ matrix[i][j] = matrix[i - 1][j - 1];
4496
+ } else {
4497
+ matrix[i][j] = Math.min(
4498
+ matrix[i - 1][j - 1] + 1,
4499
+ // substitution
4500
+ matrix[i][j - 1] + 1,
4501
+ // insertion
4502
+ matrix[i - 1][j] + 1
4503
+ // deletion
4504
+ );
4505
+ }
4506
+ }
4507
+ }
4508
+ return matrix[b.length][a.length];
4509
+ }
4510
+ function jaroWinklerSimilarity(a, b) {
4511
+ if (a === b) return 1;
4512
+ if (a.length === 0 || b.length === 0) return 0;
4513
+ const matchDistance = Math.floor(Math.max(a.length, b.length) / 2) - 1;
4514
+ const aMatches = new Array(a.length).fill(false);
4515
+ const bMatches = new Array(b.length).fill(false);
4516
+ let matches = 0;
4517
+ let transpositions = 0;
4518
+ for (let i = 0; i < a.length; i++) {
4519
+ const start = Math.max(0, i - matchDistance);
4520
+ const end = Math.min(i + matchDistance + 1, b.length);
4521
+ for (let j = start; j < end; j++) {
4522
+ if (bMatches[j] || a.charAt(i) !== b.charAt(j)) continue;
4523
+ aMatches[i] = true;
4524
+ bMatches[j] = true;
4525
+ matches++;
4526
+ break;
4527
+ }
4528
+ }
4529
+ if (matches === 0) return 0;
4530
+ let k = 0;
4531
+ for (let i = 0; i < a.length; i++) {
4532
+ if (!aMatches[i]) continue;
4533
+ while (!bMatches[k]) k++;
4534
+ if (a.charAt(i) !== b.charAt(k)) transpositions++;
4535
+ k++;
4536
+ }
4537
+ const jaro = (matches / a.length + matches / b.length + (matches - transpositions / 2) / matches) / 3;
4538
+ let prefixLength = 0;
4539
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
4540
+ if (a.charAt(i) === b.charAt(i)) {
4541
+ prefixLength++;
4542
+ } else {
4543
+ break;
4544
+ }
4545
+ }
4546
+ const scalingFactor = 0.1;
4547
+ return jaro + prefixLength * scalingFactor * (1 - jaro);
4548
+ }
4549
+ function calculateSimilarity(toolId, candidateId, description = "") {
4550
+ const lowerToolId = toolId.toLowerCase();
4551
+ const lowerCandidate = candidateId.toLowerCase();
4552
+ const prefix = lowerToolId.split("-")[0];
4553
+ const hasPrefix = lowerToolId.includes("-");
4554
+ const jwScore = jaroWinklerSimilarity(lowerToolId, lowerCandidate);
4555
+ const maxLen = Math.max(lowerToolId.length, lowerCandidate.length);
4556
+ const lvScore = maxLen === 0 ? 1 : 1 - levenshteinDistance(lowerToolId, lowerCandidate) / maxLen;
4557
+ let substringBoost = 0;
4558
+ if (lowerCandidate.includes(lowerToolId) || lowerToolId.includes(lowerCandidate)) {
4559
+ substringBoost = 0.4;
4560
+ }
4561
+ let prefixBoost = 0;
4562
+ if (hasPrefix && lowerCandidate === prefix) {
4563
+ prefixBoost = 0.5;
4564
+ }
4565
+ if (hasPrefix && lowerCandidate.startsWith(prefix + "-")) {
4566
+ prefixBoost = 0.35;
4567
+ }
4568
+ const descMatch = description.toLowerCase().includes(lowerToolId) ? 0.1 : 0;
4569
+ return jwScore * 0.4 + lvScore * 0.25 + substringBoost + prefixBoost + descMatch;
4570
+ }
4571
+ function findSimilarToolIds(toolId, toolIds) {
4572
+ const scored = toolIds.map((id) => ({
4573
+ id,
4574
+ score: calculateSimilarity(toolId, id)
4575
+ }));
4576
+ return scored.filter((item) => item.score > 0.35).sort((a, b) => b.score - a.score).slice(0, 5).map((item) => item.id);
4577
+ }
3715
4578
  async function describeTool(args) {
3716
4579
  const toolId = args[0];
3717
4580
  if (!toolId) {
@@ -3747,6 +4610,17 @@ async function describeTool(args) {
3747
4610
  console.log();
3748
4611
  } catch (error) {
3749
4612
  console.error(`Error: Tool '${toolId}' not found`);
4613
+ try {
4614
+ const allNodeIds = await fetchAllNodeIds(rainfall);
4615
+ const suggestions = findSimilarToolIds(toolId, allNodeIds);
4616
+ if (suggestions.length > 0) {
4617
+ console.error("\nDid you mean:");
4618
+ for (const suggestion of suggestions) {
4619
+ console.error(` \u2022 ${suggestion}`);
4620
+ }
4621
+ }
4622
+ } catch {
4623
+ }
3750
4624
  process.exit(1);
3751
4625
  }
3752
4626
  }
@@ -3790,20 +4664,30 @@ Options:
3790
4664
  -p, --params <json> Tool parameters as JSON string
3791
4665
  -f, --file <path> Read parameters from JSON file
3792
4666
  --raw Output raw JSON (no formatting)
4667
+ --table Output as table (if applicable)
4668
+ --terminal Output for terminal consumption (minimal formatting)
4669
+ --target-edge <id> Execute on specific edge node (for cross-node jobs)
3793
4670
  --<key> <value> Pass individual parameters (e.g., --query "AI news")
4671
+ Arrays: --tickers AAPL,GOOGL (comma-separated)
4672
+ Numbers: --count 42
4673
+ Booleans: --enabled true
3794
4674
 
3795
4675
  Examples:
3796
4676
  rainfall run figma-users-getMe
3797
4677
  rainfall run exa-web-search -p '{"query": "AI news"}'
3798
4678
  rainfall run exa-web-search --query "AI news"
4679
+ rainfall run finviz-quotes --tickers AAPL,GOOGL,MSFT
3799
4680
  rainfall run github-create-issue --owner facebook --repo react --title "Bug"
3800
4681
  rainfall run github-create-issue -f ./issue.json
4682
+ rainfall run exa-web-search --query "latest AI" --target-edge <edge-id>
3801
4683
  echo '{"query": "hello"}' | rainfall run exa-web-search
3802
4684
  `);
3803
4685
  return;
3804
4686
  }
3805
4687
  let params = {};
3806
4688
  const rawArgs = [];
4689
+ let displayMode = "pretty";
4690
+ let targetEdge;
3807
4691
  for (let i = 1; i < args.length; i++) {
3808
4692
  const arg = args[i];
3809
4693
  if (arg === "--params" || arg === "-p") {
@@ -3831,16 +4715,23 @@ Examples:
3831
4715
  process.exit(1);
3832
4716
  }
3833
4717
  } else if (arg === "--raw") {
4718
+ displayMode = "raw";
4719
+ } else if (arg === "--table") {
4720
+ displayMode = "table";
4721
+ } else if (arg === "--terminal") {
4722
+ displayMode = "terminal";
4723
+ } else if (arg === "--target-edge") {
4724
+ targetEdge = args[++i];
4725
+ if (!targetEdge) {
4726
+ console.error("Error: --target-edge requires an edge node ID");
4727
+ process.exit(1);
4728
+ }
3834
4729
  } else if (arg.startsWith("--")) {
3835
4730
  const key = arg.slice(2);
3836
4731
  const value = args[++i];
3837
4732
  if (value === void 0) {
3838
- console.error(`Error: ${arg} requires a value`);
3839
- process.exit(1);
3840
- }
3841
- try {
3842
- params[key] = JSON.parse(value);
3843
- } catch {
4733
+ params[key] = true;
4734
+ } else {
3844
4735
  params[key] = value;
3845
4736
  }
3846
4737
  } else {
@@ -3882,30 +4773,110 @@ Examples:
3882
4773
  }
3883
4774
  }
3884
4775
  const rainfall = getRainfall();
3885
- if (rawArgs.length === 1 && Object.keys(params).length === 0) {
3886
- try {
3887
- const schema = await rainfall.getToolSchema(toolId);
3888
- if (schema.parameters && typeof schema.parameters === "object") {
3889
- const paramEntries = Object.entries(schema.parameters);
3890
- const requiredParams = paramEntries.filter(([, p]) => !p.optional);
3891
- if (requiredParams.length === 1) {
3892
- const [paramName] = requiredParams[0];
3893
- params = { [paramName]: rawArgs[0] };
3894
- }
4776
+ let toolSchema;
4777
+ try {
4778
+ const fullSchema = await rainfall.getToolSchema(toolId);
4779
+ toolSchema = {
4780
+ parameters: fullSchema.parameters
4781
+ };
4782
+ } catch {
4783
+ }
4784
+ const cliFlags = /* @__PURE__ */ new Set(["--params", "-p", "--file", "-f", "--raw", "--table", "--terminal", "--target-edge"]);
4785
+ const toolArgs = args.slice(1).filter((arg, i, arr) => {
4786
+ if (cliFlags.has(arg)) {
4787
+ return false;
4788
+ }
4789
+ if (i > 0 && cliFlags.has(arr[i - 1])) {
4790
+ return false;
4791
+ }
4792
+ return true;
4793
+ });
4794
+ if (toolSchema?.parameters) {
4795
+ const { parseCliArgs: parseCliArgs2 } = await Promise.resolve().then(() => (init_param_parser(), param_parser_exports));
4796
+ const parsedParams = parseCliArgs2(
4797
+ toolArgs,
4798
+ {
4799
+ name: toolId,
4800
+ description: "",
4801
+ category: "",
4802
+ parameters: toolSchema.parameters
3895
4803
  }
3896
- } catch {
4804
+ );
4805
+ params = { ...parsedParams, ...params };
4806
+ }
4807
+ if (rawArgs.length === 1 && Object.keys(params).length === 0 && toolSchema?.parameters) {
4808
+ const paramEntries = Object.entries(toolSchema.parameters);
4809
+ const requiredParams = paramEntries.filter(([, p]) => !p.optional);
4810
+ if (requiredParams.length === 1) {
4811
+ const [paramName, paramSchema] = requiredParams[0];
4812
+ const { parseValue: parseValue2 } = await Promise.resolve().then(() => (init_param_parser(), param_parser_exports));
4813
+ params = { [paramName]: parseValue2(rawArgs[0], paramSchema) };
3897
4814
  }
3898
4815
  }
4816
+ const handler = globalHandlerRegistry.findHandler(toolId);
4817
+ const toolContext = {
4818
+ rainfall,
4819
+ toolId,
4820
+ params,
4821
+ args: rawArgs,
4822
+ flags: { raw: displayMode === "raw" }
4823
+ };
3899
4824
  try {
3900
- const result = await rainfall.executeTool(toolId, params);
3901
- if (args.includes("--raw")) {
3902
- console.log(JSON.stringify(result));
4825
+ let executionParams = params;
4826
+ let preflightContext;
4827
+ let skipExecution;
4828
+ if (handler?.preflight) {
4829
+ const preflightResult = await handler.preflight(toolContext);
4830
+ if (preflightResult) {
4831
+ if (preflightResult.skipExecution !== void 0) {
4832
+ skipExecution = preflightResult.skipExecution;
4833
+ }
4834
+ if (preflightResult.params) {
4835
+ executionParams = preflightResult.params;
4836
+ }
4837
+ preflightContext = preflightResult.context;
4838
+ }
4839
+ }
4840
+ let result;
4841
+ if (skipExecution !== void 0) {
4842
+ result = skipExecution;
4843
+ } else if (targetEdge) {
4844
+ result = await rainfall.executeTool(toolId, executionParams, { targetEdge });
3903
4845
  } else {
3904
- console.log(JSON.stringify(result, null, 2));
4846
+ result = await rainfall.executeTool(toolId, executionParams);
4847
+ }
4848
+ const postflightContext = {
4849
+ ...toolContext,
4850
+ result,
4851
+ preflightContext
4852
+ };
4853
+ if (handler?.postflight) {
4854
+ await handler.postflight(postflightContext);
4855
+ }
4856
+ let displayed = false;
4857
+ if (handler?.display) {
4858
+ displayed = await handler.display({ ...postflightContext, flags: { ...toolContext.flags, mode: displayMode } });
4859
+ }
4860
+ if (!displayed) {
4861
+ const output = await formatResult(result, { mode: displayMode });
4862
+ console.log(output);
3905
4863
  }
3906
4864
  } catch (error) {
3907
4865
  const message = error instanceof Error ? error.message : String(error);
3908
4866
  console.error(`Error: ${message}`);
4867
+ if (message.toLowerCase().includes("not found") || message.toLowerCase().includes("not found")) {
4868
+ try {
4869
+ const allNodeIds = await fetchAllNodeIds(rainfall);
4870
+ const suggestions = findSimilarToolIds(toolId, allNodeIds);
4871
+ if (suggestions.length > 0) {
4872
+ console.error("\nDid you mean:");
4873
+ for (const suggestion of suggestions) {
4874
+ console.error(` \u2022 ${suggestion}`);
4875
+ }
4876
+ }
4877
+ } catch {
4878
+ }
4879
+ }
3909
4880
  process.exit(1);
3910
4881
  }
3911
4882
  }
@@ -4022,7 +4993,7 @@ async function upgrade() {
4022
4993
  console.log(`Running: ${command} ${args.join(" ")}`);
4023
4994
  console.log();
4024
4995
  return new Promise((resolve, reject) => {
4025
- const child = (0, import_child_process.spawn)(command, args, {
4996
+ const child = (0, import_child_process2.spawn)(command, args, {
4026
4997
  stdio: "inherit",
4027
4998
  shell: true
4028
4999
  });
@@ -4198,7 +5169,7 @@ async function edgeGenerateKeys() {
4198
5169
  console.log(" Private:", privateKeyPath);
4199
5170
  console.log("\n\u{1F4CB} To register this edge node:");
4200
5171
  console.log(" 1. Copy the public key above");
4201
- console.log(" 2. Register with: rainfall edge register <public-key>");
5172
+ console.log(" 2. Register proc node with: rainfall edge register <proc-node-id> --public-key <key>");
4202
5173
  console.log(" 3. The backend will return an edgeNodeSecret (JWT)");
4203
5174
  console.log(" 4. Store the secret securely - it expires in 30 days");
4204
5175
  } catch (error) {
@@ -4206,6 +5177,98 @@ async function edgeGenerateKeys() {
4206
5177
  process.exit(1);
4207
5178
  }
4208
5179
  }
5180
+ async function edgeRegister(args) {
5181
+ const procNodeId = args[0];
5182
+ if (!procNodeId) {
5183
+ console.error("Error: Proc node ID required");
5184
+ console.error("\nUsage: rainfall edge register <proc-node-id> [options]");
5185
+ console.error("\nOptions:");
5186
+ console.error(" --public-key <key> Public key for encryption (optional)");
5187
+ console.error(" --list <id1,id2,...> Register multiple proc nodes (comma-separated)");
5188
+ console.error("\nExamples:");
5189
+ console.error(" rainfall edge register exa-web-search");
5190
+ console.error(' rainfall edge register exa-web-search --public-key "base64key..."');
5191
+ console.error(' rainfall edge register --list "exa-web-search,github-create-issue"');
5192
+ process.exit(1);
5193
+ }
5194
+ const rainfall = getRainfall();
5195
+ const config = loadConfig();
5196
+ let publicKey;
5197
+ let procNodeIds = [procNodeId];
5198
+ for (let i = 1; i < args.length; i++) {
5199
+ const arg = args[i];
5200
+ if (arg === "--public-key" || arg === "-k") {
5201
+ publicKey = args[++i];
5202
+ } else if (arg === "--list" || arg === "-l") {
5203
+ const list = args[++i];
5204
+ if (list) {
5205
+ procNodeIds = list.split(",").map((id) => id.trim());
5206
+ }
5207
+ }
5208
+ }
5209
+ if (!publicKey) {
5210
+ const configDir = getConfigDir();
5211
+ const keysDir = (0, import_path2.join)(configDir, "keys");
5212
+ const publicKeyPath = (0, import_path2.join)(keysDir, "edge-node.pub");
5213
+ if ((0, import_fs2.existsSync)(publicKeyPath)) {
5214
+ publicKey = (0, import_fs2.readFileSync)(publicKeyPath, "utf-8");
5215
+ }
5216
+ }
5217
+ console.log(`\u{1F310} Registering ${procNodeIds.length} proc node(s) for edge execution...
5218
+ `);
5219
+ try {
5220
+ let edgeNodeId = config.edgeNodeId;
5221
+ if (!edgeNodeId) {
5222
+ console.log("\u{1F4E1} Registering edge node with backend...");
5223
+ const registerResult = await rainfall.executeTool("register-edge-node", {
5224
+ hostname: process.env.HOSTNAME || "local-edge",
5225
+ capabilities: procNodeIds,
5226
+ version: "1.0.0",
5227
+ metadata: {
5228
+ publicKey: publicKey || void 0,
5229
+ source: "rainfall-devkit-cli"
5230
+ }
5231
+ });
5232
+ edgeNodeId = registerResult.edgeNodeId;
5233
+ console.log(` Edge node registered: ${edgeNodeId}`);
5234
+ } else {
5235
+ console.log(` Using existing edge node: ${edgeNodeId}`);
5236
+ }
5237
+ console.log("\n\u{1F4E1} Registering proc nodes...");
5238
+ const result = await rainfall.executeTool("register-proc-edge-nodes", {
5239
+ edgeNodeId,
5240
+ procNodeIds,
5241
+ publicKey,
5242
+ hostname: process.env.HOSTNAME || "local-edge"
5243
+ });
5244
+ if (!result.success) {
5245
+ console.error("\u274C Registration failed");
5246
+ process.exit(1);
5247
+ }
5248
+ config.edgeNodeId = result.edgeNodeId;
5249
+ config.edgeNodeSecret = result.edgeNodeSecret;
5250
+ config.edgeNodeKeysPath = (0, import_path2.join)(getConfigDir(), "keys");
5251
+ saveConfig(config);
5252
+ console.log("\u2705 Proc node(s) registered successfully!\n");
5253
+ console.log("Edge Node ID:", result.edgeNodeId);
5254
+ console.log("Proc Nodes Registered:");
5255
+ for (const nodeId of result.registeredProcNodes) {
5256
+ console.log(` \u2022 ${nodeId}`);
5257
+ }
5258
+ console.log("\n\u{1F510} Edge node secret stored in config.");
5259
+ console.log(" This secret is used for authentication with the backend.");
5260
+ console.log("\n\u{1F4CB} You can now run tools on this edge node:");
5261
+ console.log(` rainfall run ${procNodeIds[0]} --target-edge ${result.edgeNodeId}`);
5262
+ } catch (error) {
5263
+ const message = error instanceof Error ? error.message : String(error);
5264
+ console.error("\u274C Failed to register proc node:", message);
5265
+ if (message.includes("not found") || message.includes("does not exist")) {
5266
+ console.error("\n\u{1F4A1} The backend may not have the registration tools yet.");
5267
+ console.error(" Make sure you are running the latest version of Rainyday.");
5268
+ }
5269
+ process.exit(1);
5270
+ }
5271
+ }
4209
5272
  async function edgeStatus() {
4210
5273
  const configDir = getConfigDir();
4211
5274
  const keysDir = (0, import_path2.join)(configDir, "keys");
@@ -4223,23 +5286,36 @@ async function edgeStatus() {
4223
5286
  console.log(" " + publicKey.substring(0, 50) + "...");
4224
5287
  }
4225
5288
  const config = loadConfig();
5289
+ console.log("\nRegistration:");
4226
5290
  if (config.edgeNodeId) {
4227
- console.log("\nRegistration:");
4228
5291
  console.log(" Edge Node ID:", config.edgeNodeId);
5292
+ } else {
5293
+ console.log(" Edge Node ID: \u274C Not registered");
4229
5294
  }
4230
5295
  if (config.edgeNodeSecret) {
4231
- console.log(" JWT Secret: \u2705 Present (expires: check with backend)");
5296
+ console.log(" JWT Secret: \u2705 Present");
5297
+ const masked = config.edgeNodeSecret.substring(0, 10) + "..." + config.edgeNodeSecret.substring(config.edgeNodeSecret.length - 4);
5298
+ console.log(" (" + masked + ")");
4232
5299
  } else {
4233
5300
  console.log(" JWT Secret: \u274C Not configured");
4234
5301
  }
5302
+ if (config.procNodeIds && config.procNodeIds.length > 0) {
5303
+ console.log("\nRegistered Proc Nodes:");
5304
+ for (const nodeId of config.procNodeIds) {
5305
+ console.log(` \u2022 ${nodeId}`);
5306
+ }
5307
+ }
4235
5308
  console.log("\n\u{1F4DA} Next steps:");
4236
5309
  if (!hasPublicKey) {
4237
5310
  console.log(" 1. Run: rainfall edge generate-keys");
5311
+ console.log(" 2. Run: rainfall edge register <proc-node-id>");
4238
5312
  } else if (!config.edgeNodeSecret) {
4239
- console.log(" 1. Register your edge node with the backend");
4240
- console.log(" 2. Store the returned edgeNodeSecret in config");
5313
+ console.log(" 1. Register your proc node:");
5314
+ console.log(" rainfall edge register exa-web-search");
4241
5315
  } else {
4242
5316
  console.log(" Edge node is configured and ready for secure operation");
5317
+ console.log(" Run tools on this edge node:");
5318
+ console.log(` rainfall run <tool> --target-edge ${config.edgeNodeId}`);
4243
5319
  }
4244
5320
  }
4245
5321
  async function main() {
@@ -4352,12 +5428,15 @@ async function main() {
4352
5428
  case "generate-keys":
4353
5429
  await edgeGenerateKeys();
4354
5430
  break;
5431
+ case "register":
5432
+ await edgeRegister(rest);
5433
+ break;
4355
5434
  case "status":
4356
5435
  await edgeStatus();
4357
5436
  break;
4358
5437
  default:
4359
5438
  console.error("Error: Unknown edge subcommand");
4360
- console.error("\nUsage: rainfall edge <generate-keys|status>");
5439
+ console.error("\nUsage: rainfall edge <generate-keys|register|status>");
4361
5440
  process.exit(1);
4362
5441
  }
4363
5442
  break;