@rainfall-devkit/sdk 0.2.2 → 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.
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- createEdgeNodeSecurity
4
- } from "../chunk-NCQVOLS4.mjs";
3
+ createEdgeNodeSecurity,
4
+ globalHandlerRegistry
5
+ } from "../chunk-6FXRLPLR.mjs";
5
6
  import {
6
7
  Rainfall
7
- } from "../chunk-VDPKDC3R.mjs";
8
+ } from "../chunk-CQ5TV7CQ.mjs";
9
+ import "../chunk-LJQEO3CY.mjs";
10
+ import {
11
+ formatResult
12
+ } from "../chunk-XHPFY5MH.mjs";
8
13
  import {
9
14
  getConfigDir,
10
15
  loadConfig,
@@ -66,7 +71,12 @@ Options for 'run':
66
71
  --params, -p <json> Tool parameters as JSON
67
72
  --file, -f <path> Read parameters from file
68
73
  --raw Output raw JSON
74
+ --table Output as table (if applicable)
75
+ --terminal Output for terminal consumption (minimal formatting)
69
76
  --<key> <value> Pass individual parameters (e.g., --query "AI news")
77
+ Arrays: --tickers AAPL,GOOGL (comma-separated)
78
+ Numbers: --count 42
79
+ Booleans: --enabled true
70
80
 
71
81
  Options for 'daemon start':
72
82
  --port <port> WebSocket port (default: 8765)
@@ -82,6 +92,7 @@ Examples:
82
92
  rainfall tools describe github-create-issue
83
93
  rainfall run exa-web-search -p '{"query": "AI news"}'
84
94
  rainfall run exa-web-search --query "AI news"
95
+ rainfall run finviz-quotes --tickers AAPL,GOOGL,MSFT
85
96
  rainfall run github-create-issue --owner facebook --repo react --title "Bug"
86
97
  rainfall run article-summarize -f ./article.json
87
98
  rainfall daemon start
@@ -99,6 +110,24 @@ function getRainfall() {
99
110
  baseUrl: config.baseUrl
100
111
  });
101
112
  }
