@hoox-sh/pynets 0.1.0

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +661 -0
  3. package/NOTICE +46 -0
  4. package/README.md +327 -0
  5. package/dist/browser.js +44 -0
  6. package/dist/index.d.ts +348 -0
  7. package/dist/index.js +41741 -0
  8. package/package.json +77 -0
  9. package/src/ast/builder.ts +1002 -0
  10. package/src/ast/dump.ts +43 -0
  11. package/src/ast/helper.ts +77 -0
  12. package/src/ast/nodes.ts +570 -0
  13. package/src/ast/unparse.ts +360 -0
  14. package/src/cli/rich.ts +378 -0
  15. package/src/cli.ts +627 -0
  16. package/src/generated/PinescriptLexer.interp +267 -0
  17. package/src/generated/PinescriptLexer.tokens +131 -0
  18. package/src/generated/PinescriptLexer.ts +673 -0
  19. package/src/generated/PinescriptLexerBase.ts +395 -0
  20. package/src/generated/PinescriptParser.interp +267 -0
  21. package/src/generated/PinescriptParser.tokens +131 -0
  22. package/src/generated/PinescriptParser.ts +8863 -0
  23. package/src/generated/PinescriptParserBase.ts +21 -0
  24. package/src/generated/PinescriptParserVisitor.ts +840 -0
  25. package/src/index.ts +55 -0
  26. package/src/parser/LexerBase.ts +395 -0
  27. package/src/parser/ParserBase.ts +21 -0
  28. package/src/parser/error.ts +33 -0
  29. package/src/runtime/array.ts +486 -0
  30. package/src/runtime/color.ts +164 -0
  31. package/src/runtime/drawings.ts +510 -0
  32. package/src/runtime/input.ts +102 -0
  33. package/src/runtime/interpret.ts +3897 -0
  34. package/src/runtime/library.ts +282 -0
  35. package/src/runtime/log.ts +69 -0
  36. package/src/runtime/map.ts +85 -0
  37. package/src/runtime/math.ts +231 -0
  38. package/src/runtime/matrix.ts +1154 -0
  39. package/src/runtime/provider.ts +232 -0
  40. package/src/runtime/request.ts +372 -0
  41. package/src/runtime/series.ts +44 -0
  42. package/src/runtime/str.ts +433 -0
  43. package/src/runtime/strategy.ts +1105 -0
  44. package/src/runtime/ta.ts +2093 -0
  45. package/src/runtime/ticker.ts +81 -0
  46. package/src/runtime/time.ts +439 -0
  47. package/src/runtime/timeframe.ts +85 -0
  48. package/src/runtime/udt.ts +119 -0
