dexterm-cli 0.0.0-stage → 0.1.1

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 (349) 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 +379 -0
  4. package/dist/packages/agents/src/executor.js +177 -0
  5. package/dist/packages/agents/src/gate.js +176 -0
  6. package/dist/packages/agents/src/identity.js +179 -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 +114 -0
  12. package/dist/packages/agents/src/tools.js +69 -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 +67 -0
  18. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -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 +303 -0
  26. package/dist/packages/cli/src/agent-roster.js +26 -0
  27. package/dist/packages/cli/src/analyst/backend.js +526 -0
  28. package/dist/packages/cli/src/analyst/results.js +38 -0
  29. package/dist/packages/cli/src/analyst/session.js +319 -0
  30. package/dist/packages/cli/src/audit.js +51 -0
  31. package/dist/packages/cli/src/backtest-cmd.js +149 -0
  32. package/dist/packages/cli/src/boot-state.js +62 -0
  33. package/dist/packages/cli/src/custom.js +261 -0
  34. package/dist/packages/cli/src/display.js +191 -0
  35. package/dist/packages/cli/src/doctor.js +361 -0
  36. package/dist/packages/cli/src/feed.js +634 -0
  37. package/dist/packages/cli/src/fixtures.js +275 -0
  38. package/dist/packages/cli/src/follow-cmd.js +221 -0
  39. package/dist/packages/cli/src/images-config.js +59 -0
  40. package/dist/packages/cli/src/index.js +31 -0
  41. package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
  42. package/dist/packages/cli/src/larp-record.js +21 -0
  43. package/dist/packages/cli/src/live/copy.js +80 -0
  44. package/dist/packages/cli/src/live/flow.js +233 -0
  45. package/dist/packages/cli/src/live/index.js +509 -0
  46. package/dist/packages/cli/src/live/intel.js +227 -0
  47. package/dist/packages/cli/src/live/newpairs.js +334 -0
  48. package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
  49. package/dist/packages/cli/src/live/screens/token-live.js +459 -0
  50. package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
  51. package/dist/packages/cli/src/live/token.js +585 -0
  52. package/dist/packages/cli/src/live/trade.js +929 -0
  53. package/dist/packages/cli/src/live/watchlist.js +178 -0
  54. package/dist/packages/cli/src/main.js +5583 -0
  55. package/dist/packages/cli/src/narrative-cmd.js +76 -0
  56. package/dist/packages/cli/src/pack-cmd.js +323 -0
  57. package/dist/packages/cli/src/plan-cmd.js +278 -0
  58. package/dist/packages/cli/src/popup.js +810 -0
  59. package/dist/packages/cli/src/rules-cmd.js +131 -0
  60. package/dist/packages/cli/src/rules.js +339 -0
  61. package/dist/packages/cli/src/scan/command.js +57 -0
  62. package/dist/packages/cli/src/scan/engine.js +419 -0
  63. package/dist/packages/cli/src/scan/observe.js +64 -0
  64. package/dist/packages/cli/src/scan/ui.js +98 -0
  65. package/dist/packages/cli/src/screener/filter.js +241 -0
  66. package/dist/packages/cli/src/screener/presets.js +80 -0
  67. package/dist/packages/cli/src/screener/rows.js +146 -0
  68. package/dist/packages/cli/src/screener/rug.js +50 -0
  69. package/dist/packages/cli/src/screener/sort.js +52 -0
  70. package/dist/packages/cli/src/screener/view.js +183 -0
  71. package/dist/packages/cli/src/screens/analyst-session.js +343 -0
  72. package/dist/packages/cli/src/screens/analyst.js +326 -0
  73. package/dist/packages/cli/src/screens/boot.js +74 -0
  74. package/dist/packages/cli/src/screens/bubbles.js +255 -0
  75. package/dist/packages/cli/src/screens/coin.js +33 -0
  76. package/dist/packages/cli/src/screens/follows.js +201 -0
  77. package/dist/packages/cli/src/screens/kol.js +90 -0
  78. package/dist/packages/cli/src/screens/narrative.js +90 -0
  79. package/dist/packages/cli/src/screens/newpairs.js +150 -0
  80. package/dist/packages/cli/src/screens/overlay.js +257 -0
  81. package/dist/packages/cli/src/screens/plans.js +126 -0
  82. package/dist/packages/cli/src/screens/positions.js +176 -0
  83. package/dist/packages/cli/src/screens/rules.js +170 -0
  84. package/dist/packages/cli/src/screens/screener.js +459 -0
  85. package/dist/packages/cli/src/screens/social-panel.js +107 -0
  86. package/dist/packages/cli/src/screens/ticket.js +342 -0
  87. package/dist/packages/cli/src/screens/token.js +312 -0
  88. package/dist/packages/cli/src/screens/trending.js +181 -0
  89. package/dist/packages/cli/src/screens/watchlist.js +104 -0
  90. package/dist/packages/cli/src/social/demo.js +96 -0
  91. package/dist/packages/cli/src/social/view.js +250 -0
  92. package/dist/packages/cli/src/state.js +463 -0
  93. package/dist/packages/cli/src/theme-cmd.js +126 -0
  94. package/dist/packages/cli/src/trade.js +319 -0
  95. package/dist/packages/cli/src/voice-hud.js +42 -0
  96. package/dist/packages/cli/src/wallet-cmd.js +440 -0
  97. package/dist/packages/cli/src/wallet-command.js +31 -0
  98. package/dist/packages/cli/src/widgets.js +208 -0
  99. package/dist/packages/cli/src/workspace-cmd.js +68 -0
  100. package/dist/packages/cli/src/x-cmd.js +154 -0
  101. package/dist/packages/custom/src/config.js +213 -0
  102. package/dist/packages/custom/src/index.js +10 -0
  103. package/dist/packages/custom/src/keymap.js +107 -0
  104. package/dist/packages/custom/src/layout.js +235 -0
  105. package/dist/packages/custom/src/packs.js +633 -0
  106. package/dist/packages/custom/src/rules.js +704 -0
  107. package/dist/packages/custom/src/schema.js +515 -0
  108. package/dist/packages/custom/src/themes.js +176 -0
  109. package/dist/packages/custom/src/widget-html.js +222 -0
  110. package/dist/packages/custom/src/workspace.js +141 -0
  111. package/dist/packages/data/src/analyst-actions.js +133 -0
  112. package/dist/packages/data/src/budget.js +88 -0
  113. package/dist/packages/data/src/cache.js +142 -0
  114. package/dist/packages/data/src/candles.js +168 -0
  115. package/dist/packages/data/src/clock.js +64 -0
  116. package/dist/packages/data/src/credits.js +137 -0
  117. package/dist/packages/data/src/detail.js +97 -0
  118. package/dist/packages/data/src/fast-lane.js +321 -0
  119. package/dist/packages/data/src/fixtures.js +135 -0
  120. package/dist/packages/data/src/health.js +245 -0
  121. package/dist/packages/data/src/http.js +277 -0
  122. package/dist/packages/data/src/images/cache.js +141 -0
  123. package/dist/packages/data/src/images/decode-worker.js +28 -0
  124. package/dist/packages/data/src/images/decode.js +81 -0
  125. package/dist/packages/data/src/images/fetch.js +162 -0
  126. package/dist/packages/data/src/images/gif.js +214 -0
  127. package/dist/packages/data/src/images/index.js +10 -0
  128. package/dist/packages/data/src/images/jpeg.js +544 -0
  129. package/dist/packages/data/src/images/png.js +310 -0
  130. package/dist/packages/data/src/images/service.js +334 -0
  131. package/dist/packages/data/src/images/types.js +27 -0
  132. package/dist/packages/data/src/index.js +208 -0
  133. package/dist/packages/data/src/intel/helius-history.js +59 -0
  134. package/dist/packages/data/src/intel/holder-class.js +125 -0
  135. package/dist/packages/data/src/intel/rules.js +421 -0
  136. package/dist/packages/data/src/intel/scan.js +170 -0
  137. package/dist/packages/data/src/larp/evidence.js +288 -0
  138. package/dist/packages/data/src/larp/links.js +137 -0
  139. package/dist/packages/data/src/larp/robots.js +90 -0
  140. package/dist/packages/data/src/larp/safe-fetch.js +389 -0
  141. package/dist/packages/data/src/limiter.js +341 -0
  142. package/dist/packages/data/src/model.js +12 -0
  143. package/dist/packages/data/src/paper.js +331 -0
  144. package/dist/packages/data/src/rpc-config.js +126 -0
  145. package/dist/packages/data/src/rpc-pool.js +166 -0
  146. package/dist/packages/data/src/social/chain.js +83 -0
  147. package/dist/packages/data/src/social/dedupe.js +205 -0
  148. package/dist/packages/data/src/social/feed.js +462 -0
  149. package/dist/packages/data/src/social/firewall.js +110 -0
  150. package/dist/packages/data/src/social/index.js +11 -0
  151. package/dist/packages/data/src/social/kol.js +269 -0
  152. package/dist/packages/data/src/social/model.js +11 -0
  153. package/dist/packages/data/src/social/rank.js +184 -0
  154. package/dist/packages/data/src/social/rss.js +113 -0
  155. package/dist/packages/data/src/social/summary.js +104 -0
  156. package/dist/packages/data/src/social/venues.js +186 -0
  157. package/dist/packages/data/src/sources/dexscreener.js +170 -0
  158. package/dist/packages/data/src/sources/geckoterminal.js +133 -0
  159. package/dist/packages/data/src/sources/goplus.js +161 -0
  160. package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
  161. package/dist/packages/data/src/sources/jupiter.js +198 -0
  162. package/dist/packages/data/src/sources/pda.js +113 -0
  163. package/dist/packages/data/src/sources/pump-curve.js +58 -0
  164. package/dist/packages/data/src/sources/pumpportal.js +247 -0
  165. package/dist/packages/data/src/sources/rugcheck.js +71 -0
  166. package/dist/packages/data/src/sources/solana-rpc.js +299 -0
  167. package/dist/packages/data/src/sources/solana-ws.js +287 -0
  168. package/dist/packages/data/src/store.js +289 -0
  169. package/dist/packages/data/src/trade/bridge.js +464 -0
  170. package/dist/packages/data/src/trade/caps.js +323 -0
  171. package/dist/packages/data/src/trade/copy/auto.js +385 -0
  172. package/dist/packages/data/src/trade/copy/follows.js +103 -0
  173. package/dist/packages/data/src/trade/copy/index.js +9 -0
  174. package/dist/packages/data/src/trade/copy/mirror.js +91 -0
  175. package/dist/packages/data/src/trade/copy/stats.js +120 -0
  176. package/dist/packages/data/src/trade/copy/tracker.js +158 -0
  177. package/dist/packages/data/src/trade/copy/trades.js +87 -0
  178. package/dist/packages/data/src/trade/copy/types.js +6 -0
  179. package/dist/packages/data/src/trade/executor.js +933 -0
  180. package/dist/packages/data/src/trade/kill-switch.js +47 -0
  181. package/dist/packages/data/src/trade/link-store.js +52 -0
  182. package/dist/packages/data/src/trade/plans/confirm.js +157 -0
  183. package/dist/packages/data/src/trade/plans/engine.js +784 -0
  184. package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
  185. package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
  186. package/dist/packages/data/src/trade/plans/executor.js +11 -0
  187. package/dist/packages/data/src/trade/plans/index.js +12 -0
  188. package/dist/packages/data/src/trade/plans/journal.js +77 -0
  189. package/dist/packages/data/src/trade/plans/mandate.js +129 -0
  190. package/dist/packages/data/src/trade/plans/plan.js +179 -0
  191. package/dist/packages/data/src/trade/plans/service.js +168 -0
  192. package/dist/packages/data/src/trade/plans/types.js +6 -0
  193. package/dist/packages/data/src/trade/rpc-exec.js +229 -0
  194. package/dist/packages/data/src/trade/signer/client.js +197 -0
  195. package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
  196. package/dist/packages/data/src/trade/signer/host.js +97 -0
  197. package/dist/packages/data/src/trade/signer/main.js +121 -0
  198. package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
  199. package/dist/packages/data/src/trade/signer/protocol.js +92 -0
  200. package/dist/packages/data/src/trade/signer/service.js +400 -0
  201. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
  202. package/dist/packages/data/src/trade/sol-usd.js +86 -0
  203. package/dist/packages/data/src/trade/swap-build.js +108 -0
  204. package/dist/packages/data/src/trade/token-delta.js +201 -0
  205. package/dist/packages/data/src/trade/tx-decode.js +632 -0
  206. package/dist/packages/data/src/util.js +24 -0
  207. package/dist/packages/data/src/watchlist.js +176 -0
  208. package/dist/packages/data/src/x/api.js +256 -0
  209. package/dist/packages/data/src/x/budget.js +66 -0
  210. package/dist/packages/data/src/x/config.js +49 -0
  211. package/dist/packages/data/src/x/index.js +10 -0
  212. package/dist/packages/data/src/x/mock-server.js +217 -0
  213. package/dist/packages/data/src/x/oauth.js +242 -0
  214. package/dist/packages/data/src/x/pkce.js +21 -0
  215. package/dist/packages/data/src/x/source.js +285 -0
  216. package/dist/packages/data/src/x/token-store.js +134 -0
  217. package/dist/packages/data/src/x/transport.js +71 -0
  218. package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
  219. package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
  220. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
  221. package/dist/packages/mcp/assets/cases.json +1194 -0
  222. package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
  223. package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
  224. package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
  225. package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
  226. package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
  227. package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
  228. package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
  229. package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
  230. package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
  231. package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
  232. package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
  233. package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
  234. package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
  235. package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
  236. package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
  237. package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
  238. package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
  239. package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
  240. package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
  241. package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
  242. package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
  243. package/dist/packages/mcp/src/dossier.js +78 -0
  244. package/dist/packages/mcp/src/firewall.js +21 -0
  245. package/dist/packages/mcp/src/index.js +22 -0
  246. package/dist/packages/mcp/src/server.js +849 -0
  247. package/dist/packages/mcp/src/user-content.js +434 -0
  248. package/dist/packages/packs/src/backtest.js +183 -0
  249. package/dist/packages/packs/src/digest.js +95 -0
  250. package/dist/packages/packs/src/firewall.js +57 -0
  251. package/dist/packages/packs/src/index.js +19 -0
  252. package/dist/packages/packs/src/larp-eval.js +100 -0
  253. package/dist/packages/packs/src/narrative.js +309 -0
  254. package/dist/packages/packs/src/reason.js +135 -0
  255. package/dist/packages/packs/src/rug-model.js +137 -0
  256. package/dist/packages/packs/src/schema.js +54 -0
  257. package/dist/packages/packs/src/token-dossier.js +174 -0
  258. package/dist/packages/packs/src/verdicts.js +17 -0
  259. package/dist/packages/packs/src/wallet-dossier.js +105 -0
  260. package/dist/packages/popup/src/config.js +71 -0
  261. package/dist/packages/popup/src/dock.js +271 -0
  262. package/dist/packages/popup/src/host.js +210 -0
  263. package/dist/packages/popup/src/index.js +12 -0
  264. package/dist/packages/popup/src/loopback.js +53 -0
  265. package/dist/packages/popup/src/open.js +183 -0
  266. package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
  267. package/dist/packages/popup/src/pages/bubbles.css +23 -0
  268. package/dist/packages/popup/src/pages/bubbles.html +36 -0
  269. package/dist/packages/popup/src/pages/bubbles.js +214 -0
  270. package/dist/packages/popup/src/pages/chart-lib.js +262 -0
  271. package/dist/packages/popup/src/pages/chart.css +50 -0
  272. package/dist/packages/popup/src/pages/chart.html +56 -0
  273. package/dist/packages/popup/src/pages/chart.js +554 -0
  274. package/dist/packages/popup/src/pages/common.css +34 -0
  275. package/dist/packages/popup/src/pages/common.js +187 -0
  276. package/dist/packages/popup/src/pages/feeds.html +20 -0
  277. package/dist/packages/popup/src/pages/feeds.js +77 -0
  278. package/dist/packages/popup/src/pages/narrative.html +20 -0
  279. package/dist/packages/popup/src/pages/narrative.js +61 -0
  280. package/dist/packages/popup/src/pages/plans.css +30 -0
  281. package/dist/packages/popup/src/pages/plans.html +26 -0
  282. package/dist/packages/popup/src/pages/plans.js +82 -0
  283. package/dist/packages/popup/src/pages/roster.css +41 -0
  284. package/dist/packages/popup/src/pages/roster.html +25 -0
  285. package/dist/packages/popup/src/pages/roster.js +75 -0
  286. package/dist/packages/popup/src/pages/token.html +20 -0
  287. package/dist/packages/popup/src/pages/token.js +112 -0
  288. package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
  289. package/dist/packages/popup/src/pages/widget.css +5 -0
  290. package/dist/packages/popup/src/pages/widget.html +21 -0
  291. package/dist/packages/popup/src/pages/widget.js +85 -0
  292. package/dist/packages/popup/src/protocol.js +31 -0
  293. package/dist/packages/popup/src/selftest.js +156 -0
  294. package/dist/packages/popup/src/server.js +582 -0
  295. package/dist/packages/popup/src/sinkproxy.js +167 -0
  296. package/dist/packages/popup/src/widget-html.js +231 -0
  297. package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
  298. package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
  299. package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
  300. package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
  301. package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
  302. package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
  303. package/dist/packages/recorder/src/cli.js +113 -0
  304. package/dist/packages/recorder/src/export.js +71 -0
  305. package/dist/packages/recorder/src/index.js +20 -0
  306. package/dist/packages/recorder/src/labels.js +151 -0
  307. package/dist/packages/recorder/src/open.js +29 -0
  308. package/dist/packages/recorder/src/recorder.js +553 -0
  309. package/dist/packages/recorder/src/schedule.js +43 -0
  310. package/dist/packages/recorder/src/schema.js +135 -0
  311. package/dist/packages/recorder/src/snapshot.js +191 -0
  312. package/dist/packages/recorder/src/sweep.js +52 -0
  313. package/dist/packages/recorder/src/verdicts.js +52 -0
  314. package/dist/packages/tui/src/bubbles.js +415 -0
  315. package/dist/packages/tui/src/canvas.js +370 -0
  316. package/dist/packages/tui/src/chrome.js +85 -0
  317. package/dist/packages/tui/src/color.js +133 -0
  318. package/dist/packages/tui/src/flush.js +62 -0
  319. package/dist/packages/tui/src/fmt.js +188 -0
  320. package/dist/packages/tui/src/fx-trade.js +278 -0
  321. package/dist/packages/tui/src/fx.js +527 -0
  322. package/dist/packages/tui/src/images/blocks.js +141 -0
  323. package/dist/packages/tui/src/images/coin.js +22 -0
  324. package/dist/packages/tui/src/images/detect.js +140 -0
  325. package/dist/packages/tui/src/images/index.js +7 -0
  326. package/dist/packages/tui/src/images/layer.js +146 -0
  327. package/dist/packages/tui/src/images/pixels.js +157 -0
  328. package/dist/packages/tui/src/images/protocols.js +87 -0
  329. package/dist/packages/tui/src/index.js +15 -0
  330. package/dist/packages/tui/src/keymap.js +355 -0
  331. package/dist/packages/tui/src/layout.js +11 -0
  332. package/dist/packages/tui/src/sound.js +177 -0
  333. package/dist/packages/tui/src/theme.js +182 -0
  334. package/dist/packages/tui/src/widgets.js +598 -0
  335. package/dist/packages/voice/src/controller.js +395 -0
  336. package/dist/packages/voice/src/doctor.js +63 -0
  337. package/dist/packages/voice/src/engines.js +44 -0
  338. package/dist/packages/voice/src/index.js +16 -0
  339. package/dist/packages/voice/src/router.js +502 -0
  340. package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
  341. package/dist/packages/voice/src/stt/apple.js +179 -0
  342. package/dist/packages/voice/src/stt/fake.js +34 -0
  343. package/dist/packages/voice/src/stt/whisper.js +102 -0
  344. package/dist/packages/voice/src/summary.js +44 -0
  345. package/dist/packages/voice/src/tts/fake.js +30 -0
  346. package/dist/packages/voice/src/tts/say.js +96 -0
  347. package/dist/packages/voice/src/types.js +3 -0
  348. package/package.json +42 -4
  349. package/README.md +0 -3