113
+ async function fetchAllNodeIds(rainfall) {
114
+ try {
115
+ const client = rainfall.getClient();
116
+ const subscriberId = await client.ensureSubscriberId();
117
+ const result = await client.request(
118
+ `/olympic/subscribers/${subscriberId}/nodes/_utils/node-list`
119
+ );
120
+ if (result.keys && Array.isArray(result.keys)) {
121
+ return result.keys;
122
+ }
123
+ if (result.nodes && Array.isArray(result.nodes)) {
124
+ return result.nodes.map((n) => n.id);
125
+ }
126
+ return [];
127
+ } catch {
128
+ return [];
129
+ }
130
+ }
102
131
  async function authLogin(args) {
103
132
  const apiKey = args[0] || process.env.RAINFALL_API_KEY;
104
133
  if (!apiKey) {
@@ -185,6 +214,100 @@ function formatSchema(obj, indent = 0) {
185
214
  }
186
215
  return lines.join("\n");
187
216
  }
217
+ function levenshteinDistance(a, b) {
218
+ const matrix = [];
219
+ for (let i = 0; i <= b.length; i++) {
220
+ matrix[i] = [i];
221
+ }
222
+ for (let j = 0; j <= a.length; j++) {
223
+ matrix[0][j] = j;
224
+ }
225
+ for (let i = 1; i <= b.length; i++) {
226
+ for (let j = 1; j <= a.length; j++) {
227
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
228
+ matrix[i][j] = matrix[i - 1][j - 1];
229
+ } else {
230
+ matrix[i][j] = Math.min(
231
+ matrix[i - 1][j - 1] + 1,
232
+ // substitution
233
+ matrix[i][j - 1] + 1,
234
+ // insertion
235
+ matrix[i - 1][j] + 1
236
+ // deletion
237
+ );
238
+ }
239
+ }
240
+ }
241
+ return matrix[b.length][a.length];
242
+ }
243
+ function jaroWinklerSimilarity(a, b) {
244
+ if (a === b) return 1;
245
+ if (a.length === 0 || b.length === 0) return 0;
246
+ const matchDistance = Math.floor(Math.max(a.length, b.length) / 2) - 1;
247
+ const aMatches = new Array(a.length).fill(false);
248
+ const bMatches = new Array(b.length).fill(false);
249
+ let matches = 0;
250
+ let transpositions = 0;
251
+ for (let i = 0; i < a.length; i++) {
252
+ const start = Math.max(0, i - matchDistance);
253
+ const end = Math.min(i + matchDistance + 1, b.length);
254
+ for (let j = start; j < end; j++) {
255
+ if (bMatches[j] || a.charAt(i) !== b.charAt(j)) continue;
256
+ aMatches[i] = true;
257
+ bMatches[j] = true;
258
+ matches++;
259
+ break;
260
+ }
261
+ }
262
+ if (matches === 0) return 0;
263
+ let k = 0;
264
+ for (let i = 0; i < a.length; i++) {
265
+ if (!aMatches[i]) continue;
266
+ while (!bMatches[k]) k++;
267
+ if (a.charAt(i) !== b.charAt(k)) transpositions++;
268
+ k++;
269
+ }
270
+ const jaro = (matches / a.length + matches / b.length + (matches - transpositions / 2) / matches) / 3;
271
+ let prefixLength = 0;
272
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
273
+ if (a.charAt(i) === b.charAt(i)) {
274
+ prefixLength++;
275
+ } else {
276
+ break;
277
+ }
278
+ }
279
+ const scalingFactor = 0.1;
280
+ return jaro + prefixLength * scalingFactor * (1 - jaro);
281
+ }
282
+ function calculateSimilarity(toolId, candidateId, description = "") {
283
+ const lowerToolId = toolId.toLowerCase();
284
+ const lowerCandidate = candidateId.toLowerCase();
285
+ const prefix = lowerToolId.split("-")[0];
286
+ const hasPrefix = lowerToolId.includes("-");
287
+ const jwScore = jaroWinklerSimilarity(lowerToolId, lowerCandidate);
288
+ const maxLen = Math.max(lowerToolId.length, lowerCandidate.length);
289
+ const lvScore = maxLen === 0 ? 1 : 1 - levenshteinDistance(lowerToolId, lowerCandidate) / maxLen;
290
+ let substringBoost = 0;
291
+ if (lowerCandidate.includes(lowerToolId) || lowerToolId.includes(lowerCandidate)) {
292
+ substringBoost = 0.4;
293
+ }
294
+ let prefixBoost = 0;
295
+ if (hasPrefix && lowerCandidate === prefix) {
296
+ prefixBoost = 0.5;
297
+ }
298
+ if (hasPrefix && lowerCandidate.startsWith(prefix + "-")) {
299
+ prefixBoost = 0.35;
300
+ }
301
+ const descMatch = description.toLowerCase().includes(lowerToolId) ? 0.1 : 0;
302
+ return jwScore * 0.4 + lvScore * 0.25 + substringBoost + prefixBoost + descMatch;
303
+ }
304
+ function findSimilarToolIds(toolId, toolIds) {
305
+ const scored = toolIds.map((id) => ({
306
+ id,
307
+ score: calculateSimilarity(toolId, id)
308
+ }));
309
+ return scored.filter((item) => item.score > 0.35).sort((a, b) => b.score - a.score).slice(0, 5).map((item) => item.id);
310
+ }
188
311
  async function describeTool(args) {
189
312
  const toolId = args[0];
190
313
  if (!toolId) {
@@ -220,6 +343,17 @@ async function describeTool(args) {
220
343
  console.log();
221
344
  } catch (error) {
222
345
  console.error(`Error: Tool '${toolId}' not found`);
346
+ try {
347
+ const allNodeIds = await fetchAllNodeIds(rainfall);
348
+ const suggestions = findSimilarToolIds(toolId, allNodeIds);
349
+ if (suggestions.length > 0) {
350
+ console.error("\nDid you mean:");
351
+ for (const suggestion of suggestions) {
352
+ console.error(` \u2022 ${suggestion}`);
353
+ }
354
+ }
355
+ } catch {
356
+ }
223
357
  process.exit(1);
224
358
  }
225
359
  }
@@ -263,12 +397,18 @@ Options:
263
397
  -p, --params <json> Tool parameters as JSON string
264
398
  -f, --file <path> Read parameters from JSON file
265
399
  --raw Output raw JSON (no formatting)
400
+ --table Output as table (if applicable)
401
+ --terminal Output for terminal consumption (minimal formatting)
266
402
  --<key> <value> Pass individual parameters (e.g., --query "AI news")
403
+ Arrays: --tickers AAPL,GOOGL (comma-separated)
404
+ Numbers: --count 42
405
+ Booleans: --enabled true
267
406
 
268
407
  Examples:
269
408
  rainfall run figma-users-getMe
270
409
  rainfall run exa-web-search -p '{"query": "AI news"}'
271
410
  rainfall run exa-web-search --query "AI news"
411
+ rainfall run finviz-quotes --tickers AAPL,GOOGL,MSFT
272
412
  rainfall run github-create-issue --owner facebook --repo react --title "Bug"
273
413
  rainfall run github-create-issue -f ./issue.json
274
414
  echo '{"query": "hello"}' | rainfall run exa-web-search
@@ -277,6 +417,7 @@ Examples:
277
417
  }
