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,319 @@
1
+ // dexterm — PAPER trade ticket state machine (task-1310 T0, SPEC §6.1). Pure and TTY-free so the
2
+ // ticket maths and the keypress confirm gate are unit-testable. The interactive loop and the live
3
+ // binding drive this reducer and perform the side effects (quote fetch, executor.execute).
4
+ //
5
+ // Safety: this file builds an *order* and reads a *quote*. It never signs, holds a key, or sends a
6
+ // transaction, and it imports no wallet/signing library (SPEC §6.0). Every ticket is labelled PAPER
7
+ // and the confirm gate is ON by default: arming needs a deliberate keypress, and sizes above a
8
+ // configurable threshold must be typed back (SPEC §6.1 step 3).
9
+ import { solToLamports, LAMPORTS_PER_SOL } from '../../data/src/index.js';
10
+ /** Default slippage and the SOL notional above which the confirm requires a typed-back amount. */
11
+ export const DEFAULT_SLIPPAGE_BPS = 50;
12
+ export const DEFAULT_TYPEBACK_THRESHOLD_SOL = 1;
13
+ export const MAX_SLIPPAGE_BPS = 5000;
14
+ export function openTicket(seed) {
15
+ return {
16
+ mint: seed.mint,
17
+ symbol: seed.symbol,
18
+ decimals: typeof seed.decimals === 'number' && Number.isFinite(seed.decimals) && seed.decimals > 0 ? seed.decimals : 9,
19
+ side: seed.side,
20
+ sizeUnit: seed.side === 'buy' ? 'sol' : 'pct',
21
+ sizeInput: '',
22
+ slippageInput: String(seed.slippageBps ?? DEFAULT_SLIPPAGE_BPS),
23
+ field: 'size',
24
+ phase: 'editing',
25
+ mode: seed.mode ?? 'paper',
26
+ gateOn: seed.gateOn ?? true,
27
+ typebackRequired: false,
28
+ typeback: '',
29
+ message: null,
30
+ };
31
+ }
32
+ function parseNum(s) {
33
+ const n = Number(s);
34
+ return Number.isFinite(n) ? n : NaN;
35
+ }
36
+ export function slippageBps(ts) {
37
+ if (ts.slippageInput.trim() === '')
38
+ return DEFAULT_SLIPPAGE_BPS;
39
+ const n = parseNum(ts.slippageInput);
40
+ if (!Number.isFinite(n) || n < 0)
41
+ return DEFAULT_SLIPPAGE_BPS;
42
+ return Math.min(MAX_SLIPPAGE_BPS, Math.round(n));
43
+ }
44
+ /** The raw amount an order would spend, given the live context; 0n when the size is empty/invalid. */
45
+ export function orderAmountRaw(ts, ctx) {
46
+ const n = parseNum(ts.sizeInput);
47
+ if (!Number.isFinite(n) || n <= 0)
48
+ return 0n;
49
+ if (ts.side === 'buy') {
50
+ if (ts.sizeUnit === 'sol')
51
+ return solToLamports(n);
52
+ // % of free SOL balance
53
+ const pct = Math.min(100, n);
54
+ return (ctx.balanceLamports * BigInt(Math.round(pct * 100))) / 10000n;
55
+ }
56
+ // sell: % of the held position
57
+ const pct = Math.min(100, n);
58
+ return (ctx.positionTokensRaw * BigInt(Math.round(pct * 100))) / 10000n;
59
+ }
60
+ /** Build the order the ticket represents (amount already in raw base units). */
61
+ export function toOrder(ts, ctx) {
62
+ return { mint: ts.mint, symbol: ts.symbol, side: ts.side, decimals: ts.decimals, amountRaw: orderAmountRaw(ts, ctx), slippageBps: slippageBps(ts) };
63
+ }
64
+ /** Is the current size a fillable order (positive amount, and for a sell there are tokens to sell)? */
65
+ export function orderValid(ts, ctx) {
66
+ const amt = orderAmountRaw(ts, ctx);
67
+ if (amt <= 0n)
68
+ return false;
69
+ if (ts.side === 'sell' && ctx.positionTokensRaw <= 0n)
70
+ return false;
71
+ return true;
72
+ }
73
+ const DIGIT = /[0-9.]/;
74
+ function editField(ts, key) {
75
+ const cur = ts.field === 'size' ? ts.sizeInput : ts.slippageInput;
76
+ if (key.name === 'backspace') {
77
+ const next = cur.slice(0, -1);
78
+ if (ts.field === 'size')
79
+ ts.sizeInput = next;
80
+ else
81
+ ts.slippageInput = next;
82
+ return true;
83
+ }
84
+ if ((key.name === 'char') && key.ch && DIGIT.test(key.ch) && !key.ctrl && !key.alt) {
85
+ // one decimal point only; slippage is integer bps
86
+ if (key.ch === '.' && (ts.field === 'slippage' || cur.includes('.')))
87
+ return true;
88
+ const next = (cur + key.ch).slice(0, 12);
89
+ if (ts.field === 'size')
90
+ ts.sizeInput = next;
91
+ else
92
+ ts.slippageInput = next;
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+ /**
98
+ * Advance the ticket by one key. Returns the effect the caller performs:
99
+ * - 'changed' : the order inputs changed → re-fetch the warm quote;
100
+ * - 'confirm' : the gate was satisfied → the caller must execute the order now;
101
+ * - 'close' : leave the ticket (back to the token screen);
102
+ * - 'none' : nothing to do.
103
+ * A stray key during the confirm step can NEVER confirm — it cancels back to editing.
104
+ */
105
+ export function ticketKey(ts, key, ctx) {
106
+ ts.message = null;
107
+ if (ts.phase === 'filling' || ts.phase === 'signing')
108
+ return 'none'; // in-flight: ignore input
109
+ if (ts.phase === 'planning')
110
+ return 'none'; // the signer is building; esc is handled by the binding (cancel)
111
+ if (ts.phase === 'filled' || ts.phase === 'error' || ts.phase === 'refused') {
112
+ // a receipt is showing: esc closes, any other key starts a fresh ticket for the same mint (same mode)
113
+ if (key.name === 'esc')
114
+ return 'close';
115
+ const side = ts.side;
116
+ Object.assign(ts, openTicket({ mint: ts.mint, symbol: ts.symbol, decimals: ts.decimals, side, gateOn: ts.gateOn, slippageBps: slippageBps(ts), mode: ts.mode }));
117
+ return 'changed';
118
+ }
119
+ // ---- LIVE plan screen (task-1328): the keypress gate for a REAL trade. Y / ⏎ confirms (with the
120
+ // typed-back size above the threshold, exactly like paper); esc returns to editing; ANY other key
121
+ // cancels — a stray key can never confirm, and never navigates mid-plan.
122
+ if (ts.phase === 'plan') {
123
+ if (key.name === 'esc') {
124
+ ts.phase = 'editing';
125
+ ts.typeback = '';
126
+ return 'drop-plan';
127
+ }
128
+ if (ts.typebackRequired && key.name === 'char' && key.ch && DIGIT.test(key.ch)) {
129
+ if (key.ch === '.' && ts.typeback.includes('.'))
130
+ return 'none';
131
+ ts.typeback = (ts.typeback + key.ch).slice(0, 12);
132
+ return 'none';
133
+ }
134
+ if (ts.typebackRequired && key.name === 'backspace') {
135
+ ts.typeback = ts.typeback.slice(0, -1);
136
+ return 'none';
137
+ }
138
+ const confirmKey = (key.name === 'char' && (key.ch === 'y' || key.ch === 'Y')) || key.name === 'enter';
139
+ if (confirmKey) {
140
+ if (ts.typebackRequired) {
141
+ const want = parseNum(ts.sizeInput);
142
+ const got = parseNum(ts.typeback);
143
+ if (!(Number.isFinite(got) && Math.abs(got - want) < 1e-9)) {
144
+ ts.message = `type the size (${ts.sizeInput}) to confirm this LIVE ${ts.side}`;
145
+ return 'none';
146
+ }
147
+ }
148
+ ts.phase = 'signing';
149
+ return 'confirm-live';
150
+ }
151
+ ts.phase = 'editing';
152
+ ts.typeback = '';
153
+ ts.message = 'plan cancelled';
154
+ return 'drop-plan';
155
+ }
156
+ if (ts.phase === 'confirm') {
157
+ if (key.name === 'esc') {
158
+ ts.phase = 'editing';
159
+ ts.typeback = '';
160
+ return 'none';
161
+ }
162
+ if (ts.typebackRequired && key.name === 'char' && key.ch && DIGIT.test(key.ch)) {
163
+ if (key.ch === '.' && ts.typeback.includes('.'))
164
+ return 'none';
165
+ ts.typeback = (ts.typeback + key.ch).slice(0, 12);
166
+ return 'none';
167
+ }
168
+ if (ts.typebackRequired && key.name === 'backspace') {
169
+ ts.typeback = ts.typeback.slice(0, -1);
170
+ return 'none';
171
+ }
172
+ const confirmKey = (key.name === 'char' && (key.ch === 'y' || key.ch === 'Y')) || key.name === 'enter';
173
+ if (confirmKey) {
174
+ if (ts.typebackRequired) {
175
+ const want = parseNum(ts.sizeInput);
176
+ const got = parseNum(ts.typeback);
177
+ if (!(Number.isFinite(got) && Math.abs(got - want) < 1e-9)) {
178
+ ts.message = `type the size (${ts.sizeInput}) to confirm this PAPER ${ts.side}`;
179
+ return 'none';
180
+ }
181
+ }
182
+ ts.phase = 'filling';
183
+ return 'confirm';
184
+ }
185
+ // any other key cancels the arm (a stray key never confirms, and never navigates mid-confirm)
186
+ ts.phase = 'editing';
187
+ ts.typeback = '';
188
+ ts.message = 'confirm cancelled';
189
+ return 'none';
190
+ }
191
+ // ---- editing
192
+ if (key.name === 'esc')
193
+ return 'close';
194
+ if (key.name === 'char' && (key.ch === 'L' || key.ch === 'l') && !key.ctrl && !key.alt)
195
+ return 'toggle-live';
196
+ if (key.name === 'tab') {
197
+ ts.field = ts.field === 'size' ? 'slippage' : 'size';
198
+ return 'none';
199
+ }
200
+ if (key.name === 'left' || key.name === 'right') {
201
+ ts.side = ts.side === 'buy' ? 'sell' : 'buy';
202
+ ts.sizeUnit = ts.side === 'buy' ? ts.sizeUnit : 'pct';
203
+ ts.typeback = '';
204
+ return 'changed';
205
+ }
206
+ if (key.name === 'char' && key.ch === 'u' && !key.ctrl) {
207
+ if (ts.side === 'buy') {
208
+ ts.sizeUnit = ts.sizeUnit === 'sol' ? 'pct' : 'sol';
209
+ return 'changed';
210
+ }
211
+ return 'none'; // sell is always % of the position
212
+ }
213
+ if (key.name === 'enter') {
214
+ if (!orderValid(ts, ctx)) {
215
+ ts.message = ts.side === 'sell' && ctx.positionTokensRaw <= 0n ? `no ${ts.symbol} position to sell` : 'enter a size first';
216
+ return 'none';
217
+ }
218
+ ts.typebackRequired = ts.gateOn && ctx.notionalSol > ctx.typebackThresholdSol;
219
+ ts.typeback = '';
220
+ if (ts.mode === 'live') {
221
+ ts.phase = 'planning';
222
+ return 'plan';
223
+ }
224
+ ts.phase = 'confirm';
225
+ return 'none';
226
+ }
227
+ // task-1368: f on a LIVE buy = size the order to the signer's cap (the binding knows the caps + SOL/USD; the
228
+ // reducer only names the effect). Inert on PAPER (no caps) and on a sell (sized in % of the position).
229
+ if (key.name === 'char' && (key.ch === 'f' || key.ch === 'F') && !key.ctrl && !key.alt) {
230
+ return ts.mode === 'live' && ts.side === 'buy' ? 'fit-cap' : 'none';
231
+ }
232
+ if (editField(ts, key))
233
+ return 'changed';
234
+ return 'none';
235
+ }
236
+ // --------------------------------------------------------------------------- task-1368 preflight + hints (pure)
237
+ /**
238
+ * Fees + token-account rent the signer's simulated SOL-out may add on top of the size of a BUY (a tx fee, a priority
239
+ * fee and one associated-token-account rent of 0.00203928 SOL when the wallet has never held the token). The
240
+ * signer prices the LARGER of the planned SOL leg and the simulated SOL out, so a size that is exactly the cap in
241
+ * USD is refused once rent lands on top; `fitSizeSol` leaves this much room.
242
+ */
243
+ export const CAP_FIT_OVERHEAD_SOL = 0.0025;
244
+ const usd = (n) => `$${n.toFixed(2).replace(/\.00$/, '')}`;
245
+ /**
246
+ * Size vs the signer's caps BEFORE ⏎ (task-1368 item b): the USD value of the size at the signer's own SOL/USD, the
247
+ * per-trade cap and what is left of the daily cap, a red verdict when the size is over, and the size that fits.
248
+ * Pure: the signer still decides at precheck/confirm — this only stops the user from building a plan that will be
249
+ * refused for a reason they could not see.
250
+ */
251
+ export function capPreflight(i) {
252
+ const sizeSol = Number.isFinite(i.sizeSol) && i.sizeSol > 0 ? i.sizeSol : 0;
253
+ const price = i.solUsd !== null && Number.isFinite(i.solUsd) && i.solUsd > 0 ? i.solUsd : null;
254
+ const perTradeUsd = Math.max(0, i.perTradeUsd);
255
+ const dailyRemainingUsd = Math.max(0, i.dailyUsd - Math.max(0, i.spentTodayUsd));
256
+ const capUsd = Math.min(perTradeUsd, dailyRemainingUsd);
257
+ const sizeUsd = price !== null && sizeSol > 0 ? sizeSol * price : null;
258
+ const over = sizeUsd !== null && sizeUsd > capUsd + 1e-9;
259
+ const binding = !over ? null : dailyRemainingUsd < perTradeUsd ? 'daily' : 'per-trade';
260
+ const overhead = Math.max(0, i.overheadSol ?? CAP_FIT_OVERHEAD_SOL);
261
+ let fitSizeSol = null;
262
+ if (price !== null && capUsd > 0) {
263
+ const fit = Math.floor((capUsd / price - overhead) * 1000) / 1000;
264
+ fitSizeSol = fit > 0 ? fit : null;
265
+ }
266
+ const capText = `cap ${usd(perTradeUsd)}/trade · ${usd(dailyRemainingUsd)} left of ${usd(Math.max(0, i.dailyUsd))} today`;
267
+ let line;
268
+ if (sizeSol <= 0)
269
+ line = `${capText} · enter a size`;
270
+ else if (price === null)
271
+ line = `${sizeSol} SOL = $? (the signer has no SOL/USD yet; it will refuse an unpriced trade on mainnet) · ${capText}`;
272
+ else if (over) {
273
+ const which = binding === 'daily' ? `${usd(dailyRemainingUsd)} left today` : `${usd(perTradeUsd)} cap`;
274
+ line = `${sizeSol} SOL = ${usd(sizeUsd)} > ${which}: ${fitSizeSol !== null ? `use ≤ ${fitSizeSol} SOL (f fits it; fees + token-account rent counted)` : 'nothing fits — the cap is spent for today'}`;
275
+ }
276
+ else
277
+ line = `${sizeSol} SOL ≈ ${usd(sizeUsd)} at SOL ${usd(price)} · ${capText}`;
278
+ return { sizeSol, sizeUsd, perTradeUsd, dailyRemainingUsd, capUsd, over, fitSizeSol, binding, line };
279
+ }
280
+ const WSOL = 'So11111111111111111111111111111111111111112';
281
+ /**
282
+ * task-1368 item c: a DEVNET signer + a Jupiter swap = a transaction built on MAINNET state (there is no Jupiter
283
+ * devnet): it can never simulate or fill on devnet, so say so BEFORE ⏎ and name the two ways out. Null for the
284
+ * probe (built for devnet on purpose), for wrapped SOL itself, and on mainnet.
285
+ */
286
+ export function devnetMintWarning(a) {
287
+ if (a.network !== 'devnet' || a.probe || a.mint === WSOL)
288
+ return null;
289
+ const caps = `caps ${usd(a.perTradeUsd ?? 5)}/${usd(a.dailyUsd ?? 20)}`;
290
+ return `DEVNET cannot fill mainnet tokens: paper trade (L) or restart with --mainnet (real funds, ${caps})`;
291
+ }
292
+ const MAINNET_STATE_ERR = /invalid transaction|blockhash|account.*not found|not found|could not find|address (table|lookup)|AccountNotFound|ProgramAccountNotFound|unknown program|InvalidAccountData/i;
293
+ /**
294
+ * task-1368 item f: a plain-English hint under a failed simulation when the failure pattern is the devnet-vs-mainnet
295
+ * mismatch (a mainnet route simulated on a devnet node: accounts, lookup tables or blockhash not found). Null when
296
+ * the simulation passed, was not run, or failed for another reason (the full error is still shown).
297
+ */
298
+ export function simFailureHint(p, caps) {
299
+ if (p.kind !== 'swap' || !p.sim.ran || p.sim.ok)
300
+ return null;
301
+ const err = p.sim.error ?? '';
302
+ const mainnetState = MAINNET_STATE_ERR.test(err);
303
+ if (p.network === 'devnet' && (mainnetState || p.mintOnNetwork === false)) {
304
+ const mint = p.mintOnNetwork === false ? ' (the mint does not exist on devnet)' : '';
305
+ return `devnet-vs-mainnet wall${mint}: a Jupiter swap is built on MAINNET state (accounts, lookup tables, blockhash) that a devnet node does not have. Paper trade (L) or restart with --mainnet (real funds, caps ${usd(caps?.perTradeUsd ?? 5)}/${usd(caps?.dailyUsd ?? 20)}).`;
306
+ }
307
+ if (mainnetState)
308
+ return 'the node could not find the route\'s accounts or blockhash: a stale plan or the wrong RPC network — rebuild with ⏎, or check --trade-rpc';
309
+ return null;
310
+ }
311
+ /** SOL notional of a buy order sized in SOL/%, used to gate the typed-back step without a quote. */
312
+ export function buyNotionalSol(ts, balanceLamports) {
313
+ const n = parseNum(ts.sizeInput);
314
+ if (!Number.isFinite(n) || n <= 0)
315
+ return 0;
316
+ if (ts.sizeUnit === 'sol')
317
+ return n;
318
+ return (Number(balanceLamports) / 1e9) * Math.min(100, n) / 100;
319
+ }
@@ -0,0 +1,294 @@
1
+ // task-1397 — `dexterm update` and the quiet startup notice.
2
+ //
3
+ // The check is two anonymous GETs: the npm registry's `latest` for dexterm-cli and GitHub's latest published
4
+ // release of dexterm-xyz/dexterm. Both carry only fixed headers (no version, no OS, no ids, no cookies); nothing
5
+ // about the user or the machine is sent beyond the IP any request has. No telemetry.
6
+ //
7
+ // Startup: a live interactive run reads ~/.dexterm/update-check.json (no network) and shows one toast when a
8
+ // newer version was seen; at most once a day it refreshes that file in the background (never blocks, never in
9
+ // --fixtures/--replay, never with DEXTERM_OFFLINE/CI). Off switches: `dexterm update --off`,
10
+ // DEXTERM_NO_UPDATE_CHECK=1 or NO_UPDATE_NOTIFIER=1. `dexterm update` itself never upgrades anything: it prints
11
+ // the exact command for how this copy was installed (npm, Homebrew, release tarball, git checkout).
12
+ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs';
13
+ import { dirname, join, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { dextermHome } from "./boot-state.js";
16
+ import { VERSION } from "./version.js";
17
+ export const NPM_PACKAGE = 'dexterm-cli';
18
+ export const GITHUB_REPO = 'dexterm-xyz/dexterm';
19
+ export const NPM_LATEST_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
20
+ export const GITHUB_LATEST_URL = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
21
+ export const RELEASES_URL = `https://github.com/${GITHUB_REPO}/releases`;
22
+ /** Fixed request headers: the same bytes for every user (GitHub's API requires a user-agent). */
23
+ export const REQUEST_HEADERS = Object.freeze({ accept: 'application/json', 'user-agent': 'dexterm' });
24
+ export const CHECK_EVERY_MS = 24 * 60 * 60 * 1000;
25
+ export const REQUEST_TIMEOUT_MS = 4000;
26
+ export const UPDATE_FILE = 'update-check.json';
27
+ export const UPDATE_USAGE = `usage: dexterm update check npm + GitHub now and print the upgrade command for this install
28
+ dexterm update --off turn the once-a-day startup check off (dexterm update still works)
29
+ dexterm update --on turn it back on`;
30
+ /** `1.2.3`, `v1.2.3`, `1.2.3-rc.1` → parts; anything else → null (never guessed). */
31
+ export function parseVersion(s) {
32
+ if (typeof s !== 'string')
33
+ return null;
34
+ const m = /^v?(\d{1,6})\.(\d{1,6})\.(\d{1,6})(?:-([0-9A-Za-z.-]{1,64}))?(?:\+[0-9A-Za-z.-]{1,64})?$/.exec(s.trim());
35
+ if (!m)
36
+ return null;
37
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre: m[4] ? m[4].split('.') : [] };
38
+ }
39
+ /** Semver precedence: < 0 when a is older than b. Unparseable versions compare equal (no notice on junk). */
40
+ export function compareVersions(a, b) {
41
+ const x = parseVersion(a), y = parseVersion(b);
42
+ if (!x || !y)
43
+ return 0;
44
+ for (const k of ['major', 'minor', 'patch'])
45
+ if (x[k] !== y[k])
46
+ return x[k] - y[k];
47
+ if (!x.pre.length || !y.pre.length)
48
+ return y.pre.length - x.pre.length; // a release outranks its prereleases
49
+ for (let i = 0; i < Math.max(x.pre.length, y.pre.length); i++) {
50
+ const p = x.pre[i], q = y.pre[i];
51
+ if (p === undefined)
52
+ return -1;
53
+ if (q === undefined)
54
+ return 1;
55
+ const pn = /^\d+$/.test(p), qn = /^\d+$/.test(q);
56
+ if (pn && qn) {
57
+ if (Number(p) !== Number(q))
58
+ return Number(p) - Number(q);
59
+ }
60
+ else if (pn !== qn)
61
+ return pn ? -1 : 1;
62
+ else if (p !== q)
63
+ return p < q ? -1 : 1;
64
+ }
65
+ return 0;
66
+ }
67
+ const bare = (v) => v.trim().replace(/^v/, '');
68
+ /** This module's real path (symlinks resolved): …/node_modules/dexterm-cli/dist/…, …/Cellar/dexterm/…, or a source tree. */
69
+ export function selfPath() {
70
+ const p = fileURLToPath(import.meta.url);
71
+ try {
72
+ return realpathSync(p);
73
+ }
74
+ catch {
75
+ return p;
76
+ }
77
+ }
78
+ /** The source tree's root for a tarball/checkout install (this file is packages/cli/src/update.ts). */
79
+ export function sourceRoot(path = selfPath()) {
80
+ return resolve(dirname(path), '..', '..', '..');
81
+ }
82
+ export function detectInstall(path = selfPath(), exists = existsSync) {
83
+ const p = path.replace(/\\/g, '/');
84
+ if (p.includes(`/node_modules/${NPM_PACKAGE}/`))
85
+ return 'npm';
86
+ if (/\/Cellar\/dexterm\/|\/opt\/homebrew\/|\/home\/linuxbrew\/\.linuxbrew\//.test(p))
87
+ return 'brew';
88
+ return exists(join(sourceRoot(path), '.git')) ? 'checkout' : 'tarball';
89
+ }
90
+ export const INSTALL_LABEL = {
91
+ npm: `npm (${NPM_PACKAGE})`, brew: 'Homebrew (dexterm-xyz/dexterm tap)', tarball: 'release tarball (install.sh)', checkout: 'git checkout',
92
+ };
93
+ /** The exact commands that move this install to `latest`. Nothing here is ever run by dexterm. */
94
+ export function upgradeCommands(kind, latest, opts = {}) {
95
+ const v = bare(latest);
96
+ if (kind === 'npm')
97
+ return [`npm i -g ${NPM_PACKAGE}@${v}`];
98
+ if (kind === 'brew')
99
+ return ['brew update', 'brew upgrade dexterm'];
100
+ if (kind === 'checkout')
101
+ return [`git -C ${shellQuote(opts.root ?? sourceRoot())} pull --ff-only`];
102
+ const base = `${RELEASES_URL}/download/v${v}`;
103
+ const file = `dexterm-${v}-src.tar.gz`;
104
+ const sum = (opts.platform ?? process.platform) === 'darwin' ? 'shasum -a 256 -c' : 'sha256sum -c';
105
+ return [
106
+ `curl -fL -O ${base}/${file} -O ${base}/SHA256SUMS`,
107
+ `grep ' ${file}$' SHA256SUMS | ${sum}`,
108
+ `tar -xzf ${file} && cd dexterm-${v} && ./install.sh`,
109
+ ];
110
+ }
111
+ function shellQuote(s) {
112
+ return /^[\w@%+=:,./-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
113
+ }
114
+ async function getJson(fetchFn, url, timeoutMs, cancel) {
115
+ const timeout = AbortSignal.timeout(timeoutMs);
116
+ const res = await fetchFn(url, { headers: { ...REQUEST_HEADERS }, signal: cancel ? AbortSignal.any([cancel, timeout]) : timeout, redirect: 'follow' });
117
+ if (!res.ok)
118
+ throw new Error(`HTTP ${res.status}`);
119
+ return res.json();
120
+ }
121
+ function why(e) {
122
+ const err = e;
123
+ if (err?.name === 'TimeoutError' || err?.name === 'AbortError')
124
+ return 'timed out';
125
+ return String(err?.message ?? e).slice(0, 120);
126
+ }
127
+ /** Ask both sources. Never throws; each side reports its own error. */
128
+ export async function fetchLatest(fetchFn = fetch, timeoutMs = REQUEST_TIMEOUT_MS, cancel) {
129
+ const npm = getJson(fetchFn, NPM_LATEST_URL, timeoutMs, cancel).then((b) => {
130
+ const v = b?.version;
131
+ return parseVersion(v) ? { ok: true, version: bare(v), url: `https://www.npmjs.com/package/${NPM_PACKAGE}` } : { ok: false, error: 'no version in the registry reply' };
132
+ }, (e) => ({ ok: false, error: why(e) }));
133
+ const github = getJson(fetchFn, GITHUB_LATEST_URL, timeoutMs, cancel).then((b) => {
134
+ const r = b;
135
+ if (r?.draft || r?.prerelease || !parseVersion(r?.tag_name))
136
+ return { ok: false, error: 'no published release in the reply' };
137
+ const tag = r.tag_name;
138
+ return { ok: true, version: bare(tag), url: `${RELEASES_URL}/tag/${tag}` };
139
+ }, (e) => ({ ok: false, error: why(e) }));
140
+ return { npm: await npm, github: await github };
141
+ }
142
+ /** The newest stable version either source reported, or null. */
143
+ export function newestOf(r) {
144
+ let best = null;
145
+ for (const s of [r.npm, r.github]) {
146
+ if (!s.ok || parseVersion(s.version)?.pre.length)
147
+ continue;
148
+ if (best === null || compareVersions(s.version, best) > 0)
149
+ best = s.version;
150
+ }
151
+ return best;
152
+ }
153
+ /** The version this install's channel can actually move to: npm → the registry; the rest → the GitHub release. */
154
+ export function targetFor(kind, r) {
155
+ const primary = kind === 'npm' ? r.npm : r.github;
156
+ return primary.ok ? primary.version : newestOf(r);
157
+ }
158
+ export function updateStatePath(env) {
159
+ return join(dextermHome(env), UPDATE_FILE);
160
+ }
161
+ export function readUpdateState(env) {
162
+ try {
163
+ const j = JSON.parse(readFileSync(updateStatePath(env), 'utf8'));
164
+ return {
165
+ enabled: j.enabled !== false,
166
+ checkedAt: typeof j.checkedAt === 'number' && Number.isFinite(j.checkedAt) ? j.checkedAt : null,
167
+ latest: typeof j.latest === 'string' && parseVersion(j.latest) ? bare(j.latest) : null,
168
+ };
169
+ }
170
+ catch {
171
+ return { enabled: true, checkedAt: null, latest: null };
172
+ }
173
+ }
174
+ export function writeUpdateState(env, s) {
175
+ const p = updateStatePath(env);
176
+ mkdirSync(dirname(p), { recursive: true, mode: 0o700 });
177
+ const tmp = `${p}.${process.pid}.tmp`;
178
+ writeFileSync(tmp, JSON.stringify(s, null, 2) + '\n', { mode: 0o600 });
179
+ renameSync(tmp, p);
180
+ }
181
+ const truthy = (v) => !!v && v !== '0' && v.toLowerCase() !== 'false';
182
+ /** Why the startup check is off, or null when it may run. `dexterm update` itself ignores all but DEXTERM_OFFLINE. */
183
+ export function startupCheckOff(env, s) {
184
+ if (truthy(env.DEXTERM_NO_UPDATE_CHECK))
185
+ return 'DEXTERM_NO_UPDATE_CHECK is set';
186
+ if (truthy(env.NO_UPDATE_NOTIFIER))
187
+ return 'NO_UPDATE_NOTIFIER is set';
188
+ if (truthy(env.DEXTERM_OFFLINE))
189
+ return 'DEXTERM_OFFLINE is set';
190
+ if (truthy(env.CI))
191
+ return 'CI is set';
192
+ if (!s.enabled)
193
+ return 'turned off with dexterm update --off';
194
+ return null;
195
+ }
196
+ /** The one-line notice for a cached result, or null. */
197
+ export function noticeFor(current, latest) {
198
+ if (!latest || parseVersion(latest)?.pre.length || compareVersions(latest, current) <= 0)
199
+ return null;
200
+ return `dexterm ${bare(latest)} is available (you have ${bare(current)}) · run dexterm update`;
201
+ }
202
+ /**
203
+ * Startup: the cached notice now, plus a background refresh when the last check is older than a day.
204
+ * The caller decides where the notice goes (the interactive loop shows it as a toast after the boot).
205
+ */
206
+ export function startupCheck(env, opts = {}) {
207
+ const s = readUpdateState(env);
208
+ const none = () => { };
209
+ if (startupCheckOff(env, s))
210
+ return { notice: null, refresh: null, cancel: none };
211
+ const current = opts.current ?? VERSION;
212
+ const now = opts.now ?? Date.now();
213
+ const notice = noticeFor(current, s.latest);
214
+ if (s.checkedAt !== null && now - s.checkedAt >= 0 && now - s.checkedAt < CHECK_EVERY_MS)
215
+ return { notice, refresh: null, cancel: none };
216
+ const ctl = new AbortController();
217
+ const refresh = fetchLatest(opts.fetchFn, REQUEST_TIMEOUT_MS, ctl.signal).then((r) => {
218
+ if (ctl.signal.aborted || (!r.npm.ok && !r.github.ok))
219
+ return null; // quit / offline / blocked: try again next launch
220
+ const latest = newestOf(r);
221
+ try {
222
+ writeUpdateState(env, { ...s, checkedAt: now, latest });
223
+ }
224
+ catch { /* read-only home: no cache */ }
225
+ // only a notice the cache did not already produce
226
+ const n = noticeFor(current, latest);
227
+ return n && n !== notice ? n : null;
228
+ }).catch(() => null);
229
+ return { notice, refresh, cancel: () => ctl.abort() };
230
+ }
231
+ export async function updateCommand(argv, io, deps = {}) {
232
+ const [arg, ...rest] = argv;
233
+ const env = io.env;
234
+ if (arg === '-h' || arg === '--help' || arg === 'help') {
235
+ io.stdout.write(UPDATE_USAGE + '\n');
236
+ return 0;
237
+ }
238
+ if (rest.length || (arg !== undefined && arg !== '--off' && arg !== '--on')) {
239
+ io.stderr.write(`dexterm update: unknown argument ${JSON.stringify(String(rest[0] ?? arg).slice(0, 20))}\n${UPDATE_USAGE}\n`);
240
+ return 2;
241
+ }
242
+ const state = readUpdateState(env);
243
+ if (arg === '--off' || arg === '--on') {
244
+ try {
245
+ writeUpdateState(env, { ...state, enabled: arg === '--on' });
246
+ }
247
+ catch (e) {
248
+ io.stderr.write(`dexterm update: ${e.message}\n`);
249
+ return 1;
250
+ }
251
+ io.stdout.write(arg === '--off'
252
+ ? `startup update check: off (saved to ${updateStatePath(env)}). dexterm update still checks when you run it.\n`
253
+ : `startup update check: on (at most once a day; saved to ${updateStatePath(env)})\n`);
254
+ return 0;
255
+ }
256
+ if (truthy(env.DEXTERM_OFFLINE)) {
257
+ io.stderr.write('dexterm update: DEXTERM_OFFLINE is set; the check needs the network\n');
258
+ return 2;
259
+ }
260
+ const current = deps.current ?? VERSION;
261
+ const kind = deps.install ?? detectInstall();
262
+ const r = await fetchLatest(deps.fetchFn);
263
+ const line = (label, s) => ` ${label.padEnd(20)} ${s.ok ? `${s.version.padEnd(10)} ${s.url}` : `unavailable (${s.error})`}\n`;
264
+ io.stdout.write(`dexterm ${current} · installed via ${INSTALL_LABEL[kind]}\n`);
265
+ io.stdout.write(line(`npm ${NPM_PACKAGE}`, r.npm));
266
+ io.stdout.write(line('GitHub release', r.github));
267
+ if (!r.npm.ok && !r.github.ok) {
268
+ io.stderr.write(`dexterm update: could not reach npm or GitHub; check your connection, or see ${RELEASES_URL}\n`);
269
+ return 1;
270
+ }
271
+ const latest = newestOf(r);
272
+ try {
273
+ writeUpdateState(env, { ...state, checkedAt: deps.now ?? Date.now(), latest });
274
+ }
275
+ catch { /* read-only home */ }
276
+ const target = targetFor(kind, r);
277
+ if (!target || compareVersions(target, current) <= 0) {
278
+ const ahead = latest && compareVersions(latest, current) > 0;
279
+ io.stdout.write(ahead
280
+ ? `You are on the newest version ${kind === 'npm' ? 'npm has' : 'the GitHub release has'} (${current}); ${latest} is out on the other channel and reaches yours shortly.\n`
281
+ : `You are up to date (${current}).\n`);
282
+ }
283
+ else {
284
+ io.stdout.write(`\ndexterm ${target} is available. Upgrade with:\n\n`);
285
+ for (const c of upgradeCommands(kind, target, { platform: deps.platform, root: deps.root }))
286
+ io.stdout.write(` ${c}\n`);
287
+ io.stdout.write('\n');
288
+ if (kind === 'tarball')
289
+ io.stdout.write(`Verify the download first: ${RELEASES_URL}/tag/v${target} (docs/verify-your-download.md). Release notes: CHANGELOG.md.\n`);
290
+ }
291
+ const off = startupCheckOff(env, readUpdateState(env));
292
+ io.stdout.write(`startup check: ${off ? `off (${off})` : 'on, at most once a day; dexterm update --off or DEXTERM_NO_UPDATE_CHECK=1 turns it off'}. No telemetry: two anonymous GETs (registry.npmjs.org, api.github.com).\n`);
293
+ return 0;
294
+ }
@@ -0,0 +1,3 @@
1
+ // The one place the app's version lives (task-1397). --version, the help banner and `dexterm update` read it;
2
+ // cli.test.ts keeps it equal to packages/cli/package.json (release.yml refuses a tag that differs).
3
+ export const VERSION = '0.1.2';