gloomberb 0.16.2 → 0.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/brokers/account-cache.ts +45 -0
- package/src/cli/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/market.ts +170 -54
- package/src/cli/commands/overview.ts +62 -19
- package/src/cli/commands/ticker.ts +138 -43
- package/src/cli/desktop-pane-shot.ts +3 -0
- package/src/cli/help.ts +26 -6
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index.ts +12 -7
- package/src/cli/options.ts +5 -1
- package/src/cli/pane-functions/capabilities.ts +5 -0
- package/src/cli/pane-functions/dom.ts +69 -16
- package/src/cli/pane-functions/function-help.ts +4 -2
- package/src/cli/pane-functions/headless.ts +163 -31
- package/src/cli/pane-functions/index.ts +9 -2
- package/src/cli/pane-functions/report.ts +8 -1
- package/src/cli/pane-functions/resolver.ts +28 -2
- package/src/cli/pane-functions/screenshot.ts +4 -0
- package/src/cli/registry.ts +6 -3
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +13 -3
- package/src/components/data-table/export.ts +6 -51
- package/src/components/data-table/view.tsx +3 -1
- package/src/components/layout/shell/active-drag.ts +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +12 -7
- package/src/components/layout/shell/drag/live.ts +122 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +37 -51
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/layers.tsx +419 -179
- package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- package/src/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- package/src/market-data/market/freshness.ts +45 -0
- package/src/market-data/quotes/freshness.ts +5 -0
- package/src/plugins/builtin/analytics/pane-model.ts +10 -6
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/catalysts/detail.tsx +1 -1
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/holders/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +26 -5
- package/src/plugins/builtin/holders/pane.tsx +22 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- package/src/plugins/builtin/kelly-sizer/pane.tsx +12 -6
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- package/src/plugins/builtin/members/pane.tsx +10 -0
- package/src/plugins/builtin/perps/pane.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -0
- package/src/plugins/builtin/portfolio-list/headless.ts +2 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
- package/src/plugins/builtin/shared/members-menu.tsx +260 -0
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +20 -4
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +5 -5
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- package/src/renderers/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -0
- package/src/renderers/dom/host/box.tsx +113 -17
- package/src/renderers/dom/host/drag-shield.ts +24 -0
- package/src/renderers/dom/host/live-frame.ts +54 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/styles.css +13 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +3 -0
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/sources/gloomberb-cloud/normalizers.ts +16 -20
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/financial-routes.ts +2 -2
- package/src/sources/provider-router/financials.ts +70 -7
- package/src/sources/provider-router/primary.ts +2 -2
- package/src/state/pane-scroll-registry.tsx +67 -35
- package/src/test-support/margin-account.ts +108 -0
- package/src/time-series/resolution.ts +15 -0
- package/src/types/financials.ts +32 -3
- package/src/ui/host.tsx +12 -0
- package/src/ui/index.tsx +8 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/price-history-integrity.ts +19 -6
|
@@ -20,6 +20,16 @@ import type {
|
|
|
20
20
|
} from "../../types/plugin";
|
|
21
21
|
import { cliStyles, renderSection, renderStats, renderTable } from "../../utils/cli-output";
|
|
22
22
|
import { humanizeCliKey } from "../result";
|
|
23
|
+
import {
|
|
24
|
+
exportEntriesTable,
|
|
25
|
+
exportNumber,
|
|
26
|
+
exportRowsTable,
|
|
27
|
+
float32Shortest,
|
|
28
|
+
reportFooterLines,
|
|
29
|
+
type CliReportTable,
|
|
30
|
+
type CliReportTables,
|
|
31
|
+
} from "../report-tables";
|
|
32
|
+
import { labelWithUnit } from "../../utils/display-number";
|
|
23
33
|
import { observationDate } from "../../time-series/price-comparison";
|
|
24
34
|
import type { ResolvedSeries } from "../../time-series/types";
|
|
25
35
|
import type { PaneFunctionReport } from "./report";
|
|
@@ -316,20 +326,6 @@ function displayNumber(value: number): string {
|
|
|
316
326
|
return String(float32Shortest(value) ?? Number(value.toPrecision(12)));
|
|
317
327
|
}
|
|
318
328
|
|
|
319
|
-
/**
|
|
320
|
-
* Some feeds store prices as float32, so 338.93 arrives as 338.929992676. A value
|
|
321
|
-
* that is exactly a float32 prints as the shortest decimal that rounds back to it,
|
|
322
|
-
* which leaves genuine doubles (never exactly float32 unless short) untouched.
|
|
323
|
-
*/
|
|
324
|
-
function float32Shortest(value: number): number | null {
|
|
325
|
-
if (Math.fround(value) !== value) return null;
|
|
326
|
-
for (let digits = 1; digits <= 9; digits += 1) {
|
|
327
|
-
const candidate = Number(value.toPrecision(digits));
|
|
328
|
-
if (Math.fround(candidate) === value) return candidate;
|
|
329
|
-
}
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
329
|
function displayTime(time: number): string {
|
|
334
330
|
if (!Number.isFinite(time)) return "-";
|
|
335
331
|
return formatUtcTime(new Date(time));
|
|
@@ -337,14 +333,36 @@ function displayTime(time: number): string {
|
|
|
337
333
|
|
|
338
334
|
const CALENDAR_RESOLUTIONS = new Set(["1d", "1wk", "1mo"]);
|
|
339
335
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
336
|
+
type SeriesPointDate = HeadlessSeries["points"][number]["date"];
|
|
337
|
+
|
|
338
|
+
function seriesPointTime(date: SeriesPointDate): number {
|
|
339
|
+
return date instanceof Date ? date.getTime() : typeof date === "number" ? date : Date.parse(date);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function isDateOnly(date: SeriesPointDate): boolean {
|
|
343
|
+
return typeof date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(date);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Session bars are dated by the trading date the chart and comparison notice use, not the bar's open instant. */
|
|
347
|
+
function calendarSeries(series: HeadlessSeries): (HeadlessSeries & Partial<Pick<ResolvedSeries, "historyResolution" | "timeBasis">>) | null {
|
|
344
348
|
const resolved = series as HeadlessSeries & Partial<Pick<ResolvedSeries, "historyResolution" | "timeBasis">>;
|
|
345
|
-
return resolved.historyResolution && CALENDAR_RESOLUTIONS.has(resolved.historyResolution)
|
|
346
|
-
|
|
347
|
-
|
|
349
|
+
return resolved.historyResolution && CALENDAR_RESOLUTIONS.has(resolved.historyResolution) ? resolved : null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function seriesPointDate(series: HeadlessSeries, date: SeriesPointDate): string {
|
|
353
|
+
const time = seriesPointTime(date);
|
|
354
|
+
if (!Number.isFinite(time)) return typeof date === "string" ? date : "-";
|
|
355
|
+
const calendar = calendarSeries(series);
|
|
356
|
+
return calendar ? observationDate(time, calendar) : formatUtcTime({ time, dateOnly: isDateOnly(date) });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** The same date for a spreadsheet: the trading date, the calendar date, or the ISO instant. */
|
|
360
|
+
function seriesPointExportDate(series: HeadlessSeries, date: SeriesPointDate): string {
|
|
361
|
+
const time = seriesPointTime(date);
|
|
362
|
+
if (!Number.isFinite(time)) return typeof date === "string" ? date : "";
|
|
363
|
+
const calendar = calendarSeries(series);
|
|
364
|
+
if (calendar) return observationDate(time, calendar);
|
|
365
|
+
return isDateOnly(date) ? date as string : new Date(time).toISOString().replace(".000Z", "Z");
|
|
348
366
|
}
|
|
349
367
|
|
|
350
368
|
function seriesValue(series: HeadlessSeries, value: number | null): string {
|
|
@@ -377,6 +395,10 @@ function renderEntries(entries: HeadlessPaneEntry[]): string {
|
|
|
377
395
|
return renderStats(entries.map((entry) => [entry.label, entry.formatted ?? displayValue(entry.value)]));
|
|
378
396
|
}
|
|
379
397
|
|
|
398
|
+
function statisticsEntries(stats: NonNullable<HeadlessSeriesResult["stats"]>): HeadlessPaneEntry[] {
|
|
399
|
+
return Array.isArray(stats) ? stats : Object.entries(stats).map(([label, value]) => ({ label, value }));
|
|
400
|
+
}
|
|
401
|
+
|
|
380
402
|
function renderBundle(
|
|
381
403
|
definition: HeadlessPaneDefinition,
|
|
382
404
|
result: HeadlessBundleResult,
|
|
@@ -412,14 +434,118 @@ function renderSeries(result: HeadlessSeriesResult): string[] {
|
|
|
412
434
|
];
|
|
413
435
|
const lines = [renderRows(rows, columns, undefined)];
|
|
414
436
|
if (result.stats) {
|
|
415
|
-
|
|
416
|
-
? result.stats
|
|
417
|
-
: Object.entries(result.stats).map(([label, value]) => ({ label, value }));
|
|
418
|
-
lines.push("", renderSection("Statistics"), renderEntries(entries));
|
|
437
|
+
lines.push("", renderSection("Statistics"), renderEntries(statisticsEntries(result.stats)));
|
|
419
438
|
}
|
|
420
439
|
return lines;
|
|
421
440
|
}
|
|
422
441
|
|
|
442
|
+
/** The headers the text table prints. */
|
|
443
|
+
function displayedColumns(columns: HeadlessPaneColumn[]): HeadlessPaneColumn[] {
|
|
444
|
+
return columns.map((column) => ({ ...column, header: humanizeCliKey(column.header) }));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const SERIES_FIELDS = ["value", "open", "high", "low", "close", "volume"] as const;
|
|
448
|
+
|
|
449
|
+
/** Every point of a series: its date (the trading date for session bars) and its values, in the series' unit. */
|
|
450
|
+
function seriesTable(series: HeadlessSeries): CliReportTable {
|
|
451
|
+
const fields = SERIES_FIELDS.filter((field) => series.points.some((point) => point[field] != null));
|
|
452
|
+
const unit = series.unit?.trim() ?? "";
|
|
453
|
+
const dates = series.points.map((point) => seriesPointExportDate(series, point.date));
|
|
454
|
+
const header = (field: (typeof SERIES_FIELDS)[number]) => {
|
|
455
|
+
const name = `${field[0]!.toUpperCase()}${field.slice(1)}`;
|
|
456
|
+
return field === "volume" ? name : labelWithUnit(name, unit);
|
|
457
|
+
};
|
|
458
|
+
return {
|
|
459
|
+
title: series.label,
|
|
460
|
+
columns: [dates.every((date) => date.length <= 10) ? "Date" : "Time", ...fields.map(header)],
|
|
461
|
+
rows: series.points.map((point, index) => [
|
|
462
|
+
dates[index]!,
|
|
463
|
+
...fields.map((field) => {
|
|
464
|
+
const value = point[field];
|
|
465
|
+
return typeof value === "number" && Number.isFinite(value) ? exportNumber(value) : "";
|
|
466
|
+
}),
|
|
467
|
+
]),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** What a report leaves out, for the closing `# incomplete` line. */
|
|
472
|
+
function incompleteDetail(
|
|
473
|
+
result: HeadlessPaneResult,
|
|
474
|
+
status: { complete: boolean; unavailableSymbols: string[] },
|
|
475
|
+
): boolean | string {
|
|
476
|
+
if (status.complete) return false;
|
|
477
|
+
const requested = result.metadata?.requested;
|
|
478
|
+
const available = result.metadata?.available;
|
|
479
|
+
if (typeof requested === "number" && typeof available === "number" && available < requested) {
|
|
480
|
+
return `${available} of ${requested} available`;
|
|
481
|
+
}
|
|
482
|
+
return status.unavailableSymbols.length > 0 ? `no data for ${status.unavailableSymbols.join(", ")}` : true;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function textNotices(result: HeadlessPaneResult): string[] {
|
|
486
|
+
const notices = result.metadata?.notices;
|
|
487
|
+
if (!Array.isArray(notices)) return [];
|
|
488
|
+
return [...new Set(notices.filter((notice): notice is string => typeof notice === "string" && notice.trim().length > 0))];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* The tables of a headless report for `--csv` and `--ndjson`, with the text
|
|
493
|
+
* view's columns: a rows or snapshot report is one table, a bundle one per
|
|
494
|
+
* section (key/value sections as `Metric,Value`), a series report one per
|
|
495
|
+
* series with every point, then its statistics. The footer carries the text
|
|
496
|
+
* view's closing line, notices, notes and errors.
|
|
497
|
+
*/
|
|
498
|
+
export function headlessReportTables(
|
|
499
|
+
definition: HeadlessPaneDefinition,
|
|
500
|
+
result: HeadlessPaneResult,
|
|
501
|
+
args: HeadlessPaneLoadArgs,
|
|
502
|
+
fallbackTitle: string,
|
|
503
|
+
freshness: ReportFreshness,
|
|
504
|
+
status: { complete: boolean; unavailableSymbols: string[] },
|
|
505
|
+
): CliReportTables {
|
|
506
|
+
const title = reportTitle(definition, args, result, fallbackTitle);
|
|
507
|
+
let tables: CliReportTable[];
|
|
508
|
+
switch (definition.shape) {
|
|
509
|
+
case "rows": {
|
|
510
|
+
const rowsResult = result as HeadlessPaneResult & { rows: HeadlessPaneRow[]; columns?: HeadlessPaneColumn[] };
|
|
511
|
+
const columns = displayedColumns(columnsFor(rowsResult.rows, rowsResult.columns, definition.columns));
|
|
512
|
+
tables = [exportRowsTable(title, columns, rowsResult.rows)];
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case "bundle":
|
|
516
|
+
tables = (result as HeadlessBundleResult).sections.map((section) => (
|
|
517
|
+
"rows" in section && section.rows
|
|
518
|
+
? exportRowsTable(section.title, displayedColumns(columnsFor(section.rows, section.columns, definition.columns)), section.rows)
|
|
519
|
+
: exportEntriesTable(section.title, section.entries)
|
|
520
|
+
));
|
|
521
|
+
break;
|
|
522
|
+
case "series": {
|
|
523
|
+
const seriesResult = result as HeadlessSeriesResult;
|
|
524
|
+
tables = seriesResult.series.map(seriesTable);
|
|
525
|
+
if (seriesResult.stats) tables.push(exportEntriesTable("Statistics", statisticsEntries(seriesResult.stats)));
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
case "snapshot": {
|
|
529
|
+
const snapshot = result as HeadlessSnapshotResult;
|
|
530
|
+
tables = [exportRowsTable(title, displayedColumns(columnsFor(snapshot.items, undefined, definition.columns)), snapshot.items)];
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
default: {
|
|
534
|
+
const _exhaustive: never = definition.shape;
|
|
535
|
+
return _exhaustive;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
tables,
|
|
540
|
+
footer: reportFooterLines({
|
|
541
|
+
freshness,
|
|
542
|
+
incomplete: incompleteDetail(result, status),
|
|
543
|
+
errors: result.errors,
|
|
544
|
+
notes: [...textNotices(result), ...(result.notes ?? [])],
|
|
545
|
+
}),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
423
549
|
function reportTitle(
|
|
424
550
|
definition: HeadlessPaneDefinition,
|
|
425
551
|
args: HeadlessPaneLoadArgs,
|
|
@@ -450,11 +576,8 @@ export function renderHeadlessPaneText(
|
|
|
450
576
|
freshness: ReportFreshness = deriveHeadlessFreshness(definition, result),
|
|
451
577
|
): string {
|
|
452
578
|
const lines = [cliStyles.bold(reportTitle(definition, args, result, fallbackTitle)), ""];
|
|
453
|
-
const notices = result
|
|
454
|
-
if (
|
|
455
|
-
const textNotices = [...new Set(notices.filter((notice): notice is string => typeof notice === "string" && notice.trim().length > 0))];
|
|
456
|
-
if (textNotices.length) lines.push(...textNotices, "");
|
|
457
|
-
}
|
|
579
|
+
const notices = textNotices(result);
|
|
580
|
+
if (notices.length) lines.push(...notices, "");
|
|
458
581
|
switch (definition.shape) {
|
|
459
582
|
case "rows": {
|
|
460
583
|
const rowsResult = result as HeadlessPaneResult & { rows: HeadlessPaneRow[]; columns?: HeadlessPaneColumn[] };
|
|
@@ -513,6 +636,7 @@ export async function buildHeadlessFunctionReport(
|
|
|
513
636
|
const unavailableSymbols = loaded.result.unavailableSymbols ?? (rowCount === 0 ? symbols : []);
|
|
514
637
|
const serialized = serializeHeadlessPaneResult(loaded.definition, loaded.result);
|
|
515
638
|
const freshness = deriveHeadlessFreshness(loaded.definition, loaded.result);
|
|
639
|
+
const complete = loaded.result.complete !== false && unavailableSymbols.length === 0 && !loaded.result.errors?.length;
|
|
516
640
|
return {
|
|
517
641
|
data: {
|
|
518
642
|
kind: loaded.definition.shape,
|
|
@@ -522,7 +646,7 @@ export async function buildHeadlessFunctionReport(
|
|
|
522
646
|
options: resolved.options,
|
|
523
647
|
rowCount,
|
|
524
648
|
empty: rowCount === 0,
|
|
525
|
-
complete
|
|
649
|
+
complete,
|
|
526
650
|
unavailableSymbols,
|
|
527
651
|
...serialized,
|
|
528
652
|
freshness,
|
|
@@ -534,5 +658,13 @@ export async function buildHeadlessFunctionReport(
|
|
|
534
658
|
resolved.label,
|
|
535
659
|
freshness,
|
|
536
660
|
),
|
|
661
|
+
tables: headlessReportTables(
|
|
662
|
+
loaded.definition,
|
|
663
|
+
loaded.result,
|
|
664
|
+
loaded.args,
|
|
665
|
+
resolved.label,
|
|
666
|
+
freshness,
|
|
667
|
+
{ complete, unavailableSymbols },
|
|
668
|
+
),
|
|
537
669
|
};
|
|
538
670
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { accessGateStatus, incompleteReportGateMessage } from "./access-gate";
|
|
24
24
|
import { withPersistedCloudSession } from "./cloud-session";
|
|
25
25
|
import { loadForListing } from "../listing-arg";
|
|
26
|
+
import { selectReportTables } from "../report-tables";
|
|
26
27
|
|
|
27
28
|
async function withPaneRuntime<T>(
|
|
28
29
|
ctx: CliCommandContext,
|
|
@@ -33,7 +34,7 @@ async function withPaneRuntime<T>(
|
|
|
33
34
|
registry: PaneFunctionCatalog;
|
|
34
35
|
resolved: ResolvedPaneFunction;
|
|
35
36
|
}) => Promise<T>,
|
|
36
|
-
settings: { strictHeadlessOptions?: boolean } = {},
|
|
37
|
+
settings: { strictHeadlessOptions?: boolean; tableSection?: boolean } = {},
|
|
37
38
|
): Promise<T> {
|
|
38
39
|
return withMarketData(ctx, async (market) => {
|
|
39
40
|
const context: MarketContext = ctx.cliOptions.refresh ? { ...market, refresh: true } : market;
|
|
@@ -59,6 +60,11 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
59
60
|
+ `Use "gloomberb catalog ${resolved.token}" to inspect readiness.`,
|
|
60
61
|
);
|
|
61
62
|
}
|
|
63
|
+
const tabular = ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson";
|
|
64
|
+
if (resolved.tableSection !== undefined && !tabular) {
|
|
65
|
+
throw new Error("--section picks one table of --csv or --ndjson output.");
|
|
66
|
+
}
|
|
67
|
+
if (resolved.tableSection === true) throw new Error("--section needs a section title or number.");
|
|
62
68
|
// A report that fails or comes back empty for an exchange the symbol is not listed on says so.
|
|
63
69
|
const report = await withPersistedCloudSession(
|
|
64
70
|
context,
|
|
@@ -82,8 +88,9 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
82
88
|
}
|
|
83
89
|
ctx.printResult({ data: report.data }, {
|
|
84
90
|
text: () => report.text,
|
|
91
|
+
...(tabular ? { tables: selectReportTables(report.tables, resolved.tableSection) } : {}),
|
|
85
92
|
});
|
|
86
|
-
}, { strictHeadlessOptions: true });
|
|
93
|
+
}, { strictHeadlessOptions: true, tableSection: true });
|
|
87
94
|
});
|
|
88
95
|
}
|
|
89
96
|
|
|
@@ -2,8 +2,9 @@ import type { MarketContext } from "../types";
|
|
|
2
2
|
import type { NormalizedPaneFunctionOptions } from "./capabilities";
|
|
3
3
|
import type { ResolvedPaneFunction } from "./resolver";
|
|
4
4
|
import { buildHeadlessFunctionReport } from "./headless";
|
|
5
|
-
import { appendDomReportFooter, buildDomFunctionReport } from "./dom";
|
|
5
|
+
import { appendDomReportFooter, buildDomFunctionReport, domReportNote } from "./dom";
|
|
6
6
|
import type { ReportFreshness } from "./freshness";
|
|
7
|
+
import type { CliReportTables } from "../report-tables";
|
|
7
8
|
|
|
8
9
|
export type PaneFunctionReportSource = "headless" | "dom";
|
|
9
10
|
|
|
@@ -29,6 +30,8 @@ interface PaneFunctionReportData {
|
|
|
29
30
|
export interface PaneFunctionReport {
|
|
30
31
|
data: PaneFunctionReportData;
|
|
31
32
|
text: string;
|
|
33
|
+
/** What `--csv` and `--ndjson` write: the text view's tables as flat rows, and the closing `#` lines. */
|
|
34
|
+
tables: CliReportTables;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export function resolvePaneFunctionReportSource(
|
|
@@ -61,6 +64,10 @@ export async function buildFunctionReport(
|
|
|
61
64
|
report.data.truncated === true,
|
|
62
65
|
report.data.freshness,
|
|
63
66
|
);
|
|
67
|
+
report.tables = {
|
|
68
|
+
...report.tables,
|
|
69
|
+
footer: [...report.tables.footer, `note: ${domReportNote(elapsedMs, report.data.truncated === true)}`],
|
|
70
|
+
};
|
|
64
71
|
}
|
|
65
72
|
return report;
|
|
66
73
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type ParsedPaneFunctionArgs,
|
|
26
26
|
} from "./options";
|
|
27
27
|
import {
|
|
28
|
+
capabilityHasOption,
|
|
28
29
|
capabilityPaneSettings,
|
|
29
30
|
getHeadlessPaneDefinition,
|
|
30
31
|
getPaneFunctionCapability,
|
|
@@ -46,6 +47,8 @@ export interface ResolvedPaneFunction {
|
|
|
46
47
|
optionSettings: Record<string, unknown>;
|
|
47
48
|
capability: PaneFunctionCapability;
|
|
48
49
|
options: NormalizedPaneFunctionOptions;
|
|
50
|
+
/** `--section` of a report: the table `--csv` and `--ndjson` write, when the function has no `section` option of its own. */
|
|
51
|
+
tableSection?: string | true;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
async function buildPaneInstance(
|
|
@@ -126,12 +129,28 @@ export async function applyListingArgument(
|
|
|
126
129
|
return { ...args, arg: listing.key, options, listing };
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
/**
|
|
133
|
+
* `--section` picks one table of a report's `--csv` or `--ndjson` output. A
|
|
134
|
+
* function that has a `section` option of its own (CALLS: which part of a
|
|
135
|
+
* transcript) keeps the flag; its report is one table, so there is nothing to
|
|
136
|
+
* pick there.
|
|
137
|
+
*/
|
|
138
|
+
function takeTableSection(
|
|
139
|
+
capability: PaneFunctionCapability,
|
|
140
|
+
options: ParsedPaneFunctionArgs["options"],
|
|
141
|
+
): { options: ParsedPaneFunctionArgs["options"]; tableSection?: string | true } {
|
|
142
|
+
if (!("section" in options) || capabilityHasOption(capability, "section")) return { options };
|
|
143
|
+
const { section, ...rest } = options;
|
|
144
|
+
return { options: rest, tableSection: section };
|
|
145
|
+
}
|
|
146
|
+
|
|
129
147
|
export async function resolvePaneFunction(
|
|
130
148
|
registry: PaneFunctionCatalog,
|
|
131
149
|
context: MarketContext,
|
|
132
|
-
|
|
133
|
-
resolutionSettings: { strictHeadlessOptions?: boolean } = {},
|
|
150
|
+
parsedArgs: ParsedPaneFunctionArgs,
|
|
151
|
+
resolutionSettings: { strictHeadlessOptions?: boolean; tableSection?: boolean } = {},
|
|
134
152
|
): Promise<ResolvedPaneFunction> {
|
|
153
|
+
let args = parsedArgs;
|
|
135
154
|
if (!args.target) {
|
|
136
155
|
throw new Error("Usage: gloomberb fn <function-or-pane> [argument] [--key value]");
|
|
137
156
|
}
|
|
@@ -152,6 +171,12 @@ export async function resolvePaneFunction(
|
|
|
152
171
|
const createOptions = buildCreateOptions(template, args.arg);
|
|
153
172
|
const headless = getHeadlessPaneDefinition(template, pane);
|
|
154
173
|
const capability = getPaneFunctionCapability(template, pane);
|
|
174
|
+
let tableSection: string | true | undefined;
|
|
175
|
+
if (resolutionSettings.tableSection) {
|
|
176
|
+
const taken = takeTableSection(capability, args.options);
|
|
177
|
+
args = { ...args, options: taken.options };
|
|
178
|
+
tableSection = taken.tableSection;
|
|
179
|
+
}
|
|
155
180
|
const normalizedOptions = normalizeCapabilityOptions(capability, args.options, {
|
|
156
181
|
strict: args.requireBotSafe || (!!headless && resolutionSettings.strictHeadlessOptions === true),
|
|
157
182
|
});
|
|
@@ -177,6 +202,7 @@ export async function resolvePaneFunction(
|
|
|
177
202
|
optionSettings: settings,
|
|
178
203
|
capability,
|
|
179
204
|
options: normalizedOptions,
|
|
205
|
+
...(tableSection !== undefined ? { tableSection } : {}),
|
|
180
206
|
};
|
|
181
207
|
resolved.instance = await buildPaneInstance(resolved, context, args.arg);
|
|
182
208
|
return resolved;
|
|
@@ -74,6 +74,7 @@ import type { ResolvedSeries } from "../../time-series/types";
|
|
|
74
74
|
import { getQuoteMonitorPaneSettings } from "../../plugins/builtin/ticker-detail/settings";
|
|
75
75
|
import { accessGateSentence } from "./access-gate";
|
|
76
76
|
import { findCollection } from "../../plugins/builtin/portfolio-list/cli/render";
|
|
77
|
+
import { peekCachedBrokerAccounts } from "../../plugins/builtin/portfolio-list/cached-account";
|
|
77
78
|
import {
|
|
78
79
|
paneEvidenceMismatches,
|
|
79
80
|
paneScreenshotEvidenceHook,
|
|
@@ -625,6 +626,9 @@ export async function buildDesktopShotPayload(
|
|
|
625
626
|
deviceScaleFactor,
|
|
626
627
|
watermark,
|
|
627
628
|
tickers,
|
|
629
|
+
...(COLLECTION_PANE_IDS.has(resolved.pane.id)
|
|
630
|
+
? { brokerAccounts: peekCachedBrokerAccounts(context.config, context.persistence?.resources) }
|
|
631
|
+
: {}),
|
|
628
632
|
financials,
|
|
629
633
|
...(instrumentFinancials?.length ? { instrumentFinancials } : {}),
|
|
630
634
|
...(historyVariants?.length ? { historyVariants } : {}),
|
package/src/cli/registry.ts
CHANGED
|
@@ -27,6 +27,8 @@ interface RegisteredCliCommand {
|
|
|
27
27
|
command: CliCommandDef;
|
|
28
28
|
ownerId: string;
|
|
29
29
|
source: "core" | "plugin";
|
|
30
|
+
/** Core or a built-in plugin: its declared options are the only ones it takes. External plugins are not held to theirs. */
|
|
31
|
+
builtin: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export interface CliCommandRegistry {
|
|
@@ -80,6 +82,7 @@ export function buildCliCommandRegistry({
|
|
|
80
82
|
command: CliCommandDef,
|
|
81
83
|
ownerId: string,
|
|
82
84
|
source: "core" | "plugin",
|
|
85
|
+
builtin: boolean,
|
|
83
86
|
) => {
|
|
84
87
|
const tokens = new Set([
|
|
85
88
|
validateCommandToken(command.name, ownerId, source),
|
|
@@ -95,7 +98,7 @@ export function buildCliCommandRegistry({
|
|
|
95
98
|
);
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
const entry: RegisteredCliCommand = { command, ownerId, source };
|
|
101
|
+
const entry: RegisteredCliCommand = { command, ownerId, source, builtin };
|
|
99
102
|
allCommands.push(entry);
|
|
100
103
|
for (const token of tokens) {
|
|
101
104
|
allTokens.set(token, entry);
|
|
@@ -103,7 +106,7 @@ export function buildCliCommandRegistry({
|
|
|
103
106
|
};
|
|
104
107
|
|
|
105
108
|
for (const command of coreCommands) {
|
|
106
|
-
registerCommand(command, "core", "core");
|
|
109
|
+
registerCommand(command, "core", "core", true);
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
const loadablePlugins: GloomPlugin[] = [];
|
|
@@ -117,7 +120,7 @@ export function buildCliCommandRegistry({
|
|
|
117
120
|
}
|
|
118
121
|
loadablePlugins.push(entry.plugin);
|
|
119
122
|
for (const command of entry.plugin.cliCommands ?? []) {
|
|
120
|
-
registerCommand(command, entry.plugin.id, "plugin");
|
|
123
|
+
registerCommand(command, entry.plugin.id, "plugin", entry.source === "builtin");
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
|