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,174 @@
1
+ // token-dossier pack, ASSEMBLE stage (AI-SUITE-DESIGN §2, ported from research/task-1287/packs/token-dossier).
2
+ //
3
+ // Pure, deterministic, zero tokens: it reads the `get_dossier` object the MCP server already assembles
4
+ // (packages/mcp/src/dossier.ts `buildDossier`) and computes the numbers, the named rule flags, the
5
+ // deterministic verdict, the data ages and a cache key. This is what the UI chips, the batch scorer, the
6
+ // rug model and the backtest read; the optional REASON stage (reason.ts) runs one tool-less model call
7
+ // over this object and may only TIGHTEN the verdict.
8
+ //
9
+ // Thresholds are the task-1293 skill rules (owner: maintainers); the model stage may add flags, never remove.
10
+ import { createHash } from 'node:crypto';
11
+ import { scanForInjection } from "./firewall.js";
12
+ export const TOKEN_DOSSIER_PACK = 'token-dossier';
13
+ export const TOKEN_DOSSIER_VERSION = '0.1.0';
14
+ const isObj = (r) => !!r && typeof r === 'object';
15
+ const hasError = (r) => isObj(r) && 'error' in r && !!r.error;
16
+ const present = (r) => isObj(r) && !hasError(r);
17
+ const num = (x) => (typeof x === 'number' && Number.isFinite(x) ? x : null);
18
+ const boolOrNull = (x) => (x === true || x === false ? x : null);
19
+ const pct = (a, b) => (a === null || b === null || !b ? null : Math.round((a / b) * 1000) / 10);
20
+ const path = (r, ...keys) => keys.reduce((acc, k) => (isObj(acc) ? acc[k] : undefined), r);
21
+ /**
22
+ * Assemble a token-dossier from a `get_dossier` object (buildDossier output). `now` defaults to the
23
+ * dossier's `now`. Pass a `rugScorer` to fill `rug_probability` (the deterministic rug model).
24
+ */
25
+ export function assembleTokenDossier(dossier, opts = {}) {
26
+ const now = opts.now ?? num(dossier.now) ?? Date.now();
27
+ const market = dossier.market; // get_token: { token, pair, stats, audit, now }
28
+ const concentration = dossier.concentration;
29
+ const security = dossier.security;
30
+ const candles = dossier.candles;
31
+ const tape = dossier.tape;
32
+ const creator = dossier.creator;
33
+ const mint = String(dossier.mint ?? path(market, 'token', 'mint') ?? '');
34
+ const tokenObj = path(market, 'token');
35
+ const pair = path(market, 'pair');
36
+ const stats = path(market, 'stats');
37
+ const audit = path(market, 'audit');
38
+ if (!present(market) || !present(tokenObj)) {
39
+ return errDossier(mint, 'market section unavailable', dossier, now);
40
+ }
41
+ const createdAt = num(path(tokenObj, 'createdAt')) ?? num(path(pair, 'createdAt'));
42
+ const ageSec = createdAt ? Math.max(0, Math.round((now - createdAt) / 1000)) : num(path(tokenObj, 'ageSec'));
43
+ const h1 = path(stats, 'perWindow', 'h1');
44
+ const buysH1 = num(path(pair, 'txns', 'h1', 'buys'));
45
+ const sellsH1 = num(path(pair, 'txns', 'h1', 'sells'));
46
+ const volH1 = num(path(pair, 'volume', 'h1'));
47
+ const liq = num(path(pair, 'liquidityUsd'));
48
+ const mcap = num(path(pair, 'mcapResolved')) ?? num(path(pair, 'mcap'));
49
+ const f = {
50
+ age_sec: ageSec,
51
+ launchpad: path(tokenObj, 'launchpad') ?? null,
52
+ price_usd: num(path(pair, 'priceUsd')), mcap, liquidity_usd: liq,
53
+ liq_to_mcap_pct: pct(liq, mcap),
54
+ vol_h1: volH1, vol_h24: num(path(pair, 'volume', 'h24')),
55
+ vol_h1_to_liq: volH1 !== null && liq ? Math.round((volH1 / liq) * 100) / 100 : null,
56
+ buys_h1: buysH1, sells_h1: sellsH1,
57
+ buy_share_h1_pct: pct(buysH1, (buysH1 ?? 0) + (sellsH1 ?? 0)),
58
+ chg_h1_pct: num(path(pair, 'priceChange', 'h1')), chg_h24_pct: num(path(pair, 'priceChange', 'h24')),
59
+ holder_count: num(path(stats, 'holderCount')), holders_24h_delta: num(path(stats, 'holders24hDelta')),
60
+ organic_score: num(path(stats, 'organicScore')),
61
+ num_traders_h1: num(path(h1, 'numTraders')), net_buyers_h1: num(path(h1, 'numNetBuyers')),
62
+ mint_authority: boolOrNull(path(audit, 'mintAuthority')), freeze_authority: boolOrNull(path(audit, 'freezeAuthority')),
63
+ top10_pct: num(path(audit, 'top10Pct')), top10_ex_lp_pct: num(path(concentration, 'top10PctExLp')),
64
+ dev_pct: num(path(audit, 'devPct')), dev_mints: num(path(audit, 'devMints')),
65
+ lp_locked_pct: num(path(audit, 'lpLockedPct')) ?? num(path(security, 'lpLockedPct')),
66
+ rug_score: num(path(security, 'scoreNormalised')) ?? num(path(audit, 'rugScore')),
67
+ rug_risk_count: Array.isArray(path(security, 'risks')) ? path(security, 'risks').length : null,
68
+ tape_top2_share_pct: num(path(tape, 'top2WalletVolumeSharePct')),
69
+ tape_buy_usd: num(path(tape, 'buyUsd')), tape_sell_usd: num(path(tape, 'sellUsd')),
70
+ tape_distinct_wallets: num(path(tape, 'distinctWallets')),
71
+ drawdown_from_high_pct: num(path(candles, 'summary', 'drawdownFromHighPct')),
72
+ chg_window_pct: num(path(candles, 'summary', 'changePct')),
73
+ creator_launches: num(path(creator, 'devHistory', 'launches')),
74
+ creator_rugged: num(path(creator, 'devHistory', 'rugged')),
75
+ snipers: num(path(creator, 'snipers')), bundled_pct: num(path(creator, 'bundledPct')),
76
+ };
77
+ const tier1 = present(creator) && path(creator, 'tier') === 'tier1';
78
+ const flags = computeFlags(f);
79
+ const disqualifier = flags.some((x) => DISQUALIFIERS.has(x));
80
+ const hasPrice = (f.price_usd ?? 0) > 0;
81
+ const hasLiquidity = (f.liquidity_usd ?? 0) > 0;
82
+ const deterministic_verdict = !(hasPrice || hasLiquidity)
83
+ ? 'INSUFFICIENT_DATA'
84
+ : disqualifier ? 'AVOID'
85
+ : flags.some((x) => SOFT_FLAGS.has(x)) ? 'SPECULATIVE'
86
+ : (f.age_sec !== null && f.age_sec < 600 && !tier1 ? 'INSUFFICIENT_DATA' : 'WATCH');
87
+ const avail = (isObj(dossier.availability) ? dossier.availability : {});
88
+ const sections = { market, concentration, security, candles, tape, creator };
89
+ const inputHash = createHash('sha256').update(stableStringify(sections)).digest('hex').slice(0, 16);
90
+ const rug = opts.rugScorer ? Math.round(opts.rugScorer.score(f) * 1000) / 1000 : null;
91
+ return {
92
+ pack: TOKEN_DOSSIER_PACK, pack_version: TOKEN_DOSSIER_VERSION, status: 'OK', mint,
93
+ symbol: path(tokenObj, 'symbol') ?? null,
94
+ assembled_at: new Date(now).toISOString(), input_hash: inputHash,
95
+ availability: {
96
+ hasPrice, hasLiquidity, canVerdict: hasPrice || hasLiquidity,
97
+ present: avail.present ?? [], missing: avail.missing ?? [],
98
+ knownUnavailable: avail.knownUnavailable ?? [],
99
+ },
100
+ features: f, flags, deterministic_verdict,
101
+ rug_probability: rug, rug_model: opts.rugScorer?.id ?? null,
102
+ injection_suspected: scanForInjection(sections),
103
+ data_ages: dataAges(sections),
104
+ sections,
105
+ };
106
+ }
107
+ /** The named rule flags (AI-SUITE-DESIGN §2; thresholds are the task-1293 skill rules, owner: maintainers). */
108
+ const DISQUALIFIERS = new Set(['freeze_authority_enabled', 'mint_authority_enabled', 'lp_unlocked_and_concentrated', 'wash_tape']);
109
+ const SOFT_FLAGS = new Set(['bundled_over_15pct', 'dev_holds_over_10pct', 'serial_rugger', 'organic_below_40']);
110
+ export function computeFlags(f) {
111
+ const flags = [];
112
+ if (f.freeze_authority === true)
113
+ flags.push('freeze_authority_enabled');
114
+ if (f.mint_authority === true)
115
+ flags.push('mint_authority_enabled');
116
+ if (f.lp_locked_pct !== null && f.lp_locked_pct < 100 && f.launchpad !== 'pump.fun' && (f.top10_ex_lp_pct ?? 0) > 50)
117
+ flags.push('lp_unlocked_and_concentrated');
118
+ if (f.tape_top2_share_pct !== null && f.tape_top2_share_pct > 60 && f.tape_buy_usd && f.tape_sell_usd && Math.abs(f.tape_buy_usd - f.tape_sell_usd) / Math.max(f.tape_buy_usd, f.tape_sell_usd) < 0.15)
119
+ flags.push('wash_tape');
120
+ if ((f.bundled_pct ?? 0) > 15)
121
+ flags.push('bundled_over_15pct');
122
+ if ((f.dev_pct ?? 0) > 10)
123
+ flags.push('dev_holds_over_10pct');
124
+ if ((f.creator_rugged ?? 0) >= 2)
125
+ flags.push('serial_rugger');
126
+ if (f.organic_score !== null && f.organic_score < 40)
127
+ flags.push('organic_below_40');
128
+ if (f.age_sec !== null && f.age_sec < 600)
129
+ flags.push('under_10_minutes');
130
+ return flags;
131
+ }
132
+ /** Data age per section: look for an `ageSec`/`source` at the section root or on its `pair`. */
133
+ function dataAges(sections) {
134
+ const out = {};
135
+ for (const [k, v] of Object.entries(sections)) {
136
+ const root = isObj(v) ? v : undefined;
137
+ const pr = isObj(path(v, 'pair')) ? path(v, 'pair') : undefined;
138
+ out[k] = {
139
+ source: root?.source ?? pr?.source ?? null,
140
+ age_sec: num(root?.ageSec) ?? num(pr?.ageSec),
141
+ };
142
+ }
143
+ return out;
144
+ }
145
+ function errDossier(mint, error, dossier, now) {
146
+ return {
147
+ pack: TOKEN_DOSSIER_PACK, pack_version: TOKEN_DOSSIER_VERSION, status: 'ERROR', mint, symbol: null,
148
+ assembled_at: new Date(now).toISOString(), input_hash: '',
149
+ availability: { hasPrice: false, hasLiquidity: false, canVerdict: false, present: [], missing: [error], knownUnavailable: [] },
150
+ features: emptyFeatures(), flags: [], deterministic_verdict: 'INSUFFICIENT_DATA',
151
+ rug_probability: null, rug_model: null, injection_suspected: false, data_ages: {},
152
+ sections: { market: dossier.market ?? null }, error,
153
+ };
154
+ }
155
+ function emptyFeatures() {
156
+ return {
157
+ age_sec: null, launchpad: null, price_usd: null, mcap: null, liquidity_usd: null, liq_to_mcap_pct: null,
158
+ vol_h1: null, vol_h24: null, vol_h1_to_liq: null, buys_h1: null, sells_h1: null, buy_share_h1_pct: null,
159
+ chg_h1_pct: null, chg_h24_pct: null, holder_count: null, holders_24h_delta: null, organic_score: null,
160
+ num_traders_h1: null, net_buyers_h1: null, mint_authority: null, freeze_authority: null, top10_pct: null,
161
+ top10_ex_lp_pct: null, dev_pct: null, dev_mints: null, lp_locked_pct: null, rug_score: null, rug_risk_count: null,
162
+ tape_top2_share_pct: null, tape_buy_usd: null, tape_sell_usd: null, tape_distinct_wallets: null,
163
+ drawdown_from_high_pct: null, chg_window_pct: null, creator_launches: null, creator_rugged: null, snipers: null, bundled_pct: null,
164
+ };
165
+ }
166
+ /** Deterministic JSON for the input hash: object keys sorted so the cache key is stable across runs. */
167
+ export function stableStringify(v) {
168
+ if (v === null || typeof v !== 'object')
169
+ return JSON.stringify(v) ?? 'null';
170
+ if (Array.isArray(v))
171
+ return `[${v.map(stableStringify).join(',')}]`;
172
+ const keys = Object.keys(v).sort();
173
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(v[k])}`).join(',')}}`;
174
+ }
@@ -0,0 +1,17 @@
1
+ // Record a pack verdict into the recorder's `verdicts` table (schema owner: @dexterm/recorder, lane
2
+ // L-01 reserved this table). A verdict row lets the eval/backtest score a model's output against the
3
+ // outcomes the labeler later writes (the non-circular golden set, AI-SUITE-DESIGN §4). Deterministic
4
+ // stages are recorded with model = null; reason stages with the model id and cost.
5
+ //
6
+ // Takes a `node:sqlite` DatabaseSync (Recorder.database) so the pack engine never imports the Recorder
7
+ // class and tests can pass an in-memory DB.
8
+ /** Insert one verdict row. The `verdicts` table is created by the recorder's SCHEMA_SQL. No-op guard if the table is absent. */
9
+ export function recordVerdict(db, row) {
10
+ db.prepare(`INSERT INTO verdicts (mint, t, pack, pack_version, model, verdict, rug_p, confidence, score, cost_usd, wall_ms, input_hash, run_file)
11
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.mint, row.t, row.pack, row.pack_version, row.model, row.verdict, row.rug_p, row.confidence, row.score, row.cost_usd, row.wall_ms, row.input_hash, row.run_file);
12
+ }
13
+ /** The most recent verdict for a mint+pack (for the cache: don't re-spend on an unchanged input_hash). */
14
+ export function latestVerdict(db, mint, pack) {
15
+ const r = db.prepare('SELECT mint, t, pack, pack_version, model, verdict, rug_p, confidence, score, cost_usd, wall_ms, input_hash, run_file FROM verdicts WHERE mint = ? AND pack = ? ORDER BY t DESC LIMIT 1').get(mint, pack);
16
+ return r ?? null;
17
+ }
@@ -0,0 +1,105 @@
1
+ // wallet-dossier pack, ASSEMBLE stage (AI-SUITE-DESIGN §2). "Who is this wallet?" grounded in what the
2
+ // merged, keyless code can actually prove: the wallet's role inside a scanned token's holder intelligence
3
+ // (@dexterm/data WalletIntel / IntelAnalysis) — its labels (each a rule id + confidence), cluster
4
+ // membership and kind, funder / self-funded, sniper / fresh-at-buy / created-before-launch, and holding
5
+ // share.
6
+ //
7
+ // HONEST SCOPE (design §2 + §14.4): cross-token PnL, win rate and the smart-money gate need the wallet
8
+ // tracker (task-1286 W6), which is NOT built. So the `SMART` verdict can never fire on keyless data; the
9
+ // assembled dossier records that in `availability.knownUnavailable` and the deterministic verdict is
10
+ // NEUTRAL / SUSPECT / INSUFFICIENT_DATA from the available signals. No number here comes from model
11
+ // memory; every fact is a field of the IntelAnalysis the tools returned.
12
+ import { createHash } from 'node:crypto';
13
+ import { HIDDEN_LABELS } from '../../data/src/index.js';
14
+ import { stableStringify } from "./token-dossier.js";
15
+ export const WALLET_DOSSIER_PACK = 'wallet-dossier';
16
+ export const WALLET_DOSSIER_VERSION = '0.1.0';
17
+ const num = (x) => (typeof x === 'number' && Number.isFinite(x) ? x : null);
18
+ /** The cross-token facts that need the wallet tracker (task-1286 W6), not available keyless. */
19
+ const KNOWN_UNAVAILABLE = [
20
+ 'PnL and realised/unrealised profit across tokens: needs the wallet tracker (task-1286 W6, not built)',
21
+ 'win rate and trade count (n>=10 gate): needs the wallet tracker',
22
+ 'smart-money gate (not exchange/MM, not self-funded, positive PnL over n>=10): not computable keyless, so SMART never fires',
23
+ ];
24
+ /**
25
+ * Assemble a wallet-dossier for `wallet` from one token's `IntelAnalysis` (WalletIntel.scan output).
26
+ * The verdict is grounded only in that scan: a wallet not in the scanned token's holder sample is
27
+ * NOT_IN_SCAN with INSUFFICIENT_DATA.
28
+ */
29
+ export function assembleWalletDossier(wallet, analysis, opts = {}) {
30
+ const now = opts.now ?? Date.now();
31
+ const holder = analysis.holders.find((h) => h.owner === wallet);
32
+ const cluster = analysis.clusters.find((c) => c.members.includes(wallet));
33
+ const present = [];
34
+ const missing = [];
35
+ if (!holder) {
36
+ return {
37
+ pack: WALLET_DOSSIER_PACK, pack_version: WALLET_DOSSIER_VERSION, status: 'NOT_IN_SCAN',
38
+ wallet, token: analysis.mint, assembled_at: new Date(now).toISOString(),
39
+ input_hash: createHash('sha256').update(`${wallet}:${analysis.mint}:not-in-scan`).digest('hex').slice(0, 16),
40
+ availability: { canVerdict: false, present: [], missing: ['wallet not in this token\'s top-holder sample'], knownUnavailable: KNOWN_UNAVAILABLE },
41
+ features: emptyWalletFeatures(), flags: [], labels_restated: [], deterministic_verdict: 'INSUFFICIENT_DATA',
42
+ injection_suspected: false, sections: { analysis_mint: analysis.mint, holder: null, cluster: null },
43
+ };
44
+ }
45
+ present.push('holder-sample', 'labels');
46
+ if (holder.funder)
47
+ present.push('funder');
48
+ if (cluster)
49
+ present.push('cluster');
50
+ if (holder.freshAtBuy === null)
51
+ missing.push('fresh-at-buy: launch window needs a Helius key (keyless tier)');
52
+ if (holder.funder === null)
53
+ missing.push('funder: funding lookup needs a Helius key');
54
+ // Self-funded = the funder is the wallet itself, or no distinct funder was found (design: self-funded
55
+ // wallets are never SMART).
56
+ const selfFunded = holder.funder ? holder.funder.address === wallet : null;
57
+ const labels = holder.labels ?? [];
58
+ const visibleLabels = labels.filter((l) => !HIDDEN_LABELS.has(l.label));
59
+ const labelSet = new Set(visibleLabels.map((l) => l.label));
60
+ const f = {
61
+ in_token: true,
62
+ holding_pct: num(holder.pct),
63
+ owner_kind: holder.ownerKind ?? null,
64
+ cluster: cluster?.name ?? null,
65
+ cluster_kind: cluster?.kind ?? null,
66
+ cluster_pct: cluster ? num(cluster.pct) : null,
67
+ self_funded: selfFunded,
68
+ funder: holder.funder?.address ?? null,
69
+ funder_label: holder.funder?.label ?? null,
70
+ sniper: labelSet.has('sniper'),
71
+ fresh_at_buy: holder.freshAtBuy,
72
+ created_before_launch: holder.createdBeforeLaunch,
73
+ label_count: visibleLabels.length,
74
+ };
75
+ const flags = [];
76
+ if (f.sniper)
77
+ flags.push('sniper_window_buy');
78
+ if (f.cluster_kind === 'bundle')
79
+ flags.push('in_bundle_cluster');
80
+ if (f.cluster_kind === 'dev-linked')
81
+ flags.push('dev_linked_cluster');
82
+ if (f.fresh_at_buy === true)
83
+ flags.push('fresh_wallet_at_buy');
84
+ if (f.owner_kind === 'program-owned')
85
+ flags.push('program_owned_account');
86
+ // SMART is unreachable keyless (no PnL). SUSPECT when a sniper / bundle / dev-linked / fresh signal
87
+ // fired; NEUTRAL otherwise; an LP / CEX / contract address is INSUFFICIENT_DATA as a "trader".
88
+ const infra = labelSet.has('LP') || labelSet.has('cex') || labelSet.has('contract') || labelSet.has('pool');
89
+ const deterministic_verdict = infra ? 'INSUFFICIENT_DATA' : flags.length ? 'SUSPECT' : 'NEUTRAL';
90
+ const labels_restated = visibleLabels.map((l) => ({ label: l.label, rule: l.rule, confidence: l.confidence }));
91
+ const sections = { analysis_mint: analysis.mint, holder, cluster: cluster ?? null };
92
+ return {
93
+ pack: WALLET_DOSSIER_PACK, pack_version: WALLET_DOSSIER_VERSION, status: 'OK',
94
+ wallet, token: analysis.mint, assembled_at: new Date(now).toISOString(),
95
+ input_hash: createHash('sha256').update(stableStringify(sections)).digest('hex').slice(0, 16),
96
+ availability: { canVerdict: true, present, missing, knownUnavailable: KNOWN_UNAVAILABLE },
97
+ features: f, flags, labels_restated, deterministic_verdict, injection_suspected: false, sections,
98
+ };
99
+ }
100
+ function emptyWalletFeatures() {
101
+ return {
102
+ in_token: false, holding_pct: null, owner_kind: null, cluster: null, cluster_kind: null, cluster_pct: null,
103
+ self_funded: null, funder: null, funder_label: null, sniper: false, fresh_at_buy: null, created_before_launch: null, label_count: 0,
104
+ };
105
+ }
@@ -0,0 +1,71 @@
1
+ // Popup host configuration (task-1290). decision: keep the spike's hotkey chord and rain cadence
2
+ // defaults, but make both configurable in a config file. The file is ~/.dexterm/popup.json (DEXTERM_HOME
3
+ // overrides the directory, same convention as state.json / helius.key). Everything is optional; a missing
4
+ // or malformed file degrades to the defaults and never throws — a popup must open even with a bad config.
5
+ import { readFileSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ /** The spike's global-hotkey chord (⌃⌥⌘⇧B). Accelerator syntax: `Mod+Mod+Code`, matched by the shell. */
9
+ export const DEFAULT_HOTKEY = 'Control+Alt+Super+Shift+KeyB';
10
+ /** task-1325: the global chord that closes every popup window (⌃⌥⌘⇧W). */
11
+ export const DEFAULT_HOTKEY_CLOSE = 'Control+Alt+Super+Shift+KeyW';
12
+ export const DEFAULT_DOCK = { enabled: true, width: 560, follow: true, followEvery: 0.5 };
13
+ /** The rain cadence approved in the spike. speedScale 1 = the bubble-map default fall speed. */
14
+ export const DEFAULT_RAIN = { enabled: true, speedScale: 1, density: 0.55 };
15
+ export const DEFAULT_CONFIG = { hotkey: DEFAULT_HOTKEY, hotkeyClose: DEFAULT_HOTKEY_CLOSE, dock: { ...DEFAULT_DOCK }, rain: { ...DEFAULT_RAIN }, fpsCap: 60, source: 'defaults' };
16
+ /** `DEXTERM_HOME` overrides `~/.dexterm` (tests point it at a temp dir). */
17
+ export function dexHome(env = process.env) {
18
+ return env.DEXTERM_HOME && env.DEXTERM_HOME.trim() ? env.DEXTERM_HOME : join(homedir(), '.dexterm');
19
+ }
20
+ export function configPath(env = process.env) {
21
+ return join(dexHome(env), 'popup.json');
22
+ }
23
+ function num(v, lo, hi, dflt) {
24
+ const n = typeof v === 'number' ? v : NaN;
25
+ return Number.isFinite(n) ? Math.max(lo, Math.min(hi, n)) : dflt;
26
+ }
27
+ /**
28
+ * Validate a parsed config object into a PopupConfig, clamping every field and keeping the defaults for
29
+ * anything missing or out of range. The hotkey must be a short printable accelerator; an unexpected value
30
+ * is ignored so a bad file can never register a nonsense chord or an oversized string.
31
+ */
32
+ export function normalizeConfig(raw, source = 'file') {
33
+ const o = (raw && typeof raw === 'object' ? raw : {});
34
+ const rrain = (o.rain && typeof o.rain === 'object' ? o.rain : {});
35
+ // empty = disabled; otherwise a short accelerator of letters/digits/+ only (no shell metacharacters)
36
+ const chord = (v, dflt) => {
37
+ if (typeof v !== 'string')
38
+ return dflt;
39
+ const h = v.trim();
40
+ return h === '' ? '' : (/^[A-Za-z0-9+]{1,64}$/.test(h) ? h : dflt);
41
+ };
42
+ const hotkey = chord(o.hotkey, DEFAULT_HOTKEY);
43
+ const rdock = (o.dock && typeof o.dock === 'object' ? o.dock : {});
44
+ return {
45
+ hotkey,
46
+ hotkeyClose: chord(o.hotkeyClose, DEFAULT_HOTKEY_CLOSE),
47
+ dock: {
48
+ enabled: typeof rdock.enabled === 'boolean' ? rdock.enabled : DEFAULT_DOCK.enabled,
49
+ width: Math.round(num(rdock.width, 360, 1600, DEFAULT_DOCK.width)),
50
+ follow: typeof rdock.follow === 'boolean' ? rdock.follow : DEFAULT_DOCK.follow,
51
+ followEvery: num(rdock.followEvery, 0.25, 5, DEFAULT_DOCK.followEvery),
52
+ },
53
+ rain: {
54
+ enabled: typeof rrain.enabled === 'boolean' ? rrain.enabled : DEFAULT_RAIN.enabled,
55
+ speedScale: num(rrain.speedScale, 0.1, 5, DEFAULT_RAIN.speedScale),
56
+ density: num(rrain.density, 0, 1, DEFAULT_RAIN.density),
57
+ },
58
+ fpsCap: Math.round(num(o.fpsCap, 15, 120, DEFAULT_CONFIG.fpsCap)),
59
+ source,
60
+ };
61
+ }
62
+ /** Load ~/.dexterm/popup.json, or the defaults when it is missing/unreadable/malformed. Never throws. */
63
+ export function loadConfig(env = process.env) {
64
+ try {
65
+ const text = readFileSync(configPath(env), 'utf8');
66
+ return normalizeConfig(JSON.parse(text), 'file');
67
+ }
68
+ catch {
69
+ return { ...DEFAULT_CONFIG, dock: { ...DEFAULT_DOCK }, rain: { ...DEFAULT_RAIN } };
70
+ }
71
+ }
@@ -0,0 +1,271 @@
1
+ // dexterm popup — dock the chart window beside the terminal (task-1325).
2
+ //
3
+ // macOS only (elsewhere every function degrades to "no docking": the shell's default geometry is used).
4
+ //
5
+ // * Which terminal: TERM_PROGRAM (Apple_Terminal | iTerm.app), else LC_TERMINAL=iTerm2 (survives tmux/ssh).
6
+ // * Which WINDOW: the one whose tab (Terminal.app) / session (iTerm2) has OUR controlling tty, found once and
7
+ // then addressed by window id; the front window if no tty matches (e.g. inside tmux).
8
+ // * One `osascript -l JavaScript` process does the work. It prints the screens first (AppKit, no Apple Events,
9
+ // no permission), then the terminal window's bounds (Apple Events to the terminal app: from inside that
10
+ // terminal this is the app talking to itself). With follow on it stays alive, re-checks every ~0.5 s, prints
11
+ // only when something changed, and exits by itself when its parent (the TUI) is gone.
12
+ // * Placement: right edge of the terminal, same top and height; left edge when the right side has no room;
13
+ // overlapping the screen's right edge (always-on-top) when neither side has room; and for an unknown
14
+ // terminal, the right edge of the main screen (always-on-top), full visible height.
15
+ //
16
+ // Nothing here reads keys, URLs or tickets: the script only carries our tty name and the terminal kind.
17
+ import { spawn, spawnSync } from 'node:child_process';
18
+ /** The terminal app we run in, from the environment it sets. */
19
+ export function detectTerminal(env = process.env) {
20
+ const tp = env.TERM_PROGRAM ?? '';
21
+ if (tp === 'Apple_Terminal')
22
+ return 'terminal';
23
+ if (tp === 'iTerm.app' || env.LC_TERMINAL === 'iTerm2')
24
+ return 'iterm';
25
+ return 'unknown';
26
+ }
27
+ /** Our controlling tty as the terminal apps report it (`/dev/ttys003`), or null. */
28
+ export function controllingTty(pid = process.pid, run = spawnSync) {
29
+ try {
30
+ const r = run('ps', ['-o', 'tty=', '-p', String(pid)], { encoding: 'utf8', timeout: 1500 });
31
+ const t = String(r.stdout ?? '').trim();
32
+ return /^ttys?\d+$/.test(t) ? `/dev/${t}` : null;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ /** The JXA probe. `tty`/`kind` are JSON-encoded into the script (never interpolated raw). */
39
+ export function dockScript(kind, tty, follow, everyS = 0.5) {
40
+ const every = Math.max(0.25, Math.min(5, Number.isFinite(everyS) ? everyS : 0.5));
41
+ return `ObjC.import('AppKit');
42
+ ObjC.bindFunction('getppid', ['int', []]);
43
+ var KIND = ${JSON.stringify(kind)}, TTY = ${JSON.stringify(tty)}, FOLLOW = ${follow ? 'true' : 'false'}, EVERY = ${every};
44
+ var PARENT = $.getppid(), winId = null;
45
+ function rect(b) {
46
+ if (!b) return null;
47
+ if (Array.isArray(b) && b.length === 4) return [b[0], b[1], b[2] - b[0], b[3] - b[1]];
48
+ if (typeof b.width === 'number') return [b.x, b.y, b.width, b.height];
49
+ return null;
50
+ }
51
+ function screens() {
52
+ var out = [], all = $.NSScreen.screens, mainH = all.objectAtIndex(0).frame.size.height;
53
+ for (var i = 0; i < all.count; i++) {
54
+ var s = all.objectAtIndex(i), f = s.frame, v = s.visibleFrame;
55
+ out.push({ frame: [f.origin.x, mainH - f.origin.y - f.size.height, f.size.width, f.size.height],
56
+ visible: [v.origin.x, mainH - v.origin.y - v.size.height, v.size.width, v.size.height], scale: s.backingScaleFactor });
57
+ }
58
+ return out;
59
+ }
60
+ function find(app, tabsOf) {
61
+ if (!app.running()) return null;
62
+ if (winId !== null) { try { return rect(app.windows.byId(winId).bounds()); } catch (e) { winId = null; } }
63
+ var ws = app.windows();
64
+ for (var i = 0; i < ws.length; i++) { if (TTY && tabsOf(ws[i])) { winId = ws[i].id(); return rect(ws[i].bounds()); } }
65
+ return ws.length ? rect(ws[0].bounds()) : null;
66
+ }
67
+ function term() {
68
+ try {
69
+ if (KIND === 'terminal') return find(Application('com.apple.Terminal'), function (w) {
70
+ var t = w.tabs(); for (var j = 0; j < t.length; j++) if (t[j].tty() === TTY) return true; return false; });
71
+ if (KIND === 'iterm') return find(Application('com.googlecode.iterm2'), function (w) {
72
+ var t = w.tabs(); for (var j = 0; j < t.length; j++) { var s = t[j].sessions(); for (var k = 0; k < s.length; k++) if (s[k].tty() === TTY) return true; } return false; });
73
+ } catch (e) { return null; }
74
+ return null;
75
+ }
76
+ function out(s) { $.NSFileHandle.fileHandleWithStandardOutput.writeData($(s + '\\n').dataUsingEncoding($.NSUTF8StringEncoding)); }
77
+ function run() {
78
+ var sc = screens(), last = '';
79
+ out(JSON.stringify({ screens: sc, term: null, pending: KIND !== 'unknown' }));
80
+ for (;;) {
81
+ var s = JSON.stringify({ screens: screens(), term: term(), pending: false });
82
+ if (s !== last) { out(s); last = s; }
83
+ if (!FOLLOW) return '';
84
+ var pp = $.getppid(); if (pp !== PARENT || pp === 1) return '';
85
+ delay(EVERY);
86
+ }
87
+ }
88
+ `;
89
+ }
90
+ const finite = (n) => typeof n === 'number' && Number.isFinite(n);
91
+ const toRect = (a) => Array.isArray(a) && a.length === 4 && a.every(finite) && a[2] > 0 && a[3] > 0 ? { x: a[0], y: a[1], w: a[2], h: a[3] } : null;
92
+ /** Validate one probe line. Anything malformed → null (the caller keeps its last placement). */
93
+ export function parseDockProbe(line) {
94
+ let o;
95
+ try {
96
+ o = JSON.parse(line);
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ if (!o || typeof o !== 'object')
102
+ return null;
103
+ const r = o;
104
+ if (!Array.isArray(r.screens) || r.screens.length === 0 || r.screens.length > 16)
105
+ return null;
106
+ const screens = [];
107
+ for (const s of r.screens) {
108
+ const so = (s ?? {});
109
+ const frame = toRect(so.frame), visible = toRect(so.visible);
110
+ if (!frame || !visible)
111
+ return null;
112
+ screens.push({ frame, visible, scale: finite(so.scale) ? so.scale : 1 });
113
+ }
114
+ return { screens, term: toRect(r.term), pending: r.pending === true };
115
+ }
116
+ const contains = (r, x, y) => x >= r.x && x < r.x + r.w && y >= r.y && y < r.y + r.h;
117
+ /**
118
+ * Where the popup goes. Pure: unit-tested with synthetic screens/terminals.
119
+ * right — the terminal's right edge, same top + height (a narrower window if 360–width pt fit)
120
+ * left — mirrored, when the right side has no room
121
+ * overlap — neither side fits: the screen's right edge, always-on-top, over part of the terminal
122
+ * screen-right — unknown terminal: the main screen's right edge, full visible height, always-on-top
123
+ */
124
+ export function dockRect(term, screens, kind, opts = {}) {
125
+ const want = Math.max(360, Math.round(opts.width ?? 560));
126
+ const gap = Math.max(0, Math.round(opts.gap ?? 4));
127
+ const main = screens[0]?.visible ?? { x: 0, y: 25, w: 1440, h: 875 };
128
+ if (!term) {
129
+ const w = Math.min(want, Math.round(main.w * 0.5));
130
+ return { rect: { x: main.x + main.w - w, y: main.y, w, h: main.h }, mode: 'screen-right', ontop: true, kind };
131
+ }
132
+ const cx = term.x + term.w / 2, cy = term.y + Math.min(term.h / 2, 40);
133
+ const V = (screens.find((s) => contains(s.frame, cx, cy)) ?? screens[0])?.visible ?? main;
134
+ const h = Math.max(200, Math.min(term.h, V.h));
135
+ const y = Math.max(V.y, Math.min(term.y, V.y + V.h - h));
136
+ const right = V.x + V.w - (term.x + term.w) - gap;
137
+ const left = term.x - V.x - gap;
138
+ if (right >= want)
139
+ return { rect: { x: term.x + term.w + gap, y, w: want, h }, mode: 'right', ontop: false, kind };
140
+ if (right >= 360)
141
+ return { rect: { x: term.x + term.w + gap, y, w: right, h }, mode: 'right', ontop: false, kind };
142
+ if (left >= want)
143
+ return { rect: { x: term.x - gap - want, y, w: want, h }, mode: 'left', ontop: false, kind };
144
+ const w = Math.min(want, V.w);
145
+ return { rect: { x: V.x + V.w - w, y, w, h }, mode: 'overlap', ontop: true, kind };
146
+ }
147
+ const same = (a, b) => !!a && a.mode === b.mode && Math.abs(a.rect.x - b.rect.x) < 1 && Math.abs(a.rect.y - b.rect.y) < 1 && Math.abs(a.rect.w - b.rect.w) < 1 && Math.abs(a.rect.h - b.rect.h) < 1;
148
+ /** Probes and follows the terminal window. Every failure degrades to "no docking"; it never throws. */
149
+ export class TerminalDock {
150
+ kind;
151
+ enabled;
152
+ tty;
153
+ opts;
154
+ child = null;
155
+ last = null;
156
+ onExit = () => this.stop();
157
+ constructor(opts = {}) {
158
+ this.opts = opts;
159
+ this.kind = detectTerminal(opts.env ?? process.env);
160
+ this.enabled = (opts.platform ?? process.platform) === 'darwin';
161
+ this.tty = opts.tty !== undefined ? opts.tty : (this.enabled && this.kind !== 'unknown' ? controllingTty() : null);
162
+ }
163
+ get following() { return this.child !== null; }
164
+ run(follow) {
165
+ const args = ['-l', 'JavaScript', '-e', dockScript(this.kind, this.tty, follow, this.opts.followEvery)];
166
+ return this.opts.spawnImpl ? this.opts.spawnImpl('osascript', args) : spawn('osascript', args, { stdio: ['ignore', 'pipe', 'ignore'] });
167
+ }
168
+ place1(p) {
169
+ return dockRect(p.term, p.screens, p.term ? this.kind : (this.kind === 'unknown' ? 'unknown' : this.kind), { width: this.opts.width, gap: this.opts.gap });
170
+ }
171
+ /**
172
+ * One probe, at most `timeoutMs`. Resolves with the terminal-docked placement, or — when the terminal did
173
+ * not answer in time (first-run Automation prompt, app busy) — the screen-right placement from the screens
174
+ * line, or null off macOS / when even that failed.
175
+ */
176
+ place(timeoutMs = 1500) {
177
+ if (!this.enabled)
178
+ return Promise.resolve(null);
179
+ return new Promise((resolve) => {
180
+ let best = null, buf = '', done = false;
181
+ let child;
182
+ try {
183
+ child = this.run(false);
184
+ }
185
+ catch {
186
+ resolve(null);
187
+ return;
188
+ }
189
+ const finish = () => {
190
+ if (done)
191
+ return;
192
+ done = true;
193
+ clearTimeout(t);
194
+ try {
195
+ child.kill('SIGKILL');
196
+ }
197
+ catch { /* gone */ }
198
+ const p = best ? this.place1(best) : null;
199
+ if (p)
200
+ this.last = p;
201
+ resolve(p);
202
+ };
203
+ const t = setTimeout(finish, timeoutMs);
204
+ child.on('error', finish);
205
+ child.on('exit', finish);
206
+ child.stdout?.on('data', (d) => {
207
+ buf += d.toString();
208
+ let i;
209
+ while ((i = buf.indexOf('\n')) >= 0) {
210
+ const p = parseDockProbe(buf.slice(0, i));
211
+ buf = buf.slice(i + 1);
212
+ if (p) {
213
+ best = p;
214
+ if (!p.pending)
215
+ finish();
216
+ }
217
+ }
218
+ });
219
+ });
220
+ }
221
+ /** Follow the terminal: `onPlace` fires on every changed placement. Idempotent; stop() ends it. */
222
+ follow(onPlace) {
223
+ if (!this.enabled || this.child || this.kind === 'unknown')
224
+ return;
225
+ let buf = '';
226
+ try {
227
+ this.child = this.run(true);
228
+ }
229
+ catch {
230
+ this.child = null;
231
+ return;
232
+ }
233
+ const child = this.child;
234
+ child.on('error', () => { if (this.child === child)
235
+ this.child = null; });
236
+ child.on('exit', () => { if (this.child === child)
237
+ this.child = null; });
238
+ child.stdout?.on('data', (d) => {
239
+ buf += d.toString();
240
+ let i;
241
+ while ((i = buf.indexOf('\n')) >= 0) {
242
+ const p = parseDockProbe(buf.slice(0, i));
243
+ buf = buf.slice(i + 1);
244
+ if (!p || p.pending || !p.term)
245
+ continue; // never jump to screen-right while following
246
+ const pl = this.place1(p);
247
+ if (!same(this.last, pl)) {
248
+ this.last = pl;
249
+ try {
250
+ onPlace(pl);
251
+ }
252
+ catch { /* the window may be gone */ }
253
+ }
254
+ }
255
+ });
256
+ process.once('exit', this.onExit);
257
+ }
258
+ stop() {
259
+ const c = this.child;
260
+ this.child = null;
261
+ process.removeListener('exit', this.onExit);
262
+ if (c) {
263
+ try {
264
+ c.kill('SIGTERM');
265
+ }
266
+ catch { /* gone */ }
267
+ }
268
+ }
269
+ /** The child's pid while following (for the CPU measurement). */
270
+ get pid() { return this.child?.pid; }
271
+ }