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,222 @@
1
+ import { apiClient } from "../../api-client";
2
+ import type { CliCommandDef } from "../../types/plugin";
3
+ import { formatCompact } from "../../utils/format";
4
+ import { attachFearGreedPersistence, loadFearGreed, resetFearGreedPersistence } from "../../plugins/builtin/fear-greed/cache";
5
+ import { createPluginPersistence } from "../../plugins/plugin-persistence";
6
+ import {
7
+ fetchScreener,
8
+ fetchTrending,
9
+ MARKET_SUMMARY_SYMBOLS,
10
+ type ScreenerCategory,
11
+ } from "../../plugins/builtin/market-movers/screener";
12
+ import { loadCalendar, matchesCountry, matchesImpact, type CountryFilter, type ImpactFilter } from "../../plugins/builtin/econ/calendar-model";
13
+ import { isoDate, requireArg, takeOption } from "./command-utils";
14
+
15
+ const SECTOR_ETFS = [
16
+ "XLC", "XLY", "XLP", "XLE", "XLF", "XLV", "XLI", "XLK", "XLB", "XLRE", "XLU",
17
+ ];
18
+
19
+ function screenerCategory(value: string | undefined): ScreenerCategory | "trending" {
20
+ if (value === "losers") return "day_losers";
21
+ if (value === "active" || value === "most-active") return "most_actives";
22
+ if (value === "trending") return "trending";
23
+ return "day_gainers";
24
+ }
25
+
26
+ function quoteRows(results: Awaited<ReturnType<NonNullable<import("../../types/data-provider").AssetDataProvider["getQuotesBatch"]>>>) {
27
+ return results.map((result) => {
28
+ const quote = result.quote;
29
+ return {
30
+ symbol: result.target.symbol,
31
+ name: quote?.name ?? "",
32
+ price: quote?.price ?? null,
33
+ change: quote?.change ?? null,
34
+ changePercent: quote?.changePercent == null ? null : Number(quote.changePercent.toFixed(2)),
35
+ currency: quote?.currency ?? "",
36
+ providerId: quote?.providerId ?? "",
37
+ marketCap: quote?.marketCap ?? null,
38
+ };
39
+ });
40
+ }
41
+
42
+ async function runMoverCommand(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
43
+ const category = screenerCategory(args[0]);
44
+ const limit = ctx.cliOptions.limit ?? 25;
45
+ if (category === "trending") {
46
+ const services = await ctx.initServices();
47
+ try {
48
+ const trending = await fetchTrending(limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
49
+ const results = await services.dataProvider.getQuotesBatch(
50
+ trending.map(({ symbol }) => ({ symbol, exchange: "" })),
51
+ { forceRefresh: ctx.cliOptions.refresh },
52
+ );
53
+ ctx.printResult({ data: quoteRows(results), metadata: { category } });
54
+ } finally {
55
+ services.destroy();
56
+ }
57
+ return;
58
+ }
59
+
60
+ const rows = await fetchScreener(category, limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
61
+ ctx.printResult({ data: rows }, {
62
+ columns: [
63
+ { key: "symbol", header: "Symbol" },
64
+ { key: "name", header: "Name" },
65
+ { key: "price", header: "Last", align: "right" },
66
+ { key: "changePercent", header: "Chg%", align: "right" },
67
+ { key: "volume", header: "Volume", align: "right", value: (row) => formatCompact(Number(row.volume)) },
68
+ { key: "marketCap", header: "Mkt Cap", align: "right", value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
69
+ ],
70
+ });
71
+ }
72
+
73
+ async function runQuoteBasket(symbols: string[], ctx: Parameters<CliCommandDef["execute"]>[1], metadata: Record<string, unknown>) {
74
+ const services = await ctx.initServices();
75
+ try {
76
+ const results = await services.dataProvider.getQuotesBatch(
77
+ symbols.map((symbol) => ({ symbol, exchange: "" })),
78
+ { forceRefresh: ctx.cliOptions.refresh },
79
+ );
80
+ ctx.printResult({ data: quoteRows(results), metadata }, {
81
+ columns: [
82
+ { key: "symbol", header: "Symbol" },
83
+ { key: "name", header: "Name" },
84
+ { key: "price", header: "Last", align: "right" },
85
+ { key: "changePercent", header: "Chg%", align: "right" },
86
+ { key: "marketCap", header: "Mkt Cap", align: "right", value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
87
+ ],
88
+ });
89
+ } finally {
90
+ services.destroy();
91
+ }
92
+ }
93
+
94
+ async function runFearGreed(_args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
95
+ const context = await ctx.initConfigData();
96
+ attachFearGreedPersistence(createPluginPersistence(
97
+ context.persistence.pluginState,
98
+ context.persistence.resources,
99
+ "plugin:fear-greed",
100
+ "fear-greed",
101
+ ));
102
+ try {
103
+ const data = await loadFearGreed(ctx.cliOptions.refresh);
104
+ ctx.printResult({
105
+ data: [{
106
+ score: data.overall.score,
107
+ rating: data.overall.rating,
108
+ updatedAt: data.overall.updatedAt?.toISOString() ?? "",
109
+ previousClose: data.overall.previousClose,
110
+ previousWeek: data.overall.previousWeek,
111
+ previousMonth: data.overall.previousMonth,
112
+ previousYear: data.overall.previousYear,
113
+ }],
114
+ metadata: { indicators: data.indicators.map((indicator) => ({ id: indicator.definition.id, score: indicator.score, rating: indicator.rating })) },
115
+ });
116
+ } finally {
117
+ resetFearGreedPersistence();
118
+ context.persistence.close();
119
+ }
120
+ }
121
+
122
+ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
123
+ const rawArgs = [...args];
124
+ const country = (takeOption(rawArgs, "--country") ?? "all") as CountryFilter;
125
+ const impact = (takeOption(rawArgs, "--impact") ?? "all") as ImpactFilter;
126
+ const services = await ctx.initServices();
127
+ try {
128
+ const events = await loadCalendar(ctx.cliOptions.refresh);
129
+ const rows = events
130
+ .filter((event) => matchesCountry(event, country) && matchesImpact(event, impact))
131
+ .sort((left, right) => left.date.getTime() - right.date.getTime())
132
+ .slice(0, ctx.cliOptions.limit ?? 50)
133
+ .map((event) => ({
134
+ date: isoDate(event.date),
135
+ time: event.time,
136
+ country: event.country,
137
+ impact: event.impact,
138
+ event: event.event,
139
+ actual: event.actual ?? "",
140
+ forecast: event.forecast ?? "",
141
+ prior: event.prior ?? "",
142
+ }));
143
+ ctx.printResult({ data: rows, metadata: { country, impact } }, {
144
+ columns: [
145
+ { key: "date", header: "Date" },
146
+ { key: "time", header: "Time" },
147
+ { key: "country", header: "Country" },
148
+ { key: "impact", header: "Impact" },
149
+ { key: "event", header: "Event" },
150
+ { key: "actual", header: "Actual" },
151
+ { key: "forecast", header: "Forecast" },
152
+ { key: "prior", header: "Prior" },
153
+ ],
154
+ });
155
+ } finally {
156
+ services.destroy();
157
+ }
158
+ }
159
+
160
+ async function runFred(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
161
+ const seriesId = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb fred <series-id>", ctx);
162
+ const startDate = takeOption(args, "--start") ?? "2021-01-01";
163
+ const sortOrder = (takeOption(args, "--sort") ?? "desc") as "asc" | "desc";
164
+ const data = await apiClient.getCloudFredSeries(seriesId, { startDate, sortOrder });
165
+ const rows = data.observations.slice(0, ctx.cliOptions.limit ?? data.observations.length);
166
+ ctx.printResult({ data: rows, metadata: { info: data.info, seriesId, startDate, sortOrder } });
167
+ }
168
+
169
+ async function runYieldCurve(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
170
+ const startDate = takeOption(args, "--start") ?? "2021-01-01";
171
+ const series = ["DGS3MO", "DGS2", "DGS10", "DGS30"];
172
+ const results = await Promise.all(series.map(async (seriesId) => {
173
+ const data = await apiClient.getCloudFredSeries(seriesId, { startDate, sortOrder: "desc" });
174
+ const latest = data.observations[0];
175
+ return {
176
+ seriesId,
177
+ date: latest?.date ?? "",
178
+ value: latest?.value ?? null,
179
+ title: data.info?.title ?? "",
180
+ };
181
+ }));
182
+ ctx.printResult({ data: results, metadata: { startDate } });
183
+ }
184
+
185
+ async function runCorrelation(args: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
186
+ const left = requireArg(args[0]?.toUpperCase(), "Usage: gloomberb correlation <symbol-a> <symbol-b>", ctx);
187
+ const right = requireArg(args[1]?.toUpperCase(), "Usage: gloomberb correlation <symbol-a> <symbol-b>", ctx);
188
+ const services = await ctx.initServices();
189
+ try {
190
+ const [leftHistory, rightHistory] = await Promise.all([
191
+ services.dataProvider.getPriceHistory(left, "", "1Y"),
192
+ services.dataProvider.getPriceHistory(right, "", "1Y"),
193
+ ]);
194
+ const rightByDate = new Map(rightHistory.map((point) => [isoDate(point.date).slice(0, 10), point.close]));
195
+ const pairs = leftHistory
196
+ .map((point) => [point.close, rightByDate.get(isoDate(point.date).slice(0, 10))] as const)
197
+ .filter((pair): pair is readonly [number, number] => pair[1] != null);
198
+ const leftMean = pairs.reduce((sum, pair) => sum + pair[0], 0) / Math.max(1, pairs.length);
199
+ const rightMean = pairs.reduce((sum, pair) => sum + pair[1], 0) / Math.max(1, pairs.length);
200
+ const numerator = pairs.reduce((sum, pair) => sum + ((pair[0] - leftMean) * (pair[1] - rightMean)), 0);
201
+ const leftVariance = pairs.reduce((sum, pair) => sum + ((pair[0] - leftMean) ** 2), 0);
202
+ const rightVariance = pairs.reduce((sum, pair) => sum + ((pair[1] - rightMean) ** 2), 0);
203
+ const correlation = leftVariance > 0 && rightVariance > 0
204
+ ? numerator / Math.sqrt(leftVariance * rightVariance)
205
+ : null;
206
+ ctx.printResult({ data: [{ left, right, samples: pairs.length, correlation }] });
207
+ } finally {
208
+ services.destroy();
209
+ }
210
+ }
211
+
212
+ export const overviewCliCommands: CliCommandDef[] = [
213
+ { name: "movers", description: "Fetch gainers, losers, active, or trending market movers", help: { usage: ["movers [gainers|losers|active|trending]"] }, execute: runMoverCommand },
214
+ { name: "indices", description: "Fetch major US index quotes", execute: (args, ctx) => runQuoteBasket([...MARKET_SUMMARY_SYMBOLS], ctx, { group: "indices" }) },
215
+ { name: "sectors", description: "Fetch SPDR sector ETF quotes", execute: (args, ctx) => runQuoteBasket(SECTOR_ETFS, ctx, { group: "sectors" }) },
216
+ { name: "fear-greed", description: "Fetch CNN Fear & Greed gauge data", execute: runFearGreed },
217
+ { name: "econ", description: "Fetch economic calendar events", help: { usage: ["econ [--country US|G7|EU|all] [--impact high|medium|low|all]"] }, execute: runEcon },
218
+ { name: "fred", description: "Fetch a FRED series through the configured cloud session", help: { usage: ["fred <series-id> [--start yyyy-mm-dd]"] }, execute: runFred },
219
+ { name: "yield-curve", description: "Fetch standard Treasury yield FRED series", help: { usage: ["yield-curve [--start yyyy-mm-dd]"] }, execute: runYieldCurve },
220
+ { name: "correlation", description: "Compute 1Y close-price correlation for two symbols", help: { usage: ["correlation <symbol-a> <symbol-b>"] }, execute: runCorrelation },
221
+ { name: "relationship", description: "Alias for correlation", help: { usage: ["relationship <symbol-a> <symbol-b>"] }, execute: runCorrelation },
222
+ ];
@@ -12,10 +12,12 @@ import {
12
12
  import { initMarketData } from "../context";
13
13
  import { fail } from "../errors";
14
14
  import type { MarketContext } from "../types";
15
+ import type { CliCommandContext } from "../../types/plugin";
15
16
 
16
17
  interface SearchCommandDependencies {
17
18
  initMarketData?: () => Promise<MarketContext>;
18
19
  fail?: (message: string, details?: string) => never;
20
+ printResult?: CliCommandContext["printResult"];
19
21
  }
20
22
 
21
23
  export async function searchCandidatesForCli({
@@ -54,6 +56,28 @@ function resolveSearchName(candidate: TickerSearchCandidate): string {
54
56
  return candidate.detail.split(" | ")[0] || candidate.detail || "—";
55
57
  }
56
58
 
59
+ function searchCandidateRows(candidates: TickerSearchCandidate[]) {
60
+ return candidates.map((candidate) => ({
61
+ category: candidate.category,
62
+ symbol: candidate.label,
63
+ name: resolveSearchName(candidate),
64
+ exchange: candidate.result?.exchange
65
+ || candidate.result?.primaryExchange
66
+ || candidate.ticker?.metadata.exchange
67
+ || candidate.right
68
+ || "",
69
+ type: candidate.result?.type
70
+ || candidate.result?.brokerContract?.secType
71
+ || candidate.ticker?.metadata.assetCategory
72
+ || "",
73
+ source: candidate.kind === "ticker"
74
+ ? "Saved"
75
+ : candidate.result?.brokerLabel || candidate.result?.providerId || "Provider",
76
+ providerId: candidate.result?.providerId ?? "",
77
+ saved: candidate.kind === "ticker",
78
+ }));
79
+ }
80
+
57
81
  export function buildSearchReport({
58
82
  query,
59
83
  candidates,
@@ -119,6 +143,21 @@ export async function search(query: string, dependencies: SearchCommandDependenc
119
143
  dataProvider,
120
144
  });
121
145
 
146
+ if (dependencies.printResult) {
147
+ dependencies.printResult({ data: searchCandidateRows(candidates), metadata: { query: trimmedQuery } }, {
148
+ columns: [
149
+ { key: "category", header: "Category" },
150
+ { key: "symbol", header: "Symbol" },
151
+ { key: "name", header: "Name" },
152
+ { key: "exchange", header: "Exchange" },
153
+ { key: "type", header: "Type" },
154
+ { key: "source", header: "Source" },
155
+ ],
156
+ });
157
+ persistence.close();
158
+ return;
159
+ }
160
+
122
161
  console.log(buildSearchReport({
123
162
  query: trimmedQuery,
124
163
  candidates,