gloomberb 0.12.0 → 0.13.2

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 (219) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -2
  3. package/src/api-client/auth.ts +2 -1
  4. package/src/api-client/index.test.ts +34 -0
  5. package/src/api-client/index.ts +10 -2
  6. package/src/api-client/request.ts +4 -1
  7. package/src/api-client/research-activity.ts +61 -0
  8. package/src/api-client/types.ts +2 -1
  9. package/src/cli/commands/plugins.ts +4 -3
  10. package/src/cli/desktop-pane-shot.ts +79 -10
  11. package/src/cli/pane-functions/capabilities.ts +76 -23
  12. package/src/cli/pane-functions/cloud-session.test.ts +54 -0
  13. package/src/cli/pane-functions/cloud-session.ts +38 -0
  14. package/src/cli/pane-functions/dom.test.ts +41 -0
  15. package/src/cli/pane-functions/dom.ts +199 -0
  16. package/src/cli/pane-functions/index.test.ts +13 -2
  17. package/src/cli/pane-functions/index.ts +9 -5
  18. package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
  19. package/src/cli/pane-functions/intraday-shot.ts +334 -0
  20. package/src/cli/pane-functions/report.test.ts +29 -1
  21. package/src/cli/pane-functions/report.ts +68 -7
  22. package/src/cli/pane-functions/screenshot.ts +195 -42
  23. package/src/cli/restore-plugins.ts +10 -3
  24. package/src/components/chart/composite/composite-chart.test.tsx +68 -1
  25. package/src/components/chart/composite/composite-chart.tsx +87 -21
  26. package/src/components/chart/composite/rasterizer.ts +33 -0
  27. package/src/components/chart/composite/renderers.test.ts +51 -4
  28. package/src/components/chart/composite/scene.test.ts +128 -2
  29. package/src/components/chart/composite/scene.ts +92 -16
  30. package/src/components/chart/composite/text-renderer.ts +13 -0
  31. package/src/components/chart/composite/time-scale.ts +30 -10
  32. package/src/components/chart/composite/types.ts +26 -0
  33. package/src/components/command-bar/assist/runtime.ts +38 -30
  34. package/src/components/command-bar/surface/assist.test.tsx +63 -9
  35. package/src/components/command-bar/surface/test-harness.tsx +23 -4
  36. package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
  37. package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
  38. package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
  39. package/src/components/onboarding/onboarding-wizard.tsx +49 -14
  40. package/src/data/config/store/index.test.ts +23 -0
  41. package/src/data/config/store/node.ts +7 -1
  42. package/src/data/config/store/normalize.ts +4 -0
  43. package/src/news/types.ts +1 -0
  44. package/src/plugins/builtin/cds/client.test.ts +20 -0
  45. package/src/plugins/builtin/cds/client.ts +13 -3
  46. package/src/plugins/builtin/cds/headless.test.ts +30 -0
  47. package/src/plugins/builtin/cds/headless.ts +113 -0
  48. package/src/plugins/builtin/cds/index.tsx +4 -0
  49. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
  50. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  51. package/src/plugins/builtin/chat/controller/channels.ts +2 -1
  52. package/src/plugins/builtin/chat/controller/index.ts +13 -5
  53. package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
  54. package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
  55. package/src/plugins/builtin/chat/controller/state.ts +1 -0
  56. package/src/plugins/builtin/chat/controller.test.ts +260 -4
  57. package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
  58. package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
  59. package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
  60. package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
  61. package/src/plugins/builtin/cloud/askg/protocol.test.ts +190 -0
  62. package/src/plugins/builtin/cloud/askg/protocol.ts +286 -0
  63. package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
  64. package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
  65. package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
  66. package/src/plugins/builtin/cloud/auth-dialog.tsx +7 -1
  67. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
  68. package/src/plugins/builtin/cloud/plugin.tsx +2 -0
  69. package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
  70. package/src/plugins/builtin/congress-trades/client.ts +11 -0
  71. package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
  72. package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
  73. package/src/plugins/builtin/congress-trades/headless.ts +145 -0
  74. package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
  75. package/src/plugins/builtin/credit-conditions/client.ts +1 -1
  76. package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
  77. package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
  78. package/src/plugins/builtin/credit-conditions/index.tsx +4 -0
  79. package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
  80. package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
  81. package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
  82. package/src/plugins/builtin/dividend-yield/model.ts +4 -22
  83. package/src/plugins/builtin/dividend-yield/view.ts +17 -0
  84. package/src/plugins/builtin/earnings/headless.test.ts +82 -0
  85. package/src/plugins/builtin/earnings/headless.ts +162 -0
  86. package/src/plugins/builtin/earnings/index.tsx +2 -0
  87. package/src/plugins/builtin/earnings-calls/data.ts +29 -8
  88. package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
  89. package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
  90. package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
  91. package/src/plugins/builtin/earnings-calls/model.ts +238 -0
  92. package/src/plugins/builtin/earnings-calls/pane.tsx +4 -0
  93. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +13 -11
  94. package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
  95. package/src/plugins/builtin/fear-greed/headless.ts +84 -0
  96. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  97. package/src/plugins/builtin/holders/client.ts +29 -0
  98. package/src/plugins/builtin/holders/headless.test.ts +58 -0
  99. package/src/plugins/builtin/holders/headless.ts +162 -0
  100. package/src/plugins/builtin/holders/index.tsx +14 -8
  101. package/src/plugins/builtin/holders/pane.tsx +7 -1
  102. package/src/plugins/builtin/insider/client.ts +50 -0
  103. package/src/plugins/builtin/insider/headless.test.ts +122 -0
  104. package/src/plugins/builtin/insider/headless.ts +117 -0
  105. package/src/plugins/builtin/insider/index.tsx +21 -50
  106. package/src/plugins/builtin/insider/insider-data.ts +1 -1
  107. package/src/plugins/builtin/insider/model.ts +72 -0
  108. package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
  109. package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
  110. package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
  111. package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
  112. package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
  113. package/src/plugins/builtin/market-movers/client.ts +76 -0
  114. package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
  115. package/src/plugins/builtin/market-movers/headless.ts +73 -0
  116. package/src/plugins/builtin/market-movers/index.tsx +11 -51
  117. package/src/plugins/builtin/news/headless.test.ts +138 -0
  118. package/src/plugins/builtin/news/headless.ts +190 -0
  119. package/src/plugins/builtin/news/index.tsx +13 -9
  120. package/src/plugins/builtin/news/wire/index.tsx +2 -1
  121. package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
  122. package/src/plugins/builtin/research/analyst-headless.ts +172 -0
  123. package/src/plugins/builtin/research/analyst-model.ts +219 -0
  124. package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
  125. package/src/plugins/builtin/research/client.ts +12 -0
  126. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
  127. package/src/plugins/builtin/research/event-model.ts +245 -0
  128. package/src/plugins/builtin/research/events-headless.test.ts +32 -0
  129. package/src/plugins/builtin/research/events-headless.ts +87 -0
  130. package/src/plugins/builtin/research/headless.test.ts +93 -0
  131. package/src/plugins/builtin/research/headless.ts +161 -0
  132. package/src/plugins/builtin/research/index.tsx +32 -18
  133. package/src/plugins/builtin/research-search/pane.tsx +4 -0
  134. package/src/plugins/builtin/scanner/headless.test.ts +59 -0
  135. package/src/plugins/builtin/scanner/headless.ts +125 -0
  136. package/src/plugins/builtin/scanner/index.ts +4 -0
  137. package/src/plugins/builtin/sec/client.ts +16 -0
  138. package/src/plugins/builtin/sec/headless.test.ts +72 -0
  139. package/src/plugins/builtin/sec/headless.ts +69 -0
  140. package/src/plugins/builtin/sec/index.tsx +20 -75
  141. package/src/plugins/builtin/sec/model.ts +89 -0
  142. package/src/plugins/builtin/sectors/client.ts +54 -0
  143. package/src/plugins/builtin/sectors/headless.test.ts +28 -0
  144. package/src/plugins/builtin/sectors/headless.ts +80 -0
  145. package/src/plugins/builtin/sectors/index.tsx +6 -51
  146. package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
  147. package/src/plugins/builtin/short-interest/client.ts +5 -2
  148. package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
  149. package/src/plugins/builtin/short-interest/headless.ts +111 -0
  150. package/src/plugins/builtin/short-interest/index.tsx +15 -9
  151. package/src/plugins/builtin/thirteenf/data.ts +13 -11
  152. package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
  153. package/src/plugins/builtin/thirteenf/headless.ts +286 -0
  154. package/src/plugins/builtin/thirteenf/index.tsx +4 -0
  155. package/src/plugins/builtin/thirteenf/model.ts +13 -0
  156. package/src/plugins/builtin/thirteenf/table.ts +3 -15
  157. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
  158. package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
  159. package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
  160. package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
  161. package/src/plugins/builtin/volatility/client.ts +16 -3
  162. package/src/plugins/builtin/volatility/headless.test.ts +35 -0
  163. package/src/plugins/builtin/volatility/headless.ts +75 -0
  164. package/src/plugins/builtin/volatility/index.tsx +4 -0
  165. package/src/plugins/builtin/world-indices/client.ts +43 -0
  166. package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
  167. package/src/plugins/builtin/world-indices/headless.ts +109 -0
  168. package/src/plugins/builtin/world-indices/index.tsx +4 -0
  169. package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
  170. package/src/plugins/builtin/yield-curve/headless.ts +67 -0
  171. package/src/plugins/builtin/yield-curve/index.tsx +6 -8
  172. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
  173. package/src/plugins/bundle.test.ts +58 -1
  174. package/src/plugins/bundle.ts +4 -2
  175. package/src/plugins/catalog-backend.test.ts +26 -0
  176. package/src/plugins/catalog-backend.ts +9 -2
  177. package/src/plugins/catalog-browser.test.ts +0 -13
  178. package/src/plugins/catalog-browser.ts +4 -0
  179. package/src/plugins/catalog-manifest.test.ts +15 -0
  180. package/src/plugins/catalog-manifest.ts +12 -5
  181. package/src/plugins/catalog-ui.ts +0 -6
  182. package/src/plugins/host-contract.ts +9 -0
  183. package/src/plugins/host-modules.ts +12 -0
  184. package/src/plugins/loader.ts +63 -15
  185. package/src/plugins/prediction-markets/headless.test.ts +119 -0
  186. package/src/plugins/prediction-markets/headless.ts +255 -0
  187. package/src/plugins/prediction-markets/index.tsx +2 -0
  188. package/src/plugins/seed.ts +3 -0
  189. package/src/public/broker.ts +6 -0
  190. package/src/remote/schema.test.ts +65 -0
  191. package/src/remote/schema.ts +322 -39
  192. package/src/remote/types.ts +49 -2
  193. package/src/renderers/browser/app-services.ts +3 -0
  194. package/src/renderers/browser/config-host.ts +24 -8
  195. package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
  196. package/src/renderers/browser/deeplink-bridge.ts +15 -3
  197. package/src/renderers/browser/main.tsx +3 -1
  198. package/src/renderers/browser/research-entry.test.ts +8 -0
  199. package/src/renderers/browser/research-entry.ts +7 -0
  200. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
  201. package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
  202. package/src/renderers/electrobun/bun/external-plugins.ts +20 -4
  203. package/src/renderers/electrobun/view/build-assets.ts +0 -2
  204. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +63 -10
  205. package/src/sources/gloomberb-cloud/news.ts +1 -0
  206. package/src/sync/core-contributors.test.ts +111 -0
  207. package/src/sync/core-contributors.ts +155 -1
  208. package/src/time-series/resolve.test.ts +118 -1
  209. package/src/time-series/resolve.ts +14 -16
  210. package/src/types/config.ts +2 -0
  211. package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
  212. package/src/plugins/broker-sync/native-loader.ts +0 -10
  213. package/src/plugins/broker-sync/normalize.ts +0 -308
  214. package/src/plugins/broker-sync/public.ts +0 -122
  215. package/src/plugins/broker-sync/robinhood-native.ts +0 -367
  216. package/src/plugins/broker-sync/robinhood.ts +0 -114
  217. package/src/plugins/broker-sync/simplefin-native.ts +0 -201
  218. package/src/plugins/broker-sync/simplefin.ts +0 -70
  219. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
