@gloom-sh/terminal 0.19.0 → 0.19.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 (203) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +23 -1
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  8. package/src/api-client/data.ts +2 -1
  9. package/src/api-client/hiring.ts +3 -0
  10. package/src/api-client/index.ts +4 -0
  11. package/src/api-client/ipo.ts +24 -1
  12. package/src/api-client/mna.ts +3 -0
  13. package/src/api-client/paths.ts +9 -1
  14. package/src/api-client/roadmap.ts +85 -0
  15. package/src/api-client/types.ts +12 -0
  16. package/src/architecture/core-builtin-allowlist.json +2 -2
  17. package/src/cli/commands/market.ts +35 -12
  18. package/src/cli/commands/news-search.ts +22 -0
  19. package/src/cli/commands/overview.ts +86 -20
  20. package/src/cli/commands/ticker.ts +32 -2
  21. package/src/cli/errors.ts +17 -2
  22. package/src/cli/glossary.ts +6 -0
  23. package/src/cli/index-roots.ts +0 -7
  24. package/src/cli/listing-arg.ts +57 -10
  25. package/src/cli/pane-functions/capabilities.ts +1 -0
  26. package/src/cli/pane-functions/discovery.ts +21 -1
  27. package/src/cli/pane-functions/freshness.ts +7 -1
  28. package/src/cli/pane-functions/function-help.ts +7 -0
  29. package/src/cli/pane-functions/index.ts +6 -3
  30. package/src/cli/quote-answer.ts +1 -1
  31. package/src/cli/saved-copy-persistence.ts +88 -0
  32. package/src/components/command-bar/list/model.ts +5 -0
  33. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  34. package/src/components/command-bar/selection-runtime.ts +52 -3
  35. package/src/components/command-bar/surface/index.tsx +2 -0
  36. package/src/components/layout/pane/capture-stamp.ts +2 -2
  37. package/src/components/layout/pane/footer/index.tsx +22 -6
  38. package/src/components/layout/pane/footer/model.ts +83 -15
  39. package/src/components/layout/pane/status-footer.ts +35 -2
  40. package/src/components/layout/status-widget-space.ts +9 -0
  41. package/src/components/ticker/list-table-view.tsx +4 -20
  42. package/src/components/ui/display.tsx +18 -3
  43. package/src/components/ui/icon.tsx +4 -1
  44. package/src/data/plugin-cache.ts +5 -3
  45. package/src/data/saved-copies.ts +8 -0
  46. package/src/docs/snapshot.json +32 -5
  47. package/src/i18n/es.ts +2 -4
  48. package/src/i18n/ja.ts +2 -4
  49. package/src/i18n/ko.ts +2 -4
  50. package/src/i18n/zh-cn.ts +2 -4
  51. package/src/i18n/zh-tw.ts +2 -4
  52. package/src/market-data/depositary-pair.ts +25 -1
  53. package/src/market-data/quotes/metadata.ts +7 -0
  54. package/src/news/aggregator.ts +7 -1
  55. package/src/news/types.ts +6 -0
  56. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  57. package/src/plugins/builtin/alerts/cli.ts +34 -11
  58. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  59. package/src/plugins/builtin/alerts/events-pane.tsx +15 -6
  60. package/src/plugins/builtin/alerts/headless.ts +10 -6
  61. package/src/plugins/builtin/cds/client.ts +142 -30
  62. package/src/plugins/builtin/cds/headless.ts +35 -8
  63. package/src/plugins/builtin/cds/model.ts +145 -18
  64. package/src/plugins/builtin/cds/pane.tsx +16 -5
  65. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  66. package/src/plugins/builtin/chart-composer/index.tsx +4 -5
  67. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  68. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  69. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  70. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  71. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  72. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  73. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  74. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  75. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  76. package/src/plugins/builtin/clinical-trials/model.ts +86 -22
  77. package/src/plugins/builtin/clinical-trials/pane.tsx +46 -27
  78. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  79. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +87 -9
  80. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  81. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  82. package/src/plugins/builtin/company-attention/pane.tsx +3 -0
  83. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  84. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  85. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  86. package/src/plugins/builtin/composite-plugins.ts +2 -1
  87. package/src/plugins/builtin/credit-documents/headless.ts +3 -3
  88. package/src/plugins/builtin/doe/headless.ts +3 -2
  89. package/src/plugins/builtin/doe/pane.tsx +13 -3
  90. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  91. package/src/plugins/builtin/earnings-calls/headless.ts +13 -3
  92. package/src/plugins/builtin/earnings-calls/pane.tsx +7 -0
  93. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  94. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  95. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  96. package/src/plugins/builtin/econ/figures.ts +48 -0
  97. package/src/plugins/builtin/econ/index.tsx +14 -5
  98. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  99. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  100. package/src/plugins/builtin/econ-statistics/headless.ts +10 -2
  101. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  102. package/src/plugins/builtin/econ-statistics/stats.ts +3 -0
  103. package/src/plugins/builtin/help/index.tsx +61 -59
  104. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  105. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  106. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  107. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  108. package/src/plugins/builtin/holders/headless.ts +4 -2
  109. package/src/plugins/builtin/holders/pane.tsx +3 -0
  110. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  111. package/src/plugins/builtin/insider/headless.ts +2 -2
  112. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  113. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  114. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  115. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  116. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  117. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  118. package/src/plugins/builtin/macro-day/model.ts +39 -5
  119. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  120. package/src/plugins/builtin/market-movers/client.ts +2 -0
  121. package/src/plugins/builtin/market-movers/headless.ts +3 -1
  122. package/src/plugins/builtin/market-movers/screener.ts +8 -4
  123. package/src/plugins/builtin/mna/headless.ts +2 -0
  124. package/src/plugins/builtin/mna/pane.tsx +3 -1
  125. package/src/plugins/builtin/news/headless.ts +3 -1
  126. package/src/plugins/builtin/news/index.tsx +3 -0
  127. package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -6
  128. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  129. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
  130. package/src/plugins/builtin/portfolio-list/table.tsx +4 -6
  131. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  132. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  133. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  134. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  135. package/src/plugins/builtin/realized-vol/pane.tsx +9 -7
  136. package/src/plugins/builtin/research/event-model.ts +30 -4
  137. package/src/plugins/builtin/research/index.tsx +7 -3
  138. package/src/plugins/builtin/research/relative-valuation-headless.ts +8 -25
  139. package/src/plugins/builtin/research/relative-valuation-pane.tsx +52 -15
  140. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  141. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  142. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  143. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  144. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  145. package/src/plugins/builtin/roadmap/model.ts +67 -0
  146. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  147. package/src/plugins/builtin/scanner/live-headless.ts +21 -2
  148. package/src/plugins/builtin/sec/headless.ts +2 -1
  149. package/src/plugins/builtin/sec/index.tsx +3 -0
  150. package/src/plugins/builtin/shared/cloud-resource.ts +13 -5
  151. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  152. package/src/plugins/builtin/shared/report-freshness.ts +8 -0
  153. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  154. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  155. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  156. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  157. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  158. package/src/plugins/builtin/ticker-detail/overview/model.ts +10 -2
  159. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  160. package/src/plugins/builtin/ticker-detail/pane.tsx +11 -6
  161. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  162. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  163. package/src/plugins/builtin/volatility/pane.tsx +5 -2
  164. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  165. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  166. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  167. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  168. package/src/plugins/catalog-browser.ts +2 -1
  169. package/src/plugins/host-link.ts +1 -1
  170. package/src/portfolio/column-values.ts +11 -7
  171. package/src/renderers/dom/desktop/icons.tsx +7 -0
  172. package/src/renderers/electrobun/bun/desktop/backend.ts +9 -10
  173. package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
  174. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  175. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  176. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  177. package/src/renderers/electrobun/bun/index.ts +26 -2
  178. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  179. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  180. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  181. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  182. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  183. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  184. package/src/renderers/electrobun/view/news-source.ts +2 -0
  185. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  186. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  187. package/src/sources/gloomberb-cloud/index.ts +42 -8
  188. package/src/tickers/company-name-listing.ts +53 -13
  189. package/src/tickers/company-name.ts +6 -1
  190. package/src/tickers/index-roots.ts +7 -0
  191. package/src/tickers/listing-fate.ts +2 -2
  192. package/src/tickers/search/index.ts +6 -2
  193. package/src/tickers/search/ranking.ts +5 -1
  194. package/src/tickers/search/upsert.ts +13 -0
  195. package/src/tickers/ticker-changes.ts +8 -4
  196. package/src/types/company-from.ts +40 -0
  197. package/src/types/data-provider.ts +7 -0
  198. package/src/types/financials.ts +27 -0
  199. package/src/types/headless.ts +6 -0
  200. package/src/utils/crypto-pair.ts +121 -7
  201. package/src/utils/declared-dividends.ts +1 -1
  202. package/src/utils/fundamentals.ts +36 -5
  203. package/src/version.ts +2 -2