278
418
  let params = {};
279
419
  const rawArgs = [];
420
+ let displayMode = "pretty";
280
421
  for (let i = 1; i < args.length; i++) {
281
422
  const arg = args[i];
282
423
  if (arg === "--params" || arg === "-p") {
@@ -304,16 +445,17 @@ Examples:
304
445
  process.exit(1);
305
446
  }
306
447
  } else if (arg === "--raw") {
448
+ displayMode = "raw";
449
+ } else if (arg === "--table") {
450
+ displayMode = "table";
451
+ } else if (arg === "--terminal") {
452
+ displayMode = "terminal";
307
453
  } else if (arg.startsWith("--")) {
308
454
  const key = arg.slice(2);
309
455
  const value = args[++i];
310
456
  if (value === void 0) {
311
- console.error(`Error: ${arg} requires a value`);
312
- process.exit(1);
313
- }
314
- try {
315
- params[key] = JSON.parse(value);
316
- } catch {
457
+ params[key] = true;
458
+ } else {
317
459
  params[key] = value;
318
460
  }
319
461
  } else {
@@ -355,30 +497,108 @@ Examples:
355
497
  }
356
498
  }
357
499
  const rainfall = getRainfall();
358
- if (rawArgs.length === 1 && Object.keys(params).length === 0) {
359
- try {
360
- const schema = await rainfall.getToolSchema(toolId);
361
- if (schema.parameters && typeof schema.parameters === "object") {
362
- const paramEntries = Object.entries(schema.parameters);
363
- const requiredParams = paramEntries.filter(([, p]) => !p.optional);
364
- if (requiredParams.length === 1) {
365
- const [paramName] = requiredParams[0];
366
- params = { [paramName]: rawArgs[0] };
367
- }
500
+ let toolSchema;
501
+ try {
502
+ const fullSchema = await rainfall.getToolSchema(toolId);
503
+ toolSchema = {
504
+ parameters: fullSchema.parameters
505
+ };
506
+ } catch {
507
+ }
508
+ const cliFlags = /* @__PURE__ */ new Set(["--params", "-p", "--file", "-f", "--raw", "--table", "--terminal"]);
509
+ const toolArgs = args.slice(1).filter((arg, i, arr) => {
510
+ if (cliFlags.has(arg)) {
511
+ return false;
512
+ }
513
+ if (i > 0 && cliFlags.has(arr[i - 1])) {
514
+ return false;
515
+ }
516
+ return true;
517
+ });
518
+ if (toolSchema?.parameters) {
519
+ const { parseCliArgs } = await import("../param-parser-PAKCNDBX.mjs");
520
+ const parsedParams = parseCliArgs(
521
+ toolArgs,
522
+ {
523
+ name: toolId,
524
+ description: "",
525
+ category: "",
526
+ parameters: toolSchema.parameters
368
527
  }
369
- } catch {
528
+ );
529
+ params = { ...parsedParams, ...params };
530
+ }
531
+ if (rawArgs.length === 1 && Object.keys(params).length === 0 && toolSchema?.parameters) {
532
+ const paramEntries = Object.entries(toolSchema.parameters);
533
+ const requiredParams = paramEntries.filter(([, p]) => !p.optional);
534
+ if (requiredParams.length === 1) {
535
+ const [paramName, paramSchema] = requiredParams[0];
536
+ const { parseValue } = await import("../param-parser-PAKCNDBX.mjs");
537
+ params = { [paramName]: parseValue(rawArgs[0], paramSchema) };
370
538
  }
371
539
  }
540
+ const handler = globalHandlerRegistry.findHandler(toolId);
541
+ const toolContext = {
542
+ rainfall,
543
+ toolId,
544
+ params,
545
+ args: rawArgs,
546
+ flags: { raw: displayMode === "raw" }
547
+ };
372
548
  try {
373
- const result = await rainfall.executeTool(toolId, params);
374
- if (args.includes("--raw")) {
375
- console.log(JSON.stringify(result));
549
+ let executionParams = params;
550
+ let preflightContext;
551
+ let skipExecution;
552
+ if (handler?.preflight) {
553
+ const preflightResult = await handler.preflight(toolContext);
554
+ if (preflightResult) {
555
+ if (preflightResult.skipExecution !== void 0) {
556
+ skipExecution = preflightResult.skipExecution;
557
+ }
558
+ if (preflightResult.params) {
559
+ executionParams = preflightResult.params;
560
+ }
561
+ preflightContext = preflightResult.context;
562
+ }
563
+ }
564
+ let result;
565
+ if (skipExecution !== void 0) {
566
+ result = skipExecution;
376
567
  } else {
377
- console.log(JSON.stringify(result, null, 2));
568
+ result = await rainfall.executeTool(toolId, executionParams);
569
+ }
570
+ const postflightContext = {
571
+ ...toolContext,
572
+ result,
573
+ preflightContext
574
+ };
575
+ if (handler?.postflight) {
576
+ await handler.postflight(postflightContext);
577
+ }
578
+ let displayed = false;
579
+ if (handler?.display) {
580
+ displayed = await handler.display({ ...postflightContext, flags: { ...toolContext.flags, mode: displayMode } });
581
+ }
582
+ if (!displayed) {
583
+ const output = await formatResult(result, { mode: displayMode });
584
+ console.log(output);
378
585
  }
379
586
  } catch (error) {
380
587
  const message = error instanceof Error ? error.message : String(error);
381
588
  console.error(`Error: ${message}`);
589
+ if (message.toLowerCase().includes("not found") || message.toLowerCase().includes("not found")) {
590
+ try {
591
+ const allNodeIds = await fetchAllNodeIds(rainfall);
592
+ const suggestions = findSimilarToolIds(toolId, allNodeIds);
593
+ if (suggestions.length > 0) {
594
+ console.error("\nDid you mean:");
595
+ for (const suggestion of suggestions) {
596
+ console.error(` \u2022 ${suggestion}`);
597
+ }
598
+ }
599
+ } catch {
600
+ }
601
+ }
382
602
  process.exit(1);
383
603
  }
384
604
  }
@@ -1,6 +1,6 @@
1
- import { R as RainfallConfig, g as MCPClientConfig, h as MCPProxyHub } from '../sdk-4OvXPr8E.mjs';
2
- export { i as MCPClientInfo, j as MCPToolInfo, k as MCPTransportType } from '../sdk-4OvXPr8E.mjs';
3
- import { N as NetworkedExecutorOptions, C as ContextOptions, R as RainfallNetworkedExecutor, a as RainfallDaemonContext, b as RainfallListenerRegistry } from '../listeners-MNAnpZj-.mjs';
1
+ import { R as RainfallConfig, g as MCPClientConfig, h as MCPProxyHub } from '../sdk-Cl5Qzt4I.mjs';
2
+ export { i as MCPClientInfo, j as MCPToolInfo, k as MCPTransportType } from '../sdk-Cl5Qzt4I.mjs';
3
+ import { N as NetworkedExecutorOptions, C as ContextOptions, R as RainfallNetworkedExecutor, a as RainfallDaemonContext, b as RainfallListenerRegistry } from '../listeners-Ckdj6D8T.mjs';
4
4
  import 'ws';
5
5
  import '@modelcontextprotocol/sdk/client/index.js';
6
6
  import '@modelcontextprotocol/sdk/client/stdio.js';
@@ -1,6 +1,6 @@
1
- import { R as RainfallConfig, g as MCPClientConfig, h as MCPProxyHub } from '../sdk-4OvXPr8E.js';
2
- export { i as MCPClientInfo, j as MCPToolInfo, k as MCPTransportType } from '../sdk-4OvXPr8E.js';
3
- import { N as NetworkedExecutorOptions, C as ContextOptions, R as RainfallNetworkedExecutor, a as RainfallDaemonContext, b as RainfallListenerRegistry } from '../listeners-B5Vy9Ao5.js';
1
+ import { R as RainfallConfig, g as MCPClientConfig, h as MCPProxyHub } from '../sdk-Cl5Qzt4I.js';
2
+ export { i as MCPClientInfo, j as MCPToolInfo, k as MCPTransportType } from '../sdk-Cl5Qzt4I.js';
3
+ import { N as NetworkedExecutorOptions, C as ContextOptions, R as RainfallNetworkedExecutor, a as RainfallDaemonContext, b as RainfallListenerRegistry } from '../listeners-BBNBsJCk.js';
4
4
  import 'ws';
5
5
  import '@modelcontextprotocol/sdk/client/index.js';
6
6
  import '@modelcontextprotocol/sdk/client/stdio.js';