@@ -0,0 +1,119 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../types/plugin";
3
+ import {
4
+ createPredictionMarketsHeadless,
5
+ type PredictionMarketsHeadlessDependencies,
6
+ } from "./headless";
7
+ import type { PredictionMarketSummary, PredictionVenue } from "./types";
8
+
9
+ function market(
10
+ key: string,
11
+ venue: PredictionVenue,
12
+ title: string,
13
+ volume24h: number,
14
+ endsAt: string,
15
+ ): PredictionMarketSummary {
16
+ return {
17
+ key,
18
+ venue,
19
+ marketId: `${key}-id`,
20
+ title,
21
+ marketLabel: "Yes",
22
+ eventLabel: title,
23
+ category: title.includes("Fed") ? "Economics" : "Politics",
24
+ tags: [],
25
+ status: "open",
26
+ url: `https://example.com/${key}`,
27
+ description: `${title} market`,
28
+ endsAt,
29
+ updatedAt: "2026-09-04T20:00:00.000Z",
30
+ yesPrice: 0.62,
31
+ noPrice: 0.38,
32
+ yesBid: 0.61,
33
+ yesAsk: 0.63,
34
+ noBid: 0.37,
35
+ noAsk: 0.39,
36
+ spread: 0.02,
37
+ lastTradePrice: 0.62,
38
+ volume24h,
39
+ volume24hUnit: "usd",
40
+ totalVolume: volume24h * 10,
41
+ totalVolumeUnit: "usd",
42
+ openInterest: volume24h / 2,
43
+ openInterestUnit: venue === "kalshi" ? "contracts" : "usd",
44
+ liquidity: volume24h / 4,
45
+ liquidityUnit: "usd",
46
+ };
47
+ }
48
+
49
+ const markets = [
50
+ market("poly-fed", "polymarket", "Will the Fed cut rates?", 2_000_000, "2026-10-01T00:00:00.000Z"),
51
+ market("kalshi-election", "kalshi", "Will the incumbent win?", 500_000, "2026-11-01T00:00:00.000Z"),
52
+ ];
53
+
54
+ function args(
55
+ argument: string | null,
56
+ options: Record<string, string | number | boolean>,
57
+ ): HeadlessPaneLoadArgs {
58
+ return {
59
+ rawArgument: argument ?? "",
60
+ argument,
61
+ symbols: [],
62
+ options,
63
+ };
64
+ }
65
+
66
+ const context = {} as HeadlessPaneContext;
67
+
68
+ function dependencies(seen: string[]): PredictionMarketsHeadlessDependencies {
69
+ return {
70
+ loadPolymarket: async (query) => {
71
+ seen.push(`polymarket:${query}`);
72
+ return markets.filter((entry) => entry.venue === "polymarket");
73
+ },
74
+ loadKalshi: async (query) => {
75
+ seen.push(`kalshi:${query}`);
76
+ return markets.filter((entry) => entry.venue === "kalshi");
77
+ },
78
+ now: () => new Date("2026-09-04T21:00:00.000Z"),
79
+ };
80
+ }
81
+
82
+ describe("prediction markets headless", () => {
83
+ test("projects venue data through the pane's shared row ordering", async () => {
84
+ const definition = createPredictionMarketsHeadless(dependencies([]));
85
+
86
+ const result = await definition.load(args(null, {
87
+ venue: "all",
88
+ category: "all",
89
+ tab: "top",
90
+ limit: 1,
91
+ }), context);
92
+
93
+ expect(result.rows).toEqual([
94
+ expect.objectContaining({
95
+ key: "poly-fed",
96
+ venue: "polymarket",
97
+ yesProbability: 0.62,
98
+ volume24h: 2_000_000,
99
+ }),
100
+ ]);
101
+ expect(result.metadata).toMatchObject({ total: 2, returned: 1, truncated: true });
102
+ });
103
+
104
+ test("uses the shortcut venue prefix without querying the other venue", async () => {
105
+ const seen: string[] = [];
106
+ const definition = createPredictionMarketsHeadless(dependencies(seen));
107
+
108
+ const result = await definition.load(args("polymarket:fed", {
109
+ venue: "all",
110
+ category: "macro",
111
+ tab: "ending",
112
+ limit: 20,
113
+ }), context);
114
+
115
+ expect(seen).toEqual(["polymarket:fed"]);
116
+ expect(result.rows.map((row) => row.key)).toEqual(["poly-fed"]);
117
+ expect(result.metadata).toMatchObject({ venue: "polymarket", query: "fed" });
118
+ });
119
+ });
@@ -0,0 +1,255 @@
1
+ import type {
2
+ HeadlessPaneDefinition,
3
+ HeadlessPaneLoadArgs,
4
+ HeadlessRowsResult,
5
+ } from "../../types/plugin";
6
+ import { PREDICTION_CATEGORY_OPTIONS } from "./categories";
7
+ import {
8
+ filterPredictionMarkets,
9
+ getDefaultPredictionSort,
10
+ sortPredictionMarkets,
11
+ } from "./metrics";
12
+ import { parsePredictionSearchShortcut } from "./navigation";
13
+ import { buildPredictionListRows } from "./rows";
14
+ import { loadKalshiCatalog } from "./services/kalshi/adapter";
15
+ import { loadPolymarketCatalog } from "./services/polymarket/adapter";
16
+ import type {
17
+ PredictionBrowseTab,
18
+ PredictionCategoryId,
19
+ PredictionListRow,
20
+ PredictionMarketSummary,
21
+ PredictionVenueScope,
22
+ } from "./types";
23
+
24
+ const PREDICTION_COLUMNS = [
25
+ { key: "venue", header: "Venue" },
26
+ { key: "title", header: "Market" },
27
+ { key: "target", header: "Target" },
28
+ { key: "yesProbability", header: "Yes", align: "right" as const },
29
+ { key: "spread", header: "Spread", align: "right" as const },
30
+ { key: "volume24h", header: "24h volume", align: "right" as const },
31
+ { key: "openInterest", header: "Open interest", align: "right" as const },
32
+ { key: "endsAt", header: "Ends" },
33
+ { key: "status", header: "Status" },
34
+ { key: "category", header: "Category" },
35
+ ];
36
+
37
+ export interface PredictionMarketsHeadlessDependencies {
38
+ loadPolymarket(
39
+ query: string,
40
+ category: PredictionCategoryId,
41
+ limit: number,
42
+ ): Promise<PredictionMarketSummary[]>;
43
+ loadKalshi(
44
+ query: string,
45
+ category: PredictionCategoryId,
46
+ limit: number,
47
+ ): Promise<PredictionMarketSummary[]>;
48
+ now(): Date;
49
+ }
50
+
51
+ const defaultDependencies: PredictionMarketsHeadlessDependencies = {
52
+ loadPolymarket: (query, category, limit) => loadPolymarketCatalog(
53
+ query,
54
+ category,
55
+ { limit },
56
+ ),
57
+ loadKalshi: (query, category, limit) => loadKalshiCatalog(
58
+ query,
59
+ category,
60
+ { limit },
61
+ ),
62
+ now: () => new Date(),
63
+ };
64
+
65
+ interface PredictionSelection {
66
+ query: string;
67
+ venue: PredictionVenueScope;
68
+ category: PredictionCategoryId;
69
+ tab: PredictionBrowseTab;
70
+ limit: number;
71
+ }
72
+
73
+ function selectionFor(args: HeadlessPaneLoadArgs): PredictionSelection {
74
+ const parsed = parsePredictionSearchShortcut(
75
+ typeof args.argument === "string" ? args.argument : "",
76
+ );
77
+ const optionVenue = String(args.options.venue ?? "all") as PredictionVenueScope;
78
+ return {
79
+ query: parsed.searchQuery,
80
+ venue: optionVenue === "all" ? parsed.venueScope : optionVenue,
81
+ category: String(args.options.category ?? "all") as PredictionCategoryId,
82
+ tab: String(args.options.tab ?? "top") as PredictionBrowseTab,
83
+ limit: Number(args.options.limit ?? 50),
84
+ };
85
+ }
86
+
87
+ function rowForHeadless(row: PredictionListRow) {
88
+ const focusMarket = row.markets.find((market) => market.key === row.focusMarketKey)
89
+ ?? row.representative;
90
+ return {
91
+ key: row.key,
92
+ kind: row.kind,
93
+ venue: row.venue,
94
+ title: row.title,
95
+ target: row.focusMarketLabel,
96
+ yesProbability: row.focusYesPrice,
97
+ noProbability: focusMarket.noPrice,
98
+ targetMarketId: focusMarket.marketId,
99
+ marketId: row.marketId,
100
+ event: row.eventLabel,
101
+ category: row.category ?? null,
102
+ tags: row.tags ?? [],
103
+ status: row.status,
104
+ url: row.url,
105
+ description: row.description,
106
+ endsAt: row.endsAt,
107
+ updatedAt: row.updatedAt,
108
+ spread: row.spread,
109
+ lastTradePrice: row.lastTradePrice,
110
+ volume24h: row.volume24h,
111
+ volume24hUnit: row.volume24hUnit,
112
+ totalVolume: row.totalVolume,
113
+ totalVolumeUnit: row.totalVolumeUnit,
114
+ openInterest: row.openInterest,
115
+ openInterestUnit: row.openInterestUnit,
116
+ liquidity: row.liquidity,
117
+ liquidityUnit: row.liquidityUnit,
118
+ marketCount: row.kind === "group" ? row.marketCount : 1,
119
+ yesProbabilityLow: row.kind === "group" ? row.yesPriceLow : row.yesPrice,
120
+ yesProbabilityHigh: row.kind === "group" ? row.yesPriceHigh : row.yesPrice,
121
+ };
122
+ }
123
+
124
+ export function projectPredictionMarketsHeadless(
125
+ markets: PredictionMarketSummary[],
126
+ errors: string[],
127
+ args: HeadlessPaneLoadArgs,
128
+ now: Date,
129
+ ): HeadlessRowsResult {
130
+ const selection = selectionFor(args);
131
+ const rows = sortPredictionMarkets(
132
+ filterPredictionMarkets(
133
+ buildPredictionListRows(markets),
134
+ selection.tab,
135
+ selection.venue,
136
+ selection.category,
137
+ selection.query,
138
+ new Set(),
139
+ ),
140
+ getDefaultPredictionSort(selection.tab),
141
+ );
142
+ const selectedRows = rows.slice(0, selection.limit).map(rowForHeadless);
143
+
144
+ return {
145
+ columns: PREDICTION_COLUMNS,
146
+ rows: selectedRows,
147
+ errors: errors.length > 0 ? errors : undefined,
148
+ metadata: {
149
+ fetchedAt: now.toISOString(),
150
+ query: selection.query || null,
151
+ venue: selection.venue,
152
+ category: selection.category,
153
+ tab: selection.tab,
154
+ sourceMarkets: markets.length,
155
+ total: rows.length,
156
+ returned: selectedRows.length,
157
+ truncated: selectedRows.length < rows.length,
158
+ },
159
+ };
160
+ }
161
+
162
+ export function createPredictionMarketsHeadless(
163
+ dependencies: PredictionMarketsHeadlessDependencies = defaultDependencies,
164
+ ): HeadlessPaneDefinition<"rows"> {
165
+ return {
166
+ shape: "rows",
167
+ argument: {
168
+ kind: "free-text",
169
+ placeholder: "query",
170
+ description: "Optional market query, including polymarket: or kalshi: shorthand.",
171
+ optional: true,
172
+ },
173
+ options: [
174
+ {
175
+ key: "venue",
176
+ description: "Prediction venue to include.",
177
+ type: "enum",
178
+ values: [
179
+ { value: "all" },
180
+ { value: "polymarket" },
181
+ { value: "kalshi" },
182
+ ],
183
+ defaultValue: "all",
184
+ },
185
+ {
186
+ key: "category",
187
+ description: "Market category to include.",
188
+ type: "enum",
189
+ values: PREDICTION_CATEGORY_OPTIONS.map((option) => ({ value: option.id })),
190
+ defaultValue: "all",
191
+ },
192
+ {
193
+ key: "tab",
194
+ description: "Browse ordering to apply.",
195
+ type: "enum",
196
+ values: [
197
+ { value: "top" },
198
+ { value: "ending" },
199
+ { value: "new" },
200
+ ],
201
+ defaultValue: "top",
202
+ },
203
+ {
204
+ key: "limit",
205
+ description: "Maximum market rows to return.",
206
+ type: "integer",
207
+ defaultValue: 50,
208
+ minimum: 1,
209
+ maximum: 200,
210
+ },
211
+ ],
212
+ columns: PREDICTION_COLUMNS,
213
+ describe: (args) => {
214
+ const selection = selectionFor(args);
215
+ return selection.query
216
+ ? `Prediction Markets | ${selection.query}`
217
+ : "Prediction Markets";
218
+ },
219
+ async load(args) {
220
+ const selection = selectionFor(args);
221
+ const requested: Array<{
222
+ venue: "polymarket" | "kalshi";
223
+ load: Promise<PredictionMarketSummary[]>;
224
+ }> = [];
225
+ if (selection.venue !== "kalshi") {
226
+ requested.push({
227
+ venue: "polymarket",
228
+ load: dependencies.loadPolymarket(selection.query, selection.category, 200),
229
+ });
230
+ }
231
+ if (selection.venue !== "polymarket") {
232
+ requested.push({
233
+ venue: "kalshi",
234
+ load: dependencies.loadKalshi(selection.query, selection.category, 200),
235
+ });
236
+ }
237
+ const settled = await Promise.allSettled(requested.map((request) => request.load));
238
+ const markets: PredictionMarketSummary[] = [];
239
+ const errors: string[] = [];
240
+ for (const [index, result] of settled.entries()) {
241
+ if (result.status === "fulfilled") {
242
+ markets.push(...result.value);
243
+ } else {
244
+ const message = result.reason instanceof Error
245
+ ? result.reason.message
246
+ : String(result.reason);
247
+ errors.push(`${requested[index]?.venue ?? "venue"}: ${message}`);
248
+ }
249
+ }
250
+ return projectPredictionMarketsHeadless(markets, errors, args, dependencies.now());
251
+ },
252
+ };
253
+ }
254
+
255
+ export const predictionMarketsHeadless = createPredictionMarketsHeadless();
@@ -5,6 +5,7 @@ import { PredictionMarketsPane } from "./pane";
5
5
  import { attachPredictionMarketsPersistence, resetPredictionMarketsPersistence } from "./services/fetch";