package/src/cli.ts ADDED
@@ -0,0 +1,627 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Copyright (C) 2024-2026 jango_blockchained
4
+ * SPDX-License-Identifier: AGPL-3.0-or-later
5
+ *
6
+ * PyneTS CLI with a Rich-inspired TTY UI (PYNE volt).
7
+ * Pipe / CI stays plain: `check` prints `ok`, `run` prints JSON.
8
+ * Exit: 0 ok, 1 syntax/runtime, 2 usage.
9
+ */
10
+ import { readFileSync } from "node:fs";
11
+ import { basename, isAbsolute, resolve } from "node:path";
12
+ import { dump, parse, unparse, Runtime } from "./index.ts";
13
+ import type { BrokerSettings, OHLCVBar, RuntimeFill, RuntimeResult } from "./index.ts";
14
+ import {
15
+ Rich,
16
+ VERSION,
17
+ highlightPine,
18
+ sparkline,
19
+ stripFlags,
20
+ useRich,
21
+ } from "./cli/rich.ts";
22
+
23
+ export const MAX_BARS = 100_000;
24
+
25
+ export class UsageError extends Error {
26
+ readonly exitCode = 2;
27
+ constructor(message: string) {
28
+ super(message);
29
+ this.name = "UsageError";
30
+ }
31
+ }
32
+
33
+ function fail(ui: Rich, message: string, code = 1): never {
34
+ if (ui.enabled) ui.status("fail", message, true);
35
+ else console.error(message);
36
+ process.exit(code);
37
+ }
38
+
39
+ function failUsage(ui: Rich, message: string): never {
40
+ fail(ui, message, 2);
41
+ }
42
+
43
+ function ioCode(err: unknown): string {
44
+ if (err && typeof err === "object" && "code" in err && typeof (err as { code: unknown }).code === "string") {
45
+ return (err as { code: string }).code;
46
+ }
47
+ return "";
48
+ }
49
+
50
+ /** Resolve only the path the user typed (cwd-relative or absolute). No search path. */
51
+ export function resolveUserFile(file: string): string {
52
+ if (file == null || file === "" || file.includes("\0")) {
53
+ throw new UsageError("error: missing file");
54
+ }
55
+ if (isAbsolute(file)) return file;
56
+ return resolve(process.cwd(), file);
57
+ }
58
+
59
+ function readSource(ui: Rich, file: string): string {
60
+ let path: string;
61
+ try {
62
+ path = resolveUserFile(file);
63
+ } catch (err) {
64
+ failUsage(ui, err instanceof Error ? err.message : String(err));
65
+ }
66
+ try {
67
+ return readFileSync(path, "utf8");
68
+ } catch (err) {
69
+ const code = ioCode(err);
70
+ if (code === "ENOENT") failUsage(ui, `error: file not found: ${file}`);
71
+ if (code === "EISDIR") failUsage(ui, `error: not a file: ${file}`);
72
+ if (code === "EACCES") failUsage(ui, `error: permission denied: ${file}`);
73
+ failUsage(ui, `error: cannot read ${file}`);
74
+ }
75
+ }
76
+
77
+ function syntheticBars(n: number): OHLCVBar[] {
78
+ const bars: OHLCVBar[] = [];
79
+ const count = clampBars(n);
80
+ for (let i = 0; i < count; i++) {
81
+ const close = 100 + i;
82
+ bars.push({
83
+ open: close,
84
+ high: close + 0.5,
85
+ low: close - 0.5,
86
+ close,
87
+ volume: 1,
88
+ time: 1_700_000_000_000 + i * 60_000,
89
+ });
90
+ }
91
+ return bars;
92
+ }
93
+
94
+ /** Optional strategy/drawing events attached by the interpret host. */
95
+ type RuntimeEvent = {
96
+ bar?: number | null;
97
+ bar_index?: number | null;
98
+ type?: string | null;
99
+ kind?: string | null;
100
+ id?: string | number | null;
101
+ };
102
+
103
+ type DrawingLike = {
104
+ kind?: string | null;
105
+ type?: string | null;
106
+ bar?: number | null;
107
+ bar_index?: number | null;
108
+ text?: string | null;
109
+ message?: string | null;
110
+ };
111
+
112
+ type AlertLike = DrawingLike;
113
+
114
+ type RuntimeOut = RuntimeResult & {
115
+ events?: RuntimeEvent[];
116
+ drawings?: DrawingLike[];
117
+ fills?: RuntimeFill[];
118
+ alerts?: AlertLike[];
119
+ };
120
+
121
+ const DUMP_RICH_MIN_LINES = 80;
122
+
123
+ export function clampBars(n: number): number {
124
+ if (!Number.isFinite(n) || Number.isNaN(n) || n < 0) {
125
+ throw new UsageError("error: --bars requires a non-negative integer");
126
+ }
127
+ return Math.min(MAX_BARS, Math.trunc(n));
128
+ }
129
+
130
+ function requireUint(raw: string | undefined, flag: string): number {
131
+ if (raw == null || raw === "" || !/^\d+$/.test(raw)) {
132
+ throw new UsageError(`error: ${flag} requires a non-negative integer`);
133
+ }
134
+ const n = Number(raw);
135
+ if (!Number.isFinite(n) || Number.isNaN(n) || n < 0 || !Number.isSafeInteger(n)) {
136
+ throw new UsageError(`error: ${flag} requires a non-negative integer`);
137
+ }
138
+ return n;
139
+ }
140
+
141
+ function requireNumber(raw: string | undefined, flag: string): number {
142
+ if (raw == null || raw === "" || !/^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(raw)) {
143
+ throw new UsageError(`error: ${flag} requires a finite number`);
144
+ }
145
+ const n = Number(raw);
146
+ if (!Number.isFinite(n) || Number.isNaN(n)) {
147
+ throw new UsageError(`error: ${flag} requires a finite number`);
148
+ }
149
+ return n;
150
+ }
151
+
152
+ function takeValue(args: string[], i: number, flag: string): { raw: string | undefined; next: number } {
153
+ const a = args[i]!;
154
+ if (a === flag) return { raw: args[i + 1], next: i + 1 };
155
+ return { raw: a.slice(flag.length + 1), next: i };
156
+ }
157
+
158
+ export function parseArgs(argv: string[]): {
159
+ cmd: string;
160
+ file: string | undefined;
161
+ bars: number;
162
+ json: boolean;
163
+ indent: number;
164
+ full: boolean;
165
+ commission: number | undefined;
166
+ slippage: number | undefined;
167
+ pyramiding: number | undefined;
168
+ } {
169
+ const args = argv.slice(2);
170
+ const cmd = args[0] ?? "help";
171
+
172
+ let file: string | undefined;
173
+ let bars = 20;
174
+ let json = false;
175
+ let indent = 2;
176
+ let full = false;
177
+ let commission: number | undefined;
178
+ let slippage: number | undefined;
179
+ let pyramiding: number | undefined;
180
+ for (let i = 1; i < args.length; i++) {
181
+ const a = args[i]!;
182
+ if (a === "--help" || a === "-h") {
183
+ return { cmd: "help", file: undefined, bars, json, indent, full, commission, slippage, pyramiding };
184
+ }
185
+ if (a === "--json") {
186
+ json = true;
187
+ continue;
188
+ }
189
+ if (a === "--full") {
190
+ full = true;
191
+ continue;
192
+ }
193
+ if (a === "--bars" || a.startsWith("--bars=")) {
194
+ const got = takeValue(args, i, "--bars");
195
+ bars = clampBars(requireUint(got.raw, "--bars"));
196
+ i = got.next;
197
+ continue;
198
+ }
199
+ if (a === "--indent" || a.startsWith("--indent=")) {
200
+ const got = takeValue(args, i, "--indent");
201
+ if (got.raw == null || !/^\d+$/.test(got.raw)) {
202
+ throw new UsageError("error: --indent requires an integer");
203
+ }
204
+ indent = Number(got.raw);
205
+ if (!Number.isFinite(indent) || !Number.isSafeInteger(indent)) {
206
+ throw new UsageError("error: --indent requires an integer");
207
+ }
208
+ i = got.next;
209
+ continue;
210
+ }
211
+ if (a === "--commission" || a.startsWith("--commission=")) {
212
+ const got = takeValue(args, i, "--commission");
213
+ commission = requireNumber(got.raw, "--commission");
214
+ i = got.next;
215
+ continue;
216
+ }
217
+ if (a === "--slippage" || a.startsWith("--slippage=")) {
218
+ const got = takeValue(args, i, "--slippage");
219
+ slippage = requireNumber(got.raw, "--slippage");
220
+ i = got.next;
221
+ continue;
222
+ }
223
+ if (a === "--pyramiding" || a.startsWith("--pyramiding=")) {
224
+ const got = takeValue(args, i, "--pyramiding");
225
+ pyramiding = requireUint(got.raw, "--pyramiding");
226
+ i = got.next;
227
+ continue;
228
+ }
229
+ if (a.startsWith("-") && a !== "-") throw new UsageError(`error: unknown argument: ${a}`);
230
+ if (file != null) throw new UsageError(`error: unexpected argument: ${a}`);
231
+ file = a;
232
+ }
233
+ return { cmd, file, bars, json, indent, full, commission, slippage, pyramiding };
234
+ }
235
+
236
+ function fmtNum(v: number | null | undefined): string {
237
+ if (v == null || !Number.isFinite(v)) return "na";
238
+ return Number.isInteger(v) ? String(v) : v.toPrecision(6).replace(/\.?0+$/, "");
239
+ }
240
+
241
+ function check(ui: Rich, source: string, label: string): void {
242
+ const t0 = performance.now();
243
+ try {
244
+ parse(source);
245
+ } catch (err) {
246
+ fail(ui, err instanceof Error ? err.message : String(err), 1);
247
+ }
248
+ const ms = performance.now() - t0;
249
+ if (ui.enabled) {
250
+ ui.panel(`check ${basename(label)}`, [
251
+ `${ui.paint("ok", "✔")} parsed in ${ms.toFixed(1)}ms`,
252
+ ]);
253
+ } else {
254
+ console.log("ok");
255
+ }
256
+ }
257
+
258
+ function format(ui: Rich, source: string): void {
259
+ let text: string;
260
+ try {
261
+ text = unparse(parse(source));
262
+ } catch (err) {
263
+ fail(ui, err instanceof Error ? err.message : String(err), 1);
264
+ }
265
+ if (!text.endsWith("\n")) text += "\n";
266
+ if (ui.enabled) {
267
+ ui.write(highlightPine(text.replace(/\n$/, ""), ui));
268
+ ui.write("");
269
+ } else {
270
+ process.stdout.write(text);
271
+ }
272
+ }
273
+
274
+ function prettyAstDump(text: string, indent: number): string {
275
+ const step = Number.isFinite(indent) ? Math.max(0, Math.min(8, Math.trunc(indent))) : 2;
276
+ if (step === 0) return text;
277
+ let out = "";
278
+ let depth = 0;
279
+ let inStr = false;
280
+ for (let i = 0; i < text.length; i++) {
281
+ const ch = text[i]!;
282
+ if (inStr) {
283
+ out += ch;
284
+ if (ch === "\\" && i + 1 < text.length) out += text[++i];
285
+ else if (ch === '"') inStr = false;
286
+ continue;
287
+ }
288
+ if (ch === '"') {
289
+ inStr = true;
290
+ out += ch;
291
+ continue;
292
+ }
293
+ if (ch === "(") {
294
+ if (text[i + 1] === ")") {
295
+ out += "()";
296
+ i++;
297
+ continue;
298
+ }
299
+ depth++;
300
+ out += `(\n${" ".repeat(depth * step)}`;
301
+ continue;
302
+ }
303
+ if (ch === ")") {
304
+ depth = Math.max(0, depth - 1);
305
+ out += `\n${" ".repeat(depth * step)})`;
306
+ continue;
307
+ }
308
+ if (ch === ",") {
309
+ out += `,\n${" ".repeat(depth * step)}`;
310
+ if (text[i + 1] === " ") i++;
311
+ continue;
312
+ }
313
+ out += ch;
314
+ }
315
+ return out;
316
+ }
317
+
318
+ function dumpAst(ui: Rich, source: string, indent: number, full: boolean): void {
319
+ let tree;
320
+ try {
321
+ tree = parse(source);
322
+ } catch (err) {
323
+ fail(ui, err instanceof Error ? err.message : String(err), 1);
324
+ }
325
+ const raw = dump(tree, { annotate_fields: true });
326
+ if (!ui.enabled) {
327
+ process.stdout.write(`${raw}\n`);
328
+ return;
329
+ }
330
+ const text = indent > 0 ? prettyAstDump(raw, indent) : raw;
331
+ const lines = text.split("\n");
332
+ const shown = full ? lines : lines.slice(0, DUMP_RICH_MIN_LINES);
333
+ ui.panel("dump", shown);
334
+ if (!full && lines.length > DUMP_RICH_MIN_LINES) {
335
+ ui.status("info", `(truncated — ${lines.length} lines; use --full)`);
336
+ }
337
+ }
338
+
339
+ function writeJson(value: unknown): void {
340
+ process.stdout.write(`${JSON.stringify(value)}\n`);
341
+ }
342
+
343
+ function runJson(out: RuntimeOut): void {
344
+ const payload: {
345
+ plots: typeof out.plots;
346
+ series: typeof out.series;
347
+ count: number;
348
+ script_name: string | null;
349
+ events?: RuntimeEvent[];
350
+ drawings?: DrawingLike[];
351
+ fills?: RuntimeFill[];
352
+ alerts?: AlertLike[];
353
+ error?: string;
354
+ } = {
355
+ plots: out.plots,
356
+ series: out.series,
357
+ count: out.count,
358
+ script_name: out.script_name,
359
+ };
360
+ if (out.events != null) payload.events = out.events;
361
+ if (out.drawings != null) payload.drawings = out.drawings;
362
+ if (out.fills != null) payload.fills = out.fills;
363
+ if (out.alerts != null) payload.alerts = out.alerts;
364
+ if (out.error != null) payload.error = out.error;
365
+ writeJson(payload);
366
+ if (out.error != null) process.exit(1);
367
+ }
368
+
369
+ function plotTitles(out: RuntimeOut): string[] {
370
+ const seen = new Set<string>();
371
+ const titles: string[] = [];
372
+ for (const meta of out.plot_meta ?? []) {
373
+ const title = meta?.title;
374
+ if (!title || seen.has(title)) continue;
375
+ seen.add(title);
376
+ titles.push(title);
377
+ }
378
+ for (const key of Object.keys(out.series ?? {})) {
379
+ if (seen.has(key)) continue;
380
+ seen.add(key);
381
+ titles.push(key);
382
+ }
383
+ return titles;
384
+ }
385
+
386
+ function lastCell(cells: Array<number | null>): number | null {
387
+ if (cells.length === 0) return null;
388
+ const tail = cells[cells.length - 1];
389
+ return tail == null || !Number.isFinite(tail) ? null : tail;
390
+ }
391
+
392
+ function eventBar(ev: RuntimeEvent): string {
393
+ const bar = ev.bar ?? ev.bar_index;
394
+ return bar == null || !Number.isFinite(bar) ? "—" : String(bar);
395
+ }
396
+
397
+ function eventType(ev: RuntimeEvent): string {
398
+ const t = ev.type ?? ev.kind;
399
+ return t == null || t === "" ? "—" : String(t);
400
+ }
401
+
402
+ function eventId(ev: RuntimeEvent): string {
403
+ return ev.id == null || ev.id === "" ? "—" : String(ev.id);
404
+ }
405
+
406
+ function printEvents(ui: Rich, events: RuntimeEvent[] | undefined): void {
407
+ if (events == null || events.length === 0) return;
408
+ ui.write("");
409
+ ui.table(
410
+ ["bar", "type", "id"],
411
+ events.map((ev) => [
412
+ ui.paint("muted", eventBar(ev)),
413
+ ui.paint("fg", eventType(ev)),
414
+ ui.paint("accent", eventId(ev)),
415
+ ]),
416
+ );
417
+ }
418
+
419
+ function drawingKind(d: DrawingLike, fallback = "—"): string {
420
+ const k = d.kind ?? d.type;
421
+ return k == null || k === "" ? fallback : String(k);
422
+ }
423
+
424
+ function drawingBar(d: DrawingLike): string {
425
+ const bar = d.bar ?? d.bar_index;
426
+ return bar == null || !Number.isFinite(bar) ? "—" : String(bar);
427
+ }
428
+
429
+ function drawingText(d: DrawingLike): string {
430
+ const t = d.text ?? d.message;
431
+ return t == null || t === "" ? "—" : String(t);
432
+ }
433
+
434
+ function printDrawings(ui: Rich, drawings: DrawingLike[] | undefined): void {
435
+ if (!Array.isArray(drawings) || drawings.length === 0) return;
436
+ ui.write("");
437
+ ui.table(
438
+ ["kind", "bar", "text"],
439
+ drawings.map((d) => [
440
+ ui.paint("fg", drawingKind(d)),
441
+ ui.paint("muted", drawingBar(d)),
442
+ ui.paint("accent", drawingText(d)),
443
+ ]),
444
+ );
445
+ }
446
+
447
+ function printAlerts(ui: Rich, alerts: AlertLike[] | undefined): void {
448
+ if (!Array.isArray(alerts) || alerts.length === 0) return;
449
+ ui.write("");
450
+ ui.table(
451
+ ["kind", "bar", "text"],
452
+ alerts.map((a) => [
453
+ ui.paint("fg", drawingKind(a, "alert")),
454
+ ui.paint("muted", drawingBar(a)),
455
+ ui.paint("accent", drawingText(a)),
456
+ ]),
457
+ );
458
+ }
459
+
460
+ function runPretty(ui: Rich, label: string, bars: number, out: RuntimeOut, ms: number): void {
461
+ if (out.error) fail(ui, out.error, 1);
462
+ const titles = plotTitles(out);
463
+ const lines = [
464
+ `${ui.paint("muted", "script")} ${ui.paint("fg", out.script_name ?? basename(label))}`,
465
+ `${ui.paint("muted", "bars")} ${ui.paint("fg", String(bars))}`,
466
+ `${ui.paint("muted", "mode")} ${ui.paint("fg", out.mode)}`,
467
+ `${ui.paint("muted", "time")} ${ui.paint("ok", `${ms.toFixed(1)}ms`)}`,
468
+ ];
469
+ ui.panel(`run ${basename(label)}`, lines);
470
+ if (titles.length === 0) {
471
+ ui.status("warn", "no plot series");
472
+ } else {
473
+ ui.write("");
474
+ const rows = titles.map((title) => {
475
+ const cells = out.series[title] ?? [];
476
+ return [
477
+ ui.paint("fg", title),
478
+ ui.paint("muted", String(cells.length)),
479
+ ui.paint("accent", fmtNum(lastCell(cells))),
480
+ ui.paint("ok", sparkline(cells)),
481
+ ];
482
+ });
483
+ ui.table(["plot", "n", "last", "spark"], rows);
484
+ }
485
+ printEvents(ui, out.events);
486
+ printDrawings(ui, out.drawings);
487
+ printAlerts(ui, out.alerts);
488
+ }
489
+
490
+ function brokerFromFlags(parsed: {
491
+ commission: number | undefined;
492
+ slippage: number | undefined;
493
+ pyramiding: number | undefined;
494
+ }): BrokerSettings {
495
+ const broker: BrokerSettings = {};
496
+ if (parsed.commission != null) broker.commission = parsed.commission;
497
+ if (parsed.slippage != null) broker.slippage = parsed.slippage;
498
+ if (parsed.pyramiding != null) broker.pyramiding = parsed.pyramiding;
499
+ return broker;
500
+ }
501
+
502
+ function run(
503
+ ui: Rich,
504
+ source: string,
505
+ label: string,
506
+ bars: number,
507
+ asJson: boolean,
508
+ broker: BrokerSettings,
509
+ ): void {
510
+ const t0 = performance.now();
511
+ let out: RuntimeOut;
512
+ try {
513
+ out = new Runtime("AAPL", { broker }).run(source, syntheticBars(bars));
514
+ } catch (err) {
515
+ fail(ui, err instanceof Error ? err.message : String(err), 1);
516
+ }
517
+ const ms = performance.now() - t0;
518
+ if (asJson || !ui.enabled) {
519
+ runJson(out);
520
+ return;
521
+ }
522
+ runPretty(ui, label, bars, out, ms);
523
+ }
524
+
525
+ function infoPayload(ui: Rich): {
526
+ name: string;
527
+ package: string;
528
+ version: string;
529
+ runtime: string;
530
+ bun: string | null;
531
+ mode: string;
532
+ rich: boolean;
533
+ docs: string;
534
+ } {
535
+ return {
536
+ name: "pynets",
537
+ package: "@hoox-sh/pynets",
538
+ version: VERSION,
539
+ runtime: "bun",
540
+ bun: typeof Bun !== "undefined" ? Bun.version : null,
541
+ mode: "interpret",
542
+ rich: ui.enabled,
543
+ docs: "https://hoox.sh/pyne",
544
+ };
545
+ }
546
+
547
+ function info(ui: Rich, asJson: boolean): void {
548
+ const payload = infoPayload(ui);
549
+ if (asJson || !ui.enabled) {
550
+ writeJson(payload);
551
+ return;
552
+ }
553
+ ui.banner();
554
+ ui.write("");
555
+ ui.table(
556
+ ["key", "value"],
557
+ [
558
+ ["package", "@hoox-sh/pynets"],
559
+ ["version", VERSION],
560
+ ["runtime", `bun ${payload.bun ?? "?"}`],
561
+ ["engine", "interpret"],
562
+ ["rich", "yes (TTY)"],
563
+ ["docs", payload.docs],
564
+ ],
565
+ );
566
+ }
567
+
568
+ function main(): void {
569
+ const raw = process.argv;
570
+ const ui = new Rich(useRich(raw));
571
+ const argv = stripFlags(raw);
572
+ let parsed: ReturnType<typeof parseArgs>;
573
+ try {
574
+ parsed = parseArgs(argv);
575
+ } catch (err) {
576
+ const msg = err instanceof Error ? err.message : String(err);
577
+ failUsage(ui, msg);
578
+ }
579
+
580
+ const { cmd, file, bars, json, indent, full } = parsed;
581
+ const broker = brokerFromFlags(parsed);
582
+ if (cmd === "help" || cmd === "-h" || cmd === "--help") {
583
+ ui.help();
584
+ return;
585
+ }
586
+ if (cmd === "info") {
587
+ info(ui, json);
588
+ return;
589
+ }
590
+
591
+ const known = cmd === "check" || cmd === "format" || cmd === "run" || cmd === "dump";
592
+ if (!known) {
593
+ failUsage(ui, `error: unknown command: ${cmd}`);
594
+ }
595
+
596
+ if (file == null) {
597
+ failUsage(ui, `error: missing file\nusage: pynets ${cmd} <file> [options]`);
598
+ }
599
+
600
+ if (cmd === "check") {
601
+ check(ui, readSource(ui, file), file);
602
+ return;
603
+ }
604
+ if (cmd === "format") {
605
+ format(ui, readSource(ui, file));
606
+ return;
607
+ }
608
+ if (cmd === "dump") {
609
+ dumpAst(ui, readSource(ui, file), indent, full);
610
+ return;
611
+ }
612
+ if (cmd === "run") {
613
+ run(ui, readSource(ui, file), file, bars, json, broker);
614
+ return;
615
+ }
616
+ failUsage(ui, `error: unknown command: ${cmd}`);
617
+ }
618
+
619
+ if (import.meta.main) {
620
+ try {
621
+ main();
622
+ } catch (err) {
623
+ const msg = err instanceof Error ? err.message : String(err);
624
+ console.error(msg);
625
+ process.exit(err instanceof UsageError ? 2 : 1);
626
+ }
627
+ }