@rainfall-devkit/sdk 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,68 @@ 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();
291
494
  const response = await this.request(`/olympic/subscribers/${subscriberId}/nodes/${toolId}`, {
292
495
  method: "POST",
293
496
  body: params || {}
294
497
  }, options);
498
+ if (response.success === false) {
499
+ const errorMessage = typeof response.error === "string" ? response.error : JSON.stringify(response.error);
500
+ throw new RainfallError(
501
+ `Tool execution failed: ${errorMessage}`,
502
+ "TOOL_EXECUTION_ERROR",
503
+ 400,
504
+ { toolId, error: response.error }
505
+ );
506
+ }
295
507
  return response.result;
296
508
  }
509
+ /**
510
+ * Validate parameters for a tool without executing it
511
+ * Fetches the tool schema and validates the provided params
512
+ *
513
+ * @param toolId - The ID of the tool to validate params for
514
+ * @param params - Parameters to validate
515
+ * @returns Validation result with detailed error information
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * const result = await client.validateToolParams('finviz-quotes', { tickers: ['AAPL'] });
520
+ * if (!result.valid) {
521
+ * console.log('Validation errors:', result.errors);
522
+ * }
523
+ * ```
524
+ */
525
+ async validateToolParams(toolId, params) {
526
+ try {
527
+ const schema = await fetchToolSchema(this, toolId);
528
+ return validateParams(schema, params, toolId);
529
+ } catch (error) {
530
+ if (error instanceof RainfallError && error.statusCode === 404) {
531
+ return {
532
+ valid: false,
533
+ errors: [{ path: toolId, message: `Tool '${toolId}' not found` }]
534
+ };
535
+ }
536
+ return { valid: true, errors: [] };
537
+ }
538
+ }
297
539
  /**
298
540
  * List all available tools
299
541
  */
@@ -316,11 +558,20 @@ var init_client = __esm({
316
558
  }
317
559
  /**
318
560
  * Get tool schema/parameters
561
+ *
562
+ * @param toolId - The ID of the tool to get schema for
563
+ * @returns Tool schema including parameters and output definitions
319
564
  */
320
565
  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;
566
+ const schema = await fetchToolSchema(this, toolId);
567
+ return {
568
+ name: schema.name,
569
+ description: schema.description,
570
+ category: schema.category,
571
+ parameters: schema.parameters,
572
+ output: schema.output,
573
+ metadata: schema.metadata || {}
574
+ };
324
575
  }
325
576
  /**
326
577
  * Get subscriber info
@@ -898,9 +1149,40 @@ var init_sdk = __esm({
898
1149
  }
899
1150
  /**
900
1151
  * Execute any tool by ID (low-level access)
1152
+ *
1153
+ * @param toolId - The ID of the tool to execute
1154
+ * @param params - Parameters to pass to the tool
1155
+ * @param options - Execution options including skipValidation to bypass param validation
1156
+ *
1157
+ * @example
1158
+ * ```typescript
1159
+ * // Execute with validation (default)
1160
+ * const result = await rainfall.executeTool('finviz-quotes', { tickers: ['AAPL'] });
1161
+ *
1162
+ * // Execute without validation
1163
+ * const result = await rainfall.executeTool('finviz-quotes', { tickers: ['AAPL'] }, { skipValidation: true });
1164
+ * ```
901
1165
  */
