gloomberb 0.11.0 → 0.11.1

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 (222) hide show
  1. package/README.md +29 -7
  2. package/README.zh-CN.md +6 -6
  3. package/package.json +10 -3
  4. package/src/api-client/auth.ts +14 -10
  5. package/src/api-client/data.ts +47 -0
  6. package/src/api-client/index.test.ts +77 -0
  7. package/src/api-client/index.ts +78 -4
  8. package/src/api-client/paths.ts +73 -0
  9. package/src/api-client/request.ts +15 -2
  10. package/src/api-client/socket-health.test.ts +1 -0
  11. package/src/api-client/socket.ts +2 -1
  12. package/src/api-client/types.ts +128 -0
  13. package/src/app/runtime/update.ts +8 -6
  14. package/src/app.tsx +12 -5
  15. package/src/architecture/import-boundaries.test.ts +5 -1
  16. package/src/capabilities/factories.ts +4 -0
  17. package/src/cli/index.ts +10 -4
  18. package/src/cli/registry.ts +1 -1
  19. package/src/components/chart/composite/composite-chart.tsx +4 -3
  20. package/src/components/chart/composite/format.test.ts +34 -0
  21. package/src/components/chart/composite/format.ts +33 -10
  22. package/src/components/chart/core/axis-format.ts +13 -3
  23. package/src/components/chart/core/index.test.ts +7 -0
  24. package/src/components/chart/core/renderer.ts +3 -4
  25. package/src/components/command-bar/surface/index.tsx +4 -1
  26. package/src/components/company-logo.test.ts +19 -0
  27. package/src/components/company-logo.tsx +60 -0
  28. package/src/components/feed-data-table/stack-view.tsx +7 -1
  29. package/src/components/index.ts +1 -0
  30. package/src/components/layout/detached-pane-shell.tsx +11 -5
  31. package/src/components/layout/header.tsx +5 -5
  32. package/src/components/layout/shell/index.tsx +3 -1
  33. package/src/components/onboarding/account-step/form-panel.tsx +4 -2
  34. package/src/components/onboarding/account-step/index.tsx +1 -1
  35. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  36. package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
  37. package/src/components/onboarding/onboarding-wizard.tsx +13 -5
  38. package/src/components/onboarding/wizard-account.ts +4 -23
  39. package/src/components/table-view-shared.test.ts +11 -0
  40. package/src/components/table-view-shared.tsx +26 -0
  41. package/src/components/ui/data-table/index.tsx +19 -1
  42. package/src/components/ui/fields.tsx +5 -1
  43. package/src/i18n/es.ts +10 -1
  44. package/src/i18n/ja.ts +10 -1
  45. package/src/i18n/ko.ts +10 -1
  46. package/src/i18n/zh-cn.ts +10 -1
  47. package/src/i18n/zh-tw.ts +10 -1
  48. package/src/market-data/coordinator/index.test.ts +2 -2
  49. package/src/market-data/coordinator/index.ts +1 -1
  50. package/src/market-data/market/format.test.ts +7 -0
  51. package/src/market-data/market/freshness.ts +5 -2
  52. package/src/market-data/quotes/freshness.test.ts +16 -0
  53. package/src/news/aggregator.test.ts +92 -3
  54. package/src/news/aggregator.ts +98 -18
  55. package/src/news/hooks.test.tsx +2 -0
  56. package/src/news/hooks.ts +27 -1
  57. package/src/news/news-model.ts +5 -2
  58. package/src/news/types.ts +7 -0
  59. package/src/plugins/broker-sync/broker-sync.test.ts +29 -0
  60. package/src/plugins/broker-sync/normalize.ts +35 -5
  61. package/src/plugins/builtin/account-management/pane.tsx +5 -5
  62. package/src/plugins/builtin/alerts/alert-engine.test.ts +38 -1
  63. package/src/plugins/builtin/alerts/alert-engine.ts +30 -1
  64. package/src/plugins/builtin/alerts/index.test.tsx +49 -6
  65. package/src/plugins/builtin/alerts/index.tsx +7 -6
  66. package/src/plugins/builtin/alerts/pane.tsx +49 -1
  67. package/src/plugins/builtin/alerts/quotes.ts +11 -3
  68. package/src/plugins/builtin/alerts/types.ts +1 -0
  69. package/src/plugins/builtin/analytics/index.tsx +8 -1
  70. package/src/plugins/builtin/analytics/pane-model.ts +6 -4
  71. package/src/plugins/builtin/buildout/model/index.ts +1 -1
  72. package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
  73. package/src/plugins/builtin/chart-composer/pane.tsx +22 -1
  74. package/src/plugins/builtin/chat/controller/channels.ts +7 -1
  75. package/src/plugins/builtin/chat/controller/index.ts +6 -8
  76. package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
  77. package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
  78. package/src/plugins/builtin/chat/controller/send.ts +1 -3
  79. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -9
  80. package/src/plugins/builtin/chat/controller/state.ts +1 -3
  81. package/src/plugins/builtin/chat/controller/storage.ts +0 -2
  82. package/src/plugins/builtin/chat/controller/view.ts +2 -2
  83. package/src/plugins/builtin/chat/controller.test.ts +78 -0
  84. package/src/plugins/builtin/chat/profile-popover.ts +1 -1
  85. package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
  86. package/src/plugins/builtin/cloud/auth-commands.ts +21 -47
  87. package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
  88. package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
  89. package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +34 -3
  90. package/src/plugins/builtin/cloud/browser.tsx +9 -0
  91. package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
  92. package/src/plugins/builtin/cloud/device-signin.ts +2 -8
  93. package/src/plugins/builtin/cloud/index.tsx +2 -0
  94. package/src/plugins/builtin/cloud/plugin.tsx +3 -23
  95. package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
  96. package/src/plugins/builtin/cloud-tweets/registration.ts +6 -1
  97. package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
  98. package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
  99. package/src/plugins/builtin/congress-trades/model.ts +69 -1
  100. package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
  101. package/src/plugins/builtin/dividend-yield/client.ts +18 -0
  102. package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
  103. package/src/plugins/builtin/earnings/index.tsx +5 -3
  104. package/src/plugins/builtin/earnings/model.test.ts +19 -1
  105. package/src/plugins/builtin/earnings/model.ts +17 -15
  106. package/src/plugins/builtin/econ/index.test.tsx +6 -1
  107. package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
  108. package/src/plugins/builtin/insider/index.tsx +27 -11
  109. package/src/plugins/builtin/news/index.tsx +10 -6
  110. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
  111. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
  112. package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
  113. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  114. package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
  115. package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
  116. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
  117. package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
  118. package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
  119. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  120. package/src/plugins/builtin/notes/files.ts +80 -0
  121. package/src/plugins/builtin/notes/index.tsx +3 -0
  122. package/src/plugins/builtin/notes/sync.test.ts +49 -0
  123. package/src/plugins/builtin/notes/sync.ts +98 -0
  124. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +20 -0
  125. package/src/plugins/builtin/portfolio-list/account-metrics.ts +11 -6
  126. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
  127. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
  128. package/src/plugins/builtin/portfolio-list/summary/index.tsx +14 -5
  129. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
  130. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +303 -0
  131. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +485 -0
  132. package/src/plugins/builtin/research/index.test.ts +14 -0
  133. package/src/plugins/builtin/research/index.tsx +25 -0
  134. package/src/plugins/builtin/sec/index.tsx +24 -9
  135. package/src/plugins/builtin/sectors/index.tsx +14 -0
  136. package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
  137. package/src/plugins/builtin/shared/public-share.ts +23 -0
  138. package/src/plugins/builtin/substack/api/cache.ts +7 -3
  139. package/src/plugins/builtin/substack/api/loaders.ts +36 -12
  140. package/src/plugins/builtin/substack/api/types.ts +2 -0
  141. package/src/plugins/builtin/substack/pane-state.ts +1 -1
  142. package/src/plugins/builtin/substack/pane.tsx +55 -12
  143. package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
  144. package/src/plugins/builtin/thirteenf/api.ts +40 -18
  145. package/src/plugins/builtin/thirteenf/data.ts +15 -7
  146. package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
  147. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
  148. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
  149. package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
  150. package/src/plugins/catalog-browser.test.ts +68 -0
  151. package/src/plugins/catalog-browser.ts +134 -0
  152. package/src/plugins/ibkr/flex/index.test.ts +19 -0
  153. package/src/plugins/ibkr/flex/index.ts +1 -1
  154. package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
  155. package/src/plugins/prediction-markets/controller/data.ts +7 -1
  156. package/src/plugins/prediction-markets/controller/index.ts +4 -1
  157. package/src/plugins/prediction-markets/pane.tsx +8 -0
  158. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
  159. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
  160. package/src/renderers/browser/app-services.ts +61 -0
  161. package/src/renderers/browser/cloud-transport.test.ts +32 -0
  162. package/src/renderers/browser/cloud-transport.ts +29 -0
  163. package/src/renderers/browser/config-host.ts +62 -0
  164. package/src/renderers/browser/error-boundary.tsx +23 -0
  165. package/src/renderers/browser/main.tsx +61 -0
  166. package/src/renderers/browser/persistence.ts +74 -0
  167. package/src/renderers/browser/storage.test.ts +66 -0
  168. package/src/renderers/browser/storage.ts +50 -0
  169. package/src/renderers/browser/ticker-repository.ts +59 -0
  170. package/src/renderers/browser/ui-host.tsx +40 -0
  171. package/src/renderers/cloudflare/worker.test.ts +133 -0
  172. package/src/renderers/cloudflare/worker.ts +115 -0
  173. package/src/renderers/electrobun/view/app-services.ts +14 -3
  174. package/src/renderers/electrobun/view/build-assets.ts +12 -10
  175. package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
  176. package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
  177. package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
  178. package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
  179. package/src/renderers/electrobun/view/host/box.tsx +11 -6
  180. package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
  181. package/src/renderers/electrobun/view/host/input.tsx +11 -7
  182. package/src/renderers/electrobun/view/main.tsx +2 -0
  183. package/src/renderers/electrobun/view/ui-host.tsx +3 -137
  184. package/src/renderers/opentui/start.tsx +2 -0
  185. package/src/renderers/share/main.tsx +54 -0
  186. package/src/renderers/share/styles.css +19 -0
  187. package/src/renderers/share/view.test.tsx +22 -0
  188. package/src/renderers/share/view.tsx +79 -0
  189. package/src/shares/api.test.ts +56 -0
  190. package/src/shares/api.ts +103 -0
  191. package/src/shares/chart-snapshot.test.ts +34 -0
  192. package/src/shares/chart-snapshot.ts +28 -0
  193. package/src/shares/payload.ts +104 -0
  194. package/src/sources/gloomberb-cloud/index.test.ts +61 -1
  195. package/src/sources/gloomberb-cloud/index.ts +86 -14
  196. package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
  197. package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
  198. package/src/sources/provider-router/history.test.ts +2 -2
  199. package/src/sources/provider-router/news.test.ts +34 -0
  200. package/src/sources/provider-router/news.ts +15 -1
  201. package/src/sources/sec-edgar.test.ts +59 -0
  202. package/src/sources/sec-edgar.ts +63 -23
  203. package/src/sources/yahoo-finance/mappers.ts +3 -0
  204. package/src/sources/yahoo-finance/snapshots.ts +10 -2
  205. package/src/sources/yahoo-finance/symbols.test.ts +10 -0
  206. package/src/sources/yahoo-finance/symbols.ts +13 -8
  207. package/src/sources/yahoo-finance.test.ts +1 -1
  208. package/src/sources/yahoo-finance.ts +6 -2
  209. package/src/sync/baseline.ts +32 -0
  210. package/src/sync/controller.test.ts +67 -0
  211. package/src/sync/controller.ts +32 -1
  212. package/src/sync/core-contributors.test.ts +44 -0
  213. package/src/sync/core-contributors.ts +59 -10
  214. package/src/sync/react.ts +9 -2
  215. package/src/sync/types.ts +12 -0
  216. package/src/tickers/search/index.test.ts +27 -0
  217. package/src/tickers/search/upsert.ts +10 -0
  218. package/src/types/capability-route-source.ts +2 -1
  219. package/src/ui/host.tsx +4 -0
  220. package/src/utils/price-history.ts +11 -10
  221. package/src/utils/sec.test.ts +6 -0
  222. package/src/utils/sec.ts +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="https://gloomberb.com/gloomberb-logo-grayscale.svg" alt="Gloomberb logo" width="76" />
