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.
Files changed (233) hide show
  1. package/README.md +24 -2
  2. package/bin/gloomberb +1 -1
  3. package/package.json +1 -1
  4. package/src/app/runtime/desktop-menu.ts +5 -1
  5. package/src/app/runtime/ticker-refresh.ts +4 -1
  6. package/src/app.tsx +20 -8
  7. package/src/brokers/account-cache.ts +1 -1
  8. package/src/brokers/sync-broker-instance.test.ts +107 -0
  9. package/src/brokers/sync-broker-instance.ts +22 -10
  10. package/src/capabilities/registry.ts +1 -0
  11. package/src/capabilities/types.ts +23 -0
  12. package/src/cli/commands/api.ts +119 -0
  13. package/src/cli/commands/automation.ts +173 -0
  14. package/src/cli/commands/command-utils.ts +53 -0
  15. package/src/cli/commands/market.ts +527 -0
  16. package/src/cli/commands/overview.ts +222 -0
  17. package/src/cli/commands/search.ts +39 -0
  18. package/src/cli/commands/system.ts +553 -0
  19. package/src/cli/commands/ticker.ts +104 -12
  20. package/src/cli/context.ts +35 -2
  21. package/src/cli/desktop-pane-shot.ts +2 -1
  22. package/src/cli/entry.ts +51 -0
  23. package/src/cli/errors.ts +56 -5
  24. package/src/cli/index.ts +73 -11
  25. package/src/cli/options.test.ts +38 -0
  26. package/src/cli/options.ts +95 -0
  27. package/src/cli/pane-functions/index.ts +10 -1
  28. package/src/cli/pane-functions/screenshot.ts +13 -1
  29. package/src/cli/registry.test.ts +40 -0
  30. package/src/cli/registry.ts +46 -5
  31. package/src/cli/result.test.ts +82 -0
  32. package/src/cli/result.ts +149 -0
  33. package/src/cli/types.ts +11 -0
  34. package/src/cli.test.ts +11 -11
  35. package/src/components/chart/bar-chart-renderer.test.ts +108 -3
  36. package/src/components/chart/bar-chart-renderer.ts +226 -27
  37. package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
  38. package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
  39. package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
  40. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
  41. package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
  42. package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
  43. package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
  44. package/src/components/chart/core/pane-settings.test.ts +27 -0
  45. package/src/components/chart/core/pane-settings.ts +60 -1
  46. package/src/components/chart/core/pointer.ts +34 -6
  47. package/src/components/chart/price-axis-labels.tsx +3 -1
  48. package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
  49. package/src/components/chart/static/bar-chart-surface.tsx +151 -28
  50. package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
  51. package/src/components/chart/static/chart/surface.test.tsx +92 -0
  52. package/src/components/chart/static/chart/surface.tsx +167 -24
  53. package/src/components/chart/stock-chart/index.test.ts +25 -0
  54. package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
  55. package/src/components/chart/stock-chart/settings.ts +7 -3
  56. package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
  57. package/src/components/command-bar/layout-items/current-layout.ts +5 -1
  58. package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
  59. package/src/components/command-bar/surface/pane.test.tsx +76 -0
  60. package/src/components/data-table/view.tsx +13 -2
  61. package/src/components/feed-data-table/stack-view.tsx +9 -1
  62. package/src/components/index.ts +1 -1
  63. package/src/components/input-search-bar.test.tsx +103 -0
  64. package/src/components/input-search-bar.tsx +16 -0
  65. package/src/components/layout/detached-pane-shell.tsx +6 -3
  66. package/src/components/layout/floating-pane.tsx +3 -2
  67. package/src/components/layout/pane/content.tsx +20 -8
  68. package/src/components/layout/pane/footer/registration.tsx +6 -2
  69. package/src/components/layout/pane/frame.tsx +4 -4
  70. package/src/components/layout/pane/header.tsx +40 -4
  71. package/src/components/layout/pane/index.tsx +3 -1
  72. package/src/components/layout/pane/sizing.test.ts +43 -0
  73. package/src/components/layout/pane/sizing.ts +13 -4
  74. package/src/components/layout/shell/active-drag.ts +5 -1
  75. package/src/components/layout/shell/cursor-occlusion.ts +196 -0
  76. package/src/components/layout/shell/drag/runtime.ts +28 -3
  77. package/src/components/layout/shell/fullscreen.ts +10 -0
  78. package/src/components/layout/shell/index.test.tsx +487 -4
  79. package/src/components/layout/shell/index.tsx +165 -4
  80. package/src/components/layout/shell/layout-state.test.ts +33 -0
  81. package/src/components/layout/shell/layout-state.ts +4 -9
  82. package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
  83. package/src/components/layout/shell/pane/layers.tsx +34 -16
  84. package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
  85. package/src/components/layout/shell/shortcuts.ts +4 -0
  86. package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
  87. package/src/components/layout/shell/visible-layout.ts +17 -0
  88. package/src/components/layout/shell/window-mode/index.ts +11 -3
  89. package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
  90. package/src/components/layout/status-bar.test.tsx +111 -1
  91. package/src/components/layout/status-bar.tsx +37 -7
  92. package/src/components/layout/transient-layout.tsx +57 -0
  93. package/src/components/layout/window-edit/mode.test.ts +76 -5
  94. package/src/components/layout/window-edit/mode.ts +47 -2
  95. package/src/components/layout/window-edit/presentation.ts +2 -1
  96. package/src/components/onboarding/onboarding-steps.tsx +8 -28
  97. package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
  98. package/src/components/onboarding/onboarding-wizard.tsx +2 -4
  99. package/src/components/onboarding/portfolio-step/index.tsx +0 -12
  100. package/src/components/onboarding/portfolio-step/types.ts +1 -3
  101. package/src/components/onboarding/wizard-keyboard.ts +2 -5
  102. package/src/components/price-performance.tsx +9 -1
  103. package/src/components/table-view-shared.tsx +12 -4
  104. package/src/components/ui/data-table/opentui/index.tsx +25 -12
  105. package/src/components/ui/data-table/types.ts +4 -0
  106. package/src/components/ui/fields.tsx +10 -3
  107. package/src/components/ui/page-stack-view.tsx +6 -2
  108. package/src/components/ui/table-layout.test.ts +25 -0
  109. package/src/components/ui/table-layout.ts +22 -6
  110. package/src/components/ui/tabs.tsx +26 -10
  111. package/src/components/ui/ui.test.tsx +87 -0
  112. package/src/core/app-services.ts +7 -4
  113. package/src/core/state/app/layout-reducer.ts +2 -2
  114. package/src/data/config/store/index.ts +1 -2
  115. package/src/plugins/builtin/analytics/index.test.tsx +6 -2
  116. package/src/plugins/builtin/analytics/pane-model.ts +43 -8
  117. package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
  118. package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
  119. package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
  120. package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
  121. package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
  122. package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
  123. package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
  124. package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
  125. package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
  126. package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
  127. package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
  128. package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
  129. package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
  130. package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
  131. package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
  132. package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
  133. package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
  134. package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
  135. package/src/plugins/builtin/layout-manager/index.tsx +5 -1
  136. package/src/plugins/builtin/news/wire/read-state.ts +9 -32
  137. package/src/plugins/builtin/options/view.test.tsx +141 -4
  138. package/src/plugins/builtin/options/view.tsx +76 -15
  139. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
  140. package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
  141. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
  142. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
  143. package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
  144. package/src/plugins/builtin/portfolio-list/pane/index.tsx +3 -1
  145. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
  146. package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
  147. package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
  148. package/src/plugins/builtin/shared/read-state.ts +43 -0
  149. package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
  150. package/src/plugins/builtin/substack/article-detail.tsx +39 -15
  151. package/src/plugins/builtin/substack/article-stack.tsx +6 -2
  152. package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
  153. package/src/plugins/builtin/substack/pane.tsx +8 -3
  154. package/src/plugins/builtin/substack/read-state.ts +55 -0
  155. package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
  156. package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
  157. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
  158. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
  159. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
  160. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
  161. package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
  162. package/src/plugins/catalog-ui.ts +2 -0
  163. package/src/plugins/ibkr/broker-adapter.ts +19 -0
  164. package/src/plugins/ibkr/config.test.ts +31 -2
  165. package/src/plugins/ibkr/config.ts +9 -2
  166. package/src/plugins/ibkr/flex/index.test.ts +18 -9
  167. package/src/plugins/ibkr/flex/index.ts +30 -0
  168. package/src/plugins/ibkr/flex/statement-client.ts +68 -14
  169. package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
  170. package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
  171. package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
  172. package/src/plugins/pane-manager/docking.ts +9 -2
  173. package/src/plugins/pane-manager/gridlock.ts +13 -5
  174. package/src/plugins/pane-manager/layout-state.ts +25 -0
  175. package/src/plugins/pane-manager/queries.ts +8 -2
  176. package/src/plugins/pane-manager.test.ts +33 -0
  177. package/src/plugins/pane-manager.ts +6 -1
  178. package/src/plugins/prediction-markets/chart.tsx +10 -4
  179. package/src/plugins/prediction-markets/controller/index.ts +2 -0
  180. package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
  181. package/src/plugins/prediction-markets/detail/book.tsx +141 -96
  182. package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
  183. package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
  184. package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
  185. package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
  186. package/src/plugins/prediction-markets/detail.test.tsx +50 -0
  187. package/src/plugins/prediction-markets/pane.test.tsx +75 -0
  188. package/src/plugins/prediction-markets/pane.tsx +71 -23
  189. package/src/plugins/prediction-markets/test-helpers.tsx +42 -23
  190. package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
  191. package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
  192. package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
  193. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  194. package/src/renderers/electrobun/bun/index.ts +13 -0
  195. package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
  196. package/src/renderers/electrobun/bun/window/focus.ts +5 -0
  197. package/src/renderers/electrobun/view/app-services.ts +6 -4
  198. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
  199. package/src/renderers/electrobun/view/data-table/index.tsx +19 -4
  200. package/src/renderers/electrobun/view/data-table/row.tsx +30 -6
  201. package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
  202. package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
  203. package/src/renderers/electrobun/view/host/input.tsx +115 -18
  204. package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
  205. package/src/renderers/electrobun/view/host/text.test.ts +22 -0
  206. package/src/renderers/electrobun/view/host/text.tsx +24 -20
  207. package/src/renderers/electrobun/view/styles.css +11 -0
  208. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  209. package/src/renderers/opentui/host.tsx +17 -1
  210. package/src/renderers/opentui/start.tsx +13 -5
  211. package/src/renderers/opentui/ui-host.tsx +57 -25
  212. package/src/sources/provider-router/batches.ts +16 -2
  213. package/src/sources/provider-router/financial-routes.ts +5 -0
  214. package/src/sources/provider-router/financials.ts +11 -23
  215. package/src/sources/provider-router/index.test.ts +189 -0
  216. package/src/sources/provider-router/primary.ts +3 -2
  217. package/src/sources/sec-edgar.test.ts +83 -0
  218. package/src/sources/sec-edgar.ts +231 -0
  219. package/src/sources/yahoo-finance.test.ts +51 -0
  220. package/src/sources/yahoo-finance.ts +49 -1
  221. package/src/state/app/context/index.test.tsx +5 -8
  222. package/src/state/app/input-capture.ts +12 -0
  223. package/src/types/broker.ts +1 -0
  224. package/src/types/config.ts +10 -0
  225. package/src/types/plugin.ts +28 -0
  226. package/src/types/trading.ts +2 -0
  227. package/src/ui/host.tsx +16 -0
  228. package/src/ui/index.tsx +2 -0
  229. package/src/utils/cli-output.ts +6 -0
  230. package/src/utils/financial-statements.ts +23 -0
  231. package/src/utils/search-focus-navigation.ts +24 -0
  232. package/src/version.ts +1 -1
  233. 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.dockRoot && noUndoOnFreshLayout.config.layout.dockRoot.kind === "split"
154
- ? noUndoOnFreshLayout.config.layout.dockRoot.ratio
155
- : null).toBe(defaultRatio);
153
+ expect(noUndoOnFreshLayout.config.layout).toEqual(createBlankLayout());
154
+ expect(noUndoOnFreshLayout.focusedPaneId).toBeNull();
156
155
 
157
- const secondLayout = cloneLayout(noUndoOnFreshLayout.config.layout);
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.dockRoot && secondUndone.config.layout.dockRoot.kind === "split"
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
+ }
@@ -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>;
@@ -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
  }
@@ -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;
@@ -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
@@ -28,7 +28,9 @@ export type {
28
28
  Highlight,
29
29
  ImageSurfaceProps,
30
30
  InputRenderable,
31
+ NativeCursorState,
31
32
  NativeRendererHost,
33
+ NativePostProcessFn,
32
34
  PixelResolution,
33
35
  RendererHost,
34
36
  ScrollBoxRenderable,
@@ -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.2";
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
- }