@@ -1,4 +1,4 @@
1
- import { canonicalExchange, parsePublicTickerKey } from "./exchanges";
1
+ import { canonicalExchange, EXCHANGE_TIME_ZONES, isKnownExchangeCode, parsePublicTickerKey } from "./exchanges";
2
2
 
3
3
  // The fiat codes a pair against USD is read as FX for, never as a coin. The
4
4
  // list Cloud reads: it serves these pairs by the same rule, so a copy judged
@@ -9,8 +9,8 @@ const FIAT_CURRENCY_CODES = new Set([
9
9
  ]);
10
10
 
11
11
  const USD_COIN_PAIR = /^([A-Z0-9]+)[-/]USD$/;
12
- // The other currencies and coins the crypto venue quotes a coin in (BTC-EUR, ETH-BTC).
13
- const QUOTED_COIN_PAIR = /^([A-Z0-9]+)[-/](?:USDT|USDC|EUR|GBP|BTC|ETH)$/;
12
+ // The other currencies and coins the crypto venue quotes a coin in (BTC-EUR, BTC-JPY, ETH-BTC).
13
+ const QUOTED_COIN_PAIR = /^([A-Z0-9]+)[-/](?:USDT|USDC|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|BTC|ETH)$/;
14
14
 
15
15
  /** A coin quoted in dollars (BTC-USD, ETH/USD). EUR-USD is spot FX, not a coin. */
