dexterm-cli 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) 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 +394 -0
  4. package/dist/packages/agents/src/executor.js +202 -0
  5. package/dist/packages/agents/src/gate.js +179 -0
  6. package/dist/packages/agents/src/identity.js +218 -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 +119 -0
  12. package/dist/packages/agents/src/tools.js +72 -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 +75 -0
  18. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +74 -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 +336 -0
  26. package/dist/packages/cli/src/agent-roster.js +26 -0
  27. package/dist/packages/cli/src/alerts/adapters/desktop.js +40 -0
  28. package/dist/packages/cli/src/alerts/adapters/discord.js +36 -0
  29. package/dist/packages/cli/src/alerts/adapters/telegram.js +43 -0
  30. package/dist/packages/cli/src/alerts/adapters/toast.js +16 -0
  31. package/dist/packages/cli/src/alerts/creds.js +83 -0
  32. package/dist/packages/cli/src/alerts/format.js +41 -0
  33. package/dist/packages/cli/src/alerts/index.js +10 -0
  34. package/dist/packages/cli/src/alerts/notifier.js +53 -0
  35. package/dist/packages/cli/src/alerts/route.js +19 -0
  36. package/dist/packages/cli/src/alerts/types.js +1 -0
  37. package/dist/packages/cli/src/alerts-cmd.js +212 -0
  38. package/dist/packages/cli/src/analyst/backend.js +526 -0
  39. package/dist/packages/cli/src/analyst/results.js +38 -0
  40. package/dist/packages/cli/src/analyst/session.js +319 -0
  41. package/dist/packages/cli/src/audit.js +51 -0
  42. package/dist/packages/cli/src/backtest-cmd.js +208 -0
  43. package/dist/packages/cli/src/boot-state.js +62 -0
  44. package/dist/packages/cli/src/coin-target.js +2 -0
  45. package/dist/packages/cli/src/custom.js +261 -0
  46. package/dist/packages/cli/src/display.js +191 -0
  47. package/dist/packages/cli/src/doctor.js +375 -0
  48. package/dist/packages/cli/src/export.js +235 -0
  49. package/dist/packages/cli/src/feed.js +634 -0
  50. package/dist/packages/cli/src/fixtures.js +275 -0
  51. package/dist/packages/cli/src/follow-cmd.js +221 -0
  52. package/dist/packages/cli/src/images-config.js +59 -0
  53. package/dist/packages/cli/src/index.js +31 -0
  54. package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
  55. package/dist/packages/cli/src/larp-record.js +21 -0
  56. package/dist/packages/cli/src/live/copy.js +80 -0
  57. package/dist/packages/cli/src/live/flow.js +233 -0
  58. package/dist/packages/cli/src/live/index.js +511 -0
  59. package/dist/packages/cli/src/live/intel.js +227 -0
  60. package/dist/packages/cli/src/live/newpairs.js +334 -0
  61. package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
  62. package/dist/packages/cli/src/live/screens/token-live.js +459 -0
  63. package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
  64. package/dist/packages/cli/src/live/token.js +585 -0
  65. package/dist/packages/cli/src/live/trade.js +929 -0
  66. package/dist/packages/cli/src/live/watchlist.js +178 -0
  67. package/dist/packages/cli/src/main.js +5785 -0
  68. package/dist/packages/cli/src/mcp-cmd.js +326 -0
  69. package/dist/packages/cli/src/narrative-cmd.js +76 -0
  70. package/dist/packages/cli/src/pack-cmd.js +323 -0
  71. package/dist/packages/cli/src/plan-cmd.js +278 -0
  72. package/dist/packages/cli/src/popup.js +810 -0
  73. package/dist/packages/cli/src/rules-cmd.js +131 -0
  74. package/dist/packages/cli/src/rules.js +358 -0
  75. package/dist/packages/cli/src/scan/command.js +57 -0
  76. package/dist/packages/cli/src/scan/engine.js +419 -0
  77. package/dist/packages/cli/src/scan/observe.js +64 -0
  78. package/dist/packages/cli/src/scan/ui.js +98 -0
  79. package/dist/packages/cli/src/screener/filter.js +241 -0
  80. package/dist/packages/cli/src/screener/presets.js +80 -0
  81. package/dist/packages/cli/src/screener/rows.js +146 -0
  82. package/dist/packages/cli/src/screener/rug.js +50 -0
  83. package/dist/packages/cli/src/screener/sort.js +52 -0
  84. package/dist/packages/cli/src/screener/view.js +183 -0
  85. package/dist/packages/cli/src/screens/analyst-session.js +343 -0
  86. package/dist/packages/cli/src/screens/analyst.js +326 -0
  87. package/dist/packages/cli/src/screens/boot.js +74 -0
  88. package/dist/packages/cli/src/screens/bubbles.js +255 -0
  89. package/dist/packages/cli/src/screens/coin.js +33 -0
  90. package/dist/packages/cli/src/screens/follows.js +201 -0
  91. package/dist/packages/cli/src/screens/kol.js +90 -0
  92. package/dist/packages/cli/src/screens/narrative.js +90 -0
  93. package/dist/packages/cli/src/screens/newpairs.js +150 -0
  94. package/dist/packages/cli/src/screens/overlay.js +257 -0
  95. package/dist/packages/cli/src/screens/plans.js +126 -0
  96. package/dist/packages/cli/src/screens/positions.js +176 -0
  97. package/dist/packages/cli/src/screens/rules.js +170 -0
  98. package/dist/packages/cli/src/screens/screener.js +459 -0
  99. package/dist/packages/cli/src/screens/social-panel.js +107 -0
  100. package/dist/packages/cli/src/screens/ticket.js +342 -0
  101. package/dist/packages/cli/src/screens/token.js +312 -0
  102. package/dist/packages/cli/src/screens/trending.js +181 -0
  103. package/dist/packages/cli/src/screens/watchlist.js +104 -0
  104. package/dist/packages/cli/src/skill-eval-build.js +109 -0
  105. package/dist/packages/cli/src/social/demo.js +96 -0
  106. package/dist/packages/cli/src/social/view.js +250 -0
  107. package/dist/packages/cli/src/state.js +463 -0
  108. package/dist/packages/cli/src/theme-cmd.js +126 -0
  109. package/dist/packages/cli/src/trade.js +319 -0
  110. package/dist/packages/cli/src/update.js +294 -0
  111. package/dist/packages/cli/src/version.js +3 -0
  112. package/dist/packages/cli/src/voice-cmd.js +119 -0
  113. package/dist/packages/cli/src/voice-hud.js +42 -0
  114. package/dist/packages/cli/src/wallet-cmd.js +440 -0
  115. package/dist/packages/cli/src/wallet-command.js +31 -0
  116. package/dist/packages/cli/src/watch-cmd.js +190 -0
  117. package/dist/packages/cli/src/widgets.js +208 -0
  118. package/dist/packages/cli/src/workspace-cmd.js +68 -0
  119. package/dist/packages/cli/src/x-cmd.js +154 -0
  120. package/dist/packages/custom/src/config.js +213 -0
  121. package/dist/packages/custom/src/index.js +10 -0
  122. package/dist/packages/custom/src/keymap.js +107 -0
  123. package/dist/packages/custom/src/layout.js +235 -0
  124. package/dist/packages/custom/src/packs.js +633 -0
  125. package/dist/packages/custom/src/rules.js +704 -0
  126. package/dist/packages/custom/src/schema.js +538 -0
  127. package/dist/packages/custom/src/themes.js +176 -0
  128. package/dist/packages/custom/src/widget-html.js +222 -0
  129. package/dist/packages/custom/src/workspace.js +141 -0
  130. package/dist/packages/data/src/analyst-actions.js +133 -0
  131. package/dist/packages/data/src/budget.js +88 -0
  132. package/dist/packages/data/src/cache.js +142 -0
  133. package/dist/packages/data/src/candles.js +168 -0
  134. package/dist/packages/data/src/clock.js +64 -0
  135. package/dist/packages/data/src/coin-target.js +82 -0
  136. package/dist/packages/data/src/credits.js +137 -0
  137. package/dist/packages/data/src/detail.js +97 -0
  138. package/dist/packages/data/src/fast-lane.js +321 -0
  139. package/dist/packages/data/src/fixtures.js +135 -0
  140. package/dist/packages/data/src/health.js +245 -0
  141. package/dist/packages/data/src/http.js +277 -0
  142. package/dist/packages/data/src/images/cache.js +141 -0
  143. package/dist/packages/data/src/images/decode-worker.js +28 -0
  144. package/dist/packages/data/src/images/decode.js +81 -0
  145. package/dist/packages/data/src/images/fetch.js +162 -0
  146. package/dist/packages/data/src/images/gif.js +214 -0
  147. package/dist/packages/data/src/images/index.js +10 -0
  148. package/dist/packages/data/src/images/jpeg.js +544 -0
  149. package/dist/packages/data/src/images/png.js +310 -0
  150. package/dist/packages/data/src/images/service.js +334 -0
  151. package/dist/packages/data/src/images/types.js +27 -0
  152. package/dist/packages/data/src/index.js +211 -0
  153. package/dist/packages/data/src/intel/helius-history.js +70 -0
  154. package/dist/packages/data/src/intel/holder-class.js +125 -0
  155. package/dist/packages/data/src/intel/rules.js +421 -0
  156. package/dist/packages/data/src/intel/scan.js +170 -0
  157. package/dist/packages/data/src/larp/evidence.js +288 -0
  158. package/dist/packages/data/src/larp/link-baseline.js +196 -0
  159. package/dist/packages/data/src/larp/links.js +137 -0
  160. package/dist/packages/data/src/larp/robots.js +90 -0
  161. package/dist/packages/data/src/larp/safe-fetch.js +389 -0
  162. package/dist/packages/data/src/limiter.js +341 -0
  163. package/dist/packages/data/src/model.js +12 -0
  164. package/dist/packages/data/src/paper.js +331 -0
  165. package/dist/packages/data/src/rpc-config.js +126 -0
  166. package/dist/packages/data/src/rpc-pool.js +166 -0
  167. package/dist/packages/data/src/social/chain.js +83 -0
  168. package/dist/packages/data/src/social/dedupe.js +205 -0
  169. package/dist/packages/data/src/social/feed.js +462 -0
  170. package/dist/packages/data/src/social/firewall.js +110 -0
  171. package/dist/packages/data/src/social/index.js +11 -0
  172. package/dist/packages/data/src/social/kol.js +269 -0
  173. package/dist/packages/data/src/social/model.js +11 -0
  174. package/dist/packages/data/src/social/rank.js +184 -0
  175. package/dist/packages/data/src/social/rss.js +113 -0
  176. package/dist/packages/data/src/social/summary.js +104 -0
  177. package/dist/packages/data/src/social/venues.js +186 -0
  178. package/dist/packages/data/src/sources/dexscreener.js +184 -0
  179. package/dist/packages/data/src/sources/geckoterminal.js +133 -0
  180. package/dist/packages/data/src/sources/goplus.js +161 -0
  181. package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
  182. package/dist/packages/data/src/sources/jupiter.js +198 -0
  183. package/dist/packages/data/src/sources/pda.js +113 -0
  184. package/dist/packages/data/src/sources/pump-curve.js +58 -0
  185. package/dist/packages/data/src/sources/pumpportal.js +252 -0
  186. package/dist/packages/data/src/sources/rugcheck.js +71 -0
  187. package/dist/packages/data/src/sources/solana-rpc.js +299 -0
  188. package/dist/packages/data/src/sources/solana-ws.js +287 -0
  189. package/dist/packages/data/src/store.js +289 -0
  190. package/dist/packages/data/src/trade/bridge.js +464 -0
  191. package/dist/packages/data/src/trade/caps.js +323 -0
  192. package/dist/packages/data/src/trade/copy/auto.js +385 -0
  193. package/dist/packages/data/src/trade/copy/follows.js +103 -0
  194. package/dist/packages/data/src/trade/copy/index.js +9 -0
  195. package/dist/packages/data/src/trade/copy/mirror.js +91 -0
  196. package/dist/packages/data/src/trade/copy/stats.js +120 -0
  197. package/dist/packages/data/src/trade/copy/tracker.js +158 -0
  198. package/dist/packages/data/src/trade/copy/trades.js +87 -0
  199. package/dist/packages/data/src/trade/copy/types.js +6 -0
  200. package/dist/packages/data/src/trade/executor.js +933 -0
  201. package/dist/packages/data/src/trade/kill-switch.js +47 -0
  202. package/dist/packages/data/src/trade/link-store.js +52 -0
  203. package/dist/packages/data/src/trade/plans/confirm.js +157 -0
  204. package/dist/packages/data/src/trade/plans/engine.js +784 -0
  205. package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
  206. package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
  207. package/dist/packages/data/src/trade/plans/executor.js +11 -0
  208. package/dist/packages/data/src/trade/plans/index.js +12 -0
  209. package/dist/packages/data/src/trade/plans/journal.js +77 -0
  210. package/dist/packages/data/src/trade/plans/mandate.js +129 -0
  211. package/dist/packages/data/src/trade/plans/plan.js +179 -0
  212. package/dist/packages/data/src/trade/plans/service.js +168 -0
  213. package/dist/packages/data/src/trade/plans/types.js +6 -0
  214. package/dist/packages/data/src/trade/rpc-exec.js +229 -0
  215. package/dist/packages/data/src/trade/signer/client.js +197 -0
  216. package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
  217. package/dist/packages/data/src/trade/signer/host.js +97 -0
  218. package/dist/packages/data/src/trade/signer/main.js +121 -0
  219. package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
  220. package/dist/packages/data/src/trade/signer/protocol.js +92 -0
  221. package/dist/packages/data/src/trade/signer/service.js +400 -0
  222. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
  223. package/dist/packages/data/src/trade/sol-usd.js +86 -0
  224. package/dist/packages/data/src/trade/swap-build.js +108 -0
  225. package/dist/packages/data/src/trade/token-delta.js +201 -0
  226. package/dist/packages/data/src/trade/tx-decode.js +632 -0
  227. package/dist/packages/data/src/util.js +24 -0
  228. package/dist/packages/data/src/watchlist.js +176 -0
  229. package/dist/packages/data/src/x/api.js +256 -0
  230. package/dist/packages/data/src/x/budget.js +66 -0
  231. package/dist/packages/data/src/x/config.js +49 -0
  232. package/dist/packages/data/src/x/index.js +10 -0
  233. package/dist/packages/data/src/x/mock-server.js +217 -0
  234. package/dist/packages/data/src/x/oauth.js +242 -0
  235. package/dist/packages/data/src/x/pkce.js +21 -0
  236. package/dist/packages/data/src/x/source.js +285 -0
  237. package/dist/packages/data/src/x/token-store.js +134 -0
  238. package/dist/packages/data/src/x/transport.js +71 -0
  239. package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
  240. package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
  241. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
  242. package/dist/packages/mcp/assets/cases.json +1194 -0
  243. package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
  244. package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
  245. package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
  246. package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
  247. package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
  248. package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
  249. package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
  250. package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
  251. package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
  252. package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
  253. package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
  254. package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
  255. package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
  256. package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
  257. package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
  258. package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
  259. package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
  260. package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
  261. package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
  262. package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
  263. package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
  264. package/dist/packages/mcp/src/dossier.js +78 -0
  265. package/dist/packages/mcp/src/firewall.js +21 -0
  266. package/dist/packages/mcp/src/index.js +22 -0
  267. package/dist/packages/mcp/src/server.js +936 -0
  268. package/dist/packages/mcp/src/user-content.js +434 -0
  269. package/dist/packages/packs/src/backtest.js +183 -0
  270. package/dist/packages/packs/src/digest.js +95 -0
  271. package/dist/packages/packs/src/firewall.js +57 -0
  272. package/dist/packages/packs/src/index.js +20 -0
  273. package/dist/packages/packs/src/larp-eval.js +100 -0
  274. package/dist/packages/packs/src/narrative.js +309 -0
  275. package/dist/packages/packs/src/reason.js +135 -0
  276. package/dist/packages/packs/src/rug-model.js +137 -0
  277. package/dist/packages/packs/src/schema.js +54 -0
  278. package/dist/packages/packs/src/skill-backtest.js +300 -0
  279. package/dist/packages/packs/src/token-dossier.js +174 -0
  280. package/dist/packages/packs/src/verdicts.js +17 -0
  281. package/dist/packages/packs/src/wallet-dossier.js +105 -0
  282. package/dist/packages/popup/src/config.js +71 -0
  283. package/dist/packages/popup/src/dock.js +271 -0
  284. package/dist/packages/popup/src/host.js +210 -0
  285. package/dist/packages/popup/src/index.js +12 -0
  286. package/dist/packages/popup/src/loopback.js +53 -0
  287. package/dist/packages/popup/src/open.js +183 -0
  288. package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
  289. package/dist/packages/popup/src/pages/bubbles.css +23 -0
  290. package/dist/packages/popup/src/pages/bubbles.html +36 -0
  291. package/dist/packages/popup/src/pages/bubbles.js +214 -0
  292. package/dist/packages/popup/src/pages/chart-lib.js +262 -0
  293. package/dist/packages/popup/src/pages/chart.css +50 -0
  294. package/dist/packages/popup/src/pages/chart.html +56 -0
  295. package/dist/packages/popup/src/pages/chart.js +554 -0
  296. package/dist/packages/popup/src/pages/common.css +34 -0
  297. package/dist/packages/popup/src/pages/common.js +187 -0
  298. package/dist/packages/popup/src/pages/feeds.html +20 -0
  299. package/dist/packages/popup/src/pages/feeds.js +77 -0
  300. package/dist/packages/popup/src/pages/narrative.html +20 -0
  301. package/dist/packages/popup/src/pages/narrative.js +61 -0
  302. package/dist/packages/popup/src/pages/plans.css +30 -0
  303. package/dist/packages/popup/src/pages/plans.html +26 -0
  304. package/dist/packages/popup/src/pages/plans.js +82 -0
  305. package/dist/packages/popup/src/pages/roster.css +41 -0
  306. package/dist/packages/popup/src/pages/roster.html +25 -0
  307. package/dist/packages/popup/src/pages/roster.js +75 -0
  308. package/dist/packages/popup/src/pages/token.html +20 -0
  309. package/dist/packages/popup/src/pages/token.js +112 -0
  310. package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
  311. package/dist/packages/popup/src/pages/widget.css +5 -0
  312. package/dist/packages/popup/src/pages/widget.html +21 -0
  313. package/dist/packages/popup/src/pages/widget.js +85 -0
  314. package/dist/packages/popup/src/protocol.js +31 -0
  315. package/dist/packages/popup/src/selftest.js +156 -0
  316. package/dist/packages/popup/src/server.js +582 -0
  317. package/dist/packages/popup/src/sinkproxy.js +167 -0
  318. package/dist/packages/popup/src/widget-html.js +231 -0
  319. package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
  320. package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
  321. package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
  322. package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
  323. package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
  324. package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
  325. package/dist/packages/recorder/src/cli.js +113 -0
  326. package/dist/packages/recorder/src/export.js +71 -0
  327. package/dist/packages/recorder/src/index.js +20 -0
  328. package/dist/packages/recorder/src/labels.js +151 -0
  329. package/dist/packages/recorder/src/open.js +29 -0
  330. package/dist/packages/recorder/src/recorder.js +553 -0
  331. package/dist/packages/recorder/src/schedule.js +43 -0
  332. package/dist/packages/recorder/src/schema.js +135 -0
  333. package/dist/packages/recorder/src/snapshot.js +191 -0
  334. package/dist/packages/recorder/src/sweep.js +52 -0
  335. package/dist/packages/recorder/src/verdicts.js +52 -0
  336. package/dist/packages/tui/src/bubbles.js +415 -0
  337. package/dist/packages/tui/src/canvas.js +370 -0
  338. package/dist/packages/tui/src/chrome.js +85 -0
  339. package/dist/packages/tui/src/color.js +133 -0
  340. package/dist/packages/tui/src/flush.js +62 -0
  341. package/dist/packages/tui/src/fmt.js +188 -0
  342. package/dist/packages/tui/src/fx-trade.js +278 -0
  343. package/dist/packages/tui/src/fx.js +527 -0
  344. package/dist/packages/tui/src/images/blocks.js +141 -0
  345. package/dist/packages/tui/src/images/coin.js +22 -0
  346. package/dist/packages/tui/src/images/detect.js +140 -0
  347. package/dist/packages/tui/src/images/index.js +7 -0
  348. package/dist/packages/tui/src/images/layer.js +146 -0
  349. package/dist/packages/tui/src/images/pixels.js +157 -0
  350. package/dist/packages/tui/src/images/protocols.js +87 -0
  351. package/dist/packages/tui/src/index.js +18 -0
  352. package/dist/packages/tui/src/keymap.js +357 -0
  353. package/dist/packages/tui/src/layout.js +11 -0
  354. package/dist/packages/tui/src/raster-font.js +195 -0
  355. package/dist/packages/tui/src/raster.js +213 -0
  356. package/dist/packages/tui/src/sound.js +177 -0
  357. package/dist/packages/tui/src/theme.js +182 -0
  358. package/dist/packages/tui/src/widgets.js +598 -0
  359. package/dist/packages/voice/src/controller.js +407 -0
  360. package/dist/packages/voice/src/doctor.js +131 -0
  361. package/dist/packages/voice/src/download.js +184 -0
  362. package/dist/packages/voice/src/engines.js +84 -0
  363. package/dist/packages/voice/src/index.js +20 -0
  364. package/dist/packages/voice/src/router.js +502 -0
  365. package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
  366. package/dist/packages/voice/src/stt/apple.js +179 -0
  367. package/dist/packages/voice/src/stt/fake.js +34 -0
  368. package/dist/packages/voice/src/stt/whisper.js +112 -0
  369. package/dist/packages/voice/src/summary.js +44 -0
  370. package/dist/packages/voice/src/tts/espeak.js +131 -0
  371. package/dist/packages/voice/src/tts/fake.js +30 -0
  372. package/dist/packages/voice/src/tts/piper.js +206 -0
  373. package/dist/packages/voice/src/tts/say.js +132 -0
  374. package/dist/packages/voice/src/tts/voices.js +206 -0
  375. package/dist/packages/voice/src/types.js +3 -0
  376. package/package.json +38 -4
  377. 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); }