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,183 @@
1
+ import { skeletonRows } from "./rows.js";
2
+ import { applyFilter, parseFilter } from "./filter.js";
3
+ import { DEFAULT_SORT, SORT_KEYS, sortRows, sortSpec } from "./sort.js";
4
+ import { PRESETS, bakedFilter, cyclePreset, presetSpec, presetViews } from "./presets.js";
5
+ /**
6
+ * Classify the live feed's current condition from the rows it produced and the errors it reported.
7
+ * `ok` once any row is present; otherwise connecting (no error yet), offline (a network/DNS failure),
8
+ * or ratelimited (an HTTP 429). 429 wins over a network error because the host did answer.
9
+ */
10
+ export function feedCondition(sc) {
11
+ if (sc.rows.length > 0)
12
+ return { kind: 'ok', message: '' };
13
+ const joined = Object.values(sc.errors ?? {}).join(' ').toLowerCase();
14
+ if (/\b429\b|rate.?limit|too many requests/.test(joined)) {
15
+ return { kind: 'ratelimited', message: 'rate-limited by the data feed — backing off and retrying (press r to retry now)' };
16
+ }
17
+ if (/enotfound|econnrefused|eai_again|getaddrinfo|fetch failed|network|timeout|etimedout|socket|dns/.test(joined)) {
18
+ return { kind: 'offline', message: "offline — can't reach the data feed; retrying (press r to retry, or run `dexterm doctor`)" };
19
+ }
20
+ return { kind: 'connecting', message: 'connecting to the live feed…' };
21
+ }
22
+ export function makeScreenerState(source, rows = [], opts = {}) {
23
+ return {
24
+ source, rows, query: '', filter: null, sort: { ...DEFAULT_SORT }, preset: 'trending',
25
+ presets: opts.presets ?? presetViews(), gates: opts.gates ?? null, mode: 'table',
26
+ edit: { text: '', error: null }, sortCursor: 0, notice: null,
27
+ };
28
+ }
29
+ const SKELETON = skeletonRows();
30
+ /**
31
+ * True while a live/replay feed has produced nothing yet and nothing went wrong (task-1323): the table
32
+ * shows placeholder rows instead of an empty box. An error (offline / 429) keeps the explicit banner.
33
+ */
34
+ export function showsSkeleton(s) {
35
+ return s.source !== 'fixtures' && s.rows.length === 0 && feedCondition(s).kind === 'connecting';
36
+ }
37
+ /** Rows as the table shows them: preset's baked filter, then the user's filter, then the sort. */
38
+ export function screenerView(s) {
39
+ if (showsSkeleton(s))
40
+ return SKELETON;
41
+ const spec = presetSpec(s.preset) ?? PRESETS[0];
42
+ const base = s.source === 'fixtures' || spec.baked ? applyFilter(s.rows, bakedFilter(spec)) : s.rows;
43
+ return sortRows(applyFilter(base, s.filter), s.sort.key, s.sort.dir);
44
+ }
45
+ /** Set the applied filter from text. Returns the parse error (and leaves the old filter) on bad input. */
46
+ export function setFilter(s, text) {
47
+ const trimmed = text.trim();
48
+ if (!trimmed) {
49
+ s.query = '';
50
+ s.filter = null;
51
+ return null;
52
+ }
53
+ const r = parseFilter(trimmed);
54
+ if (!r.ok)
55
+ return { error: r.error, at: r.at };
56
+ s.query = trimmed;
57
+ s.filter = r.filter;
58
+ return null;
59
+ }
60
+ export function setSort(s, key, dir) {
61
+ const spec = sortSpec(key);
62
+ if (!spec)
63
+ return;
64
+ s.sort = { key: spec.key, dir: dir ?? spec.defaultDir };
65
+ }
66
+ /** Select a preset; disabled ones (needs key / pending card) are refused. Returns true when it changed. */
67
+ export function setPreset(s, id) {
68
+ const v = s.presets.find((p) => p.id === id);
69
+ if (!v || !v.available || s.preset === id)
70
+ return false;
71
+ s.preset = id;
72
+ return true;
73
+ }
74
+ export function stepPreset(s, step) {
75
+ return setPreset(s, cyclePreset(s.presets, s.preset, step));
76
+ }
77
+ export function clampSelection(s, selected) {
78
+ const n = screenerView(s).length;
79
+ return n === 0 ? 0 : Math.max(0, Math.min(n - 1, selected));
80
+ }
81
+ // ---- modes --------------------------------------------------------------------------------
82
+ export function openFilterEditor(s) {
83
+ s.mode = 'filter';
84
+ s.edit = { text: s.query, error: null };
85
+ s.notice = null;
86
+ }
87
+ export function openSortMenu(s) {
88
+ s.mode = 'sort';
89
+ s.sortCursor = Math.max(0, SORT_KEYS.findIndex((k) => k.key === s.sort.key));
90
+ s.notice = null;
91
+ }
92
+ /** Keys while the filter editor is open. Enter applies (only when the text parses), esc cancels. */
93
+ export function filterEditorKey(s, k) {
94
+ const e = s.edit;
95
+ const revalidate = () => { const t = e.text.trim(); if (!t) {
96
+ e.error = null;
97
+ return;
98
+ } const r = parseFilter(t); e.error = r.ok ? null : { error: r.error, at: r.at }; };
99
+ if (k.name === 'esc') {
100
+ s.mode = 'table';
101
+ return 'closed';
102
+ }
103
+ if (k.name === 'enter') {
104
+ const err = setFilter(s, e.text);
105
+ if (err) {
106
+ e.error = err;
107
+ return 'handled';
108
+ }
109
+ s.mode = 'table';
110
+ return 'applied';
111
+ }
112
+ if (k.name === 'backspace') {
113
+ e.text = e.text.slice(0, -1);
114
+ revalidate();
115
+ return 'handled';
116
+ }
117
+ if (k.ctrl && k.ch === 'u') {
118
+ e.text = '';
119
+ e.error = null;
120
+ return 'handled';
121
+ }
122
+ if (k.ctrl && k.ch === 'w') {
123
+ e.text = e.text.replace(/\s*\S+\s*$/, '');
124
+ revalidate();
125
+ return 'handled';
126
+ }
127
+ if ((k.name === 'char' || k.name === 'space') && k.ch && !k.ctrl && !k.alt) {
128
+ if (e.text.length >= 200)
129
+ return 'handled';
130
+ e.text += k.ch;
131
+ revalidate();
132
+ return 'handled';
133
+ }
134
+ return 'ignored';
135
+ }
136
+ /** Keys while the sort menu is open: j/k move, enter picks (same key toggles direction), esc/s close. */
137
+ export function sortMenuKey(s, k) {
138
+ const n = SORT_KEYS.length;
139
+ if (k.name === 'esc' || (k.name === 'char' && (k.ch === 's' || k.ch === 'q'))) {
140
+ s.mode = 'table';
141
+ return 'closed';
142
+ }
143
+ if (k.name === 'down' || (k.name === 'char' && k.ch === 'j')) {
144
+ s.sortCursor = (s.sortCursor + 1) % n;
145
+ return 'handled';
146
+ }
147
+ if (k.name === 'up' || (k.name === 'char' && k.ch === 'k')) {
148
+ s.sortCursor = (s.sortCursor + n - 1) % n;
149
+ return 'handled';
150
+ }
151
+ if (k.name === 'home' || (k.name === 'char' && k.ch === 'g')) {
152
+ s.sortCursor = 0;
153
+ return 'handled';
154
+ }
155
+ if (k.name === 'end' || (k.name === 'char' && k.ch === 'G')) {
156
+ s.sortCursor = n - 1;
157
+ return 'handled';
158
+ }
159
+ if (k.name === 'enter' || k.name === 'space') {
160
+ const pick = SORT_KEYS[s.sortCursor];
161
+ if (pick.key === s.sort.key)
162
+ s.sort = { key: pick.key, dir: s.sort.dir === 'desc' ? 'asc' : 'desc' };
163
+ else
164
+ s.sort = { key: pick.key, dir: pick.defaultDir };
165
+ s.mode = 'table';
166
+ return 'applied';
167
+ }
168
+ return 'ignored';
169
+ }
170
+ /** Caption fragment for columns the registry says cannot be trusted right now, e.g. 'holders, org: degraded'. */
171
+ export function gateCaption(gates, columns) {
172
+ if (!gates)
173
+ return '';
174
+ const by = new Map();
175
+ for (const id of columns) {
176
+ const g = gates[id];
177
+ if (!g || g.available)
178
+ continue;
179
+ const label = g.state === 'needs-key' ? 'needs key' : g.state === 'disabled' ? 'off' : 'degraded';
180
+ (by.get(label) ?? by.set(label, []).get(label)).push(id);
181
+ }
182
+ return [...by.entries()].map(([label, ids]) => `${ids.join(', ')}: ${label}`).join(' · ');
183
+ }
@@ -0,0 +1,343 @@
1
+ // dexterm — shared renderer for a live Claude analyst session (task-1302). Used by the full Analyst
2
+ // screen and the token ANALYST panel. Cyberspace styling: the verdict and the tool-call lines are
3
+ // coral, the answer decrypts in through code glyphs, and a cost/latency footer sits on the caption.
4
+ import { Canvas, badge, spinnerGlyph, decryptText, wrap, mix, fmtUsd, fmtPrice, fmtPctDir, } from '../../../tui/src/index.js';
5
+ const verdictColor = (c, v) => {
6
+ const t = c.theme;
7
+ if (v === 'WATCH')
8
+ return t.up;
9
+ if (v === 'AVOID')
10
+ return t.down;
11
+ if (v === 'SPECULATIVE' || v === 'AMBIGUOUS')
12
+ return t.warn;
13
+ return t.muted;
14
+ };
15
+ /** Draw the session inside a box at (x,y,w,h). Pure: reads only the session + opts. */
16
+ export function renderAnalystSession(c, x, y, w, h, s, opts) {
17
+ const t = c.theme;
18
+ const bc = mix(t.border, t.ai, 0.55);
19
+ // Narrow panels (token screen) drop the model so cost · latency · tools still fits beside the title.
20
+ const full = s.status === 'error' ? 'analyst unavailable' : s.footer;
21
+ const caption = (opts.title ?? '✦ ANALYST · claude').length + full.length + 10 <= w ? full : full.split(' · ').slice(0, 3).join(' · ');
22
+ const capFg = s.status === 'error' ? t.warn : t.dim;
23
+ const box = c.box(x, y, w, h, { title: opts.title ?? '✦ ANALYST · claude', titleFg: t.ai, caption, captionFg: capFg, border: bc });
24
+ const bg = box.bg;
25
+ const inner = box.w - 2;
26
+ const frame = opts.frame;
27
+ const inputY = box.y + box.h - 2;
28
+ let cy = box.y;
29
+ // context chips
30
+ if (opts.chips?.length) {
31
+ let cx = box.x + 1;
32
+ for (const chip of opts.chips) {
33
+ if (cx + chip.length + 3 > box.x + box.w - 1)
34
+ break;
35
+ cx = badge(c, cx, cy, chip, t.ai, { on: bg }) + 1;
36
+ }
37
+ cy += 2;
38
+ }
39
+ const line = (str, st, indent = 1) => {
40
+ for (const w2 of wrap(str, inner - indent)) {
41
+ if (cy >= inputY - 1)
42
+ return;
43
+ c.text(box.x + indent, cy, w2, st, inner - indent);
44
+ cy += 1;
45
+ }
46
+ };
47
+ const drawTail = () => drawInput(c, box, bc, inputY, opts, s);
48
+ // earlier exchanges on this warm session (compact marker; the current turn is drawn in full below)
49
+ if (s.history.length && cy < inputY - 1) {
50
+ c.text(box.x + 1, cy, `⟲ ${s.history.length} earlier ${s.history.length === 1 ? 'exchange' : 'exchanges'} · one warm claude session`, { fg: t.dim, bg, i: true }, inner - 1);
51
+ cy += 1;
52
+ const last = s.history[s.history.length - 1];
53
+ const prev = last.error ? `last: ${last.error}` : last.verdict ? `last: ${last.verdict}` : last.summary[0] ? `last: ${last.summary[0]}` : '';
54
+ if (prev && cy < inputY - 1) {
55
+ c.text(box.x + 3, cy, prev, { fg: t.dim, bg, i: true }, inner - 3);
56
+ cy += 1;
57
+ }
58
+ cy += 1;
59
+ }
60
+ // ---- the question
61
+ if (cy < inputY - 1) {
62
+ c.text(box.x + 1, cy, '›', { fg: t.accent, bg, b: true });
63
+ line(s.question, { fg: t.text, bg, b: true }, 3);
64
+ cy += 1;
65
+ }
66
+ // ---- interrupted (Esc during a run): soft note, input stays usable for the next question
67
+ if (s.status === 'interrupted') {
68
+ c.text(box.x + 1, cy, '○', { fg: t.warn, bg, b: true });
69
+ line(s.error?.message ?? 'interrupted', { fg: t.warn, bg, b: true }, 3);
70
+ if (s.error?.detail)
71
+ line(s.error.detail, { fg: t.dim, bg, i: true }, 3);
72
+ drawTail();
73
+ return;
74
+ }
75
+ // ---- error (clean one-liner, app keeps working)
76
+ if (s.status === 'error' && s.error) {
77
+ c.text(box.x + 1, cy, '○', { fg: t.warn, bg, b: true });
78
+ line(s.error.message, { fg: t.warn, bg, b: true }, 3);
79
+ if (s.error.detail)
80
+ line(s.error.detail, { fg: t.dim, bg, i: true }, 3);
81
+ cy += 1;
82
+ line('The rest of dexterm keeps working; this panel is read-only and never trades.', { fg: t.dim, bg, i: true }, 1);
83
+ drawTail();
84
+ return;
85
+ }
86
+ // ---- tool-call lines (coral), newest reasoning visible
87
+ for (const tl of s.toolLines) {
88
+ if (cy >= inputY - 1)
89
+ break;
90
+ c.text(box.x + 3, cy, '↳ ' + tl, { fg: t.ai, bg, i: true }, inner - 3);
91
+ cy += 1;
92
+ }
93
+ // ---- spinner while thinking / running
94
+ if (s.status === 'thinking' || s.status === 'running') {
95
+ if (cy < inputY - 1) {
96
+ c.text(box.x + 2, cy, spinnerGlyph(frame), { fg: t.ai, bg, b: true });
97
+ const status = s.status === 'thinking'
98
+ ? ['Thinking', 'Loading skill', 'Connecting tools'][Math.floor(frame / 24) % 3]
99
+ : ['Querying tools', 'Reading chain', 'Weighing risk'][Math.floor(frame / 24) % 3];
100
+ c.text(box.x + 4, cy, `${status}…`, { fg: t.ai, bg, b: true });
101
+ const secs = ((Date.now() - s.startedAt) / 1000).toFixed(1);
102
+ c.text(box.x + 5 + status.length, cy, `(esc to interrupt · ${secs}s)`, { fg: t.dim, bg }, Math.max(0, inner - status.length - 6));
103
+ cy += 1;
104
+ }
105
+ // stream any natural-language text the model emitted before the structured answer
106
+ if (s.text && cy < inputY - 1) {
107
+ cy += 1;
108
+ line(s.text.slice(-400), { fg: t.muted, bg, i: true }, 2);
109
+ }
110
+ drawTail();
111
+ return;
112
+ }
113
+ // ---- done: verdict (pinned) + scrollable summary (decrypts in), invalidation, flags, proposals
114
+ cy += 1;
115
+ // task-1311 (QA M12): the decrypt reveals fast (~600 chars/s) and any key reveals it in full, so a
116
+ // finished answer is never hidden; `revealAll` also skips it on demand / in calm mode.
117
+ const revealAll = opts.revealAll || opts.calm;
118
+ if (s.verdict && cy < inputY - 1) {
119
+ const vc = verdictColor(c, s.verdict);
120
+ c.fill(box.x + 1, cy, inner, 1, ' ', { bg: mix(bg, vc, 0.18) });
121
+ c.text(box.x + 2, cy, `● ${s.verdict}`, { fg: vc, bg: mix(bg, vc, 0.18), b: true });
122
+ const right = s.rugProbability !== null ? `rug ${(s.rugProbability * 100).toFixed(0)}% ` : s.confidence !== null ? `conf ${s.confidence.toFixed(2)} ` : '';
123
+ if (right)
124
+ c.textR(box.x + 1, cy, inner - 1, right, { fg: t.muted, bg: mix(bg, vc, 0.18) });
125
+ cy += 1;
126
+ }
127
+ // task-1313 trading view: the directional bias + setup quality, drawn under the risk verdict.
128
+ if (s.bias && cy < inputY - 1) {
129
+ const bc2 = s.bias === 'long' ? t.up : s.bias === 'short' || s.bias === 'avoid' ? t.down : t.warn;
130
+ c.text(box.x + 2, cy, `▸ bias ${s.bias.toUpperCase()}`, { fg: bc2, bg, b: true });
131
+ if (s.setupQuality)
132
+ c.text(box.x + 2 + 7 + s.bias.length + 1, cy, `· setup ${s.setupQuality}`, { fg: t.muted, bg }, inner - 10 - s.bias.length);
133
+ cy += 1;
134
+ }
135
+ if (s.injection && cy < inputY - 1) {
136
+ c.text(box.x + 2, cy, '⚠ prompt injection in token text — ignored', { fg: t.warn, bg, b: true }, inner - 2);
137
+ cy += 1;
138
+ }
139
+ // task-1313 part E: the structured filter the analyst applied for a plain-language request, so the
140
+ // user sees the numbers it chose and can correct them ("tighter", "only locked LP", …).
141
+ if (s.filterApplied && cy < inputY - 1) {
142
+ c.text(box.x + 2, cy, `applied filter: ${s.filterApplied.query}`, { fg: t.accent, bg, b: true }, inner - 2);
143
+ cy += 1;
144
+ if (s.filterApplied.notes) {
145
+ line(s.filterApplied.notes, { fg: t.dim, bg, i: true }, 4);
146
+ }
147
+ }
148
+ // task-1311 (QA M12): the answer body scrolls (j/k, PgUp/Dn) instead of truncating at 16 lines.
149
+ const sumLines = s.summary.flatMap((ln) => wrap(ln, inner - 2));
150
+ let capacity = Math.max(1, inputY - 1 - cy);
151
+ const overflow = sumLines.length > capacity;
152
+ if (overflow)
153
+ capacity -= 1; // reserve a row for the scroll marker
154
+ const maxScroll = Math.max(0, sumLines.length - capacity);
155
+ const scroll = Math.max(0, Math.min(opts.scroll ?? 0, maxScroll));
156
+ let revealLeft = revealAll ? 1e9 : s.doneAt ? Math.floor(((Date.now() - s.doneAt) / 1000) * 600) : 1e9;
157
+ for (let i = 0; i < scroll; i++)
158
+ revealLeft -= (sumLines[i]?.length ?? 0) + 1; // spend the budget on hidden lines first
159
+ for (const w2 of sumLines.slice(scroll, scroll + capacity)) {
160
+ if (cy >= inputY - 1)
161
+ break;
162
+ const n = Math.min(revealLeft, w2.length);
163
+ if (revealAll || n >= w2.length)
164
+ c.text(box.x + 2, cy, w2, { fg: t.text, bg }, inner - 2);
165
+ else
166
+ decryptText(c, box.x + 2, cy, w2, Math.max(0, n), frame, { fg: t.text, bg }, inner - 2, opts.code);
167
+ revealLeft -= w2.length + 1;
168
+ cy += 1;
169
+ }
170
+ if (overflow && cy < inputY - 1) {
171
+ const above = scroll, below = Math.max(0, sumLines.length - (scroll + capacity));
172
+ const marker = `${above ? `↑${above} ` : ''}${below ? `↓${below} more ` : ''}· j/k · PgUp/Dn`;
173
+ c.text(box.x + 2, cy, marker, { fg: t.dim, bg, i: true }, inner - 2);
174
+ cy += 1;
175
+ }
176
+ // task-1313 part F: a list-of-coins answer, rendered as a REAL table (symbol/price/change/liq/vol/
177
+ // mcap/age + the analyst's one-line reason), navigable with ↑↓ and ⏎-to-jump. The numbers come from
178
+ // live data (resolved by the caller from the app's data layer, never from the model's text).
179
+ if (opts.results?.length && cy < inputY - 1) {
180
+ cy = renderResultsTable(c, box, bg, cy, inputY, inner, opts.results, opts.resultsCursor ?? 0, opts.resultsActive ?? false);
181
+ }
182
+ if (s.candidates.length && cy < inputY - 1) {
183
+ cy += 1;
184
+ line('ambiguous — pick a mint:', { fg: t.warn, bg, b: true }, 2);
185
+ for (const cand of s.candidates.slice(0, 3)) {
186
+ if (cy >= inputY - 1)
187
+ break;
188
+ c.text(box.x + 2, cy, `· ${String(cand.mint).slice(0, 10)}… ${cand.launchpad ?? ''}`, { fg: t.muted, bg }, inner - 2);
189
+ cy += 1;
190
+ }
191
+ }
192
+ if (s.keyLevels.length && cy < inputY - 1) {
193
+ cy += 1;
194
+ c.text(box.x + 2, cy, 'key levels:', { fg: t.dim, bg, i: true });
195
+ cy += 1;
196
+ for (const lv of s.keyLevels.slice(0, 6)) {
197
+ if (cy >= inputY - 1)
198
+ break;
199
+ const val = lv.value !== undefined && lv.value !== null ? ` ${lv.value}` : '';
200
+ const note = lv.note ? ` — ${lv.note}` : '';
201
+ c.text(box.x + 2, cy, `· ${lv.label}${val}${note}`, { fg: t.muted, bg }, inner - 2);
202
+ cy += 1;
203
+ }
204
+ }
205
+ if (s.invalidation && cy < inputY - 1) {
206
+ cy += 1;
207
+ c.text(box.x + 2, cy, 'invalidates if:', { fg: t.dim, bg, i: true });
208
+ cy += 1;
209
+ line(s.invalidation, { fg: t.muted, bg }, 2);
210
+ }
211
+ if (s.sizeHint && cy < inputY - 1) {
212
+ cy += 1;
213
+ c.text(box.x + 2, cy, 'size:', { fg: t.dim, bg, i: true });
214
+ cy += 1;
215
+ line(s.sizeHint, { fg: t.muted, bg }, 2);
216
+ }
217
+ if (s.proposals.length && cy < inputY - 1) {
218
+ cy += 1;
219
+ for (const p of s.proposals) {
220
+ if (cy >= inputY - 1)
221
+ break;
222
+ c.text(box.x + 2, cy, `+ ${p.type}: ${p.text}`, { fg: t.accent, bg }, inner - 2);
223
+ cy += 1;
224
+ }
225
+ if (cy < inputY - 1) {
226
+ c.text(box.x + 2, cy, 'proposals only — confirm with a keypress', { fg: t.dim, bg, i: true }, inner - 2);
227
+ cy += 1;
228
+ }
229
+ }
230
+ drawTail();
231
+ }
232
+ /** Compact age label from minutes: 45m, 3h, 2d, — when unknown (task-1313 part F table). */
233
+ function fmtAge(min) {
234
+ if (min === null || !Number.isFinite(min))
235
+ return '—';
236
+ if (min < 60)
237
+ return `${Math.round(min)}m`;
238
+ if (min < 1440)
239
+ return `${Math.round(min / 60)}h`;
240
+ return `${Math.round(min / 1440)}d`;
241
+ }
242
+ /**
243
+ * Draw a list-of-coins answer as a real table (task-1313 part F). Columns adapt to width: at ≥96 inner
244
+ * cols the full set (symbol/price/24h/liq/vol/mcap/age + reason); narrower drops columns right-to-left
245
+ * so it stays legible instead of wrapping into prose. The highlighted row (cursor) is tinted; when the
246
+ * table is active a `▸` marks it and the footer hint changes. Returns the new y.
247
+ */
248
+ function renderResultsTable(c, box, bg, startY, inputY, inner, rows, cursor, active) {
249
+ const t = c.theme;
250
+ let cy = startY + 1;
251
+ if (cy >= inputY - 1)
252
+ return cy;
253
+ // Column plan: [symbol, price, 24h, liq, vol, mcap, age] then the reason fills the rest. Drop from
254
+ // the right as width shrinks so a narrow panel still shows symbol + price + change + a reason.
255
+ const full = [['SYMBOL', 10], ['PRICE', 9], ['24H', 8], ['LIQ', 8], ['VOL', 8], ['MCAP', 8], ['AGE', 5]];
256
+ let cols = full.slice();
257
+ // keep trimming trailing columns until the header + a minimal reason (12) fits
258
+ while (cols.length > 3 && cols.reduce((a, [, w]) => a + w + 1, 0) + 12 > inner - 2)
259
+ cols.pop();
260
+ const reasonX = cols.reduce((a, [, w]) => a + w + 1, 0) + box.x + 3;
261
+ const reasonW = Math.max(0, box.x + box.w - 1 - reasonX);
262
+ // header
263
+ c.text(box.x + 2, cy, `◆ ${rows.length} coin${rows.length === 1 ? '' : 's'}${active ? ' ↑↓ pick · ⏎ open · o ticket · esc back' : ' (↑↓ to browse)'}`, { fg: t.ai, bg, b: true }, inner - 2);
264
+ cy += 1;
265
+ if (cy >= inputY - 1)
266
+ return cy;
267
+ let hx = box.x + 3;
268
+ for (const [lab, w] of cols) {
269
+ c.textR(hx, cy, w, lab, { fg: t.dim, bg });
270
+ hx += w + 1;
271
+ }
272
+ if (reasonW >= 6)
273
+ c.text(reasonX, cy, 'WHY', { fg: t.dim, bg });
274
+ cy += 1;
275
+ const cap = Math.max(1, inputY - 1 - cy);
276
+ const startIdx = Math.max(0, Math.min(cursor - Math.floor(cap / 2), Math.max(0, rows.length - cap)));
277
+ for (let i = startIdx; i < rows.length && cy < inputY - 1; i++) {
278
+ const r = rows[i];
279
+ const on = active && i === cursor;
280
+ const rowBg = on ? mix(bg, t.accent, 0.3) : i % 2 ? mix(bg, t.panelAlt, 1) : bg;
281
+ c.fill(box.x + 1, cy, inner, 1, ' ', { bg: rowBg });
282
+ let rx = box.x + 1;
283
+ c.text(rx, cy, on ? '▸' : ' ', { fg: t.accent, bg: rowBg, b: true });
284
+ rx += 2;
285
+ for (const [lab, w] of cols) {
286
+ if (lab === 'SYMBOL')
287
+ c.text(rx, cy, r.symbol || r.mint.slice(0, 4), { fg: t.text, bg: rowBg, b: on }, w);
288
+ else if (lab === 'PRICE')
289
+ c.textR(rx, cy, w, r.price !== null ? fmtPrice(r.price) : '—', { fg: t.text, bg: rowBg });
290
+ else if (lab === '24H')
291
+ c.textR(rx, cy, w, r.chg24 !== null ? fmtPctDir(r.chg24) : '—', { fg: (r.chg24 ?? 0) >= 0 ? t.up : t.down, bg: rowBg });
292
+ else if (lab === 'LIQ')
293
+ c.textR(rx, cy, w, r.liq !== null ? fmtUsd(r.liq, { digits: 0 }) : '—', { fg: t.text, bg: rowBg });
294
+ else if (lab === 'VOL')
295
+ c.textR(rx, cy, w, r.vol24 !== null ? fmtUsd(r.vol24, { digits: 0 }) : '—', { fg: t.text, bg: rowBg });
296
+ else if (lab === 'MCAP')
297
+ c.textR(rx, cy, w, r.mcap !== null ? fmtUsd(r.mcap, { digits: 0 }) : '—', { fg: t.text, bg: rowBg });
298
+ else if (lab === 'AGE')
299
+ c.textR(rx, cy, w, fmtAge(r.ageMin), { fg: t.dim, bg: rowBg });
300
+ rx += w + 1;
301
+ }
302
+ if (reasonW >= 6 && r.reason)
303
+ c.text(reasonX, cy, r.reason, { fg: on ? t.text : t.muted, bg: rowBg }, reasonW);
304
+ cy += 1;
305
+ }
306
+ if (rows.length > startIdx + cap && cy < inputY - 1) {
307
+ c.text(box.x + 2, cy, `↓ ${rows.length - (startIdx + cap)} more`, { fg: t.dim, bg, i: true }, inner - 2);
308
+ cy += 1;
309
+ }
310
+ return cy;
311
+ }
312
+ /**
313
+ * The chat input line (task-1303): the live buffer with a cursor when focused, a "queued" banner
314
+ * when a follow-up is waiting on an in-flight run, else the placeholder hint. Always usable.
315
+ */
316
+ function drawInput(c, box, bc, inputY, opts, s) {
317
+ const t = c.theme;
318
+ const bg = box.bg;
319
+ c.hline(box.x, inputY - 1, box.w, { fg: bc, bg, soft: true });
320
+ c.put(box.x, inputY - 1, '├', { fg: bc, bg, soft: true });
321
+ c.put(box.x + box.w - 1, inputY - 1, '┤', { fg: bc, bg, soft: true });
322
+ const promptFg = s.busy ? t.dim : t.ai;
323
+ c.text(box.x + 1, inputY, '▌', { fg: promptFg, bg, b: true, soft: true });
324
+ const avail = box.w - 4;
325
+ const typed = opts.input ?? '';
326
+ if (typed) {
327
+ // Show the tail of what is typed so the caret stays visible, with a block cursor when focused.
328
+ const caret = opts.inputFocused ? '█' : '';
329
+ const shown = typed.length + caret.length > avail ? typed.slice(typed.length - (avail - caret.length)) : typed;
330
+ const x = c.text(box.x + 2, inputY, shown, { fg: t.text, bg }, avail);
331
+ if (caret)
332
+ c.text(x, inputY, caret, { fg: t.accent, bg, b: true });
333
+ }
334
+ else {
335
+ const hint = s.queued
336
+ ? `queued: ${s.queued} · will ask when the run finishes`
337
+ : s.busy
338
+ ? 'streaming… type a follow-up (it queues) · esc interrupts'
339
+ : (opts.inputHint ?? 'ask a follow-up… ⏎ send · read-only · never trades');
340
+ const fg = s.queued ? t.warn : t.dim;
341
+ c.text(box.x + 2, inputY, (opts.inputFocused ? '█ ' : '') + hint, { fg, bg, i: true }, avail);
342
+ }
343
+ }