gloomberb 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +23 -2
  2. package/bin/gloomberb +1 -1
  3. package/package.json +1 -1
  4. package/src/app.tsx +10 -7
  5. package/src/capabilities/registry.ts +1 -0
  6. package/src/capabilities/types.ts +23 -0
  7. package/src/cli/commands/api.ts +119 -0
  8. package/src/cli/commands/automation.ts +173 -0
  9. package/src/cli/commands/command-utils.ts +53 -0
  10. package/src/cli/commands/market.ts +527 -0
  11. package/src/cli/commands/overview.ts +222 -0
  12. package/src/cli/commands/search.ts +39 -0
  13. package/src/cli/commands/system.ts +553 -0
  14. package/src/cli/commands/ticker.ts +104 -12
  15. package/src/cli/context.ts +35 -2
  16. package/src/cli/entry.ts +51 -0
  17. package/src/cli/errors.ts +56 -5
  18. package/src/cli/index.ts +73 -11
  19. package/src/cli/options.test.ts +38 -0
  20. package/src/cli/options.ts +95 -0
  21. package/src/cli/pane-functions/index.ts +10 -1
  22. package/src/cli/registry.test.ts +40 -0
  23. package/src/cli/registry.ts +46 -5
  24. package/src/cli/result.test.ts +82 -0
  25. package/src/cli/result.ts +149 -0
  26. package/src/cli/types.ts +11 -0
  27. package/src/cli.test.ts +11 -11
  28. package/src/components/chart/comparison-stock-chart/helpers.ts +0 -32
  29. package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
  30. package/src/components/chart/comparison-stock-chart/layout-metrics.ts +6 -6
  31. package/src/components/chart/comparison-stock-chart/legend.tsx +45 -44
  32. package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
  33. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
  34. package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
  35. package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
  36. package/src/components/chart/comparison-stock-chart/view.tsx +56 -6
  37. package/src/components/chart/core/pane-settings.test.ts +27 -0
  38. package/src/components/chart/core/pane-settings.ts +60 -1
  39. package/src/components/chart/core/pointer.ts +2 -0
  40. package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
  41. package/src/components/chart/stock-chart/settings.ts +7 -3
  42. package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
  43. package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
  44. package/src/components/command-bar/surface/pane.test.tsx +76 -0
  45. package/src/components/data-table/view.tsx +13 -2
  46. package/src/components/feed-data-table/stack-view.tsx +9 -1
  47. package/src/components/index.ts +1 -1
  48. package/src/components/input-search-bar.test.tsx +103 -0
  49. package/src/components/input-search-bar.tsx +16 -0
  50. package/src/components/layout/detached-pane-shell.tsx +5 -2
  51. package/src/components/layout/header.tsx +13 -5
  52. package/src/components/layout/shell/active-drag.ts +5 -1
  53. package/src/components/layout/shell/drag/runtime.ts +21 -3
  54. package/src/components/layout/shell/fullscreen.ts +10 -0
  55. package/src/components/layout/shell/index.test.tsx +395 -2
  56. package/src/components/layout/shell/index.tsx +128 -2
  57. package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
  58. package/src/components/layout/shell/pane/layers.tsx +26 -14
  59. package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
  60. package/src/components/layout/shell/shortcuts.ts +4 -0
  61. package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
  62. package/src/components/layout/shell/window-mode/index.ts +11 -3
  63. package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
  64. package/src/components/layout/status-bar.test.tsx +105 -0
  65. package/src/components/layout/status-bar.tsx +32 -6
  66. package/src/components/layout/transient-layout.tsx +57 -0
  67. package/src/components/layout/window-controls.tsx +19 -10
  68. package/src/components/layout/window-edit/mode.test.ts +76 -5
  69. package/src/components/layout/window-edit/mode.ts +47 -2
  70. package/src/components/layout/window-edit/presentation.ts +2 -1
  71. package/src/components/price-performance.tsx +183 -0
  72. package/src/core/app-services.ts +7 -4
  73. package/src/data/config/store/index.ts +1 -2
  74. package/src/market-data/performance.test.ts +54 -0
  75. package/src/market-data/performance.ts +109 -0
  76. package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
  77. package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
  78. package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
  79. package/src/plugins/builtin/comparison-chart/index.test.tsx +218 -15
  80. package/src/plugins/builtin/congress-trades/detail.tsx +204 -68
  81. package/src/plugins/builtin/congress-trades/footer.ts +14 -8
  82. package/src/plugins/builtin/congress-trades/keyboard.ts +4 -23
  83. package/src/plugins/builtin/congress-trades/model.ts +29 -0
  84. package/src/plugins/builtin/congress-trades/pane.tsx +8 -13
  85. package/src/plugins/builtin/congress-trades/table.tsx +10 -1
  86. package/src/plugins/builtin/news/wire/read-state.ts +9 -32
  87. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
  88. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
  89. package/src/plugins/builtin/portfolio-list/pane/index.tsx +1 -1
  90. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
  91. package/src/plugins/builtin/shared/read-state.ts +43 -0
  92. package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
  93. package/src/plugins/builtin/substack/article-detail.tsx +39 -15
  94. package/src/plugins/builtin/substack/article-stack.tsx +6 -2
  95. package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
  96. package/src/plugins/builtin/substack/pane.tsx +8 -3
  97. package/src/plugins/builtin/substack/read-state.ts +55 -0
  98. package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
  99. package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
  100. package/src/plugins/builtin/ticker-detail/overview/model.ts +0 -14
  101. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -0
  102. package/src/plugins/pane-manager/docking.ts +9 -2
  103. package/src/plugins/pane-manager/queries.ts +8 -2
  104. package/src/plugins/prediction-markets/controller/index.ts +2 -0
  105. package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
  106. package/src/plugins/prediction-markets/pane.test.tsx +32 -0
  107. package/src/plugins/prediction-markets/pane.tsx +21 -1
  108. package/src/plugins/prediction-markets/test-helpers.tsx +1 -0
  109. package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
  110. package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
  111. package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
  112. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  113. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +9 -2
  114. package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
  115. package/src/renderers/electrobun/bun/desktop/window-controls.ts +11 -2
  116. package/src/renderers/electrobun/bun/desktop/window-style.ts +27 -1
  117. package/src/renderers/electrobun/bun/desktop/windows-custom-chrome.ts +58 -0
  118. package/src/renderers/electrobun/bun/desktop/windows-icons.ts +11 -64
  119. package/src/renderers/electrobun/bun/desktop/windows-native.ts +75 -0
  120. package/src/renderers/electrobun/bun/index.ts +22 -2
  121. package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
  122. package/src/renderers/electrobun/bun/window/focus.ts +5 -0
  123. package/src/renderers/electrobun/shared/protocol.ts +1 -0
  124. package/src/renderers/electrobun/view/app-services.ts +6 -4
  125. package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
  126. package/src/renderers/electrobun/view/host/text.test.ts +22 -0
  127. package/src/renderers/electrobun/view/host/text.tsx +24 -20
  128. package/src/renderers/electrobun/view/main.tsx +2 -1
  129. package/src/renderers/electrobun/view/styles.css +9 -8
  130. package/src/renderers/electrobun/view/ui-host.tsx +102 -77
  131. package/src/renderers/opentui/start.tsx +13 -5
  132. package/src/state/app/input-capture.ts +12 -0
  133. package/src/types/plugin.ts +28 -0
  134. package/src/utils/cli-output.ts +6 -0
  135. package/src/utils/search-focus-navigation.ts +24 -0
  136. package/src/version.ts +1 -1