902
- async executeTool(toolId, params) {
903
- return this.client.executeTool(toolId, params);
1166
+ async executeTool(toolId, params, options) {
1167
+ return this.client.executeTool(toolId, params, options);
1168
+ }
1169
+ /**
1170
+ * Validate parameters for a tool without executing it
1171
+ *
1172
+ * @param toolId - The ID of the tool to validate params for
1173
+ * @param params - Parameters to validate
1174
+ * @returns Validation result with detailed error information
1175
+ *
1176
+ * @example
1177
+ * ```typescript
1178
+ * const result = await rainfall.validateToolParams('finviz-quotes', { tickers: ['AAPL'] });
1179
+ * if (!result.valid) {
1180
+ * console.log('Validation errors:', result.errors);
1181
+ * }
1182
+ * ```
1183
+ */
1184
+ async validateToolParams(toolId, params) {
1185
+ return this.client.validateToolParams(toolId, params);
904
1186
  }
905
1187
  /**
906
1188
  * Get current subscriber info and usage
@@ -1058,6 +1340,291 @@ var init_config = __esm({
1058
1340
  }
1059
1341
  });
1060
1342
 
1343
+ // src/cli/core/display.ts
1344
+ var display_exports = {};
1345
+ __export(display_exports, {
1346
+ detectImageData: () => detectImageData,
1347
+ detectImageSupport: () => detectImageSupport,
1348
+ displayImage: () => displayImage,
1349
+ formatAsTable: () => formatAsTable,
1350
+ formatResult: () => formatResult
1351
+ });
1352
+ function detectImageSupport() {
1353
+ if (process.env.TERM_PROGRAM === "iTerm.app") {
1354
+ return { supported: true, command: "imgcat" };
1355
+ }
1356
+ if (process.env.KITTY_WINDOW_ID) {
1357
+ return { supported: true, command: "kitty +kitten icat" };
1358
+ }
1359
+ try {
1360
+ return { supported: true, command: "xan" };
1361
+ } catch {
1362
+ }
1363
+ try {
1364
+ return { supported: true, command: "catimg" };
1365
+ } catch {
1366
+ }
1367
+ return { supported: false };
1368
+ }
1369
+ async function displayImage(imageData, options = {}) {
1370
+ const imageCommand = options.imageCommand || detectImageSupport().command;
1371
+ if (!imageCommand) {
1372
+ const { writeFileSync: writeFileSync3 } = await import("fs");
1373
+ const { tmpdir } = await import("os");
1374
+ const { join: join3 } = await import("path");
1375
+ const tempPath = join3(tmpdir(), `rainfall-image-${Date.now()}.png`);
1376
+ const buffer = typeof imageData === "string" ? Buffer.from(imageData, "base64") : imageData;
1377
+ writeFileSync3(tempPath, buffer);
1378
+ console.log(`Image saved to: ${tempPath}`);
1379
+ return;
1380
+ }
1381
+ return new Promise((resolve, reject) => {
1382
+ const buffer = typeof imageData === "string" ? Buffer.from(imageData, "base64") : imageData;
1383
+ const child = (0, import_child_process.spawn)(imageCommand, [], {
1384
+ stdio: ["pipe", "inherit", "inherit"],
1385
+ shell: true
1386
+ });
1387
+ child.stdin.write(buffer);
1388
+ child.stdin.end();
1389
+ child.on("close", (code) => {
1390
+ if (code === 0) {
1391
+ resolve();
1392
+ } else {
1393
+ reject(new Error(`Image display failed with code ${code}`));
1394
+ }
1395
+ });
1396
+ child.on("error", reject);
1397
+ });
1398
+ }
1399
+ function formatAsTable(data, columns) {
1400
+ if (!Array.isArray(data) || data.length === 0) {
1401
+ return "No data";
1402
+ }
1403
+ const cols = columns || Object.keys(data[0]);
1404
+ const widths = {};
1405
+ for (const col of cols) {
1406
+ widths[col] = Math.max(
1407
+ col.length,
1408
+ ...data.map((row) => {
1409
+ const val = row?.[col];
1410
+ return String(val ?? "").slice(0, 50).length;
1411
+ })
1412
+ );
1413
+ }
1414
+ const lines = [];
1415
+ const header = cols.map((col) => col.padEnd(widths[col])).join(" ");
1416
+ lines.push(header);
1417
+ lines.push(cols.map((col) => "-".repeat(widths[col])).join(" "));
1418
+ for (const row of data) {
1419
+ const line = cols.map((col) => {
1420
+ const val = row?.[col];
1421
+ const str = String(val ?? "").slice(0, 50);
1422
+ return str.padEnd(widths[col]);
1423
+ }).join(" ");
1424
+ lines.push(line);
1425
+ }
1426
+ return lines.join("\n");
1427
+ }
1428
+ async function formatResult(result, options = {}) {
1429
+ const mode = options.mode || "pretty";
1430
+ switch (mode) {
1431
+ case "raw":
1432
+ return JSON.stringify(result);
1433
+ case "pretty":
1434
+ return JSON.stringify(result, null, 2);
1435
+ case "table":
1436
+ if (Array.isArray(result)) {
1437
+ return formatAsTable(result, options.columns);
1438
+ }
1439
+ return JSON.stringify(result, null, 2);
1440
+ case "terminal":
1441
+ if (typeof result === "string") {
1442
+ return result;
1443
+ }
1444
+ if (Array.isArray(result) && result.every((r) => typeof r === "string")) {
1445
+ return result.join("\n");
1446
+ }
1447
+ return JSON.stringify(result);
1448
+ default:
1449
+ return JSON.stringify(result, null, 2);
1450
+ }
1451
+ }
1452
+ function detectImageData(result) {
1453
+ if (!result || typeof result !== "object") {
1454
+ return { hasImage: false };
1455
+ }
1456
+ const obj = result;
1457
+ const imageFields = ["image", "imageData", "imageBase64", "png", "jpeg", "data"];
1458
+ for (const field of imageFields) {
1459
+ if (obj[field] && typeof obj[field] === "string") {
1460
+ const value = obj[field];
1461
+ if (value.startsWith("data:image/") || value.length > 100) {
1462
+ return {
1463
+ hasImage: true,
1464
+ imageData: value.startsWith("data:image/") ? value.split(",")[1] : value
1465
+ };
1466
+ }
1467
+ }
1468
+ }
1469
+ if (obj.url && typeof obj.url === "string" && (obj.url.endsWith(".png") || obj.url.endsWith(".jpg") || obj.url.endsWith(".jpeg"))) {
1470
+ return { hasImage: true, imagePath: obj.url };
1471
+ }
1472
+ return { hasImage: false };
1473
+ }
1474
+ var import_child_process;
1475
+ var init_display = __esm({
1476
+ "src/cli/core/display.ts"() {
1477
+ "use strict";
1478
+ init_cjs_shims();
1479
+ import_child_process = require("child_process");
1480
+ }
1481
+ });
1482
+
1483
+ // src/cli/core/param-parser.ts
1484
+ var param_parser_exports = {};
1485
+ __export(param_parser_exports, {
1486
+ formatValueForDisplay: () => formatValueForDisplay,
1487
+ generateParamExample: () => generateParamExample,
1488
+ parseCliArgs: () => parseCliArgs,
1489
+ parseValue: () => parseValue
1490
+ });
1491
+ function parseValue(value, schema, options = {}) {
1492
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1493
+ if (!schema) {
1494
+ try {
1495
+ return JSON.parse(value);
1496
+ } catch {
1497
+ return value;
1498
+ }
1499
+ }
1500
+ const expectedType = schema.type;
1501
+ switch (expectedType) {
1502
+ case "array":
1503
+ return parseArrayValue(value, schema, opts);
1504
+ case "number":
1505
+ return parseNumberValue(value, opts);
1506
+ case "boolean":
1507
+ return parseBooleanValue(value, opts);
1508
+ case "string":
1509
+ return value;
1510
+ case "object":
1511
+ try {
1512
+ return JSON.parse(value);
1513
+ } catch {
1514
+ return value;
1515
+ }
1516
+ default:
1517
+ try {
1518
+ return JSON.parse(value);
1519
+ } catch {
1520
+ return value;
1521
+ }
1522
+ }
1523
+ }
1524
+ function parseArrayValue(value, schema, options) {
1525
+ if (value.startsWith("[") && value.endsWith("]")) {
1526
+ try {
1527
+ return JSON.parse(value);
1528
+ } catch {
1529
+ }
1530
+ }
1531
+ if (options.arrayInterpolation && value.includes(options.arraySeparator)) {
1532
+ const items = value.split(options.arraySeparator).map((s) => s.trim()).filter(Boolean);
1533
+ if (schema.items) {
1534
+ return items.map((item) => parseValue(item, schema.items, { ...options, arrayInterpolation: false }));
1535
+ }
1536
+ return items;
1537
+ }
1538
+ if (schema.items) {
1539
+ return [parseValue(value, schema.items, { ...options, arrayInterpolation: false })];
1540
+ }
1541
+ return [value];
1542
+ }
1543
+ function parseNumberValue(value, options) {
1544
+ if (!options.numberParsing) {
1545
+ return value;
1546
+ }
1547
+ const num = Number(value);
1548
+ if (!isNaN(num) && isFinite(num)) {
1549
+ return num;
1550
+ }
1551
+ return value;
1552
+ }
1553
+ function parseBooleanValue(value, options) {
1554
+ if (!options.booleanParsing) {
1555
+ return value;
1556
+ }
1557
+ const lower = value.toLowerCase();
1558
+ if (lower === "true" || lower === "yes" || lower === "1" || lower === "on") {
1559
+ return true;
1560
+ }
1561
+ if (lower === "false" || lower === "no" || lower === "0" || lower === "off") {
1562
+ return false;
1563
+ }
1564
+ return value;
1565
+ }
1566
+ function parseCliArgs(args, schema, options = {}) {
1567
+ const params = {};
1568
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1569
+ const parameters = schema?.parameters || {};
1570
+ for (let i = 0; i < args.length; i++) {
1571
+ const arg = args[i];
1572
+ if (arg.startsWith("--")) {
1573
+ const key = arg.slice(2);
1574
+ const value = args[++i];
1575
+ if (value === void 0) {
1576
+ params[key] = true;
1577
+ continue;
1578
+ }
1579
+ const paramSchema = parameters[key];
1580
+ params[key] = parseValue(value, paramSchema, opts);
1581
+ }
1582
+ }
1583
+ return params;
1584
+ }
1585
+ function formatValueForDisplay(value) {
1586
+ if (value === null) return "null";
1587
+ if (value === void 0) return "undefined";
1588
+ if (typeof value === "string") return value;
1589
+ if (typeof value === "number") return String(value);
1590
+ if (typeof value === "boolean") return String(value);
1591
+ if (Array.isArray(value)) {
1592
+ return value.map(formatValueForDisplay).join(",");
1593
+ }
1594
+ return JSON.stringify(value);
1595
+ }
1596
+ function generateParamExample(key, schema) {
1597
+ const type = schema.type || "string";
1598
+ switch (type) {
1599
+ case "array":
1600
+ if (schema.items?.type === "string") {
1601
+ return `--${key} item1,item2,item3`;
1602
+ }
1603
+ return `--${key} '["item1", "item2"]'`;
1604
+ case "number":
1605
+ return `--${key} 42`;
1606
+ case "boolean":
1607
+ return `--${key} true`;
1608
+ case "object":
1609
+ return `--${key} '{"key": "value"}'`;
1610
+ default:
1611
+ return `--${key} "value"`;
1612
+ }
1613
+ }
1614
+ var DEFAULT_OPTIONS;
1615
+ var init_param_parser = __esm({
1616
+ "src/cli/core/param-parser.ts"() {
1617
+ "use strict";
1618
+ init_cjs_shims();
1619
+ DEFAULT_OPTIONS = {
1620
+ arrayInterpolation: true,
1621
+ numberParsing: true,
1622
+ booleanParsing: true,
1623
+ arraySeparator: ","
1624
+ };
1625
+ }
1626
+ });
1627
+
1061
1628
  // src/services/networked.ts
1062
1629
  var RainfallNetworkedExecutor;
1063
1630
  var init_networked = __esm({
@@ -1717,7 +2284,7 @@ var init_listeners = __esm({
1717
2284
  });
1718
2285
 
1719
2286
  // src/services/mcp-proxy.ts
1720
- var import_ws, import_client2, import_stdio, import_streamableHttp, import_types, MCPProxyHub;
2287
+ var import_ws, import_client2, import_stdio, import_streamableHttp, import_types2, MCPProxyHub;
1721
2288
  var init_mcp_proxy = __esm({
1722
2289
  "src/services/mcp-proxy.ts"() {
1723
2290
  "use strict";
@@ -1726,7 +2293,7 @@ var init_mcp_proxy = __esm({
1726
2293
  import_client2 = require("@modelcontextprotocol/sdk/client/index.js");
1727
2294
  import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
1728
2295
  import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
1729
- import_types = require("@modelcontextprotocol/sdk/types.js");
2296
+ import_types2 = require("@modelcontextprotocol/sdk/types.js");
1730
2297
  MCPProxyHub = class {
1731
2298
  clients = /* @__PURE__ */ new Map();
1732
2299
  options;
@@ -1845,7 +2412,7 @@ var init_mcp_proxy = __esm({
1845
2412
  method: "tools/list",
1846
2413
  params: {}
1847
2414
  },
1848
- import_types.ListToolsResultSchema
2415
+ import_types2.ListToolsResultSchema
1849
2416
  );
1850
2417
  const tools = toolsResult.tools.map((tool) => ({
1851
2418
  name: tool.name,
@@ -1959,7 +2526,7 @@ var init_mcp_proxy = __esm({
1959
2526
  arguments: args
1960
2527
  }
1961
2528
  },
1962
- import_types.CallToolResultSchema
2529
+ import_types2.CallToolResultSchema
1963
2530
  ),
1964
2531
  new Promise(
1965
2532
  (_, reject) => setTimeout(() => reject(new Error(`Tool call timeout after ${timeout}ms`)), timeout)
@@ -1969,7 +2536,7 @@ var init_mcp_proxy = __esm({
1969
2536
  return this.formatToolResult(result);
1970
2537
  } catch (error) {
1971
2538
  this.log(`[${requestId}] Failed:`, error instanceof Error ? error.message : error);
1972
- if (error instanceof import_types.McpError) {
2539
+ if (error instanceof import_types2.McpError) {
1973
2540
  throw new Error(`MCP Error (${toolName}): ${error.message} (code: ${error.code})`);
1974
2541
  }
1975
2542
  throw error;
@@ -2053,7 +2620,7 @@ var init_mcp_proxy = __esm({
2053
2620
  method: "tools/list",
2054
2621
  params: {}
2055
2622
  },
2056
- import_types.ListToolsResultSchema
2623
+ import_types2.ListToolsResultSchema
2057
2624
  );
2058
2625
  client.tools = toolsResult.tools.map((tool) => ({
2059
2626
  name: tool.name,
@@ -2084,7 +2651,7 @@ var init_mcp_proxy = __esm({
2084
2651
  method: "resources/list",
2085
2652
  params: {}
2086
2653
  },
2087
- import_types.ListResourcesResultSchema
2654
+ import_types2.ListResourcesResultSchema
2088
2655
  );
2089
2656
  results.push({
2090
2657
  clientName: name,
@@ -2110,7 +2677,7 @@ var init_mcp_proxy = __esm({
2110
2677
  method: "resources/read",
2111
2678
  params: { uri }
2112
2679
  },
2113
- import_types.ReadResourceResultSchema
2680
+ import_types2.ReadResourceResultSchema
2114
2681
  );
2115
2682
  return result;
2116
2683
  } else {
@@ -2121,7 +2688,7 @@ var init_mcp_proxy = __esm({
2121
2688
  method: "resources/read",
2122
2689
  params: { uri }
2123
2690
  },
2124
- import_types.ReadResourceResultSchema
2691
+ import_types2.ReadResourceResultSchema
2125
2692
  );
2126
2693
  return { clientName: name, ...result };
2127
2694
  } catch {
@@ -2145,7 +2712,7 @@ var init_mcp_proxy = __esm({
2145
2712
  method: "prompts/list",
2146
2713
  params: {}
2147
2714
  },
2148
- import_types.ListPromptsResultSchema
2715
+ import_types2.ListPromptsResultSchema
2149
2716
  );
2150
2717
  results.push({
2151
2718
  clientName: name,
@@ -2171,7 +2738,7 @@ var init_mcp_proxy = __esm({
2171
2738
  method: "prompts/get",
2172
2739
  params: { name, arguments: args }
2173
2740
  },
2174
- import_types.GetPromptResultSchema
2741
+ import_types2.GetPromptResultSchema
2175
2742
  );
2176
2743
  return result;
2177
2744
  } else {
@@ -2182,7 +2749,7 @@ var init_mcp_proxy = __esm({
2182
2749
  method: "prompts/get",
2183
2750
  params: { name, arguments: args }
2184
2751
  },
2185
- import_types.GetPromptResultSchema
2752
+ import_types2.GetPromptResultSchema
2186
2753
  );
2187
2754
  return { clientName: cName, ...result };
2188
2755
  } catch {
@@ -2204,7 +2771,7 @@ var init_mcp_proxy = __esm({
2204
2771
  method: "tools/list",
2205
2772
  params: {}
2206
2773
  },
2207
- import_types.ListToolsResultSchema
2774
+ import_types2.ListToolsResultSchema
2208
2775
  );
2209
2776
  results.set(name, {
2210
2777
  status: "healthy",
@@ -3274,17 +3841,17 @@ var import_path2 = require("path");
3274
3841
  var import_url = require("url");
3275
3842
  init_sdk();
3276
3843
  init_config();
3277
- var import_child_process = require("child_process");
3844
+ var import_child_process2 = require("child_process");
3278
3845
 
3279
3846
  // src/security/edge-node.ts
3280
3847
  init_cjs_shims();
3281
- var sodium = __toESM(require("libsodium-wrappers"));
3848
+ var import_libsodium_wrappers_sumo = __toESM(require("libsodium-wrappers-sumo"));
3282
3849
  var EdgeNodeSecurity = class {
3283
3850
  sodiumReady;
3284
3851
  backendSecret;
3285
3852
  keyPair;
3286
3853
  constructor(options = {}) {
3287
- this.sodiumReady = sodium.ready;
3854
+ this.sodiumReady = import_libsodium_wrappers_sumo.default.ready;
3288
3855
  this.backendSecret = options.backendSecret;
3289
3856
  this.keyPair = options.keyPair;
3290
3857
  }
@@ -3414,7 +3981,7 @@ var EdgeNodeSecurity = class {
3414
3981
  */
3415
3982
  async generateKeyPair() {
3416
3983
  await this.sodiumReady;
3417
- const keyPair = sodium.crypto_box_keypair();
3984
+ const keyPair = import_libsodium_wrappers_sumo.default.crypto_box_keypair();
3418
3985
  return {
3419
3986
  publicKey: this.bytesToBase64(keyPair.publicKey),
3420
3987
  privateKey: this.bytesToBase64(keyPair.privateKey)
@@ -3429,10 +3996,10 @@ var EdgeNodeSecurity = class {
3429
3996
  throw new Error("Local key pair not configured");
3430
3997
  }
3431
3998
  const targetPublicKey = this.base64ToBytes(targetPublicKeyBase64);
3432
- const ephemeralKeyPair = sodium.crypto_box_keypair();
3433
- const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
3999
+ const ephemeralKeyPair = import_libsodium_wrappers_sumo.default.crypto_box_keypair();
4000
+ const nonce = import_libsodium_wrappers_sumo.default.randombytes_buf(import_libsodium_wrappers_sumo.default.crypto_box_NONCEBYTES);
3434
4001
  const message = new TextEncoder().encode(plaintext);
3435
- const ciphertext = sodium.crypto_box_easy(
4002
+ const ciphertext = import_libsodium_wrappers_sumo.default.crypto_box_easy(
3436
4003
  message,
3437
4004
  nonce,
3438
4005
  targetPublicKey,
@@ -3456,7 +4023,7 @@ var EdgeNodeSecurity = class {
3456
4023
  const ephemeralPublicKey = this.base64ToBytes(encrypted.ephemeralPublicKey);
3457
4024
  const nonce = this.base64ToBytes(encrypted.nonce);
3458
4025
  const ciphertext = this.base64ToBytes(encrypted.ciphertext);
3459
- const decrypted = sodium.crypto_box_open_easy(
4026
+ const decrypted = import_libsodium_wrappers_sumo.default.crypto_box_open_easy(
3460
4027
  ciphertext,
3461
4028
  nonce,
3462
4029
  ephemeralPublicKey,
@@ -3473,9 +4040,9 @@ var EdgeNodeSecurity = class {
3473
4040
  async encryptLocal(plaintext, key) {
3474
4041
  await this.sodiumReady;
3475
4042
  const keyBytes = this.deriveKey(key);
3476
- const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
4043
+ const nonce = import_libsodium_wrappers_sumo.default.randombytes_buf(import_libsodium_wrappers_sumo.default.crypto_secretbox_NONCEBYTES);
3477
4044
  const message = new TextEncoder().encode(plaintext);
3478
- const ciphertext = sodium.crypto_secretbox_easy(message, nonce, keyBytes);
4045
+ const ciphertext = import_libsodium_wrappers_sumo.default.crypto_secretbox_easy(message, nonce, keyBytes);
3479
4046
  return {
3480
4047
  ciphertext: this.bytesToBase64(ciphertext),
3481
4048
  nonce: this.bytesToBase64(nonce)
@@ -3489,7 +4056,7 @@ var EdgeNodeSecurity = class {
3489
4056
  const keyBytes = this.deriveKey(key);
3490
4057
  const nonce = this.base64ToBytes(encrypted.nonce);
3491
4058
  const ciphertext = this.base64ToBytes(encrypted.ciphertext);
3492
- const decrypted = sodium.crypto_secretbox_open_easy(ciphertext, nonce, keyBytes);
4059
+ const decrypted = import_libsodium_wrappers_sumo.default.crypto_secretbox_open_easy(ciphertext, nonce, keyBytes);
3493
4060
  if (!decrypted) {
3494
4061
  throw new Error("Local decryption failed");
3495
4062
  }
@@ -3512,7 +4079,7 @@ var EdgeNodeSecurity = class {
3512
4079
  hmacSha256(message, secret) {
3513
4080
  const key = new TextEncoder().encode(secret);
3514
4081
  const msg = new TextEncoder().encode(message);
3515
- const hash = sodium.crypto_auth(msg, key);
4082
+ const hash = import_libsodium_wrappers_sumo.default.crypto_auth(msg, key);
3516
4083
  return this.bytesToBase64(hash);
3517
4084
  }
3518
4085
  timingSafeEqual(a, b) {
@@ -3533,7 +4100,7 @@ var EdgeNodeSecurity = class {
3533
4100
  }
3534
4101
  deriveKey(password) {
3535
4102
  const passwordBytes = new TextEncoder().encode(password);
3536
- return sodium.crypto_generichash(32, passwordBytes, null);
4103
+ return import_libsodium_wrappers_sumo.default.crypto_generichash(32, passwordBytes, null);
3537
4104
  }
3538
4105
  };
3539
4106
  async function createEdgeNodeSecurity(options = {}) {
@@ -3542,6 +4109,179 @@ async function createEdgeNodeSecurity(options = {}) {
3542
4109
  return security;
3543
4110
  }
3544
4111
 
4112
+ // src/cli/index.ts
4113
+ init_display();
4114
+
4115
+ // src/cli/handlers/_registry.ts
4116
+ init_cjs_shims();
4117
+
4118
+ // src/cli/core/types.ts
4119
+ init_cjs_shims();
4120
+ var ToolHandlerRegistry = class {
4121
+ handlers = [];
4122
+ register(handler) {
4123
+ this.handlers.push(handler);
4124
+ }
4125
+ findHandler(toolId) {
4126
+ return this.handlers.find((h) => {
4127
+ if (typeof h.toolId === "string") {
4128
+ return h.toolId === toolId;
4129
+ }
4130
+ return h.toolId.test(toolId);
4131
+ });
4132
+ }
4133
+ getAllHandlers() {
4134
+ return [...this.handlers];
4135
+ }
4136
+ };
4137
+ var globalHandlerRegistry = new ToolHandlerRegistry();
4138
+
4139
+ // src/cli/handlers/_registry.ts
4140
+ var imageGenerationHandler = {
4141
+ toolId: /image-generation|generate-image/,
4142
+ async display(context) {
4143
+ const { detectImageData: detectImageData2, displayImage: displayImage2 } = await Promise.resolve().then(() => (init_display(), display_exports));
4144
+ const { result, flags } = context;
4145
+ const imageInfo = detectImageData2(result);
4146
+ if (imageInfo.hasImage && !flags.raw) {
4147
+ try {
4148
+ if (imageInfo.imageData) {
4149
+ await displayImage2(imageInfo.imageData);
4150
+ return true;
4151
+ }
4152
+ } catch (error) {
4153
+ console.warn("Failed to display image:", error instanceof Error ? error.message : error);
4154
+ }
4155
+ }
4156
+ return false;
4157
+ }
4158
+ };
4159
+ var finvizQuotesHandler = {
4160
+ toolId: "finviz-quotes",
4161
+ async preflight(context) {
4162
+ const { parseValue: parseValue2 } = await Promise.resolve().then(() => (init_param_parser(), param_parser_exports));
4163
+ const params = { ...context.params };
4164
+ if (params.tickers && typeof params.tickers === "string") {
4165
+ params.tickers = parseValue2(params.tickers, { type: "array", items: { type: "string" } });
4166
+ }
4167
+ return { params };
4168
+ },
4169
+ async display(context) {
4170
+ const { result, flags } = context;
4171
+ if (flags.raw) {
4172
+ return false;
4173
+ }
4174
+ const obj = result;
4175
+ const quotes = obj?.quotes;
4176
+ if (Array.isArray(quotes) && quotes.length > 0) {
4177
+ const { formatAsTable: formatAsTable2 } = await Promise.resolve().then(() => (init_display(), display_exports));
4178
+ const tableData = quotes.map((q) => {
4179
+ const quote = q;
4180
+ const data = quote.data || {};
4181
+ return {
4182
+ Ticker: quote.ticker || data.Ticker || "-",
4183
+ Price: data.Price || data.Close || "-",
4184
+ Change: data.Change || "-",
4185
+ Volume: data.Volume || "-",
4186
+ "Market Cap": data.MarketCap || "-"
4187
+ };
4188
+ });
4189
+ console.log(formatAsTable2(tableData));
4190
+ const summary = obj?.summary;
4191
+ if (summary && typeof summary === "string") {
4192
+ console.log(`
4193
+ ${summary}`);
4194
+ }
4195
+ return true;
4196
+ }
4197
+ return false;
4198
+ }
4199
+ };
4200
+ var csvQueryHandler = {
4201
+ toolId: /query-csv|csv-query/,
4202
+ async display(context) {
4203
+ const { result, flags } = context;
4204
+ if (flags.raw) {
4205
+ return false;
4206
+ }
4207
+ if (Array.isArray(result) && result.length > 0) {
4208
+ const { formatAsTable: formatAsTable2 } = await Promise.resolve().then(() => (init_display(), display_exports));
4209
+ console.log(formatAsTable2(result));
4210
+ return true;
4211
+ }
4212
+ return false;
4213
+ }
4214
+ };
4215
+ var webSearchHandler = {
4216
+ toolId: /web-search|exa-web-search|perplexity/,
4217
+ async display(context) {
4218
+ const { result, flags } = context;
4219
+ if (flags.raw) {
4220
+ return false;
4221
+ }
4222
+ const obj = result;
4223
+ if (obj.results && typeof obj.results === "string") {
4224
+ console.log(obj.results);
4225
+ return true;
4226
+ }
4227
+ if (obj.answer || obj.summary) {
4228
+ console.log(obj.answer || obj.summary);
4229
+ if (obj.sources && Array.isArray(obj.sources)) {
4230
+ console.log("\n--- Sources ---");
4231
+ obj.sources.forEach((source, i) => {
4232
+ if (typeof source === "string") {
4233
+ console.log(` ${i + 1}. ${source}`);
4234
+ } else if (source && typeof source === "object") {
4235
+ const s = source;
4236
+ console.log(` ${i + 1}. ${s.title || s.url || JSON.stringify(source)}`);
4237
+ }
4238
+ });
4239
+ }
4240
+ return true;
4241
+ }
4242
+ return false;
4243
+ }
4244
+ };
4245
+ var memoryRecallHandler = {
4246
+ toolId: /memory-recall|recall/,
4247
+ async display(context) {
4248
+ const { result, flags } = context;
4249
+ if (flags.raw) {
4250
+ return false;
4251
+ }
4252
+ if (Array.isArray(result)) {
4253
+ if (result.length === 0) {
4254
+ console.log("No memories found.");
4255
+ return true;
4256
+ }
4257
+ console.log(`Found ${result.length} memory(s):
4258
+ `);
4259
+ result.forEach((mem, i) => {
4260
+ const memory = mem;
4261
+ console.log(`\u2500`.repeat(60));
4262
+ console.log(` ${i + 1}. ${memory.content || memory.text || JSON.stringify(memory).slice(0, 100)}`);
4263
+ if (memory.similarity) {
4264
+ console.log(` Similarity: ${(Number(memory.similarity) * 100).toFixed(1)}%`);
4265
+ }
4266
+ if (memory.keywords && Array.isArray(memory.keywords)) {
4267
+ console.log(` Keywords: ${memory.keywords.join(", ")}`);
4268
+ }
4269
+ console.log();
4270
+ });
4271
+ return true;
4272
+ }
4273
+ return false;
4274
+ }
4275
+ };
4276
+ function registerBuiltInHandlers(registry = globalHandlerRegistry) {
4277
+ registry.register(imageGenerationHandler);
4278
+ registry.register(finvizQuotesHandler);
4279
+ registry.register(csvQueryHandler);
4280
+ registry.register(webSearchHandler);
4281
+ registry.register(memoryRecallHandler);
4282
+ }
4283
+ registerBuiltInHandlers();
4284
+
3545
4285
  // src/cli/index.ts
3546
4286
  function printHelp() {
3547
4287
  console.log(`
@@ -3593,7 +4333,12 @@ Options for 'run':
3593
4333
  --params, -p <json> Tool parameters as JSON
3594
4334
  --file, -f <path> Read parameters from file
3595
4335
  --raw Output raw JSON
4336
+ --table Output as table (if applicable)
4337
+ --terminal Output for terminal consumption (minimal formatting)
3596
4338
  --<key> <value> Pass individual parameters (e.g., --query "AI news")
4339
+ Arrays: --tickers AAPL,GOOGL (comma-separated)
4340
+ Numbers: --count 42
4341
+ Booleans: --enabled true
3597
4342
 
3598
4343
  Options for 'daemon start':
3599
4344
  --port <port> WebSocket port (default: 8765)
@@ -3609,6 +4354,7 @@ Examples:
3609
4354
  rainfall tools describe github-create-issue
3610
4355
  rainfall run exa-web-search -p '{"query": "AI news"}'
3611
4356
  rainfall run exa-web-search --query "AI news"
4357
+ rainfall run finviz-quotes --tickers AAPL,GOOGL,MSFT
3612
4358
  rainfall run github-create-issue --owner facebook --repo react --title "Bug"
3613
4359
  rainfall run article-summarize -f ./article.json
3614
4360
  rainfall daemon start
@@ -3626,6 +4372,24 @@ function getRainfall() {
3626
4372
  baseUrl: config.baseUrl
3627
4373
  });
3628
4374
  }
4375
+ async function fetchAllNodeIds(rainfall) {
4376
+ try {
4377
+ const client = rainfall.getClient();
4378
+ const subscriberId = await client.ensureSubscriberId();
4379
+ const result = await client.request(
4380
+ `/olympic/subscribers/${subscriberId}/nodes/_utils/node-list`
4381
+ );
4382
+ if (result.keys && Array.isArray(result.keys)) {
4383
+ return result.keys;
4384
+ }
4385
+ if (result.nodes && Array.isArray(result.nodes)) {
4386
+ return result.nodes.map((n) => n.id);
4387
+ }
4388
+ return [];
4389
+ } catch {
4390
+ return [];
4391
+ }
4392
+ }
3629
4393
  async function authLogin(args) {
3630
4394
  const apiKey = args[0] || process.env.RAINFALL_API_KEY;
3631
4395
  if (!apiKey) {
@@ -3712,6 +4476,100 @@ function formatSchema(obj, indent = 0) {
3712
4476
  }
3713
4477
  return lines.join("\n");
3714
4478
  }
4479
+ function levenshteinDistance(a, b) {
4480
+ const matrix = [];
4481
+ for (let i = 0; i <= b.length; i++) {
4482
+ matrix[i] = [i];
4483
+ }
4484
+ for (let j = 0; j <= a.length; j++) {
4485
+ matrix[0][j] = j;
4486
+ }
4487
+ for (let i = 1; i <= b.length; i++) {
4488
+ for (let j = 1; j <= a.length; j++) {
4489
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
4490
+ matrix[i][j] = matrix[i - 1][j - 1];
4491
+ } else {
4492
+ matrix[i][j] = Math.min(
4493
+ matrix[i - 1][j - 1] + 1,
4494
+ // substitution
4495
+ matrix[i][j - 1] + 1,
4496
+ // insertion
4497
+ matrix[i - 1][j] + 1
4498
+ // deletion
4499
+ );
4500
+ }
4501
+ }
4502
+ }
4503
+ return matrix[b.length][a.length];
4504
+ }
4505
+ function jaroWinklerSimilarity(a, b) {
4506
+ if (a === b) return 1;
4507
+ if (a.length === 0 || b.length === 0) return 0;
4508
+ const matchDistance = Math.floor(Math.max(a.length, b.length) / 2) - 1;
4509
+ const aMatches = new Array(a.length).fill(false);
4510
+ const bMatches = new Array(b.length).fill(false);
4511
+ let matches = 0;
4512
+ let transpositions = 0;
4513
+ for (let i = 0; i < a.length; i++) {
4514
+ const start = Math.max(0, i - matchDistance);
4515
+ const end = Math.min(i + matchDistance + 1, b.length);
4516
+ for (let j = start; j < end; j++) {
4517
+ if (bMatches[j] || a.charAt(i) !== b.charAt(j)) continue;
4518
+ aMatches[i] = true;
4519
+ bMatches[j] = true;
4520
+ matches++;
4521
+ break;
4522
+ }
4523
+ }
4524
+ if (matches === 0) return 0;
4525
+ let k = 0;
4526
+ for (let i = 0; i < a.length; i++) {
4527
+ if (!aMatches[i]) continue;
4528
+ while (!bMatches[k]) k++;
4529
+ if (a.charAt(i) !== b.charAt(k)) transpositions++;
4530
+ k++;
4531
+ }
4532
+ const jaro = (matches / a.length + matches / b.length + (matches - transpositions / 2) / matches) / 3;
4533
+ let prefixLength = 0;
4534
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
4535
+ if (a.charAt(i) === b.charAt(i)) {
4536
+ prefixLength++;
4537
+ } else {
4538
+ break;
4539
+ }
4540
+ }
4541
+ const scalingFactor = 0.1;
4542
+ return jaro + prefixLength * scalingFactor * (1 - jaro);
4543
+ }
4544
+ function calculateSimilarity(toolId, candidateId, description = "") {
4545
+ const lowerToolId = toolId.toLowerCase();
4546
+ const lowerCandidate = candidateId.toLowerCase();
4547
+ const prefix = lowerToolId.split("-")[0];
4548
+ const hasPrefix = lowerToolId.includes("-");
4549
+ const jwScore = jaroWinklerSimilarity(lowerToolId, lowerCandidate);
4550
+ const maxLen = Math.max(lowerToolId.length, lowerCandidate.length);
4551
+ const lvScore = maxLen === 0 ? 1 : 1 - levenshteinDistance(lowerToolId, lowerCandidate) / maxLen;
4552
+ let substringBoost = 0;
4553
+ if (lowerCandidate.includes(lowerToolId) || lowerToolId.includes(lowerCandidate)) {
4554
+ substringBoost = 0.4;
4555
+ }
4556
+ let prefixBoost = 0;
4557
+ if (hasPrefix && lowerCandidate === prefix) {
4558
+ prefixBoost = 0.5;
4559
+ }
4560
+ if (hasPrefix && lowerCandidate.startsWith(prefix + "-")) {
4561
+ prefixBoost = 0.35;
4562
+ }
4563
+ const descMatch = description.toLowerCase().includes(lowerToolId) ? 0.1 : 0;
4564
+ return jwScore * 0.4 + lvScore * 0.25 + substringBoost + prefixBoost + descMatch;
4565
+ }
4566
+ function findSimilarToolIds(toolId, toolIds) {
4567
+ const scored = toolIds.map((id) => ({
4568
+ id,
4569
+ score: calculateSimilarity(toolId, id)
4570
+ }));
4571
+ return scored.filter((item) => item.score > 0.35).sort((a, b) => b.score - a.score).slice(0, 5).map((item) => item.id);
4572
+ }
3715
4573
  async function describeTool(args) {
3716
4574
  const toolId = args[0];
3717
4575
  if (!toolId) {
@@ -3747,6 +4605,17 @@ async function describeTool(args) {
3747
4605
  console.log();
3748
4606
  } catch (error) {
3749
4607
  console.error(`Error: Tool '${toolId}' not found`);
4608
+ try {
4609
+ const allNodeIds = await fetchAllNodeIds(rainfall);
4610
+ const suggestions = findSimilarToolIds(toolId, allNodeIds);
4611
+ if (suggestions.length > 0) {
4612
+ console.error("\nDid you mean:");
4613
+ for (const suggestion of suggestions) {
4614
+ console.error(` \u2022 ${suggestion}`);
4615
+ }
4616
+ }
4617
+ } catch {
4618
+ }
3750
4619
  process.exit(1);
3751
4620
  }
3752
4621
  }
@@ -3790,12 +4659,18 @@ Options:
3790
4659
  -p, --params <json> Tool parameters as JSON string
3791
4660
  -f, --file <path> Read parameters from JSON file
3792
4661
  --raw Output raw JSON (no formatting)
4662
+ --table Output as table (if applicable)
4663
+ --terminal Output for terminal consumption (minimal formatting)
3793
4664
  --<key> <value> Pass individual parameters (e.g., --query "AI news")
4665
+ Arrays: --tickers AAPL,GOOGL (comma-separated)
4666
+ Numbers: --count 42
4667
+ Booleans: --enabled true
3794
4668
 
3795
4669
  Examples:
3796
4670
  rainfall run figma-users-getMe
3797
4671
  rainfall run exa-web-search -p '{"query": "AI news"}'
3798
4672
  rainfall run exa-web-search --query "AI news"
4673
+ rainfall run finviz-quotes --tickers AAPL,GOOGL,MSFT
3799
4674
  rainfall run github-create-issue --owner facebook --repo react --title "Bug"
3800
4675
  rainfall run github-create-issue -f ./issue.json
3801
4676
  echo '{"query": "hello"}' | rainfall run exa-web-search
@@ -3804,6 +4679,7 @@ Examples:
3804
4679
  }
3805
4680
  let params = {};
3806
4681
  const rawArgs = [];
4682
+ let displayMode = "pretty";
3807
4683
  for (let i = 1; i < args.length; i++) {
3808
4684
  const arg = args[i];
3809
4685
  if (arg === "--params" || arg === "-p") {
@@ -3831,16 +4707,17 @@ Examples:
3831
4707
  process.exit(1);
3832
4708
  }
3833
4709
  } else if (arg === "--raw") {
4710
+ displayMode = "raw";
4711
+ } else if (arg === "--table") {
4712
+ displayMode = "table";
4713
+ } else if (arg === "--terminal") {
4714
+ displayMode = "terminal";
3834
4715
  } else if (arg.startsWith("--")) {
3835
4716
  const key = arg.slice(2);
3836
4717
  const value = args[++i];
3837
4718
  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 {
4719
+ params[key] = true;
4720
+ } else {
3844
4721
  params[key] = value;
3845
4722
  }
3846
4723
  } else {
@@ -3882,30 +4759,108 @@ Examples:
3882
4759
  }
3883
4760
  }
3884
4761
  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
- }
4762
+ let toolSchema;
4763
+ try {
4764
+ const fullSchema = await rainfall.getToolSchema(toolId);
4765
+ toolSchema = {
4766
+ parameters: fullSchema.parameters
4767
+ };
4768
+ } catch {
4769
+ }
4770
+ const cliFlags = /* @__PURE__ */ new Set(["--params", "-p", "--file", "-f", "--raw", "--table", "--terminal"]);
4771
+ const toolArgs = args.slice(1).filter((arg, i, arr) => {
4772
+ if (cliFlags.has(arg)) {
4773
+ return false;
4774
+ }
4775
+ if (i > 0 && cliFlags.has(arr[i - 1])) {
4776
+ return false;
4777
+ }
4778
+ return true;
4779
+ });
4780
+ if (toolSchema?.parameters) {
4781
+ const { parseCliArgs: parseCliArgs2 } = await Promise.resolve().then(() => (init_param_parser(), param_parser_exports));
4782
+ const parsedParams = parseCliArgs2(
4783
+ toolArgs,
4784
+ {
4785
+ name: toolId,
4786
+ description: "",
4787
+ category: "",
4788
+ parameters: toolSchema.parameters
3895
4789
  }
3896
- } catch {
4790
+ );
4791
+ params = { ...parsedParams, ...params };
4792
+ }
4793
+ if (rawArgs.length === 1 && Object.keys(params).length === 0 && toolSchema?.parameters) {
4794
+ const paramEntries = Object.entries(toolSchema.parameters);
4795
+ const requiredParams = paramEntries.filter(([, p]) => !p.optional);
4796
+ if (requiredParams.length === 1) {
4797
+ const [paramName, paramSchema] = requiredParams[0];
4798
+ const { parseValue: parseValue2 } = await Promise.resolve().then(() => (init_param_parser(), param_parser_exports));
4799
+ params = { [paramName]: parseValue2(rawArgs[0], paramSchema) };
3897
4800
  }
3898
4801
  }
4802
+ const handler = globalHandlerRegistry.findHandler(toolId);
4803
+ const toolContext = {
4804
+ rainfall,
4805
+ toolId,
4806
+ params,
4807
+ args: rawArgs,
4808
+ flags: { raw: displayMode === "raw" }
4809
+ };
3899
4810
  try {
3900
- const result = await rainfall.executeTool(toolId, params);
3901
- if (args.includes("--raw")) {
3902
- console.log(JSON.stringify(result));
4811
+ let executionParams = params;
4812
+ let preflightContext;
4813
+ let skipExecution;
4814
+ if (handler?.preflight) {
4815
+ const preflightResult = await handler.preflight(toolContext);
4816
+ if (preflightResult) {
4817
+ if (preflightResult.skipExecution !== void 0) {
4818
+ skipExecution = preflightResult.skipExecution;
4819
+ }
4820
+ if (preflightResult.params) {
4821
+ executionParams = preflightResult.params;
4822
+ }
4823
+ preflightContext = preflightResult.context;
4824
+ }
4825
+ }
4826
+ let result;
4827
+ if (skipExecution !== void 0) {
4828
+ result = skipExecution;
3903
4829
  } else {
3904
- console.log(JSON.stringify(result, null, 2));
4830
+ result = await rainfall.executeTool(toolId, executionParams);
4831
+ }
4832
+ const postflightContext = {
4833
+ ...toolContext,
4834
+ result,
4835
+ preflightContext
4836
+ };
4837
+ if (handler?.postflight) {
4838
+ await handler.postflight(postflightContext);
4839
+ }
4840
+ let displayed = false;
4841
+ if (handler?.display) {
4842
+ displayed = await handler.display({ ...postflightContext, flags: { ...toolContext.flags, mode: displayMode } });
4843
+ }
4844
+ if (!displayed) {
4845
+ const output = await formatResult(result, { mode: displayMode });
4846
+ console.log(output);
3905
4847
  }
3906
4848
  } catch (error) {
3907
4849
  const message = error instanceof Error ? error.message : String(error);
3908
4850
  console.error(`Error: ${message}`);
4851
+ if (message.toLowerCase().includes("not found") || message.toLowerCase().includes("not found")) {
4852
+ try {
4853
+ const allNodeIds = await fetchAllNodeIds(rainfall);
4854
+ const suggestions = findSimilarToolIds(toolId, allNodeIds);
4855
+ if (suggestions.length > 0) {
4856
+ console.error("\nDid you mean:");
4857
+ for (const suggestion of suggestions) {
4858
+ console.error(` \u2022 ${suggestion}`);
4859
+ }
4860
+ }
4861
+ } catch {
4862
+ }
4863
+ }
3909
4864
  process.exit(1);
3910
4865
  }
3911
4866
  }
@@ -4022,7 +4977,7 @@ async function upgrade() {
4022
4977
  console.log(`Running: ${command} ${args.join(" ")}`);
4023
4978
  console.log();
4024
4979
  return new Promise((resolve, reject) => {
4025
- const child = (0, import_child_process.spawn)(command, args, {
4980
+ const child = (0, import_child_process2.spawn)(command, args, {
4026
4981
  stdio: "inherit",
4027
4982
  shell: true
4028
4983
  });