@@ -0,0 +1,395 @@
1
+ // VoiceController (task-1282): the push-to-talk state machine that ties speech-in, the safe router,
2
+ // and speech-out together, measures latency, and stays entirely UI-agnostic so it runs headless in
3
+ // tests (fake engines) and in the app (real macOS engines). It emits a decision the app performs; it
4
+ // NEVER performs a trade itself. The mic is open only in the 'listening' mode — the single place
5
+ // listen() is called — so the on-screen indicator and the real mic state can never disagree.
6
+ import { route, isSafe } from "./router.js";
7
+ import { spokenSummary } from "./summary.js";
8
+ export class VoiceController {
9
+ stt;
10
+ tts;
11
+ opts;
12
+ now;
13
+ enabled = false;
14
+ mode = 'off';
15
+ muted;
16
+ voice;
17
+ rate;
18
+ voices = [];
19
+ lastTranscript = null;
20
+ lastSay = null;
21
+ message = null;
22
+ session = null;
23
+ speaking = null;
24
+ /** Key-release timestamp awaiting its first-spoken-word, and the path it will be attributed to. */
25
+ pendingLatency = null;
26
+ maxListenMs;
27
+ setTimer;
28
+ clearTimer;
29
+ /** The live mic's auto-stop handle; non-null only while listening. */
30
+ listenTimer = null;
31
+ /**
32
+ * True from the talk key until stt.listen() has resolved (task-1339, re-verify N3). A second talk key
33
+ * in that window is ignored instead of opening a second, orphaned mic session; a cancel in that
34
+ * window closes the session the moment it arrives.
35
+ */
36
+ starting = false;
37
+ startCancelled = false;
38
+ samples = [];
39
+ constructor(opts) {
40
+ this.opts = opts;
41
+ this.stt = opts.stt;
42
+ this.tts = opts.tts;
43
+ this.muted = opts.muted ?? false;
44
+ this.voice = opts.voice ?? null;
45
+ this.rate = opts.rate ?? 190;
46
+ this.now = opts.now ?? (() => Date.now());
47
+ this.maxListenMs = opts.maxListenMs ?? 15_000;
48
+ this.setTimer = opts.setTimer ?? ((fn, ms) => { const h = setTimeout(fn, ms); h.unref?.(); return h; });
49
+ this.clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
50
+ }
51
+ status() {
52
+ return {
53
+ enabled: this.enabled,
54
+ mode: this.mode,
55
+ starting: this.starting,
56
+ muted: this.muted,
57
+ voice: this.voice,
58
+ sttLabel: this.stt?.label ?? null,
59
+ ttsLabel: this.tts?.label ?? null,
60
+ lastTranscript: this.lastTranscript,
61
+ lastSay: this.lastSay,
62
+ message: this.message,
63
+ };
64
+ }
65
+ notify() { try {
66
+ this.opts.onState?.(this.status());
67
+ }
68
+ catch { /* UI handler must not break us */ } }
69
+ setMode(m) { this.mode = m; this.notify(); }
70
+ get isEnabled() { return this.enabled; }
71
+ get isListening() { return this.mode === 'listening'; }
72
+ /** The mic is live OR about to be (listen() in flight): the states a key must cancel. */
73
+ get micEngaged() { return this.mode === 'listening' || this.starting; }
74
+ /** Load the voice list lazily (used by cycleVoice and the status line). */
75
+ async loadVoices() {
76
+ if (!this.tts)
77
+ return;
78
+ try {
79
+ this.voices = await this.tts.listVoices();
80
+ if (!this.voice && this.voices.length)
81
+ this.voice = this.voices[0];
82
+ }
83
+ catch {
84
+ this.voices = [];
85
+ }
86
+ }
87
+ /** Turn voice mode on/off. Enabling with no STT engine still works for spoken replies to typed Qs. */
88
+ enable() {
89
+ if (this.enabled)
90
+ return;
91
+ this.enabled = true;
92
+ this.message = this.stt ? null : 'voice-in unavailable on this machine — type questions; replies are still spoken';
93
+ this.setMode('idle');
94
+ void this.loadVoices();
95
+ }
96
+ disable() {
97
+ if (!this.enabled)
98
+ return;
99
+ this.cancelListen();
100
+ this.stopSpeaking();
101
+ this.enabled = false;
102
+ this.pendingLatency = null;
103
+ this.setMode('off');
104
+ }
105
+ toggle() { this.enabled ? this.disable() : this.enable(); }
106
+ /**
107
+ * The push-to-talk key. Terminals deliver no key-up, so this toggles: first press opens the mic
108
+ * (mode 'listening' — the only place the mic is live), second press closes it, transcribes, and
109
+ * routes. Starting to talk also interrupts any reply in progress. Returns when the decision (if any)
110
+ * has been emitted, so tests can await it.
111
+ */
112
+ async toggleTalk() {
113
+ if (!this.enabled)
114
+ this.enable();
115
+ if (this.mode === 'listening')
116
+ return this.stopTalkAndRoute();
117
+ if (this.mode === 'transcribing' || this.mode === 'thinking')
118
+ return; // busy; ignore re-press
119
+ if (this.starting)
120
+ return; // a second press while the mic is opening: never a second session (N3)
121
+ return this.startTalk();
122
+ }
123
+ async startTalk() {
124
+ if (!this.stt) {
125
+ this.message = 'voice-in unavailable — type your question instead';
126
+ this.notify();
127
+ return;
128
+ }
129
+ this.stopSpeaking(); // starting to talk cuts off any reply
130
+ this.starting = true;
131
+ this.startCancelled = false;
132
+ this.notify();
133
+ let session = null;
134
+ try {
135
+ session = await this.stt.listen();
136
+ }
137
+ catch (e) {
138
+ this.starting = false;
139
+ this.session = null;
140
+ this.message = `mic/STT could not start: ${describe(e)}`;
141
+ this.setMode('idle');
142
+ return;
143
+ }
144
+ this.starting = false;
145
+ // The user moved on (a key, esc, disable) while the mic was opening: close it at once (N3/N8).
146
+ if (this.startCancelled || !this.enabled) {
147
+ try {
148
+ session.cancel();
149
+ }
150
+ catch { /* ignore */ }
151
+ this.session = null;
152
+ this.setMode(this.enabled ? 'idle' : 'off');
153
+ return;
154
+ }
155
+ this.session = session;
156
+ this.setMode('listening');
157
+ this.armListenTimeout(); // the mic can never stay live indefinitely (task-1282 rework D2)
158
+ }
159
+ /** Start the hard cap that auto-stops a live mic. Cleared whenever we leave 'listening'. */
160
+ armListenTimeout() {
161
+ this.clearListenTimeout();
162
+ if (this.maxListenMs <= 0)
163
+ return;
164
+ this.listenTimer = this.setTimer(() => {
165
+ this.listenTimer = null;
166
+ if (this.mode === 'listening') {
167
+ this.cancelListen();
168
+ this.message = 'mic timed out — press v to talk again';
169
+ this.notify();
170
+ }
171
+ }, this.maxListenMs);
172
+ }
173
+ clearListenTimeout() {
174
+ if (this.listenTimer !== null) {
175
+ try {
176
+ this.clearTimer(this.listenTimer);
177
+ }
178
+ catch { /* ignore */ }
179
+ this.listenTimer = null;
180
+ }
181
+ }
182
+ async stopTalkAndRoute() {
183
+ this.clearListenTimeout(); // leaving 'listening': the mic is no longer live
184
+ const session = this.session;
185
+ this.session = null;
186
+ this.setMode('transcribing');
187
+ // "Key release": the clock for key-release → first-spoken-word starts now.
188
+ const releaseAt = this.now();
189
+ let result;
190
+ try {
191
+ result = session ? await session.stop() : { text: '', confidence: 0 };
192
+ }
193
+ catch (e) {
194
+ // Always release the capture on a transcription error so the mic can never be left open.
195
+ try {
196
+ session?.cancel();
197
+ }
198
+ catch { /* ignore */ }
199
+ this.message = `transcription failed: ${describe(e)}`;
200
+ this.setMode('idle');
201
+ return;
202
+ }
203
+ this.lastTranscript = result.text || null;
204
+ // An empty capture used to route to a silent 'ignore': the user saw the badge vanish and nothing else.
205
+ if (!result.text.trim()) {
206
+ this.message = "didn't hear anything — press v, wait for the red LISTENING badge, speak, then press v";
207
+ this.setMode('idle');
208
+ return;
209
+ }
210
+ const ss = this.opts.getScreenState?.() ?? {};
211
+ const prov = {
212
+ pttActive: true, // this transcript came from a real PTT session, by construction
213
+ speakerCount: result.speakerCount ?? 1,
214
+ echoFlagged: result.echoFlagged ?? false,
215
+ sttConfidence: result.confidence,
216
+ lowConfTokens: result.lowConfTokens ?? [],
217
+ };
218
+ const decision = route(result.text, prov, ss);
219
+ // Hard safety gate: never act on a decision that is not in the safe set (defence in depth).
220
+ if (!isSafe(decision)) {
221
+ this.message = 'blocked an unsafe voice decision';
222
+ this.setMode('idle');
223
+ return;
224
+ }
225
+ this.dispatch(decision, releaseAt);
226
+ }
227
+ /** Emit the decision for the app, speak any immediate confirmation, and arm latency timing. */
228
+ dispatch(d, releaseAt) {
229
+ if (d.kind === 'analyst') {
230
+ // The spoken output is the analyst's answer; the app calls speakAnswer() when it lands. Time the
231
+ // key-release → first word of that answer.
232
+ this.pendingLatency = { at: releaseAt, path: 'analyst', transcript: this.lastTranscript ?? '' };
233
+ this.setMode('thinking');
234
+ try {
235
+ this.opts.onDecision?.(d);
236
+ }
237
+ catch { /* app handler */ }
238
+ return;
239
+ }
240
+ // Instant paths (navigate/open/filter/watchlist/open-ticket/popup/clarify) speak their own one-liner —
241
+ // unless the app answered itself (returned false): then its reply stands and `say` is NOT read over
242
+ // it, so a clarify such as "I see 2 tokens called X" is what the user actually hears (N1).
243
+ this.pendingLatency = { at: releaseAt, path: 'instant', transcript: this.lastTranscript ?? '' };
244
+ let appAnswered = false;
245
+ try {
246
+ appAnswered = this.opts.onDecision?.(d) === false;
247
+ }
248
+ catch { /* app handler */ }
249
+ if (appAnswered) {
250
+ if (this.mode !== 'speaking')
251
+ this.setMode('idle');
252
+ return;
253
+ }
254
+ const line = d.kind === 'clarify' ? d.question : ('say' in d ? d.say : null);
255
+ if (line)
256
+ this.speak(line);
257
+ else {
258
+ this.setMode('idle');
259
+ }
260
+ }
261
+ /**
262
+ * Speak the analyst's answer (first ~2 sentences). Called by the app when a turn settles. Honours
263
+ * mute, is interruptible, and records the pending analyst-path latency on the first audio sample.
264
+ */
265
+ speakAnswer(src) {
266
+ const line = spokenSummary(src);
267
+ if (!line) {
268
+ this.setMode('idle');
269
+ return;
270
+ }
271
+ this.speak(line);
272
+ }
273
+ /** Low-level speak: stops any current reply, then reads `text`. Muted → skip audio but update state. */
274
+ speak(text) {
275
+ this.lastSay = text;
276
+ if (!this.tts || this.muted) {
277
+ // Still resolve the latency sample (the "spoken word" is just suppressed) so the table is complete.
278
+ this.recordFirstAudio();
279
+ this.setMode('idle');
280
+ this.notify();
281
+ return;
282
+ }
283
+ this.stopSpeaking();
284
+ this.setMode('speaking');
285
+ const handle = this.tts.speak(text, {
286
+ voice: this.voice ?? undefined,
287
+ rate: this.rate,
288
+ onFirstAudio: () => this.recordFirstAudio(),
289
+ });
290
+ this.speaking = handle;
291
+ handle.done.then(() => {
292
+ if (this.speaking === handle) {
293
+ this.speaking = null;
294
+ if (this.mode === 'speaking')
295
+ this.setMode('idle');
296
+ }
297
+ });
298
+ }
299
+ recordFirstAudio() {
300
+ const p = this.pendingLatency;
301
+ if (!p)
302
+ return;
303
+ this.pendingLatency = null;
304
+ this.samples.push({ path: p.path, ms: this.now() - p.at, transcript: p.transcript });
305
+ }
306
+ /** Interrupt a spoken reply (a key, or the user starting to talk). */
307
+ stopSpeaking() {
308
+ if (this.speaking) {
309
+ try {
310
+ this.speaking.stop();
311
+ }
312
+ catch { /* ignore */ }
313
+ this.speaking = null;
314
+ }
315
+ if (this.mode === 'speaking')
316
+ this.setMode('idle');
317
+ }
318
+ /** Abandon a live capture without transcribing. Always closes the mic and the auto-stop timer. */
319
+ cancelListen() {
320
+ this.clearListenTimeout();
321
+ if (this.starting)
322
+ this.startCancelled = true; // the session is closed the moment listen() resolves
323
+ if (this.session) {
324
+ try {
325
+ this.session.cancel();
326
+ }
327
+ catch { /* ignore */ }
328
+ this.session = null;
329
+ }
330
+ if (this.mode === 'listening')
331
+ this.setMode('idle');
332
+ }
333
+ /**
334
+ * Interrupt whatever voice is doing and return to idle — a mic capture, a spoken reply, OR a pending
335
+ * analyst turn ('thinking') / in-flight 'transcribing'. Resetting 'thinking' is the task-1282 rework
336
+ * D3 fix: an analyst error used to leave the mode stuck in 'thinking', where toggleTalk ignores every
337
+ * press, so voice was dead until restart. Now any key (esc) or an error cleanly returns to idle.
338
+ */
339
+ interrupt() {
340
+ this.stopSpeaking();
341
+ this.cancelListen();
342
+ this.pendingLatency = null;
343
+ if (this.enabled && this.mode !== 'idle' && this.mode !== 'off')
344
+ this.setMode('idle');
345
+ }
346
+ /**
347
+ * Abort the current turn because the analyst run failed/was cancelled, and surface a clear error both
348
+ * on screen (the HUD message) and aloud. Always leaves voice in idle so the next `v` works
349
+ * (task-1282 rework D3).
350
+ */
351
+ abortTurn(message = "Sorry — I couldn't get an answer. Try again.") {
352
+ this.interrupt();
353
+ if (!this.enabled) {
354
+ this.setMode('off');
355
+ return;
356
+ }
357
+ this.message = message;
358
+ this.mode = 'idle'; // speak()'s done handler returns to idle from 'speaking'
359
+ this.speak(message);
360
+ }
361
+ setMuted(m) { this.muted = m; if (m)
362
+ this.stopSpeaking(); this.notify(); }
363
+ toggleMute() { this.setMuted(!this.muted); }
364
+ get isMuted() { return this.muted; }
365
+ /** Cycle to the next installed voice (macOS voices). No-op when there are none. */
366
+ cycleVoice() {
367
+ if (!this.voices.length) {
368
+ this.notify();
369
+ return;
370
+ }
371
+ const i = this.voice ? this.voices.indexOf(this.voice) : -1;
372
+ this.voice = this.voices[(i + 1) % this.voices.length];
373
+ this.notify();
374
+ }
375
+ currentVoice() { return this.voice; }
376
+ availableVoices() { return this.voices.slice(); }
377
+ /** A compact latency report for the handoff / `--latency` demo. */
378
+ report() {
379
+ const paths = ['instant', 'analyst'];
380
+ return paths.map((path) => {
381
+ const xs = this.samples.filter((s) => s.path === path).map((s) => s.ms).sort((a, b) => a - b);
382
+ return { path, n: xs.length, p50: pct(xs, 0.5), min: xs[0] ?? null, max: xs[xs.length - 1] ?? null };
383
+ });
384
+ }
385
+ }
386
+ function pct(sorted, p) {
387
+ if (!sorted.length)
388
+ return null;
389
+ const i = Math.min(sorted.length - 1, Math.floor(p * sorted.length));
390
+ return sorted[i];
391
+ }
392
+ function describe(e) {
393
+ const m = e?.message;
394
+ return typeof m === 'string' ? m : String(e);
395
+ }
@@ -0,0 +1,63 @@
1
+ // Voice-mode doctor checks (task-1282 scope item 4). Returns the same check shape the CLI doctor uses
2
+ // so `dexterm doctor` can render them inline. Everything the checks depend on (platform, which engines
3
+ // resolved, whether swiftc / a whisper model / a mic tool are present) is injected, so the checks are
4
+ // deterministic in tests and never spawn or hang. The guiding rule: if a component is missing, say
5
+ // exactly what to install, and make clear voice DEGRADES TO TEXT rather than breaking the app.
6
+ /** Produce the voice checks. Never throws. */
7
+ export function voiceDoctor(deps) {
8
+ const checks = [];
9
+ if (deps.platform !== 'darwin') {
10
+ checks.push({
11
+ name: 'Voice mode', status: 'warn',
12
+ detail: `voice mode is macOS-only for now (speech uses the built-in \`say\` and Apple Speech); on ${deps.platform} it stays off and dexterm runs as text`,
13
+ fix: 'Use dexterm on macOS for voice, or just use the keyboard — every voice action has a key.',
14
+ });
15
+ return checks;
16
+ }
17
+ // Speech OUT — macOS say.
18
+ if (deps.sayAvailable) {
19
+ checks.push({ name: 'Voice out (say)', status: 'pass', detail: `macOS \`say\` ready · ${deps.voiceCount} voice${deps.voiceCount === 1 ? '' : 's'} (press the voice key, then cycle the voice)` });
20
+ }
21
+ else {
22
+ checks.push({
23
+ name: 'Voice out (say)', status: 'warn',
24
+ detail: 'macOS `say` not found; spoken replies are off, the analyst still answers in text',
25
+ fix: '`say` ships with macOS — if it is missing, reinstall the Command Line Tools (`xcode-select --install`).',
26
+ });
27
+ }
28
+ // Speech IN — Apple Speech (preferred) or whisper (fallback).
29
+ if (deps.resolvedStt === 'apple' || deps.swiftcPresent) {
30
+ checks.push({ name: 'Voice in (Apple Speech)', status: 'pass', detail: 'on-device Apple Speech via swiftc (no key, streaming) — the first run asks for Microphone + Speech permission at the Mac' });
31
+ }
32
+ else if (deps.resolvedStt === 'whisper' || (deps.whisperBinPresent && deps.whisperModelPresent && deps.micToolPresent)) {
33
+ checks.push({ name: 'Voice in (whisper)', status: 'pass', detail: 'whisper.cpp base.en + a mic tool (local, no key)' });
34
+ }
35
+ else {
36
+ // Nothing for voice-in: steer to the fastest free path and make clear text still works.
37
+ const bits = [];
38
+ if (!deps.swiftcPresent)
39
+ bits.push('no swiftc (Apple Speech)');
40
+ if (!deps.whisperBinPresent)
41
+ bits.push('no whisper-cli');
42
+ else if (!deps.whisperModelPresent)
43
+ bits.push('no base.en model');
44
+ if (!deps.micToolPresent)
45
+ bits.push('no mic tool (sox/ffmpeg)');
46
+ checks.push({
47
+ name: 'Voice in', status: 'warn',
48
+ detail: `speech-to-text is off (${bits.join(', ')}); you can still type questions — voice degrades to text`,
49
+ fix: 'Fastest free path: install Xcode command line tools for Apple Speech (`xcode-select --install`). '
50
+ + 'Or whisper: `brew install whisper-cpp sox`, then download base.en to '
51
+ + '`~/.dexterm/models/ggml-base.en.bin` (from the whisper.cpp model repo). Never use large-v3-turbo on a CPU-only Mac — it is unusably slow.',
52
+ });
53
+ }
54
+ // Mic-capture tool note (only relevant to the whisper path; Apple Speech captures its own audio).
55
+ if (!deps.micToolPresent && deps.resolvedStt !== 'apple') {
56
+ checks.push({
57
+ name: 'Mic capture', status: 'warn',
58
+ detail: 'no sox/ffmpeg found; the whisper voice-in path needs one (Apple Speech does not)',
59
+ fix: '`brew install sox` (smallest) or `brew install ffmpeg`.',
60
+ });
61
+ }
62
+ return checks;
63
+ }
@@ -0,0 +1,44 @@
1
+ // Engine selection (task-1282). Picks the STT and TTS engines for this machine, honouring explicit
2
+ // env overrides and falling back gracefully. The app degrades to text (never crashes) when nothing is
3
+ // available; selectStt() returns null in that case and the controller says so.
4
+ import { AppleStt } from "./stt/apple.js";
5
+ import { WhisperStt } from "./stt/whisper.js";
6
+ import { FakeStt } from "./stt/fake.js";
7
+ import { SayTts } from "./tts/say.js";
8
+ import { FakeTts } from "./tts/fake.js";
9
+ /**
10
+ * Resolve the STT engine. `auto` prefers Apple Speech (on-device, streaming), then whisper base.en,
11
+ * then none. An explicit choice is returned even if it later proves unavailable, so the doctor and the
12
+ * status line can report exactly why voice-in is off.
13
+ */
14
+ export async function selectStt(choice = process.env.DEXTERM_VOICE_STT || 'auto') {
15
+ if (choice === 'none')
16
+ return null;
17
+ if (choice === 'fake')
18
+ return new FakeStt();
19
+ if (choice === 'apple')
20
+ return new AppleStt();
21
+ if (choice === 'whisper')
22
+ return new WhisperStt();
23
+ // auto: probe in preference order.
24
+ const apple = new AppleStt();
25
+ if (await apple.available())
26
+ return apple;
27
+ const whisper = new WhisperStt();
28
+ if (await whisper.available())
29
+ return whisper;
30
+ return null;
31
+ }
32
+ /** Resolve the TTS engine. `auto` uses macOS `say` when present, else none (voice replies off). */
33
+ export async function selectTts(choice = process.env.DEXTERM_VOICE_TTS || 'auto', defaultVoice) {
34
+ if (choice === 'none')
35
+ return null;
36
+ if (choice === 'fake')
37
+ return new FakeTts();
38
+ if (choice === 'say')
39
+ return new SayTts(defaultVoice);
40
+ const say = new SayTts(defaultVoice);
41
+ if (await say.available())
42
+ return say;
43
+ return null;
44
+ }
@@ -0,0 +1,16 @@
1
+ // @dexterm/voice — JARVIS voice mode (task-1282). Public surface.
2
+ //
3
+ // Free and portable by requirement: macOS `say` for speech-out, on-device Apple Speech (compiled with
4
+ // swiftc) or local whisper.cpp for speech-in — no paid APIs, no shipped keys. Voice can only ask the
5
+ // analyst or trigger its existing safe actions; it can NEVER confirm, sign, or execute a trade.
6
+ export * from "./types.js";
7
+ export { route, isSafe } from "./router.js";
8
+ export { spokenSummary, splitSentences } from "./summary.js";
9
+ export { VoiceController } from "./controller.js";
10
+ export { selectStt, selectTts } from "./engines.js";
11
+ export { voiceDoctor } from "./doctor.js";
12
+ export { SayTts } from "./tts/say.js";
13
+ export { FakeTts } from "./tts/fake.js";
14
+ export { WhisperStt, WHISPER_MODEL, WHISPER_MODEL_DIR } from "./stt/whisper.js";
15
+ export { AppleStt, ensureHelper } from "./stt/apple.js";
16
+ export { FakeStt } from "./stt/fake.js";