3
+ <img src="https://gloom.sh/gloomberb-logo-grayscale.svg" alt="Gloomberb logo" width="76" />
4
4
 
5
5
  # Gloomberb
6
6
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  Desktop app for macOS and Windows. Terminal UI for macOS, Linux, and Windows.
10
10
 
11
- <a href="https://gloomberb.com/download/desktop"><strong>Download desktop</strong></a>
11
+ <a href="https://gloom.sh/download/desktop"><strong>Download desktop</strong></a>
12
12
  &nbsp;&middot;&nbsp;
13
13
  <a href="#install"><strong>Install the TUI</strong></a>
14
14
  &nbsp;&middot;&nbsp;
@@ -17,7 +17,7 @@ Desktop app for macOS and Windows. Terminal UI for macOS, Linux, and Windows.
17
17
  <br />
18
18
  <br />
19
19
 
20
- <img src="https://gloomberb.com/landing-terminal.png" alt="Gloomberb terminal showing portfolio, watchlists, market data, and chart panels." width="720" />
20
+ <img src="https://gloom.sh/landing-terminal.png" alt="Gloomberb terminal showing portfolio, watchlists, market data, and chart panels." width="720" />
21
21
 
22
22
  </div>
23
23
 
@@ -29,8 +29,24 @@ Gloomberb has two ways in:
29
29
  |---------|----------|-------------|
