gloomberb 0.2.0 → 0.3.1
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 +20 -19
- package/bin/gloomberb +3 -0
- package/package.json +5 -4
- package/patches/@opentui%2Fcore@0.1.90.patch +356 -0
- package/src/app.tsx +1330 -0
- package/src/brokers/sync-broker-instance.test.ts +100 -0
- package/src/brokers/sync-broker-instance.ts +341 -0
- package/src/cli/commands/plugins.ts +173 -0
- package/src/cli/commands/portfolio.ts +210 -0
- package/src/cli/commands/search.ts +120 -0
- package/src/cli/commands/ticker.ts +375 -0
- package/src/cli/commands/watchlist.ts +210 -0
- package/src/cli/context.ts +62 -0
- package/src/cli/errors.ts +13 -0
- package/src/cli/helpers.ts +85 -0
- package/src/cli/index.ts +143 -0
- package/src/cli/ticker-resolution.ts +39 -0
- package/src/cli/types.ts +15 -0
- package/src/cli.test.ts +361 -0
- package/src/components/chart/chart-data.ts +239 -0
- package/src/components/chart/chart-renderer.ts +1199 -0
- package/src/components/chart/chart-types.ts +82 -0
- package/src/components/chart/chart-viewport.ts +21 -0
- package/src/components/chart/chart.test.ts +439 -0
- package/src/components/chart/comparison-chart-data.test.ts +109 -0
- package/src/components/chart/comparison-chart-data.ts +316 -0
- package/src/components/chart/comparison-chart-renderer.test.ts +84 -0
- package/src/components/chart/comparison-chart-renderer.ts +354 -0
- package/src/components/chart/comparison-stock-chart.tsx +1486 -0
- package/src/components/chart/cursor-motion.ts +48 -0
- package/src/components/chart/native/chart-rasterizer.ts +732 -0
- package/src/components/chart/native/kitty-adapter.ts +12 -0
- package/src/components/chart/native/kitty-manager.ts +142 -0
- package/src/components/chart/native/kitty-protocol.ts +110 -0
- package/src/components/chart/native/kitty-support.ts +71 -0
- package/src/components/chart/native/kitty.test.ts +620 -0
- package/src/components/chart/native/renderer-selection.ts +24 -0
- package/src/components/chart/native/surface-manager.ts +225 -0
- package/src/components/chart/native/surface-sync.ts +34 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +165 -0
- package/src/components/chart/stock-chart.test.ts +107 -0
- package/src/components/chart/stock-chart.tsx +1556 -0
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +137 -0
- package/src/components/command-bar/command-bar.test.tsx +1524 -0
- package/src/components/command-bar/command-bar.tsx +4823 -0
- package/src/components/command-bar/command-registry.ts +261 -0
- package/src/components/command-bar/helpers.test.ts +224 -0
- package/src/components/command-bar/helpers.ts +371 -0
- package/src/components/command-bar/root-shortcuts.ts +158 -0
- package/src/components/command-bar/view-model.test.ts +152 -0
- package/src/components/command-bar/view-model.ts +185 -0
- package/src/components/command-bar/workflow-ops.test.ts +70 -0
- package/src/components/command-bar/workflow-ops.ts +433 -0
- package/src/components/command-bar/workflow-types.ts +127 -0
- package/src/components/detail-feed-view.test.tsx +105 -0
- package/src/components/detail-feed-view.tsx +295 -0
- package/src/components/index.ts +32 -0
- package/src/components/layout/floating-pane.tsx +49 -0
- package/src/components/layout/header.tsx +129 -0
- package/src/components/layout/pane-header.tsx +44 -0
- package/src/components/layout/pane.tsx +49 -0
- package/src/components/layout/pixel-mouse-mode.test.ts +122 -0
- package/src/components/layout/pixel-mouse.test.ts +40 -0
- package/src/components/layout/shell.test.tsx +625 -0
- package/src/components/layout/shell.tsx +1200 -0
- package/src/components/layout/status-bar.test.tsx +173 -0
- package/src/components/layout/status-bar.tsx +138 -0
- package/src/components/markdown-editor.tsx +134 -0
- package/src/components/onboarding/onboarding-wizard.test.tsx +184 -0
- package/src/components/onboarding/onboarding-wizard.tsx +1365 -0
- package/src/components/pane-settings-dialog.tsx +468 -0
- package/src/components/pane-template-wizard.test.tsx +52 -0
- package/src/components/pane-template-wizard.tsx +199 -0
- package/src/components/price-selector-dialog.tsx +195 -0
- package/src/components/spinner.tsx +2 -0
- package/src/components/tab-bar.tsx +17 -0
- package/src/components/ticker-badge-text.test.tsx +160 -0
- package/src/components/ticker-badge-text.tsx +58 -0
- package/src/components/ticker-badge.tsx +88 -0
- package/src/components/ticker-list-table.tsx +188 -0
- package/src/components/toggle-list.tsx +74 -0
- package/src/components/ui/button.tsx +77 -0
- package/src/components/ui/data-table.tsx +210 -0
- package/src/components/ui/external-link.tsx +22 -0
- package/src/components/ui/fields.tsx +187 -0
- package/src/components/ui/frame.tsx +73 -0
- package/src/components/ui/index.ts +53 -0
- package/src/components/ui/list-view.tsx +166 -0
- package/src/components/ui/loading.tsx +70 -0
- package/src/components/ui/status.tsx +83 -0
- package/src/components/ui/tabs.tsx +47 -0
- package/src/components/ui/toggle.tsx +164 -0
- package/src/components/ui/ui.test.tsx +134 -0
- package/src/data/app-persistence.ts +25 -0
- package/src/data/config-store.test.ts +310 -0
- package/src/data/config-store.ts +469 -0
- package/src/data/plugin-state-store.ts +102 -0
- package/src/data/resource-store.ts +323 -0
- package/src/data/session-store.ts +52 -0
- package/src/data/sqlite-cache.test.ts +103 -0
- package/src/data/sqlite-database.ts +62 -0
- package/src/data/sqlite-json.ts +19 -0
- package/src/data/ticker-repository.ts +45 -0
- package/src/data/ticker-store.ts +46 -0
- package/src/index.tsx +67 -0
- package/src/market-data/coordinator.test.ts +369 -0
- package/src/market-data/coordinator.ts +580 -0
- package/src/market-data/hooks.tsx +234 -0
- package/src/market-data/query-store.ts +28 -0
- package/src/market-data/request-types.test.ts +64 -0
- package/src/market-data/request-types.ts +70 -0
- package/src/market-data/result-types.ts +56 -0
- package/src/market-data/selectors.ts +166 -0
- package/src/market-data/trace.ts +7 -0
- package/src/pane-settings.test.ts +71 -0
- package/src/pane-settings.ts +73 -0
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +338 -0
- package/src/plugins/builtin/ai/gloomberb-cli.ts +70 -0
- package/src/plugins/builtin/ai/index.test.tsx +139 -0
- package/src/plugins/builtin/ai/index.tsx +86 -0
- package/src/plugins/builtin/ai/providers.ts +99 -0
- package/src/plugins/builtin/ai/runner.ts +82 -0
- package/src/plugins/builtin/ai/screener-contract.test.ts +117 -0
- package/src/plugins/builtin/ai/screener-contract.ts +174 -0
- package/src/plugins/builtin/ai/screener-pane.test.tsx +365 -0
- package/src/plugins/builtin/ai/screener-pane.tsx +1161 -0
- package/src/plugins/builtin/ai/settings.ts +79 -0
- package/src/plugins/builtin/ai/ticker-context.ts +64 -0
- package/src/plugins/builtin/ai/utils.ts +71 -0
- package/src/plugins/builtin/ask-ai.test.tsx +258 -0
- package/src/plugins/builtin/ask-ai.tsx +9 -0
- package/src/plugins/builtin/chat-controller.test.ts +217 -0
- package/src/plugins/builtin/chat-controller.ts +340 -0
- package/src/plugins/builtin/chat.test.tsx +451 -0
- package/src/plugins/builtin/chat.tsx +620 -0
- package/src/plugins/builtin/comparison-chart.test.tsx +277 -0
- package/src/plugins/builtin/comparison-chart.tsx +169 -0
- package/src/plugins/builtin/data-providers.ts +16 -0
- package/src/plugins/builtin/debug.tsx +351 -0
- package/src/plugins/builtin/help.test.tsx +110 -0
- package/src/plugins/builtin/help.tsx +281 -0
- package/src/plugins/builtin/layout-manager.tsx +264 -0
- package/src/plugins/builtin/manual-entry.tsx +25 -0
- package/src/plugins/builtin/news.tsx +143 -0
- package/src/plugins/builtin/notes-files.ts +58 -0
- package/src/plugins/builtin/notes.tsx +396 -0
- package/src/plugins/builtin/options-availability.test.tsx +179 -0
- package/src/plugins/builtin/options-availability.ts +158 -0
- package/src/plugins/builtin/options.tsx +364 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +227 -0
- package/src/plugins/builtin/portfolio-list/index.tsx +99 -0
- package/src/plugins/builtin/portfolio-list/metrics.ts +436 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +533 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +280 -0
- package/src/plugins/builtin/portfolio-list/summary.tsx +270 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +64 -0
- package/src/plugins/builtin/portfolio-list.test.ts +74 -0
- package/src/plugins/builtin/portfolio-list.test.tsx +1164 -0
- package/src/plugins/builtin/portfolio-metrics.test.ts +153 -0
- package/src/plugins/builtin/quote-monitor.test.tsx +114 -0
- package/src/plugins/builtin/sec.tsx +250 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +43 -0
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +390 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +80 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +242 -0
- package/src/plugins/builtin/ticker-detail/pane.tsx +183 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +83 -0
- package/src/plugins/builtin/ticker-detail/settings.ts +132 -0
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +193 -0
- package/src/plugins/builtin/ticker-detail.test.tsx +578 -0
- package/src/plugins/builtin/yahoo.ts +18 -0
- package/src/plugins/event-bus.ts +37 -0
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +35 -0
- package/src/plugins/ibkr/account-cache.test.ts +101 -0
- package/src/plugins/ibkr/account-cache.ts +137 -0
- package/src/plugins/ibkr/broker-adapter.ts +168 -0
- package/src/plugins/ibkr/config.test.ts +68 -0
- package/src/plugins/ibkr/config.ts +262 -0
- package/src/plugins/ibkr/dialogs.tsx +83 -0
- package/src/plugins/ibkr/flex.test.ts +107 -0
- package/src/plugins/ibkr/flex.ts +267 -0
- package/src/plugins/ibkr/fundamental-parser.ts +149 -0
- package/src/plugins/ibkr/gateway-helpers.ts +28 -0
- package/src/plugins/ibkr/gateway-selection.ts +51 -0
- package/src/plugins/ibkr/gateway-service.test.ts +450 -0
- package/src/plugins/ibkr/gateway-service.ts +2228 -0
- package/src/plugins/ibkr/index.test.tsx +183 -0
- package/src/plugins/ibkr/index.tsx +153 -0
- package/src/plugins/ibkr/instance-selection.test.ts +121 -0
- package/src/plugins/ibkr/instance-selection.ts +36 -0
- package/src/plugins/ibkr/price-normalization.test.ts +38 -0
- package/src/plugins/ibkr/price-normalization.ts +59 -0
- package/src/plugins/ibkr/trade-badge.tsx +27 -0
- package/src/plugins/ibkr/trade-tab.test.tsx +225 -0
- package/src/plugins/ibkr/trade-tab.tsx +1050 -0
- package/src/plugins/ibkr/trade-utils.test.ts +175 -0
- package/src/plugins/ibkr/trade-utils.ts +149 -0
- package/src/plugins/ibkr/trading-pane.tsx +438 -0
- package/src/plugins/ibkr/trading-state.test.ts +109 -0
- package/src/plugins/ibkr/trading-state.ts +339 -0
- package/src/plugins/loader.ts +71 -0
- package/src/plugins/pane-manager.test.ts +103 -0
- package/src/plugins/pane-manager.ts +974 -0
- package/src/plugins/plugin-persistence.ts +85 -0
- package/src/plugins/plugin-runtime.test.tsx +185 -0
- package/src/plugins/plugin-runtime.tsx +175 -0
- package/src/plugins/prediction-markets/cache.ts +114 -0
- package/src/plugins/prediction-markets/categories.ts +223 -0
- package/src/plugins/prediction-markets/chart.tsx +512 -0
- package/src/plugins/prediction-markets/columns.ts +133 -0
- package/src/plugins/prediction-markets/controller-data.ts +614 -0
- package/src/plugins/prediction-markets/controller.ts +725 -0
- package/src/plugins/prediction-markets/detail/book.tsx +118 -0
- package/src/plugins/prediction-markets/detail/outcomes.tsx +102 -0
- package/src/plugins/prediction-markets/detail/overview.tsx +73 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +308 -0
- package/src/plugins/prediction-markets/detail/rules.tsx +16 -0
- package/src/plugins/prediction-markets/detail/shared.tsx +29 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +30 -0
- package/src/plugins/prediction-markets/detail.test.tsx +47 -0
- package/src/plugins/prediction-markets/index.tsx +102 -0
- package/src/plugins/prediction-markets/keyboard.ts +64 -0
- package/src/plugins/prediction-markets/launch.test.ts +126 -0
- package/src/plugins/prediction-markets/launch.ts +240 -0
- package/src/plugins/prediction-markets/metrics.ts +311 -0
- package/src/plugins/prediction-markets/navigation.ts +90 -0
- package/src/plugins/prediction-markets/outcome-order.ts +19 -0
- package/src/plugins/prediction-markets/pane.test.tsx +425 -0
- package/src/plugins/prediction-markets/pane.tsx +294 -0
- package/src/plugins/prediction-markets/plugin.test.tsx +570 -0
- package/src/plugins/prediction-markets/rows.ts +229 -0
- package/src/plugins/prediction-markets/services/fetch.ts +97 -0
- package/src/plugins/prediction-markets/services/kalshi-adapter.ts +555 -0
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +660 -0
- package/src/plugins/prediction-markets/services/polymarket-ws.ts +108 -0
- package/src/plugins/prediction-markets/settings.ts +164 -0
- package/src/plugins/prediction-markets/status.ts +24 -0
- package/src/plugins/prediction-markets/table.tsx +84 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +636 -0
- package/src/plugins/prediction-markets/types.ts +186 -0
- package/src/plugins/registry.ts +633 -0
- package/src/sources/exchange-rates.ts +18 -0
- package/src/sources/gloomberb-cloud.test.ts +199 -0
- package/src/sources/gloomberb-cloud.ts +326 -0
- package/src/sources/provider-errors.ts +19 -0
- package/src/sources/provider-router.test.ts +1476 -0
- package/src/sources/provider-router.ts +1405 -0
- package/src/sources/sec-edgar.test.ts +286 -0
- package/src/sources/sec-edgar.ts +526 -0
- package/src/sources/yahoo-finance.test.ts +14 -0
- package/src/sources/yahoo-finance.ts +1017 -0
- package/src/state/app-activity.test.ts +41 -0
- package/src/state/app-activity.ts +106 -0
- package/src/state/app-bootstrap.test.ts +471 -0
- package/src/state/app-bootstrap.ts +308 -0
- package/src/state/app-context.test.ts +406 -0
- package/src/state/app-context.test.tsx +132 -0
- package/src/state/app-context.tsx +969 -0
- package/src/state/selectors.ts +67 -0
- package/src/state/session-persistence.test.ts +128 -0
- package/src/state/session-persistence.ts +161 -0
- package/src/state/ticker-refresh-queue.test.ts +46 -0
- package/src/state/ticker-refresh-queue.ts +40 -0
- package/src/state/use-inline-tickers.ts +174 -0
- package/src/state/use-quote-streaming.test.ts +37 -0
- package/src/state/use-quote-streaming.test.tsx +70 -0
- package/src/state/use-quote-streaming.ts +80 -0
- package/src/state/use-session-snapshot.ts +84 -0
- package/src/test-support/data-provider.ts +22 -0
- package/src/theme/color-utils.ts +67 -0
- package/src/theme/colors.ts +128 -0
- package/src/theme/themes.test.ts +45 -0
- package/src/theme/themes.ts +491 -0
- package/src/types/broker.ts +105 -0
- package/src/types/config.ts +434 -0
- package/src/types/data-provider.ts +81 -0
- package/src/types/financials.ts +142 -0
- package/src/types/instrument.ts +29 -0
- package/src/types/persistence.ts +21 -0
- package/src/types/plugin.ts +354 -0
- package/src/types/ticker.ts +58 -0
- package/src/types/trading.ts +85 -0
- package/src/updater.test.ts +162 -0
- package/src/updater.ts +247 -0
- package/src/utils/api-client.ts +666 -0
- package/src/utils/ascii-chart.ts +172 -0
- package/src/utils/broker-instances.ts +47 -0
- package/src/utils/cli-output.ts +83 -0
- package/src/utils/currency-units.test.ts +49 -0
- package/src/utils/currency-units.ts +82 -0
- package/src/utils/debug-log.ts +133 -0
- package/src/utils/exchange-rate-map.ts +8 -0
- package/src/utils/format.ts +158 -0
- package/src/utils/fuzzy-search.ts +34 -0
- package/src/utils/market-status.ts +95 -0
- package/src/utils/options.ts +86 -0
- package/src/utils/price-history.ts +39 -0
- package/src/utils/quote-time.test.ts +22 -0
- package/src/utils/quote-time.ts +50 -0
- package/src/utils/sec.test.ts +55 -0
- package/src/utils/sec.ts +45 -0
- package/src/utils/ticker-list.test.ts +22 -0
- package/src/utils/ticker-list.ts +35 -0
- package/src/utils/ticker-metadata.ts +51 -0
- package/src/utils/ticker-search.test.ts +227 -0
- package/src/utils/ticker-search.ts +764 -0
- package/src/utils/ticker-tokenizer.test.ts +48 -0
- package/src/utils/ticker-tokenizer.ts +72 -0
- package/src/version.ts +1 -0
- package/install.mjs +0 -68
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASCII stock chart using braille characters (U+2800-U+28FF).
|
|
3
|
+
* Each braille char is 2 columns x 4 rows of dots.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BRAILLE_BASE = 0x2800;
|
|
7
|
+
// Dot bit positions for each column (left=0, right=1) and row (0=top, 3=bottom)
|
|
8
|
+
const DOT_BITS = [
|
|
9
|
+
[0x01, 0x02, 0x04, 0x40], // left column
|
|
10
|
+
[0x08, 0x10, 0x20, 0x80], // right column
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export interface StockChartOptions {
|
|
14
|
+
width: number; // chart width in characters
|
|
15
|
+
height: number; // chart height in terminal rows
|
|
16
|
+
showAxis?: boolean;
|
|
17
|
+
showLabels?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface StockChartData {
|
|
21
|
+
dates: (Date | string | number)[];
|
|
22
|
+
prices: number[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Render a full stock chart with braille line, axis labels, and price range.
|
|
27
|
+
* Returns an array of strings (one per terminal row).
|
|
28
|
+
*/
|
|
29
|
+
export function renderStockChart(
|
|
30
|
+
data: StockChartData,
|
|
31
|
+
opts: StockChartOptions,
|
|
32
|
+
): string[] {
|
|
33
|
+
const { dates, prices } = data;
|
|
34
|
+
if (prices.length === 0) return [];
|
|
35
|
+
|
|
36
|
+
const showAxis = opts.showAxis !== false;
|
|
37
|
+
const showLabels = opts.showLabels !== false;
|
|
38
|
+
|
|
39
|
+
// Reserve space for labels
|
|
40
|
+
const priceAxisWidth = showLabels ? 10 : 0;
|
|
41
|
+
const chartWidth = opts.width - priceAxisWidth;
|
|
42
|
+
const chartHeight = opts.height - (showAxis ? 1 : 0); // bottom row for time axis
|
|
43
|
+
|
|
44
|
+
if (chartWidth < 4 || chartHeight < 2) return [];
|
|
45
|
+
|
|
46
|
+
const min = Math.min(...prices);
|
|
47
|
+
const max = Math.max(...prices);
|
|
48
|
+
const range = max - min || 1;
|
|
49
|
+
const totalDotsV = chartHeight * 4; // vertical braille resolution
|
|
50
|
+
|
|
51
|
+
// Sample prices to fit chart width (2 data points per braille char)
|
|
52
|
+
const sampleCount = chartWidth * 2;
|
|
53
|
+
const sampled: number[] = [];
|
|
54
|
+
const sampledDates: (Date | string | number)[] = [];
|
|
55
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
56
|
+
const srcIdx = Math.floor((i / sampleCount) * prices.length);
|
|
57
|
+
sampled.push(prices[srcIdx]!);
|
|
58
|
+
sampledDates.push(dates[srcIdx]!);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Convert to dot Y positions (0 = bottom, totalDotsV-1 = top)
|
|
62
|
+
const dotPositions = sampled.map(
|
|
63
|
+
(v) => Math.round(((v - min) / range) * (totalDotsV - 1))
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Build braille chart rows with connected lines
|
|
67
|
+
const chartLines: string[] = [];
|
|
68
|
+
for (let row = 0; row < chartHeight; row++) {
|
|
69
|
+
let line = "";
|
|
70
|
+
for (let col = 0; col < chartWidth; col++) {
|
|
71
|
+
let bits = 0;
|
|
72
|
+
for (let subCol = 0; subCol < 2; subCol++) {
|
|
73
|
+
const valueIdx = col * 2 + subCol;
|
|
74
|
+
const dotY = dotPositions[valueIdx] ?? 0;
|
|
75
|
+
// Get previous data point to interpolate a connected line
|
|
76
|
+
const prevIdx = valueIdx - 1;
|
|
77
|
+
const prevDotY = prevIdx >= 0 ? (dotPositions[prevIdx] ?? dotY) : dotY;
|
|
78
|
+
const lo = Math.min(dotY, prevDotY);
|
|
79
|
+
const hi = Math.max(dotY, prevDotY);
|
|
80
|
+
|
|
81
|
+
const rowBottomDot = (chartHeight - 1 - row) * 4;
|
|
82
|
+
for (let subRow = 0; subRow < 4; subRow++) {
|
|
83
|
+
const absoluteDot = rowBottomDot + subRow;
|
|
84
|
+
// Draw connected line: fill between current and previous point
|
|
85
|
+
if (absoluteDot >= lo && absoluteDot <= hi) {
|
|
86
|
+
bits |= DOT_BITS[subCol]![subRow]!;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
line += String.fromCharCode(BRAILLE_BASE + bits);
|
|
91
|
+
}
|
|
92
|
+
chartLines.push(line);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Build full output with labels
|
|
96
|
+
const output: string[] = [];
|
|
97
|
+
|
|
98
|
+
for (let row = 0; row < chartHeight; row++) {
|
|
99
|
+
let line = "";
|
|
100
|
+
if (showLabels) {
|
|
101
|
+
// Price label on right side at top, middle, bottom
|
|
102
|
+
let label = "";
|
|
103
|
+
if (row === 0) {
|
|
104
|
+
label = formatAxisPrice(max);
|
|
105
|
+
} else if (row === Math.floor(chartHeight / 2)) {
|
|
106
|
+
label = formatAxisPrice((max + min) / 2);
|
|
107
|
+
} else if (row === chartHeight - 1) {
|
|
108
|
+
label = formatAxisPrice(min);
|
|
109
|
+
}
|
|
110
|
+
line = chartLines[row]! + " " + label.padStart(priceAxisWidth - 1);
|
|
111
|
+
} else {
|
|
112
|
+
line = chartLines[row]!;
|
|
113
|
+
}
|
|
114
|
+
output.push(line);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Time axis
|
|
118
|
+
if (showAxis && dates.length > 0) {
|
|
119
|
+
const firstDate = dates[0]!;
|
|
120
|
+
const lastDate = dates[dates.length - 1]!;
|
|
121
|
+
const midDate = dates[Math.floor(dates.length / 2)]!;
|
|
122
|
+
|
|
123
|
+
const startLabel = formatAxisDate(firstDate);
|
|
124
|
+
const midLabel = formatAxisDate(midDate);
|
|
125
|
+
const endLabel = formatAxisDate(lastDate);
|
|
126
|
+
|
|
127
|
+
let axis = startLabel;
|
|
128
|
+
const midPos = Math.floor(chartWidth / 2) - Math.floor(midLabel.length / 2);
|
|
129
|
+
const endPos = chartWidth - endLabel.length;
|
|
130
|
+
|
|
131
|
+
// Build axis string with proper spacing
|
|
132
|
+
axis = startLabel.padEnd(midPos) + midLabel;
|
|
133
|
+
axis = axis.padEnd(endPos) + endLabel;
|
|
134
|
+
if (axis.length > chartWidth + priceAxisWidth) {
|
|
135
|
+
axis = axis.slice(0, chartWidth + priceAxisWidth);
|
|
136
|
+
}
|
|
137
|
+
output.push(axis);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return output;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function formatAxisPrice(value: number): string {
|
|
144
|
+
if (Math.abs(value) >= 1e6) return `$${(value / 1e6).toFixed(1)}M`;
|
|
145
|
+
if (Math.abs(value) >= 1e3) return `$${(value / 1e3).toFixed(1)}K`;
|
|
146
|
+
return `$${value.toFixed(2)}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function formatAxisDate(date: Date | string | number): string {
|
|
150
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
151
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
152
|
+
if (isNaN(d.getTime())) return "—";
|
|
153
|
+
return `${months[d.getMonth()]} ${d.getFullYear()}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Simple block sparkline for inline use (single line) */
|
|
157
|
+
export function inlineSparkline(values: number[], width: number): string {
|
|
158
|
+
if (values.length === 0) return " ".repeat(width);
|
|
159
|
+
const blocks = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
160
|
+
const min = Math.min(...values);
|
|
161
|
+
const max = Math.max(...values);
|
|
162
|
+
const range = max - min || 1;
|
|
163
|
+
|
|
164
|
+
let result = "";
|
|
165
|
+
for (let i = 0; i < width; i++) {
|
|
166
|
+
const idx = Math.floor((i / width) * values.length);
|
|
167
|
+
const normalized = (values[idx]! - min) / range;
|
|
168
|
+
const blockIdx = Math.min(Math.floor(normalized * blocks.length), blocks.length - 1);
|
|
169
|
+
result += blocks[blockIdx];
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { BrokerInstanceConfig } from "../types/config";
|
|
2
|
+
|
|
3
|
+
function slugify(value: string): string {
|
|
4
|
+
return value
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
7
|
+
.replace(/^-|-$/g, "");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createBrokerInstanceId(
|
|
11
|
+
brokerType: string,
|
|
12
|
+
label: string,
|
|
13
|
+
existingIds: Iterable<string>,
|
|
14
|
+
): string {
|
|
15
|
+
const base = `${brokerType}-${slugify(label) || "account"}`;
|
|
16
|
+
const seen = new Set(existingIds);
|
|
17
|
+
if (!seen.has(base)) return base;
|
|
18
|
+
|
|
19
|
+
let suffix = 2;
|
|
20
|
+
while (seen.has(`${base}-${suffix}`)) {
|
|
21
|
+
suffix += 1;
|
|
22
|
+
}
|
|
23
|
+
return `${base}-${suffix}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildBrokerPortfolioId(brokerInstanceId: string, accountId?: string): string {
|
|
27
|
+
return `broker:${brokerInstanceId}:${accountId?.trim() || "default"}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isBrokerPortfolioId(collectionId: string | undefined | null): collectionId is string {
|
|
31
|
+
return typeof collectionId === "string" && collectionId.startsWith("broker:");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getBrokerInstance(
|
|
35
|
+
brokerInstances: BrokerInstanceConfig[],
|
|
36
|
+
instanceId: string | undefined,
|
|
37
|
+
): BrokerInstanceConfig | undefined {
|
|
38
|
+
if (!instanceId) return undefined;
|
|
39
|
+
return brokerInstances.find((instance) => instance.id === instanceId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getBrokerInstancesByType(
|
|
43
|
+
brokerInstances: BrokerInstanceConfig[],
|
|
44
|
+
brokerType: string,
|
|
45
|
+
): BrokerInstanceConfig[] {
|
|
46
|
+
return brokerInstances.filter((instance) => instance.brokerType === brokerType);
|
|
47
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type CliAlign = "left" | "right" | "center";
|
|
2
|
+
|
|
3
|
+
export interface CliTableColumn {
|
|
4
|
+
header: string;
|
|
5
|
+
align?: CliAlign;
|
|
6
|
+
width?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
10
|
+
|
|
11
|
+
function colorEnabled(): boolean {
|
|
12
|
+
return !!process.stdout.isTTY && process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function applyAnsi(text: string, code: string): string {
|
|
16
|
+
if (!colorEnabled()) return text;
|
|
17
|
+
return `\x1b[${code}m${text}\x1b[0m`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function stripAnsi(text: string): string {
|
|
21
|
+
return text.replace(ANSI_PATTERN, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function visibleLength(text: string): number {
|
|
25
|
+
return stripAnsi(text).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function padDisplay(text: string, width: number, align: CliAlign = "left"): string {
|
|
29
|
+
const padding = Math.max(0, width - visibleLength(text));
|
|
30
|
+
if (padding === 0) return text;
|
|
31
|
+
|
|
32
|
+
if (align === "right") return `${" ".repeat(padding)}${text}`;
|
|
33
|
+
if (align === "center") {
|
|
34
|
+
const left = Math.floor(padding / 2);
|
|
35
|
+
const right = padding - left;
|
|
36
|
+
return `${" ".repeat(left)}${text}${" ".repeat(right)}`;
|
|
37
|
+
}
|
|
38
|
+
return `${text}${" ".repeat(padding)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const cliStyles = {
|
|
42
|
+
bold: (text: string) => applyAnsi(text, "1"),
|
|
43
|
+
dim: (text: string) => applyAnsi(text, "2"),
|
|
44
|
+
muted: (text: string) => applyAnsi(text, "90"),
|
|
45
|
+
accent: (text: string) => applyAnsi(text, "1;36"),
|
|
46
|
+
success: (text: string) => applyAnsi(text, "32"),
|
|
47
|
+
warning: (text: string) => applyAnsi(text, "33"),
|
|
48
|
+
danger: (text: string) => applyAnsi(text, "31"),
|
|
49
|
+
positive: (text: string) => applyAnsi(text, "32"),
|
|
50
|
+
negative: (text: string) => applyAnsi(text, "31"),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function colorBySign(text: string, value: number | undefined | null): string {
|
|
54
|
+
if (value == null || value === 0) return text;
|
|
55
|
+
return value > 0 ? cliStyles.positive(text) : cliStyles.negative(text);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function renderSection(title: string): string {
|
|
59
|
+
return `${cliStyles.accent(title)}\n${cliStyles.muted("-".repeat(Math.max(12, title.length)))}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function renderStat(label: string, value: string, labelWidth = 16): string {
|
|
63
|
+
return `${cliStyles.dim(padDisplay(`${label}:`, labelWidth))} ${value}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function renderTable(columns: CliTableColumn[], rows: string[][]): string {
|
|
67
|
+
const widths = columns.map((column, index) => {
|
|
68
|
+
const cellWidths = rows.map((row) => visibleLength(row[index] ?? ""));
|
|
69
|
+
return column.width ?? Math.max(visibleLength(column.header), ...cellWidths);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const header = columns
|
|
73
|
+
.map((column, index) => cliStyles.bold(padDisplay(column.header, widths[index]!, column.align ?? "left")))
|
|
74
|
+
.join(" ");
|
|
75
|
+
const divider = widths.map((width) => cliStyles.muted("-".repeat(width))).join(" ");
|
|
76
|
+
const body = rows.map((row) =>
|
|
77
|
+
row
|
|
78
|
+
.map((cell, index) => padDisplay(cell ?? "", widths[index]!, columns[index]?.align ?? "left"))
|
|
79
|
+
.join(" ")
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return [header, divider, ...body].join("\n");
|
|
83
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { hasLikelyQuoteUnitMismatch, normalizePriceValueByDivisor, resolveCurrencyUnit } from "./currency-units";
|
|
3
|
+
|
|
4
|
+
describe("currency unit helpers", () => {
|
|
5
|
+
test("normalizes known sub-unit currencies to their main currency", () => {
|
|
6
|
+
expect(resolveCurrencyUnit("GBp")).toEqual({ currency: "GBP", divisor: 100 });
|
|
7
|
+
expect(resolveCurrencyUnit("GBP")).toEqual({ currency: "GBP", divisor: 1 });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("scales quote prices by the configured divisor", () => {
|
|
11
|
+
expect(normalizePriceValueByDivisor(23.1, 100)).toBeCloseTo(0.231, 8);
|
|
12
|
+
expect(normalizePriceValueByDivisor(125, 1)).toBe(125);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("detects sub-unit quotes that match the same main-currency price", () => {
|
|
16
|
+
expect(hasLikelyQuoteUnitMismatch(
|
|
17
|
+
{ price: 23.1, currency: "GBp" },
|
|
18
|
+
{ price: 0.231, currency: "GBP" },
|
|
19
|
+
)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("detects classic same-currency 100x mismatches", () => {
|
|
23
|
+
expect(hasLikelyQuoteUnitMismatch(
|
|
24
|
+
{ price: 24.5, currency: "GBP" },
|
|
25
|
+
{ price: 0.245, currency: "GBP" },
|
|
26
|
+
)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("detects likely 1000x mismatches for three-decimal currencies", () => {
|
|
30
|
+
expect(hasLikelyQuoteUnitMismatch(
|
|
31
|
+
{ price: 721, currency: "KWD" },
|
|
32
|
+
{ price: 0.721, currency: "KWD" },
|
|
33
|
+
)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("does not flag similar quotes that already use the same unit", () => {
|
|
37
|
+
expect(hasLikelyQuoteUnitMismatch(
|
|
38
|
+
{ price: 0.231, currency: "GBP" },
|
|
39
|
+
{ price: 0.245, currency: "GBP" },
|
|
40
|
+
)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("does not assume 1000x mismatches for ordinary two-decimal currencies", () => {
|
|
44
|
+
expect(hasLikelyQuoteUnitMismatch(
|
|
45
|
+
{ price: 231, currency: "USD" },
|
|
46
|
+
{ price: 0.231, currency: "USD" },
|
|
47
|
+
)).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const SUB_UNIT_CURRENCIES: Record<string, { currency: string; divisor: number }> = {
|
|
2
|
+
GBp: { currency: "GBP", divisor: 100 },
|
|
3
|
+
GBX: { currency: "GBP", divisor: 100 },
|
|
4
|
+
ILA: { currency: "ILS", divisor: 100 },
|
|
5
|
+
ZAc: { currency: "ZAR", divisor: 100 },
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const LIKELY_UNIT_MISMATCH_RATIOS: Record<string, number[]> = {
|
|
9
|
+
BHD: [1000],
|
|
10
|
+
GBP: [100],
|
|
11
|
+
ILS: [100],
|
|
12
|
+
JOD: [1000],
|
|
13
|
+
KWD: [1000],
|
|
14
|
+
OMR: [1000],
|
|
15
|
+
TND: [1000],
|
|
16
|
+
ZAR: [100],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface CurrencyUnitInfo {
|
|
20
|
+
currency: string;
|
|
21
|
+
divisor: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveCurrencyUnit(currency?: string | null): CurrencyUnitInfo {
|
|
25
|
+
const raw = (currency ?? "").trim();
|
|
26
|
+
if (!raw) {
|
|
27
|
+
return { currency: "", divisor: 1 };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const normalized = SUB_UNIT_CURRENCIES[raw];
|
|
31
|
+
if (normalized) {
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
currency: raw.toUpperCase(),
|
|
37
|
+
divisor: 1,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function normalizePriceValueByDivisor(value: number | undefined, divisor: number): number | undefined {
|
|
42
|
+
if (value == null || !Number.isFinite(value) || divisor === 1) return value;
|
|
43
|
+
return value / divisor;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizedRatio(left: number, right: number): number {
|
|
47
|
+
const ratio = left / right;
|
|
48
|
+
return ratio >= 1 ? ratio : 1 / ratio;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isRatioWithin(ratio: number, target: number, tolerance = 0.05): boolean {
|
|
52
|
+
return Math.abs(ratio - target) / target < tolerance;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function likelyUnitMismatchRatios(currency: string): number[] {
|
|
56
|
+
return LIKELY_UNIT_MISMATCH_RATIOS[currency] ?? [100];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function hasLikelyQuoteUnitMismatch(
|
|
60
|
+
left: { currency?: string; price: number } | null | undefined,
|
|
61
|
+
right: { currency?: string; price: number } | null | undefined,
|
|
62
|
+
): boolean {
|
|
63
|
+
if (!left || !right) return false;
|
|
64
|
+
|
|
65
|
+
const leftUnit = resolveCurrencyUnit(left.currency);
|
|
66
|
+
const rightUnit = resolveCurrencyUnit(right.currency);
|
|
67
|
+
if (!leftUnit.currency || !rightUnit.currency) return false;
|
|
68
|
+
if (leftUnit.currency !== rightUnit.currency) return false;
|
|
69
|
+
if (!Number.isFinite(left.price) || !Number.isFinite(right.price)) return false;
|
|
70
|
+
if (left.price <= 0 || right.price <= 0) return false;
|
|
71
|
+
|
|
72
|
+
if (leftUnit.divisor !== rightUnit.divisor) {
|
|
73
|
+
const leftCanonicalPrice = left.price / leftUnit.divisor;
|
|
74
|
+
const rightCanonicalPrice = right.price / rightUnit.divisor;
|
|
75
|
+
if (isRatioWithin(normalizedRatio(leftCanonicalPrice, rightCanonicalPrice), 1)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ratio = normalizedRatio(left.price, right.price);
|
|
81
|
+
return likelyUnitMismatchRatios(leftUnit.currency).some((target) => isRatioWithin(ratio, target));
|
|
82
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
|
|
3
|
+
export interface LogEntry {
|
|
4
|
+
id: number;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
level: LogLevel;
|
|
7
|
+
source: string;
|
|
8
|
+
message: string;
|
|
9
|
+
data?: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type LogListener = (entry: LogEntry) => void;
|
|
13
|
+
|
|
14
|
+
class DebugLog {
|
|
15
|
+
private entries: LogEntry[] = [];
|
|
16
|
+
private listeners = new Set<LogListener>();
|
|
17
|
+
private nextId = 1;
|
|
18
|
+
private maxEntries = 5000;
|
|
19
|
+
|
|
20
|
+
/** Create a logger scoped to a source (plugin id, module name, etc.) */
|
|
21
|
+
createLogger(source: string) {
|
|
22
|
+
return {
|
|
23
|
+
debug: (message: string, data?: unknown) => this.add("debug", source, message, data),
|
|
24
|
+
info: (message: string, data?: unknown) => this.add("info", source, message, data),
|
|
25
|
+
warn: (message: string, data?: unknown) => this.add("warn", source, message, data),
|
|
26
|
+
error: (message: string, data?: unknown) => this.add("error", source, message, data),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private add(level: LogLevel, source: string, message: string, data?: unknown) {
|
|
31
|
+
const entry: LogEntry = {
|
|
32
|
+
id: this.nextId++,
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
level,
|
|
35
|
+
source,
|
|
36
|
+
message,
|
|
37
|
+
data,
|
|
38
|
+
};
|
|
39
|
+
this.entries.push(entry);
|
|
40
|
+
if (this.entries.length > this.maxEntries) {
|
|
41
|
+
this.entries = this.entries.slice(-Math.floor(this.maxEntries * 0.8));
|
|
42
|
+
}
|
|
43
|
+
for (const listener of this.listeners) {
|
|
44
|
+
try { listener(entry); } catch { /* ignore */ }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Subscribe to new log entries. Returns unsubscribe function. */
|
|
49
|
+
subscribe(listener: LogListener): () => void {
|
|
50
|
+
this.listeners.add(listener);
|
|
51
|
+
return () => { this.listeners.delete(listener); };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Get all entries, optionally filtered */
|
|
55
|
+
getEntries(filter?: { level?: LogLevel; source?: string }): LogEntry[] {
|
|
56
|
+
if (!filter) return this.entries;
|
|
57
|
+
return this.entries.filter((e) => {
|
|
58
|
+
if (filter.level && e.level !== filter.level) return false;
|
|
59
|
+
if (filter.source && e.source !== filter.source) return false;
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Get unique source names seen so far */
|
|
65
|
+
getSources(): string[] {
|
|
66
|
+
const sources = new Set<string>();
|
|
67
|
+
for (const entry of this.entries) sources.add(entry.source);
|
|
68
|
+
return [...sources].sort();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Clear all entries */
|
|
72
|
+
clear() {
|
|
73
|
+
this.entries = [];
|
|
74
|
+
this.nextId = 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Export entries as a formatted text string */
|
|
78
|
+
exportAsText(filter?: { level?: LogLevel; source?: string }): string {
|
|
79
|
+
const entries = this.getEntries(filter);
|
|
80
|
+
const lines: string[] = [
|
|
81
|
+
`Gloomberb Debug Log — exported ${new Date().toISOString()}`,
|
|
82
|
+
`Total entries: ${entries.length}`,
|
|
83
|
+
"=".repeat(80),
|
|
84
|
+
"",
|
|
85
|
+
];
|
|
86
|
+
for (const e of entries) {
|
|
87
|
+
const ts = new Date(e.timestamp).toISOString().slice(11, 23);
|
|
88
|
+
const lvl = e.level.toUpperCase().padEnd(5);
|
|
89
|
+
let dataStr = "";
|
|
90
|
+
if (e.data !== undefined) {
|
|
91
|
+
const serialized = JSON.stringify(e.data);
|
|
92
|
+
if (serialized.length <= 500) dataStr = ` ${serialized}`;
|
|
93
|
+
}
|
|
94
|
+
lines.push(`[${ts}] ${lvl} [${e.source}] ${e.message}${dataStr}`);
|
|
95
|
+
}
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Intercept console methods and route them through the debug log */
|
|
100
|
+
interceptConsole() {
|
|
101
|
+
const orig = {
|
|
102
|
+
log: console.log.bind(console),
|
|
103
|
+
warn: console.warn.bind(console),
|
|
104
|
+
error: console.error.bind(console),
|
|
105
|
+
info: console.info.bind(console),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const format = (args: unknown[]): string =>
|
|
109
|
+
args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" ");
|
|
110
|
+
|
|
111
|
+
console.log = (...args: unknown[]) => {
|
|
112
|
+
this.add("debug", "console", format(args));
|
|
113
|
+
orig.log(...args);
|
|
114
|
+
};
|
|
115
|
+
console.info = (...args: unknown[]) => {
|
|
116
|
+
this.add("info", "console", format(args));
|
|
117
|
+
orig.info(...args);
|
|
118
|
+
};
|
|
119
|
+
console.warn = (...args: unknown[]) => {
|
|
120
|
+
this.add("warn", "console", format(args));
|
|
121
|
+
orig.warn(...args);
|
|
122
|
+
};
|
|
123
|
+
console.error = (...args: unknown[]) => {
|
|
124
|
+
this.add("error", "console", format(args));
|
|
125
|
+
orig.error(...args);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Singleton debug log instance */
|
|
131
|
+
export const debugLog = new DebugLog();
|
|
132
|
+
|
|
133
|
+
export type PluginLogger = ReturnType<DebugLog["createLogger"]>;
|