@insightsentry/mcp 1.4.14 → 1.4.16
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.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +350 -36
- package/dist/cli.js.map +1 -1
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/resources.d.ts +4 -2
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +59 -2743
- package/dist/resources.js.map +1 -1
- package/dist/tool-runner.d.ts +1 -0
- package/dist/tool-runner.d.ts.map +1 -1
- package/dist/tool-runner.js +64 -3
- package/dist/tool-runner.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -11,15 +11,26 @@ export declare function buildHelp(): string;
|
|
|
11
11
|
export declare function buildDownloadHistoryHelp(): string;
|
|
12
12
|
export declare function buildToolHelp(tool: ToolDefinition): string;
|
|
13
13
|
type RequestFn = (method: string, pathTemplate: string, params: Record<string, any>) => Promise<any>;
|
|
14
|
+
type RunCommandFn = (command: string, args: string[]) => Promise<{
|
|
15
|
+
stdout?: string;
|
|
16
|
+
stderr?: string;
|
|
17
|
+
}>;
|
|
14
18
|
interface CliIO {
|
|
15
19
|
write: (s: string) => void;
|
|
16
20
|
exit: (code: number) => void;
|
|
17
21
|
request?: RequestFn;
|
|
22
|
+
createRequestFromApiKey?: (apiKey: string) => RequestFn;
|
|
23
|
+
runCommand?: RunCommandFn;
|
|
18
24
|
progress?: (s: string) => void;
|
|
19
25
|
prompt?: (question: string) => Promise<string>;
|
|
26
|
+
selectTool?: (options: ToolSelectionOption[]) => Promise<string | null>;
|
|
20
27
|
isInteractive?: boolean;
|
|
21
28
|
getAuthStatus?: () => AuthStatus;
|
|
22
29
|
}
|
|
30
|
+
interface ToolSelectionOption {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
}
|
|
23
34
|
export declare function runCli(argv: string[], io: CliIO): Promise<void>;
|
|
24
35
|
export declare function main(): void;
|
|
25
36
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,UAAU,EAAyC,MAAM,kBAAkB,CAAC;AAgB1F,OAAO,EAAE,KAAK,cAAc,EAAmB,MAAM,uBAAuB,CAAC;AAG7E,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AA8B/C,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAoCpD;AAED,wBAAgB,SAAS,IAAI,MAAM,CA8ClC;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CA8BjD;AAyFD,wBAAgB,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAsC1D;AAkCD,KAAK,SAAS,GAAG,CACf,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KACxB,OAAO,CAAC,GAAG,CAAC,CAAC;AAClB,KAAK,YAAY,GAAG,CAClB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnD,UAAU,KAAK;IACb,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,uBAAuB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;IACxD,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,UAAU,CAAC;CAClC;AAKD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA+OrE;AA04BD,wBAAgB,IAAI,SAWnB"}
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
1
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { emitKeypressEvents } from "node:readline";
|
|
2
4
|
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { promisify } from "node:util";
|
|
3
6
|
import { z } from "zod";
|
|
4
7
|
import { ApiClient } from "./api-client.js";
|
|
5
8
|
import { coerceArgs, getZodEnumValues, getZodTypeName, isOptionalZodType } from "./arg-coercion.js";
|
|
@@ -12,10 +15,12 @@ import { supportsCsvStorage, validateResponseStorageTarget, } from "./response-s
|
|
|
12
15
|
import { analyzeSymbolCodes, shouldPromptSymbolScopedParam } from "./symbol-param-applicability.js";
|
|
13
16
|
import { validateSymbolLikeArg } from "./symbol-validation.js";
|
|
14
17
|
import { toolDefinitions } from "./tool-definitions.js";
|
|
15
|
-
import { runApiTool } from "./tool-runner.js";
|
|
18
|
+
import { runApiTool, validateFilterExpression } from "./tool-runner.js";
|
|
16
19
|
export { coerceArgs } from "./arg-coercion.js";
|
|
17
20
|
const DOWNLOAD_HISTORY_COMMAND = "download_history";
|
|
21
|
+
const UPDATE_COMMAND = "update";
|
|
18
22
|
const MAX_INTERACTIVE_PROMPT_ATTEMPTS = 3;
|
|
23
|
+
const execFileAsync = promisify(execFile);
|
|
19
24
|
const STORAGE_DESTINATION_SCHEMA = {
|
|
20
25
|
output_file: z.string().describe("File path for stored response.").optional(),
|
|
21
26
|
output_dir: z
|
|
@@ -33,6 +38,10 @@ const CSV_STORE_SCHEMA = z
|
|
|
33
38
|
.default("none")
|
|
34
39
|
.optional()
|
|
35
40
|
.describe("Store original API response before filtering. CSV is available for series/history responses. Default: none.");
|
|
41
|
+
const FILTER_SCHEMA = z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("JSONata expression to transform the response. Leave empty for no filter.");
|
|
36
45
|
export function parseArgs(argv) {
|
|
37
46
|
const args = {};
|
|
38
47
|
let toolName = null;
|
|
@@ -91,13 +100,14 @@ export function buildHelp() {
|
|
|
91
100
|
lines.push(" insight get_earnings --c US");
|
|
92
101
|
lines.push(' insight list_options --code "NASDAQ:AAPL" --type call --range 10');
|
|
93
102
|
lines.push("");
|
|
94
|
-
lines.push("
|
|
103
|
+
lines.push("API tools support --filter <jsonata> to transform the response.");
|
|
95
104
|
lines.push("Use: insight <tool> --help for tool-specific parameters.");
|
|
96
105
|
lines.push("");
|
|
97
106
|
lines.push("Authentication:");
|
|
98
107
|
lines.push(" insight login --key <your-api-key> Save API key (persisted across sessions)");
|
|
99
108
|
lines.push(" insight whoami Print the logged-in user's email");
|
|
100
109
|
lines.push(" insight logout Remove saved API key");
|
|
110
|
+
lines.push(" insight update Update this CLI with npm");
|
|
101
111
|
lines.push("");
|
|
102
112
|
lines.push(" Or set INSIGHTSENTRY_API_KEY environment variable (takes priority over saved key).");
|
|
103
113
|
lines.push(" Get your API key from https://insightsentry.com/dashboard");
|
|
@@ -281,7 +291,24 @@ function formatTypeName(t) {
|
|
|
281
291
|
}
|
|
282
292
|
const REQUIRED_DOWNLOAD_HISTORY_ARGS = ["symbol", "bar_type", "from", "to", "output_dir"];
|
|
283
293
|
export async function runCli(argv, io) {
|
|
284
|
-
|
|
294
|
+
let { toolName, args, help } = parseArgs(argv);
|
|
295
|
+
let sessionApiKey;
|
|
296
|
+
if (!toolName) {
|
|
297
|
+
if (!help && io.isInteractive === true && io.prompt) {
|
|
298
|
+
const selected = await resolveInteractiveToolName(io);
|
|
299
|
+
if (!selected) {
|
|
300
|
+
io.exit(1);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
toolName = selected.toolName;
|
|
304
|
+
sessionApiKey = selected.apiKey;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
io.write(buildHelp());
|
|
308
|
+
io.exit(0);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
285
312
|
if (!toolName) {
|
|
286
313
|
io.write(buildHelp());
|
|
287
314
|
io.exit(0);
|
|
@@ -312,6 +339,10 @@ export async function runCli(argv, io) {
|
|
|
312
339
|
io.exit(0);
|
|
313
340
|
return;
|
|
314
341
|
}
|
|
342
|
+
if (toolName === UPDATE_COMMAND) {
|
|
343
|
+
await runUpdateCommand(io);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
315
346
|
if (toolName === "whoami") {
|
|
316
347
|
const status = (io.getAuthStatus ?? getAuthStatus)();
|
|
317
348
|
if (status.subject) {
|
|
@@ -330,22 +361,26 @@ export async function runCli(argv, io) {
|
|
|
330
361
|
io.exit(0);
|
|
331
362
|
return;
|
|
332
363
|
}
|
|
333
|
-
const request = resolveRequest(io);
|
|
334
|
-
if (!request) {
|
|
335
|
-
io.write("Error: No API key found.\n\nSet it with: insight login --key <your-api-key>\nOr export: export INSIGHTSENTRY_API_KEY=your-api-key\n\nGet your API key from https://insightsentry.com/dashboard");
|
|
336
|
-
io.exit(1);
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
364
|
try {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
365
|
+
const inputArgs = { ...args };
|
|
366
|
+
const interactive = io.isInteractive === true && Boolean(io.prompt);
|
|
367
|
+
if (interactive) {
|
|
368
|
+
for (const error of collectInvalidProvidedDownloadHistoryArgs(inputArgs)) {
|
|
369
|
+
io.write(`Invalid ${downloadHistoryPromptLabel(error.key)}: ${error.error}\n`);
|
|
370
|
+
delete inputArgs[error.key];
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
const providedValidationError = validateDownloadHistoryArgs(inputArgs);
|
|
375
|
+
if (providedValidationError) {
|
|
376
|
+
io.write(`Invalid ${downloadHistoryPromptLabel(providedValidationError.key)}: ${providedValidationError.error}\n`);
|
|
377
|
+
io.exit(1);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
345
380
|
}
|
|
346
|
-
const historyArgs = await resolveDownloadHistoryArgs(
|
|
381
|
+
const historyArgs = await resolveDownloadHistoryArgs(inputArgs, io);
|
|
347
382
|
if (!historyArgs) {
|
|
348
|
-
io.write(`Error: Missing required options for download_history: ${missingDownloadHistoryArgs(
|
|
383
|
+
io.write(`Error: Missing required options for download_history: ${missingDownloadHistoryArgs(inputArgs).join(", ")}\n\nRun: insight download_history --help`);
|
|
349
384
|
io.exit(1);
|
|
350
385
|
return;
|
|
351
386
|
}
|
|
@@ -355,6 +390,12 @@ export async function runCli(argv, io) {
|
|
|
355
390
|
io.exit(1);
|
|
356
391
|
return;
|
|
357
392
|
}
|
|
393
|
+
const request = resolveRequest(io, sessionApiKey);
|
|
394
|
+
if (!request) {
|
|
395
|
+
io.write("Error: No API key found.\n\nSet it with: insight login --key <your-api-key>\nOr export: export INSIGHTSENTRY_API_KEY=your-api-key\n\nGet your API key from https://insightsentry.com/dashboard");
|
|
396
|
+
io.exit(1);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
358
399
|
const result = await downloadHistory(parseDownloadHistoryArgs(historyArgs), {
|
|
359
400
|
request,
|
|
360
401
|
onProgress: (event) => {
|
|
@@ -384,13 +425,6 @@ export async function runCli(argv, io) {
|
|
|
384
425
|
io.exit(0);
|
|
385
426
|
return;
|
|
386
427
|
}
|
|
387
|
-
// Resolve request function
|
|
388
|
-
const request = resolveRequest(io);
|
|
389
|
-
if (!request) {
|
|
390
|
-
io.write("Error: No API key found.\n\nSet it with: insight login --key <your-api-key>\nOr export: export INSIGHTSENTRY_API_KEY=your-api-key\n\nGet your API key from https://insightsentry.com/dashboard");
|
|
391
|
-
io.exit(1);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
428
|
const inputArgs = { ...args };
|
|
395
429
|
const interactive = io.isInteractive === true && Boolean(io.prompt);
|
|
396
430
|
if (interactive) {
|
|
@@ -411,9 +445,27 @@ export async function runCli(argv, io) {
|
|
|
411
445
|
}
|
|
412
446
|
const storeModeError = validateStoreMode(tool.name, inputArgs.store);
|
|
413
447
|
if (storeModeError) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
448
|
+
if (interactive) {
|
|
449
|
+
io.write(`Invalid Store: ${storeModeError}\n`);
|
|
450
|
+
delete inputArgs.store;
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
io.write(`Invalid Store: ${storeModeError}\n`);
|
|
454
|
+
io.exit(1);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const filterError = validateFilterExpression(inputArgs.filter);
|
|
459
|
+
if (filterError) {
|
|
460
|
+
if (interactive) {
|
|
461
|
+
io.write(`Invalid Filter: ${filterError}\n`);
|
|
462
|
+
delete inputArgs.filter;
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
io.write(`Invalid Filter: ${filterError}\n`);
|
|
466
|
+
io.exit(1);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
417
469
|
}
|
|
418
470
|
const resolvedArgs = await resolveToolArgs(inputArgs, tool, io);
|
|
419
471
|
if (!resolvedArgs) {
|
|
@@ -428,6 +480,13 @@ export async function runCli(argv, io) {
|
|
|
428
480
|
io.exit(1);
|
|
429
481
|
return;
|
|
430
482
|
}
|
|
483
|
+
// Resolve request function
|
|
484
|
+
const request = resolveRequest(io, sessionApiKey);
|
|
485
|
+
if (!request) {
|
|
486
|
+
io.write("Error: No API key found.\n\nSet it with: insight login --key <your-api-key>\nOr export: export INSIGHTSENTRY_API_KEY=your-api-key\n\nGet your API key from https://insightsentry.com/dashboard");
|
|
487
|
+
io.exit(1);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
431
490
|
try {
|
|
432
491
|
const outputValue = await runApiTool({
|
|
433
492
|
toolName: tool.name,
|
|
@@ -444,6 +503,91 @@ export async function runCli(argv, io) {
|
|
|
444
503
|
io.exit(1);
|
|
445
504
|
}
|
|
446
505
|
}
|
|
506
|
+
async function resolveInteractiveToolName(io) {
|
|
507
|
+
let apiKey;
|
|
508
|
+
const status = (io.getAuthStatus ?? getAuthStatus)();
|
|
509
|
+
if (!status.authenticated) {
|
|
510
|
+
io.write(status.message);
|
|
511
|
+
apiKey = (await resolveLoginKey({}, io)) ?? undefined;
|
|
512
|
+
if (!apiKey) {
|
|
513
|
+
io.write("Usage: insight login --key <your-api-key>\n\nGet your API key from https://insightsentry.com/dashboard");
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
saveConfig({ apiKey });
|
|
517
|
+
io.write(`API key saved to ${getConfigLocation()}`);
|
|
518
|
+
}
|
|
519
|
+
const options = interactiveToolOptions();
|
|
520
|
+
if (io.selectTool) {
|
|
521
|
+
const selected = await io.selectTool(options);
|
|
522
|
+
if (!selected)
|
|
523
|
+
io.write("No tool selected.");
|
|
524
|
+
return selected ? { toolName: selected, apiKey } : null;
|
|
525
|
+
}
|
|
526
|
+
io.write(buildInteractiveToolList(options));
|
|
527
|
+
for (let attempt = 0; attempt < MAX_INTERACTIVE_PROMPT_ATTEMPTS; attempt++) {
|
|
528
|
+
const answer = (await io.prompt?.("Choose tool (number or name): "))?.trim() ?? "";
|
|
529
|
+
const selected = parseToolSelection(answer, options);
|
|
530
|
+
if (selected)
|
|
531
|
+
return { toolName: selected, apiKey };
|
|
532
|
+
io.write("Invalid tool selection.");
|
|
533
|
+
}
|
|
534
|
+
io.write("No tool selected.");
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
async function runUpdateCommand(io) {
|
|
538
|
+
const runCommand = io.runCommand ?? defaultRunCommand;
|
|
539
|
+
io.write("Updating InsightSentry MCP CLI with: npm install -g @insightsentry/mcp");
|
|
540
|
+
try {
|
|
541
|
+
const result = await runCommand("npm", ["install", "-g", "@insightsentry/mcp"]);
|
|
542
|
+
const details = [result.stdout?.trim(), result.stderr?.trim()].filter(Boolean).join("\n");
|
|
543
|
+
if (details)
|
|
544
|
+
io.write(details);
|
|
545
|
+
io.write("InsightSentry MCP CLI updated.");
|
|
546
|
+
io.exit(0);
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
io.write(`Error: ${error?.message ?? String(error)}`);
|
|
550
|
+
io.exit(1);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
async function defaultRunCommand(command, args) {
|
|
554
|
+
return execFileAsync(command, args);
|
|
555
|
+
}
|
|
556
|
+
function interactiveToolOptions() {
|
|
557
|
+
return [
|
|
558
|
+
...toolDefinitions.map((tool) => ({
|
|
559
|
+
name: tool.name,
|
|
560
|
+
description: summarizeToolDescription(tool.description),
|
|
561
|
+
})),
|
|
562
|
+
{
|
|
563
|
+
name: DOWNLOAD_HISTORY_COMMAND,
|
|
564
|
+
description: "Download historical ranges to JSON/CSV files",
|
|
565
|
+
},
|
|
566
|
+
];
|
|
567
|
+
}
|
|
568
|
+
function buildInteractiveToolList(options) {
|
|
569
|
+
return [
|
|
570
|
+
"Choose a tool:",
|
|
571
|
+
"",
|
|
572
|
+
...options.map((option, index) => `${index + 1}. ${option.name} - ${option.description}`),
|
|
573
|
+
"",
|
|
574
|
+
"Use arrow keys in a TTY, or type a number/name.",
|
|
575
|
+
].join("\n");
|
|
576
|
+
}
|
|
577
|
+
function summarizeToolDescription(description) {
|
|
578
|
+
return description.split("→")[0].split(".")[0].trim();
|
|
579
|
+
}
|
|
580
|
+
function parseToolSelection(rawSelection, options) {
|
|
581
|
+
const selection = rawSelection.trim();
|
|
582
|
+
if (!selection)
|
|
583
|
+
return null;
|
|
584
|
+
const number = Number(selection);
|
|
585
|
+
if (Number.isInteger(number) && number >= 1 && number <= options.length) {
|
|
586
|
+
return options[number - 1].name;
|
|
587
|
+
}
|
|
588
|
+
const normalized = selection.toLowerCase();
|
|
589
|
+
return options.find((option) => option.name.toLowerCase() === normalized)?.name ?? null;
|
|
590
|
+
}
|
|
447
591
|
async function resolveLoginKey(args, io) {
|
|
448
592
|
if (args.key?.trim())
|
|
449
593
|
return args.key.trim();
|
|
@@ -460,12 +604,14 @@ async function resolveLoginKey(args, io) {
|
|
|
460
604
|
}
|
|
461
605
|
return null;
|
|
462
606
|
}
|
|
463
|
-
function resolveRequest(io) {
|
|
607
|
+
function resolveRequest(io, apiKeyOverride) {
|
|
464
608
|
if (io.request)
|
|
465
609
|
return io.request;
|
|
466
|
-
const apiKey = resolveApiKey();
|
|
610
|
+
const apiKey = apiKeyOverride?.trim() || resolveApiKey();
|
|
467
611
|
if (!apiKey)
|
|
468
612
|
return null;
|
|
613
|
+
if (io.createRequestFromApiKey)
|
|
614
|
+
return io.createRequestFromApiKey(apiKey);
|
|
469
615
|
const client = new ApiClient(apiKey);
|
|
470
616
|
return (method, path, params) => client.request(method, path, params);
|
|
471
617
|
}
|
|
@@ -508,6 +654,9 @@ async function resolveToolArgs(args, tool, io) {
|
|
|
508
654
|
if (storageAnswer === null)
|
|
509
655
|
return null;
|
|
510
656
|
await resolveStorageDestination(resolved, tool.name, io);
|
|
657
|
+
const filterAnswer = await resolveInteractiveFilter(resolved, io);
|
|
658
|
+
if (filterAnswer === null)
|
|
659
|
+
return null;
|
|
511
660
|
}
|
|
512
661
|
else if (missingToolArgs(resolved, tool).length > 0) {
|
|
513
662
|
return null;
|
|
@@ -528,6 +677,29 @@ async function resolveInteractiveStorageMode(resolved, toolName, io) {
|
|
|
528
677
|
function storeModeSchema(toolName) {
|
|
529
678
|
return supportsCsvStorage(toolName) ? CSV_STORE_SCHEMA : JSON_STORE_SCHEMA;
|
|
530
679
|
}
|
|
680
|
+
async function resolveInteractiveFilter(resolved, io) {
|
|
681
|
+
if (resolved.filter !== undefined)
|
|
682
|
+
return;
|
|
683
|
+
const answer = await promptForOptionalFilterArg(io);
|
|
684
|
+
if (answer === null)
|
|
685
|
+
return null;
|
|
686
|
+
if (answer !== undefined)
|
|
687
|
+
resolved.filter = answer;
|
|
688
|
+
}
|
|
689
|
+
async function promptForOptionalFilterArg(io) {
|
|
690
|
+
const label = "Filter";
|
|
691
|
+
const question = optionalPromptQuestion(label, FILTER_SCHEMA);
|
|
692
|
+
for (let attempt = 0; attempt < MAX_INTERACTIVE_PROMPT_ATTEMPTS; attempt++) {
|
|
693
|
+
const answer = (await io.prompt?.(question))?.trim() ?? "";
|
|
694
|
+
if (!answer)
|
|
695
|
+
return undefined;
|
|
696
|
+
const error = validateFilterExpression(answer);
|
|
697
|
+
if (!error)
|
|
698
|
+
return answer;
|
|
699
|
+
io.write(`Invalid ${label}: ${error}\n`);
|
|
700
|
+
}
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
531
703
|
function shouldSkipInteractiveToolArg(toolName, key, args) {
|
|
532
704
|
if (toolName === "screen_crypto" && key === "countries")
|
|
533
705
|
return true;
|
|
@@ -640,7 +812,7 @@ async function resolveDownloadHistoryArgs(args, io) {
|
|
|
640
812
|
if (resolved[key] !== undefined)
|
|
641
813
|
continue;
|
|
642
814
|
if (REQUIRED_DOWNLOAD_HISTORY_ARGS.includes(key)) {
|
|
643
|
-
const answer = await promptForDownloadHistoryArg(key, io);
|
|
815
|
+
const answer = await promptForDownloadHistoryArg(key, resolved, io);
|
|
644
816
|
if (!answer)
|
|
645
817
|
return null;
|
|
646
818
|
resolved[key] = answer;
|
|
@@ -786,11 +958,18 @@ function validateStoreMode(toolName, store) {
|
|
|
786
958
|
}
|
|
787
959
|
return null;
|
|
788
960
|
}
|
|
789
|
-
async function promptForDownloadHistoryArg(key, io) {
|
|
961
|
+
async function promptForDownloadHistoryArg(key, resolved, io) {
|
|
790
962
|
const label = downloadHistoryPromptLabel(key);
|
|
791
|
-
const
|
|
963
|
+
const defaultAnswer = getDownloadHistoryPromptDefault(key, resolved);
|
|
964
|
+
const question = defaultAnswer
|
|
965
|
+
? promptQuestion(label, downloadHistorySchema[key], "required", {
|
|
966
|
+
blankInstruction: "press Enter to use default",
|
|
967
|
+
value: defaultAnswer,
|
|
968
|
+
})
|
|
969
|
+
: promptQuestion(label, downloadHistorySchema[key], "required");
|
|
792
970
|
for (let attempt = 0; attempt < MAX_INTERACTIVE_PROMPT_ATTEMPTS; attempt++) {
|
|
793
|
-
const
|
|
971
|
+
const rawAnswer = (await io.prompt?.(question))?.trim() ?? "";
|
|
972
|
+
const answer = rawAnswer || defaultAnswer || "";
|
|
794
973
|
const error = validateDownloadHistoryArgAnswer(key, answer);
|
|
795
974
|
if (error) {
|
|
796
975
|
io.write(`Invalid ${label}: ${error}\n`);
|
|
@@ -803,6 +982,20 @@ async function promptForDownloadHistoryArg(key, io) {
|
|
|
803
982
|
}
|
|
804
983
|
return null;
|
|
805
984
|
}
|
|
985
|
+
function getDownloadHistoryPromptDefault(key, resolved) {
|
|
986
|
+
if (key !== "to")
|
|
987
|
+
return null;
|
|
988
|
+
return resolved.bar_type === "second" ? currentUtcDay() : currentUtcMonth();
|
|
989
|
+
}
|
|
990
|
+
function currentUtcDay(date = new Date()) {
|
|
991
|
+
return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())}`;
|
|
992
|
+
}
|
|
993
|
+
function currentUtcMonth(date = new Date()) {
|
|
994
|
+
return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}`;
|
|
995
|
+
}
|
|
996
|
+
function pad2(value) {
|
|
997
|
+
return String(value).padStart(2, "0");
|
|
998
|
+
}
|
|
806
999
|
async function validateDownloadHistoryStorageAnswer(key, answer) {
|
|
807
1000
|
if (key !== "output_dir")
|
|
808
1001
|
return null;
|
|
@@ -859,6 +1052,22 @@ function validateDownloadHistoryArgs(args) {
|
|
|
859
1052
|
return historyIntervalError;
|
|
860
1053
|
return null;
|
|
861
1054
|
}
|
|
1055
|
+
function collectInvalidProvidedDownloadHistoryArgs(args) {
|
|
1056
|
+
const errors = [];
|
|
1057
|
+
for (const key of Object.keys(downloadHistorySchema)) {
|
|
1058
|
+
if (args[key] === undefined)
|
|
1059
|
+
continue;
|
|
1060
|
+
const error = validateDownloadHistoryArgAnswer(key, args[key]);
|
|
1061
|
+
if (error)
|
|
1062
|
+
errors.push({ key, error });
|
|
1063
|
+
}
|
|
1064
|
+
if (!errors.some((error) => error.key === "bar_interval")) {
|
|
1065
|
+
const historyIntervalError = validateHistoryIntervalArgs(DOWNLOAD_HISTORY_COMMAND, args);
|
|
1066
|
+
if (historyIntervalError)
|
|
1067
|
+
errors.push(historyIntervalError);
|
|
1068
|
+
}
|
|
1069
|
+
return errors;
|
|
1070
|
+
}
|
|
862
1071
|
function downloadHistoryPromptLabel(key) {
|
|
863
1072
|
switch (key) {
|
|
864
1073
|
case "symbol":
|
|
@@ -919,7 +1128,7 @@ function parseDownloadHistoryArgs(args) {
|
|
|
919
1128
|
function optionalPromptQuestion(label, zodType) {
|
|
920
1129
|
return promptQuestion(label, zodType, "optional");
|
|
921
1130
|
}
|
|
922
|
-
function promptQuestion(label, zodType, requirement) {
|
|
1131
|
+
function promptQuestion(label, zodType, requirement, defaultOverride) {
|
|
923
1132
|
const parts = [requirement];
|
|
924
1133
|
const enumValues = getZodEnumValues(zodType);
|
|
925
1134
|
if (enumValues.length > 0)
|
|
@@ -929,12 +1138,16 @@ function promptQuestion(label, zodType, requirement) {
|
|
|
929
1138
|
if (typeName && typeName !== "string")
|
|
930
1139
|
parts.push(`type: ${typeName}`);
|
|
931
1140
|
}
|
|
932
|
-
const defaultValue = getPromptDefault(zodType);
|
|
1141
|
+
const defaultValue = defaultOverride?.value ?? getPromptDefault(zodType);
|
|
933
1142
|
if (defaultValue !== null)
|
|
934
1143
|
parts.push(`Default: ${defaultValue}`);
|
|
935
1144
|
const hint = getPromptHint(zodType);
|
|
936
|
-
if (
|
|
1145
|
+
if (defaultOverride) {
|
|
1146
|
+
parts.push(defaultOverride.blankInstruction);
|
|
1147
|
+
}
|
|
1148
|
+
else if (requirement !== "required") {
|
|
937
1149
|
parts.push("press Enter to skip");
|
|
1150
|
+
}
|
|
938
1151
|
const description = hint ? `${label}: ${hint}\n` : "";
|
|
939
1152
|
return `${description}${label} (${parts.join(", ")}): `;
|
|
940
1153
|
}
|
|
@@ -975,13 +1188,114 @@ function getPromptHint(zodType) {
|
|
|
975
1188
|
return null;
|
|
976
1189
|
return hint;
|
|
977
1190
|
}
|
|
1191
|
+
function canUseKeyboardToolSelection() {
|
|
1192
|
+
return (input.isTTY === true &&
|
|
1193
|
+
output.isTTY === true &&
|
|
1194
|
+
typeof input.setRawMode === "function" &&
|
|
1195
|
+
process.env.CI !== "true");
|
|
1196
|
+
}
|
|
1197
|
+
function selectToolWithKeyboard(options) {
|
|
1198
|
+
return new Promise((resolve) => {
|
|
1199
|
+
let selectedIndex = 0;
|
|
1200
|
+
let typed = "";
|
|
1201
|
+
let error = "";
|
|
1202
|
+
let closed = false;
|
|
1203
|
+
const wasRaw = input.isRaw;
|
|
1204
|
+
const render = () => {
|
|
1205
|
+
output.write("\x1B[2J\x1B[H\x1B[?25l");
|
|
1206
|
+
output.write("Choose a tool:\n\n");
|
|
1207
|
+
for (const [index, option] of options.entries()) {
|
|
1208
|
+
const marker = index === selectedIndex ? ">" : " ";
|
|
1209
|
+
output.write(`${marker} ${index + 1}. ${option.name} - ${option.description}\n`);
|
|
1210
|
+
}
|
|
1211
|
+
output.write("\nUse Up/Down, Enter, or type a number/name.");
|
|
1212
|
+
output.write(`\nChoose tool: ${typed}`);
|
|
1213
|
+
if (error)
|
|
1214
|
+
output.write(`\n${error}`);
|
|
1215
|
+
};
|
|
1216
|
+
const cleanup = () => {
|
|
1217
|
+
if (closed)
|
|
1218
|
+
return;
|
|
1219
|
+
closed = true;
|
|
1220
|
+
input.off("keypress", onKeypress);
|
|
1221
|
+
input.setRawMode(Boolean(wasRaw));
|
|
1222
|
+
output.write("\x1B[?25h\n");
|
|
1223
|
+
};
|
|
1224
|
+
const finish = (toolName) => {
|
|
1225
|
+
cleanup();
|
|
1226
|
+
resolve(toolName);
|
|
1227
|
+
};
|
|
1228
|
+
const updateSelectedFromTyped = () => {
|
|
1229
|
+
const selected = parseToolSelection(typed, options);
|
|
1230
|
+
if (!selected)
|
|
1231
|
+
return;
|
|
1232
|
+
selectedIndex = options.findIndex((option) => option.name === selected);
|
|
1233
|
+
};
|
|
1234
|
+
const onKeypress = (value, key) => {
|
|
1235
|
+
if (key.ctrl && key.name === "c") {
|
|
1236
|
+
finish(null);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (key.name === "escape") {
|
|
1240
|
+
finish(null);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (key.name === "up") {
|
|
1244
|
+
selectedIndex = selectedIndex === 0 ? options.length - 1 : selectedIndex - 1;
|
|
1245
|
+
typed = "";
|
|
1246
|
+
error = "";
|
|
1247
|
+
render();
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (key.name === "down") {
|
|
1251
|
+
selectedIndex = selectedIndex === options.length - 1 ? 0 : selectedIndex + 1;
|
|
1252
|
+
typed = "";
|
|
1253
|
+
error = "";
|
|
1254
|
+
render();
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
if (key.name === "return" || key.name === "enter") {
|
|
1258
|
+
const selected = typed.trim()
|
|
1259
|
+
? parseToolSelection(typed, options)
|
|
1260
|
+
: options[selectedIndex].name;
|
|
1261
|
+
if (selected) {
|
|
1262
|
+
finish(selected);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
error = "Invalid tool selection.";
|
|
1266
|
+
render();
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
if (key.name === "backspace") {
|
|
1270
|
+
typed = typed.slice(0, -1);
|
|
1271
|
+
error = "";
|
|
1272
|
+
updateSelectedFromTyped();
|
|
1273
|
+
render();
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
if (value && value >= " " && !key.ctrl && key.sequence !== "\x7F") {
|
|
1277
|
+
typed += value;
|
|
1278
|
+
error = "";
|
|
1279
|
+
updateSelectedFromTyped();
|
|
1280
|
+
render();
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
emitKeypressEvents(input);
|
|
1284
|
+
input.setRawMode(true);
|
|
1285
|
+
input.resume();
|
|
1286
|
+
input.on("keypress", onKeypress);
|
|
1287
|
+
render();
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
978
1290
|
export function main() {
|
|
979
1291
|
const rl = createInterface({ input, output });
|
|
1292
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY && process.env.CI !== "true";
|
|
980
1293
|
runCli(process.argv.slice(2), {
|
|
981
1294
|
write: (s) => process.stdout.write(`${s}\n`),
|
|
982
1295
|
progress: (s) => process.stderr.write(`${s}\n`),
|
|
983
1296
|
prompt: (question) => rl.question(question),
|
|
984
|
-
|
|
1297
|
+
selectTool: canUseKeyboardToolSelection() ? selectToolWithKeyboard : undefined,
|
|
1298
|
+
isInteractive: interactive,
|
|
985
1299
|
exit: (code) => process.exit(code),
|
|
986
1300
|
}).finally(() => rl.close());
|
|
987
1301
|
}
|