16
16
  function isUsdCoinPair(symbol: string): boolean {
@@ -35,10 +35,18 @@ export function isRoundTheClockCoin(symbol?: string, exchange?: string): boolean
35
35
  const parsed = symbol ? parsePublicTickerKey(symbol) : null;
36
36
  const venue = canonicalExchange(parsed?.exchange ?? exchange) || canonicalExchange(exchange);
37
37
  if (venue === "CCC" || canonicalExchange(exchange) === "CCC") return true;
38
+ if (parsed && exchangeLineCoinListing(parsed.symbol, venue)) return true;
38
39
  return !!parsed && (isUsdCoinPair(parsed.symbol) || (!venue && isQuotedCoinPair(parsed.symbol)));
39
40
  }
40
41
 
41
- const SLASH_COIN_PAIR = /^([A-Z0-9]{1,15})\/(USD|EUR|GBP|USDT|USDC|BTC)$/;
42
+ const SLASH_COIN_PAIR = /^([A-Z0-9]{1,15})\/(USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
43
+
44
+ /**
45
+ * What the crypto venue (CCC) quotes a coin in: BTC-GBP, BTC-KRW, LINK-ETH.
46
+ * It has no coin in a stablecoin (BTC-USDT) or in most other currencies
47
+ * (BTC-TRY, BTC-BRL).
48
+ */
49
+ const VENUE_QUOTE_LEGS: ReadonlySet<string> = new Set(["USD", "EUR", "GBP", "JPY", "KRW", "CAD", "INR", "CNY", "AUD", "BTC", "ETH"]);
42
50
 
43
51
  let isoCurrencyCodes: ReadonlySet<string> | null = null;
44
52
 
@@ -58,9 +66,114 @@ export function dashedCoinPair(symbol: string): string | null {
58
66
  return `${match[1]}-${match[2]}`;
59
67
  }
60
68
 
69
+ /**
70
+ * The crypto exchanges whose own lines of a coin pair search lists in a currency the crypto venue quotes coins in,
71
+ * and the venue itself. A slash pair saved on any other venue, or on none, is not read as a coin: on the commodity
72
+ * or currency venue it is spot crude, gold by the gram or a currency (WTI/USD, GAU/USD, RUB/USD).
73
+ */
74
+ const COIN_EXCHANGES: ReadonlySet<string> = new Set([
75
+ "CCC", "BIGONE", "BINANCE", "BITBANK", "BITBNS", "BITFINEX", "BITFLYER", "BITHUMB", "BITMART", "BITRUE", "BITSTAMP",
76
+ "BTC MARKETS", "BYBIT", "CEX.IO", "COINBASE PRO", "COINONE", "CRYPTO.COM EXCHANGE", "FREIEXCHANGE", "GATE.IO", "HITBTC",
77
+ "HUOBI", "INDEPENDENT RESERVE", "KRAKEN", "KUCOIN", "LATOKEN", "MEXC", "MXC", "OKEX", "OKX", "POLONIEX", "SYNTHETIC",
78
+ "UNISWAP", "UPBIT", "WAZIRX", "XT.COM", "YOBIT", "ZAIF",
79
+ ]);
80
+
81
+ /** Venues whose slash pairs are commodities or currencies, never coins, whatever a row's type says. */
82
+ const NOT_COIN_VENUES: ReadonlySet<string> = new Set(["COMMODITY", "CCY", "FX", "FOREX", "CURRENCY", "PHYSICAL CURRENCY"]);
83
+
84
+ /**
85
+ * Spot commodities written as a pair: gold, silver, platinum and palladium, gold and silver by the gram (GAU,
86
+ * XAGG), WTI, Brent (XBR) and Urals crude, and copper (HG1). Never a coin, though tokens share the letters
87
+ * (GAU-USD is Gamer Arena).
88
+ */
89
+ const SPOT_COMMODITY_BASES: ReadonlySet<string> = new Set(["XAU", "GAU", "XAG", "XAGG", "XPT", "XPD", "WTI", "XBR", "URALS", "HG1"]);
90
+
91
+ /** A spot commodity's pair (WTI/USD, GAU/USD) or a pair on the commodity or currency venue, which no coin is. */
92
+ export function isSpotCommodityOrCurrencyPair(symbol: string, exchange?: string): boolean {
93
+ const parsed = parsePublicTickerKey(symbol.trim().toUpperCase());
94
+ const named = (parsed.exchange ?? exchange ?? "").trim().toUpperCase();
95
+ const base = /^([A-Z0-9]{1,15})\//.exec(parsed.symbol)?.[1];
96
+ return !!base && (SPOT_COMMODITY_BASES.has(base) || NOT_COIN_VENUES.has(named) || NOT_COIN_VENUES.has(canonicalExchange(named)));
97
+ }
98
+
99
+ /** The crypto venue's listing of a coin pair written with a slash (BTC/GBP); null for a currency, a spot commodity or another leg. */
100
+ function slashCoinPairListing(symbol: string): { symbol: string; exchange: "CCC"; currency: string } | null {
101
+ const match = SLASH_COIN_PAIR.exec(symbol.trim().toUpperCase());
102
+ if (!match) return null;
103
+ const [, base, quote] = match as unknown as [string, string, string];
104
+ if (isIsoCurrencyCode(base) || SPOT_COMMODITY_BASES.has(base) || !VENUE_QUOTE_LEGS.has(quote)) return null;
105
+ return { symbol: `${base}-${quote}`, exchange: "CCC", currency: quote };
106
+ }
107
+
108
+ /**
109
+ * A crypto exchange's own line of a coin pair (BTC/GBP on COINBASE PRO, LINK/EUR on KRAKEN, DOGE/BTC on BINANCE)
110
+ * as the crypto venue's listing of it, in the currency it is quoted in: BTC-GBP on CCC, in pounds. Search offers
111
+ * those lines, and no source quotes them.
112
+ *
113
+ * Only a pair on a crypto exchange (COIN_EXCHANGES) is one, or a search row whose type says it is a coin
114
+ * (`coinRow`) on a venue that is no stock, commodity or currency venue. Null for a currency pair (EUR/USD,
115
+ * RUB/USD), a spot commodity (XAU/USD, WTI/USD), a pair the venue quotes in no such currency (BTC/USDT), a pair
116
+ * with no venue, and a slash on a stock exchange, which is a share class (BRK/B).
117
+ */
118
+ export function exchangeLineCoinListing(
119
+ symbol: string,
120
+ exchange?: string,
121
+ { coinRow = false }: { coinRow?: boolean } = {},
122
+ ): { symbol: string; exchange: "CCC"; currency: string } | null {
123
+ const parsed = parsePublicTickerKey(symbol);
124
+ const listing = slashCoinPairListing(parsed.symbol);
125
+ if (!listing) return null;
126
+ const named = (parsed.exchange ?? exchange ?? "").trim().toUpperCase();
127
+ const venue = canonicalExchange(named);
128
+ if (COIN_EXCHANGES.has(named) || COIN_EXCHANGES.has(venue)) return listing;
129
+ if (!coinRow || !venue || NOT_COIN_VENUES.has(named) || NOT_COIN_VENUES.has(venue)) return null;
130
+ // The crypto venues trade around the clock; a venue the app knows that does not is a stock exchange.
131
+ return isKnownExchangeCode(venue) && EXCHANGE_TIME_ZONES[venue] !== "UTC" ? null : listing;
132
+ }
133
+
134
+ /**
135
+ * A coin pair typed with a slash (btc/gbp) as the crypto venue's listing of it, BTC-GBP on CCC; with a venue
136
+ * (BTC/GBP:KRAKEN), as that exchange's line reads (exchangeLineCoinListing). Null for a currency pair, a spot
137
+ * commodity (wti/usd) and a pair the venue quotes in no such currency.
138
+ */
139
+ export function typedCoinPairListing(query: string): { symbol: string; exchange: "CCC"; currency: string } | null {
140
+ const parsed = parsePublicTickerKey(query.trim().toUpperCase());
141
+ return parsed.exchange ? exchangeLineCoinListing(parsed.symbol, parsed.exchange) : slashCoinPairListing(parsed.symbol);
142
+ }
143
+
144
+ /**
145
+ * Search rows with each crypto exchange's line of a coin pair (ETH/EUR on KRAKEN, COINBASE PRO, BITSTAMP and
146
+ * BINANCE) as the crypto venue's one listing of it, ETH-EUR on CCC in euros, where the first line stood. A pair
147
+ * already offered on the venue is not offered twice. Newer servers send the listing themselves.
148
+ */
149
+ export function withExchangeLinesAsCoinListings<T extends { symbol: string; exchange: string; type: string; currency?: string; brokerContract?: unknown }>(rows: T[]): T[] {
150
+ const offered = new Set(rows.filter((row) => canonicalExchange(row.exchange) === "CCC").map((row) => row.symbol.trim().toUpperCase()));
151
+ let changed = false;
152
+ const mapped = rows.flatMap((row) => {
153
+ const coin = !row.brokerContract && isCoinRowType(row.type) ? exchangeLineCoinListing(row.symbol, row.exchange, { coinRow: true }) : null;
154
+ if (!coin) return [row];
155
+ changed = true;
156
+ if (offered.has(coin.symbol)) return [];
157
+ offered.add(coin.symbol);
158
+ return [{ ...row, symbol: coin.symbol, exchange: coin.exchange, currency: row.currency || coin.currency }];
159
+ });
160
+ return changed ? mapped : rows;
161
+ }
162
+
163
+ /** Whether a search row's type says it is a coin (CRYPTOCURRENCY, Digital Currency). */
164
+ export function isCoinRowType(type: string | undefined): boolean {
165
+ return /CRYPTO|DIGITAL|COIN/i.test(type ?? "");
166
+ }
167
+
168
+ /** The currency or coin a pair on the crypto venue is quoted in: GBP for BTC-GBP; null for anything else. */
169
+ export function coinPairQuoteCurrency(symbol: string): string | null {
170
+ const match = /^([A-Z0-9]{1,15})-([A-Z]{3})$/.exec(parsePublicTickerKey(symbol).symbol);
171
+ return match && !isIsoCurrencyCode(match[1]!) && VENUE_QUOTE_LEGS.has(match[2]!) ? match[2]! : null;
172
+ }
173
+
61
174
  // A base-quote pair such as BTC-USD or SOL-EUR. Equities with a share-class dash (BRK-B) and
62
175
  // currency pairs (EURUSD=X) do not match.
63
- const CRYPTO_PAIR = /^[A-Z0-9]{1,15}-(?:USD|EUR|GBP|USDT|USDC|BTC)$/;
176
+ const CRYPTO_PAIR = /^[A-Z0-9]{1,15}-(?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
64
177
 
65
178
  /**
66
179
  * Wider than isUsdCoinPair, for the hint under a pair that will not load: it
@@ -135,9 +248,10 @@ export const TOP_COIN_NAMES: ReadonlyMap<string, string> = new Map([
135
248
  ["RENDER", "render"],
136
249
  ]);
137
250
 
138
- const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|EUR|GBP|USDT|USDC|BTC)$/;
251
+ const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|ETH)$/;
139
252
  // What a pair's listing name adds after the coin's: its quote currency, an id in brackets.
140
- const PAIR_NAME_SUFFIX = /(?:\s*\([^)]*\))+$|\s+(?:USD|EUR|GBP|USDT|USDC|BTC|US Dollars?)$/i;
253
+ // Not ETH, which ends coins' own names (Lido Staked ETH).
254
+ const PAIR_NAME_SUFFIX = /(?:\s*\([^)]*\))+$|\s+(?:USD|EUR|GBP|JPY|KRW|CAD|INR|CNY|AUD|USDT|USDC|BTC|US Dollars?)$/i;
141
255
 
142
256
  /**
143
257
  * The coin a crypto pair trades, as stories name it: "bitcoin" for BTC-USD
@@ -34,6 +34,6 @@ export function declaredDividends(
34
34
  const at = Date.parse(`${exDate}T00:00:00Z`);
35
35
  if (paid.some((time) => Math.abs(time - at) <= SAME_DISTRIBUTION_DAYS * DAY_MS)) return [];
36
36
  return [{ exDate, amount: row.amount, ...declarationDate ? { declarationDate } : {},
37
- ...recordDate ? { recordDate } : {}, ...payDate ? { payDate } : {} }];
37
+ ...recordDate ? { recordDate } : {}, ...payDate ? { payDate } : {}, ...row.special === true ? { special: true as const } : {} }];
38
38
  }).sort((left, right) => left.exDate.localeCompare(right.exDate));
39
39
  }
@@ -2,6 +2,7 @@ import { formatFxRate } from "../cli/fx-pair";
2
2
  import type { EnterpriseValueCheck, Fundamentals, Quote, TickerFinancials } from "../types/financials";
3
3
  import { quotedFxPair } from "./currency-pair";
4
4
  import { resolveCurrencyUnit } from "./currency-units";
5
+ import { isUsListingExchange } from "./exchanges";
5
6
  import { formatCompact } from "./format";
6
7
  import { formatUtcTime } from "./utc-time";
7
8
 
@@ -119,24 +120,53 @@ export function computedEnterpriseValueNote(fundamentals: Parameters<typeof ente
119
120
  }
120
121
 
121
122
  /** What names the currency of the trailing figures: the fundamentals' own labels, else the statements' on a listing that trades in theirs. */
122
- type FundamentalsCurrencies = Pick<TickerFinancials, "financialCurrency"> & {
123
+ type FundamentalsCurrencies = Pick<TickerFinancials, "financialCurrency" | "statementHistory"> & {
123
124
  fundamentals?: Pick<Fundamentals, "financialCurrency" | "epsCurrency" | "marketCapCurrency"> | null;
124
- quote?: Pick<Quote, "currency"> | null;
125
+ quote?: Pick<Quote, "currency" | "exchangeName" | "listingExchangeName" | "isDepositaryReceipt"> | null;
125
126
  };
126
127
 
128
+ /** What a caller knows of the listing beyond its quote: the day it listed (its IPO's), when known. */
129
+ interface ListingAge {
130
+ listedSince?: string | null;
131
+ now?: Date;
132
+ }
133
+
134
+ /** A listing that started within about a year: its statements may not be served yet. */
135
+ const RECENT_LISTING_DAYS = 372;
136
+
137
+ /**
138
+ * The listing's own currency, standing in for figures with no statements to tell their currency by: only for a
139
+ * listing of the last year or so (a recent IPO such as HAWK) or a US listing in dollars that is no receipt, and
140
+ * never after a statements read that failed. A minor unit (GBp) does not say whether figures are pence or pounds.
141
+ */
142
+ function listingCurrencyFallback(financials: FundamentalsCurrencies | null | undefined, listing: ListingAge): string | undefined {
143
+ const listed = financials?.fundamentals?.marketCapCurrency?.trim() || financials?.quote?.currency?.trim();
144
+ if (!listed || resolveCurrencyUnit(listed).divisor !== 1) return undefined;
145
+ if (financials?.statementHistory?.status === "retryable-failure") return undefined;
146
+ const currency = resolveCurrencyUnit(listed).currency;
147
+ const since = listing.listedSince ? Date.parse(`${listing.listedSince.slice(0, 10)}T00:00:00Z`) : NaN;
148
+ const recent = Number.isFinite(since) && (listing.now ?? new Date()).getTime() - since <= RECENT_LISTING_DAYS * 86_400_000;
149
+ const quote = financials?.quote;
150
+ const home = currency === "USD" && quote?.isDepositaryReceipt !== true
151
+ && isUsListingExchange(quote?.listingExchangeName || quote?.exchangeName);
152
+ return recent || home ? currency : undefined;
153
+ }
154
+
127
155
  /**
128
156
  * The currency of the trailing EPS, revenue, income and cash flows: the one the fundamentals declare for themselves.
129
157
  * A block that declares none is in the statements' currency only when the listing trades in it; on a listing quoted
130
158
  * in another (a receipt in USD beside TWD books, YPFD in ARS beside USD statements) its currency is unknown, since
131
- * Gloom Cloud labels every block whose currency it can tell. Never the quote's.
159
+ * Gloom Cloud labels every block whose currency it can tell. With no statements to tell by, the listing's own
160
+ * currency where that is safe (listingCurrencyFallback).
132
161
  */
133
162
  export function fundamentalsCurrency(
134
163
  financials: FundamentalsCurrencies | null | undefined,
164
+ listing: ListingAge = {},
135
165
  ): string | undefined {
136
166
  const declared = financials?.fundamentals?.financialCurrency?.trim();
137
167
  if (declared) return declared;
138
168
  const statements = financials?.financialCurrency?.trim();
139
- if (!statements) return undefined;
169
+ if (!statements) return listingCurrencyFallback(financials, listing);
140
170
  const listed = financials?.fundamentals?.marketCapCurrency?.trim() || financials?.quote?.currency?.trim();
141
171
  return !listed || resolveCurrencyUnit(listed).currency === resolveCurrencyUnit(statements).currency ? statements : undefined;
142
172
  }
@@ -147,8 +177,9 @@ export function fundamentalsCurrency(
147
177
  */
148
178
  export function fundamentalsEpsCurrency(
149
179
  financials: FundamentalsCurrencies | null | undefined,
180
+ listing: ListingAge = {},
150
181
  ): string | undefined {
151
- return financials?.fundamentals?.epsCurrency?.trim() || fundamentalsCurrency(financials);
182
+ return financials?.fundamentals?.epsCurrency?.trim() || fundamentalsCurrency(financials, listing);
152
183
  }
153
184
 
154
185
  const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
package/src/version.ts CHANGED
@@ -3,7 +3,7 @@ import packageJson from "../package.json" with { type: "json" };
3
3
  export const VERSION = packageJson.version;
4
4
 
5
5
  /**
6
- * The npm package this copy came from: `gloomberb`, or `@gloom-sh/terminal`
7
- * once releases are also published under that name.
6
+ * The npm package this copy came from: `@gloom-sh/terminal` or `gloomberb`,
7
+ * which every release publishes with the same build.
8
8
  */
9
9
  export const PACKAGE_NAME = packageJson.name;