dexterm-cli 0.0.0-stage → 0.1.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 (349) hide show
  1. package/LICENSE +661 -0
  2. package/dist/packages/agents/src/audit.js +122 -0
  3. package/dist/packages/agents/src/bus.js +379 -0
  4. package/dist/packages/agents/src/executor.js +177 -0
  5. package/dist/packages/agents/src/gate.js +176 -0
  6. package/dist/packages/agents/src/identity.js +179 -0
  7. package/dist/packages/agents/src/index.js +15 -0
  8. package/dist/packages/agents/src/proposals.js +153 -0
  9. package/dist/packages/agents/src/schema.js +87 -0
  10. package/dist/packages/agents/src/sdk.js +140 -0
  11. package/dist/packages/agents/src/tiers.js +114 -0
  12. package/dist/packages/agents/src/tools.js +69 -0
  13. package/dist/packages/agents/src/ws.js +158 -0
  14. package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
  15. package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
  16. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
  17. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +67 -0
  18. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -0
  19. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
  20. package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
  21. package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
  22. package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
  23. package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
  24. package/dist/packages/cli/bin/dexterm.mjs +11 -0
  25. package/dist/packages/cli/src/agent-cmd.js +303 -0
  26. package/dist/packages/cli/src/agent-roster.js +26 -0
  27. package/dist/packages/cli/src/analyst/backend.js +526 -0
  28. package/dist/packages/cli/src/analyst/results.js +38 -0
  29. package/dist/packages/cli/src/analyst/session.js +319 -0
  30. package/dist/packages/cli/src/audit.js +51 -0
  31. package/dist/packages/cli/src/backtest-cmd.js +149 -0
  32. package/dist/packages/cli/src/boot-state.js +62 -0
  33. package/dist/packages/cli/src/custom.js +261 -0
  34. package/dist/packages/cli/src/display.js +191 -0
  35. package/dist/packages/cli/src/doctor.js +361 -0
  36. package/dist/packages/cli/src/feed.js +634 -0
  37. package/dist/packages/cli/src/fixtures.js +275 -0
  38. package/dist/packages/cli/src/follow-cmd.js +221 -0
  39. package/dist/packages/cli/src/images-config.js +59 -0
  40. package/dist/packages/cli/src/index.js +31 -0
  41. package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
  42. package/dist/packages/cli/src/larp-record.js +21 -0
  43. package/dist/packages/cli/src/live/copy.js +80 -0
  44. package/dist/packages/cli/src/live/flow.js +233 -0
  45. package/dist/packages/cli/src/live/index.js +509 -0
  46. package/dist/packages/cli/src/live/intel.js +227 -0
  47. package/dist/packages/cli/src/live/newpairs.js +334 -0
  48. package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
  49. package/dist/packages/cli/src/live/screens/token-live.js +459 -0
  50. package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
  51. package/dist/packages/cli/src/live/token.js +585 -0
  52. package/dist/packages/cli/src/live/trade.js +929 -0
  53. package/dist/packages/cli/src/live/watchlist.js +178 -0
  54. package/dist/packages/cli/src/main.js +5583 -0
  55. package/dist/packages/cli/src/narrative-cmd.js +76 -0
  56. package/dist/packages/cli/src/pack-cmd.js +323 -0
  57. package/dist/packages/cli/src/plan-cmd.js +278 -0
  58. package/dist/packages/cli/src/popup.js +810 -0
  59. package/dist/packages/cli/src/rules-cmd.js +131 -0
  60. package/dist/packages/cli/src/rules.js +339 -0
  61. package/dist/packages/cli/src/scan/command.js +57 -0
  62. package/dist/packages/cli/src/scan/engine.js +419 -0
  63. package/dist/packages/cli/src/scan/observe.js +64 -0
  64. package/dist/packages/cli/src/scan/ui.js +98 -0
  65. package/dist/packages/cli/src/screener/filter.js +241 -0
  66. package/dist/packages/cli/src/screener/presets.js +80 -0
  67. package/dist/packages/cli/src/screener/rows.js +146 -0
  68. package/dist/packages/cli/src/screener/rug.js +50 -0
  69. package/dist/packages/cli/src/screener/sort.js +52 -0
  70. package/dist/packages/cli/src/screener/view.js +183 -0
  71. package/dist/packages/cli/src/screens/analyst-session.js +343 -0
  72. package/dist/packages/cli/src/screens/analyst.js +326 -0
  73. package/dist/packages/cli/src/screens/boot.js +74 -0
  74. package/dist/packages/cli/src/screens/bubbles.js +255 -0
  75. package/dist/packages/cli/src/screens/coin.js +33 -0
  76. package/dist/packages/cli/src/screens/follows.js +201 -0
  77. package/dist/packages/cli/src/screens/kol.js +90 -0
  78. package/dist/packages/cli/src/screens/narrative.js +90 -0
  79. package/dist/packages/cli/src/screens/newpairs.js +150 -0
  80. package/dist/packages/cli/src/screens/overlay.js +257 -0
  81. package/dist/packages/cli/src/screens/plans.js +126 -0
  82. package/dist/packages/cli/src/screens/positions.js +176 -0
  83. package/dist/packages/cli/src/screens/rules.js +170 -0
  84. package/dist/packages/cli/src/screens/screener.js +459 -0
  85. package/dist/packages/cli/src/screens/social-panel.js +107 -0
  86. package/dist/packages/cli/src/screens/ticket.js +342 -0
  87. package/dist/packages/cli/src/screens/token.js +312 -0
  88. package/dist/packages/cli/src/screens/trending.js +181 -0
  89. package/dist/packages/cli/src/screens/watchlist.js +104 -0
  90. package/dist/packages/cli/src/social/demo.js +96 -0
  91. package/dist/packages/cli/src/social/view.js +250 -0
  92. package/dist/packages/cli/src/state.js +463 -0
  93. package/dist/packages/cli/src/theme-cmd.js +126 -0
  94. package/dist/packages/cli/src/trade.js +319 -0
  95. package/dist/packages/cli/src/voice-hud.js +42 -0
  96. package/dist/packages/cli/src/wallet-cmd.js +440 -0
  97. package/dist/packages/cli/src/wallet-command.js +31 -0
  98. package/dist/packages/cli/src/widgets.js +208 -0
  99. package/dist/packages/cli/src/workspace-cmd.js +68 -0
  100. package/dist/packages/cli/src/x-cmd.js +154 -0
  101. package/dist/packages/custom/src/config.js +213 -0
  102. package/dist/packages/custom/src/index.js +10 -0
  103. package/dist/packages/custom/src/keymap.js +107 -0
  104. package/dist/packages/custom/src/layout.js +235 -0
  105. package/dist/packages/custom/src/packs.js +633 -0
  106. package/dist/packages/custom/src/rules.js +704 -0
  107. package/dist/packages/custom/src/schema.js +515 -0
  108. package/dist/packages/custom/src/themes.js +176 -0
  109. package/dist/packages/custom/src/widget-html.js +222 -0
  110. package/dist/packages/custom/src/workspace.js +141 -0
  111. package/dist/packages/data/src/analyst-actions.js +133 -0
  112. package/dist/packages/data/src/budget.js +88 -0
  113. package/dist/packages/data/src/cache.js +142 -0
  114. package/dist/packages/data/src/candles.js +168 -0
  115. package/dist/packages/data/src/clock.js +64 -0
  116. package/dist/packages/data/src/credits.js +137 -0
  117. package/dist/packages/data/src/detail.js +97 -0
  118. package/dist/packages/data/src/fast-lane.js +321 -0
  119. package/dist/packages/data/src/fixtures.js +135 -0
  120. package/dist/packages/data/src/health.js +245 -0
  121. package/dist/packages/data/src/http.js +277 -0
  122. package/dist/packages/data/src/images/cache.js +141 -0
  123. package/dist/packages/data/src/images/decode-worker.js +28 -0
  124. package/dist/packages/data/src/images/decode.js +81 -0
  125. package/dist/packages/data/src/images/fetch.js +162 -0
  126. package/dist/packages/data/src/images/gif.js +214 -0
  127. package/dist/packages/data/src/images/index.js +10 -0
  128. package/dist/packages/data/src/images/jpeg.js +544 -0
  129. package/dist/packages/data/src/images/png.js +310 -0
  130. package/dist/packages/data/src/images/service.js +334 -0
  131. package/dist/packages/data/src/images/types.js +27 -0
  132. package/dist/packages/data/src/index.js +208 -0
  133. package/dist/packages/data/src/intel/helius-history.js +59 -0
  134. package/dist/packages/data/src/intel/holder-class.js +125 -0
  135. package/dist/packages/data/src/intel/rules.js +421 -0
  136. package/dist/packages/data/src/intel/scan.js +170 -0
  137. package/dist/packages/data/src/larp/evidence.js +288 -0
  138. package/dist/packages/data/src/larp/links.js +137 -0
  139. package/dist/packages/data/src/larp/robots.js +90 -0
  140. package/dist/packages/data/src/larp/safe-fetch.js +389 -0
  141. package/dist/packages/data/src/limiter.js +341 -0
  142. package/dist/packages/data/src/model.js +12 -0
  143. package/dist/packages/data/src/paper.js +331 -0
  144. package/dist/packages/data/src/rpc-config.js +126 -0
  145. package/dist/packages/data/src/rpc-pool.js +166 -0
  146. package/dist/packages/data/src/social/chain.js +83 -0
  147. package/dist/packages/data/src/social/dedupe.js +205 -0
  148. package/dist/packages/data/src/social/feed.js +462 -0
  149. package/dist/packages/data/src/social/firewall.js +110 -0
  150. package/dist/packages/data/src/social/index.js +11 -0
  151. package/dist/packages/data/src/social/kol.js +269 -0
  152. package/dist/packages/data/src/social/model.js +11 -0
  153. package/dist/packages/data/src/social/rank.js +184 -0
  154. package/dist/packages/data/src/social/rss.js +113 -0
  155. package/dist/packages/data/src/social/summary.js +104 -0
  156. package/dist/packages/data/src/social/venues.js +186 -0
  157. package/dist/packages/data/src/sources/dexscreener.js +170 -0
  158. package/dist/packages/data/src/sources/geckoterminal.js +133 -0
  159. package/dist/packages/data/src/sources/goplus.js +161 -0
  160. package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
  161. package/dist/packages/data/src/sources/jupiter.js +198 -0
  162. package/dist/packages/data/src/sources/pda.js +113 -0
  163. package/dist/packages/data/src/sources/pump-curve.js +58 -0
  164. package/dist/packages/data/src/sources/pumpportal.js +247 -0
  165. package/dist/packages/data/src/sources/rugcheck.js +71 -0
  166. package/dist/packages/data/src/sources/solana-rpc.js +299 -0
  167. package/dist/packages/data/src/sources/solana-ws.js +287 -0
  168. package/dist/packages/data/src/store.js +289 -0
  169. package/dist/packages/data/src/trade/bridge.js +464 -0
  170. package/dist/packages/data/src/trade/caps.js +323 -0
  171. package/dist/packages/data/src/trade/copy/auto.js +385 -0
  172. package/dist/packages/data/src/trade/copy/follows.js +103 -0
  173. package/dist/packages/data/src/trade/copy/index.js +9 -0
  174. package/dist/packages/data/src/trade/copy/mirror.js +91 -0
  175. package/dist/packages/data/src/trade/copy/stats.js +120 -0
  176. package/dist/packages/data/src/trade/copy/tracker.js +158 -0
  177. package/dist/packages/data/src/trade/copy/trades.js +87 -0
  178. package/dist/packages/data/src/trade/copy/types.js +6 -0
  179. package/dist/packages/data/src/trade/executor.js +933 -0
  180. package/dist/packages/data/src/trade/kill-switch.js +47 -0
  181. package/dist/packages/data/src/trade/link-store.js +52 -0
  182. package/dist/packages/data/src/trade/plans/confirm.js +157 -0
  183. package/dist/packages/data/src/trade/plans/engine.js +784 -0
  184. package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
  185. package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
  186. package/dist/packages/data/src/trade/plans/executor.js +11 -0
  187. package/dist/packages/data/src/trade/plans/index.js +12 -0
  188. package/dist/packages/data/src/trade/plans/journal.js +77 -0
  189. package/dist/packages/data/src/trade/plans/mandate.js +129 -0
  190. package/dist/packages/data/src/trade/plans/plan.js +179 -0
  191. package/dist/packages/data/src/trade/plans/service.js +168 -0
  192. package/dist/packages/data/src/trade/plans/types.js +6 -0
  193. package/dist/packages/data/src/trade/rpc-exec.js +229 -0
  194. package/dist/packages/data/src/trade/signer/client.js +197 -0
  195. package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
  196. package/dist/packages/data/src/trade/signer/host.js +97 -0
  197. package/dist/packages/data/src/trade/signer/main.js +121 -0
  198. package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
  199. package/dist/packages/data/src/trade/signer/protocol.js +92 -0
  200. package/dist/packages/data/src/trade/signer/service.js +400 -0
  201. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
  202. package/dist/packages/data/src/trade/sol-usd.js +86 -0
  203. package/dist/packages/data/src/trade/swap-build.js +108 -0
  204. package/dist/packages/data/src/trade/token-delta.js +201 -0
  205. package/dist/packages/data/src/trade/tx-decode.js +632 -0
  206. package/dist/packages/data/src/util.js +24 -0
  207. package/dist/packages/data/src/watchlist.js +176 -0
  208. package/dist/packages/data/src/x/api.js +256 -0
  209. package/dist/packages/data/src/x/budget.js +66 -0
  210. package/dist/packages/data/src/x/config.js +49 -0
  211. package/dist/packages/data/src/x/index.js +10 -0
  212. package/dist/packages/data/src/x/mock-server.js +217 -0
  213. package/dist/packages/data/src/x/oauth.js +242 -0
  214. package/dist/packages/data/src/x/pkce.js +21 -0
  215. package/dist/packages/data/src/x/source.js +285 -0
  216. package/dist/packages/data/src/x/token-store.js +134 -0
  217. package/dist/packages/data/src/x/transport.js +71 -0
  218. package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
  219. package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
  220. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
  221. package/dist/packages/mcp/assets/cases.json +1194 -0
  222. package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
  223. package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
  224. package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
  225. package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
  226. package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
  227. package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
  228. package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
  229. package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
  230. package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
  231. package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
  232. package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
  233. package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
  234. package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
  235. package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
  236. package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
  237. package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
  238. package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
  239. package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
  240. package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
  241. package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
  242. package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
  243. package/dist/packages/mcp/src/dossier.js +78 -0
  244. package/dist/packages/mcp/src/firewall.js +21 -0
  245. package/dist/packages/mcp/src/index.js +22 -0
  246. package/dist/packages/mcp/src/server.js +849 -0
  247. package/dist/packages/mcp/src/user-content.js +434 -0
  248. package/dist/packages/packs/src/backtest.js +183 -0
  249. package/dist/packages/packs/src/digest.js +95 -0
  250. package/dist/packages/packs/src/firewall.js +57 -0
  251. package/dist/packages/packs/src/index.js +19 -0
  252. package/dist/packages/packs/src/larp-eval.js +100 -0
  253. package/dist/packages/packs/src/narrative.js +309 -0
  254. package/dist/packages/packs/src/reason.js +135 -0
  255. package/dist/packages/packs/src/rug-model.js +137 -0
  256. package/dist/packages/packs/src/schema.js +54 -0
  257. package/dist/packages/packs/src/token-dossier.js +174 -0
  258. package/dist/packages/packs/src/verdicts.js +17 -0
  259. package/dist/packages/packs/src/wallet-dossier.js +105 -0
  260. package/dist/packages/popup/src/config.js +71 -0
  261. package/dist/packages/popup/src/dock.js +271 -0
  262. package/dist/packages/popup/src/host.js +210 -0
  263. package/dist/packages/popup/src/index.js +12 -0
  264. package/dist/packages/popup/src/loopback.js +53 -0
  265. package/dist/packages/popup/src/open.js +183 -0
  266. package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
  267. package/dist/packages/popup/src/pages/bubbles.css +23 -0
  268. package/dist/packages/popup/src/pages/bubbles.html +36 -0
  269. package/dist/packages/popup/src/pages/bubbles.js +214 -0
  270. package/dist/packages/popup/src/pages/chart-lib.js +262 -0
  271. package/dist/packages/popup/src/pages/chart.css +50 -0
  272. package/dist/packages/popup/src/pages/chart.html +56 -0
  273. package/dist/packages/popup/src/pages/chart.js +554 -0
  274. package/dist/packages/popup/src/pages/common.css +34 -0
  275. package/dist/packages/popup/src/pages/common.js +187 -0
  276. package/dist/packages/popup/src/pages/feeds.html +20 -0
  277. package/dist/packages/popup/src/pages/feeds.js +77 -0
  278. package/dist/packages/popup/src/pages/narrative.html +20 -0
  279. package/dist/packages/popup/src/pages/narrative.js +61 -0
  280. package/dist/packages/popup/src/pages/plans.css +30 -0
  281. package/dist/packages/popup/src/pages/plans.html +26 -0
  282. package/dist/packages/popup/src/pages/plans.js +82 -0
  283. package/dist/packages/popup/src/pages/roster.css +41 -0
  284. package/dist/packages/popup/src/pages/roster.html +25 -0
  285. package/dist/packages/popup/src/pages/roster.js +75 -0
  286. package/dist/packages/popup/src/pages/token.html +20 -0
  287. package/dist/packages/popup/src/pages/token.js +112 -0
  288. package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
  289. package/dist/packages/popup/src/pages/widget.css +5 -0
  290. package/dist/packages/popup/src/pages/widget.html +21 -0
  291. package/dist/packages/popup/src/pages/widget.js +85 -0
  292. package/dist/packages/popup/src/protocol.js +31 -0
  293. package/dist/packages/popup/src/selftest.js +156 -0
  294. package/dist/packages/popup/src/server.js +582 -0
  295. package/dist/packages/popup/src/sinkproxy.js +167 -0
  296. package/dist/packages/popup/src/widget-html.js +231 -0
  297. package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
  298. package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
  299. package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
  300. package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
  301. package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
  302. package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
  303. package/dist/packages/recorder/src/cli.js +113 -0
  304. package/dist/packages/recorder/src/export.js +71 -0
  305. package/dist/packages/recorder/src/index.js +20 -0
  306. package/dist/packages/recorder/src/labels.js +151 -0
  307. package/dist/packages/recorder/src/open.js +29 -0
  308. package/dist/packages/recorder/src/recorder.js +553 -0
  309. package/dist/packages/recorder/src/schedule.js +43 -0
  310. package/dist/packages/recorder/src/schema.js +135 -0
  311. package/dist/packages/recorder/src/snapshot.js +191 -0
  312. package/dist/packages/recorder/src/sweep.js +52 -0
  313. package/dist/packages/recorder/src/verdicts.js +52 -0
  314. package/dist/packages/tui/src/bubbles.js +415 -0
  315. package/dist/packages/tui/src/canvas.js +370 -0
  316. package/dist/packages/tui/src/chrome.js +85 -0
  317. package/dist/packages/tui/src/color.js +133 -0
  318. package/dist/packages/tui/src/flush.js +62 -0
  319. package/dist/packages/tui/src/fmt.js +188 -0
  320. package/dist/packages/tui/src/fx-trade.js +278 -0
  321. package/dist/packages/tui/src/fx.js +527 -0
  322. package/dist/packages/tui/src/images/blocks.js +141 -0
  323. package/dist/packages/tui/src/images/coin.js +22 -0
  324. package/dist/packages/tui/src/images/detect.js +140 -0
  325. package/dist/packages/tui/src/images/index.js +7 -0
  326. package/dist/packages/tui/src/images/layer.js +146 -0
  327. package/dist/packages/tui/src/images/pixels.js +157 -0
  328. package/dist/packages/tui/src/images/protocols.js +87 -0
  329. package/dist/packages/tui/src/index.js +15 -0
  330. package/dist/packages/tui/src/keymap.js +355 -0
  331. package/dist/packages/tui/src/layout.js +11 -0
  332. package/dist/packages/tui/src/sound.js +177 -0
  333. package/dist/packages/tui/src/theme.js +182 -0
  334. package/dist/packages/tui/src/widgets.js +598 -0
  335. package/dist/packages/voice/src/controller.js +395 -0
  336. package/dist/packages/voice/src/doctor.js +63 -0
  337. package/dist/packages/voice/src/engines.js +44 -0
  338. package/dist/packages/voice/src/index.js +16 -0
  339. package/dist/packages/voice/src/router.js +502 -0
  340. package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
  341. package/dist/packages/voice/src/stt/apple.js +179 -0
  342. package/dist/packages/voice/src/stt/fake.js +34 -0
  343. package/dist/packages/voice/src/stt/whisper.js +102 -0
  344. package/dist/packages/voice/src/summary.js +44 -0
  345. package/dist/packages/voice/src/tts/fake.js +30 -0
  346. package/dist/packages/voice/src/tts/say.js +96 -0
  347. package/dist/packages/voice/src/types.js +3 -0
  348. package/package.json +42 -4
  349. package/README.md +0 -3
