gloomberb 0.9.2 → 0.9.4
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 +24 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app/runtime/desktop-menu.ts +5 -1
- package/src/app/runtime/ticker-refresh.ts +4 -1
- package/src/app.tsx +20 -8
- package/src/brokers/account-cache.ts +1 -1
- package/src/brokers/sync-broker-instance.test.ts +107 -0
- package/src/brokers/sync-broker-instance.ts +22 -10
- package/src/capabilities/registry.ts +1 -0
- package/src/capabilities/types.ts +23 -0
- package/src/cli/commands/api.ts +119 -0
- package/src/cli/commands/automation.ts +173 -0
- package/src/cli/commands/command-utils.ts +53 -0
- package/src/cli/commands/market.ts +527 -0
- package/src/cli/commands/overview.ts +222 -0
- package/src/cli/commands/search.ts +39 -0
- package/src/cli/commands/system.ts +553 -0
- package/src/cli/commands/ticker.ts +104 -12
- package/src/cli/context.ts +35 -2
- package/src/cli/desktop-pane-shot.ts +2 -1
- package/src/cli/entry.ts +51 -0
- package/src/cli/errors.ts +56 -5
- package/src/cli/index.ts +73 -11
- package/src/cli/options.test.ts +38 -0
- package/src/cli/options.ts +95 -0
- package/src/cli/pane-functions/index.ts +10 -1
- package/src/cli/pane-functions/screenshot.ts +13 -1
- package/src/cli/registry.test.ts +40 -0
- package/src/cli/registry.ts +46 -5
- package/src/cli/result.test.ts +82 -0
- package/src/cli/result.ts +149 -0
- package/src/cli/types.ts +11 -0
- package/src/cli.test.ts +11 -11
- package/src/components/chart/bar-chart-renderer.test.ts +108 -3
- package/src/components/chart/bar-chart-renderer.ts +226 -27
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
- package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
- package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
- package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
- package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
- package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
- package/src/components/chart/core/pane-settings.test.ts +27 -0
- package/src/components/chart/core/pane-settings.ts +60 -1
- package/src/components/chart/core/pointer.ts +34 -6
- package/src/components/chart/price-axis-labels.tsx +3 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
- package/src/components/chart/static/bar-chart-surface.tsx +151 -28
- package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
- package/src/components/chart/static/chart/surface.test.tsx +92 -0
- package/src/components/chart/static/chart/surface.tsx +167 -24
- package/src/components/chart/stock-chart/index.test.ts +25 -0
- package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
- package/src/components/chart/stock-chart/settings.ts +7 -3
- package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
- package/src/components/command-bar/layout-items/current-layout.ts +5 -1
- package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
- package/src/components/command-bar/surface/pane.test.tsx +76 -0
- package/src/components/data-table/view.tsx +13 -2
- package/src/components/feed-data-table/stack-view.tsx +9 -1
- package/src/components/index.ts +1 -1
- package/src/components/input-search-bar.test.tsx +103 -0
- package/src/components/input-search-bar.tsx +16 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +3 -2
- package/src/components/layout/pane/content.tsx +20 -8
- package/src/components/layout/pane/footer/registration.tsx +6 -2
- package/src/components/layout/pane/frame.tsx +4 -4
- package/src/components/layout/pane/header.tsx +40 -4
- package/src/components/layout/pane/index.tsx +3 -1
- package/src/components/layout/pane/sizing.test.ts +43 -0
- package/src/components/layout/pane/sizing.ts +13 -4
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/cursor-occlusion.ts +196 -0
- package/src/components/layout/shell/drag/runtime.ts +28 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +487 -4
- package/src/components/layout/shell/index.tsx +165 -4
- package/src/components/layout/shell/layout-state.test.ts +33 -0
- package/src/components/layout/shell/layout-state.ts +4 -9
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +34 -16
- package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
- package/src/components/layout/shell/shortcuts.ts +4 -0
- package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
- package/src/components/layout/shell/visible-layout.ts +17 -0
- package/src/components/layout/shell/window-mode/index.ts +11 -3
- package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
- package/src/components/layout/status-bar.test.tsx +111 -1
- package/src/components/layout/status-bar.tsx +37 -7
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-edit/mode.test.ts +76 -5
- package/src/components/layout/window-edit/mode.ts +47 -2
- package/src/components/layout/window-edit/presentation.ts +2 -1
- package/src/components/onboarding/onboarding-steps.tsx +8 -28
- package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
- package/src/components/onboarding/onboarding-wizard.tsx +2 -4
- package/src/components/onboarding/portfolio-step/index.tsx +0 -12
- package/src/components/onboarding/portfolio-step/types.ts +1 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -5
- package/src/components/price-performance.tsx +9 -1
- package/src/components/table-view-shared.tsx +12 -4
- package/src/components/ui/data-table/opentui/index.tsx +25 -12
- package/src/components/ui/data-table/types.ts +4 -0
- package/src/components/ui/fields.tsx +10 -3
- package/src/components/ui/page-stack-view.tsx +6 -2
- package/src/components/ui/table-layout.test.ts +25 -0
- package/src/components/ui/table-layout.ts +22 -6
- package/src/components/ui/tabs.tsx +26 -10
- package/src/components/ui/ui.test.tsx +87 -0
- package/src/core/app-services.ts +7 -4
- package/src/core/state/app/layout-reducer.ts +2 -2
- package/src/data/config/store/index.ts +1 -2
- package/src/plugins/builtin/analytics/index.test.tsx +6 -2
- package/src/plugins/builtin/analytics/pane-model.ts +43 -8
- package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
- package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
- package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
- package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
- package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
- package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
- package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
- package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
- package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
- package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
- package/src/plugins/builtin/layout-manager/index.tsx +5 -1
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- package/src/plugins/builtin/options/view.test.tsx +141 -4
- package/src/plugins/builtin/options/view.tsx +76 -15
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
- package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +3 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
- package/src/plugins/builtin/shared/read-state.ts +43 -0
- package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
- package/src/plugins/builtin/substack/article-detail.tsx +39 -15
- package/src/plugins/builtin/substack/article-stack.tsx +6 -2
- package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
- package/src/plugins/builtin/substack/pane.tsx +8 -3
- package/src/plugins/builtin/substack/read-state.ts +55 -0
- package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/ibkr/broker-adapter.ts +19 -0
- package/src/plugins/ibkr/config.test.ts +31 -2
- package/src/plugins/ibkr/config.ts +9 -2
- package/src/plugins/ibkr/flex/index.test.ts +18 -9
- package/src/plugins/ibkr/flex/index.ts +30 -0
- package/src/plugins/ibkr/flex/statement-client.ts +68 -14
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/gridlock.ts +13 -5
- package/src/plugins/pane-manager/layout-state.ts +25 -0
- package/src/plugins/pane-manager/queries.ts +8 -2
- package/src/plugins/pane-manager.test.ts +33 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +10 -4
- package/src/plugins/prediction-markets/controller/index.ts +2 -0
- package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
- package/src/plugins/prediction-markets/detail/book.tsx +141 -96
- package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
- package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
- package/src/plugins/prediction-markets/detail.test.tsx +50 -0
- package/src/plugins/prediction-markets/pane.test.tsx +75 -0
- package/src/plugins/prediction-markets/pane.tsx +71 -23
- package/src/plugins/prediction-markets/test-helpers.tsx +42 -23
- package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/index.ts +13 -0
- package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
- package/src/renderers/electrobun/bun/window/focus.ts +5 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
- package/src/renderers/electrobun/view/data-table/index.tsx +19 -4
- package/src/renderers/electrobun/view/data-table/row.tsx +30 -6
- package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- package/src/renderers/electrobun/view/host/input.tsx +115 -18
- package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
- package/src/renderers/electrobun/view/host/text.test.ts +22 -0
- package/src/renderers/electrobun/view/host/text.tsx +24 -20
- package/src/renderers/electrobun/view/styles.css +11 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1 -1
- package/src/renderers/opentui/host.tsx +17 -1
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/renderers/opentui/ui-host.tsx +57 -25
- package/src/sources/provider-router/batches.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +5 -0
- package/src/sources/provider-router/financials.ts +11 -23
- package/src/sources/provider-router/index.test.ts +189 -0
- package/src/sources/provider-router/primary.ts +3 -2
- package/src/sources/sec-edgar.test.ts +83 -0
- package/src/sources/sec-edgar.ts +231 -0
- package/src/sources/yahoo-finance.test.ts +51 -0
- package/src/sources/yahoo-finance.ts +49 -1
- package/src/state/app/context/index.test.tsx +5 -8
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/broker.ts +1 -0
- package/src/types/config.ts +10 -0
- package/src/types/plugin.ts +28 -0
- package/src/types/trading.ts +2 -0
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/financial-statements.ts +23 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
|
@@ -17,6 +17,7 @@ describe("YahooFinanceClient exchange aliases", () => {
|
|
|
17
17
|
provider.fetchAssetProfile = async () => undefined;
|
|
18
18
|
provider.fetchQuoteSupplement = async () => ({});
|
|
19
19
|
provider.fetchExtendedHoursData = async () => ({});
|
|
20
|
+
provider.secClient.getFinancialStatements = async () => null;
|
|
20
21
|
provider.fetchTimeseries = async () => [
|
|
21
22
|
point("annualAccountsReceivable", 7_450_000_000),
|
|
22
23
|
point("annualInventory", 4_880_000_000),
|
|
@@ -52,6 +53,56 @@ describe("YahooFinanceClient exchange aliases", () => {
|
|
|
52
53
|
});
|
|
53
54
|
});
|
|
54
55
|
|
|
56
|
+
test("supplements US Yahoo financials with deeper SEC statement history", async () => {
|
|
57
|
+
const provider = new YahooFinanceClient() as any;
|
|
58
|
+
const point = (type: string, date: string, value: number) => ({
|
|
59
|
+
meta: { type: [type] },
|
|
60
|
+
[type]: [{ asOfDate: date, reportedValue: { raw: value } }],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
provider.fetchChart = async () => ({
|
|
64
|
+
meta: { currency: "USD", regularMarketPrice: 100, shortName: "AMD" },
|
|
65
|
+
history: [{ date: new Date("2025-12-31T00:00:00Z"), close: 100 }],
|
|
66
|
+
});
|
|
67
|
+
provider.fetchAssetProfile = async () => undefined;
|
|
68
|
+
provider.fetchQuoteSupplement = async () => ({});
|
|
69
|
+
provider.fetchExtendedHoursData = async () => ({});
|
|
70
|
+
provider.fetchTimeseries = async () => [
|
|
71
|
+
point("annualTotalRevenue", "2025-12-31", 200),
|
|
72
|
+
point("quarterlyTotalRevenue", "2025-12-31", 60),
|
|
73
|
+
];
|
|
74
|
+
provider.secClient.getFinancialStatements = async (ticker: string) => {
|
|
75
|
+
expect(ticker).toBe("AMD");
|
|
76
|
+
return {
|
|
77
|
+
annualStatements: [
|
|
78
|
+
{ date: "2023-12-31", totalRevenue: 100 },
|
|
79
|
+
{ date: "2024-12-31", totalRevenue: 150 },
|
|
80
|
+
{ date: "2025-12-31", totalRevenue: 190, netIncome: 40 },
|
|
81
|
+
],
|
|
82
|
+
quarterlyStatements: [
|
|
83
|
+
{ date: "2025-03-31", totalRevenue: 45 },
|
|
84
|
+
{ date: "2025-06-30", totalRevenue: 50 },
|
|
85
|
+
{ date: "2025-09-30", totalRevenue: 55 },
|
|
86
|
+
{ date: "2025-12-31", totalRevenue: 58, netIncome: 12 },
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const financials = await provider.getTickerFinancials("AMD", "NASDAQ");
|
|
92
|
+
|
|
93
|
+
expect(financials.annualStatements.map((row: any) => [row.date, row.totalRevenue, row.netIncome])).toEqual([
|
|
94
|
+
["2023-12-31", 100, undefined],
|
|
95
|
+
["2024-12-31", 150, undefined],
|
|
96
|
+
["2025-12-31", 200, 40],
|
|
97
|
+
]);
|
|
98
|
+
expect(financials.quarterlyStatements.map((row: any) => [row.date, row.totalRevenue, row.netIncome])).toEqual([
|
|
99
|
+
["2025-03-31", 45, undefined],
|
|
100
|
+
["2025-06-30", 50, undefined],
|
|
101
|
+
["2025-09-30", 55, undefined],
|
|
102
|
+
["2025-12-31", 60, 12],
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
55
106
|
test("tries the Taipei Exchange suffix for TPEX tickers", () => {
|
|
56
107
|
expect(getYahooSymbolsToTry("3105", "TPEX")).toEqual(["3105.TWO", "3105.TW"]);
|
|
57
108
|
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import type { InstrumentSearchResult } from "../types/instrument";
|
|
9
9
|
import { parseOptionSymbol } from "../utils/options";
|
|
10
10
|
import { SecEdgarClient } from "./sec-edgar";
|
|
11
|
+
import { mergeFinancialStatementRows } from "../utils/financial-statements";
|
|
11
12
|
import { YahooHttpClient } from "./yahoo-finance/http";
|
|
12
13
|
import {
|
|
13
14
|
normalizeSubUnitCurrency,
|
|
@@ -42,6 +43,21 @@ import {
|
|
|
42
43
|
loadYahooTickerFinancials,
|
|
43
44
|
} from "./yahoo-finance/snapshots";
|
|
44
45
|
|
|
46
|
+
const SEC_STATEMENT_SUPPLEMENT_EXCHANGES = new Set([
|
|
47
|
+
"",
|
|
48
|
+
"AMEX",
|
|
49
|
+
"ARCA",
|
|
50
|
+
"BATS",
|
|
51
|
+
"BYX",
|
|
52
|
+
"IEX",
|
|
53
|
+
"NASDAQ",
|
|
54
|
+
"NMS",
|
|
55
|
+
"NYSE",
|
|
56
|
+
"NYSEARCA",
|
|
57
|
+
"OTC",
|
|
58
|
+
"PINK",
|
|
59
|
+
]);
|
|
60
|
+
|
|
45
61
|
export class YahooFinanceClient implements DataProvider {
|
|
46
62
|
readonly id = "yahoo";
|
|
47
63
|
readonly name = "Yahoo Finance";
|
|
@@ -50,6 +66,38 @@ export class YahooFinanceClient implements DataProvider {
|
|
|
50
66
|
|
|
51
67
|
constructor(private readonly http = new YahooHttpClient()) {}
|
|
52
68
|
|
|
69
|
+
private shouldSupplementSecStatements(ticker: string, exchange: string, financials: TickerFinancials): boolean {
|
|
70
|
+
if (!/^[A-Z0-9.-]+$/i.test(ticker.trim())) return false;
|
|
71
|
+
if (ticker.includes(".")) return false;
|
|
72
|
+
const normalizedExchange = exchange.trim().toUpperCase();
|
|
73
|
+
return SEC_STATEMENT_SUPPLEMENT_EXCHANGES.has(normalizedExchange)
|
|
74
|
+
&& (financials.quote?.currency ?? "USD").toUpperCase() === "USD";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async supplementSecStatements(
|
|
78
|
+
ticker: string,
|
|
79
|
+
exchange: string,
|
|
80
|
+
financials: TickerFinancials,
|
|
81
|
+
): Promise<TickerFinancials> {
|
|
82
|
+
if (!this.shouldSupplementSecStatements(ticker, exchange, financials)) return financials;
|
|
83
|
+
try {
|
|
84
|
+
const secStatements = await this.secClient.getFinancialStatements(ticker);
|
|
85
|
+
if (
|
|
86
|
+
!secStatements
|
|
87
|
+
|| (secStatements.annualStatements.length === 0 && secStatements.quarterlyStatements.length === 0)
|
|
88
|
+
) {
|
|
89
|
+
return financials;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
...financials,
|
|
93
|
+
annualStatements: mergeFinancialStatementRows(financials.annualStatements, secStatements.annualStatements),
|
|
94
|
+
quarterlyStatements: mergeFinancialStatementRows(financials.quarterlyStatements, secStatements.quarterlyStatements),
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return financials;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
53
101
|
private async fetchChart(symbol: string, range: string, interval = "1d", includePrePost = false) {
|
|
54
102
|
return fetchYahooChart(this.http, symbol, range, interval, includePrePost);
|
|
55
103
|
}
|
|
@@ -90,7 +138,7 @@ export class YahooFinanceClient implements DataProvider {
|
|
|
90
138
|
fetchTimeseries: (targetSymbol, types, period1) => this.fetchTimeseries(targetSymbol, types, period1),
|
|
91
139
|
providerId: this.id,
|
|
92
140
|
});
|
|
93
|
-
return result;
|
|
141
|
+
return await this.supplementSecStatements(ticker, exchange, result);
|
|
94
142
|
} catch (err) {
|
|
95
143
|
lastError = err;
|
|
96
144
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { cloneLayout, createDefaultConfig } from "../../../types/config";
|
|
2
|
+
import { cloneLayout, createBlankLayout, createDefaultConfig } from "../../../types/config";
|
|
3
3
|
import { appReducer, createInitialState } from "./index";
|
|
4
4
|
|
|
5
5
|
describe("appReducer command bar state", () => {
|
|
@@ -150,11 +150,10 @@ describe("appReducer command bar state", () => {
|
|
|
150
150
|
|
|
151
151
|
const noUndoOnFreshLayout = appReducer(state, { type: "UNDO_LAYOUT" });
|
|
152
152
|
expect(noUndoOnFreshLayout.config.activeLayoutIndex).toBe(newLayoutIndex);
|
|
153
|
-
expect(noUndoOnFreshLayout.config.layout.
|
|
154
|
-
|
|
155
|
-
: null).toBe(defaultRatio);
|
|
153
|
+
expect(noUndoOnFreshLayout.config.layout).toEqual(createBlankLayout());
|
|
154
|
+
expect(noUndoOnFreshLayout.focusedPaneId).toBeNull();
|
|
156
155
|
|
|
157
|
-
const secondLayout = cloneLayout(
|
|
156
|
+
const secondLayout = cloneLayout(initial.config.layout);
|
|
158
157
|
if (!secondLayout.dockRoot || secondLayout.dockRoot.kind !== "split") {
|
|
159
158
|
throw new Error("expected split dock root");
|
|
160
159
|
}
|
|
@@ -172,9 +171,7 @@ describe("appReducer command bar state", () => {
|
|
|
172
171
|
const backToSecond = appReducer(firstUndone, { type: "SWITCH_LAYOUT", index: newLayoutIndex });
|
|
173
172
|
const secondUndone = appReducer(backToSecond, { type: "UNDO_LAYOUT" });
|
|
174
173
|
expect(secondUndone.config.activeLayoutIndex).toBe(newLayoutIndex);
|
|
175
|
-
expect(secondUndone.config.layout.
|
|
176
|
-
? secondUndone.config.layout.dockRoot.ratio
|
|
177
|
-
: null).toBe(defaultRatio);
|
|
174
|
+
expect(secondUndone.config.layout).toEqual(createBlankLayout());
|
|
178
175
|
});
|
|
179
176
|
|
|
180
177
|
test("tracks manual update-check feedback", () => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useAppDispatch } from "./context";
|
|
3
|
+
|
|
4
|
+
export function useAppInputCapture(captured: boolean) {
|
|
5
|
+
const dispatch = useAppDispatch();
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!captured) return;
|
|
9
|
+
dispatch({ type: "SET_INPUT_CAPTURED", captured: true });
|
|
10
|
+
return () => dispatch({ type: "SET_INPUT_CAPTURED", captured: false });
|
|
11
|
+
}, [captured, dispatch]);
|
|
12
|
+
}
|
package/src/types/broker.ts
CHANGED
|
@@ -85,6 +85,7 @@ export interface BrokerAdapter {
|
|
|
85
85
|
readonly cachePolicy?: CachePolicyMap;
|
|
86
86
|
validate(instance: BrokerInstanceConfig): Promise<boolean>;
|
|
87
87
|
importPositions(instance: BrokerInstanceConfig): Promise<BrokerPosition[]>;
|
|
88
|
+
importPortfolioSnapshot?(instance: BrokerInstanceConfig): Promise<{ accounts: BrokerAccount[]; positions: BrokerPosition[] }>;
|
|
88
89
|
configSchema: BrokerConfigField[];
|
|
89
90
|
connect?(instance: BrokerInstanceConfig): Promise<void>;
|
|
90
91
|
disconnect?(instance: BrokerInstanceConfig): Promise<void>;
|
package/src/types/config.ts
CHANGED
|
@@ -309,6 +309,12 @@ const DEFAULT_MONITOR_LAYOUT: LayoutConfig = {
|
|
|
309
309
|
};
|
|
310
310
|
|
|
311
311
|
export const DEFAULT_LAYOUT = DEFAULT_HOME_LAYOUT;
|
|
312
|
+
const BLANK_LAYOUT: LayoutConfig = {
|
|
313
|
+
dockRoot: null,
|
|
314
|
+
instances: [],
|
|
315
|
+
floating: [],
|
|
316
|
+
detached: [],
|
|
317
|
+
};
|
|
312
318
|
|
|
313
319
|
let nextPaneInstanceSeq = 0;
|
|
314
320
|
|
|
@@ -594,6 +600,10 @@ export function cloneLayout(layout: LayoutConfig): LayoutConfig {
|
|
|
594
600
|
};
|
|
595
601
|
}
|
|
596
602
|
|
|
603
|
+
export function createBlankLayout(): LayoutConfig {
|
|
604
|
+
return cloneLayout(BLANK_LAYOUT);
|
|
605
|
+
}
|
|
606
|
+
|
|
597
607
|
export function findPaneInstance(layout: LayoutConfig, instanceId: string): PaneInstanceConfig | undefined {
|
|
598
608
|
return layout.instances.find((instance) => instance.instanceId === instanceId);
|
|
599
609
|
}
|
package/src/types/plugin.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { PluginEvents } from "../plugins/event-bus";
|
|
|
4
4
|
import type { PluginLogger } from "../utils/debug-log";
|
|
5
5
|
import type { BrokerAdapter } from "./broker";
|
|
6
6
|
import type { PluginCapability } from "../capabilities";
|
|
7
|
+
import type { CliGlobalOptions } from "../cli/options";
|
|
8
|
+
import type { CliResult, CliResultRenderOptions } from "../cli/result";
|
|
7
9
|
import type { ContextMenuContext, ContextMenuItem } from "./context-menu";
|
|
8
10
|
import type {
|
|
9
11
|
AppConfig,
|
|
@@ -245,6 +247,8 @@ export type CliDispatchResult =
|
|
|
245
247
|
export interface CliCommandContext {
|
|
246
248
|
initConfigData(): Promise<import("../cli/types").ConfigContext>;
|
|
247
249
|
initMarketData(): Promise<import("../cli/types").MarketContext>;
|
|
250
|
+
initServices(): Promise<import("../cli/types").CliServicesContext>;
|
|
251
|
+
cliOptions: CliGlobalOptions;
|
|
248
252
|
plugins: GloomPlugin[];
|
|
249
253
|
fail(message: string, details?: string): never;
|
|
250
254
|
closeAndFail(
|
|
@@ -259,6 +263,10 @@ export interface CliCommandContext {
|
|
|
259
263
|
renderStat: typeof import("../utils/cli-output").renderStat;
|
|
260
264
|
renderTable: typeof import("../utils/cli-output").renderTable;
|
|
261
265
|
};
|
|
266
|
+
printResult<T, Row extends Record<string, unknown> = Record<string, unknown>>(
|
|
267
|
+
result: CliResult<T>,
|
|
268
|
+
options?: CliResultRenderOptions<T, Row>,
|
|
269
|
+
): void;
|
|
262
270
|
log: PluginLogger;
|
|
263
271
|
}
|
|
264
272
|
|
|
@@ -270,6 +278,25 @@ export interface CliCommandDef {
|
|
|
270
278
|
execute(args: string[], ctx: CliCommandContext): void | CliDispatchResult | Promise<void | CliDispatchResult>;
|
|
271
279
|
}
|
|
272
280
|
|
|
281
|
+
export interface PluginCliCommandDescriptor {
|
|
282
|
+
name: string;
|
|
283
|
+
aliases?: string[];
|
|
284
|
+
summary: string;
|
|
285
|
+
inputShape?: string;
|
|
286
|
+
outputShape?: string;
|
|
287
|
+
examples?: string[];
|
|
288
|
+
sideEffectLevel?: "none" | "local-write" | "network-write" | "external-trade" | "external-side-effect";
|
|
289
|
+
requirements?: string[];
|
|
290
|
+
batch?: boolean;
|
|
291
|
+
formats?: Array<"text" | "json" | "csv" | "ndjson">;
|
|
292
|
+
safety?: string[];
|
|
293
|
+
execute?: CliCommandDef["execute"];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface PluginCliDescriptor {
|
|
297
|
+
commands?: PluginCliCommandDescriptor[];
|
|
298
|
+
}
|
|
299
|
+
|
|
273
300
|
export interface CommandDef {
|
|
274
301
|
id: string;
|
|
275
302
|
label: string;
|
|
@@ -489,6 +516,7 @@ export interface GloomPlugin {
|
|
|
489
516
|
toggleable?: boolean;
|
|
490
517
|
order?: number;
|
|
491
518
|
cliCommands?: CliCommandDef[];
|
|
519
|
+
cli?: PluginCliDescriptor;
|
|
492
520
|
|
|
493
521
|
setup?(ctx: GloomPluginContext): void | Promise<void>;
|
|
494
522
|
dispose?(): void;
|
package/src/types/trading.ts
CHANGED
|
@@ -16,7 +16,9 @@ export interface BrokerAccount {
|
|
|
16
16
|
currency?: string;
|
|
17
17
|
source?: "gateway" | "flex";
|
|
18
18
|
updatedAt?: number;
|
|
19
|
+
asOfDate?: string;
|
|
19
20
|
netLiquidation?: number;
|
|
21
|
+
grossPositionValue?: number;
|
|
20
22
|
totalCashValue?: number;
|
|
21
23
|
settledCash?: number;
|
|
22
24
|
buyingPower?: number;
|
package/src/ui/host.tsx
CHANGED
|
@@ -143,12 +143,22 @@ export interface TextareaRenderable extends InputRenderable {
|
|
|
143
143
|
clearLineHighlights?(lineIdx: number): void;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
export interface NativeCursorState {
|
|
147
|
+
x: number;
|
|
148
|
+
y: number;
|
|
149
|
+
visible: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type NativePostProcessFn = (buffer: unknown, deltaTime: number) => void;
|
|
153
|
+
|
|
146
154
|
export interface NativeRendererHost {
|
|
147
155
|
terminalWidth: number;
|
|
148
156
|
terminalHeight: number;
|
|
149
157
|
resolution: PixelResolution | null;
|
|
150
158
|
capabilities?: unknown;
|
|
151
159
|
isDestroyed?: boolean;
|
|
160
|
+
currentFocusedRenderable?: unknown;
|
|
161
|
+
currentFocusedEditor?: unknown;
|
|
152
162
|
keyInput?: {
|
|
153
163
|
on(event: string, handler: (...args: any[]) => void): void;
|
|
154
164
|
off(event: string, handler: (...args: any[]) => void): void;
|
|
@@ -160,8 +170,13 @@ export interface NativeRendererHost {
|
|
|
160
170
|
registerLifecyclePass(renderable: unknown): void;
|
|
161
171
|
unregisterLifecyclePass(renderable: unknown): void;
|
|
162
172
|
getSelection?(): { getSelectedText(): string } | null;
|
|
173
|
+
getCursorState?(): NativeCursorState;
|
|
174
|
+
setCursorPosition?(x: number, y: number, visible?: boolean): void;
|
|
175
|
+
addPostProcessFn?(processFn: NativePostProcessFn): void;
|
|
176
|
+
removePostProcessFn?(processFn: NativePostProcessFn): void;
|
|
163
177
|
copyToClipboardOSC52?(text: string): boolean;
|
|
164
178
|
write?(data: string | Uint8Array): boolean;
|
|
179
|
+
captureMouseRenderable?(renderable: unknown): void;
|
|
165
180
|
}
|
|
166
181
|
|
|
167
182
|
interface BoxProps {
|
|
@@ -244,6 +259,7 @@ export interface HostTabsProps {
|
|
|
244
259
|
closeMode?: "active" | "always";
|
|
245
260
|
addLabel?: string;
|
|
246
261
|
onAdd?: () => void;
|
|
262
|
+
focused?: boolean;
|
|
247
263
|
palette: HostTabsPalette;
|
|
248
264
|
}
|
|
249
265
|
|
package/src/ui/index.tsx
CHANGED
package/src/utils/cli-output.ts
CHANGED
|
@@ -8,11 +8,17 @@ export interface CliTableColumn {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
11
|
+
let colorEnabledOverride: boolean | null = null;
|
|
11
12
|
|
|
12
13
|
function colorEnabled(): boolean {
|
|
14
|
+
if (colorEnabledOverride != null) return colorEnabledOverride;
|
|
13
15
|
return !!process.stdout.isTTY && process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb";
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
export function setCliColorEnabledOverride(value: boolean | null): void {
|
|
19
|
+
colorEnabledOverride = value;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
function applyAnsi(text: string, code: string): string {
|
|
17
23
|
if (!colorEnabled()) return text;
|
|
18
24
|
return `\x1b[${code}m${text}\x1b[0m`;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FinancialStatement } from "../types/financials";
|
|
2
|
+
|
|
3
|
+
export function mergeFinancialStatementRows(
|
|
4
|
+
primaryRows: FinancialStatement[],
|
|
5
|
+
fallbackRows: FinancialStatement[],
|
|
6
|
+
): FinancialStatement[] {
|
|
7
|
+
if (primaryRows.length === 0) return fallbackRows;
|
|
8
|
+
if (fallbackRows.length === 0) return primaryRows;
|
|
9
|
+
|
|
10
|
+
const fallbackByDate = new Map(fallbackRows.map((row) => [row.date, row]));
|
|
11
|
+
const primaryDates = new Set(primaryRows.map((row) => row.date));
|
|
12
|
+
const mergedRows = primaryRows.map((row) => ({
|
|
13
|
+
...fallbackByDate.get(row.date),
|
|
14
|
+
...row,
|
|
15
|
+
date: row.date,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
for (const row of fallbackRows) {
|
|
19
|
+
if (!primaryDates.has(row.date)) mergedRows.push(row);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return mergedRows.sort((left, right) => left.date.localeCompare(right.date));
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isPlainKeyboardEvent, type KeyboardModifierEventLike } from "./keyboard";
|
|
2
|
+
|
|
3
|
+
export interface SearchFocusNavigationEvent extends KeyboardModifierEventLike {
|
|
4
|
+
sequence?: string;
|
|
5
|
+
preventDefault?: () => void;
|
|
6
|
+
stopPropagation?: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isPlainArrowDown(event: SearchFocusNavigationEvent): boolean {
|
|
10
|
+
if (!isPlainKeyboardEvent(event)) return false;
|
|
11
|
+
const name = event.name?.toLowerCase();
|
|
12
|
+
return name === "down" || event.sequence === "\u001b[B" || event.sequence === "\u001bOB";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isPlainArrowUp(event: SearchFocusNavigationEvent): boolean {
|
|
16
|
+
if (!isPlainKeyboardEvent(event)) return false;
|
|
17
|
+
const name = event.name?.toLowerCase();
|
|
18
|
+
return name === "up" || event.sequence === "\u001b[A" || event.sequence === "\u001bOA";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function stopSearchFocusNavigation(event: SearchFocusNavigationEvent): void {
|
|
22
|
+
event.preventDefault?.();
|
|
23
|
+
event.stopPropagation?.();
|
|
24
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.9.
|
|
1
|
+
export const VERSION = "0.9.4";
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import type { RefObject } from "react";
|
|
2
|
-
import { Box, Text, TextAttributes, type InputRenderable } from "../../../ui";
|
|
3
|
-
import { colors } from "../../../theme/colors";
|
|
4
|
-
import { TextField } from "../../ui";
|
|
5
|
-
|
|
6
|
-
export function ManualPortfolioNamePanel({
|
|
7
|
-
portfolioName,
|
|
8
|
-
onNameChange,
|
|
9
|
-
editing,
|
|
10
|
-
inputRef,
|
|
11
|
-
}: {
|
|
12
|
-
portfolioName: string;
|
|
13
|
-
onNameChange: (name: string) => void;
|
|
14
|
-
editing: boolean;
|
|
15
|
-
inputRef: RefObject<InputRenderable | null>;
|
|
16
|
-
}) {
|
|
17
|
-
return (
|
|
18
|
-
<Box flexDirection="column" paddingX={2}>
|
|
19
|
-
<Box height={1}>
|
|
20
|
-
<Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Name your portfolio"}</Text>
|
|
21
|
-
</Box>
|
|
22
|
-
<Box height={1} />
|
|
23
|
-
<Box height={1}>
|
|
24
|
-
<Text fg={colors.textDim}>{"Create watchlists later."}</Text>
|
|
25
|
-
</Box>
|
|
26
|
-
<Box height={2} />
|
|
27
|
-
<Box height={1}>
|
|
28
|
-
<Text fg={colors.text}>{"Portfolio name:"}</Text>
|
|
29
|
-
</Box>
|
|
30
|
-
<Box height={1}>
|
|
31
|
-
{editing ? (
|
|
32
|
-
<TextField
|
|
33
|
-
inputRef={inputRef}
|
|
34
|
-
value={portfolioName}
|
|
35
|
-
placeholder="Main Portfolio"
|
|
36
|
-
focused
|
|
37
|
-
backgroundColor={colors.panel}
|
|
38
|
-
textColor={colors.text}
|
|
39
|
-
placeholderColor={colors.textDim}
|
|
40
|
-
onChange={onNameChange}
|
|
41
|
-
onSubmit={() => {}}
|
|
42
|
-
/>
|
|
43
|
-
) : (
|
|
44
|
-
<Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
|
|
45
|
-
{`> ${portfolioName}`}
|
|
46
|
-
</Text>
|
|
47
|
-
)}
|
|
48
|
-
</Box>
|
|
49
|
-
<Box height={2} />
|
|
50
|
-
<Box height={1} flexDirection="row">
|
|
51
|
-
<Text fg={colors.textDim}>{"After setup, use the command bar ("}</Text>
|
|
52
|
-
<Text fg={colors.text}>{"Ctrl+P"}</Text>
|
|
53
|
-
<Text fg={colors.textDim}>{") and"}</Text>
|
|
54
|
-
</Box>
|
|
55
|
-
<Box height={1} flexDirection="row">
|
|
56
|
-
<Text fg={colors.textDim}>{"type "}</Text>
|
|
57
|
-
<Text fg={colors.text} attributes={TextAttributes.BOLD}>{"DES AAPL"}</Text>
|
|
58
|
-
<Text fg={colors.textDim}>{" to open security details for a stock or ETF."}</Text>
|
|
59
|
-
</Box>
|
|
60
|
-
</Box>
|
|
61
|
-
);
|
|
62
|
-
}
|