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,210 @@
1
+ // dexterm popup host — the window layer behind the PopupHost interface (task-1290).
2
+ //
3
+ // Two implementations, chosen at runtime:
4
+ // * ShellPopupHost — spawns the frameless wry shell (shell/wry) and speaks the stdio control protocol.
5
+ // Opens chromeless, transparent, always-on-top HUD windows. native = true.
6
+ // * BrowserPopupHost — graceful fallback when the shell binary or a display is unavailable: opens the
7
+ // same loopback URL in the OS default browser (`open` / `xdg-open`). native = false.
8
+ //
9
+ // The popup URL (which carries a single-use ticket) is handed to the shell over STDIN, never argv, so `ps`
10
+ // from another local user cannot read it. The browser fallback necessarily passes it as an argument — that
11
+ // tradeoff is documented in the README threat notes; the ticket is single-use and loopback-only.
12
+ import { spawn } from 'node:child_process';
13
+ import { EventEmitter } from 'node:events';
14
+ import { existsSync } from 'node:fs';
15
+ import { fileURLToPath } from 'node:url';
16
+ import path from 'node:path';
17
+ import { DEFAULT_HOTKEY, DEFAULT_HOTKEY_CLOSE } from "./config.js";
18
+ import { isLoopbackUrl } from "./loopback.js";
19
+ const here = path.dirname(fileURLToPath(import.meta.url));
20
+ /** The built wry shell binary. `DEXTERM_POPUP_SHELL` overrides (e.g. a packaged location). */
21
+ export function shellBinaryPath(env = process.env) {
22
+ if (env.DEXTERM_POPUP_SHELL && env.DEXTERM_POPUP_SHELL.trim())
23
+ return env.DEXTERM_POPUP_SHELL;
24
+ return path.join(here, '..', 'shell', 'wry', 'target', 'release', 'dexterm-shell-wry');
25
+ }
26
+ /** Whether a GUI display is reachable (a frameless window can be created). Browser fallback otherwise. */
27
+ export function displayAvailable(env = process.env, platform = process.platform) {
28
+ if (platform === 'darwin')
29
+ return true; // macOS GUI session (a locked screen still has a display)
30
+ if (platform === 'win32')
31
+ return true;
32
+ return !!(env.DISPLAY || env.WAYLAND_DISPLAY); // X11 / Wayland on Linux; empty in a headless/CI box
33
+ }
34
+ /** Whether `createPopupHost` would pick the native shell (binary present + display), without starting anything. */
35
+ export function nativeShellAvailable(env = process.env, platform = process.platform) {
36
+ return existsSync(shellBinaryPath(env)) && displayAvailable(env, platform);
37
+ }
38
+ /**
39
+ * Decide and construct the host without starting it. The shell is chosen only when its binary exists AND a
40
+ * display is available AND the browser was not forced; otherwise the browser fallback is returned with a
41
+ * reason the caller can show the user ("wry shell not built", "no display", ...).
42
+ */
43
+ export function createPopupHost(opts = {}) {
44
+ const env = opts.env ?? process.env;
45
+ const platform = opts.platform ?? process.platform;
46
+ if (opts.forceBrowser)
47
+ return { host: new BrowserPopupHost(platform, opts.port), native: false, reason: 'browser requested' };
48
+ const bin = shellBinaryPath(env);
49
+ if (!existsSync(bin))
50
+ return { host: new BrowserPopupHost(platform, opts.port), native: false, reason: `wry shell not built (${bin}); run \`npm run build:shell\` in @dexterm/popup` };
51
+ if (!displayAvailable(env, platform))
52
+ return { host: new BrowserPopupHost(platform, opts.port), native: false, reason: 'no display available' };
53
+ return { host: new ShellPopupHost({ bin, hotkey: opts.hotkey ?? DEFAULT_HOTKEY, hotkeyClose: opts.hotkeyClose ?? DEFAULT_HOTKEY_CLOSE, env, ...(opts.proxyPort ? { proxyPort: opts.proxyPort } : {}) }), native: true, reason: 'wry shell' };
54
+ }
55
+ export class ShellPopupHost extends EventEmitter {
56
+ native = true;
57
+ proc = null;
58
+ spawnedAt = 0;
59
+ bin;
60
+ hotkey;
61
+ env;
62
+ hotkeyClose;
63
+ proxyPort;
64
+ /** Set from shell.ready (task-1325). */
65
+ hotkeyCloseRegistered = false;
66
+ /** task-1358 r4: the sink proxy the shell confirmed in shell.ready (`127.0.0.1:<port>`), or null. */
67
+ sinkProxy = null;
68
+ constructor(deps) { super(); this.bin = deps.bin; this.hotkey = deps.hotkey; this.env = deps.env; this.hotkeyClose = deps.hotkeyClose ?? DEFAULT_HOTKEY_CLOSE; this.proxyPort = deps.proxyPort; }
69
+ async start() {
70
+ if (this.proc)
71
+ return { hotkeyRegistered: false, spawnToReadyMs: 0, sinkProxy: this.sinkProxy };
72
+ this.spawnedAt = performance.now();
73
+ // The hotkey chord (and, r4, the sink proxy address) reach the shell via the environment, never a
74
+ // shell-interpolated command line.
75
+ const env = { ...this.env, DEXTERM_POPUP_HOTKEY: this.hotkey, DEXTERM_POPUP_HOTKEY_CLOSE: this.hotkeyClose, ...(this.proxyPort ? { DEXTERM_POPUP_PROXY: `127.0.0.1:${this.proxyPort}` } : {}) };
76
+ this.proc = spawn(this.bin, [], { stdio: ['pipe', 'pipe', 'pipe'], env });
77
+ let buf = '';
78
+ this.proc.stdout.on('data', (d) => {
79
+ buf += d.toString();
80
+ let i;
81
+ while ((i = buf.indexOf('\n')) >= 0) {
82
+ const line = buf.slice(0, i);
83
+ buf = buf.slice(i + 1);
84
+ if (!line.trim())
85
+ continue;
86
+ let ev;
87
+ try {
88
+ ev = JSON.parse(line);
89
+ }
90
+ catch {
91
+ this.emit('stderr', line);
92
+ continue;
93
+ }
94
+ this.emit('shell', ev);
95
+ if (ev.ev)
96
+ this.emit(ev.ev, ev);
97
+ }
98
+ });
99
+ this.proc.stderr.on('data', (d) => this.emit('stderr', d.toString().trimEnd()));
100
+ this.proc.on('exit', (code, sig) => { this.proc = null; this.emit('exit', { code, sig }); });
101
+ this.proc.on('error', (err) => this.emit('error', err));
102
+ const ready = await this.waitFor('shell.ready', 20_000);
103
+ this.hotkeyCloseRegistered = !!ready.hotkey_close_registered;
104
+ this.sinkProxy = typeof ready.sink_proxy === 'string' ? ready.sink_proxy : null;
105
+ return { hotkeyRegistered: !!ready.hotkey_registered, spawnToReadyMs: +(performance.now() - this.spawnedAt).toFixed(1), sinkProxy: this.sinkProxy };
106
+ }
107
+ sendOp(op) { if (!this.proc)
108
+ throw new Error('shell not running'); this.proc.stdin.write(JSON.stringify(op) + '\n'); }
109
+ open(id, url, geom = {}) {
110
+ // The URL reaching here is already pinned to our server origin (assertLoopbackUrl / a server ticket).
111
+ // The shell pins every later navigation of this window to the same origin; `port` tells it which.
112
+ let port;
113
+ try {
114
+ const u = new URL(url);
115
+ port = Number(u.port || '80');
116
+ }
117
+ catch {
118
+ port = undefined;
119
+ }
120
+ this.sendOp({ op: 'open', id, url, port, x: geom.x ?? 80, y: geom.y ?? 80, w: geom.w ?? 960, h: geom.h ?? 640, ontop: geom.ontop ?? true, focus: geom.focus ?? false });
121
+ }
122
+ close(id) { this.sendOp({ op: 'close', id }); }
123
+ show(id) { this.sendOp({ op: 'show', id }); }
124
+ hide(id) { this.sendOp({ op: 'hide', id }); }
125
+ focus(id) { this.sendOp({ op: 'focus', id }); }
126
+ alwaysOnTop(id, on) { this.sendOp({ op: 'ontop', id, on }); }
127
+ clickThrough(id, on) { this.sendOp({ op: 'clickthrough', id, on }); }
128
+ move(id, x, y) { this.sendOp({ op: 'move', id, x, y }); }
129
+ setBounds(id, r) { this.sendOp({ op: 'bounds', id, x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.w), h: Math.round(r.h) }); }
130
+ snapshot(id, file) { this.sendOp({ op: 'snapshot', id, path: file }); }
131
+ quit() {
132
+ if (!this.proc)
133
+ return;
134
+ try {
135
+ this.sendOp({ op: 'quit' });
136
+ }
137
+ catch { /* already gone */ }
138
+ const p = this.proc;
139
+ setTimeout(() => { try {
140
+ p.kill('SIGKILL');
141
+ }
142
+ catch { /* ignore */ } }, 1500).unref();
143
+ }
144
+ get pid() { return this.proc?.pid; }
145
+ waitFor(ev, timeoutMs = 10_000, pred = () => true) {
146
+ return new Promise((resolve, reject) => {
147
+ const t = setTimeout(() => { this.off(ev, h); reject(new Error(`timeout waiting for ${ev}`)); }, timeoutMs);
148
+ const h = (e) => { if (!pred(e))
149
+ return; clearTimeout(t); this.off(ev, h); resolve(e); };
150
+ this.on(ev, h);
151
+ });
152
+ }
153
+ }
154
+ // ------------------------------------------------------------------ browser fallback host
155
+ /**
156
+ * Opens popups as plain tabs/windows in the OS default browser. Everything else is a no-op: a browser tab
157
+ * cannot be made always-on-top / click-through / frameless, and closing it is the user's job. The loopback
158
+ * URL and its single-use ticket are identical to the shell path, so the security model is unchanged.
159
+ */
160
+ export class BrowserPopupHost extends EventEmitter {
161
+ native = false;
162
+ platform;
163
+ port;
164
+ opened = new Set();
165
+ /** `port`: when known, only http://127.0.0.1:<port> URLs are ever launched (the manager passes the server's). */
166
+ constructor(platform = process.platform, port) { super(); this.platform = platform; this.port = port; }
167
+ start() { return Promise.resolve(null); }
168
+ open(id, url) {
169
+ if (!openInBrowser(url, this.platform, this.port)) {
170
+ this.emit('error', new Error(`refused to open ${url} in a browser on ${this.platform} (not our loopback origin, or no launcher)`));
171
+ return;
172
+ }
173
+ this.opened.add(id);
174
+ this.emit('window.created', { ev: 'window.created', id, t_ms: 0, create_ms: 0 });
175
+ }
176
+ close(id) { this.opened.delete(id); /* a browser tab cannot be closed from here */ }
177
+ show(_id) { }
178
+ hide(_id) { }
179
+ focus(_id) { }
180
+ alwaysOnTop(_id, _on) { }
181
+ clickThrough(_id, _on) { }
182
+ move(_id, _x, _y) { }
183
+ setBounds(_id, _r) { }
184
+ quit() { this.opened.clear(); }
185
+ }
186
+ /**
187
+ * Launch a URL in the OS browser: `open` (macOS) or `xdg-open` (Linux); `cmd /c start "" <url>` on Windows.
188
+ * Only http://127.0.0.1 URLs are ever launched — enforced here by a parsed comparison (see loopback.ts),
189
+ * not just assumed: https, remote hosts, userinfo (`http://127.0.0.1:1@evil.example/`), `localhost`,
190
+ * `[::1]` and non-http schemes are refused so a remote page can never be opened via the fallback (ROADMAP
191
+ * "no remote content"); with `port` given, any other loopback port is refused too. The URL is a single
192
+ * argument — no shell interpolation. Returns false when refused or when no launcher is available.
193
+ */
194
+ export function openInBrowser(url, platform = process.platform, port) {
195
+ if (!isLoopbackUrl(url, port))
196
+ return false;
197
+ const href = new URL(url).toString();
198
+ const cmd = platform === 'darwin' ? ['open', href]
199
+ : platform === 'win32' ? ['cmd', '/c', 'start', '', href]
200
+ : ['xdg-open', href];
201
+ try {
202
+ const p = spawn(cmd[0], cmd.slice(1), { stdio: 'ignore' });
203
+ p.on('error', () => { });
204
+ p.unref?.();
205
+ return true;
206
+ }
207
+ catch {
208
+ return false;
209
+ }
210
+ }
@@ -0,0 +1,12 @@
1
+ // @dexterm/popup — loopback popup host: a frameless HUD-window layer (wry) with a graceful OS-browser
2
+ // fallback, driven by a loopback-only server and a typed protocol (task-1290). The terminal is the hub;
3
+ // heavy views (chart, token detail, feeds) open as chromeless always-on-top windows over the same data.
4
+ export * from "./protocol.js";
5
+ export { createPopupServer, POPUP_EVENTS, IMAGE_ROUTE_RE, IMAGE_ROUTE_PREFIX } from "./server.js";
6
+ export { auditWidgetHtml, inlineHashes, normalizeWidgetHtml, cspHash, WIDGET_INLINE_MAX } from "./widget-html.js";
7
+ export { createPopupHost, ShellPopupHost, BrowserPopupHost, openInBrowser, shellBinaryPath, displayAvailable, nativeShellAvailable } from "./host.js";
8
+ export { createPopupManager, assertLoopbackUrl } from "./open.js";
9
+ export { loopbackUrlProblem, isLoopbackUrl } from "./loopback.js";
10
+ export { loadConfig, normalizeConfig, configPath, dexHome, DEFAULT_CONFIG, DEFAULT_HOTKEY, DEFAULT_HOTKEY_CLOSE, DEFAULT_DOCK, DEFAULT_RAIN } from "./config.js";
11
+ export { detectTerminal, controllingTty, dockScript, parseDockProbe, dockRect, TerminalDock } from "./dock.js";
12
+ export { runSelfTest, renderSelfTest, fixtureChartVM, fixtureRosterVM } from "./selftest.js";
@@ -0,0 +1,53 @@
1
+ // The one URL pin every popup path goes through (task-1290 follow-up): a popup may only load, navigate to,
2
+ // or be launched in a browser at OUR loopback server origin, http://127.0.0.1:<port>/… — never a remote
3
+ // page (ROADMAP "no remote content"), never another local service.
4
+ //
5
+ // This is a parsed comparison, not a string prefix: `http://127.0.0.1:1@evil.example/` has host
6
+ // evil.example (the "127.0.0.1:1" is userinfo), so a prefix match on "http://127.0.0.1:" passes it and a
7
+ // parsed one refuses it. Any userinfo is refused outright, even to our own host, so the launched URL can
8
+ // never carry a credential-shaped prefix. The hostname must be exactly 127.0.0.1 after WHATWG
9
+ // normalisation (so `0x7f.1`, `2130706433`, `127.0.0.%31` and `HTTP://` all resolve to our host and
10
+ // pass — they ARE 127.0.0.1 — while `localhost`, `[::1]`, `127.0.0.1.evil.example` and `evil.example`
11
+ // are refused: the server only answers `Host: 127.0.0.1:<port>`). The port must equal the server's
12
+ // (an http:// URL with no explicit port means 80).
13
+ //
14
+ // The wry shell applies the same rule in Rust (shell/wry/src/main.rs `nav_allowed`) as defence in depth.
15
+ /** Why `raw` is not an allowed popup URL for the server on `port`, or null when it is allowed. */
16
+ export function loopbackUrlProblem(raw, port) {
17
+ let u;
18
+ try {
19
+ u = new URL(raw);
20
+ }
21
+ catch {
22
+ return `popup url is not a valid URL: ${raw}`;
23
+ }
24
+ if (u.protocol !== 'http:')
25
+ return `popup url must be an http:// loopback url, got ${u.protocol}//`;
26
+ if (u.username !== '' || u.password !== '')
27
+ return 'popup url must not carry userinfo (user:pass@)';
28
+ if (u.hostname !== '127.0.0.1')
29
+ return `popup url host must be 127.0.0.1, got ${u.hostname || '(empty)'}`;
30
+ if ((u.port || '80') !== String(port))
31
+ return `popup url port must be the popup server's (${port}), got ${u.port || '(default)'}`;
32
+ return null;
33
+ }
34
+ /** The normalised URL when allowed (see loopbackUrlProblem); throws otherwise. */
35
+ export function assertLoopbackUrl(raw, port) {
36
+ const problem = loopbackUrlProblem(raw, port);
37
+ if (problem)
38
+ throw new Error(problem);
39
+ return new URL(raw).toString();
40
+ }
41
+ /** True when `raw` is an http:// URL at exactly 127.0.0.1 with no userinfo (and on `port`, when given). */
42
+ export function isLoopbackUrl(raw, port) {
43
+ let u;
44
+ try {
45
+ u = new URL(raw);
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ if (u.protocol !== 'http:' || u.username !== '' || u.password !== '' || u.hostname !== '127.0.0.1')
51
+ return false;
52
+ return port === undefined || (u.port || '80') === String(port);
53
+ }
@@ -0,0 +1,183 @@
1
+ // dexterm popup host — the small, lane-reusable facade (task-1290 scope item 3).
2
+ //
3
+ // const mgr = createPopupManager({ theme, onEvent });
4
+ // const h = await mgr.open({ kind: 'chart', title: 'WIF', data: chartVm }); // opens a frameless HUD window
5
+ // mgr.setData(h.id, nextChartVm); // the popup follows the open coin
6
+ // h.close(); // closing never touches the TUI
7
+ //
8
+ // The same `open({ kind, url|route, title })` is what Jarvis will call ("show me the bubble map for X") and
9
+ // what the trading lane's wallet bridge can call to render its loopback page in a chromeless window instead
10
+ // of the default browser — pass `url` for a custom loopback page, or `route` for one of the built-in kinds.
11
+ import { EventEmitter } from 'node:events';
12
+ import { randomBytes } from 'node:crypto';
13
+ import os from 'node:os';
14
+ import { createPopupServer } from "./server.js";
15
+ import { createPopupHost } from "./host.js";
16
+ import { createSinkProxy } from "./sinkproxy.js";
17
+ import { DEFAULT_HOTKEY, DEFAULT_RAIN } from "./config.js";
18
+ /** macOS major (Darwin kernel major − 9): WKWebsiteDataStore.proxyConfigurations needs macOS 14 = Darwin 23. */
19
+ const darwinMajor = () => Number(os.release().split('.')[0]) || 0;
20
+ // A custom popup URL may only point at our own loopback server origin (http://127.0.0.1:<server-port>/…).
21
+ // This enforces the ROADMAP "no remote content" rule in code, not just in the doc comment: https, any
22
+ // other host, any other port, userinfo and non-http schemes are all rejected before a window or browser
23
+ // tab opens. The rule itself lives in ./loopback.ts (shared with the browser fallback); re-exported here.
24
+ import { assertLoopbackUrl } from "./loopback.js";
25
+ export { assertLoopbackUrl, loopbackUrlProblem } from "./loopback.js";
26
+ export function createPopupManager(opts = {}) {
27
+ const emitter = new EventEmitter();
28
+ if (opts.onEvent)
29
+ emitter.on('event', opts.onEvent);
30
+ let server = null;
31
+ let host = null;
32
+ let native = false;
33
+ let reason = 'not started';
34
+ let hotkeyRegistered = false;
35
+ let hostDead = false;
36
+ let startPromise = null;
37
+ const openIds = new Set();
38
+ // task-1358 r4: the sink proxy + the widget gate (see PopupManager.widgetsAllowed)
39
+ let sinkProxy = null;
40
+ let widgetsOk = false;
41
+ let widgetReason = 'popups not started';
42
+ const warn = (m) => { opts.onWarn?.(m); };
43
+ const noWidgets = (why) => { widgetsOk = false; widgetReason = why; };
44
+ /** Swap a crashed native shell for the browser fallback so the next open() degrades instead of throwing. */
45
+ async function switchToBrowser(why) {
46
+ hostDead = false;
47
+ native = false;
48
+ hotkeyRegistered = false;
49
+ reason = `${why}; using browser`;
50
+ noWidgets(`browser fallback (${reason})`);
51
+ const fb = createPopupHost({ forceBrowser: true, env: opts.env, platform: opts.platform, port: server?.port });
52
+ host = fb.host;
53
+ await host.start();
54
+ warn(`popups open in your browser: ${reason}`);
55
+ }
56
+ async function ensureStarted() {
57
+ if (startPromise)
58
+ return startPromise;
59
+ startPromise = (async () => {
60
+ server = await createPopupServer({
61
+ theme: opts.theme,
62
+ rain: opts.rain ?? { ...DEFAULT_RAIN },
63
+ onEvent: (ev) => emitter.emit('event', ev),
64
+ ...(opts.imageProvider ? { imageProvider: opts.imageProvider } : {}),
65
+ });
66
+ const hostOpts = { hotkey: opts.hotkey ?? DEFAULT_HOTKEY, hotkeyClose: opts.hotkeyClose, forceBrowser: opts.forceBrowser, env: opts.env, platform: opts.platform, port: server.port };
67
+ let decision = createPopupHost(hostOpts);
68
+ if (decision.native) {
69
+ // r4: the shell gets a loopback sink proxy that tunnels to the popup server and nowhere else; every
70
+ // connection a WebView makes — page loads, the socket, a runtime <link rel=preconnect> — goes through it.
71
+ const srvPort = server.port;
72
+ sinkProxy = await createSinkProxy({ targetPort: srvPort, onRefused: (r) => emitter.emit('event', { ev: 'proxy.refused', ...r }) });
73
+ decision = createPopupHost({ ...hostOpts, proxyPort: sinkProxy.port });
74
+ }
75
+ host = decision.host;
76
+ native = decision.native;
77
+ reason = decision.reason;
78
+ if (!native) {
79
+ warn(`popups open in your browser: ${reason}`);
80
+ noWidgets(`browser fallback (${reason})`);
81
+ }
82
+ else {
83
+ // If the native window shell dies (crash/kill), mark it dead so the next open() re-starts via the
84
+ // browser fallback instead of letting a later close/open throw "shell not running" into the TUI.
85
+ const nativeHost = host;
86
+ nativeHost.once?.('exit', () => { hostDead = true; noWidgets('the popup shell exited'); });
87
+ }
88
+ try {
89
+ const info = await host.start();
90
+ if (info)
91
+ hotkeyRegistered = info.hotkeyRegistered;
92
+ if (native) {
93
+ const confirmed = info?.sinkProxy ?? null;
94
+ if ((opts.platform ?? process.platform) === 'darwin' && darwinMajor() < 23)
95
+ noWidgets('the sink proxy needs macOS 14 or newer');
96
+ else if (!sinkProxy || confirmed !== sinkProxy.address)
97
+ noWidgets(`the popup shell did not confirm the sink proxy (${confirmed ?? 'none'})`);
98
+ else {
99
+ widgetsOk = true;
100
+ widgetReason = `native shell behind the sink proxy ${sinkProxy.address}`;
101
+ }
102
+ }
103
+ }
104
+ catch (err) {
105
+ // The shell was present but would not start (missing libs, no GUI session, crash): degrade to the
106
+ // browser so a popup still opens, and tell the user why.
107
+ reason = `shell failed to start (${err.message}); using browser`;
108
+ native = false;
109
+ hotkeyRegistered = false;
110
+ noWidgets(`browser fallback (${reason})`);
111
+ warn(`popups open in your browser: ${reason}`);
112
+ const fb = createPopupHost({ forceBrowser: true, env: opts.env, platform: opts.platform, port: server.port });
113
+ host = fb.host;
114
+ await host.start();
115
+ }
116
+ })();
117
+ return startPromise;
118
+ }
119
+ function handleFor(id, kind) {
120
+ return {
121
+ id, kind, native,
122
+ setData: (data) => server?.setData(id, data),
123
+ sendData: (topic, payload) => server?.sendData(id, topic, payload),
124
+ close: () => { server?.closePopup(id); host?.close(id); openIds.delete(id); },
125
+ show: () => host?.show(id),
126
+ hide: () => host?.hide(id),
127
+ focus: () => host?.focus(id),
128
+ alwaysOnTop: (on) => host?.alwaysOnTop(id, on),
129
+ setBounds: (r) => { if (native && !hostDead)
130
+ host?.setBounds?.(id, r); },
131
+ };
132
+ }
133
+ return {
134
+ async open(req) {
135
+ await ensureStarted();
136
+ if (hostDead)
137
+ await switchToBrowser('popup window shell exited');
138
+ if (!server || !host)
139
+ throw new Error('popup host failed to start');
140
+ const id = `p_${randomBytes(6).toString('hex')}`;
141
+ const kind = req.route ?? req.kind;
142
+ // r4: a pack widget never opens in an engine the host does not route through its sink proxy
143
+ if ((kind === 'widget' || req.widget) && !widgetsOk)
144
+ throw new Error(`pack widgets open only in the native popup shell behind its sink proxy — ${widgetReason}`);
145
+ // A custom loopback URL (e.g. the wallet bridge's own page) is pinned to our own loopback origin —
146
+ // never a remote page; a built-in kind gets a single-use ticket URL from our server with the initial
147
+ // view model attached. assertLoopbackUrl throws on https/other-host/other-port before anything opens.
148
+ const url = req.url !== undefined
149
+ ? assertLoopbackUrl(req.url, server.port)
150
+ : server.issueTicket(kind, id, { ...(req.data ? { data: req.data } : {}), ...(req.widget ? { widget: req.widget } : {}), ...(native ? {} : { fallback: reason }) });
151
+ host.open(id, url, req.geom);
152
+ openIds.add(id);
153
+ return handleFor(id, kind);
154
+ },
155
+ setData: (id, data) => server?.setData(id, data),
156
+ close: (id) => { server?.closePopup(id); host?.close(id); openIds.delete(id); },
157
+ get native() { return native; },
158
+ get reason() { return reason; },
159
+ get hotkeyRegistered() { return hotkeyRegistered; },
160
+ get port() { return server?.port ?? null; },
161
+ get widgetsAllowed() { return widgetsOk; },
162
+ get widgetReason() { return widgetReason; },
163
+ get sinkProxyPort() { return sinkProxy?.port ?? null; },
164
+ get sinkProxyStats() { return sinkProxy ? { forwarded: sinkProxy.forwarded, refused: sinkProxy.refused } : null; },
165
+ async shutdown() {
166
+ try {
167
+ host?.quit();
168
+ }
169
+ catch { /* ignore */ }
170
+ if (sinkProxy) {
171
+ await sinkProxy.close();
172
+ sinkProxy = null;
173
+ }
174
+ noWidgets('popups shut down');
175
+ if (server)
176
+ await server.close();
177
+ server = null;
178
+ host = null;
179
+ startPromise = null;
180
+ openIds.clear();
181
+ },
182
+ };
183
+ }
@@ -0,0 +1,160 @@
1
+ // dexterm popup — bubble map pure helpers (task-1340). No DOM, no network, no globals besides the export.
2
+ // The SAME layout algorithm as @dexterm/tui bubbles.ts (packed circles per cluster, members radial around their
3
+ // hub, area ∝ share with a per-bubble cap), in plain JS so the page can inline it under its CSP nonce and
4
+ // test/bubbles-lib.test.ts can load it through node:vm. Pixel space here is real pixels (W × H), not half-rows.
5
+ (function (root) {
6
+ 'use strict';
7
+ const KIND_LETTER = { 'dev-linked': 'D', dev: 'D', bundle: 'B', funder: 'F', transfer: 'T', sniper: 'S', pool: 'P', cex: 'X', whale: 'W', plain: '·' };
8
+ const LEGEND = [['dev', 'dev'], ['bundle', 'bundle'], ['funder', 'linked'], ['sniper', 'sniper'], ['pool', 'pool/LP'], ['cex', 'exchange'], ['plain', 'unlinked']];
9
+
10
+ /** Colour = rule; `t` is the popup theme tokens. */
11
+ function kindColor(t, kind) {
12
+ switch (kind) {
13
+ case 'dev': case 'dev-linked': return t.warn;
14
+ case 'bundle': return t.pump || t.accent;
15
+ case 'funder': return t.up;
16
+ case 'transfer': return t.dbc || t.warn;
17
+ case 'sniper': return t.down;
18
+ case 'pool': return t.accent;
19
+ case 'cex': case 'whale': return t.muted;
20
+ default: return t.dim;
21
+ }
22
+ }
23
+
24
+ function placeCluster(members) {
25
+ const hub = members[0];
26
+ const out = [{ i: hub.i, r: hub.r, dx: 0, dy: 0 }];
27
+ if (members.length === 1) return { items: out, R: hub.r + 1.5 };
28
+ let ringR = hub.r, k = 1, maxReach = hub.r;
29
+ while (k < members.length) {
30
+ const ring = [];
31
+ const d0 = ringR + 2;
32
+ let angleUsed = 0, biggest = 0;
33
+ while (k < members.length) {
34
+ const m = members[k];
35
+ const d = d0 + m.r;
36
+ const need = 2 * Math.asin(Math.min(1, (m.r + 1) / d));
37
+ if (ring.length && angleUsed + need > 2 * Math.PI) break;
38
+ ring.push({ m, d, need });
39
+ angleUsed += need; biggest = Math.max(biggest, m.r); k++;
40
+ }
41
+ const slack = Math.max(0, 2 * Math.PI - angleUsed) / ring.length;
42
+ let a = -Math.PI / 2 + (ring.length % 2 ? 0 : slack / 2) + out.length * 0.37;
43
+ for (const { m, d, need } of ring) {
44
+ a += need / 2 + slack / 2;
45
+ out.push({ i: m.i, r: m.r, dx: Math.cos(a) * d, dy: Math.sin(a) * d });
46
+ maxReach = Math.max(maxReach, d + m.r);
47
+ a += need / 2 + slack / 2;
48
+ }
49
+ ringR = d0 + 2 * biggest;
50
+ }
51
+ return { items: out, R: maxReach + 1.5 };
52
+ }
53
+
54
+ function packClusters(cls, W, H, gap) {
55
+ const placed = [];
56
+ const cx0 = W / 2, cy0 = H / 2;
57
+ const inside = (x, y, R) => x - R >= 0 && x + R <= W && y - R >= 0 && y + R <= H;
58
+ const free = (x, y, R) => placed.every((p) => Math.hypot(p.x - x, p.y - y) >= p.R + R + gap - 1e-6);
59
+ for (const c of cls) {
60
+ if (!placed.length) { if (!inside(cx0, cy0, c.R)) return null; placed.push(Object.assign({}, c, { x: cx0, y: cy0 })); continue; }
61
+ let best = null, bestD = Infinity;
62
+ for (const p of placed) {
63
+ for (let s = 0; s < 48; s++) {
64
+ const th = (s / 48) * 2 * Math.PI;
65
+ const d = p.R + c.R + gap;
66
+ const x = p.x + Math.cos(th) * d, y = p.y + Math.sin(th) * d;
67
+ if (!inside(x, y, c.R) || !free(x, y, c.R)) continue;
68
+ const dd = (x - cx0) ** 2 + ((y - cy0) * 1.15) ** 2;
69
+ if (dd < bestD) { bestD = dd; best = { x, y }; }
70
+ }
71
+ }
72
+ if (!best) return null;
73
+ placed.push(Object.assign({}, c, { x: best.x, y: best.y }));
74
+ }
75
+ return placed;
76
+ }
77
+
78
+ /**
79
+ * Lay out holders in a W×H pixel area. Returns { bubbles:[{idx,cx,cy,r}], clusters:[{name,kind,pct,cx,cy,R,members,multi}], k }.
80
+ * `focus` = a cluster name → only that cluster (zoom). Deterministic. Never throws (grid fallback).
81
+ */
82
+ function layout(holders, clusters, W, H, opts) {
83
+ opts = opts || {};
84
+ const idxs = holders.map((_, i) => i).filter((i) => !opts.focus || holders[i].cluster === opts.focus);
85
+ if (!idxs.length || W < 20 || H < 20) return { bubbles: [], clusters: [], k: 0, W, H };
86
+ const total = idxs.reduce((s, i) => s + Math.max(0.01, holders[i].pct), 0);
87
+ const fill = opts.fill != null ? opts.fill : opts.focus ? 0.6 : 0.5;
88
+ let k = Math.sqrt((fill * W * H) / (Math.PI * total));
89
+ const rMax = Math.min(W / 5.2, H / 2.6);
90
+ const gap = Math.max(2, Math.min(W, H) * 0.01);
91
+ for (let iter = 0; iter < 14; iter++) {
92
+ const byCluster = new Map();
93
+ for (const i of idxs) {
94
+ const h = holders[i];
95
+ const r = Math.max(3, Math.min(rMax, k * Math.sqrt(Math.max(0.01, h.pct))));
96
+ const key = h.cluster || `single:${i}`;
97
+ const arr = byCluster.get(key) || []; arr.push({ i, r }); byCluster.set(key, arr);
98
+ }
99
+ const cls = [];
100
+ for (const [key, members] of byCluster) {
101
+ members.sort((a, b) => b.r - a.r);
102
+ const pc = placeCluster(members);
103
+ const c = clusters.find((x) => x.name === key) || null;
104
+ cls.push({ key, name: c ? c.name : null, kind: c ? c.kind : holders[members[0].i].kind, pct: c ? c.pct : holders[members[0].i].pct, items: pc.items, R: pc.R, multi: members.length > 1 });
105
+ }
106
+ cls.sort((a, b) => b.R - a.R);
107
+ const placed = packClusters(cls, W, H, gap);
108
+ if (placed) {
109
+ const bubbles = [], out = [];
110
+ for (const p of placed) {
111
+ for (const it of p.items) bubbles.push({ idx: it.i, cx: p.x + it.dx, cy: p.y + it.dy, r: it.r });
112
+ out.push({ name: p.name, kind: p.kind, pct: p.pct, cx: p.x, cy: p.y, R: p.R, members: p.items.map((it) => it.i), multi: p.multi });
113
+ }
114
+ return { bubbles, clusters: out, k, W, H };
115
+ }
116
+ k *= 0.86;
117
+ }
118
+ const cols = Math.max(1, Math.floor(W / 24));
119
+ return { bubbles: idxs.map((i, n) => ({ idx: i, cx: 12 + (n % cols) * 24, cy: 12 + Math.floor(n / cols) * 24, r: 8 })), clusters: [], k, W, H, overflow: true };
120
+ }
121
+
122
+ /** Holder index under a point, or -1. */
123
+ function hit(lay, x, y) {
124
+ let best = -1, bestD = Infinity;
125
+ for (const b of lay.bubbles) { const d = Math.hypot(b.cx - x, b.cy - y); if (d <= b.r && d < bestD) { bestD = d; best = b.idx; } }
126
+ return best;
127
+ }
128
+
129
+ const shortAddr = (s, n) => { n = n || 4; s = String(s || ''); return s.length > 2 * n + 1 ? `${s.slice(0, n)}…${s.slice(-n)}` : s; };
130
+ const pctText = (p) => `${p.toFixed(p >= 10 ? 1 : 2)}%`;
131
+ const fmtNum = (n) => (n == null ? '—' : n >= 1e9 ? `${(n / 1e9).toFixed(2)}B` : n >= 1e6 ? `${(n / 1e6).toFixed(2)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(Math.round(n)));
132
+
133
+ /** The rule panel lines for one holder: label · confidence, then the rule text, then up to 3 evidence lines. */
134
+ function whyLines(h, cluster) {
135
+ const lines = [];
136
+ if (!h) return lines;
137
+ lines.push({ kind: 'head', text: `${shortAddr(h.owner, 6)} ${pctText(h.pct)} ${h.kind.toUpperCase()}${h.cluster ? ` ${h.cluster}` : ''}` });
138
+ // task-1367: the holder class + its on-chain reason (absent from older terminals)
139
+ if (h.cls) {
140
+ lines.push({ kind: 'label', text: `■ ${h.cls.tag}`, right: 'on-chain', label: h.cls.tag === 'UNLABELLED' ? 'plain' : h.cls.tag === 'DEV' ? 'dev' : h.cls.tag === 'LINKED' ? 'dev-linked' : h.cls.tag === 'CEX' ? 'cex' : 'pool' });
141
+ lines.push({ kind: 'rule', text: h.cls.reason });
142
+ if (h.cls.lock) lines.push({ kind: 'muted', text: `${h.cls.lock.program} escrow ${shortAddr(h.cls.lock.escrow, 4)} · ${h.cls.lock.unlock || h.cls.lock.note}` });
143
+ }
144
+ if (h.funder) lines.push({ kind: 'muted', text: `funded by ${shortAddr(h.funder.address, 4)}${h.funder.label ? ` (${h.funder.label})` : ''}${h.funder.blockTime ? ` · ${new Date(h.funder.blockTime * 1000).toISOString().slice(0, 10)}` : ''}` });
145
+ else if (h.walletFirstSeen) lines.push({ kind: 'muted', text: `first seen ${new Date(h.walletFirstSeen * 1000).toISOString().slice(0, 10)} · funder not found in first txs` });
146
+ else lines.push({ kind: 'muted', text: 'funding not looked up (outside the funded sample)' });
147
+ const linked = cluster && cluster.members && cluster.members.length > 1;
148
+ const rules = h.labels && h.labels.length ? h.labels : linked
149
+ ? [{ label: `linked · ${cluster.kind}`, rule: `no label of its own; linked into ${cluster.name} (${cluster.members.length} wallets, ${Number(cluster.pct).toFixed(1)}%) by the evidence below`, confidence: 'on-chain' }]
150
+ : [{ label: 'unlinked', rule: 'no rule matched: no shared funder, same-slot buy, transfer or creator link found in the sample', confidence: 'n/a' }];
151
+ for (const r of rules) { lines.push({ kind: 'label', text: `● ${r.label}`, right: r.confidence || '', label: r.label }); lines.push({ kind: 'rule', text: r.rule }); }
152
+ const ev = cluster ? cluster.evidence.filter((e) => e.linked !== false && (e.a === h.owner || e.b === h.owner)).slice(0, 3) : [];
153
+ for (const e of ev) lines.push({ kind: 'evidence', text: `↳ ${shortAddr(e.a, 4)} ↔ ${shortAddr(e.b, 4)}: ${e.rule}` });
154
+ lines.push({ kind: 'foot', text: 'labels are heuristics (rule + evidence) · never asserted as fact' });
155
+ return lines;
156
+ }
157
+
158
+ const api = { KIND_LETTER, LEGEND, kindColor, layout, hit, shortAddr, pctText, fmtNum, whyLines };
159
+ root.DTBubbles = Object.freeze(api);
160
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -0,0 +1,23 @@
1
+ /* dexterm popup — bubble map page (task-1340). Same HUD chrome as the chart; the map is a canvas, the panels are text. */
2
+ .hud.bubbles .stage { min-height: 260px; }
3
+ .hud.bubbles #map { z-index: 1; cursor: crosshair; }
4
+ .hud.bubbles .depth { font-size: 11px; color: var(--dim); white-space: nowrap; }
5
+ .hud.bubbles .banner { padding: 6px 12px; font-size: 12px; color: var(--warn); border-bottom: 1px solid var(--border); background: rgba(245,255,45,.05); }
6
+ .hud.bubbles .banner.bad { color: var(--down); background: rgba(255,59,107,.06); }
7
+ .hud.bubbles .tip { position: absolute; z-index: 2; max-width: 360px; padding: 6px 9px; font-size: 11px; line-height: 1.35; color: var(--text);
8
+ background: rgba(3,9,12,.94); border: 1px solid var(--border); border-radius: 8px; pointer-events: none; white-space: pre-wrap; word-break: break-word; }
9
+ .hud.bubbles .tip .k { color: var(--muted); }
10
+ .hud.bubbles .insights { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 0 10px; padding: 5px 10px 3px; border-top: 1px solid var(--border); font-size: 11px; }
11
+ .hud.bubbles .insights .cell .l { color: var(--dim); text-transform: uppercase; letter-spacing: .04em; font-size: 10px; }
12
+ .hud.bubbles .insights .cell .v { color: var(--text); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
13
+ .hud.bubbles .insights .cell .s { color: var(--muted); font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
14
+ .hud.bubbles .insights .sample { grid-column: 1 / -1; color: var(--dim); font-size: 10px; padding-top: 2px; }
15
+ .hud.bubbles .why { padding: 4px 10px 6px; border-top: 1px solid var(--border); font-size: 11px; line-height: 1.35; max-height: 118px; overflow: auto; }
16
+ .hud.bubbles .why .head { color: var(--borderFocus); font-weight: 700; }
17
+ .hud.bubbles .why .label { display: flex; justify-content: space-between; color: var(--text); font-weight: 700; margin-top: 3px; }
18
+ .hud.bubbles .why .label .conf { color: var(--dim); font-weight: 400; }
19
+ .hud.bubbles .why .rule { color: var(--muted); padding-left: 12px; }
20
+ .hud.bubbles .why .evidence { color: var(--dim); font-style: italic; padding-left: 8px; }
21
+ .hud.bubbles .why .foot, .hud.bubbles .why .muted { color: var(--dim); }
22
+ .hud.bubbles .legend span { display: inline-flex; align-items: center; gap: 5px; }
23
+ .hud.bubbles .legend i { display: inline-block; width: 9px; height: 9px; border-radius: 50%; }