@insightsentry/mcp 1.4.38 → 1.4.40
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 +64 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +507 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +6 -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/tool-definitions.d.ts.map +1 -1
- package/dist/tool-definitions.js +44 -3
- package/dist/tool-definitions.js.map +1 -1
- package/dist/websocket-contract.d.ts +70 -0
- package/dist/websocket-contract.d.ts.map +1 -0
- package/dist/websocket-contract.js +269 -0
- package/dist/websocket-contract.js.map +1 -0
- package/dist/websocket-stream.d.ts +27 -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/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,20 @@ 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 { runWebSocketStream, } from "./websocket-stream.js";
|
|
22
26
|
export { coerceArgs } from "./arg-coercion.js";
|
|
23
27
|
const DOWNLOAD_HISTORY_COMMAND = "download_history";
|
|
24
28
|
const GET_SYMBOL_HISTORY_COMMAND = "get_symbol_history";
|
|
25
29
|
const RENDER_CHART_COMMAND = "render_chart";
|
|
30
|
+
const STREAM_LIVE_COMMAND = "stream_live";
|
|
31
|
+
const STREAM_NEWSFEED_COMMAND = "stream_newsfeed";
|
|
26
32
|
const UPDATE_COMMAND = "update";
|
|
27
33
|
const VERSION_COMMAND = "version";
|
|
28
34
|
const MAX_INTERACTIVE_PROMPT_ATTEMPTS = 3;
|
|
@@ -69,8 +75,86 @@ const RENDER_CHART_SCHEMA = {
|
|
|
69
75
|
.optional()
|
|
70
76
|
.describe("Chart height in pixels. Default: 400."),
|
|
71
77
|
};
|
|
78
|
+
const LIVE_STREAM_SCHEMA = {
|
|
79
|
+
symbols: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Comma-separated EXCHANGE:SYMBOL codes for simple subscriptions."),
|
|
83
|
+
type: z
|
|
84
|
+
.enum(["quote", "quotes", "series", "both"])
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Data to receive for every symbol. quotes is accepted as an alias for quote."),
|
|
87
|
+
subscriptions: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("JSON array of subscription objects for different per-symbol settings."),
|
|
91
|
+
bar_type: z
|
|
92
|
+
.enum(["tick", "second", "minute", "hour", "day", "week", "month"])
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Series bar type."),
|
|
95
|
+
bar_interval: z
|
|
96
|
+
.number()
|
|
97
|
+
.int()
|
|
98
|
+
.positive()
|
|
99
|
+
.optional()
|
|
100
|
+
.describe("Series interval. Tick requires 1; second accepts 1, 5, 10, 15, 30, or 45."),
|
|
101
|
+
dp: z
|
|
102
|
+
.number()
|
|
103
|
+
.int()
|
|
104
|
+
.min(1)
|
|
105
|
+
.max(30_000)
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("Initial series data points, 1-30000."),
|
|
108
|
+
currency: z.string().optional().describe("Requested currency code."),
|
|
109
|
+
unit: z.string().optional().describe("Requested unit code."),
|
|
110
|
+
extended: z.boolean().optional().describe("Include extended-hours data."),
|
|
111
|
+
"24h": z.boolean().optional().describe("Include 24-hour market data."),
|
|
112
|
+
split: z.boolean().optional().describe("Apply split adjustment."),
|
|
113
|
+
dadj: z.boolean().optional().describe("Apply dividend adjustment."),
|
|
114
|
+
badj: z.boolean().optional().describe("Back-adjust continuous futures."),
|
|
115
|
+
settlement: z.boolean().optional().describe("Use settlement as daily close."),
|
|
116
|
+
slow_consumer_policy: z
|
|
117
|
+
.enum(["disconnect", "adaptive"])
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Use adaptive for fresh lower-frequency delivery instead of backlog disconnects."),
|
|
120
|
+
};
|
|
121
|
+
const NEWSFEED_STREAM_SCHEMA = {
|
|
122
|
+
source: z.string().optional().describe("Case-insensitive exact source name."),
|
|
123
|
+
related_symbols: z.string().optional().describe("Comma- or space-separated related symbols."),
|
|
124
|
+
content: z
|
|
125
|
+
.string()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe("Comma- or space-separated terms; matches when content contains at least one term."),
|
|
128
|
+
};
|
|
129
|
+
const STREAM_OUTPUT_SCHEMA = {
|
|
130
|
+
output_file: z.string().optional().describe("Write NDJSON messages to this file."),
|
|
131
|
+
append: z
|
|
132
|
+
.boolean()
|
|
133
|
+
.optional()
|
|
134
|
+
.describe("Append instead of replacing the output file. Requires output_file."),
|
|
135
|
+
tee: z.boolean().optional().describe("Also write NDJSON to stdout when output_file is set."),
|
|
136
|
+
max_reconnect_attempts: z
|
|
137
|
+
.number()
|
|
138
|
+
.int()
|
|
139
|
+
.min(0)
|
|
140
|
+
.max(100)
|
|
141
|
+
.optional()
|
|
142
|
+
.describe("Maximum consecutive reconnect or request-retry attempts. Use 0 to disable automatic reconnects. Default: 10."),
|
|
143
|
+
duration_seconds: z
|
|
144
|
+
.number()
|
|
145
|
+
.positive()
|
|
146
|
+
.optional()
|
|
147
|
+
.describe("Stop successfully after this total duration, including reconnects."),
|
|
148
|
+
max_messages: z
|
|
149
|
+
.number()
|
|
150
|
+
.int()
|
|
151
|
+
.positive()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe("Stop successfully after this many data messages; status and time frames do not count."),
|
|
154
|
+
};
|
|
72
155
|
export function parseArgs(argv) {
|
|
73
156
|
const args = {};
|
|
157
|
+
const valuelessArgs = new Set();
|
|
74
158
|
let toolName = null;
|
|
75
159
|
let help = false;
|
|
76
160
|
let version = false;
|
|
@@ -97,10 +181,12 @@ export function parseArgs(argv) {
|
|
|
97
181
|
const next = argv[i + 1];
|
|
98
182
|
if (next === undefined || next.startsWith("--")) {
|
|
99
183
|
args[key] = "true";
|
|
184
|
+
valuelessArgs.add(key);
|
|
100
185
|
i++;
|
|
101
186
|
}
|
|
102
187
|
else {
|
|
103
188
|
args[key] = next;
|
|
189
|
+
valuelessArgs.delete(key);
|
|
104
190
|
i += 2;
|
|
105
191
|
}
|
|
106
192
|
}
|
|
@@ -108,7 +194,7 @@ export function parseArgs(argv) {
|
|
|
108
194
|
i++;
|
|
109
195
|
}
|
|
110
196
|
}
|
|
111
|
-
return { toolName, args, help, version };
|
|
197
|
+
return { toolName, args, valuelessArgs: [...valuelessArgs], help, version };
|
|
112
198
|
}
|
|
113
199
|
export function buildVersion() {
|
|
114
200
|
return `${PACKAGE_JSON.name} ${PACKAGE_JSON.version}`;
|
|
@@ -130,6 +216,8 @@ export function buildHelp() {
|
|
|
130
216
|
lines.push(` ${DOWNLOAD_HISTORY_COMMAND.padEnd(32)} Download historical ranges to JSON/CSV files with concurrency and progress`);
|
|
131
217
|
lines.push(` ${GET_SYMBOL_HISTORY_COMMAND.padEnd(32)} Same as download_history; downloads history to files`);
|
|
132
218
|
lines.push(` ${RENDER_CHART_COMMAND.padEnd(32)} Render Chart.js configs as PNG images`);
|
|
219
|
+
lines.push(` ${STREAM_LIVE_COMMAND.padEnd(32)} Stream real-time market data as NDJSON`);
|
|
220
|
+
lines.push(` ${STREAM_NEWSFEED_COMMAND.padEnd(32)} Stream financial news as NDJSON`);
|
|
133
221
|
lines.push("");
|
|
134
222
|
lines.push("Quick Start:");
|
|
135
223
|
lines.push(' insight search_symbols --query "apple"');
|
|
@@ -139,6 +227,8 @@ export function buildHelp() {
|
|
|
139
227
|
lines.push(' insight screen_stocks --fields "close,volume,market_cap" --exchanges "NYSE,NASDAQ" --sortBy market_cap --sortOrder desc');
|
|
140
228
|
lines.push(" insight get_earnings --c US");
|
|
141
229
|
lines.push(' insight get_options_contracts --code "NASDAQ:AAPL" --type call --range 10');
|
|
230
|
+
lines.push(' insight stream_live --symbols "NASDAQ:AAPL" --type both --bar_type minute');
|
|
231
|
+
lines.push(" insight stream_newsfeed --output_file ./news.ndjson --append");
|
|
142
232
|
lines.push("");
|
|
143
233
|
lines.push("API tools support --filter <jsonata> to transform the response.");
|
|
144
234
|
lines.push("Use: insight <tool> --help for tool-specific parameters.");
|
|
@@ -201,6 +291,54 @@ export function buildRenderChartHelp() {
|
|
|
201
291
|
' insight render_chart --config \'{"type":"bar","data":{"labels":["Jan"],"datasets":[{"label":"Volume","data":[1200]}]}}\' --width 1200 --height 600',
|
|
202
292
|
].join("\n");
|
|
203
293
|
}
|
|
294
|
+
export function buildStreamHelp(commandName) {
|
|
295
|
+
const isLive = commandName === STREAM_LIVE_COMMAND;
|
|
296
|
+
const schema = isLive
|
|
297
|
+
? { ...LIVE_STREAM_SCHEMA, ...STREAM_OUTPUT_SCHEMA }
|
|
298
|
+
: { ...NEWSFEED_STREAM_SCHEMA, ...STREAM_OUTPUT_SCHEMA };
|
|
299
|
+
const lines = [
|
|
300
|
+
`insight ${commandName}`,
|
|
301
|
+
"",
|
|
302
|
+
isLive
|
|
303
|
+
? "Stream real-time quotes and series as newline-delimited JSON. Use --symbols with --type for common settings, or --subscriptions for per-symbol settings."
|
|
304
|
+
: "Stream matching financial news as newline-delimited JSON.",
|
|
305
|
+
];
|
|
306
|
+
if (isLive) {
|
|
307
|
+
lines.push("", "Subscription modes:", " Simple: --symbols <codes> --type <quote|series|both>", " Per-symbol: --subscriptions <json-array>");
|
|
308
|
+
}
|
|
309
|
+
lines.push("", "Parameters:");
|
|
310
|
+
for (const [key, zodType] of Object.entries(schema)) {
|
|
311
|
+
const requirement = isLive && key === "symbols"
|
|
312
|
+
? "[required with --type]"
|
|
313
|
+
: isLive && key === "type"
|
|
314
|
+
? "[required with --symbols]"
|
|
315
|
+
: isLive && key === "subscriptions"
|
|
316
|
+
? "[alternative to --symbols/--type]"
|
|
317
|
+
: "[optional]";
|
|
318
|
+
lines.push(` --${key.padEnd(24)} ${formatTypeName(zodType)} ${requirement} ${formatHelpDescription(zodType)}`);
|
|
319
|
+
}
|
|
320
|
+
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.");
|
|
321
|
+
if (isLive) {
|
|
322
|
+
lines.push("Quote updates can be sparse; merge updates by code when maintaining a current snapshot.");
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
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.");
|
|
326
|
+
}
|
|
327
|
+
lines.push("");
|
|
328
|
+
lines.push("Examples:");
|
|
329
|
+
if (isLive) {
|
|
330
|
+
lines.push(' insight stream_live --symbols "NASDAQ:AAPL,NASDAQ:MSFT" --type both --bar_type minute --bar_interval 1');
|
|
331
|
+
lines.push(` insight stream_live --subscriptions '[{"code":"NASDAQ:AAPL","type":"series","bar_type":"day"},{"code":"NASDAQ:MSFT","type":"quote"}]' --output_file ./market.ndjson --append`);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
lines.push(" insight stream_newsfeed");
|
|
335
|
+
lines.push(' insight stream_newsfeed --content "earnings" --related_symbols "NASDAQ:AAPL" --output_file ./news.ndjson --append');
|
|
336
|
+
}
|
|
337
|
+
lines.push("");
|
|
338
|
+
lines.push("Press Ctrl+C to close the socket after queued output is written.");
|
|
339
|
+
lines.push("Docs: https://insightsentry.com/docs/ws");
|
|
340
|
+
return lines.join("\n");
|
|
341
|
+
}
|
|
204
342
|
const toolExamples = {
|
|
205
343
|
search_symbols: [
|
|
206
344
|
'insight search_symbols --query "tesla"',
|
|
@@ -221,6 +359,16 @@ const toolExamples = {
|
|
|
221
359
|
'insight get_symbol_history --symbol "NASDAQ:AAPL" --bar_type minute --from 2025-01 --to 2025-06 --output_dir ./history',
|
|
222
360
|
'insight get_symbol_history --symbol "NASDAQ:AAPL" --bar_type hour --from 2025-06 --to 2025-06 --output_dir ./history --bar_interval 4',
|
|
223
361
|
],
|
|
362
|
+
get_symbol_history_period: [
|
|
363
|
+
'insight get_symbol_history_period --symbol "NASDAQ:AAPL" --bar_type minute --start_date 2025-01',
|
|
364
|
+
],
|
|
365
|
+
get_sip_quotes: ['insight get_sip_quotes --codes "SIP:AAPL,SIP:MSFT"'],
|
|
366
|
+
get_sip_series: [
|
|
367
|
+
'insight get_sip_series --code "SIP:AAPL" --bar_type minute --bar_interval 5 --dp 100',
|
|
368
|
+
],
|
|
369
|
+
get_sip_history: [
|
|
370
|
+
'insight get_sip_history --code "SIP:AAPL" --bar_type minute --start_date 2026-08',
|
|
371
|
+
],
|
|
224
372
|
get_symbol_contracts: ['insight get_symbol_contracts --symbol "CME_MINI:NQ1!"'],
|
|
225
373
|
get_symbol_info: [
|
|
226
374
|
'insight get_symbol_info --symbol "NASDAQ:AAPL"',
|
|
@@ -383,7 +531,7 @@ function isPromptAbortError(error) {
|
|
|
383
531
|
return candidate.code === "ABORT_ERR" || candidate.name === "AbortError";
|
|
384
532
|
}
|
|
385
533
|
async function runCliInner(argv, io) {
|
|
386
|
-
let { toolName, args, help, version } = parseArgs(argv);
|
|
534
|
+
let { toolName, args, valuelessArgs, help, version } = parseArgs(argv);
|
|
387
535
|
let sessionApiKey;
|
|
388
536
|
if (version) {
|
|
389
537
|
io.write(buildVersion());
|
|
@@ -456,6 +604,15 @@ async function runCliInner(argv, io) {
|
|
|
456
604
|
}
|
|
457
605
|
return;
|
|
458
606
|
}
|
|
607
|
+
if (isStreamCommand(toolName)) {
|
|
608
|
+
if (help) {
|
|
609
|
+
io.write(buildStreamHelp(toolName));
|
|
610
|
+
io.exit(0);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
await runStreamCliCommand(toolName, args, valuelessArgs, sessionApiKey, io);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
459
616
|
if (isHistoryDownloadCommand(toolName)) {
|
|
460
617
|
if (help) {
|
|
461
618
|
io.write(buildDownloadHistoryHelp(toolName));
|
|
@@ -691,6 +848,304 @@ async function resolveInteractiveToolName(io) {
|
|
|
691
848
|
io.write("No tool selected.");
|
|
692
849
|
return null;
|
|
693
850
|
}
|
|
851
|
+
async function runStreamCliCommand(commandName, args, valuelessArgs, sessionApiKey, io) {
|
|
852
|
+
const inputSchema = commandName === STREAM_LIVE_COMMAND ? LIVE_STREAM_SCHEMA : NEWSFEED_STREAM_SCHEMA;
|
|
853
|
+
const fullSchema = { ...inputSchema, ...STREAM_OUTPUT_SCHEMA };
|
|
854
|
+
const inputArgs = { ...args };
|
|
855
|
+
const guided = io.isInteractive === true && Boolean(io.prompt) && Object.keys(args).length === 0;
|
|
856
|
+
const unknownArg = Object.keys(inputArgs).find((key) => !(key in fullSchema));
|
|
857
|
+
if (unknownArg) {
|
|
858
|
+
const suggestion = closestStreamOption(unknownArg, Object.keys(fullSchema));
|
|
859
|
+
writeStreamError(io, `Error: Unknown option --${unknownArg}.${suggestion ? ` Did you mean --${suggestion}?` : ` Run: insight ${commandName} --help`}`);
|
|
860
|
+
io.exit(1);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const validationErrors = collectInvalidStreamArgs(inputArgs, fullSchema, valuelessArgs);
|
|
864
|
+
if (validationErrors.length > 0) {
|
|
865
|
+
const validationError = validationErrors[0];
|
|
866
|
+
writeStreamError(io, `Invalid ${toolPromptLabel(validationError.key)}: ${validationError.error}`);
|
|
867
|
+
io.exit(1);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const resolvedArgs = guided ? await resolveStreamArgs(commandName, inputArgs, io) : inputArgs;
|
|
871
|
+
if (!resolvedArgs) {
|
|
872
|
+
writeStreamError(io, `Error: Missing required streaming options.\n\nRun: insight ${commandName} --help`);
|
|
873
|
+
io.exit(1);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const resolvedValidationError = validateStreamArgs(resolvedArgs, fullSchema);
|
|
877
|
+
if (resolvedValidationError) {
|
|
878
|
+
writeStreamError(io, `Invalid ${toolPromptLabel(resolvedValidationError.key)}: ${resolvedValidationError.error}`);
|
|
879
|
+
io.exit(1);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const outputDependencyError = validateStreamOutputDependencies(resolvedArgs, fullSchema);
|
|
883
|
+
if (outputDependencyError) {
|
|
884
|
+
writeStreamError(io, `Error: ${outputDependencyError}`);
|
|
885
|
+
io.exit(1);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const apiKey = sessionApiKey?.trim() || io.getApiKey?.()?.trim() || resolveApiKey();
|
|
889
|
+
if (!apiKey) {
|
|
890
|
+
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");
|
|
891
|
+
io.exit(1);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
try {
|
|
895
|
+
const knownArgs = pickKnownArgs(resolvedArgs, Object.keys(fullSchema));
|
|
896
|
+
const coerced = coerceArgs(knownArgs, fullSchema);
|
|
897
|
+
const request = commandName === STREAM_LIVE_COMMAND
|
|
898
|
+
? buildLiveStreamRequest(pickStreamInput(coerced, LIVE_STREAM_SCHEMA), apiKey)
|
|
899
|
+
: buildNewsfeedStreamRequest(pickStreamInput(coerced, NEWSFEED_STREAM_SCHEMA), apiKey);
|
|
900
|
+
reportArgUsage(io, knownArgs, Object.keys(fullSchema), inputArgs);
|
|
901
|
+
if (request.ignoredArgs.length > 0) {
|
|
902
|
+
io.progress?.(`Ignoring inapplicable subscription args: ${request.ignoredArgs.join(", ")}`);
|
|
903
|
+
}
|
|
904
|
+
const outputFactory = io.createStreamOutput ?? createStreamOutput;
|
|
905
|
+
const streamOutput = await outputFactory({
|
|
906
|
+
outputFile: optionalString(coerced.output_file),
|
|
907
|
+
append: coerced.append === true,
|
|
908
|
+
tee: coerced.tee === true,
|
|
909
|
+
});
|
|
910
|
+
try {
|
|
911
|
+
const runner = io.runStream ?? runWebSocketStream;
|
|
912
|
+
const result = await runner({
|
|
913
|
+
request,
|
|
914
|
+
output: streamOutput,
|
|
915
|
+
diagnostics: io.progress,
|
|
916
|
+
signal: io.streamSignal,
|
|
917
|
+
maxReconnectAttempts: typeof coerced.max_reconnect_attempts === "number"
|
|
918
|
+
? coerced.max_reconnect_attempts
|
|
919
|
+
: undefined,
|
|
920
|
+
durationMs: typeof coerced.duration_seconds === "number"
|
|
921
|
+
? coerced.duration_seconds * 1_000
|
|
922
|
+
: undefined,
|
|
923
|
+
maxMessages: typeof coerced.max_messages === "number" ? coerced.max_messages : undefined,
|
|
924
|
+
});
|
|
925
|
+
io.exit(io.getStreamSignalExitCode?.() ?? result.exitCode);
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
await streamOutput.close().catch(() => { });
|
|
929
|
+
throw error;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
catch (error) {
|
|
933
|
+
writeStreamError(io, `Error: ${errorMessage(error)}`);
|
|
934
|
+
io.exit(1);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
async function resolveStreamArgs(commandName, args, io) {
|
|
938
|
+
if (io.isInteractive !== true || !io.prompt)
|
|
939
|
+
return args;
|
|
940
|
+
const resolved = { ...args };
|
|
941
|
+
if (commandName === STREAM_LIVE_COMMAND) {
|
|
942
|
+
const symbols = await promptForToolArg("symbols", LIVE_STREAM_SCHEMA.symbols, io);
|
|
943
|
+
if (!symbols)
|
|
944
|
+
return null;
|
|
945
|
+
resolved.symbols = symbols;
|
|
946
|
+
const type = await promptForToolArg("type", LIVE_STREAM_SCHEMA.type, io);
|
|
947
|
+
if (!type)
|
|
948
|
+
return null;
|
|
949
|
+
resolved.type = type;
|
|
950
|
+
if (type === "series" || type === "both") {
|
|
951
|
+
const seriesSettings = await promptForStreamChoice("Series Settings", "Use server defaults or configure bar settings.", ["default", "custom"], "default", io);
|
|
952
|
+
if (!seriesSettings)
|
|
953
|
+
return null;
|
|
954
|
+
if (seriesSettings === "custom") {
|
|
955
|
+
const barType = await promptForToolArg("bar_type", LIVE_STREAM_SCHEMA.bar_type, io);
|
|
956
|
+
if (!barType)
|
|
957
|
+
return null;
|
|
958
|
+
resolved.bar_type = barType;
|
|
959
|
+
for (const key of ["bar_interval", "dp"]) {
|
|
960
|
+
const answer = await promptForOptionalToolArg(key, LIVE_STREAM_SCHEMA[key], io);
|
|
961
|
+
if (answer === null)
|
|
962
|
+
return null;
|
|
963
|
+
if (answer !== undefined)
|
|
964
|
+
resolved[key] = answer;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
const advanced = await promptForStreamChoice("Advanced Settings", "Configure session, adjustment, currency, unit, or delivery options.", ["no", "yes"], "no", io);
|
|
969
|
+
if (!advanced)
|
|
970
|
+
return null;
|
|
971
|
+
if (advanced === "yes") {
|
|
972
|
+
for (const key of [
|
|
973
|
+
"currency",
|
|
974
|
+
"unit",
|
|
975
|
+
"extended",
|
|
976
|
+
"24h",
|
|
977
|
+
"split",
|
|
978
|
+
"dadj",
|
|
979
|
+
"badj",
|
|
980
|
+
"settlement",
|
|
981
|
+
"slow_consumer_policy",
|
|
982
|
+
]) {
|
|
983
|
+
if (shouldSkipLiveStreamPrompt(key, type))
|
|
984
|
+
continue;
|
|
985
|
+
const answer = await promptForOptionalToolArg(key, LIVE_STREAM_SCHEMA[key], io);
|
|
986
|
+
if (answer === null)
|
|
987
|
+
return null;
|
|
988
|
+
if (answer !== undefined)
|
|
989
|
+
resolved[key] = answer;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
const selection = await promptForStreamChoice("News Selection", "Stream all news or configure matching filters.", ["all", "filtered"], "all", io);
|
|
995
|
+
if (!selection)
|
|
996
|
+
return null;
|
|
997
|
+
if (selection === "filtered") {
|
|
998
|
+
for (const [key, zodType] of Object.entries(NEWSFEED_STREAM_SCHEMA)) {
|
|
999
|
+
const answer = await promptForOptionalToolArg(key, zodType, io);
|
|
1000
|
+
if (answer === null)
|
|
1001
|
+
return null;
|
|
1002
|
+
if (answer !== undefined)
|
|
1003
|
+
resolved[key] = answer;
|
|
1004
|
+
}
|
|
1005
|
+
if (!Object.keys(NEWSFEED_STREAM_SCHEMA).some((key) => hasArgValue(resolved[key]))) {
|
|
1006
|
+
writeStreamError(io, "No newsfeed filters were entered; streaming all news.");
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return (await resolveInteractiveStreamOutput(resolved, io)) ? resolved : null;
|
|
1011
|
+
}
|
|
1012
|
+
async function resolveInteractiveStreamOutput(resolved, io) {
|
|
1013
|
+
const destination = await promptForStreamChoice("Output Destination", "Choose where received NDJSON messages are written.", ["terminal", "file", "both"], "terminal", io);
|
|
1014
|
+
if (!destination)
|
|
1015
|
+
return false;
|
|
1016
|
+
if (destination === "terminal")
|
|
1017
|
+
return true;
|
|
1018
|
+
const outputFile = await promptForToolArg("output_file", STREAM_OUTPUT_SCHEMA.output_file, io);
|
|
1019
|
+
if (!outputFile)
|
|
1020
|
+
return false;
|
|
1021
|
+
resolved.output_file = outputFile;
|
|
1022
|
+
if (destination === "both")
|
|
1023
|
+
resolved.tee = "true";
|
|
1024
|
+
if (await pathExists(outputFile)) {
|
|
1025
|
+
const existingFileAction = await promptForStreamChoice("Existing Output File", "Append to the existing file or replace it. Press Ctrl+C to cancel.", ["append", "replace"], "append", io);
|
|
1026
|
+
if (!existingFileAction)
|
|
1027
|
+
return false;
|
|
1028
|
+
resolved.append = existingFileAction === "append" ? "true" : "false";
|
|
1029
|
+
}
|
|
1030
|
+
return true;
|
|
1031
|
+
}
|
|
1032
|
+
async function promptForStreamChoice(label, description, choices, defaultValue, io) {
|
|
1033
|
+
const question = `${label}: ${description}\n${label} (choices: ${choices.join("/")}, Default: ${defaultValue}): `;
|
|
1034
|
+
for (let attempt = 0; attempt < MAX_INTERACTIVE_PROMPT_ATTEMPTS; attempt++) {
|
|
1035
|
+
const raw = (await io.prompt?.(question))?.trim().toLowerCase() ?? "";
|
|
1036
|
+
const answer = normalizeStreamChoice(raw || defaultValue, choices);
|
|
1037
|
+
if (answer)
|
|
1038
|
+
return answer;
|
|
1039
|
+
writeStreamError(io, `Invalid ${label}: expected one of: ${choices.join(", ")}`);
|
|
1040
|
+
}
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
function normalizeStreamChoice(value, choices) {
|
|
1044
|
+
const aliases = {
|
|
1045
|
+
y: "yes",
|
|
1046
|
+
n: "no",
|
|
1047
|
+
stdout: "terminal",
|
|
1048
|
+
tee: "both",
|
|
1049
|
+
};
|
|
1050
|
+
const normalized = aliases[value] ?? value;
|
|
1051
|
+
return choices.includes(normalized) ? normalized : null;
|
|
1052
|
+
}
|
|
1053
|
+
async function pathExists(value) {
|
|
1054
|
+
try {
|
|
1055
|
+
await access(value);
|
|
1056
|
+
return true;
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
function validateStreamArgs(args, schema) {
|
|
1063
|
+
for (const [key, zodType] of Object.entries(schema)) {
|
|
1064
|
+
if (args[key] === undefined)
|
|
1065
|
+
continue;
|
|
1066
|
+
const error = validateToolArgAnswer(key, zodType, args[key]);
|
|
1067
|
+
if (error)
|
|
1068
|
+
return { key, error };
|
|
1069
|
+
}
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
function collectInvalidStreamArgs(args, schema, valuelessArgs) {
|
|
1073
|
+
const valueless = new Set(valuelessArgs);
|
|
1074
|
+
const errors = [];
|
|
1075
|
+
for (const [key, zodType] of Object.entries(schema)) {
|
|
1076
|
+
if (args[key] === undefined)
|
|
1077
|
+
continue;
|
|
1078
|
+
if (valueless.has(key) && getZodTypeName(zodType) !== "boolean") {
|
|
1079
|
+
errors.push({ key, error: "value is required" });
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
const error = validateToolArgAnswer(key, zodType, args[key]);
|
|
1083
|
+
if (error)
|
|
1084
|
+
errors.push({ key, error });
|
|
1085
|
+
}
|
|
1086
|
+
return errors;
|
|
1087
|
+
}
|
|
1088
|
+
function validateStreamOutputDependencies(args, schema) {
|
|
1089
|
+
if (hasArgValue(args.output_file))
|
|
1090
|
+
return null;
|
|
1091
|
+
const coerced = coerceArgs(args, schema);
|
|
1092
|
+
if (coerced.append === true)
|
|
1093
|
+
return "--append requires --output_file.";
|
|
1094
|
+
if (coerced.tee === true)
|
|
1095
|
+
return "--tee requires --output_file.";
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
function writeStreamError(io, message) {
|
|
1099
|
+
(io.progress ?? io.write)(message);
|
|
1100
|
+
}
|
|
1101
|
+
function closestStreamOption(input, options) {
|
|
1102
|
+
const boundedInput = input.slice(0, 64);
|
|
1103
|
+
let closest;
|
|
1104
|
+
for (const option of options) {
|
|
1105
|
+
const distance = editDistance(boundedInput, option);
|
|
1106
|
+
if (!closest || distance < closest.distance)
|
|
1107
|
+
closest = { option, distance };
|
|
1108
|
+
}
|
|
1109
|
+
if (!closest || closest.distance > Math.max(2, Math.floor(boundedInput.length / 3)))
|
|
1110
|
+
return null;
|
|
1111
|
+
return closest.option;
|
|
1112
|
+
}
|
|
1113
|
+
function editDistance(left, right) {
|
|
1114
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
1115
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex++) {
|
|
1116
|
+
const current = [leftIndex];
|
|
1117
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex++) {
|
|
1118
|
+
current[rightIndex] = Math.min(current[rightIndex - 1] + 1, previous[rightIndex] + 1, previous[rightIndex - 1] + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
|
|
1119
|
+
}
|
|
1120
|
+
previous = current;
|
|
1121
|
+
}
|
|
1122
|
+
return previous[right.length];
|
|
1123
|
+
}
|
|
1124
|
+
function pickStreamInput(values, schema) {
|
|
1125
|
+
const known = new Set(Object.keys(schema));
|
|
1126
|
+
return Object.fromEntries(Object.entries(values).filter(([key]) => known.has(key)));
|
|
1127
|
+
}
|
|
1128
|
+
function shouldSkipLiveStreamPrompt(key, type) {
|
|
1129
|
+
if (type !== "quote" && type !== "quotes")
|
|
1130
|
+
return false;
|
|
1131
|
+
return [
|
|
1132
|
+
"bar_type",
|
|
1133
|
+
"bar_interval",
|
|
1134
|
+
"dp",
|
|
1135
|
+
"currency",
|
|
1136
|
+
"unit",
|
|
1137
|
+
"split",
|
|
1138
|
+
"dadj",
|
|
1139
|
+
"badj",
|
|
1140
|
+
"settlement",
|
|
1141
|
+
].includes(key);
|
|
1142
|
+
}
|
|
1143
|
+
function optionalString(value) {
|
|
1144
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
1145
|
+
}
|
|
1146
|
+
function errorMessage(error) {
|
|
1147
|
+
return error instanceof Error ? error.message : String(error);
|
|
1148
|
+
}
|
|
694
1149
|
async function runUpdateCommand(io) {
|
|
695
1150
|
const runCommand = io.runCommand ?? defaultRunCommand;
|
|
696
1151
|
const status = await getCachedVersionStatus(io);
|
|
@@ -757,11 +1212,22 @@ function interactiveToolOptions() {
|
|
|
757
1212
|
name: RENDER_CHART_COMMAND,
|
|
758
1213
|
description: "Render Chart.js configs as PNG images",
|
|
759
1214
|
},
|
|
1215
|
+
{
|
|
1216
|
+
name: STREAM_LIVE_COMMAND,
|
|
1217
|
+
description: "Stream real-time market data as NDJSON",
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
name: STREAM_NEWSFEED_COMMAND,
|
|
1221
|
+
description: "Stream financial news as NDJSON",
|
|
1222
|
+
},
|
|
760
1223
|
];
|
|
761
1224
|
}
|
|
762
1225
|
function isHistoryDownloadCommand(toolName) {
|
|
763
1226
|
return toolName === DOWNLOAD_HISTORY_COMMAND || toolName === GET_SYMBOL_HISTORY_COMMAND;
|
|
764
1227
|
}
|
|
1228
|
+
function isStreamCommand(toolName) {
|
|
1229
|
+
return toolName === STREAM_LIVE_COMMAND || toolName === STREAM_NEWSFEED_COMMAND;
|
|
1230
|
+
}
|
|
765
1231
|
function buildInteractiveToolList(options) {
|
|
766
1232
|
return [
|
|
767
1233
|
"Choose a tool:",
|
|
@@ -1586,6 +2052,21 @@ function selectToolWithKeyboard(options) {
|
|
|
1586
2052
|
export function main() {
|
|
1587
2053
|
const rl = createInterface({ input, output });
|
|
1588
2054
|
const interactive = process.stdin.isTTY && process.stdout.isTTY && process.env.CI !== "true";
|
|
2055
|
+
const streamAbort = new AbortController();
|
|
2056
|
+
let streamRunning = false;
|
|
2057
|
+
let signalExitCode;
|
|
2058
|
+
const handleSignal = (exitCode) => {
|
|
2059
|
+
signalExitCode = exitCode;
|
|
2060
|
+
streamAbort.abort();
|
|
2061
|
+
if (!streamRunning) {
|
|
2062
|
+
process.exitCode = exitCode;
|
|
2063
|
+
rl.close();
|
|
2064
|
+
}
|
|
2065
|
+
};
|
|
2066
|
+
const handleSigint = () => handleSignal(130);
|
|
2067
|
+
const handleSigterm = () => handleSignal(143);
|
|
2068
|
+
process.once("SIGINT", handleSigint);
|
|
2069
|
+
process.once("SIGTERM", handleSigterm);
|
|
1589
2070
|
runCli(process.argv.slice(2), {
|
|
1590
2071
|
write: (s) => process.stdout.write(`${s}\n`),
|
|
1591
2072
|
progress: (s) => process.stderr.write(`${s}\n`),
|
|
@@ -1593,7 +2074,29 @@ export function main() {
|
|
|
1593
2074
|
prompt: (question) => rl.question(question),
|
|
1594
2075
|
selectTool: canUseKeyboardToolSelection() ? selectToolWithKeyboard : undefined,
|
|
1595
2076
|
isInteractive: interactive,
|
|
1596
|
-
|
|
1597
|
-
|
|
2077
|
+
streamSignal: streamAbort.signal,
|
|
2078
|
+
getStreamSignalExitCode: () => signalExitCode,
|
|
2079
|
+
runStream: async (options) => {
|
|
2080
|
+
streamRunning = true;
|
|
2081
|
+
try {
|
|
2082
|
+
return await runWebSocketStream(options);
|
|
2083
|
+
}
|
|
2084
|
+
finally {
|
|
2085
|
+
streamRunning = false;
|
|
2086
|
+
}
|
|
2087
|
+
},
|
|
2088
|
+
exit: (code) => {
|
|
2089
|
+
process.exitCode = code;
|
|
2090
|
+
},
|
|
2091
|
+
})
|
|
2092
|
+
.catch((error) => {
|
|
2093
|
+
process.stderr.write(`Error: ${errorMessage(error)}\n`);
|
|
2094
|
+
process.exitCode = 1;
|
|
2095
|
+
})
|
|
2096
|
+
.finally(() => {
|
|
2097
|
+
process.removeListener("SIGINT", handleSigint);
|
|
2098
|
+
process.removeListener("SIGTERM", handleSigterm);
|
|
2099
|
+
rl.close();
|
|
2100
|
+
});
|
|
1598
2101
|
}
|
|
1599
2102
|
//# sourceMappingURL=cli.js.map
|