@@ -0,0 +1,810 @@
1
+ // CLI ↔ popup-host wiring (task-1290; chart v2 task-1325). Maps the app's live TokenView + active Theme into the
2
+ // popup protocol's view models and theme tokens, and owns the window lifecycle for the token screen's chart
3
+ // popup. The heavy lifting (loopback server, frameless shell, browser fallback) lives in @dexterm/popup;
4
+ // this file is the thin, app-aware adapter the interactive loop calls.
5
+ //
6
+ // task-1325: the chart popup is a TradingView Lightweight Charts page with its OWN timeframe. When it asks
7
+ // for an interval, this adapter answers from TokenLive's in-memory candle cache (1m/1h bases, 5m/15m/4h/1d
8
+ // derived locally) — never a fetch. Every terminal tick re-sends the view model: a full series when the series
9
+ // changed, otherwise just the last two bars ("tail"), so a live tick costs the page one `series.update`.
10
+ // When the coin is held (task-1323 PositionSource) the VM carries the position row + entry/stop/target levels
11
+ // and the fills of the current position from the paper ledger. Read-only: nothing here can trade.
12
+ import { createPopupManager, loadConfig, nativeShellAvailable, TerminalDock, CHART_INTERVALS } from '../../popup/src/index.js';
13
+ import { fmtPrice, fmtNum } from '../../tui/src/index.js';
14
+ import { positionRow } from "./live/trade.js";
15
+ import { signedUsd, signedPctOf, fmtUsdFine, sol as fmtSol, signedSol } from "./screens/positions.js";
16
+ import { supplyRatio, positionPriceCells } from "./display.js";
17
+ /** Map the app's active Theme to the popup protocol's theme-token subset. */
18
+ export function popupThemeFrom(t) {
19
+ return {
20
+ bg: t.bg, panel: t.panel, panelAlt: t.panelAlt, border: t.border, borderFocus: t.borderFocus,
21
+ text: t.text, muted: t.muted, dim: t.dim, accent: t.accent, up: t.up, down: t.down, warn: t.warn, ai: t.ai,
22
+ pump: t.pump, dbc: t.dbc, rain: t.rain, rainPump: t.rainPump, rainDump: t.rainDump, scan: t.scan, glow: t.glow,
23
+ };
24
+ }
25
+ /** Most bars one VM carries (the 1m base is 1000 bars; this only guards a future bigger base). */
26
+ export const CHART_MAX_BARS = 1500;
27
+ export const DEFAULT_CHART_PREFS = { style: 'candles', scale: 'auto', indicators: ['volume', 'tape', 'ema9', 'ema21'] };
28
+ const finitePos = (n) => typeof n === 'number' && Number.isFinite(n) && n > 0;
29
+ /** Token amounts up to trillions (same rule as the terminal's position row). */
30
+ function fmtAmt(n) { return n >= 1e12 ? `${(n / 1e12).toFixed(2)}T` : fmtNum(n); }
31
+ function pxText(usd, solPx) {
32
+ if (usd !== null)
33
+ return fmtPrice(usd);
34
+ if (solPx !== null)
35
+ return `${fmtPrice(solPx).replace('$', '')} SOL`;
36
+ return '—';
37
+ }
38
+ /**
39
+ * The popup's position row cells — the SAME fields, order and formatting as the terminal's task-1323 row
40
+ * (token-live.ts positionPanel): SIDE · SIZE · VALUE · AVG ENTRY · PRICE · UNREALIZED, then REALIZED (only
41
+ * when non-zero) and STOP / TARGET (only when set). The page renders them as text only.
42
+ */
43
+ export function positionCells(r, mode = 'price', supply = null) {
44
+ const usd = r.solUsd !== null;
45
+ const tone = (n) => (n === null ? 'muted' : n > 0 ? 'up' : n < 0 ? 'down' : 'muted');
46
+ const cells = [];
47
+ cells.push({ label: 'SIDE', value: r.side.toUpperCase(), tone: r.side === 'long' ? 'up' : 'down' });
48
+ cells.push({ label: 'SIZE', value: `${fmtAmt(r.tokens)} ${r.symbol}`, tone: 'text' });
49
+ cells.push({ label: 'VALUE', value: r.valueUsd !== null ? fmtUsdFine(r.valueUsd) : r.valueSol !== null ? `${fmtSol(r.valueSol)} SOL` : '—', tone: 'text' });
50
+ // task-1366: in mcap mode (supply known) AVG / PRICE read market cap, exactly like the terminal's row; PnL stays USD
51
+ const pc = positionPriceCells(mode, supply, r.avgEntryUsd, r.priceUsd, pxText(r.avgEntryUsd, r.avgEntrySol), pxText(r.priceUsd, r.priceSol));
52
+ cells.push({ label: pc.avg.label, value: pc.avg.value, tone: 'muted' });
53
+ cells.push({ label: pc.price.label, value: pc.price.value, tone: 'text' });
54
+ const uAmt = usd ? (r.unrealizedUsd !== null ? signedUsd(r.unrealizedUsd) : null) : (r.unrealizedSol !== null ? `${signedSol(r.unrealizedSol)} SOL` : null);
55
+ cells.push({ label: 'UNREALIZED', value: uAmt === null ? '—' : `${uAmt}${r.unrealizedPct !== null ? ` ${signedPctOf(r.unrealizedPct)}` : ''}`, tone: tone(r.unrealizedSol) });
56
+ if (r.realizedSol !== 0)
57
+ cells.push({ label: 'REALIZED', value: usd && r.realizedUsd !== null ? signedUsd(r.realizedUsd) : `${signedSol(r.realizedSol)} SOL`, tone: tone(r.realizedSol) });
58
+ if (r.stopUsd !== null)
59
+ cells.push({ label: 'STOP', value: `${fmtPrice(r.stopUsd)}${r.stopDistPct !== null ? ` ${signedPctOf(r.stopDistPct)}` : ''}`, tone: 'down' });
60
+ if (r.targetUsd !== null)
61
+ cells.push({ label: 'TARGET', value: `${fmtPrice(r.targetUsd)}${r.targetDistPct !== null ? ` ${signedPctOf(r.targetDistPct)}` : ''}`, tone: 'up' });
62
+ return cells;
63
+ }
64
+ /** The chart's position model from the shared task-1323 selector (PositionSource → positionRow). */
65
+ export function chartPositionFrom(held, tickUsd, solUsd, kind, fills = [], mode = 'price', supply = null) {
66
+ const r = positionRow(held, tickUsd, solUsd);
67
+ const caption = r.basis === 'none' ? 'no price yet' : r.solUsd !== null ? `${kind} ledger · SOL $${r.solUsd.toFixed(2)}` : `${kind} ledger · SOL units (no SOL/USD yet)`;
68
+ return {
69
+ kind, side: r.side, symbol: r.symbol, tokens: r.tokens, sizeLabel: `${fmtAmt(r.tokens)} ${r.symbol}`,
70
+ avgEntryUsd: finitePos(r.avgEntryUsd) ? r.avgEntryUsd : null,
71
+ priceUsd: r.priceUsd, unrealizedPct: r.unrealizedPct, stopUsd: r.stopUsd, targetUsd: r.targetUsd,
72
+ caption, cells: positionCells(r, mode, supply), fills,
73
+ };
74
+ }
75
+ /**
76
+ * Fills of the CURRENT position: replay the ledger oldest → newest and keep only the fills since the
77
+ * balance was last flat, so markers from a closed earlier round-trip do not clutter the chart.
78
+ */
79
+ export function currentPositionFills(trades) {
80
+ const sorted = [...trades].sort((a, b) => a.t - b.t);
81
+ let bal = 0n;
82
+ let cycle = [];
83
+ for (const f of sorted) {
84
+ bal += f.tokenDelta;
85
+ const gross = f.grossLamports < 0n ? -f.grossLamports : f.grossLamports;
86
+ const tokRaw = f.tokenDelta < 0n ? -f.tokenDelta : f.tokenDelta;
87
+ cycle.push({ t: f.t, side: f.side, priceUsd: f.priceUsd ?? null, sol: Number(gross) / 1e9, tokens: Number(tokRaw) / 10 ** f.decimals });
88
+ if (bal <= 0n) {
89
+ bal = 0n;
90
+ cycle = [];
91
+ }
92
+ }
93
+ return cycle;
94
+ }
95
+ /**
96
+ * Build the chart popup view model from the live TokenView (candles + header stats; no keys, no commands).
97
+ * With no extras this is the task-1290 VM (the terminal's interval and its visible candles). With extras it
98
+ * is the task-1325 VM: the popup's own interval from the cache, the tape, the feed + live price, the held
99
+ * position and the run's chart preferences.
100
+ */
101
+ export function chartVMFromView(v, x = {}) {
102
+ const iv = x.interval ?? v.interval;
103
+ let candles;
104
+ let cold = false;
105
+ let source;
106
+ if (x.series && x.series.candles.length) {
107
+ candles = x.series.candles.length > CHART_MAX_BARS ? x.series.candles.slice(-CHART_MAX_BARS) : x.series.candles;
108
+ source = iv === v.interval
109
+ ? { state: v.panels.candles.state, stale: v.panels.candles.stale, reason: v.panels.candles.reason }
110
+ : { state: 'ok', stale: x.series.stale, reason: null };
111
+ }
112
+ else if (iv === v.interval) {
113
+ candles = v.candles;
114
+ source = { state: v.panels.candles.state, stale: v.panels.candles.stale, reason: v.panels.candles.reason };
115
+ }
116
+ else {
117
+ candles = [];
118
+ cold = true;
119
+ source = { state: 'loading', stale: false, reason: null };
120
+ }
121
+ const bars = candles.map((c) => ({ t: c.t, o: c.o, h: c.h, l: c.l, c: c.c, v: c.v }));
122
+ const openBarT = bars.length ? bars[bars.length - 1].t : null;
123
+ const vm = {
124
+ symbol: v.symbol, name: v.name, mint: v.mint, interval: iv,
125
+ price: v.price, chg: { m5: v.chg.m5, h1: v.chg.h1, h6: v.chg.h6, h24: v.chg.h24 },
126
+ liq: v.liq, vol24: v.vol24, mcap: v.mcap,
127
+ candles: bars, openBarT, updatedAt: v.updatedAt,
128
+ };
129
+ // task-1290 shape when called without extras (kept byte-compatible for the v1 test and the self-test)
130
+ if (Object.keys(x).length === 0)
131
+ return vm;
132
+ const tick = x.feed === 'LIVE' && finitePos(x.livePrice) ? x.livePrice : v.price;
133
+ vm.mode = 'full';
134
+ vm.seriesKey = `${v.mint}|${iv}|${bars.length ? bars[0].t : 'none'}`;
135
+ vm.cold = cold;
136
+ vm.source = source;
137
+ vm.feed = x.feed ?? 'POLL';
138
+ vm.livePrice = x.feed === 'LIVE' && finitePos(x.livePrice) ? x.livePrice : null;
139
+ vm.trades = v.trades.map((t) => ({ t: t.t, side: t.side, usd: t.usd, price: t.price })); // no wallet addresses
140
+ const mode = x.priceMode === 'mcap' ? 'mcap' : 'price';
141
+ const supply = mode === 'mcap' ? supplyRatio(v.price, v.mcap) : null;
142
+ vm.position = x.held ? chartPositionFrom(x.held, tick, x.solUsd ?? null, x.positionKind ?? 'paper', x.fills ?? [], mode, supply) : null;
143
+ vm.prefs = x.prefs ?? DEFAULT_CHART_PREFS;
144
+ vm.sentAt = x.now ?? Date.now();
145
+ vm.image = x.image ?? null;
146
+ // task-1366: display only — the bars stay USD prices; the page formats the axis as price × supply in mcap mode
147
+ vm.priceMode = mode;
148
+ vm.supply = supply;
149
+ return vm;
150
+ }
151
+ /** Build the token-detail popup view model from the live TokenView (reused by the openPopup interface / Jarvis). */
152
+ export function tokenDetailVMFromView(v, intel = null) {
153
+ return {
154
+ intel,
155
+ symbol: v.symbol, name: v.name, mint: v.mint, launchpad: v.launchpad,
156
+ price: v.price, chg: { m5: v.chg.m5, h1: v.chg.h1, h6: v.chg.h6, h24: v.chg.h24 },
157
+ mcap: v.mcap, fdv: v.fdv, liq: v.liq, vol24: v.vol24, holders: v.holders, top10: v.top10,
158
+ dev: v.dev, lpLocked: v.lpLocked, mintAuth: v.mintAuth, freezeAuth: v.freezeAuth,
159
+ rugScore: v.rug?.score ?? null, risks: (v.rug?.risks ?? []).map((r) => ({ name: r.name, level: r.level })),
160
+ flags: v.flags.map((f) => String(f)), holdersSample: v.holdersSample, updatedAt: v.updatedAt,
161
+ };
162
+ }
163
+ /** "docked right of Terminal" / "screen right (unknown terminal)" — for the TUI toast. */
164
+ export function describeDock(p) {
165
+ if (!p)
166
+ return null;
167
+ const app = p.kind === 'terminal' ? 'Terminal' : p.kind === 'iterm' ? 'iTerm2' : 'the terminal';
168
+ if (p.mode === 'right')
169
+ return `docked right of ${app}`;
170
+ if (p.mode === 'left')
171
+ return `docked left of ${app} (no room on the right)`;
172
+ if (p.mode === 'overlap')
173
+ return `pinned at the screen edge over ${app} (no room beside it)`;
174
+ return 'screen right (terminal not detected)';
175
+ }
176
+ /**
177
+ * Owns the token screen's chart popup. Lazily starts the popup host on first open. If the native shell or a
178
+ * display is unavailable the manager falls back to the OS browser and `onNote` explains it; closing the
179
+ * popup (or quitting) never touches the terminal.
180
+ */
181
+ export class TokenPopups {
182
+ mgr = null;
183
+ chartId = null;
184
+ chartHandle = null;
185
+ theme;
186
+ env;
187
+ onNote;
188
+ onInterval;
189
+ makeManager;
190
+ source;
191
+ syncMinMs;
192
+ now;
193
+ /** The popup's own timeframe (null = follow the terminal's until the popup asks for one). */
194
+ interval = null;
195
+ /** Chart preferences remembered for the run (re-applied on reopen). */
196
+ prefs = { ...DEFAULT_CHART_PREFS, indicators: [...DEFAULT_CHART_PREFS.indicators] };
197
+ ready = false;
198
+ forceFull = true;
199
+ lastKey = null;
200
+ lastLen = 0;
201
+ lastPushAt = -Infinity;
202
+ lastView = null;
203
+ pending = null;
204
+ fillsMemo = null;
205
+ dockOpt;
206
+ imageProvider;
207
+ onHolderSelected;
208
+ /** task-1340 bubble-map popup (independent of the chart: both may be open). */
209
+ bubblesId = null;
210
+ bubblesHandle = null;
211
+ narrativeId = null;
212
+ narrativeHandle = null;
213
+ onPlanCancel;
214
+ plansId = null;
215
+ plansHandle = null;
216
+ onFeedSelected;
217
+ tokenId = null;
218
+ tokenHandle = null;
219
+ feedsId = null;
220
+ feedsHandle = null;
221
+ /** The mint behind each feeds item, in the newest-first order the page indexes (`feed.selected` → { i }). */
222
+ feedsMints = [];
223
+ /** task-1358 Tier 5: open pack widgets by key (`<pack>/<widget>/<id>`). */
224
+ widgets = new Map();
225
+ onWidgetReady;
226
+ dockImpl = null;
227
+ cfg = null;
228
+ /** Where the chart window was placed (null = shell default / browser). */
229
+ placement = null;
230
+ /** True once the user dragged the window away: stop following the terminal until the next open. */
231
+ undocked = false;
232
+ metrics = { openedAt: null, dockProbeMs: null, redocks: 0, openToFirstPaintMs: null, dataToPaintMs: [], intervalRequests: 0, pushes: { full: 0, tail: 0 } };
233
+ native = null;
234
+ constructor(opts) {
235
+ this.theme = opts.theme;
236
+ this.env = opts.env ?? process.env;
237
+ this.onNote = opts.onNote;
238
+ this.onInterval = opts.onInterval;
239
+ this.makeManager = opts.createManager ?? createPopupManager;
240
+ this.source = opts.source ?? null;
241
+ this.syncMinMs = opts.syncMinMs ?? 50;
242
+ this.now = opts.now ?? (() => Date.now());
243
+ this.dockOpt = opts.dock ?? null;
244
+ this.imageProvider = opts.imageProvider;
245
+ this.onHolderSelected = opts.onHolderSelected;
246
+ this.onPlanCancel = opts.onPlanCancel;
247
+ this.onFeedSelected = opts.onFeedSelected;
248
+ this.onWidgetReady = opts.onWidgetReady;
249
+ }
250
+ config() { return (this.cfg ??= loadConfig(this.env)); }
251
+ /** The dock for this run: injected, or a TerminalDock when docking can actually happen (macOS, native shell). */
252
+ dock() {
253
+ if (this.dockOpt !== 'auto')
254
+ return this.dockOpt;
255
+ if (this.dockImpl)
256
+ return this.dockImpl;
257
+ const cfg = this.config();
258
+ if (!cfg.dock.enabled || process.platform !== 'darwin' || !nativeShellAvailable(this.env))
259
+ return null;
260
+ this.dockImpl = new TerminalDock({ env: this.env, width: cfg.dock.width, followEvery: cfg.dock.followEvery });
261
+ return this.dockImpl;
262
+ }
263
+ manager() {
264
+ if (this.mgr)
265
+ return this.mgr;
266
+ const cfg = this.config();
267
+ this.mgr = this.makeManager({
268
+ theme: popupThemeFrom(this.theme), rain: cfg.rain, hotkey: cfg.hotkey, hotkeyClose: cfg.hotkeyClose, env: this.env,
269
+ onWarn: (m) => this.onNote?.(m),
270
+ onEvent: (ev) => this.onPopupEvent(ev),
271
+ ...(this.imageProvider ? { imageProvider: this.imageProvider } : {}),
272
+ });
273
+ return this.mgr;
274
+ }
275
+ /** Popup → TUI events. Payloads were already validated by the server against closed sets (task-1325). */
276
+ onPopupEvent(ev) {
277
+ // task-1358 r4: the native shell's sink proxy refused a connection to somewhere other than the popup server — a
278
+ // widget page (or anything in a WebView) tried to reach a host:port of its own. Nothing left this machine; say so.
279
+ if (ev.ev === 'proxy.refused') {
280
+ this.onNote?.(`popup shell: connection to ${ev.target} refused by the sink proxy (${ev.method}); only the popup server is reachable`);
281
+ return;
282
+ }
283
+ // task-1358 Tier 5: pack widgets emit nothing but the generic ready/close (every other event name is dropped
284
+ // by the server before it gets here, and none of the allow-listed ones does anything for a widget).
285
+ if (ev.ev !== 'probe.rejected') {
286
+ for (const [key, w] of this.widgets) {
287
+ if (w.id !== ev.popupId)
288
+ continue;
289
+ if (ev.ev === 'popup.closed') {
290
+ this.widgets.delete(key);
291
+ return;
292
+ }
293
+ if (ev.ev === 'popup.ready') {
294
+ this.onWidgetReady?.(key);
295
+ return;
296
+ }
297
+ if (ev.ev === 'popup.event' && ev.name === 'popup.close-request') {
298
+ this.closeWidget(key);
299
+ return;
300
+ }
301
+ return;
302
+ }
303
+ }
304
+ // task-1359: the token-detail popup emits only close; the feeds popup may ask to open item i (an index into
305
+ // OUR newest-first list: the mint is resolved here, the page never carries an address or a URL).
306
+ if (this.tokenId && ev.ev !== 'probe.rejected' && ev.popupId === this.tokenId) {
307
+ if (ev.ev === 'popup.closed') {
308
+ this.tokenId = null;
309
+ this.tokenHandle = null;
310
+ return;
311
+ }
312
+ if (ev.ev === 'popup.event' && ev.name === 'popup.close-request')
313
+ this.closeToken();
314
+ return;
315
+ }
316
+ if (this.feedsId && ev.ev !== 'probe.rejected' && ev.popupId === this.feedsId) {
317
+ if (ev.ev === 'popup.closed') {
318
+ this.feedsId = null;
319
+ this.feedsHandle = null;
320
+ return;
321
+ }
322
+ if (ev.ev === 'popup.event') {
323
+ if (ev.name === 'popup.close-request') {
324
+ this.closeFeeds();
325
+ return;
326
+ }
327
+ if (ev.name === 'feed.selected') {
328
+ const i = ev.payload?.i;
329
+ const mint = typeof i === 'number' && Number.isInteger(i) && i >= 0 ? this.feedsMints[i] ?? null : null;
330
+ if (mint)
331
+ this.onFeedSelected?.(mint);
332
+ else
333
+ this.onNote?.('feeds popup: that item names no coin');
334
+ }
335
+ }
336
+ return;
337
+ }
338
+ // task-1356: the narrative-board popup's own events (close only; it emits nothing else)
339
+ if (this.narrativeId && ev.ev !== 'probe.rejected' && ev.popupId === this.narrativeId) {
340
+ if (ev.ev === 'popup.closed') {
341
+ this.narrativeId = null;
342
+ this.narrativeHandle = null;
343
+ return;
344
+ }
345
+ if (ev.ev === 'popup.event' && ev.name === 'popup.close-request') {
346
+ this.closeNarrative();
347
+ return;
348
+ }
349
+ }
350
+ // task-1355: the plans popup's own events (close, and the one write it may ask for: plan.cancel). The id
351
+ // was shape-checked by the server; we re-validate it here (defence in depth) and hand it to the TUI seam,
352
+ // which re-checks it against the live plan list before cancelling. No arm/cap/sign event exists at all.
353
+ if (this.plansId && ev.ev !== 'probe.rejected' && ev.popupId === this.plansId) {
354
+ if (ev.ev === 'popup.closed') {
355
+ this.plansId = null;
356
+ this.plansHandle = null;
357
+ return;
358
+ }
359
+ if (ev.ev === 'popup.event') {
360
+ if (ev.name === 'popup.close-request') {
361
+ this.closePlans();
362
+ return;
363
+ }
364
+ if (ev.name === 'plan.cancel') {
365
+ const id = ev.payload?.id;
366
+ if (typeof id === 'string' && /^plan_[0-9a-z]+_[0-9a-z]+$/.test(id))
367
+ this.onPlanCancel?.(id);
368
+ return;
369
+ }
370
+ }
371
+ return;
372
+ }
373
+ // task-1340: the bubble-map popup's own events (close, holder click)
374
+ if (this.bubblesId && ev.ev !== 'probe.rejected' && ev.popupId === this.bubblesId) {
375
+ if (ev.ev === 'popup.closed') {
376
+ this.bubblesId = null;
377
+ this.bubblesHandle = null;
378
+ return;
379
+ }
380
+ if (ev.ev === 'popup.event') {
381
+ if (ev.name === 'popup.close-request') {
382
+ this.closeBubbles();
383
+ return;
384
+ }
385
+ if (ev.name === 'holder.selected') {
386
+ const a = ev.payload?.address;
387
+ if (typeof a === 'string' && /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(a))
388
+ this.onHolderSelected?.(a);
389
+ return;
390
+ }
391
+ }
392
+ return;
393
+ }
394
+ if (ev.ev === 'popup.closed' && ev.popupId === this.chartId) {
395
+ this.resetChart();
396
+ return;
397
+ }
398
+ if (ev.ev === 'popup.ready' && ev.popupId === this.chartId) {
399
+ this.ready = true;
400
+ return;
401
+ }
402
+ if (ev.ev !== 'popup.event' || ev.popupId !== this.chartId)
403
+ return;
404
+ const p = (ev.payload ?? {});
405
+ switch (ev.name) {
406
+ case 'chart.interval':
407
+ case 'interval.changed': {
408
+ const iv = p.interval;
409
+ if (!CHART_INTERVALS.includes(iv))
410
+ return; // defence in depth; the server already checked
411
+ this.metrics.intervalRequests++;
412
+ this.interval = iv;
413
+ this.forceFull = true;
414
+ this.onInterval?.(iv);
415
+ if (this.lastView)
416
+ this.push(this.lastView); // answered at once from the cache
417
+ return;
418
+ }
419
+ case 'chart.indicator': {
420
+ const id = p.id;
421
+ const set = new Set(this.prefs.indicators);
422
+ if (p.on === true)
423
+ set.add(id);
424
+ else
425
+ set.delete(id);
426
+ this.prefs = { ...this.prefs, indicators: [...set] };
427
+ return;
428
+ }
429
+ case 'chart.style':
430
+ this.prefs = { ...this.prefs, style: p.style };
431
+ return;
432
+ case 'chart.scale':
433
+ this.prefs = { ...this.prefs, scale: p.scale };
434
+ return;
435
+ case 'chart.painted': {
436
+ const ms = Number(p.ms);
437
+ if (p.kind === 'first' && this.metrics.openedAt !== null && this.metrics.openToFirstPaintMs === null)
438
+ this.metrics.openToFirstPaintMs = this.now() - this.metrics.openedAt;
439
+ else if (p.kind === 'data' && Number.isFinite(ms)) {
440
+ this.metrics.dataToPaintMs.push(ms);
441
+ if (this.metrics.dataToPaintMs.length > 200)
442
+ this.metrics.dataToPaintMs.shift();
443
+ }
444
+ return;
445
+ }
446
+ case 'popup.close-request':
447
+ this.closeChart();
448
+ return;
449
+ case 'popup.drag': // the user is moving the window: stop docking it back beside the terminal
450
+ if (!this.undocked) {
451
+ this.undocked = true;
452
+ this.dock()?.stop();
453
+ }
454
+ return;
455
+ default: return;
456
+ }
457
+ }
458
+ chartOpen() { return this.chartId !== null; }
459
+ /** The popup's current timeframe (its own once it asked for one, else the terminal's). */
460
+ chartInterval(view) { return this.interval ?? view?.interval ?? null; }
461
+ async openChart(view) {
462
+ if (this.chartId) {
463
+ this.syncChart(view);
464
+ return { native: this.manager().native, reason: this.manager().reason, dock: describeDock(this.placement) };
465
+ }
466
+ const mgr = this.manager();
467
+ this.metrics.openedAt = this.now();
468
+ this.metrics.openToFirstPaintMs = null;
469
+ this.metrics.dockProbeMs = null;
470
+ this.ready = false;
471
+ this.forceFull = true;
472
+ this.lastKey = null;
473
+ this.lastLen = 0;
474
+ this.undocked = false;
475
+ this.placement = null;
476
+ if (this.interval === null)
477
+ this.interval = view.interval;
478
+ // task-1325: dock beside the terminal — one probe (≤1.2 s; ~0.2 s measured) decides the window's rect.
479
+ const dock = this.dock();
480
+ if (dock) {
481
+ const t = this.now();
482
+ try {
483
+ this.placement = await dock.place(1200);
484
+ }
485
+ catch {
486
+ this.placement = null;
487
+ }
488
+ this.metrics.dockProbeMs = this.now() - t;
489
+ }
490
+ const geom = this.placement ? { ...this.placement.rect, ontop: this.placement.ontop, focus: false } : undefined;
491
+ const vm = this.buildVM(view, true);
492
+ this.lastView = view;
493
+ const handle = await mgr.open({ kind: 'chart', title: view.symbol, data: vm, ...(geom ? { geom } : {}) });
494
+ this.chartId = handle.id;
495
+ this.chartHandle = handle;
496
+ this.native = mgr.native;
497
+ // Follow the terminal (move/resize) only for a real window that is actually docked to a known terminal.
498
+ if (dock && mgr.native && this.placement && this.placement.mode !== 'screen-right' && this.config().dock.follow) {
499
+ dock.follow((p) => { if (this.undocked || !this.chartHandle)
500
+ return; this.placement = p; this.metrics.redocks++; this.chartHandle.setBounds?.(p.rect); });
501
+ }
502
+ return { native: mgr.native, reason: mgr.reason, dock: mgr.native ? describeDock(this.placement) : null };
503
+ }
504
+ /** Build the VM for the popup's interval; tracks the series identity so the next push can be a tail. */
505
+ buildVM(view, full) {
506
+ const src = this.source;
507
+ const iv = this.interval ?? view.interval;
508
+ if (!src)
509
+ return chartVMFromView(view, { interval: iv, prefs: this.prefs, now: this.now() });
510
+ const held = src.positions?.held(view.mint) ?? null;
511
+ const vm = chartVMFromView(view, {
512
+ interval: iv,
513
+ series: src.series(view.mint, iv),
514
+ feed: src.feed(view.mint),
515
+ livePrice: src.livePrice(view.mint),
516
+ held,
517
+ positionKind: held?.kind ?? src.positions?.kind ?? 'paper',
518
+ solUsd: src.solUsd() ?? src.positions?.solUsd() ?? null,
519
+ fills: held ? this.fillsFor(view.mint) : [],
520
+ prefs: this.prefs,
521
+ now: this.now(),
522
+ image: src.imagePath?.(view) ?? null,
523
+ priceMode: src.priceMode?.() ?? 'price',
524
+ });
525
+ const key = vm.seriesKey ?? null;
526
+ const n = vm.candles.length;
527
+ const canTail = !full && this.ready && key !== null && key === this.lastKey && this.lastLen > 0 && n >= this.lastLen && n <= this.lastLen + 1;
528
+ this.lastKey = key;
529
+ this.lastLen = n;
530
+ if (canTail) {
531
+ vm.mode = 'tail';
532
+ vm.candles = vm.candles.slice(-2);
533
+ }
534
+ return vm;
535
+ }
536
+ /** Ledger fills for the held coin, memoised 1 s (the loop pushes up to ~20×/s). */
537
+ fillsFor(mint) {
538
+ const now = this.now();
539
+ if (this.fillsMemo && this.fillsMemo.mint === mint && now - this.fillsMemo.at < 1000)
540
+ return this.fillsMemo.v;
541
+ let v = [];
542
+ try {
543
+ v = currentPositionFills(this.source?.fills(mint) ?? []);
544
+ }
545
+ catch {
546
+ v = [];
547
+ }
548
+ this.fillsMemo = { mint, at: now, v };
549
+ return v;
550
+ }
551
+ push(view) {
552
+ if (!this.chartHandle)
553
+ return;
554
+ const full = this.forceFull || !this.ready;
555
+ const vm = this.buildVM(view, full);
556
+ if (vm.mode === 'tail')
557
+ this.metrics.pushes.tail++;
558
+ else {
559
+ this.metrics.pushes.full++;
560
+ if (this.ready)
561
+ this.forceFull = false;
562
+ }
563
+ this.lastPushAt = this.now();
564
+ this.chartHandle.setData(vm);
565
+ }
566
+ syncChart(view) {
567
+ if (!this.chartId || !this.chartHandle)
568
+ return;
569
+ if (this.lastView && this.lastView.mint !== view.mint)
570
+ this.forceFull = true; // the popup follows the open coin
571
+ this.lastView = view;
572
+ const wait = this.lastPushAt + this.syncMinMs - this.now();
573
+ if (wait <= 0) {
574
+ this.push(view);
575
+ return;
576
+ }
577
+ if (this.pending)
578
+ return; // a trailing push is already scheduled; it reads the newest view
579
+ this.pending = setTimeout(() => { this.pending = null; if (this.lastView && this.chartHandle)
580
+ this.push(this.lastView); }, wait);
581
+ this.pending.unref?.();
582
+ }
583
+ resetChart() {
584
+ this.chartId = null;
585
+ this.chartHandle = null;
586
+ this.ready = false;
587
+ if (this.dockOpt !== 'auto')
588
+ this.dockOpt?.stop();
589
+ else
590
+ this.dockImpl?.stop();
591
+ if (this.pending) {
592
+ clearTimeout(this.pending);
593
+ this.pending = null;
594
+ }
595
+ }
596
+ closeChart() {
597
+ const handle = this.chartHandle;
598
+ // Clear our state first so we are consistent even if the underlying close throws.
599
+ this.resetChart();
600
+ if (!handle)
601
+ return;
602
+ // Closing must never affect the terminal: if the window shell already died (crash/kill), the close op
603
+ // throws ("shell not running"). Swallow it — the popup is gone regardless — and surface it as a note so
604
+ // the rejection can never reach the process-level fatal handler and take the whole TUI down.
605
+ try {
606
+ handle.close();
607
+ }
608
+ catch (e) {
609
+ this.onNote?.(`chart popup already gone (${e.message})`);
610
+ }
611
+ }
612
+ // ---------------------------------------------------------------------------------------------- bubble map (task-1340)
613
+ bubblesOpen() { return this.bubblesId !== null; }
614
+ /** Open the bubble-map HUD for a coin (or push a fresh VM if already open). Read-only view model; no keys. */
615
+ async openBubbles(vm) {
616
+ if (this.bubblesId) {
617
+ this.syncBubbles(vm);
618
+ return { native: this.manager().native, reason: this.manager().reason };
619
+ }
620
+ const mgr = this.manager();
621
+ const handle = await mgr.open({ kind: 'bubbles', title: `${vm.symbol} bubbles`, data: vm, geom: { w: 760, h: 620, focus: false } });
622
+ this.bubblesId = handle.id;
623
+ this.bubblesHandle = handle;
624
+ this.native = mgr.native;
625
+ return { native: mgr.native, reason: mgr.reason };
626
+ }
627
+ syncBubbles(vm) { this.bubblesHandle?.setData(vm); }
628
+ closeBubbles() {
629
+ const h = this.bubblesHandle;
630
+ this.bubblesId = null;
631
+ this.bubblesHandle = null;
632
+ if (!h)
633
+ return;
634
+ try {
635
+ h.close();
636
+ }
637
+ catch (e) {
638
+ this.onNote?.(`bubble popup already gone (${e.message})`);
639
+ }
640
+ }
641
+ // ---------------------------------------------------------------------------------------------- narrative board (task-1356)
642
+ narrativeOpen() { return this.narrativeId !== null; }
643
+ /** Open the narrative-board HUD (or push a fresh VM if already open). Deterministic view model; no model, no keys. */
644
+ async openNarrative(vm) {
645
+ if (this.narrativeId) {
646
+ this.syncNarrative(vm);
647
+ return { native: this.manager().native, reason: this.manager().reason };
648
+ }
649
+ const mgr = this.manager();
650
+ const handle = await mgr.open({ kind: 'narrative', title: 'narrative board', data: vm, geom: { w: 720, h: 560, focus: false } });
651
+ this.narrativeId = handle.id;
652
+ this.narrativeHandle = handle;
653
+ this.native = mgr.native;
654
+ return { native: mgr.native, reason: mgr.reason };
655
+ }
656
+ syncNarrative(vm) { this.narrativeHandle?.setData(vm); }
657
+ closeNarrative() {
658
+ const h = this.narrativeHandle;
659
+ this.narrativeId = null;
660
+ this.narrativeHandle = null;
661
+ if (!h)
662
+ return;
663
+ try {
664
+ h.close();
665
+ }
666
+ catch (e) {
667
+ this.onNote?.(`narrative popup already gone (${e.message})`);
668
+ }
669
+ }
670
+ // ---------------------------------------------------------------------------------------------- order plans (task-1355)
671
+ plansOpen() { return this.plansId !== null; }
672
+ /** Open the order-plans HUD (or push a fresh VM if already open). Read-only view model; no keys, no signer. */
673
+ async openPlans(vm) {
674
+ if (this.plansId) {
675
+ this.syncPlans(vm);
676
+ return { native: this.manager().native, reason: this.manager().reason };
677
+ }
678
+ const mgr = this.manager();
679
+ const handle = await mgr.open({ kind: 'plans', title: 'order plans', data: vm, geom: { w: 640, h: 560, focus: false } });
680
+ this.plansId = handle.id;
681
+ this.plansHandle = handle;
682
+ this.native = mgr.native;
683
+ return { native: mgr.native, reason: mgr.reason };
684
+ }
685
+ syncPlans(vm) { this.plansHandle?.setData(vm); }
686
+ closePlans() {
687
+ const h = this.plansHandle;
688
+ this.plansId = null;
689
+ this.plansHandle = null;
690
+ if (!h)
691
+ return;
692
+ try {
693
+ h.close();
694
+ }
695
+ catch (e) {
696
+ this.onNote?.(`plans popup already gone (${e.message})`);
697
+ }
698
+ }
699
+ // ---------------------------------------------------------------------------------------------- token detail + feeds (task-1359)
700
+ tokenOpen() { return this.tokenId !== null; }
701
+ /** Open the token-detail HUD (or push a fresh VM if already open). Read-only; no keys, no trade path. */
702
+ async openToken(vm) {
703
+ if (this.tokenId) {
704
+ this.syncToken(vm);
705
+ return { native: this.manager().native, reason: this.manager().reason };
706
+ }
707
+ const mgr = this.manager();
708
+ const handle = await mgr.open({ kind: 'token', title: `${vm.symbol} token`, data: vm, geom: { w: 420, h: 640, focus: false } });
709
+ this.tokenId = handle.id;
710
+ this.tokenHandle = handle;
711
+ this.native = mgr.native;
712
+ return { native: mgr.native, reason: mgr.reason };
713
+ }
714
+ syncToken(vm) { this.tokenHandle?.setData(vm); }
715
+ closeToken() {
716
+ const h = this.tokenHandle;
717
+ this.tokenId = null;
718
+ this.tokenHandle = null;
719
+ if (!h)
720
+ return;
721
+ try {
722
+ h.close();
723
+ }
724
+ catch (e) {
725
+ this.onNote?.(`token popup already gone (${e.message})`);
726
+ }
727
+ }
728
+ feedsOpen() { return this.feedsId !== null; }
729
+ /** Open the feeds HUD with the newest-first items and the mint behind each (kept here, never sent). */
730
+ async openFeeds(vm, mints) {
731
+ if (this.feedsId) {
732
+ this.syncFeeds(vm, mints);
733
+ return { native: this.manager().native, reason: this.manager().reason };
734
+ }
735
+ const mgr = this.manager();
736
+ this.feedsMints = mints.slice();
737
+ const handle = await mgr.open({ kind: 'feeds', title: 'feeds', data: vm, geom: { w: 560, h: 620, focus: false } });
738
+ this.feedsId = handle.id;
739
+ this.feedsHandle = handle;
740
+ this.native = mgr.native;
741
+ return { native: mgr.native, reason: mgr.reason };
742
+ }
743
+ syncFeeds(vm, mints) { if (!this.feedsHandle)
744
+ return; this.feedsMints = mints.slice(); this.feedsHandle.setData(vm); }
745
+ closeFeeds() {
746
+ const h = this.feedsHandle;
747
+ this.feedsId = null;
748
+ this.feedsHandle = null;
749
+ this.feedsMints = [];
750
+ if (!h)
751
+ return;
752
+ try {
753
+ h.close();
754
+ }
755
+ catch (e) {
756
+ this.onNote?.(`feeds popup already gone (${e.message})`);
757
+ }
758
+ }
759
+ // ---------------------------------------------------------------------------------------------- pack widgets (task-1358 Tier 5)
760
+ widgetOpen(key) { return this.widgets.has(key); }
761
+ openWidgets() { return [...this.widgets.entries()].map(([key, w]) => ({ key, topics: [...w.topics] })); }
762
+ /** Open a pack widget page (consent + integrity were checked by the caller: widgets.ts). Already open → no-op. */
763
+ async openWidget(page) {
764
+ const mgr = this.manager();
765
+ if (this.widgets.has(page.key))
766
+ return { native: mgr.native, reason: mgr.reason };
767
+ const handle = await mgr.open({ kind: 'widget', title: page.title, widget: page, geom: { w: 520, h: 420, focus: false } });
768
+ this.widgets.set(page.key, { id: handle.id, handle, topics: [...page.topics] });
769
+ this.native = mgr.native;
770
+ return { native: mgr.native, reason: mgr.reason };
771
+ }
772
+ /** Push one topic's view model to an open widget; a topic the widget did not declare is not sent. */
773
+ syncWidget(key, topic, payload) {
774
+ const w = this.widgets.get(key);
775
+ if (!w || !w.topics.includes(topic))
776
+ return;
777
+ w.handle.sendData?.(topic, payload);
778
+ }
779
+ closeWidget(key) {
780
+ const w = this.widgets.get(key);
781
+ this.widgets.delete(key);
782
+ if (!w)
783
+ return;
784
+ try {
785
+ w.handle.close();
786
+ }
787
+ catch (e) {
788
+ this.onNote?.(`widget ${key} already gone (${e.message})`);
789
+ }
790
+ }
791
+ shutdown() {
792
+ const mgr = this.mgr;
793
+ this.mgr = null;
794
+ this.resetChart();
795
+ this.widgets.clear();
796
+ this.tokenId = null;
797
+ this.tokenHandle = null;
798
+ this.feedsId = null;
799
+ this.feedsHandle = null;
800
+ this.feedsMints = [];
801
+ this.bubblesId = null;
802
+ this.bubblesHandle = null;
803
+ this.narrativeId = null;
804
+ this.narrativeHandle = null;
805
+ this.plansId = null;
806
+ this.plansHandle = null;
807
+ if (mgr)
808
+ void mgr.shutdown().catch(() => { });
809
+ }
810
+ }