@@ -0,0 +1,527 @@
1
+ import type { CliCommandDef } from "../../types/plugin";
2
+ import type { TimeRange } from "../../components/chart/core/types";
3
+ import type { EarningsEvent, QuoteBatchResult, SecFilingItem } from "../../types/data-provider";
4
+ import type { NewsArticle, NewsFeed, NewsQuery } from "../../news/types";
5
+ import type {
6
+ AnalystResearchData,
7
+ CorporateActionsData,
8
+ HolderData,
9
+ OptionsChain,
10
+ PricePoint,
11
+ TickerFinancials,
12
+ } from "../../types/financials";
13
+ import { formatMarketPriceWithCurrency } from "../../market-data/market/format";
14
+ import { formatCompact } from "../../utils/format";
15
+ import { isoDate, parsePositiveInt, requireArg, takeOption } from "./command-utils";
16
+
17
+ const VALID_RANGES = new Set<TimeRange>(["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"]);
18
+ const VALID_NEWS_FEEDS = new Set<NewsFeed>(["latest", "top", "breaking", "ticker", "sector", "topic"]);
19
+
20
+ type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & { error: string | null };
21
+ type FinancialsCliData = TickerFinancials & {
22
+ symbol: string;
23
+ exchange: string;
24
+ providerId: string | null;
25
+ };
26
+
27
+ function parseRange(value: string | undefined): TimeRange {
28
+ const range = (value ?? "1Y").toUpperCase() as TimeRange;
29
+ return VALID_RANGES.has(range) ? range : "1Y";
30
+ }
31
+
32
+ function parseNewsFeed(value: string | undefined): NewsQuery["feed"] | undefined {
33
+ return value && VALID_NEWS_FEEDS.has(value as NewsFeed) ? value as NewsQuery["feed"] : undefined;
34
+ }
35
+
36
+ function normalizeSymbols(args: string[]): string[] {
37
+ return args
38
+ .flatMap((arg) => arg.split(","))
39
+ .map((symbol) => symbol.trim().toUpperCase())
40
+ .filter(Boolean);
41
+ }
42
+
43
+ function quoteColumns() {
44
+ return [
45
+ { key: "symbol", header: "Symbol" },
46
+ { key: "name", header: "Name" },
47
+ { key: "price", header: "Last", align: "right" as const },
48
+ { key: "changePercent", header: "Chg%", align: "right" as const },
49
+ { key: "currency", header: "Cur" },
50
+ { key: "source", header: "Source" },
51
+ { key: "updatedAt", header: "Updated" },
52
+ ];
53
+ }
54
+
55
+ function errorMessage(error: unknown): string | null {
56
+ if (error == null) return null;
57
+ return error instanceof Error ? error.message : String(error);
58
+ }
59
+
60
+ function quoteRows(results: QuoteCliRecord[]) {
61
+ return results.map((result) => {
62
+ const quote = result.quote;
63
+ return {
64
+ symbol: result.target.symbol,
65
+ name: quote?.name ?? "",
66
+ price: quote ? formatMarketPriceWithCurrency(quote.price, quote.currency) : "",
67
+ rawPrice: quote?.price ?? null,
68
+ change: quote?.change ?? null,
69
+ changePercent: quote?.changePercent == null ? null : Number(quote.changePercent.toFixed(2)),
70
+ currency: quote?.currency ?? "",
71
+ providerId: quote?.providerId ?? "",
72
+ source: quote?.dataSource ?? quote?.providerId ?? "",
73
+ updatedAt: quote?.lastUpdated ? new Date(quote.lastUpdated).toISOString() : "",
74
+ error: result.error ?? "",
75
+ };
76
+ });
77
+ }
78
+
79
+ function historyRows(points: PricePoint[]) {
80
+ return points.map((point) => ({
81
+ date: isoDate(point.date).slice(0, 10),
82
+ open: point.open ?? null,
83
+ high: point.high ?? null,
84
+ low: point.low ?? null,
85
+ close: point.close,
86
+ volume: point.volume ?? null,
87
+ }));
88
+ }
89
+
90
+ function financialStatementRows(financials: FinancialsCliData) {
91
+ return financials.annualStatements.slice(0, 8).map((statement) => ({
92
+ date: statement.date,
93
+ revenue: statement.totalRevenue ?? statement.operatingRevenue ?? null,
94
+ grossProfit: statement.grossProfit ?? null,
95
+ operatingIncome: statement.operatingIncome ?? null,
96
+ netIncome: statement.netIncome ?? statement.netIncomeCommonStockholders ?? null,
97
+ eps: statement.eps ?? statement.basicEps ?? null,
98
+ }));
99
+ }
100
+
101
+ function newsRows(articles: NewsArticle[]) {
102
+ return articles.map((article) => ({
103
+ title: article.title,
104
+ source: article.source,
105
+ publishedAt: isoDate(article.publishedAt),
106
+ topic: article.topic,
107
+ tickers: article.tickers.join(","),
108
+ url: article.url,
109
+ importance: article.importance,
110
+ breaking: article.isBreaking,
111
+ }));
112
+ }
113
+
114
+ function filingRows(filings: SecFilingItem[]) {
115
+ return filings.map((filing) => ({
116
+ form: filing.form,
117
+ filingDate: isoDate(filing.filingDate).slice(0, 10),
118
+ companyName: filing.companyName ?? "",
119
+ accessionNumber: filing.accessionNumber,
120
+ url: filing.primaryDocumentUrl ?? filing.filingUrl,
121
+ }));
122
+ }
123
+
124
+ function holderRows(data: HolderData, ownerTypes?: Set<string>) {
125
+ const holders = ownerTypes
126
+ ? data.holders.filter((holder) => ownerTypes.has(holder.ownerType))
127
+ : data.holders;
128
+ return holders.map((holder) => ({
129
+ type: holder.ownerType,
130
+ name: holder.name,
131
+ reportDate: holder.reportDate ?? "",
132
+ shares: holder.shares ?? null,
133
+ value: holder.value ?? null,
134
+ percentHeld: holder.percentHeld ?? null,
135
+ changeShares: holder.changeShares ?? null,
136
+ }));
137
+ }
138
+
139
+ function analystRows(data: AnalystResearchData) {
140
+ return data.ratings.map((rating) => ({
141
+ date: rating.date,
142
+ firm: rating.firm,
143
+ action: rating.action ?? "",
144
+ current: rating.current ?? "",
145
+ prior: rating.prior ?? "",
146
+ target: rating.currentPriceTarget ?? null,
147
+ }));
148
+ }
149
+
150
+ function corporateActionRows(data: CorporateActionsData) {
151
+ return [
152
+ ...data.earnings.map((event) => ({
153
+ type: "earnings",
154
+ date: event.date,
155
+ detail: event.epsActual == null ? `est ${event.epsEstimate ?? ""}` : `eps ${event.epsActual}`,
156
+ })),
157
+ ...data.dividends.map((event) => ({
158
+ type: "dividend",
159
+ date: event.exDate,
160
+ detail: String(event.amount),
161
+ })),
162
+ ...data.splits.map((event) => ({
163
+ type: "split",
164
+ date: event.date,
165
+ detail: event.description ?? `${event.fromFactor ?? ""}:${event.toFactor ?? ""}`,
166
+ })),
167
+ ].sort((left, right) => right.date.localeCompare(left.date));
168
+ }
169
+
170
+ function optionRows(chain: OptionsChain) {
171
+ return [...chain.calls.map((contract) => ({ side: "call", ...contract })), ...chain.puts.map((contract) => ({ side: "put", ...contract }))]
172
+ .map((contract) => ({
173
+ side: contract.side,
174
+ contract: contract.contractSymbol,
175
+ strike: contract.strike,
176
+ last: contract.lastPrice,
177
+ bid: contract.bid,
178
+ ask: contract.ask,
179
+ volume: contract.volume,
180
+ openInterest: contract.openInterest,
181
+ iv: contract.impliedVolatility,
182
+ expiration: new Date(contract.expiration * 1000).toISOString().slice(0, 10),
183
+ }));
184
+ }
185
+
186
+ function earningsRows(events: EarningsEvent[]) {
187
+ return events.map((event) => ({
188
+ symbol: event.symbol,
189
+ name: event.name,
190
+ date: isoDate(event.earningsDate).slice(0, 10),
191
+ timing: event.timing,
192
+ epsEstimate: event.epsEstimate,
193
+ epsActual: event.epsActual,
194
+ revenueEstimate: event.revenueEstimate,
195
+ revenueActual: event.revenueActual,
196
+ }));
197
+ }
198
+
199
+ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
200
+ const args = [...rawArgs];
201
+ const exchange = takeOption(args, "--exchange") ?? "";
202
+ const symbols = normalizeSymbols(args);
203
+ if (symbols.length === 0) ctx.fail("Usage: gloomberb quote <symbol...>");
204
+
205
+ const market = await ctx.initMarketData();
206
+ try {
207
+ const results = await market.dataProvider.getQuotesBatch(
208
+ symbols.map((symbol) => ({ symbol, exchange })),
209
+ { forceRefresh: ctx.cliOptions.refresh },
210
+ );
211
+ const data = results.map((result) => ({
212
+ target: result.target,
213
+ quote: result.quote,
214
+ error: errorMessage(result.error),
215
+ }));
216
+ ctx.printResult({ data }, { rows: quoteRows, columns: quoteColumns() });
217
+ } finally {
218
+ market.persistence.close();
219
+ }
220
+ }
221
+
222
+ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
223
+ const args = [...rawArgs];
224
+ const range = parseRange(takeOption(args, "--range"));
225
+ const requestedExchange = takeOption(args, "--exchange") ?? "";
226
+ const symbol = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb history <symbol> [--range 1Y]", ctx);
227
+ const market = await ctx.initMarketData();
228
+ try {
229
+ const localTicker = requestedExchange ? null : await market.store.loadTicker(symbol);
230
+ const exchange = requestedExchange || localTicker?.metadata.exchange || "";
231
+ const points = await market.dataProvider.getPriceHistory(symbol, exchange, range);
232
+ const data = historyRows(points);
233
+ ctx.printResult({ data, metadata: { symbol, range, exchange } }, {
234
+ columns: [
235
+ { key: "date", header: "Date" },
236
+ { key: "open", header: "Open", align: "right" },
237
+ { key: "high", header: "High", align: "right" },
238
+ { key: "low", header: "Low", align: "right" },
239
+ { key: "close", header: "Close", align: "right" },
240
+ { key: "volume", header: "Volume", align: "right" },
241
+ ],
242
+ });
243
+ } finally {
244
+ market.persistence.close();
245
+ }
246
+ }
247
+
248
+ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], fundamentalsOnly = false) {
249
+ const args = [...rawArgs];
250
+ const exchange = takeOption(args, "--exchange") ?? "";
251
+ const symbol = requireArg(args[0]?.toUpperCase(), fundamentalsOnly ? "Usage: gloomberb fundamentals <symbol>" : "Usage: gloomberb financials <symbol>", ctx);
252
+ const market = await ctx.initMarketData();
253
+ try {
254
+ const financials = await market.dataProvider.getTickerFinancials(symbol, exchange, {
255
+ cacheMode: ctx.cliOptions.refresh ? "refresh" : "default",
256
+ });
257
+ const data: FinancialsCliData = {
258
+ symbol,
259
+ exchange,
260
+ providerId: financials.quote?.providerId ?? null,
261
+ ...financials,
262
+ };
263
+ if (fundamentalsOnly) {
264
+ ctx.printResult({ data });
265
+ return;
266
+ }
267
+ ctx.printResult({
268
+ data,
269
+ metadata: {
270
+ symbol,
271
+ providerId: financials.quote?.providerId,
272
+ annualStatements: financials.annualStatements.length,
273
+ quarterlyStatements: financials.quarterlyStatements.length,
274
+ fundamentals: financials.fundamentals,
275
+ profile: financials.profile,
276
+ },
277
+ }, {
278
+ rows: financialStatementRows,
279
+ columns: [
280
+ { key: "date", header: "Date" },
281
+ { key: "revenue", header: "Revenue", align: "right", value: (row) => row.revenue == null ? "" : formatCompact(Number(row.revenue)) },
282
+ { key: "grossProfit", header: "Gross", align: "right", value: (row) => row.grossProfit == null ? "" : formatCompact(Number(row.grossProfit)) },
283
+ { key: "operatingIncome", header: "Op Inc", align: "right", value: (row) => row.operatingIncome == null ? "" : formatCompact(Number(row.operatingIncome)) },
284
+ { key: "netIncome", header: "Net Inc", align: "right", value: (row) => row.netIncome == null ? "" : formatCompact(Number(row.netIncome)) },
285
+ { key: "eps", header: "EPS", align: "right" },
286
+ ],
287
+ });
288
+ } finally {
289
+ market.persistence.close();
290
+ }
291
+ }
292
+
293
+ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
294
+ const args = [...rawArgs];
295
+ const feed = parseNewsFeed(takeOption(args, "--feed"));
296
+ const ticker = args[0]?.toUpperCase();
297
+ const market = await ctx.initMarketData();
298
+ try {
299
+ const limit = ctx.cliOptions.limit ?? 20;
300
+ const articles = await market.dataProvider.getNews({
301
+ feed: feed ?? (ticker ? "ticker" : "latest"),
302
+ scope: ticker ? "ticker" : "global",
303
+ ticker,
304
+ limit,
305
+ });
306
+ ctx.printResult({ data: articles, metadata: { ticker: ticker ?? null, feed: feed ?? null } }, {
307
+ rows: newsRows,
308
+ columns: [
309
+ { key: "publishedAt", header: "Published" },
310
+ { key: "source", header: "Source" },
311
+ { key: "title", header: "Title" },
312
+ { key: "tickers", header: "Tickers" },
313
+ { key: "url", header: "URL" },
314
+ ],
315
+ });
316
+ } finally {
317
+ market.persistence.close();
318
+ }
319
+ }
320
+
321
+ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
322
+ const args = [...rawArgs];
323
+ const count = parsePositiveInt(takeOption(args, "--count"), ctx.cliOptions.limit ?? 15, "Count", ctx);
324
+ const exchange = takeOption(args, "--exchange") ?? "";
325
+ const symbol = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb filings <symbol>", ctx);
326
+ const market = await ctx.initMarketData();
327
+ try {
328
+ const filings = await market.dataProvider.getSecFilings(symbol, count, exchange);
329
+ ctx.printResult({ data: filings, metadata: { symbol } }, {
330
+ rows: filingRows,
331
+ columns: [
332
+ { key: "filingDate", header: "Date" },
333
+ { key: "form", header: "Form" },
334
+ { key: "companyName", header: "Company" },
335
+ { key: "url", header: "URL" },
336
+ ],
337
+ });
338
+ } finally {
339
+ market.persistence.close();
340
+ }
341
+ }
342
+
343
+ async function runHolders(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], ownerTypes?: Set<string>) {
344
+ const args = [...rawArgs];
345
+ const exchange = takeOption(args, "--exchange") ?? "";
346
+ const symbol = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb holders <symbol>", ctx);
347
+ const market = await ctx.initMarketData();
348
+ try {
349
+ const data = await market.dataProvider.getHolders(symbol, exchange);
350
+ ctx.printResult({ data, metadata: { symbol, summary: data.summary } }, {
351
+ rows: (holderData) => holderRows(holderData, ownerTypes),
352
+ columns: [
353
+ { key: "type", header: "Type" },
354
+ { key: "name", header: "Holder" },
355
+ { key: "reportDate", header: "Date" },
356
+ { key: "shares", header: "Shares", align: "right" },
357
+ { key: "value", header: "Value", align: "right", value: (row) => row.value == null ? "" : formatCompact(Number(row.value)) },
358
+ { key: "percentHeld", header: "% Held", align: "right" },
359
+ ],
360
+ });
361
+ } finally {
362
+ market.persistence.close();
363
+ }
364
+ }
365
+
366
+ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
367
+ const args = [...rawArgs];
368
+ const exchange = takeOption(args, "--exchange") ?? "";
369
+ const symbol = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb analyst <symbol>", ctx);
370
+ const market = await ctx.initMarketData();
371
+ try {
372
+ const data = await market.dataProvider.getAnalystResearch(symbol, exchange);
373
+ ctx.printResult({
374
+ data,
375
+ metadata: {
376
+ symbol,
377
+ recommendationRating: data.recommendationRating,
378
+ priceTarget: data.priceTarget,
379
+ recommendations: data.recommendations,
380
+ },
381
+ }, {
382
+ rows: analystRows,
383
+ columns: [
384
+ { key: "date", header: "Date" },
385
+ { key: "firm", header: "Firm" },
386
+ { key: "action", header: "Action" },
387
+ { key: "current", header: "Current" },
388
+ { key: "target", header: "Target", align: "right" },
389
+ ],
390
+ });
391
+ } finally {
392
+ market.persistence.close();
393
+ }
394
+ }
395
+
396
+ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
397
+ const args = [...rawArgs];
398
+ const exchange = takeOption(args, "--exchange") ?? "";
399
+ const symbol = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb events <symbol>", ctx);
400
+ const market = await ctx.initMarketData();
401
+ try {
402
+ const data = await market.dataProvider.getCorporateActions(symbol, exchange);
403
+ ctx.printResult({ data, metadata: { symbol } }, {
404
+ rows: corporateActionRows,
405
+ columns: [
406
+ { key: "date", header: "Date" },
407
+ { key: "type", header: "Type" },
408
+ { key: "detail", header: "Detail" },
409
+ ],
410
+ });
411
+ } finally {
412
+ market.persistence.close();
413
+ }
414
+ }
415
+
416
+ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
417
+ const args = [...rawArgs];
418
+ const expiration = takeOption(args, "--expiration");
419
+ const exchange = takeOption(args, "--exchange") ?? "";
420
+ const symbol = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb options <symbol>", ctx);
421
+ const market = await ctx.initMarketData();
422
+ try {
423
+ const chain = await market.dataProvider.getOptionsChain(
424
+ symbol,
425
+ exchange,
426
+ expiration == null ? undefined : Number(expiration),
427
+ );
428
+ ctx.printResult({ data: chain, metadata: { symbol, expirations: chain.expirationDates } }, {
429
+ rows: optionRows,
430
+ columns: [
431
+ { key: "side", header: "Side" },
432
+ { key: "contract", header: "Contract" },
433
+ { key: "expiration", header: "Expiry" },
434
+ { key: "strike", header: "Strike", align: "right" },
435
+ { key: "last", header: "Last", align: "right" },
436
+ { key: "bid", header: "Bid", align: "right" },
437
+ { key: "ask", header: "Ask", align: "right" },
438
+ { key: "volume", header: "Vol", align: "right" },
439
+ { key: "openInterest", header: "OI", align: "right" },
440
+ ],
441
+ });
442
+ } finally {
443
+ market.persistence.close();
444
+ }
445
+ }
446
+
447
+ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
448
+ const currency = requireArg(rawArgs[0]?.toUpperCase(), "Usage: gloomberb fx <currency>", ctx);
449
+ const market = await ctx.initMarketData();
450
+ try {
451
+ const rate = await market.dataProvider.getExchangeRate(currency);
452
+ ctx.printResult({ data: [{ currency, baseCurrency: market.config.baseCurrency, rate }] }, {
453
+ columns: [
454
+ { key: "currency", header: "Currency" },
455
+ { key: "baseCurrency", header: "Base" },
456
+ { key: "rate", header: "Rate", align: "right" },
457
+ ],
458
+ });
459
+ } finally {
460
+ market.persistence.close();
461
+ }
462
+ }
463
+
464
+ async function runEarnings(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
465
+ const symbols = normalizeSymbols([...rawArgs]);
466
+ if (symbols.length === 0) ctx.fail("Usage: gloomberb earnings <symbol...>");
467
+ const services = await ctx.initServices();
468
+ try {
469
+ let events: EarningsEvent[] = [];
470
+ for (const { capability } of services.services.pluginRegistry.capabilities.list("asset-data")) {
471
+ if (!capability.operations.getEarningsCalendar?.handler) continue;
472
+ try {
473
+ events = await services.services.pluginRegistry.capabilities.invoke<EarningsEvent[]>(
474
+ capability.id,
475
+ "getEarningsCalendar",
476
+ { symbols },
477
+ );
478
+ } catch {
479
+ continue;
480
+ }
481
+ if (events.length > 0) break;
482
+ }
483
+ ctx.printResult({ data: events }, {
484
+ rows: earningsRows,
485
+ columns: [
486
+ { key: "date", header: "Date" },
487
+ { key: "symbol", header: "Symbol" },
488
+ { key: "name", header: "Name" },
489
+ { key: "timing", header: "Timing" },
490
+ { key: "epsEstimate", header: "EPS Est", align: "right" },
491
+ { key: "epsActual", header: "EPS", align: "right" },
492
+ ],
493
+ });
494
+ } finally {
495
+ services.destroy();
496
+ }
497
+ }
498
+
499
+ export const marketDataCliCommands: CliCommandDef[] = [
500
+ { name: "quote", description: "Fetch one or more quotes", help: { usage: ["quote <symbol...>"] }, execute: runQuote },
501
+ { name: "provider-search", description: "Search provider instruments", help: { usage: ["provider-search <query>"] }, execute: async (args, ctx) => {
502
+ const query = args.join(" ");
503
+ if (!query) ctx.fail("Usage: gloomberb provider-search <query>");
504
+ const market = await ctx.initMarketData();
505
+ try {
506
+ const results = await market.dataProvider.search(query);
507
+ ctx.printResult({ data: results.slice(0, ctx.cliOptions.limit ?? results.length) });
508
+ } finally {
509
+ market.persistence.close();
510
+ }
511
+ } },
512
+ { name: "history", description: "Fetch historical prices", help: { usage: ["history <symbol> [--range 1Y]"] }, execute: runHistory },
513
+ { name: "financials", description: "Fetch annual financial statements", help: { usage: ["financials <symbol>"] }, execute: (args, ctx) => runFinancials(args, ctx, false) },
514
+ { name: "fundamentals", description: "Fetch fundamentals and profile data", help: { usage: ["fundamentals <symbol>"] }, execute: (args, ctx) => runFinancials(args, ctx, true) },
515
+ { name: "news", description: "Fetch market or ticker news", help: { usage: ["news [symbol] [--feed latest|top|ticker]"] }, execute: runNews },
516
+ { name: "filings", description: "Fetch SEC filings", help: { usage: ["filings <symbol>"] }, execute: runFilings },
517
+ { name: "holders", description: "Fetch holder data", help: { usage: ["holders <symbol>"] }, execute: (args, ctx) => runHolders(args, ctx) },
518
+ { name: "insider", description: "Fetch insider holder rows", help: { usage: ["insider <symbol>"] }, execute: (args, ctx) => runHolders(args, ctx, new Set(["insider", "direct"])) },
519
+ { name: "13f", description: "Fetch institutional and fund holder rows", help: { usage: ["13f <symbol>"] }, execute: (args, ctx) => runHolders(args, ctx, new Set(["institution", "fund"])) },
520
+ { name: "analyst", description: "Fetch analyst research and ratings", help: { usage: ["analyst <symbol>"] }, execute: runAnalyst },
521
+ { name: "events", description: "Fetch dividends, splits, and earnings events", help: { usage: ["events <symbol>"] }, execute: runEvents },
522
+ { name: "valuation", description: "Fetch valuation-related fundamentals", help: { usage: ["valuation <symbol>"] }, execute: (args, ctx) => runFinancials(args, ctx, true) },
523
+ { name: "options", description: "Fetch options chain rows", help: { usage: ["options <symbol> [--expiration unix]"] }, execute: runOptions },
524
+ { name: "compare", description: "Compare quotes for several symbols", help: { usage: ["compare <symbol...>"] }, execute: runQuote },
525
+ { name: "fx", description: "Fetch exchange rate into the configured base currency", help: { usage: ["fx <currency>"] }, execute: runFx },
526
+ { name: "earnings", description: "Fetch earnings calendar entries for symbols", help: { usage: ["earnings <symbol...>"] }, execute: runEarnings },
527
+ ];