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,370 @@
1
+ // @dexterm/tui — cell-buffer canvas with plain, ANSI (24-bit / 256 / none) and HTML emitters.
2
+ // Every glyph drawn is single-column (box drawing, blocks, braille, half-width katakana), so x == column.
3
+ //
4
+ // Two masks make the cyberspace practicality rules mechanical (VISUAL-V2 §3):
5
+ // `hard[i]` set by put() for any data glyph (digits, letters, $ % ▲ ▼ …) unless the writer says
6
+ // `soft: true` (borders, blocks, braille, rain). Effects write through putFx(), which
7
+ // refuses hard cells and non-blank cells: no effect can overdraw a number by construction.
8
+ // `zone[i]` 1 = band/gutter (full-brightness rain), 2 = panel interior (dim rain), 0 = never rains.
9
+ // Screens opt areas in with rainZone(); widgets opt the area under their last row in.
10
+ // `headings` are rects (box titles, logo, section caps) that may take the chromatic colour split.
11
+ import { hexToRgb, hexToAnsi256, mix } from "./color.js";
12
+ export const ATTR_BOLD = 1;
13
+ export const ATTR_DIM = 2;
14
+ export const ATTR_ITALIC = 4;
15
+ export const ATTR_UNDERLINE = 8;
16
+ /** Data glyphs: anything carrying a value or a label the trader reads. Effects never overdraw these. */
17
+ const HARD_RE = /[0-9A-Za-z$%+\-.,:/…₀-₉▲▼✓✗●◐○•›↳⏎⌕◉◌✦#§'"?!()<>=×_|]/;
18
+ export function isHardGlyph(ch) { return HARD_RE.test(ch); }
19
+ /** SGR sequence for one cell style at the given depth. Exported so the flusher shares it. */
20
+ export function sgr(fg, bg, at, depth) {
21
+ let seq = '\x1b[0';
22
+ if (at & ATTR_BOLD)
23
+ seq += ';1';
24
+ if (at & ATTR_DIM)
25
+ seq += ';2';
26
+ if (at & ATTR_ITALIC)
27
+ seq += ';3';
28
+ if (at & ATTR_UNDERLINE)
29
+ seq += ';4';
30
+ if (depth === 'truecolor') {
31
+ const [r, g, b] = hexToRgb(fg);
32
+ const [r2, g2, b2] = hexToRgb(bg);
33
+ seq += `;38;2;${r};${g};${b};48;2;${r2};${g2};${b2}`;
34
+ }
35
+ else if (depth === '256') {
36
+ seq += `;38;5;${hexToAnsi256(fg)};48;5;${hexToAnsi256(bg)}`;
37
+ }
38
+ return seq + 'm';
39
+ }
40
+ export class Canvas {
41
+ w;
42
+ h;
43
+ theme;
44
+ ch;
45
+ fg;
46
+ bg;
47
+ /** bit0 bold, bit1 dim, bit2 italic, bit3 underline */
48
+ at;
49
+ /** 1 = data glyph; effects never touch it. */
50
+ hard;
51
+ /** 0 never rains, 1 band/gutter (bright), 2 panel interior (dim). */
52
+ zone;
53
+ /** Heading rects eligible for the chromatic colour split. */
54
+ headings;
55
+ /**
56
+ * task-1340: 1 = a cell NO effect may touch at all (the trade ticket in every phase, the caps line, the result
57
+ * card): putFx / tintFx refuse it, datamosh skips it as source and destination, scanlines skip it, and the
58
+ * whole-frame shake is disabled while any shield exists. `shields` keeps the rects for tests.
59
+ */
60
+ shield;
61
+ shields;
62
+ /**
63
+ * task-1326: image slots recorded by coinImage() when `imageMode` is 'pixels' — cell boxes a pixel
64
+ * protocol (kitty / iTerm2 / sixel) paints over after the flush. Empty in 'cells' mode (block art is
65
+ * drawn straight into the cells). Not part of the plain/ANSI emitters.
66
+ */
67
+ images;
68
+ imageMode;
69
+ constructor(w, h, theme) {
70
+ this.w = w;
71
+ this.h = h;
72
+ this.theme = theme;
73
+ const n = w * h;
74
+ this.ch = new Array(n).fill(' ');
75
+ this.fg = new Array(n).fill(theme.text);
76
+ this.bg = new Array(n).fill(theme.bg);
77
+ this.at = new Uint8Array(n);
78
+ this.hard = new Uint8Array(n);
79
+ this.zone = new Uint8Array(n);
80
+ this.headings = [];
81
+ this.shield = new Uint8Array(n);
82
+ this.shields = [];
83
+ this.images = [];
84
+ this.imageMode = 'cells';
85
+ }
86
+ idx(x, y) { return y * this.w + x; }
87
+ /** Copy every buffer of `src` (same size) into this canvas. */
88
+ copyFrom(src) {
89
+ this.ch = src.ch.slice();
90
+ this.fg = src.fg.slice();
91
+ this.bg = src.bg.slice();
92
+ this.at = src.at.slice();
93
+ this.hard = src.hard.slice();
94
+ this.zone = src.zone.slice();
95
+ this.headings = src.headings.slice();
96
+ this.shield = src.shield.slice();
97
+ this.shields = src.shields.slice();
98
+ this.images = src.images.slice();
99
+ this.imageMode = src.imageMode;
100
+ }
101
+ put(x, y, ch, st = {}) {
102
+ if (x < 0 || y < 0 || x >= this.w || y >= this.h)
103
+ return;
104
+ const i = this.idx(x, y);
105
+ this.ch[i] = ch;
106
+ if (st.fg)
107
+ this.fg[i] = st.fg;
108
+ if (st.bg)
109
+ this.bg[i] = st.bg;
110
+ this.at[i] = (st.b ? ATTR_BOLD : 0) | (st.d ? ATTR_DIM : 0) | (st.i ? ATTR_ITALIC : 0) | (st.u ? ATTR_UNDERLINE : 0);
111
+ this.hard[i] = st.soft ? 0 : (st.hard || HARD_RE.test(ch)) ? 1 : 0;
112
+ }
113
+ /** Effects-only writer: refuses hard cells, non-blank cells (unless `overwrite`) and, with needZone, cells outside rain zones. */
114
+ putFx(x, y, ch, st = {}, needZone = false) {
115
+ if (x < 0 || y < 0 || x >= this.w || y >= this.h)
116
+ return false;
117
+ const i = this.idx(x, y);
118
+ if (this.hard[i] || this.shield[i])
119
+ return false;
120
+ if (needZone && !this.zone[i])
121
+ return false;
122
+ if (this.ch[i] !== ' ' && !st.overwrite)
123
+ return false;
124
+ this.ch[i] = ch;
125
+ if (st.fg)
126
+ this.fg[i] = st.fg;
127
+ if (st.bg)
128
+ this.bg[i] = st.bg;
129
+ this.at[i] = (st.b ? ATTR_BOLD : 0) | (st.d ? ATTR_DIM : 0);
130
+ return true;
131
+ }
132
+ /** Effects-only background tint (task-1340): refuses hard and shielded cells, so a number's contrast never moves. */
133
+ tintFx(x, y, color, amount) {
134
+ if (x < 0 || y < 0 || x >= this.w || y >= this.h)
135
+ return false;
136
+ const i = this.idx(x, y);
137
+ if (this.hard[i] || this.shield[i])
138
+ return false;
139
+ this.bg[i] = mix(this.bg[i], color, amount);
140
+ return true;
141
+ }
142
+ /** Shield a rect from every effect pass (task-1340: the trade ticket). */
143
+ shieldRect(x, y, w, h) {
144
+ this.shields.push({ x, y, w, h });
145
+ for (let j = 0; j < h; j++)
146
+ for (let k = 0; k < w; k++) {
147
+ const xx = x + k, yy = y + j;
148
+ if (xx < 0 || yy < 0 || xx >= this.w || yy >= this.h)
149
+ continue;
150
+ this.shield[this.idx(xx, yy)] = 1;
151
+ }
152
+ }
153
+ get hasShield() { return this.shields.length > 0; }
154
+ /** Draw text left-aligned at (x, y), clipped to maxW columns. Returns the x after the text. */
155
+ text(x, y, str, st = {}, maxW = Infinity) {
156
+ const s = String(str);
157
+ const lim = Math.min(s.length, maxW);
158
+ for (let k = 0; k < lim; k++)
159
+ this.put(x + k, y, s[k], st);
160
+ return x + lim;
161
+ }
162
+ /** Right-aligned text within [x, x+w). */
163
+ textR(x, y, w, str, st = {}) {
164
+ const s = String(str).slice(0, w);
165
+ return this.text(x + w - s.length, y, s, st);
166
+ }
167
+ /** Centered text within [x, x+w). */
168
+ textC(x, y, w, str, st = {}) {
169
+ const s = String(str).slice(0, w);
170
+ return this.text(x + Math.floor((w - s.length) / 2), y, s, st);
171
+ }
172
+ fill(x, y, w, h, ch = ' ', st = {}) {
173
+ for (let j = 0; j < h; j++)
174
+ for (let k = 0; k < w; k++)
175
+ this.put(x + k, y + j, ch, st);
176
+ }
177
+ /** Paint background only, keep glyphs. */
178
+ shade(x, y, w, h, bg) {
179
+ for (let j = 0; j < h; j++) {
180
+ for (let k = 0; k < w; k++) {
181
+ const xx = x + k, yy = y + j;
182
+ if (xx < 0 || yy < 0 || xx >= this.w || yy >= this.h)
183
+ continue;
184
+ this.bg[this.idx(xx, yy)] = bg;
185
+ }
186
+ }
187
+ }
188
+ /** Mark a rect as rain-eligible. kind 1 = band/gutter (full brightness), 2 = panel interior (dim). Only blank cells rain. */
189
+ rainZone(x, y, w, h, kind = 1) {
190
+ for (let j = 0; j < h; j++) {
191
+ for (let k = 0; k < w; k++) {
192
+ const xx = x + k, yy = y + j;
193
+ if (xx < 0 || yy < 0 || xx >= this.w || yy >= this.h)
194
+ continue;
195
+ this.zone[this.idx(xx, yy)] = kind;
196
+ }
197
+ }
198
+ }
199
+ /** Clear the rain mask over a rect (logos, status boxes, toasts). */
200
+ noRain(x, y, w, h) {
201
+ for (let j = 0; j < h; j++) {
202
+ for (let k = 0; k < w; k++) {
203
+ const xx = x + k, yy = y + j;
204
+ if (xx < 0 || yy < 0 || xx >= this.w || yy >= this.h)
205
+ continue;
206
+ this.zone[this.idx(xx, yy)] = 0;
207
+ }
208
+ }
209
+ }
210
+ /** Record a heading rect so fx can chromatic-aberrate it (ghost glyphs land only in blank neighbours). */
211
+ heading(x, y, w) { this.headings.push({ x, y, w, h: 1 }); }
212
+ hline(x, y, w, st = {}, ch = '─') { for (let k = 0; k < w; k++)
213
+ this.put(x + k, y, ch, st); }
214
+ vline(x, y, h, st = {}, ch = '│') { for (let k = 0; k < h; k++)
215
+ this.put(x, y + k, ch, st); }
216
+ /** Panel box with optional title and right-side caption. Returns the inner rect. */
217
+ box(x, y, w, h, opts = {}) {
218
+ const t = this.theme;
219
+ const bg = opts.bg || t.panel;
220
+ const bc = opts.focus ? t.borderFocus : (opts.border || t.border);
221
+ const st = { fg: bc, bg };
222
+ this.fill(x, y, w, h, ' ', { bg });
223
+ const r = opts.rounded !== false;
224
+ this.put(x, y, r ? '╭' : '┌', st);
225
+ this.put(x + w - 1, y, r ? '╮' : '┐', st);
226
+ this.put(x, y + h - 1, r ? '╰' : '└', st);
227
+ this.put(x + w - 1, y + h - 1, r ? '╯' : '┘', st);
228
+ this.hline(x + 1, y, w - 2, st);
229
+ this.hline(x + 1, y + h - 1, w - 2, st);
230
+ this.vline(x, y + 1, h - 2, st);
231
+ this.vline(x + w - 1, y + 1, h - 2, st);
232
+ const title = opts.title ? ` ${opts.title} ` : '';
233
+ const cap = opts.caption ? ` ${opts.caption} ` : '';
234
+ // Title always wins; caption is dropped when both do not fit.
235
+ const showCap = cap && (title.length + cap.length + 6 <= w);
236
+ if (title) {
237
+ this.text(x + 2, y, title, { fg: opts.focus ? t.borderFocus : (opts.titleFg || t.text), bg, b: true }, w - 4);
238
+ if (!opts.plain)
239
+ this.heading(x + 2, y, Math.min(title.length, w - 4));
240
+ }
241
+ if (showCap)
242
+ this.text(x + w - 2 - cap.length, y, cap, { fg: opts.captionFg || t.muted, bg }, w - 4);
243
+ return { x: x + 1, y: y + 1, w: w - 2, h: h - 2, bg };
244
+ }
245
+ // ---------- emitters ----------
246
+ /** One row as plain text, trailing whitespace trimmed. */
247
+ rowPlain(y) {
248
+ let line = '';
249
+ for (let x = 0; x < this.w; x++)
250
+ line += this.ch[this.idx(x, y)];
251
+ return line.replace(/\s+$/, '');
252
+ }
253
+ /** Monochrome text render (the snapshot-test surface). */
254
+ toPlain() {
255
+ const rows = [];
256
+ for (let y = 0; y < this.h; y++)
257
+ rows.push(this.rowPlain(y));
258
+ return rows.join('\n');
259
+ }
260
+ /** One row as ANSI with a trailing reset; styles are re-emitted at the start of the row. */
261
+ rowAnsi(y, depth = 'truecolor') {
262
+ let row = '';
263
+ let pfg = null, pbg = null, pat = -1;
264
+ for (let x = 0; x < this.w; x++) {
265
+ const i = this.idx(x, y);
266
+ const fg = this.fg[i], bg = this.bg[i], at = this.at[i];
267
+ if (depth === 'none') {
268
+ // Colors dropped; attributes still carry emphasis. Collapse color-only changes.
269
+ if (at !== pat) {
270
+ row += sgr(fg, bg, at, depth);
271
+ pat = at;
272
+ }
273
+ }
274
+ else if (fg !== pfg || bg !== pbg || at !== pat) {
275
+ row += sgr(fg, bg, at, depth);
276
+ pfg = fg;
277
+ pbg = bg;
278
+ pat = at;
279
+ }
280
+ row += this.ch[i];
281
+ }
282
+ return row + '\x1b[0m';
283
+ }
284
+ /** Full-frame ANSI render. */
285
+ toAnsi({ home = false, depth = 'truecolor' } = {}) {
286
+ let out = home ? '\x1b[H' : '';
287
+ for (let y = 0; y < this.h; y++) {
288
+ out += this.rowAnsi(y, depth) + (y < this.h - 1 ? '\n' : '');
289
+ }
290
+ return out;
291
+ }
292
+ /**
293
+ * Standalone HTML page (screenshots, design review, video frames). Cell-exact: per-cell fg/bg only,
294
+ * every run pinned to `N ch` so a glyph that falls back to another font cannot shift the grid.
295
+ */
296
+ toHtml({ title = 'dexterm', fontPx = 15, chrome = true, fit = null, depth = 'truecolor' } = {}) {
297
+ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
298
+ const t = this.theme;
299
+ // 256-colour proof: map each hex to the xterm cube/grey entry its SGR index would select
300
+ const pal = (hex) => {
301
+ if (depth !== '256')
302
+ return hex;
303
+ const i = hexToAnsi256(hex);
304
+ if (i >= 232) {
305
+ const v = 8 + 10 * (i - 232);
306
+ return '#' + [v, v, v].map((n) => n.toString(16).padStart(2, '0')).join('');
307
+ }
308
+ const k = i - 16, cube = [0, 95, 135, 175, 215, 255];
309
+ return '#' + [cube[Math.floor(k / 36)], cube[Math.floor(k / 6) % 6], cube[k % 6]].map((n) => n.toString(16).padStart(2, '0')).join('');
310
+ };
311
+ let body = '';
312
+ for (let y = 0; y < this.h; y++) {
313
+ let run = '', pk = null, line = '';
314
+ const flush = () => {
315
+ if (!run || pk === null)
316
+ return;
317
+ const [fg, bg, at] = pk.split('|');
318
+ const a = Number(at);
319
+ let style = `color:${fg};background:${bg};width:${run.length}ch;overflow:hidden;`;
320
+ if (a & ATTR_BOLD)
321
+ style += 'font-weight:700;';
322
+ if (a & ATTR_DIM)
323
+ style += 'opacity:.6;';
324
+ if (a & ATTR_ITALIC)
325
+ style += 'font-style:italic;';
326
+ if (a & ATTR_UNDERLINE)
327
+ style += 'text-decoration:underline;';
328
+ line += `<span style="${style}">${esc(run)}</span>`;
329
+ run = '';
330
+ };
331
+ for (let x = 0; x < this.w; x++) {
332
+ const i = this.idx(x, y);
333
+ const k = `${pal(this.fg[i])}|${pal(this.bg[i])}|${this.at[i]}`;
334
+ if (k !== pk) {
335
+ flush();
336
+ pk = k;
337
+ }
338
+ run += this.ch[i];
339
+ }
340
+ flush();
341
+ body += line + '\n';
342
+ }
343
+ const lh = Math.round(fontPx * 1.3);
344
+ const font = `font-family:'JetBrains Mono','SF Mono',Menlo,Monaco,'Cascadia Code',monospace;font-size:${fontPx}px;line-height:${lh}px;font-variant-ligatures:none;`;
345
+ if (fit) {
346
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${esc(title)}</title>
347
+ <style>
348
+ html,body{margin:0;padding:0;background:${t.bg};width:${fit.width}px;height:${fit.height}px;overflow:hidden;}
349
+ body{display:flex;align-items:center;justify-content:center;}
350
+ pre{margin:0;${font}background:${t.bg};color:${t.text};white-space:pre;}
351
+ pre span{display:inline-block;height:${lh}px;vertical-align:top;}
352
+ </style></head><body><pre>${body}</pre></body></html>`;
353
+ }
354
+ const chromeHtml = chrome
355
+ ? `<div class="bar"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="ttl">${esc(title)}</span></div>`
356
+ : '';
357
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${esc(title)}</title>
358
+ <style>
359
+ html,body{margin:0;padding:0;background:#05070a;}
360
+ body{padding:28px;display:inline-block;}
361
+ .win{display:inline-block;border-radius:12px;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.06);background:${t.bg};}
362
+ .bar{height:34px;background:${t.headBg};display:flex;align-items:center;padding:0 14px;gap:8px;border-bottom:1px solid ${t.border};}
363
+ .dot{width:12px;height:12px;border-radius:50%;display:inline-block}
364
+ .dot.r{background:#ff5f57}.dot.y{background:#febc2e}.dot.g{background:#28c840}
365
+ .ttl{margin-left:auto;margin-right:auto;color:${t.muted};font:500 12px -apple-system,'SF Pro Text',Inter,system-ui,sans-serif;letter-spacing:.2px}
366
+ pre{margin:0;padding:10px 12px 12px 12px;${font}background:${t.bg};color:${t.text};white-space:pre;}
367
+ pre span{display:inline-block;height:${lh}px;vertical-align:top;}
368
+ </style></head><body><div class="win">${chromeHtml}<pre>${body}</pre></div></body></html>`;
369
+ }
370
+ }
@@ -0,0 +1,85 @@
1
+ import { keyHints, dirGlyph } from "./widgets.js";
2
+ import { fmtPct, clock } from "./fmt.js";
3
+ import { mix } from "./color.js";
4
+ export const SCREENS = [
5
+ { key: 'screener', label: 'Screener', hot: '1' },
6
+ { key: 'newpairs', label: 'New pairs', hot: '2' },
7
+ { key: 'watchlist', label: 'Watchlist', hot: '3' },
8
+ { key: 'analyst', label: 'Analyst', hot: '4' },
9
+ ];
10
+ export function header(c, m) {
11
+ const t = c.theme;
12
+ const bg = t.headBg;
13
+ c.fill(0, 0, c.w, 1, ' ', { bg });
14
+ // right status cluster (computed first so tabs never collide with it)
15
+ const solStr = m.sol ? `SOL $${m.sol.price.toFixed(2)} ` : '';
16
+ const chgStr = m.sol ? dirGlyph(m.sol.chg24) + fmtPct(Math.abs(m.sol.chg24), { sign: false }) : '';
17
+ const live = m.status ?? (m.paused ? '◌ paused' : m.calm ? '◉ live · calm' : '◉ live');
18
+ const feed = c.w >= 150 ? ` ${m.feedLabel || 'dexscreener · jup · gecko · rugcheck'} ` : '';
19
+ const clk = clock(m.now, m.timeZone);
20
+ const rightW = feed.length + solStr.length + chgStr.length + (m.sol ? 2 : 0) + live.length + 2 + clk.length + 1;
21
+ let rx = c.w - rightW;
22
+ if (feed)
23
+ rx = c.text(rx, 0, feed, { fg: t.dim, bg });
24
+ if (m.sol) {
25
+ rx = c.text(rx, 0, solStr, { fg: t.muted, bg });
26
+ rx = c.text(rx, 0, chgStr, { fg: m.sol.chg24 >= 0 ? t.up : t.down, bg });
27
+ rx = c.text(rx, 0, ' ', { bg });
28
+ }
29
+ rx = c.text(rx, 0, live, { fg: m.status ? t.warn : m.paused ? t.warn : t.up, bg, b: true });
30
+ rx = c.text(rx, 0, ' ', { bg });
31
+ c.text(rx, 0, clk, { fg: t.muted, bg });
32
+ const limit = c.w - rightW - 1;
33
+ // logo: "dex" phosphor, "term" white, both bold; registered as a heading so it chromatic-aberrates
34
+ let x = c.text(1, 0, ' dex', { fg: t.borderFocus, bg, b: true });
35
+ x = c.text(x, 0, 'term ', { fg: t.text, bg, b: true });
36
+ c.heading(2, 0, 7);
37
+ x = c.text(x, 0, '┃ ', { fg: t.border, bg, soft: true });
38
+ const compact = c.w < 110;
39
+ for (const s of SCREENS) {
40
+ const active = s.key === m.screen || ((m.screen === 'token' || m.screen === 'ticket') && s.key === 'screener');
41
+ const label = compact ? ` ${s.hot} ${s.label.split(' ')[0]} ` : ` ${s.hot} ${s.label} `;
42
+ if (x + label.length > limit)
43
+ break;
44
+ if (active) {
45
+ x = c.text(x, 0, label, { fg: t.borderFocus, bg: t.tabActiveBg, b: true });
46
+ c.put(x, 0, '▌', { fg: t.tabActiveBg, bg, soft: true });
47
+ x += 1;
48
+ }
49
+ else {
50
+ x = c.text(x, 0, label, { fg: t.muted, bg });
51
+ x += 1;
52
+ }
53
+ }
54
+ // blank middle of the header + the band under it are rain zones
55
+ c.rainZone(x, 0, Math.max(0, c.w - rightW - x - 1), 1, 1);
56
+ c.fill(0, 1, c.w, 1, ' ', { bg: t.bg });
57
+ c.rainZone(0, 1, c.w, 1, 1);
58
+ }
59
+ export function footer(c, hints, themeName, calm = false) {
60
+ const t = c.theme;
61
+ const y = c.h - 1;
62
+ c.fill(0, y, c.w, 1, ' ', { bg: t.headBg });
63
+ const right = ` ${themeName}${calm ? ' · calm' : ''} · ${c.w}×${c.h} `;
64
+ const end = keyHints(c, 1, y, c.w - right.length - 2, hints, t.headBg);
65
+ // Prototype parity: the label is drawn at the last column and clipped (fix queued for D-19).
66
+ c.textR(c.w - 1, y, right.length, right, { fg: t.dim, bg: t.headBg });
67
+ c.rainZone(end, y, Math.max(0, c.w - right.length - 1 - end), 1, 1);
68
+ }
69
+ /** Toast (bottom-right, above the footer). */
70
+ export function toast(c, tst) {
71
+ if (!tst)
72
+ return;
73
+ const t = c.theme;
74
+ const { text, level } = tst;
75
+ const color = level === 'up' ? t.up : level === 'down' ? t.down : level === 'warn' ? t.warn : level === 'ai' ? t.ai : t.accent;
76
+ const w = Math.min(c.w - 4, text.length + 4);
77
+ const x = c.w - w - 1, y = c.h - 4;
78
+ const bg = mix(t.bg, color, 0.14);
79
+ c.fill(x, y, w, 3, ' ', { bg });
80
+ c.noRain(x, y, w, 3);
81
+ c.hline(x, y, w, { fg: color, bg, soft: true }, '▔');
82
+ c.hline(x, y + 2, w, { fg: color, bg, soft: true }, '▁');
83
+ c.text(x + 2, y + 1, text, { fg: t.text, bg, b: true }, w - 4);
84
+ c.put(x, y + 1, '▌', { fg: color, bg, soft: true });
85
+ }
@@ -0,0 +1,133 @@
1
+ // @dexterm/tui — color math and terminal color-depth detection.
2
+ // Colors are hex strings ('#rrggbb') everywhere in the theme and the canvas; the emitters
3
+ // convert them to 24-bit SGR, the nearest xterm-256 index, or drop them (NO_COLOR).
4
+ const hexCache = new Map();
5
+ export function hexToRgb(hex) {
6
+ let v = hexCache.get(hex);
7
+ if (v)
8
+ return v;
9
+ const h = hex.replace('#', '');
10
+ v = [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
11
+ hexCache.set(hex, v);
12
+ return v;
13
+ }
14
+ export function rgbToHex(r, g, b) {
15
+ return '#' + [r, g, b].map((n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0')).join('');
16
+ }
17
+ const mixCache = new Map();
18
+ /** Linear blend of two hex colors; t = 0 returns a, t = 1 returns b. Memoised on the exact t: the rain mixes thousands of cells per frame. */
19
+ export function mix(a, b, t) {
20
+ const key = a + b + t;
21
+ const hit = mixCache.get(key);
22
+ if (hit !== undefined)
23
+ return hit;
24
+ const [r1, g1, b1] = hexToRgb(a);
25
+ const [r2, g2, b2] = hexToRgb(b);
26
+ const r = Math.round(r1 + (r2 - r1) * t);
27
+ const g = Math.round(g1 + (g2 - g1) * t);
28
+ const bb = Math.round(b1 + (b2 - b1) * t);
29
+ const out = '#' + [r, g, bb].map((n) => n.toString(16).padStart(2, '0')).join('');
30
+ if (mixCache.size > 50_000)
31
+ mixCache.clear();
32
+ mixCache.set(key, out);
33
+ return out;
34
+ }
35
+ const lumCache = new Map();
36
+ /** WCAG 2.x relative luminance of a hex colour (0 black .. 1 white). */
37
+ export function luminance(hex) {
38
+ const hit = lumCache.get(hex);
39
+ if (hit !== undefined)
40
+ return hit;
41
+ const [r, g, b] = hexToRgb(hex).map((c) => { const s = c / 255; return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; });
42
+ const v = 0.2126 * r + 0.7152 * g + 0.0722 * b;
43
+ lumCache.set(hex, v);
44
+ return v;
45
+ }
46
+ /** WCAG contrast ratio between two colours (1 .. 21). The readability gate wants ≥ 4.5 for every numeric cell. */
47
+ export function contrast(fg, bg) {
48
+ const a = luminance(fg), b = luminance(bg);
49
+ return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
50
+ }
51
+ // xterm-256 palette: 16 system colors (not used as targets, their RGB varies per terminal),
52
+ // a 6×6×6 cube (16..231) and a 24-step gray ramp (232..255).
53
+ const CUBE = [0, 95, 135, 175, 215, 255];
54
+ function nearestCubeIndex(v) {
55
+ let best = 0;
56
+ let bestD = Infinity;
57
+ for (let i = 0; i < CUBE.length; i++) {
58
+ const d = Math.abs(CUBE[i] - v);
59
+ if (d < bestD) {
60
+ bestD = d;
61
+ best = i;
62
+ }
63
+ }
64
+ return best;
65
+ }
66
+ const ansi256Cache = new Map();
67
+ /** Nearest xterm-256 color index (16..255) for a hex color. */
68
+ export function hexToAnsi256(hex) {
69
+ const cached = ansi256Cache.get(hex);
70
+ if (cached !== undefined)
71
+ return cached;
72
+ const [r, g, b] = hexToRgb(hex);
73
+ const ri = nearestCubeIndex(r), gi = nearestCubeIndex(g), bi = nearestCubeIndex(b);
74
+ const cubeIdx = 16 + 36 * ri + 6 * gi + bi;
75
+ const cubeD = (CUBE[ri] - r) ** 2 + (CUBE[gi] - g) ** 2 + (CUBE[bi] - b) ** 2;
76
+ // gray ramp: 232 + i has value 8 + 10*i
77
+ const avg = Math.round((r + g + b) / 3);
78
+ const gi2 = Math.max(0, Math.min(23, Math.round((avg - 8) / 10)));
79
+ const gv = 8 + 10 * gi2;
80
+ const grayD = (gv - r) ** 2 + (gv - g) ** 2 + (gv - b) ** 2;
81
+ const idx = grayD < cubeD ? 232 + gi2 : cubeIdx;
82
+ ansi256Cache.set(hex, idx);
83
+ return idx;
84
+ }
85
+ /**
86
+ * Decide the color depth from the environment (SPEC §7.1):
87
+ * NO_COLOR wins; COLORTERM=truecolor/24bit or a known 24-bit terminal gives truecolor;
88
+ * a 256color TERM gives 256; anything else falls back to 256 (every modern terminal has it)
89
+ * unless TERM is dumb.
90
+ */
91
+ export function detectColorDepth(env = process.env, isTTY = true) {
92
+ if (env.NO_COLOR !== undefined && env.NO_COLOR !== '')
93
+ return 'none';
94
+ const force = env.FORCE_COLOR;
95
+ if (force !== undefined && force !== '') {
96
+ if (force === '0')
97
+ return 'none';
98
+ if (force === '3')
99
+ return 'truecolor';
100
+ if (force === '2' || force === '1')
101
+ return '256';
102
+ }
103
+ if (!isTTY)
104
+ return 'none';
105
+ const term = (env.TERM || '').toLowerCase();
106
+ if (term === 'dumb')
107
+ return 'none';
108
+ const ct = (env.COLORTERM || '').toLowerCase();
109
+ if (ct === 'truecolor' || ct === '24bit')
110
+ return 'truecolor';
111
+ const prog = (env.TERM_PROGRAM || '').toLowerCase();
112
+ if (['iterm.app', 'wezterm', 'ghostty', 'vscode', 'hyper', 'alacritty', 'kitty', 'rio', 'tabby'].includes(prog))
113
+ return 'truecolor';
114
+ if (env.WT_SESSION)
115
+ return 'truecolor';
116
+ if (term.includes('truecolor') || term.includes('24bit') || term.startsWith('xterm-kitty') || term.startsWith('wezterm') || term.startsWith('alacritty'))
117
+ return 'truecolor';
118
+ if (term.includes('256color') || prog === 'apple_terminal')
119
+ return '256';
120
+ return '256';
121
+ }
122
+ export function parseColorDepth(s) {
123
+ if (!s)
124
+ return undefined;
125
+ const v = s.toLowerCase();
126
+ if (v === 'truecolor' || v === '24bit' || v === '24-bit' || v === '16m')
127
+ return 'truecolor';
128
+ if (v === '256' || v === 'ansi256')
129
+ return '256';
130
+ if (v === 'none' || v === 'no' || v === 'off' || v === 'mono')
131
+ return 'none';
132
+ return undefined;
133
+ }
@@ -0,0 +1,62 @@
1
+ export class Flusher {
2
+ prev = null;
3
+ prevW = -1;
4
+ prevH = -1;
5
+ dirty = true;
6
+ depth;
7
+ hideCursor;
8
+ constructor(opts = {}) {
9
+ this.depth = opts.depth ?? 'truecolor';
10
+ this.hideCursor = opts.hideCursor ?? true;
11
+ }
12
+ /** Force the next flush to repaint every row. */
13
+ invalidate() { this.dirty = true; }
14
+ /** Compute the delta against the previous frame. Does not write anywhere. */
15
+ diff(c) {
16
+ const rows = new Array(c.h);
17
+ for (let y = 0; y < c.h; y++)
18
+ rows[y] = c.rowAnsi(y, this.depth);
19
+ const full = this.dirty || this.prev === null || this.prevW !== c.w || this.prevH !== c.h;
20
+ let out = '';
21
+ let n = 0;
22
+ const changed = new Array(c.h).fill(false);
23
+ if (full) {
24
+ out += '\x1b[H';
25
+ if (this.prev !== null && (this.prevW !== c.w || this.prevH !== c.h))
26
+ out += '\x1b[2J';
27
+ for (let y = 0; y < c.h; y++) {
28
+ out += `\x1b[${y + 1};1H` + rows[y];
29
+ changed[y] = true;
30
+ n++;
31
+ }
32
+ }
33
+ else {
34
+ const prev = this.prev;
35
+ for (let y = 0; y < c.h; y++) {
36
+ if (rows[y] === prev[y])
37
+ continue;
38
+ out += `\x1b[${y + 1};1H` + rows[y];
39
+ changed[y] = true;
40
+ n++;
41
+ }
42
+ }
43
+ if (out && this.hideCursor)
44
+ out = '\x1b[?25l' + out;
45
+ this.prev = rows;
46
+ this.prevW = c.w;
47
+ this.prevH = c.h;
48
+ this.dirty = false;
49
+ return { out, rows: n, full, changed };
50
+ }
51
+ /** Diff and write to `write` (e.g. process.stdout.write). Returns the result for callers that count rows. */
52
+ flush(c, write) {
53
+ const r = this.diff(c);
54
+ if (r.out)
55
+ write(r.out);
56
+ return r;
57
+ }
58
+ }
59
+ /** Enter the alternate screen, clear it, hide the cursor. */
60
+ export const ENTER_SCREEN = '\x1b[?1049h\x1b[H\x1b[2J\x1b[?25l';
61
+ /** Reset attributes, show the cursor, leave the alternate screen. */
62
+ export const LEAVE_SCREEN = '\x1b[0m\x1b[?25h\x1b[?1049l';