30
30
  | Desktop app | A polished app window, pop-out panes, OS shortcuts, and built-in updates | Published for macOS and Windows. It also installs a `gloomberb` terminal command for the TUI. |
31
31
  | Terminal UI | Fast keyboard workflows inside your terminal, SSH/dev boxes, Linux machines, and script-friendly setups | Runs with `gloomberb` on macOS, Linux, and Windows. |
32
+ | Browser app | The shared DOM interface without an install | Open [term.gloom.sh](https://term.gloom.sh). |
32
33
 
33
- Both share the same command language, plugin system, market data surfaces, portfolios, watchlists, alerts, notes, and AI tools.
34
+ The desktop app and TUI share the full command language and plugin system. The browser app uses the same official DOM renderer and layout with a reviewed browser plugin catalog.
35
+
36
+ ## Browser App
37
+
38
+ [term.gloom.sh](https://term.gloom.sh) works anonymously with configuration, tickers, layouts, session state, and plugin state stored in the browser. Anonymous sessions receive rate-limited, 15-minute-delayed Gloom Cloud market data and read-only chat. Login remains optional and enables sync and chat posting; Pro accounts receive realtime market data. Cloud REST and WebSocket traffic uses the same-origin `/api` path, which the Worker forwards only to `https://api.gloom.sh`; it is not an arbitrary network proxy.
39
+
40
+ The browser build intentionally omits brokers and native integrations, filesystem notes, local AI, external plugins, updater/debug tools, application menus, native window controls, pop-out native windows, and native context menus. Modules that still depend on desktop-only or CORS-blocked feeds are also absent for now: RSS/Substack, prediction markets and polls, market halts/heatmap/movers, dividend/ownership/SEC panes, earnings/IPO, and TV. Public shares open under `/s/:id` in a separate slim bundle. Share creation and owner deletion use the signed-in Gloom Cloud session through the same fixed API path; public reads require no account.
41
+
42
+ Local browser development:
43
+
44
+ ```bash
45
+ bun run web:build
46
+ bunx wrangler dev
47
+ ```
48
+
49
+ Validate the public artifacts with `bun run web:audit` and `bun run cloudflare:dry-run`. `wrangler.jsonc` is intentionally generic and contains no account, route, domain, namespace, or secret. Production routing and the private Gloom Cloud API deployment are configured separately.
34
50
 
35
51
  ## Install
36
52
 
@@ -41,21 +57,21 @@ Install the desktop app and the `gloomberb` terminal command:
41
57
  ```bash
42
58
  brew install --cask vincelwt/tap/gloomberb
43
59
  # or
44
- curl -fsSL gloomberb.com/install | bash
60
+ curl -fsSL gloom.sh/install | bash
45
61
  ```
46
62
 
47
63
  Both install `Gloomberb.app` and a `gloomberb` command that runs the TUI through the app bundle, so the bundled runtime is stored once.
48
64
 
49
65
  Prefer a direct download?
50
66
 
51
- - [Download Gloomberb for Mac](https://gloomberb.com/download/desktop)
67
+ - [Download Gloomberb for Mac](https://gloom.sh/download/desktop)
52
68
 
53
69
  ### Linux
54
70
 
55
71
  Install the standalone TUI binary:
56
72
 
57
73
  ```bash
58
- curl -fsSL gloomberb.com/install | bash
74
+ curl -fsSL gloom.sh/install | bash
59
75
  ```
60
76
 
61
77
  This installs `gloomberb` to `~/.local/bin` by default. A Linux desktop package is not published yet.
@@ -224,6 +240,7 @@ Use `HELP` inside Gloomberb for the live shortcut list. The common command-bar p
224
240
  | `CMP <tickers>` | Normalized price comparison |
225
241
  | `CORR <tickers>` | Ticker return correlations |
226
242
  | `ANR <ticker>` | Analyst targets and ratings |
243
+ | `DIAG <ticker>` | Equity Diagnostic with cited flags and anomalies |
227
244
  | `SEC <ticker>` | SEC filings and company disclosures |
228
245
  | `OMON <ticker>` | Options monitor |
229
246
  | `OVME` | Black-Scholes option calculator with Greeks and implied volatility |
@@ -307,6 +324,11 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
307
324
  | `SB` | Toggle the status bar |
308
325
  | `VF` | Toggle quote value flashing |
309
326
  | `TH <theme>` | Change color theme |
327
+ | `FONT+` / `FONT-` | Increase or decrease desktop font size |
328
+ | `CONN` | Connection health |
329
+ | `POLL` | Prediction-market polls |
330
+ | `ART` | Loaded article lookup |
331
+ | `UPGRADE` | Account upgrade |
310
332
  | `CR` | Cycle chart renderer |
311
333
  | `LANG <locale>` | Change interface language (`auto`, `en`, `es`, `zh-CN`, `zh-TW`, `ja`, or `ko`) |
312
334
  | `PL <plugin>` | Manage plugins |
package/README.zh-CN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="https://gloomberb.com/gloomberb-logo-grayscale.svg" alt="Gloomberb logo" width="76" />
3
+ <img src="https://gloom.sh/gloomberb-logo-grayscale.svg" alt="Gloomberb logo" width="76" />
4
4
 
5
5
  # Gloomberb
6
6
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  桌面应用支持 macOS 和 Windows。终端界面(TUI)支持 macOS、Linux 和 Windows。
10
10
 
11
- <a href="https://gloomberb.com/download/desktop"><strong>下载桌面版</strong></a>
11
+ <a href="https://gloom.sh/download/desktop"><strong>下载桌面版</strong></a>
12
12
  &nbsp;&middot;&nbsp;
13
13
  <a href="#安装">安装 TUI</a>
14
14
  &nbsp;&middot;&nbsp;
@@ -17,7 +17,7 @@
17
17
  <br />
18
18
  <br />
19
19
 
20
- <img src="https://gloomberb.com/landing-terminal.png" alt="Gloomberb 终端界面,显示投资组合、自选列表、市场数据和图表面板。" width="720" />
20
+ <img src="https://gloom.sh/landing-terminal.png" alt="Gloomberb 终端界面,显示投资组合、自选列表、市场数据和图表面板。" width="720" />
21
21
 
22
22
  </div>
23
23
 
@@ -43,21 +43,21 @@ Gloomberb 有两种使用方式:
43
43
  ```bash
44
44
  brew install --cask vincelwt/tap/gloomberb
45
45
  # 或
46
- curl -fsSL gloomberb.com/install | bash
46
+ curl -fsSL gloom.sh/install | bash
47
47
  ```
48
48
 
49
49
  两种方式都会安装 `Gloomberb.app`,以及一个通过应用内置运行时执行 TUI 的 `gloomberb` 命令,运行时只会存储一份。
50
50
 
51
51
  想直接下载?
52
52
 
53
- - [下载 Gloomberb(Mac 版)](https://gloomberb.com/download/desktop)
53
+ - [下载 Gloomberb(Mac 版)](https://gloom.sh/download/desktop)
54
54
 
55
55
  ### Linux
56
56
 
57
57
  安装独立的 TUI 可执行文件:
58
58
 
59
59
  ```bash
60
- curl -fsSL gloomberb.com/install | bash
60
+ curl -fsSL gloom.sh/install | bash
61
61
  ```
62
62
 
63
63
  默认会将 `gloomberb` 安装到 `~/.local/bin`。Linux 桌面安装包暂未发布。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "packageManager": "bun@1.3.11",
5
5
  "description": "Market research and portfolio tracker for the terminal",
6
6
  "module": "src/index.tsx",
@@ -19,15 +19,21 @@
19
19
  "build": "bun run scripts/build.ts",
20
20
  "build:all": "bun run scripts/build.ts --all",
21
21
  "desktop:view:build": "bun run scripts/build-electrobun-view.ts",
22
+ "web:build": "bun run scripts/build-web.ts",
23
+ "web:audit": "bun run web:build && bun run scripts/audit-web-bundle.ts",
24
+ "cloudflare:typecheck": "tsc --project tsconfig.cloudflare.json",
25
+ "cloudflare:dry-run": "bun run web:build && wrangler deploy --dry-run",
22
26
  "desktop:dev": "bun run scripts/electrobun-dev.ts",
23
27
  "desktop:build": "electrobun build",
24
28
  "desktop:release": "electrobun build --env=stable",
25
29
  "desktop:release:windows": "electrobun build --env=stable --targets=win-x64",
26
30
  "i18n:audit": "bun run scripts/i18n-audit.ts",
27
- "typecheck": "bun run typecheck:opentui && bun run typecheck:electrobun-bun && bun run typecheck:electrobun-view && bun run typecheck:scripts",
31
+ "typecheck": "bun run typecheck:opentui && bun run typecheck:electrobun-bun && bun run typecheck:electrobun-view && bun run typecheck:browser && bun run typecheck:cloudflare && bun run typecheck:scripts",
28
32
  "typecheck:opentui": "tsc --project tsconfig.opentui.json",
29
33
  "typecheck:electrobun-bun": "tsc --project tsconfig.electrobun-bun.json",
30
34
  "typecheck:electrobun-view": "tsc --project tsconfig.electrobun-view.json",
35
+ "typecheck:browser": "tsc --project tsconfig.browser.json",
36
+ "typecheck:cloudflare": "tsc --project tsconfig.cloudflare.json",
31
37
  "typecheck:scripts": "tsc --project tsconfig.scripts.json",
32
38
  "test": "bun test",
33
39
  "test:global-upgrade": "bun run scripts/smoke-global-upgrade.ts",
@@ -52,7 +58,8 @@
52
58
  "electrobun": "^1.18.1",
53
59
  "happy-dom": "^20.11.1",
54
60
  "png-to-ico": "^2.1.8",
55
- "rcedit": "^4.0.1"
61
+ "rcedit": "^4.0.1",
62
+ "wrangler": "^4.0.0"
56
63
  },
57
64
  "peerDependencies": {
58
65
  "typescript": "^5.9.2"
@@ -18,6 +18,7 @@ type CloudApiRequest = <T>(path: string, options?: RequestInit) => Promise<T>;
18
18
  interface CloudAuthApiOptions {
19
19
  getCurrentUser(): AuthUser | null;
20
20
  getSessionToken(): string | null;
21
+ hasSessionCredential(): boolean;
21
22
  request: CloudApiRequest;
22
23
  requireCapturedSession(message: string): void;
23
24
  setCurrentUser(user: AuthUser | null): void;
@@ -29,7 +30,7 @@ export class CloudAuthApi {
29
30
  constructor(private readonly options: CloudAuthApiOptions) {}
30
31
 
31
32
  restoreCachedUser(user: PersistedAuthUser | null): void {
32
- if (!this.options.getSessionToken() || !user?.id) {
33
+ if (!this.options.hasSessionCredential() || !user?.id) {
33
34
  this.options.setCurrentUser(null);
34
35
  return;
35
36
  }
@@ -56,15 +57,6 @@ export class CloudAuthApi {
56
57
  });
57
58
  }
58
59
 
59
- async ensureVerifiedSession(): Promise<AuthUser | null> {
60
- if (!this.options.getSessionToken()) return null;
61
- if (!this.options.getCurrentUser()) {
62
- await this.getSession();
63
- }
64
- const user = this.options.getCurrentUser();
65
- return user?.emailVerified ? user : null;
66
- }
67
-
68
60
  async signUp(email: string, username: string, name: string, password: string): Promise<AuthUser> {
69
61
  const result = await this.options.request<{ user: AuthUser }>("/auth/sign-up/email", {
70
62
  method: "POST",
@@ -134,6 +126,18 @@ export class CloudAuthApi {
134
126
  }
135
127
  }
136
128
 
129
+ /**
130
+ * Emails a password reset link. The reset itself completes on the gloom.sh
131
+ * site, so the app only ever sends the email. The server answers the same
132
+ * way whether or not the address exists.
133
+ */
134
+ async requestPasswordReset(email: string): Promise<void> {
135
+ await this.options.request("/auth/request-password-reset", {
136
+ method: "POST",
137
+ body: JSON.stringify({ email }),
138
+ });
139
+ }
140
+
137
141
  async sendVerification(): Promise<CloudVerificationResponse> {
138
142
  return this.options.request<CloudVerificationResponse>("/cloud/auth/send-verification", {
139
143
  method: "POST",
@@ -4,6 +4,10 @@ import { normalizeTweetSearchResponse } from "./normalizers";
4
4
  import {
5
5
  cloudCongressHousePath,
6
6
  cloudExchangeRatePath,
7
+ cloudSec13FPath,
8
+ cloudSecFilingContentPath,
9
+ cloudSecFilingDocumentsPath,
10
+ cloudSecFilingsPath,
7
11
  cloudFredSeriesPath,
8
12
  cloudHistoryPath,
9
13
  cloudMarketSearchPath,
@@ -17,6 +21,8 @@ import {
17
21
  type CloudFredSeriesParams,
18
22
  type CloudHistoryParams,
19
23
  type CloudNewsParams,
24
+ type CloudSecFilingParams,
25
+ type CloudSecFilingsParams,
20
26
  type CloudTickerTweetsParams,
21
27
  type CloudTweetSearchParams,
22
28
  } from "./paths";
@@ -27,6 +33,8 @@ import type {
27
33
  CloudCongressHousePayload,
28
34
  CloudCorporateActionsPayload,
29
35
  CloudEconEventPayload,
36
+ CloudEquityDiagnosticMode,
37
+ CloudEquityDiagnosticResult,
30
38
  CloudFredSeriesPayload,
31
39
  CloudFundamentals,
32
40
  CloudHoldersPayload,
@@ -37,6 +45,9 @@ import type {
37
45
  CloudMarketScreenerPayload,
38
46
  CloudNewsListResponse,
39
47
  CloudNewsPayload,
48
+ CloudSecContentResponse,
49
+ CloudSecDocumentsResponse,
50
+ CloudSecFilingsResponse,
40
51
  CloudOptionsChainPayload,
41
52
  CloudPricePointPayload,
42
53
  CloudQuotePayload,
@@ -163,6 +174,26 @@ export class CloudDataApi {
163
174
  return this.request<CloudMarketResponse<{ rate: number }>>(cloudExchangeRatePath(fromCurrency));
164
175
  }
165
176
 
177
+ /**
178
+ * On-demand single-company evidence review. This is a cloud product endpoint,
179
+ * not a market-data capability, so it is called directly instead of routed
180
+ * through the asset-data provider.
181
+ */
182
+ async getCloudEquityDiagnostic(
183
+ symbol: string,
184
+ exchange?: string,
185
+ mode: CloudEquityDiagnosticMode = "cache-first",
186
+ ): Promise<CloudEquityDiagnosticResult> {
187
+ return this.request<CloudEquityDiagnosticResult>("/research/equity-diagnostic", {
188
+ method: "POST",
189
+ body: JSON.stringify({
190
+ symbol: symbol.trim().toUpperCase(),
191
+ ...(exchange ? { exchange } : {}),
192
+ mode,
193
+ }),
194
+ });
195
+ }
196
+
166
197
  async getCloudEconomicCalendar(): Promise<CloudEconEventPayload[]> {
167
198
  return this.request<CloudEconEventPayload[]>("/cloud/econ/calendar");
168
199
  }
@@ -182,6 +213,22 @@ export class CloudDataApi {
182
213
  return this.request<CloudCongressHousePayload>(cloudCongressHousePath(params));
183
214
  }
184
215
 
216
+ async getCloudSecFilings(params: CloudSecFilingsParams): Promise<CloudSecFilingsResponse> {
217
+ return this.request<CloudSecFilingsResponse>(cloudSecFilingsPath(params));
218
+ }
219
+
220
+ async getCloudSecFilingDocuments(params: CloudSecFilingParams): Promise<CloudSecDocumentsResponse> {
221
+ return this.request<CloudSecDocumentsResponse>(cloudSecFilingDocumentsPath(params));
222
+ }
223
+
224
+ async getCloudSecFilingContent(params: CloudSecFilingParams): Promise<CloudSecContentResponse> {
225
+ return this.request<CloudSecContentResponse>(cloudSecFilingContentPath(params));
226
+ }
227
+
228
+ async getCloudSec13F(path: string, params: Record<string, string | number | undefined> = {}): Promise<unknown> {
229
+ return this.request<unknown>(cloudSec13FPath(path, params));
230
+ }
231
+
185
232
  async getCloudNews(params: CloudNewsParams = {}): Promise<CloudNewsListResponse> {
186
233
  return this.request<CloudNewsListResponse>(cloudNewsPath(params));
187
234
  }
@@ -104,10 +104,53 @@ afterEach(() => {
104
104
  setCloudApiFetchTransport(null);
105
105
  apiClient.setSessionToken(null);
106
106
  apiClient.setWebSocketToken(null);
107
+ apiClient.setCookieSessionMode(false);
107
108
  jest.useRealTimers();
108
109
  });
109
110
 
110
111
  describe("apiClient auth cookies", () => {
112
+ test("accepts a browser-managed api.gloom.sh cookie without exposing its value", async () => {
113
+ apiClient.setCookieSessionMode(true);
114
+ setCloudApiFetchTransport(mockFetch(() => createResponse({ user: verifiedUser })));
115
+
116
+ await expect(apiClient.signIn("test@example.com", "password")).resolves.toEqual(verifiedUser);
117
+ expect(apiClient.getSessionToken()).toBeNull();
118
+ expect(apiClient.isVerified()).toBe(true);
119
+ });
120
+
121
+ test("reports signed-in from the restored user when the cookie hides the raw token", async () => {
122
+ apiClient.setCookieSessionMode(true);
123
+ setCloudApiFetchTransport(mockFetch(() => createResponse({ user: verifiedUser })));
124
+
125
+ expect(apiClient.isSignedIn()).toBe(false);
126
+ await apiClient.signIn("test@example.com", "password");
127
+ expect(apiClient.getSessionToken()).toBeNull();
128
+ expect(apiClient.isSignedIn()).toBe(true);
129
+ });
130
+
131
+ test("coalesces anonymous browser session checks and remembers the result", async () => {
132
+ apiClient.setCookieSessionMode(true);
133
+ let requests = 0;
134
+ let finishRequest!: () => void;
135
+ setCloudApiFetchTransport(async () => {
136
+ requests += 1;
137
+ await new Promise<void>((resolve) => { finishRequest = resolve; });
138
+ return createResponse({ user: null });
139
+ });
140
+
141
+ const checks = [
142
+ apiClient.ensureVerifiedSession(),
143
+ apiClient.ensureVerifiedSession(),
144
+ apiClient.ensureVerifiedSession(),
145
+ ];
146
+ expect(requests).toBe(1);
147
+ finishRequest();
148
+
149
+ await expect(Promise.all(checks)).resolves.toEqual([null, null, null]);
150
+ await expect(apiClient.ensureVerifiedSession()).resolves.toBeNull();
151
+ expect(requests).toBe(1);
152
+ });
153
+
111
154
  test("captures secure session cookies after login and reuses them on session refresh", async () => {
112
155
  const seenCookies: Array<string | null> = [];
113
156
 
@@ -1039,3 +1082,37 @@ describe("apiClient cloud news", () => {
1039
1082
  expect(story.id).toBe("story-1");
1040
1083
  });
1041
1084
  });
1085
+
1086
+ describe("apiClient equity diagnostic", () => {
1087
+ test("posts the symbol, exchange, and cache mode to the research route", async () => {
1088
+ let seenUrl = "";
1089
+ let seenInit: RequestInit | undefined;
1090
+ globalThis.fetch = mockFetch(async (input: Request | string | URL, init?: RequestInit) => {
1091
+ seenUrl = String(input);
1092
+ seenInit = init;
1093
+ return createResponse({ symbol: "AAPL", status: "complete" });
1094
+ });
1095
+
1096
+ await apiClient.getCloudEquityDiagnostic(" aapl ", "NASDAQ", "refresh");
1097
+
1098
+ expect(new URL(seenUrl).pathname).toBe("/research/equity-diagnostic");
1099
+ expect(seenInit?.method).toBe("POST");
1100
+ expect(JSON.parse(String(seenInit?.body))).toEqual({
1101
+ symbol: "AAPL",
1102
+ exchange: "NASDAQ",
1103
+ mode: "refresh",
1104
+ });
1105
+ });
1106
+
1107
+ test("omits an unknown exchange and defaults to the cached answer", async () => {
1108
+ let seenInit: RequestInit | undefined;
1109
+ globalThis.fetch = mockFetch(async (_input: Request | string | URL, init?: RequestInit) => {
1110
+ seenInit = init;
1111
+ return createResponse({ symbol: "AAPL", status: "complete" });
1112
+ });
1113
+
1114
+ await apiClient.getCloudEquityDiagnostic("AAPL");
1115
+
1116
+ expect(JSON.parse(String(seenInit?.body))).toEqual({ symbol: "AAPL", mode: "cache-first" });
1117
+ });
1118
+ });
@@ -8,6 +8,8 @@ import { CloudApiSocket } from "./socket";
8
8
  import type {
9
9
  CloudCongressHouseParams,
10
10
  CloudFredSeriesParams,
11
+ CloudSecFilingParams,
12
+ CloudSecFilingsParams,
11
13
  CloudHistoryParams,
12
14
  CloudNewsParams,
13
15
  CloudTickerTweetsParams,
@@ -40,10 +42,15 @@ import type {
40
42
  CloudCorporateActionsPayload,
41
43
  CloudPricePointPayload,
42
44
  CloudEconEventPayload,
45
+ CloudEquityDiagnosticMode,
46
+ CloudEquityDiagnosticResult,
43
47
  CloudFredSeriesPayload,
44
48
  CloudYieldPointPayload,
45
49
  CloudCongressHousePayload,
46
50
  CloudNewsPayload,
51
+ CloudSecContentResponse,
52
+ CloudSecDocumentsResponse,
53
+ CloudSecFilingsResponse,
47
54
  CloudNewsListResponse,
48
55
  CloudTweetSearchResponse,
49
56
  CloudMarketResponse,
@@ -73,6 +80,8 @@ const ASSIST_REQUEST_TIMEOUT_MS = 6_000;
73
80
 
74
81
  class GloomApiClient {
75
82
  private currentUser: AuthUser | null = null;
83
+ private sessionChecked = false;
84
+ private sessionRequest: Promise<AuthUser | null> | null = null;
76
85
  private readonly currentUserListeners = new Set<() => void>();
77
86
  private readonly transport = new CloudApiRequestTransport();
78
87
  private readonly auth: CloudAuthApi;
@@ -84,6 +93,7 @@ class GloomApiClient {
84
93
  this.auth = new CloudAuthApi({
85
94
  getCurrentUser: () => this.currentUser,
86
95
  getSessionToken: () => this.transport.getSessionToken(),
96
+ hasSessionCredential: () => this.transport.hasSessionCredential(),
87
97
  request: (path, options) => this.request(path, options),
88
98
  requireCapturedSession: (message) => this.requireCapturedSession(message),
89
99
  setCurrentUser: (user) => this.setCurrentUser(user),
@@ -93,6 +103,7 @@ class GloomApiClient {
93
103
  this.socket = new CloudApiSocket({
94
104
  getBaseUrl: () => this.transport.baseUrl,
95
105
  getSocketAuthToken: () => this.getSocketAuthToken(),
106
+ hasSessionCredential: () => this.transport.hasSessionCredential(),
96
107
  hasVerifiedUser: () => this.currentUser?.emailVerified === true,
97
108
  isUsingWebSocketToken: () => !!this.transport.getWebSocketToken(),
98
109
  clearWebSocketTokenForFallback: () => this.transport.clearWebSocketTokenForFallback(),
@@ -123,8 +134,14 @@ class GloomApiClient {
123
134
  return this.transport.getWebSocketToken();
124
135
  }
125
136
 
137
+ setCookieSessionMode(enabled: boolean): void {
138
+ this.sessionChecked = false;
139
+ this.transport.setCookieSessionMode(enabled);
140
+ }
141
+
126
142
  setSessionToken(token: string | null): void {
127
143
  const changed = this.transport.getSessionToken() !== token;
144
+ this.sessionChecked = false;
128
145
  this.transport.setSessionToken(token);
129
146
  if (!token) {
130
147
  this.currentUser = null;
@@ -143,6 +160,15 @@ class GloomApiClient {
143
160
  return this.currentUser;
144
161
  }
145
162
 
163
+ /**
164
+ * Whether a signed-in session exists on this surface. Browser builds keep the
165
+ * session in an HttpOnly cookie, so the raw token is deliberately null there
166
+ * and the restored user is the only signal.
167
+ */
168
+ isSignedIn(): boolean {
169
+ return !!this.transport.getSessionToken() || !!this.currentUser;
170
+ }
171
+
146
172
  /** Notifies when the signed-in user changes, including plan and trial entitlement. */
147
173
  subscribeCurrentUser(listener: () => void): () => void {
148
174
  this.currentUserListeners.add(listener);
@@ -156,7 +182,7 @@ class GloomApiClient {
156
182
  }
157
183
 
158
184
  isVerified(): boolean {
159
- return !!this.transport.getSessionToken() && !!this.currentUser?.emailVerified;
185
+ return this.transport.hasSessionCredential() && !!this.currentUser?.emailVerified;
160
186
  }
161
187
 
162
188
  private setCurrentUser(user: AuthUser | null): void {
@@ -187,7 +213,7 @@ class GloomApiClient {
187
213
  }
188
214
 
189
215
  private requireCapturedSession(message: string): void {
190
- if (this.transport.getSessionToken()) return;
216
+ if (this.transport.hasSessionCredential()) return;
191
217
  this.transport.setWebSocketToken(null);
192
218
  this.setCurrentUser(null);
193
219
  throw new Error(message);
@@ -202,7 +228,9 @@ class GloomApiClient {
202
228
  }
203
229
 
204
230
  async ensureVerifiedSession(): Promise<AuthUser | null> {
205
- return this.auth.ensureVerifiedSession();
231
+ if (!this.transport.hasSessionCredential()) return null;
232
+ if (!this.currentUser && !this.sessionChecked) await this.getSession();
233
+ return this.currentUser?.emailVerified ? this.currentUser : null;
206
234
  }
207
235
 
208
236
  async signUp(email: string, username: string, name: string, password: string): Promise<AuthUser> {
@@ -226,17 +254,39 @@ class GloomApiClient {
226
254
  }
227
255
 
228
256
  async getSession(): Promise<AuthUser | null> {
229
- return this.auth.getSession();
257
+ if (this.sessionRequest) return this.sessionRequest;
258
+ this.sessionRequest = this.auth.getSession();
259
+ try {
260
+ const user = await this.sessionRequest;
261
+ this.sessionChecked = true;
262
+ return user;
263
+ } finally {
264
+ this.sessionRequest = null;
265
+ }
230
266
  }
231
267
 
232
268
  async sendVerification(): Promise<CloudVerificationResponse> {
233
269
  return this.auth.sendVerification();
234
270
  }
235
271
 
272
+ async requestPasswordReset(email: string): Promise<void> {
273
+ return this.auth.requestPasswordReset(email);
274
+ }
275
+
236
276
  async createBrowserHandoff(): Promise<CloudBrowserHandoffResponse> {
237
277
  return this.auth.createBrowserHandoff();
238
278
  }
239
279
 
280
+ /** Creates a Stripe checkout session for Cloud Pro; the URL opens in a browser. */
281
+ async createCloudCheckout(): Promise<{ url: string }> {
282
+ return this.request<{ url: string }>("/stripe/checkout", { method: "POST", body: JSON.stringify({}) });
283
+ }
284
+
285
+ /** Stripe billing portal for an account that already has a subscription. */
286
+ async createBillingPortal(): Promise<{ url: string }> {
287
+ return this.request<{ url: string }>("/stripe/portal", { method: "POST", body: JSON.stringify({}) });
288
+ }
289
+
240
290
  async getAccountProfile(): Promise<AccountProfile> {
241
291
  return this.auth.getAccountProfile();
242
292
  }
@@ -513,6 +563,14 @@ class GloomApiClient {
513
563
  return this.data.getCloudEconomicCalendar();
514
564
  }
515
565
 
566
+ async getCloudEquityDiagnostic(
567
+ symbol: string,
568
+ exchange?: string,
569
+ mode: CloudEquityDiagnosticMode = "cache-first",
570
+ ): Promise<CloudEquityDiagnosticResult> {
571
+ return this.data.getCloudEquityDiagnostic(symbol, exchange, mode);
572
+ }
573
+
516
574
  async getCloudFredSeries(
517
575
  seriesId: string,
518
576
  params: CloudFredSeriesParams = {},
@@ -528,6 +586,22 @@ class GloomApiClient {
528
586
  return this.data.getCloudCongressHouse(params);
529
587
  }
530
588
 
589
+ async getCloudSecFilings(params: CloudSecFilingsParams): Promise<CloudSecFilingsResponse> {
590
+ return this.data.getCloudSecFilings(params);
591
+ }
592
+
593
+ async getCloudSecFilingDocuments(params: CloudSecFilingParams): Promise<CloudSecDocumentsResponse> {
594
+ return this.data.getCloudSecFilingDocuments(params);
595
+ }
596
+
597
+ async getCloudSecFilingContent(params: CloudSecFilingParams): Promise<CloudSecContentResponse> {
598
+ return this.data.getCloudSecFilingContent(params);
599
+ }
600
+
601
+ async getCloudSec13F(path: string, params: Record<string, string | number | undefined> = {}): Promise<unknown> {
602
+ return this.data.getCloudSec13F(path, params);
603
+ }
604
+
531
605
  async getCloudNews(params: CloudNewsParams = {}): Promise<CloudNewsListResponse> {
532
606
  return this.data.getCloudNews(params);
533
607
  }