6
6
  import { predictionMarketsCliCommand } from "./cli";
7
7
  import { predictionChartSeriesCapability } from "./capability";
8
+ import { predictionMarketsHeadless } from "./headless";
8
9
  import {
9
10
  buildPredictionMarketsPaneSettingsDef,
10
11
  createPredictionMarketsPaneSettings,
@@ -59,6 +60,7 @@ export const predictionMarketsPlugin: GloomPlugin = {
59
60
  description: "Open a new prediction markets browser pane",
60
61
  keywords: ["prediction", "markets", "polymarket", "kalshi", "events"],
61
62
  shortcut: { prefix: "PM", argPlaceholder: "query", argKind: "text" },
63
+ headless: predictionMarketsHeadless,
62
64
  createInstance: (_context, options) => {
63
65
  const parsed = parsePredictionSearchShortcut(options?.arg ?? "");
64
66
  return {
@@ -19,6 +19,9 @@ export const EXTRACTED_PLUGINS = [
19
19
  { id: "substack", repo: "gloom-sh/gloomberb-substack", directory: "gloomberb-substack" },
20
20
  { id: "ibkr", repo: "gloom-sh/gloomberb-ibkr", directory: "gloomberb-ibkr" },
21
21
  { id: "ibkr-gateway", repo: "gloom-sh/gloomberb-ibkr-gateway", directory: "gloomberb-ibkr-gateway" },
22
+ { id: "public", repo: "gloom-sh/gloomberb-public", directory: "gloomberb-public" },
23
+ { id: "robinhood", repo: "gloom-sh/gloomberb-robinhood", directory: "gloomberb-robinhood" },
24
+ { id: "simplefin", repo: "gloom-sh/gloomberb-simplefin", directory: "gloomberb-simplefin" },
22
25
  ] as const;
23
26
 
24
27
  export interface SeedResult {
@@ -7,6 +7,12 @@
7
7
  * from the view, since the renderer cannot open sockets itself.
8
8
  */
9
9
  export { getBrokerRemoteClient, setBrokerRemoteClient } from "../brokers/remote-broker-adapter";
10
+ /**
11
+ * Placeholder a broker shows instead of a stored secret, so an edited profile
12
+ * can tell "unchanged" from "cleared". Broker plugins that persist a derived
13
+ * credential need the host's exact string for the round trip to work.
14
+ */
15
+ export { PRESERVED_PASSWORD_HINT } from "../brokers/profile-form";
10
16
  export type { BrokerRemoteClient } from "../brokers/remote-broker-adapter";
11
17
  export type { ResourceStore } from "../data/resource-store";
12
18
  export { resolveTickerFinancialsForInstrument } from "../market-data/coordinator";
@@ -0,0 +1,65 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { TOOL_NAME_PATTERN } from "../plugins/builtin/cloud/askg/protocol";
3
+ import {
4
+ REMOTE_OPERATIONS,
5
+ remoteOperationDescriptors,
6
+ remoteOperationToolName,
7
+ writeTierForSideEffectLevel,
8
+ } from "./schema";
9
+
10
+ function jsonRoundTrip<T>(value: T): T {
11
+ return JSON.parse(JSON.stringify(value)) as T;
12
+ }
13
+
14
+ describe("remote operation schema", () => {
15
+ test("publishes stable input schemas and write tiers", () => {
16
+ expect(REMOTE_OPERATIONS.length).toBeGreaterThan(0);
17
+
18
+ for (const operation of REMOTE_OPERATIONS) {
19
+ expect(operation.title.length).toBeGreaterThan(0);
20
+ expect(operation.inputSchema.type).toBe("object");
21
+ const mappedTier = writeTierForSideEffectLevel(operation.sideEffectLevel);
22
+ if (operation.id === "layout.delete") {
23
+ expect(operation.sideEffectLevel).toBe("local-write");
24
+ expect(operation.writeTier).toBe("user-data");
25
+ } else {
26
+ expect(operation.writeTier).toBe(mappedTier);
27
+ }
28
+ expect(jsonRoundTrip(operation.inputSchema)).toEqual(operation.inputSchema);
29
+ }
30
+
31
+ expect(writeTierForSideEffectLevel("none")).toBe("read");
32
+ expect(writeTierForSideEffectLevel("network-write")).toBe("user-data");
33
+ expect(writeTierForSideEffectLevel("external-trade")).toBe("broker");
34
+ expect(REMOTE_OPERATIONS.find(({ id }) => id === "layout.delete")?.writeTier)
35
+ .toBe("user-data");
36
+ expect(REMOTE_OPERATIONS.find(({ id }) => id === "capability.invoke")?.writeTier)
37
+ .toBe("broker");
38
+ expect(REMOTE_OPERATIONS.find(({ id }) => id === "layout.placePane")?.inputSchema)
39
+ .toMatchObject({
40
+ required: ["paneId", "region"],
41
+ properties: {
42
+ region: { enum: ["left", "right", "top", "bottom", "floating"] },
43
+ },
44
+ });
45
+ });
46
+
47
+ test("serializes operation descriptors without losing fields", () => {
48
+ const descriptors = remoteOperationDescriptors();
49
+
50
+ expect(jsonRoundTrip(descriptors)).toEqual(descriptors);
51
+ expect(descriptors).toHaveLength(REMOTE_OPERATIONS.length);
52
+ expect(new Set(descriptors.map(({ name }) => name)).size).toBe(descriptors.length);
53
+ expect(descriptors.every(({ name }) => new RegExp(TOOL_NAME_PATTERN).test(name)))
54
+ .toBe(true);
55
+ expect(remoteOperationToolName("commandBar.activateResult"))
56
+ .toBe("command_bar.activate_result");
57
+ expect(descriptors[0]).toEqual({
58
+ name: remoteOperationToolName(REMOTE_OPERATIONS[0]!.id),
59
+ title: REMOTE_OPERATIONS[0]!.title,
60
+ description: REMOTE_OPERATIONS[0]!.description,
61
+ writeTier: REMOTE_OPERATIONS[0]!.writeTier,
62
+ inputSchema: REMOTE_OPERATIONS[0]!.inputSchema,
63
+ });
64
+ });
65
+ });