@insightsentry/mcp 1.4.39 → 1.4.41
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/README.md +77 -1
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +423 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/stream-output.d.ts +20 -0
- package/dist/stream-output.d.ts.map +1 -0
- package/dist/stream-output.js +203 -0
- package/dist/stream-output.js.map +1 -0
- package/dist/websocket-collector.d.ts +25 -0
- package/dist/websocket-collector.d.ts.map +1 -0
- package/dist/websocket-collector.js +224 -0
- package/dist/websocket-collector.js.map +1 -0
- package/dist/websocket-contract.d.ts +71 -0
- package/dist/websocket-contract.d.ts.map +1 -0
- package/dist/websocket-contract.js +272 -0
- package/dist/websocket-contract.js.map +1 -0
- package/dist/websocket-schemas.d.ts +85 -0
- package/dist/websocket-schemas.d.ts.map +1 -0
- package/dist/websocket-schemas.js +177 -0
- package/dist/websocket-schemas.js.map +1 -0
- package/dist/websocket-stream.d.ts +28 -0
- package/dist/websocket-stream.d.ts.map +1 -0
- package/dist/websocket-stream.js +482 -0
- package/dist/websocket-stream.js.map +1 -0
- package/dist/websocket-tools.d.ts +9 -0
- package/dist/websocket-tools.d.ts.map +1 -0
- package/dist/websocket-tools.js +88 -0
- package/dist/websocket-tools.js.map +1 -0
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
2
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
4
|
import { emitKeypressEvents } from "node:readline";
|
|
4
5
|
import { createInterface } from "node:readline/promises";
|
|
@@ -14,15 +15,21 @@ import { downloadHistory, validateOutputDirectory, } from "./history.js";
|
|
|
14
15
|
import { validateHistoryIntervalArgs } from "./history-validation.js";
|
|
15
16
|
import { PACKAGE_JSON } from "./package-info.js";
|
|
16
17
|
import { supportsCsvStorage, validateResponseStorageTarget, } from "./response-storage.js";
|
|
18
|
+
import { createStreamOutput, } from "./stream-output.js";
|
|
17
19
|
import { analyzeSymbolCodes, shouldPromptSymbolScopedParam } from "./symbol-param-applicability.js";
|
|
18
20
|
import { validateSymbolLikeArg } from "./symbol-validation.js";
|
|
19
21
|
import { toolDefinitions } from "./tool-definitions.js";
|
|
20
22
|
import { runApiTool, validateFilterExpression } from "./tool-runner.js";
|
|
21
23
|
import { fetchLatestPackageVersion, formatUpgradeNotice, formatVersionStatus, getVersionStatus, } from "./version-status.js";
|
|
24
|
+
import { buildLiveStreamRequest, buildNewsfeedStreamRequest, } from "./websocket-contract.js";
|
|
25
|
+
import { LIVE_STREAM_SCHEMA, NEWSFEED_STREAM_SCHEMA, STREAM_OUTPUT_SCHEMA, } from "./websocket-schemas.js";
|
|
26
|
+
import { runWebSocketStream, } from "./websocket-stream.js";
|
|
22
27
|
export { coerceArgs } from "./arg-coercion.js";
|
|
23
28
|
const DOWNLOAD_HISTORY_COMMAND = "download_history";
|
|
24
29
|
const GET_SYMBOL_HISTORY_COMMAND = "get_symbol_history";
|
|
25
30
|
const RENDER_CHART_COMMAND = "render_chart";
|
|
31
|
+
const STREAM_LIVE_COMMAND = "stream_live";
|
|
32
|
+
const STREAM_NEWSFEED_COMMAND = "stream_newsfeed";
|
|
26
33
|
const UPDATE_COMMAND = "update";
|
|
27
34
|
const VERSION_COMMAND = "version";
|
|
28
35
|
const MAX_INTERACTIVE_PROMPT_ATTEMPTS = 3;
|
|
@@ -71,6 +78,7 @@ const RENDER_CHART_SCHEMA = {
|
|
|
71
78
|
};
|
|
72
79
|
export function parseArgs(argv) {
|
|
73
80
|
const args = {};
|
|
81
|
+
const valuelessArgs = new Set();
|
|
74
82
|
let toolName = null;
|
|
75
83
|
let help = false;
|
|
76
84
|
let version = false;
|
|
@@ -97,10 +105,12 @@ export function parseArgs(argv) {
|
|
|
97
105
|
const next = argv[i + 1];
|
|
98
106
|
if (next === undefined || next.startsWith("--")) {
|
|
99
107
|
args[key] = "true";
|
|
108
|
+
valuelessArgs.add(key);
|
|
100
109
|
i++;
|
|
101
110
|
}
|
|
102
111
|
else {
|
|
103
112
|
args[key] = next;
|
|
113
|
+
valuelessArgs.delete(key);
|
|
104
114
|
i += 2;
|
|
105
115
|
}
|
|
106
116
|
}
|
|
@@ -108,7 +118,7 @@ export function parseArgs(argv) {
|
|
|
108
118
|
i++;
|
|
109
119
|
}
|
|
110
120
|
}
|
|
111
|
-
return { toolName, args, help, version };
|
|
121
|
+
return { toolName, args, valuelessArgs: [...valuelessArgs], help, version };
|
|
112
122
|
}
|
|
113
123
|
export function buildVersion() {
|
|
114
124
|
return `${PACKAGE_JSON.name} ${PACKAGE_JSON.version}`;
|
|
@@ -130,6 +140,8 @@ export function buildHelp() {
|
|
|
130
140
|
lines.push(` ${DOWNLOAD_HISTORY_COMMAND.padEnd(32)} Download historical ranges to JSON/CSV files with concurrency and progress`);
|
|
131
141
|
lines.push(` ${GET_SYMBOL_HISTORY_COMMAND.padEnd(32)} Same as download_history; downloads history to files`);
|
|
132
142
|
lines.push(` ${RENDER_CHART_COMMAND.padEnd(32)} Render Chart.js configs as PNG images`);
|
|
143
|
+
lines.push(` ${STREAM_LIVE_COMMAND.padEnd(32)} Stream real-time market data as NDJSON`);
|
|
144
|
+
lines.push(` ${STREAM_NEWSFEED_COMMAND.padEnd(32)} Stream financial news as NDJSON`);
|
|
133
145
|
lines.push("");
|
|
134
146
|
lines.push("Quick Start:");
|
|
135
147
|
lines.push(' insight search_symbols --query "apple"');
|
|
@@ -139,6 +151,8 @@ export function buildHelp() {
|
|
|
139
151
|
lines.push(' insight screen_stocks --fields "close,volume,market_cap" --exchanges "NYSE,NASDAQ" --sortBy market_cap --sortOrder desc');
|
|
140
152
|
lines.push(" insight get_earnings --c US");
|
|
141
153
|
lines.push(' insight get_options_contracts --code "NASDAQ:AAPL" --type call --range 10');
|
|
154
|
+
lines.push(' insight stream_live --symbols "NASDAQ:AAPL" --type both --bar_type minute --max_messages 100 --duration_seconds 30');
|
|
155
|
+
lines.push(" insight stream_newsfeed --output_file ./news.ndjson --append --duration_seconds 30");
|
|
142
156
|
lines.push("");
|
|
143
157
|
lines.push("API tools support --filter <jsonata> to transform the response.");
|
|
144
158
|
lines.push("Use: insight <tool> --help for tool-specific parameters.");
|
|
@@ -201,6 +215,54 @@ export function buildRenderChartHelp() {
|
|
|
201
215
|
' insight render_chart --config \'{"type":"bar","data":{"labels":["Jan"],"datasets":[{"label":"Volume","data":[1200]}]}}\' --width 1200 --height 600',
|
|
202
216
|
].join("\n");
|
|
203
217
|
}
|
|
218
|
+
export function buildStreamHelp(commandName) {
|
|
219
|
+
const isLive = commandName === STREAM_LIVE_COMMAND;
|
|
220
|
+
const schema = isLive
|
|
221
|
+
? { ...LIVE_STREAM_SCHEMA, ...STREAM_OUTPUT_SCHEMA }
|
|
222
|
+
: { ...NEWSFEED_STREAM_SCHEMA, ...STREAM_OUTPUT_SCHEMA };
|
|
223
|
+
const lines = [
|
|
224
|
+
`insight ${commandName}`,
|
|
225
|
+
"",
|
|
226
|
+
isLive
|
|
227
|
+
? "Stream real-time quotes and series as newline-delimited JSON. Use --symbols with --type for common settings, or --subscriptions for per-symbol settings."
|
|
228
|
+
: "Stream matching financial news as newline-delimited JSON.",
|
|
229
|
+
];
|
|
230
|
+
if (isLive) {
|
|
231
|
+
lines.push("", "Subscription modes:", " Simple: --symbols <codes> --type <quote|series|both>", " Per-symbol: --subscriptions <json-array>");
|
|
232
|
+
}
|
|
233
|
+
lines.push("", "Parameters:");
|
|
234
|
+
for (const [key, zodType] of Object.entries(schema)) {
|
|
235
|
+
const requirement = isLive && key === "symbols"
|
|
236
|
+
? "[required with --type]"
|
|
237
|
+
: isLive && key === "type"
|
|
238
|
+
? "[required with --symbols]"
|
|
239
|
+
: isLive && key === "subscriptions"
|
|
240
|
+
? "[alternative to --symbols/--type]"
|
|
241
|
+
: "[optional]";
|
|
242
|
+
lines.push(` --${key.padEnd(24)} ${formatTypeName(zodType)} ${requirement} ${formatHelpDescription(zodType)}`);
|
|
243
|
+
}
|
|
244
|
+
lines.push("", "Boolean options accept a bare flag or an explicit true/false value.", "Received WebSocket frames use stdout as NDJSON; CLI status and local errors use stderr.", "Use --quiet to suppress WebSocket status diagnostics for machine-readable terminal output.");
|
|
245
|
+
if (isLive) {
|
|
246
|
+
lines.push("Quote updates can be sparse; merge updates by code when maintaining a current snapshot.");
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
lines.push("Populated filter fields are combined, so a news item must satisfy every populated field.", "Each initial connection and reconnection replays up to 10 recent matching news items before new items.");
|
|
250
|
+
}
|
|
251
|
+
lines.push("");
|
|
252
|
+
lines.push("Examples:");
|
|
253
|
+
if (isLive) {
|
|
254
|
+
lines.push(' insight stream_live --symbols "NASDAQ:AAPL,NASDAQ:MSFT" --type both --bar_type minute --bar_interval 1 --max_messages 100 --duration_seconds 30');
|
|
255
|
+
lines.push(` insight stream_live --subscriptions '[{"code":"NASDAQ:AAPL","type":"series","bar_type":"day"},{"code":"NASDAQ:MSFT","type":"quote"}]' --output_file ./market.ndjson --append --duration_seconds 60`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
lines.push(" insight stream_newsfeed --max_messages 20 --duration_seconds 30");
|
|
259
|
+
lines.push(' insight stream_newsfeed --content "earnings" --related_symbols "NASDAQ:AAPL" --output_file ./news.ndjson --append --duration_seconds 60');
|
|
260
|
+
}
|
|
261
|
+
lines.push("");
|
|
262
|
+
lines.push("Press Ctrl+C to close the socket after queued output is written.");
|
|
263
|
+
lines.push("Docs: https://insightsentry.com/docs/ws");
|
|
264
|
+
return lines.join("\n");
|
|
265
|
+
}
|
|
204
266
|
const toolExamples = {
|
|
205
267
|
search_symbols: [
|
|
206
268
|
'insight search_symbols --query "tesla"',
|
|
@@ -393,7 +455,7 @@ function isPromptAbortError(error) {
|
|
|
393
455
|
return candidate.code === "ABORT_ERR" || candidate.name === "AbortError";
|
|
394
456
|
}
|
|
395
457
|
async function runCliInner(argv, io) {
|
|
396
|
-
let { toolName, args, help, version } = parseArgs(argv);
|
|
458
|
+
let { toolName, args, valuelessArgs, help, version } = parseArgs(argv);
|
|
397
459
|
let sessionApiKey;
|
|
398
460
|
if (version) {
|
|
399
461
|
io.write(buildVersion());
|
|
@@ -466,6 +528,15 @@ async function runCliInner(argv, io) {
|
|
|
466
528
|
}
|
|
467
529
|
return;
|
|
468
530
|
}
|
|
531
|
+
if (isStreamCommand(toolName)) {
|
|
532
|
+
if (help) {
|
|
533
|
+
io.write(buildStreamHelp(toolName));
|
|
534
|
+
io.exit(0);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
await runStreamCliCommand(toolName, args, valuelessArgs, sessionApiKey, io);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
469
540
|
if (isHistoryDownloadCommand(toolName)) {
|
|
470
541
|
if (help) {
|
|
471
542
|
io.write(buildDownloadHistoryHelp(toolName));
|
|
@@ -701,6 +772,306 @@ async function resolveInteractiveToolName(io) {
|
|
|
701
772
|
io.write("No tool selected.");
|
|
702
773
|
return null;
|
|
703
774
|
}
|
|
775
|
+
async function runStreamCliCommand(commandName, args, valuelessArgs, sessionApiKey, io) {
|
|
776
|
+
const inputSchema = commandName === STREAM_LIVE_COMMAND ? LIVE_STREAM_SCHEMA : NEWSFEED_STREAM_SCHEMA;
|
|
777
|
+
const fullSchema = { ...inputSchema, ...STREAM_OUTPUT_SCHEMA };
|
|
778
|
+
const inputArgs = { ...args };
|
|
779
|
+
const guided = io.isInteractive === true && Boolean(io.prompt) && Object.keys(args).length === 0;
|
|
780
|
+
const unknownArg = Object.keys(inputArgs).find((key) => !(key in fullSchema));
|
|
781
|
+
if (unknownArg) {
|
|
782
|
+
const suggestion = closestStreamOption(unknownArg, Object.keys(fullSchema));
|
|
783
|
+
writeStreamError(io, `Error: Unknown option --${unknownArg}.${suggestion ? ` Did you mean --${suggestion}?` : ` Run: insight ${commandName} --help`}`);
|
|
784
|
+
io.exit(1);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const validationErrors = collectInvalidStreamArgs(inputArgs, fullSchema, valuelessArgs);
|
|
788
|
+
if (validationErrors.length > 0) {
|
|
789
|
+
const validationError = validationErrors[0];
|
|
790
|
+
writeStreamError(io, `Invalid ${toolPromptLabel(validationError.key)}: ${validationError.error}`);
|
|
791
|
+
io.exit(1);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const resolvedArgs = guided ? await resolveStreamArgs(commandName, inputArgs, io) : inputArgs;
|
|
795
|
+
if (!resolvedArgs) {
|
|
796
|
+
writeStreamError(io, `Error: Missing required streaming options.\n\nRun: insight ${commandName} --help`);
|
|
797
|
+
io.exit(1);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
const resolvedValidationError = validateStreamArgs(resolvedArgs, fullSchema);
|
|
801
|
+
if (resolvedValidationError) {
|
|
802
|
+
writeStreamError(io, `Invalid ${toolPromptLabel(resolvedValidationError.key)}: ${resolvedValidationError.error}`);
|
|
803
|
+
io.exit(1);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const outputDependencyError = validateStreamOutputDependencies(resolvedArgs, fullSchema);
|
|
807
|
+
if (outputDependencyError) {
|
|
808
|
+
writeStreamError(io, `Error: ${outputDependencyError}`);
|
|
809
|
+
io.exit(1);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const apiKey = sessionApiKey?.trim() || io.getApiKey?.()?.trim() || resolveApiKey();
|
|
813
|
+
if (!apiKey) {
|
|
814
|
+
writeStreamError(io, "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");
|
|
815
|
+
io.exit(1);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
const knownArgs = pickKnownArgs(resolvedArgs, Object.keys(fullSchema));
|
|
820
|
+
const coerced = coerceArgs(knownArgs, fullSchema);
|
|
821
|
+
const request = commandName === STREAM_LIVE_COMMAND
|
|
822
|
+
? buildLiveStreamRequest(pickStreamInput(coerced, LIVE_STREAM_SCHEMA), apiKey)
|
|
823
|
+
: buildNewsfeedStreamRequest(pickStreamInput(coerced, NEWSFEED_STREAM_SCHEMA), apiKey);
|
|
824
|
+
if (coerced.quiet !== true) {
|
|
825
|
+
reportArgUsage(io, knownArgs, Object.keys(fullSchema), inputArgs);
|
|
826
|
+
}
|
|
827
|
+
if (coerced.quiet !== true && request.ignoredArgs.length > 0) {
|
|
828
|
+
io.progress?.(`Ignoring inapplicable subscription args: ${request.ignoredArgs.join(", ")}`);
|
|
829
|
+
}
|
|
830
|
+
const outputFactory = io.createStreamOutput ?? createStreamOutput;
|
|
831
|
+
const streamOutput = await outputFactory({
|
|
832
|
+
outputFile: optionalString(coerced.output_file),
|
|
833
|
+
append: coerced.append === true,
|
|
834
|
+
tee: coerced.tee === true,
|
|
835
|
+
});
|
|
836
|
+
try {
|
|
837
|
+
const runner = io.runStream ?? runWebSocketStream;
|
|
838
|
+
const result = await runner({
|
|
839
|
+
request,
|
|
840
|
+
output: streamOutput,
|
|
841
|
+
diagnostics: coerced.quiet === true ? () => { } : io.progress,
|
|
842
|
+
signal: io.streamSignal,
|
|
843
|
+
maxReconnectAttempts: typeof coerced.max_reconnect_attempts === "number"
|
|
844
|
+
? coerced.max_reconnect_attempts
|
|
845
|
+
: undefined,
|
|
846
|
+
durationMs: typeof coerced.duration_seconds === "number"
|
|
847
|
+
? coerced.duration_seconds * 1_000
|
|
848
|
+
: undefined,
|
|
849
|
+
maxMessages: typeof coerced.max_messages === "number" ? coerced.max_messages : undefined,
|
|
850
|
+
});
|
|
851
|
+
io.exit(io.getStreamSignalExitCode?.() ?? result.exitCode);
|
|
852
|
+
}
|
|
853
|
+
catch (error) {
|
|
854
|
+
await streamOutput.close().catch(() => { });
|
|
855
|
+
throw error;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
writeStreamError(io, `Error: ${errorMessage(error)}`);
|
|
860
|
+
io.exit(1);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
async function resolveStreamArgs(commandName, args, io) {
|
|
864
|
+
if (io.isInteractive !== true || !io.prompt)
|
|
865
|
+
return args;
|
|
866
|
+
const resolved = { ...args };
|
|
867
|
+
if (commandName === STREAM_LIVE_COMMAND) {
|
|
868
|
+
const symbols = await promptForToolArg("symbols", LIVE_STREAM_SCHEMA.symbols, io);
|
|
869
|
+
if (!symbols)
|
|
870
|
+
return null;
|
|
871
|
+
resolved.symbols = symbols;
|
|
872
|
+
const type = await promptForToolArg("type", LIVE_STREAM_SCHEMA.type, io);
|
|
873
|
+
if (!type)
|
|
874
|
+
return null;
|
|
875
|
+
resolved.type = type;
|
|
876
|
+
if (type === "series" || type === "both") {
|
|
877
|
+
const seriesSettings = await promptForStreamChoice("Series Settings", "Use server defaults or configure bar settings.", ["default", "custom"], "default", io);
|
|
878
|
+
if (!seriesSettings)
|
|
879
|
+
return null;
|
|
880
|
+
if (seriesSettings === "custom") {
|
|
881
|
+
const barType = await promptForToolArg("bar_type", LIVE_STREAM_SCHEMA.bar_type, io);
|
|
882
|
+
if (!barType)
|
|
883
|
+
return null;
|
|
884
|
+
resolved.bar_type = barType;
|
|
885
|
+
for (const key of ["bar_interval", "dp"]) {
|
|
886
|
+
const answer = await promptForOptionalToolArg(key, LIVE_STREAM_SCHEMA[key], io);
|
|
887
|
+
if (answer === null)
|
|
888
|
+
return null;
|
|
889
|
+
if (answer !== undefined)
|
|
890
|
+
resolved[key] = answer;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const advanced = await promptForStreamChoice("Advanced Settings", "Configure session, adjustment, currency, unit, or delivery options.", ["no", "yes"], "no", io);
|
|
895
|
+
if (!advanced)
|
|
896
|
+
return null;
|
|
897
|
+
if (advanced === "yes") {
|
|
898
|
+
for (const key of [
|
|
899
|
+
"currency",
|
|
900
|
+
"unit",
|
|
901
|
+
"extended",
|
|
902
|
+
"24h",
|
|
903
|
+
"split",
|
|
904
|
+
"dadj",
|
|
905
|
+
"badj",
|
|
906
|
+
"settlement",
|
|
907
|
+
"slow_consumer_policy",
|
|
908
|
+
]) {
|
|
909
|
+
if (shouldSkipLiveStreamPrompt(key, type))
|
|
910
|
+
continue;
|
|
911
|
+
const answer = await promptForOptionalToolArg(key, LIVE_STREAM_SCHEMA[key], io);
|
|
912
|
+
if (answer === null)
|
|
913
|
+
return null;
|
|
914
|
+
if (answer !== undefined)
|
|
915
|
+
resolved[key] = answer;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
const selection = await promptForStreamChoice("News Selection", "Stream all news or configure matching filters.", ["all", "filtered"], "all", io);
|
|
921
|
+
if (!selection)
|
|
922
|
+
return null;
|
|
923
|
+
if (selection === "filtered") {
|
|
924
|
+
for (const [key, zodType] of Object.entries(NEWSFEED_STREAM_SCHEMA)) {
|
|
925
|
+
const answer = await promptForOptionalToolArg(key, zodType, io);
|
|
926
|
+
if (answer === null)
|
|
927
|
+
return null;
|
|
928
|
+
if (answer !== undefined)
|
|
929
|
+
resolved[key] = answer;
|
|
930
|
+
}
|
|
931
|
+
if (!Object.keys(NEWSFEED_STREAM_SCHEMA).some((key) => hasArgValue(resolved[key]))) {
|
|
932
|
+
writeStreamError(io, "No newsfeed filters were entered; streaming all news.");
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return (await resolveInteractiveStreamOutput(resolved, io)) ? resolved : null;
|
|
937
|
+
}
|
|
938
|
+
async function resolveInteractiveStreamOutput(resolved, io) {
|
|
939
|
+
const destination = await promptForStreamChoice("Output Destination", "Choose where received NDJSON messages are written.", ["terminal", "file", "both"], "terminal", io);
|
|
940
|
+
if (!destination)
|
|
941
|
+
return false;
|
|
942
|
+
if (destination === "terminal")
|
|
943
|
+
return true;
|
|
944
|
+
const outputFile = await promptForToolArg("output_file", STREAM_OUTPUT_SCHEMA.output_file, io);
|
|
945
|
+
if (!outputFile)
|
|
946
|
+
return false;
|
|
947
|
+
resolved.output_file = outputFile;
|
|
948
|
+
if (destination === "both")
|
|
949
|
+
resolved.tee = "true";
|
|
950
|
+
if (await pathExists(outputFile)) {
|
|
951
|
+
const existingFileAction = await promptForStreamChoice("Existing Output File", "Append to the existing file or replace it. Press Ctrl+C to cancel.", ["append", "replace"], "append", io);
|
|
952
|
+
if (!existingFileAction)
|
|
953
|
+
return false;
|
|
954
|
+
resolved.append = existingFileAction === "append" ? "true" : "false";
|
|
955
|
+
}
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
async function promptForStreamChoice(label, description, choices, defaultValue, io) {
|
|
959
|
+
const question = `${label}: ${description}\n${label} (choices: ${choices.join("/")}, Default: ${defaultValue}): `;
|
|
960
|
+
for (let attempt = 0; attempt < MAX_INTERACTIVE_PROMPT_ATTEMPTS; attempt++) {
|
|
961
|
+
const raw = (await io.prompt?.(question))?.trim().toLowerCase() ?? "";
|
|
962
|
+
const answer = normalizeStreamChoice(raw || defaultValue, choices);
|
|
963
|
+
if (answer)
|
|
964
|
+
return answer;
|
|
965
|
+
writeStreamError(io, `Invalid ${label}: expected one of: ${choices.join(", ")}`);
|
|
966
|
+
}
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
function normalizeStreamChoice(value, choices) {
|
|
970
|
+
const aliases = {
|
|
971
|
+
y: "yes",
|
|
972
|
+
n: "no",
|
|
973
|
+
stdout: "terminal",
|
|
974
|
+
tee: "both",
|
|
975
|
+
};
|
|
976
|
+
const normalized = aliases[value] ?? value;
|
|
977
|
+
return choices.includes(normalized) ? normalized : null;
|
|
978
|
+
}
|
|
979
|
+
async function pathExists(value) {
|
|
980
|
+
try {
|
|
981
|
+
await access(value);
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
function validateStreamArgs(args, schema) {
|
|
989
|
+
for (const [key, zodType] of Object.entries(schema)) {
|
|
990
|
+
if (args[key] === undefined)
|
|
991
|
+
continue;
|
|
992
|
+
const error = validateToolArgAnswer(key, zodType, args[key]);
|
|
993
|
+
if (error)
|
|
994
|
+
return { key, error };
|
|
995
|
+
}
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
function collectInvalidStreamArgs(args, schema, valuelessArgs) {
|
|
999
|
+
const valueless = new Set(valuelessArgs);
|
|
1000
|
+
const errors = [];
|
|
1001
|
+
for (const [key, zodType] of Object.entries(schema)) {
|
|
1002
|
+
if (args[key] === undefined)
|
|
1003
|
+
continue;
|
|
1004
|
+
if (valueless.has(key) && getZodTypeName(zodType) !== "boolean") {
|
|
1005
|
+
errors.push({ key, error: "value is required" });
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
const error = validateToolArgAnswer(key, zodType, args[key]);
|
|
1009
|
+
if (error)
|
|
1010
|
+
errors.push({ key, error });
|
|
1011
|
+
}
|
|
1012
|
+
return errors;
|
|
1013
|
+
}
|
|
1014
|
+
function validateStreamOutputDependencies(args, schema) {
|
|
1015
|
+
if (hasArgValue(args.output_file))
|
|
1016
|
+
return null;
|
|
1017
|
+
const coerced = coerceArgs(args, schema);
|
|
1018
|
+
if (coerced.append === true)
|
|
1019
|
+
return "--append requires --output_file.";
|
|
1020
|
+
if (coerced.tee === true)
|
|
1021
|
+
return "--tee requires --output_file.";
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
function writeStreamError(io, message) {
|
|
1025
|
+
(io.progress ?? io.write)(message);
|
|
1026
|
+
}
|
|
1027
|
+
function closestStreamOption(input, options) {
|
|
1028
|
+
const boundedInput = input.slice(0, 64);
|
|
1029
|
+
let closest;
|
|
1030
|
+
for (const option of options) {
|
|
1031
|
+
const distance = editDistance(boundedInput, option);
|
|
1032
|
+
if (!closest || distance < closest.distance)
|
|
1033
|
+
closest = { option, distance };
|
|
1034
|
+
}
|
|
1035
|
+
if (!closest || closest.distance > Math.max(2, Math.floor(boundedInput.length / 3)))
|
|
1036
|
+
return null;
|
|
1037
|
+
return closest.option;
|
|
1038
|
+
}
|
|
1039
|
+
function editDistance(left, right) {
|
|
1040
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
1041
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex++) {
|
|
1042
|
+
const current = [leftIndex];
|
|
1043
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex++) {
|
|
1044
|
+
current[rightIndex] = Math.min(current[rightIndex - 1] + 1, previous[rightIndex] + 1, previous[rightIndex - 1] + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
|
|
1045
|
+
}
|
|
1046
|
+
previous = current;
|
|
1047
|
+
}
|
|
1048
|
+
return previous[right.length];
|
|
1049
|
+
}
|
|
1050
|
+
function pickStreamInput(values, schema) {
|
|
1051
|
+
const known = new Set(Object.keys(schema));
|
|
1052
|
+
return Object.fromEntries(Object.entries(values).filter(([key]) => known.has(key)));
|
|
1053
|
+
}
|
|
1054
|
+
function shouldSkipLiveStreamPrompt(key, type) {
|
|
1055
|
+
if (type !== "quote" && type !== "quotes")
|
|
1056
|
+
return false;
|
|
1057
|
+
return [
|
|
1058
|
+
"bar_type",
|
|
1059
|
+
"bar_interval",
|
|
1060
|
+
"dp",
|
|
1061
|
+
"currency",
|
|
1062
|
+
"unit",
|
|
1063
|
+
"split",
|
|
1064
|
+
"dadj",
|
|
1065
|
+
"badj",
|
|
1066
|
+
"settlement",
|
|
1067
|
+
].includes(key);
|
|
1068
|
+
}
|
|
1069
|
+
function optionalString(value) {
|
|
1070
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
1071
|
+
}
|
|
1072
|
+
function errorMessage(error) {
|
|
1073
|
+
return error instanceof Error ? error.message : String(error);
|
|
1074
|
+
}
|
|
704
1075
|
async function runUpdateCommand(io) {
|
|
705
1076
|
const runCommand = io.runCommand ?? defaultRunCommand;
|
|
706
1077
|
const status = await getCachedVersionStatus(io);
|
|
@@ -767,11 +1138,22 @@ function interactiveToolOptions() {
|
|
|
767
1138
|
name: RENDER_CHART_COMMAND,
|
|
768
1139
|
description: "Render Chart.js configs as PNG images",
|
|
769
1140
|
},
|
|
1141
|
+
{
|
|
1142
|
+
name: STREAM_LIVE_COMMAND,
|
|
1143
|
+
description: "Stream real-time market data as NDJSON",
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
name: STREAM_NEWSFEED_COMMAND,
|
|
1147
|
+
description: "Stream financial news as NDJSON",
|
|
1148
|
+
},
|
|
770
1149
|
];
|
|
771
1150
|
}
|
|
772
1151
|
function isHistoryDownloadCommand(toolName) {
|
|
773
1152
|
return toolName === DOWNLOAD_HISTORY_COMMAND || toolName === GET_SYMBOL_HISTORY_COMMAND;
|
|
774
1153
|
}
|
|
1154
|
+
function isStreamCommand(toolName) {
|
|
1155
|
+
return toolName === STREAM_LIVE_COMMAND || toolName === STREAM_NEWSFEED_COMMAND;
|
|
1156
|
+
}
|
|
775
1157
|
function buildInteractiveToolList(options) {
|
|
776
1158
|
return [
|
|
777
1159
|
"Choose a tool:",
|
|
@@ -1596,6 +1978,21 @@ function selectToolWithKeyboard(options) {
|
|
|
1596
1978
|
export function main() {
|
|
1597
1979
|
const rl = createInterface({ input, output });
|
|
1598
1980
|
const interactive = process.stdin.isTTY && process.stdout.isTTY && process.env.CI !== "true";
|
|
1981
|
+
const streamAbort = new AbortController();
|
|
1982
|
+
let streamRunning = false;
|
|
1983
|
+
let signalExitCode;
|
|
1984
|
+
const handleSignal = (exitCode) => {
|
|
1985
|
+
signalExitCode = exitCode;
|
|
1986
|
+
streamAbort.abort();
|
|
1987
|
+
if (!streamRunning) {
|
|
1988
|
+
process.exitCode = exitCode;
|
|
1989
|
+
rl.close();
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
const handleSigint = () => handleSignal(130);
|
|
1993
|
+
const handleSigterm = () => handleSignal(143);
|
|
1994
|
+
process.once("SIGINT", handleSigint);
|
|
1995
|
+
process.once("SIGTERM", handleSigterm);
|
|
1599
1996
|
runCli(process.argv.slice(2), {
|
|
1600
1997
|
write: (s) => process.stdout.write(`${s}\n`),
|
|
1601
1998
|
progress: (s) => process.stderr.write(`${s}\n`),
|
|
@@ -1603,7 +2000,29 @@ export function main() {
|
|
|
1603
2000
|
prompt: (question) => rl.question(question),
|
|
1604
2001
|
selectTool: canUseKeyboardToolSelection() ? selectToolWithKeyboard : undefined,
|
|
1605
2002
|
isInteractive: interactive,
|
|
1606
|
-
|
|
1607
|
-
|
|
2003
|
+
streamSignal: streamAbort.signal,
|
|
2004
|
+
getStreamSignalExitCode: () => signalExitCode,
|
|
2005
|
+
runStream: async (options) => {
|
|
2006
|
+
streamRunning = true;
|
|
2007
|
+
try {
|
|
2008
|
+
return await runWebSocketStream(options);
|
|
2009
|
+
}
|
|
2010
|
+
finally {
|
|
2011
|
+
streamRunning = false;
|
|
2012
|
+
}
|
|
2013
|
+
},
|
|
2014
|
+
exit: (code) => {
|
|
2015
|
+
process.exitCode = code;
|
|
2016
|
+
},
|
|
2017
|
+
})
|
|
2018
|
+
.catch((error) => {
|
|
2019
|
+
process.stderr.write(`Error: ${errorMessage(error)}\n`);
|
|
2020
|
+
process.exitCode = 1;
|
|
2021
|
+
})
|
|
2022
|
+
.finally(() => {
|
|
2023
|
+
process.removeListener("SIGINT", handleSigint);
|
|
2024
|
+
process.removeListener("SIGTERM", handleSigterm);
|
|
2025
|
+
rl.close();
|
|
2026
|
+
});
|
|
1608
2027
|
}
|
|
1609
2028
|
//# sourceMappingURL=cli.js.map
|