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,554 @@
1
+ // dexterm popup — CHART v2 (task-1325): TradingView Lightweight Charts™ 5.2.1 (Apache-2.0, vendored and served
2
+ // by our own loopback server — never a CDN) driven ONLY by our data: the ChartVM the terminal pushes over the
3
+ // WS (its cached/derived candles, its trade tape, its position ledger). Inlined after common.js + chart-lib.js
4
+ // under the page's CSP nonce. No innerHTML anywhere in this file: every untrusted string (symbol, name) goes
5
+ // through textContent or the chart's canvas text. The page can only REQUEST things (interval, indicator, style,
6
+ // scale) through allow-listed events; the server validates every payload against closed sets.
7
+ (() => {
8
+ 'use strict';
9
+ const DT = window.DT; DT.page = 'chart';
10
+ const C = window.DTChart;
11
+ const L = window.LightweightCharts;
12
+ const $ = (id) => document.getElementById(id);
13
+ const titleEl = $('title'), legendEl = $('legend'), liveEl = $('live'), bannerEl = $('banner'), hintEl = $('hint');
14
+ const posEl = $('pos'), posTitleEl = $('pos-title'), posCapEl = $('pos-cap'), posCellsEl = $('pos-cells');
15
+ const imgSlot = $('coin-img-slot');
16
+ const chartEl = $('chart'), drawCv = $('draw'), dctx = drawCv.getContext('2d');
17
+ const STALE_MS = 20_000;
18
+ const DEFAULT_IND = ['volume', 'tape', 'ema9', 'ema21'];
19
+ /** Exposed for the headless harness (measure/popup-v2-1325): first paint + setData → repaint latency. */
20
+ const perf = window.__dtPerf = { firstPaintMs: null, firstPaintWall: null, dataLatencies: [], intervalLatencies: [], vmCount: 0 };
21
+
22
+ const S = {
23
+ vm: null, bars: [], key: null, interval: null, pendingInterval: null, mint: null,
24
+ style: 'candles', scale: 'auto', ind: new Set(DEFAULT_IND), prefsApplied: false,
25
+ draw: null, tPending: null, hover: null, drawings: [], lastDataAt: 0, firstPainted: false, fitNext: true,
26
+ banners: { fallback: null, data: null },
27
+ display: C.displayOf(null), // task-1366: price | mcap (from the VM's priceMode + supply)
28
+ };
29
+
30
+ const banner = () => {
31
+ const lines = [S.banners.fallback, S.banners.data].filter(Boolean);
32
+ bannerEl.hidden = lines.length === 0;
33
+ bannerEl.textContent = lines.join(' · ');
34
+ };
35
+
36
+ // The page is useless without the vendored library: say so plainly instead of drawing a fake chart.
37
+ if (!L || !C || typeof L.createChart !== 'function') {
38
+ S.banners.data = 'chart library failed to load (the vendored Lightweight Charts route did not answer)';
39
+ banner(); DT.wireChrome(); DT.connect(); return;
40
+ }
41
+
42
+ const rain = DT.mountRain('rain');
43
+ DT.rainEvery = 2; // rain at half the frame rate behind the chart: atmosphere, not a CPU cost
44
+ let T = DT.theme;
45
+
46
+ // ---------------------------------------------------------------- time labels in the viewer's local time zone
47
+ // Bar times are shifted by the page's current UTC offset (chart-lib setTzOffset), so formatting them with the
48
+ // UTC getters prints local wall time and LWC's day/month tick marks fall on LOCAL boundaries.
49
+ C.setTzOffset(-new Date().getTimezoneOffset() * 60);
50
+ const pad = (n) => String(n).padStart(2, '0');
51
+ const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
52
+ function fmtTime(t) { const d = new Date(t * 1000); return `${pad(d.getUTCDate())} ${MON[d.getUTCMonth()]} '${String(d.getUTCFullYear()).slice(2)} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`; }
53
+ function tickMark(t, type) {
54
+ const d = new Date(t * 1000);
55
+ if (type === 0) return String(d.getUTCFullYear());
56
+ if (type === 1) return MON[d.getUTCMonth()];
57
+ if (type === 2) return String(d.getUTCDate());
58
+ return `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
59
+ }
60
+ const volFmt = (v) => C.fmtUsd(v);
61
+
62
+ // ---------------------------------------------------------------- chart + series
63
+ const chart = L.createChart(chartEl, {
64
+ autoSize: true,
65
+ layout: {
66
+ background: { type: 'solid', color: 'rgba(0,0,0,0)' }, textColor: T.muted, fontSize: 11,
67
+ fontFamily: 'ui-monospace, "SF Mono", Menlo, monospace',
68
+ attributionLogo: true, // REQUIRED attribution (license): TradingView logo link on the chart
69
+ panes: { separatorColor: T.border, separatorHoverColor: DT.rgba(T.accent, 0.25), enableResize: true },
70
+ },
71
+ grid: { vertLines: { color: DT.rgba(T.border, 0.45) }, horzLines: { color: DT.rgba(T.border, 0.45) } },
72
+ crosshair: {
73
+ mode: L.CrosshairMode.Normal,
74
+ vertLine: { color: DT.rgba(T.accent, 0.45), labelBackgroundColor: T.panel, width: 1, style: L.LineStyle.Dashed },
75
+ horzLine: { color: DT.rgba(T.accent, 0.45), labelBackgroundColor: T.panel, width: 1, style: L.LineStyle.Dashed },
76
+ },
77
+ rightPriceScale: { borderColor: T.border, scaleMargins: { top: 0.1, bottom: 0.08 } },
78
+ timeScale: { borderColor: T.border, timeVisible: true, secondsVisible: false, rightOffset: 6, barSpacing: 7, minBarSpacing: 0.5, tickMarkFormatter: tickMark },
79
+ localization: { timeFormatter: fmtTime, locale: 'en-US' },
80
+ handleScroll: { mouseWheel: true, pressedMouseMove: true, horzTouchDrag: true, vertTouchDrag: false },
81
+ handleScale: { axisPressedMouseMove: { time: true, price: true }, axisDoubleClickReset: true, mouseWheel: true, pinch: true },
82
+ });
83
+ // task-1366: the axis formatter follows the terminal's price mode (mcap = price × the current supply, K/M/B)
84
+ const pricePF = (p) => ({ type: 'custom', formatter: C.axisFormatter(S.display), minMove: C.priceFormatFor(p).minMove });
85
+ // Keep the position levels (entry / stop / target) and the live price in view: autoscale widens to include them
86
+ // when they are within 3× of the last close (a far-away level would squash the candles; its row cell still shows it).
87
+ function levels() {
88
+ const vm = S.vm || {}, pos = vm.position || null, out = [];
89
+ const last = S.bars.length ? S.bars[S.bars.length - 1].close : null;
90
+ for (const v of [pos && pos.avgEntryUsd, pos && pos.stopUsd, pos && pos.targetUsd, vm.feed === 'LIVE' ? vm.livePrice : null]) {
91
+ if (typeof v === 'number' && v > 0 && last && v / last < 3 && last / v < 3) out.push(v);
92
+ }
93
+ return out;
94
+ }
95
+ const autoscaleInfoProvider = (original) => {
96
+ const res = original();
97
+ const lv = levels();
98
+ if (!res || !res.priceRange || !lv.length) return res;
99
+ return { ...res, priceRange: { minValue: Math.min(res.priceRange.minValue, ...lv), maxValue: Math.max(res.priceRange.maxValue, ...lv) } };
100
+ };
101
+ const lineOpts = (color, w = 1) => ({ color, lineWidth: w, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: false, priceFormat: pricePF(1) });
102
+ const main = {
103
+ candles: chart.addSeries(L.CandlestickSeries, { upColor: T.up, downColor: T.down, borderUpColor: T.up, borderDownColor: T.down, wickUpColor: T.up, wickDownColor: T.down, priceLineColor: T.accent, priceLineStyle: L.LineStyle.Dashed, priceFormat: pricePF(1), autoscaleInfoProvider }, 0),
104
+ line: chart.addSeries(L.LineSeries, { color: T.accent, lineWidth: 2, visible: false, priceLineColor: T.accent, priceLineStyle: L.LineStyle.Dashed, priceFormat: pricePF(1), autoscaleInfoProvider }, 0),
105
+ area: chart.addSeries(L.AreaSeries, { lineColor: T.accent, topColor: DT.rgba(T.accent, 0.28), bottomColor: DT.rgba(T.accent, 0.0), lineWidth: 2, visible: false, priceLineColor: T.accent, priceLineStyle: L.LineStyle.Dashed, priceFormat: pricePF(1), autoscaleInfoProvider }, 0),
106
+ };
107
+ const markers = { candles: L.createSeriesMarkers(main.candles, []), line: L.createSeriesMarkers(main.line, []), area: L.createSeriesMarkers(main.area, []) };
108
+ const IND_COLORS = () => ({ ema9: T.warn, ema21: T.pump || '#ff2bd6', vwap: T.dbc || '#ff8a3d', volma: DT.rgba(T.accent, 0.8) });
109
+ const ind = {
110
+ ema9: chart.addSeries(L.LineSeries, lineOpts(IND_COLORS().ema9), 0),
111
+ ema21: chart.addSeries(L.LineSeries, lineOpts(IND_COLORS().ema21), 0),
112
+ vwap: chart.addSeries(L.LineSeries, { ...lineOpts(IND_COLORS().vwap, 1), lineStyle: L.LineStyle.Dotted }, 0),
113
+ };
114
+ let vol = null, volMa = null;
115
+ const watermark = L.createTextWatermark(chart.panes()[0], { horzAlign: 'center', vertAlign: 'center', lines: [] });
116
+ const active = () => main[S.style];
117
+
118
+ function ensureVolume(on) {
119
+ if (on && !vol) {
120
+ vol = chart.addSeries(L.HistogramSeries, { priceFormat: { type: 'custom', formatter: volFmt, minMove: 1 }, lastValueVisible: false, priceLineVisible: false }, 1);
121
+ volMa = chart.addSeries(L.LineSeries, { ...lineOpts(IND_COLORS().volma, 1), priceFormat: { type: 'custom', formatter: volFmt, minMove: 1 } }, 1);
122
+ sizePanes();
123
+ } else if (!on && vol) {
124
+ chart.removeSeries(volMa); chart.removeSeries(vol); vol = null; volMa = null;
125
+ }
126
+ }
127
+ function sizePanes() {
128
+ const panes = chart.panes();
129
+ if (panes.length > 1) { const h = chartEl.clientHeight; panes[1].setHeight(Math.max(48, Math.round(h * 0.2))); }
130
+ }
131
+
132
+ // ---------------------------------------------------------------- price lines on the active series (entry/stop/target/live/drawn)
133
+ const plines = new Map(); // name → { series, line }
134
+ function setLine(name, opts) {
135
+ const s = active(); const cur = plines.get(name);
136
+ if (!opts) { if (cur) { try { cur.series.removePriceLine(cur.line); } catch { /* gone */ } plines.delete(name); } return; }
137
+ if (cur && cur.series === s) { cur.line.applyOptions(opts); return; }
138
+ if (cur) { try { cur.series.removePriceLine(cur.line); } catch { /* gone */ } }
139
+ plines.set(name, { series: s, line: s.createPriceLine(opts) });
140
+ }
141
+ function rebindLines() { const saved = [...plines.entries()].map(([k, v]) => [k, v.line.options()]); for (const [k] of saved) setLine(k, null); for (const [k, o] of saved) setLine(k, o); }
142
+
143
+ // ---------------------------------------------------------------- theme
144
+ function applyChartTheme() {
145
+ T = DT.theme;
146
+ chart.applyOptions({
147
+ layout: { textColor: T.muted, panes: { separatorColor: T.border, separatorHoverColor: DT.rgba(T.accent, 0.25) } },
148
+ grid: { vertLines: { color: DT.rgba(T.border, 0.45) }, horzLines: { color: DT.rgba(T.border, 0.45) } },
149
+ crosshair: { vertLine: { color: DT.rgba(T.accent, 0.45), labelBackgroundColor: T.panel }, horzLine: { color: DT.rgba(T.accent, 0.45), labelBackgroundColor: T.panel } },
150
+ rightPriceScale: { borderColor: T.border }, timeScale: { borderColor: T.border },
151
+ });
152
+ main.candles.applyOptions({ upColor: T.up, downColor: T.down, borderUpColor: T.up, borderDownColor: T.down, wickUpColor: T.up, wickDownColor: T.down, priceLineColor: T.accent });
153
+ main.line.applyOptions({ color: T.accent, priceLineColor: T.accent });
154
+ main.area.applyOptions({ lineColor: T.accent, topColor: DT.rgba(T.accent, 0.28), bottomColor: DT.rgba(T.accent, 0), priceLineColor: T.accent });
155
+ const ic = IND_COLORS(); ind.ema9.applyOptions({ color: ic.ema9 }); ind.ema21.applyOptions({ color: ic.ema21 }); ind.vwap.applyOptions({ color: ic.vwap });
156
+ if (volMa) volMa.applyOptions({ color: ic.volma });
157
+ if (S.bars.length) renderFull();
158
+ }
159
+ DT.onTheme(applyChartTheme);
160
+
161
+ // ---------------------------------------------------------------- toolbar (buttons built with textContent only)
162
+ const btns = { tf: new Map(), styles: new Map(), scales: new Map(), inds: new Map(), draws: new Map() };
163
+ function mkBtn(group, id, label, title, onClick) {
164
+ const b = document.createElement('button'); b.type = 'button'; b.className = 'tb'; b.textContent = label; b.title = title;
165
+ b.addEventListener('click', (e) => { e.preventDefault(); onClick(); });
166
+ $(group).appendChild(b); btns[group].set(id, b); return b;
167
+ }
168
+ C.INTERVALS.forEach((iv, i) => mkBtn('tf', iv, iv, `timeframe ${iv} (${i + 1})`, () => requestInterval(iv)));
169
+ mkBtn('styles', 'candles', 'candles', 'candlesticks (c cycles)', () => setStyle('candles', true));
170
+ mkBtn('styles', 'line', 'line', 'line (c cycles)', () => setStyle('line', true));
171
+ mkBtn('styles', 'area', 'area', 'area (c cycles)', () => setStyle('area', true));
172
+ mkBtn('scales', 'auto', 'auto', 'auto-scaled price axis (a)', () => setScale('auto', true));
173
+ mkBtn('scales', 'log', 'log', 'logarithmic price axis (l)', () => setScale('log', true));
174
+ mkBtn('scales', 'percent', '%', 'percent from the first visible bar (p)', () => setScale('percent', true));
175
+ mkBtn('inds', 'ema9', 'EMA9', 'EMA 9 (e toggles 9+21)', () => toggleInd('ema9'));
176
+ mkBtn('inds', 'ema21', 'EMA21', 'EMA 21 (e toggles 9+21)', () => toggleInd('ema21'));
177
+ mkBtn('inds', 'vwap', 'VWAP', 'VWAP, UTC-day anchored (w)', () => toggleInd('vwap'));
178
+ mkBtn('inds', 'volume', 'VOL', 'volume pane (v)', () => toggleInd('volume'));
179
+ mkBtn('inds', 'volma', 'VOL·MA', 'volume 20-bar MA (m)', () => toggleInd('volma'));
180
+ mkBtn('inds', 'tape', 'TAPE', 'buy/sell markers from the trade tape (b)', () => toggleInd('tape'));
181
+ mkBtn('draws', 'h', '─ hline', 'horizontal line: click a price (h)', () => armDraw('h'));
182
+ mkBtn('draws', 't', '╱ trend', 'trend line: click two points (t)', () => armDraw('t'));
183
+ mkBtn('draws', 'x', '✕ clear', 'clear drawings (x; backspace = undo last)', () => clearDrawings());
184
+ function syncToolbar() {
185
+ for (const [iv, b] of btns.tf) { b.classList.toggle('on', iv === S.interval); b.classList.toggle('pending', iv === S.pendingInterval && iv !== S.interval); }
186
+ for (const [k, b] of btns.styles) b.classList.toggle('on', k === S.style);
187
+ for (const [k, b] of btns.scales) b.classList.toggle('on', k === S.scale);
188
+ for (const [k, b] of btns.inds) b.classList.toggle('on', S.ind.has(k));
189
+ for (const [k, b] of btns.draws) b.classList.toggle('on', k === S.draw);
190
+ }
191
+
192
+ // ---------------------------------------------------------------- requests to the TUI (validated server-side)
193
+ function requestInterval(iv) {
194
+ if (!C.INTERVALS.includes(iv)) return;
195
+ if (iv === S.interval && !S.pendingInterval) return;
196
+ S.pendingInterval = iv; S.reqAt = performance.now(); syncToolbar();
197
+ DT.send({ type: 'event', name: 'chart.interval', payload: { interval: iv } });
198
+ }
199
+ function setStyle(st, user) {
200
+ if (!C.STYLES.includes(st) || st === S.style) return;
201
+ S.style = st;
202
+ for (const k of C.STYLES) main[k].applyOptions({ visible: k === st });
203
+ for (const k of C.STYLES) if (k !== st) { main[k].setData([]); markers[k].setMarkers([]); }
204
+ rebindLines();
205
+ if (S.bars.length) renderFull();
206
+ syncToolbar();
207
+ if (user) DT.send({ type: 'event', name: 'chart.style', payload: { style: st } });
208
+ }
209
+ function setScale(sc, user) {
210
+ if (!C.SCALES.includes(sc)) return;
211
+ S.scale = sc;
212
+ chart.priceScale('right', 0).applyOptions({ mode: sc === 'log' ? L.PriceScaleMode.Logarithmic : sc === 'percent' ? L.PriceScaleMode.Percentage : L.PriceScaleMode.Normal, autoScale: true });
213
+ syncToolbar();
214
+ if (user) DT.send({ type: 'event', name: 'chart.scale', payload: { scale: sc } });
215
+ }
216
+ function toggleInd(id, force) {
217
+ if (!C.INDICATORS.includes(id)) return;
218
+ const on = force === undefined ? !S.ind.has(id) : force;
219
+ if (on) S.ind.add(id); else S.ind.delete(id);
220
+ if (id === 'volume') ensureVolume(on);
221
+ if (S.bars.length) renderFull();
222
+ syncToolbar();
223
+ DT.send({ type: 'event', name: 'chart.indicator', payload: { id, on } });
224
+ }
225
+ function applyPrefs(p) {
226
+ if (!p) return;
227
+ if (Array.isArray(p.indicators)) S.ind = new Set(p.indicators.filter((x) => C.INDICATORS.includes(x)));
228
+ ensureVolume(S.ind.has('volume'));
229
+ if (p.style && p.style !== S.style) setStyle(p.style, false);
230
+ if (p.scale) setScale(p.scale, false);
231
+ S.prefsApplied = true; syncToolbar();
232
+ }
233
+
234
+ // ---------------------------------------------------------------- data → chart
235
+ const periodMs = () => C.PERIOD_MS[S.interval] || 3_600_000;
236
+ let indCache = { ema9: new Map(), ema21: new Map(), vwap: new Map() };
237
+ const mapOf = (pts) => new Map(pts.map((p) => [p.time, p.value]));
238
+ function seriesData(st) { return st === 'candles' ? S.bars.map((b) => ({ time: b.time, open: b.open, high: b.high, low: b.low, close: b.close })) : S.bars.map((b) => ({ time: b.time, value: b.close })); }
239
+ function safeUpdate(series, point, fallback) { try { series.update(point); } catch { fallback(); } }
240
+
241
+ function renderIndicators(tailOnly) {
242
+ const out = { ema9: S.ind.has('ema9') ? C.ema(S.bars, 9) : [], ema21: S.ind.has('ema21') ? C.ema(S.bars, 21) : [], vwap: S.ind.has('vwap') ? C.vwap(S.bars) : [] };
243
+ for (const k of ['ema9', 'ema21', 'vwap']) {
244
+ indCache[k] = mapOf(out[k]);
245
+ if (tailOnly && out[k].length) { for (const p of out[k].slice(-2)) safeUpdate(ind[k], p, () => ind[k].setData(out[k])); }
246
+ else ind[k].setData(out[k]);
247
+ }
248
+ if (vol) {
249
+ const v = C.volumes(S.bars, DT.rgba(T.up, 0.45), DT.rgba(T.down, 0.45));
250
+ const ma = S.ind.has('volma') ? C.sma(v, 20) : [];
251
+ if (tailOnly && v.length) {
252
+ for (const p of v.slice(-2)) safeUpdate(vol, p, () => vol.setData(v));
253
+ if (ma.length) for (const p of ma.slice(-2)) safeUpdate(volMa, p, () => volMa.setData(ma)); else volMa.setData([]);
254
+ } else { vol.setData(v); volMa.setData(ma); }
255
+ }
256
+ }
257
+
258
+ function renderMarkers() {
259
+ const vm = S.vm || {};
260
+ const times = new Set(S.bars.map((b) => b.time));
261
+ const list = [];
262
+ if (S.ind.has('tape')) list.push(...C.tapeMarkers(vm.trades, periodMs(), times, { up: DT.rgba(T.up, 0.85), down: DT.rgba(T.down, 0.85) }));
263
+ if (vm.position && vm.position.fills) list.push(...C.fillMarkers(vm.position.fills, periodMs(), times, { buy: T.accent, sell: T.warn }));
264
+ list.sort((a, b) => a.time - b.time);
265
+ markers[S.style].setMarkers(list);
266
+ }
267
+
268
+ function renderOverlays() {
269
+ const vm = S.vm || {};
270
+ const pos = vm.position || null;
271
+ if (pos && typeof pos.avgEntryUsd === 'number' && pos.avgEntryUsd > 0) {
272
+ const pct = typeof pos.unrealizedPct === 'number' ? ` ${C.fmtPct(pos.unrealizedPct, 1)}` : '';
273
+ setLine('entry', { price: pos.avgEntryUsd, color: T.warn, lineWidth: 1, lineStyle: L.LineStyle.Solid, axisLabelVisible: true, axisLabelColor: T.warn, axisLabelTextColor: T.bg, title: `ENTRY ${pos.sizeLabel}${pct}` });
274
+ } else setLine('entry', null);
275
+ setLine('stop', pos && typeof pos.stopUsd === 'number' && pos.stopUsd > 0 ? { price: pos.stopUsd, color: T.down, lineWidth: 1, lineStyle: L.LineStyle.Dashed, axisLabelVisible: true, title: 'STOP' } : null);
276
+ setLine('target', pos && typeof pos.targetUsd === 'number' && pos.targetUsd > 0 ? { price: pos.targetUsd, color: T.up, lineWidth: 1, lineStyle: L.LineStyle.Dashed, axisLabelVisible: true, title: 'TARGET' } : null);
277
+ const lastClose = S.bars.length ? S.bars[S.bars.length - 1].close : null;
278
+ const lp = vm.feed === 'LIVE' && typeof vm.livePrice === 'number' && vm.livePrice > 0 && vm.livePrice !== lastClose ? vm.livePrice : null;
279
+ setLine('live', lp ? { price: lp, color: T.accent, lineWidth: 1, lineStyle: L.LineStyle.Dotted, axisLabelVisible: true, axisLabelColor: T.accent, axisLabelTextColor: T.bg, title: 'LIVE' } : null);
280
+ for (const d of S.drawings) if (d.kind === 'h') setLine(`hline-${d.id}`, { price: d.price, color: DT.rgba(T.accent, 0.85), lineWidth: 1, lineStyle: L.LineStyle.LargeDashed, axisLabelVisible: true, title: '' });
281
+ }
282
+
283
+ function applyPriceFormat() {
284
+ const last = S.bars.length ? S.bars[S.bars.length - 1].close : 1;
285
+ const pf = pricePF(last);
286
+ for (const k of C.STYLES) main[k].applyOptions({ priceFormat: pf });
287
+ for (const k of ['ema9', 'ema21', 'vwap']) ind[k].applyOptions({ priceFormat: pf });
288
+ }
289
+ function renderFull() {
290
+ applyPriceFormat();
291
+ active().setData(seriesData(S.style));
292
+ renderIndicators(false);
293
+ renderMarkers();
294
+ renderOverlays();
295
+ if (S.fitNext && S.bars.length) {
296
+ const n = S.bars.length;
297
+ chart.timeScale().setVisibleLogicalRange({ from: Math.max(-2, n - 140), to: n + 5 });
298
+ S.fitNext = false;
299
+ }
300
+ renderLegend(S.hover); // indicator toggles change the legend's second row
301
+ }
302
+ function renderTail(tailBars) {
303
+ const s = active();
304
+ for (const b of tailBars) {
305
+ const pt = S.style === 'candles' ? { time: b.time, open: b.open, high: b.high, low: b.low, close: b.close } : { time: b.time, value: b.close };
306
+ safeUpdate(s, pt, () => s.setData(seriesData(S.style)));
307
+ }
308
+ renderIndicators(true);
309
+ renderMarkers();
310
+ renderOverlays();
311
+ }
312
+
313
+ // ---------------------------------------------------------------- legend (crosshair OHLCV + indicator values)
314
+ function el(tag, cls, text) { const e = document.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; }
315
+ function renderLegend(time) {
316
+ while (legendEl.firstChild) legendEl.removeChild(legendEl.firstChild);
317
+ if (!S.bars.length) return;
318
+ let i = time == null ? S.bars.length - 1 : C.indexOfTime(S.bars, time);
319
+ if (i < 0) i = S.bars.length - 1;
320
+ const b = S.bars[i], lg = C.legendFor(b, i > 0 ? S.bars[i - 1].close : null, S.display);
321
+ const r1 = el('div', 'row1');
322
+ for (const [k, v] of [['O', lg.o], ['H', lg.h], ['L', lg.l], ['C', lg.c]]) { r1.appendChild(el('b', null, k)); r1.appendChild(el('span', lg.up ? 'up' : 'down', ` ${v}`)); }
323
+ r1.appendChild(el('span', lg.up ? 'up' : 'down', ` ${lg.chg}`));
324
+ r1.appendChild(el('b', null, 'V')); r1.appendChild(el('span', null, ` ${lg.v}`));
325
+ legendEl.appendChild(r1);
326
+ const r2 = el('div', 'row2'); const ic = IND_COLORS();
327
+ for (const k of ['ema9', 'ema21', 'vwap']) {
328
+ if (!S.ind.has(k)) continue;
329
+ const v = indCache[k].get(b.time);
330
+ const s = el('span', 'ind', `${k === 'vwap' ? 'VWAP' : k === 'ema9' ? 'EMA 9' : 'EMA 21'} ${v == null ? '—' : C.axisFormatter(S.display)(v)}`); s.style.color = ic[k];
331
+ r2.appendChild(s);
332
+ }
333
+ if (r2.firstChild) legendEl.appendChild(r2);
334
+ }
335
+ chart.subscribeCrosshairMove((p) => { S.hover = p && p.time != null ? p.time : null; renderLegend(S.hover); });
336
+
337
+ // ---------------------------------------------------------------- header, LIVE badge, banner, position row
338
+ function renderHeader() {
339
+ const vm = S.vm; if (!vm) return;
340
+ const sym = String(vm.symbol || '?').replace(/^\$/, '');
341
+ // task-1366: the title says when the axis is market cap (history scaled by the CURRENT supply) or why it is not
342
+ titleEl.textContent = `$${sym} · ${S.interval || vm.interval || ''}${S.display.label ? ' · ' + S.display.label : ''}${vm.name ? ' · ' + String(vm.name).slice(0, 32) : ''}`;
343
+ document.title = `dexterm · ${sym} · ${S.interval || ''}`;
344
+ renderCoinImage(vm.image);
345
+ const px = typeof vm.livePrice === 'number' && vm.livePrice > 0 ? vm.livePrice : vm.price;
346
+ const ch = vm.chg && typeof vm.chg.h24 === 'number' ? vm.chg.h24 : null;
347
+ // TokenView.chg is already in PERCENT (DexScreener priceChange: 42 = +42 %), same as the terminal header.
348
+ const head = S.display.mode === 'mcap' && typeof px === 'number' ? `$${C.fmtMcapAxis(px * S.display.supply)} (${C.fmtPrice(px)})` : C.fmtPrice(px);
349
+ DT.status(`${head}${ch == null ? '' : ` ${ch >= 0 ? '+' : '-'}${Math.abs(ch).toFixed(Math.abs(ch) >= 1000 ? 0 : 1)}% 24h`}`, ch == null ? null : ch >= 0 ? 'up' : 'down');
350
+ watermark.applyOptions({ lines: [{ text: `${sym} · ${S.interval || ''}`, color: DT.rgba(T.accent, 0.06), fontSize: 46, fontStyle: 'bold', fontFamily: 'ui-monospace, Menlo, monospace' }] });
351
+ }
352
+ // task-1326: the coin image comes from OUR loopback server only (`/img/<32 hex>.png`); anything else is ignored,
353
+ // so a hostile VM can never make the page load a remote URL (the CSP would refuse it too).
354
+ let imgShown = null;
355
+ function renderCoinImage(path) {
356
+ const ok = typeof path === 'string' && /^\/img\/[0-9a-f]{32}\.png$/.test(path);
357
+ if (!ok) { if (imgShown !== null) { imgShown = null; while (imgSlot.firstChild) imgSlot.removeChild(imgSlot.firstChild); imgSlot.hidden = true; } return; }
358
+ if (imgShown === path) return;
359
+ imgShown = path;
360
+ while (imgSlot.firstChild) imgSlot.removeChild(imgSlot.firstChild);
361
+ const im = document.createElement('img');
362
+ im.width = 16; im.height = 16; im.alt = ''; im.decoding = 'async'; im.referrerPolicy = 'no-referrer';
363
+ im.src = path; // same-origin path: resolves against this loopback origin
364
+ im.onerror = () => { imgSlot.hidden = true; };
365
+ imgSlot.appendChild(im);
366
+ imgSlot.hidden = false;
367
+ }
368
+ function renderLive() {
369
+ if (!S.lastDataAt) { liveEl.className = 'live wait'; liveEl.textContent = '● …'; return; }
370
+ const age = performance.now() - S.lastDataAt;
371
+ const feed = S.vm && S.vm.feed === 'LIVE' ? 'stream' : 'poll';
372
+ if (age > STALE_MS) { liveEl.className = 'live stale'; liveEl.textContent = `◌ STALE ${Math.round(age / 1000)}s`; liveEl.title = 'no tick from the terminal recently'; }
373
+ else { liveEl.className = 'live on'; liveEl.textContent = '● LIVE'; liveEl.title = `ticks from the terminal (${feed})`; }
374
+ }
375
+ setInterval(renderLive, 1000);
376
+ function renderDataBanner() {
377
+ const vm = S.vm || {};
378
+ if (vm.cold) S.banners.data = `warming ${vm.interval} from the terminal's candle cache — appears as soon as the terminal has it (no extra requests)`;
379
+ else if (!S.bars.length && vm.source && vm.source.state === 'loading') S.banners.data = 'loading candles…';
380
+ else if (!S.bars.length) S.banners.data = `no candles for ${vm.interval || 'this timeframe'}${vm.source && vm.source.reason ? ` · ${vm.source.reason}` : ''}`;
381
+ else if (vm.source && (vm.source.stale || vm.source.state === 'error')) S.banners.data = `candles stale${vm.source.reason ? ` · ${vm.source.reason}` : ''}`;
382
+ else S.banners.data = null;
383
+ banner();
384
+ }
385
+ function renderPosition() {
386
+ const pos = S.vm && S.vm.position;
387
+ if (!pos) { posEl.hidden = true; return; }
388
+ posEl.hidden = false;
389
+ posTitleEl.textContent = pos.kind === 'paper' ? '◈ POSITION · PAPER' : '◈ POSITION';
390
+ posTitleEl.classList.toggle('live-ledger', pos.kind !== 'paper');
391
+ posCapEl.textContent = pos.caption || '';
392
+ while (posCellsEl.firstChild) posCellsEl.removeChild(posCellsEl.firstChild);
393
+ for (const c of pos.cells || []) {
394
+ const d = el('div', 'pos-cell'); d.appendChild(el('span', 'k', c.label));
395
+ d.appendChild(el('span', `v ${['up', 'down', 'muted', 'warn'].includes(c.tone) ? c.tone : ''}`, c.value));
396
+ posCellsEl.appendChild(d);
397
+ }
398
+ }
399
+
400
+ // ---------------------------------------------------------------- incoming view models
401
+ function onData(vm) {
402
+ if (!vm || typeof vm !== 'object') return;
403
+ perf.vmCount++;
404
+ S.lastDataAt = performance.now();
405
+ if (vm.mint !== S.mint) { S.mint = vm.mint; S.fitNext = true; clearDrawings(true); }
406
+ if (!S.prefsApplied) applyPrefs(vm.prefs || { indicators: DEFAULT_IND, style: 'candles', scale: 'auto' });
407
+ const prevKey = S.key;
408
+ const tail = vm.mode === 'tail';
409
+ // task-1366: a mode / supply change re-labels the axis in place (the bars are prices either way)
410
+ const disp = C.displayOf(vm);
411
+ if (disp.key !== S.display.key) { S.display = disp; applyPriceFormat(); }
412
+ if (tail && (!vm.seriesKey || vm.seriesKey !== S.key)) { S.vm = { ...vm, candles: [] }; renderHeader(); renderPosition(); renderOverlays(); return; }
413
+ const intervalChanged = vm.interval !== S.interval;
414
+ S.interval = vm.interval;
415
+ const answered = S.pendingInterval === vm.interval ? S.reqAt : null;
416
+ if (S.pendingInterval === vm.interval) S.pendingInterval = null;
417
+ S.vm = vm;
418
+ if (tail) {
419
+ const before = S.bars.length;
420
+ S.bars = C.mergeTail(S.bars, vm.candles);
421
+ renderTail(S.bars.slice(Math.max(0, before - 1)));
422
+ } else {
423
+ S.bars = C.toBars(vm.candles);
424
+ S.key = vm.seriesKey || null;
425
+ if (intervalChanged || S.key !== prevKey) S.fitNext = true;
426
+ renderFull();
427
+ }
428
+ renderHeader(); renderLegend(S.hover); renderDataBanner(); renderPosition(); syncToolbar(); renderLive();
429
+ // rain mood follows direction (gold on a pump, red-shift on a dump), as in the v1 HUD; chg is in percent
430
+ const dir = vm.chg ? (vm.chg.h24 ?? vm.chg.h1 ?? 0) : 0;
431
+ if (rain) { rain.setMood(dir > 1 ? 'rainPump' : dir < -1 ? 'rainDump' : 'rain'); rain.setActivity(Math.min(1, 0.4 + Math.abs(dir) / 100)); rain.setSources([String(vm.symbol || ''), String(vm.mint || '').slice(0, 6)]); }
432
+ // paint measurement: the frame after next has the chart's render committed
433
+ const sentAt = typeof vm.sentAt === 'number' ? vm.sentAt : null;
434
+ requestAnimationFrame(() => requestAnimationFrame(() => {
435
+ const wall = Date.now();
436
+ if (answered != null) perf.intervalLatencies.push(+(performance.now() - answered).toFixed(1)); // request → repainted, in-page
437
+ if (!S.firstPainted && S.bars.length) {
438
+ S.firstPainted = true; perf.firstPaintMs = +performance.now().toFixed(1); perf.firstPaintWall = wall;
439
+ DT.send({ type: 'event', name: 'chart.painted', payload: { kind: 'first', ms: perf.firstPaintMs, sentAt: sentAt ?? 0 } });
440
+ } else if (sentAt) {
441
+ const ms = Math.max(0, wall - sentAt); perf.dataLatencies.push(ms);
442
+ if (perf.dataLatencies.length <= 20 || perf.dataLatencies.length % 20 === 0) DT.send({ type: 'event', name: 'chart.painted', payload: { kind: 'data', ms, sentAt } });
443
+ }
444
+ }));
445
+ }
446
+ DT.onData(onData);
447
+ DT.onHello((m) => {
448
+ const p = (m && m.popup && m.popup.params) || {};
449
+ // Browser fallback: say WHY on the page itself (the terminal shows the same reason as a toast).
450
+ if (!DT.native) S.banners.fallback = `opened in your browser, not the dexterm window: ${typeof p.fallback === 'string' ? p.fallback : 'the native popup shell is unavailable'}`;
451
+ banner();
452
+ });
453
+
454
+ // ---------------------------------------------------------------- drawing tools: horizontal line + trend line
455
+ let drawId = 0, mouse = null;
456
+ function armDraw(kind) {
457
+ S.draw = kind && S.draw !== kind ? kind : null; S.tPending = null; mouse = null;
458
+ drawCv.classList.toggle('armed', !!S.draw);
459
+ hintEl.hidden = !S.draw;
460
+ hintEl.textContent = S.draw === 'h' ? 'click a price for a horizontal line · esc cancel' : S.draw === 't' ? 'click the first point · esc cancel' : '';
461
+ syncToolbar();
462
+ }
463
+ function clearDrawings(silent) {
464
+ for (const d of S.drawings) if (d.kind === 'h') setLine(`hline-${d.id}`, null);
465
+ S.drawings = []; if (!silent) armDraw(null);
466
+ }
467
+ function undoDrawing() { const d = S.drawings.pop(); if (d && d.kind === 'h') setLine(`hline-${d.id}`, null); }
468
+ function pointAt(e) {
469
+ const r = chartEl.getBoundingClientRect(); const x = e.clientX - r.left, y = e.clientY - r.top;
470
+ const logical = chart.timeScale().coordinateToLogical(x); const price = active().coordinateToPrice(y);
471
+ if (logical == null || price == null) return null;
472
+ return { time: C.timeOfLogical(S.bars, logical, periodMs() / 1000), price };
473
+ }
474
+ drawCv.addEventListener('mousedown', (e) => {
475
+ if (!S.draw || e.button !== 0 || !S.bars.length) return;
476
+ e.preventDefault();
477
+ const pt = pointAt(e); if (!pt) return;
478
+ if (S.draw === 'h') { S.drawings.push({ kind: 'h', id: ++drawId, price: pt.price }); renderOverlays(); armDraw(null); return; }
479
+ if (!S.tPending) { S.tPending = pt; hintEl.textContent = 'click the second point · esc cancel'; return; }
480
+ S.drawings.push({ kind: 't', id: ++drawId, a: S.tPending, b: pt }); armDraw(null);
481
+ });
482
+ drawCv.addEventListener('mousemove', (e) => { mouse = S.draw === 't' && S.tPending ? pointAt(e) : null; });
483
+ function resizeDraw() {
484
+ const dpr = Math.min(2, window.devicePixelRatio || 1); const w = chartEl.clientWidth, h = chartEl.clientHeight;
485
+ if (drawCv.width !== Math.floor(w * dpr) || drawCv.height !== Math.floor(h * dpr)) { drawCv.width = Math.floor(w * dpr); drawCv.height = Math.floor(h * dpr); }
486
+ dctx.setTransform(dpr, 0, 0, dpr, 0, 0);
487
+ }
488
+ /** Trend lines live in (time, price) space and are re-projected every frame, so they pan/zoom with the chart. */
489
+ function drawOverlay() {
490
+ resizeDraw();
491
+ const w = chartEl.clientWidth, h = chartEl.clientHeight;
492
+ dctx.clearRect(0, 0, w, h);
493
+ const segs = S.drawings.filter((d) => d.kind === 't').map((d) => [d.a, d.b]);
494
+ if (S.tPending && mouse) segs.push([S.tPending, mouse]);
495
+ if (!segs.length || !S.bars.length) return;
496
+ const ts = chart.timeScale(), s = active(), pS = periodMs() / 1000;
497
+ const paneH = chart.panes()[0] ? chart.panes()[0].getHeight() : h;
498
+ dctx.save(); dctx.beginPath(); dctx.rect(0, 0, ts.width(), paneH); dctx.clip();
499
+ dctx.strokeStyle = T.accent; dctx.fillStyle = T.accent; dctx.lineWidth = 1.5;
500
+ for (const [a, b] of segs) {
501
+ const xa = ts.logicalToCoordinate(C.logicalOf(S.bars, a.time, pS)), xb = ts.logicalToCoordinate(C.logicalOf(S.bars, b.time, pS));
502
+ const ya = s.priceToCoordinate(a.price), yb = s.priceToCoordinate(b.price);
503
+ if ([xa, xb, ya, yb].some((v) => v == null)) continue;
504
+ dctx.beginPath(); dctx.moveTo(xa, ya); dctx.lineTo(xb, yb); dctx.stroke();
505
+ for (const [x, y] of [[xa, ya], [xb, yb]]) { dctx.beginPath(); dctx.arc(x, y, 2.5, 0, Math.PI * 2); dctx.fill(); }
506
+ }
507
+ dctx.restore();
508
+ }
509
+
510
+ // ---------------------------------------------------------------- keyboard (terminal-style, single keys)
511
+ window.addEventListener('keydown', (e) => {
512
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
513
+ const k = e.key;
514
+ const n = parseInt(k, 10);
515
+ if (n >= 1 && n <= 6) { requestInterval(C.INTERVALS[n - 1]); return; }
516
+ switch (k) {
517
+ case 'c': setStyle(C.STYLES[(C.STYLES.indexOf(S.style) + 1) % C.STYLES.length], true); break;
518
+ case 'a': setScale('auto', true); break;
519
+ case 'l': setScale(S.scale === 'log' ? 'auto' : 'log', true); break;
520
+ case 'p': case '%': setScale(S.scale === 'percent' ? 'auto' : 'percent', true); break;
521
+ case 'e': { const on = !(S.ind.has('ema9') && S.ind.has('ema21')); toggleInd('ema9', on); toggleInd('ema21', on); break; }
522
+ case 'w': toggleInd('vwap'); break;
523
+ case 'm': toggleInd('volma'); break;
524
+ case 'v': toggleInd('volume'); break;
525
+ case 'b': toggleInd('tape'); break;
526
+ case 'h': armDraw('h'); break;
527
+ case 't': armDraw('t'); break;
528
+ case 'x': clearDrawings(); break;
529
+ case 'Backspace': undoDrawing(); renderOverlays(); break;
530
+ case 'f': chart.timeScale().fitContent(); break;
531
+ case 'r': chart.timeScale().scrollToRealTime(); break;
532
+ case 'q': DT.requestClose(); break;
533
+ default: return;
534
+ }
535
+ e.preventDefault();
536
+ });
537
+ // Esc cancels an armed drawing tool first; otherwise common.js closes the popup.
538
+ DT.onEscape = () => { if (S.draw) { armDraw(null); return true; } return false; };
539
+
540
+ // In the native window the attribution links must not navigate the HUD (the shell refuses off-origin
541
+ // navigation anyway); in the browser fallback they open normally in a new tab (target=_blank, noopener).
542
+ document.addEventListener('click', (e) => {
543
+ const a = e.target && e.target.closest ? e.target.closest('a') : null;
544
+ if (a && DT.native) e.preventDefault();
545
+ }, true);
546
+
547
+ window.addEventListener('resize', () => { sizePanes(); });
548
+ ensureVolume(S.ind.has('volume'));
549
+ syncToolbar();
550
+ DT.wireChrome();
551
+ DT.connect();
552
+ DT.loop(drawOverlay);
553
+ window.__dtChart = { chart, S, main, ind, get vol() { return vol; } }; // harness hook (read-only use)
554
+ })();
@@ -0,0 +1,34 @@
1
+ /* dexterm popup — shared chrome + cyberspace look (task-1281 palette; approved HUD look from the spike).
2
+ Inlined under a CSP nonce. Transparent body so the frameless shell shows CSS-rounded corners. */
3
+ :root { --bg:#020507; --panel:#03090c; --border:#0c3a24; --borderFocus:#39ff14; --text:#eafff0; --muted:#62c98a; --dim:#3aa866; --accent:#00f0ff; --up:#5dff6e; --down:#ff3b6b; --warn:#f5ff2d; --ai:#d97757; --pump:#ff2bd6; --dbc:#ff8a3d; }
4
+ * { box-sizing: border-box; margin: 0; padding: 0; }
5
+ html, body { width: 100%; height: 100%; background: transparent; overflow: hidden; }
6
+ body { font: 13px/1.35 ui-monospace, "SF Mono", Menlo, monospace; color: var(--text); -webkit-user-select: none; user-select: none; }
7
+ .hud {
8
+ position: absolute; inset: 0; display: flex; flex-direction: column;
9
+ background: radial-gradient(130% 100% at 50% 0%, var(--panel) 0%, var(--bg) 78%);
10
+ border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
11
+ box-shadow: 0 10px 40px rgba(0,0,0,.6), inset 0 0 0 1px rgba(57,255,20,.04);
12
+ }
13
+ .drag { display: flex; align-items: center; gap: 8px; padding: 6px 10px; cursor: default; -webkit-app-region: drag;
14
+ background: linear-gradient(to bottom, rgba(12,58,36,.5), rgba(3,9,12,0)); border-bottom: 1px solid var(--border); }
15
+ .drag .mark { color: var(--accent); }
16
+ .drag .title { font-weight: 700; letter-spacing: .04em; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
17
+ .drag .status { margin-left: auto; color: var(--warn); font-size: 11px; }
18
+ .drag .close { -webkit-app-region: no-drag; cursor: pointer; background: none; border: 1px solid var(--border); color: var(--muted);
19
+ border-radius: 6px; width: 22px; height: 20px; line-height: 1; font-size: 12px; }
20
+ .drag .close:hover { color: var(--down); border-color: var(--down); }
21
+ .stage { position: relative; flex: 1 1 auto; min-height: 0; }
22
+ .stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
23
+ .legend { display: flex; flex-wrap: wrap; gap: 10px; padding: 4px 10px; font-size: 11px; color: var(--muted); border-top: 1px solid var(--border); }
24
+ .legend b { color: var(--dim); font-weight: 400; }
25
+ .foot { display: flex; gap: 14px; padding: 4px 10px; font-size: 11px; color: var(--dim); border-top: 1px solid var(--border); }
26
+ .foot .fps { margin-left: auto; color: var(--muted); }
27
+ /* list-style popups (token detail, feeds) */
28
+ .body { position: relative; flex: 1 1 auto; min-height: 0; overflow: auto; }
29
+ .rows { position: relative; z-index: 1; }
30
+ .row { display: flex; gap: 10px; padding: 4px 12px; border-bottom: 1px solid rgba(12,58,36,.3); }
31
+ .row .k { color: var(--muted); min-width: 92px; }
32
+ .row .v { color: var(--text); white-space: pre-wrap; word-break: break-word; }
33
+ .row.sel { background: rgba(0,240,255,.08); }
34
+ .up { color: var(--up); } .down { color: var(--down); } .warn { color: var(--warn); } .muted { color: var(--muted); }