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,190 @@
1
+ // dexterm — `dexterm watch` (task-1398): run the rules engine against the live feed with NO TUI and PUSH
2
+ // every fired alert (and, when alerts.proposals is on, a one-line proposal summary) through the delivery
3
+ // layer — so a price / holder / LARP / new-pair alert reaches you when the terminal is closed.
4
+ //
5
+ // dexterm watch [--interval S] [--ticks N | --once] [--replay FILE] [--db PATH] [--verbose]
6
+ //
7
+ // SAFETY: this builds NO signer and passes no `trade` option; a rule can only alert / annotate / open an
8
+ // inert PROPOSAL (RulesEngine's money-safety invariant). The notifier only reads an AlertDelivery. There is
9
+ // no arm/sign/confirm/cap path anywhere under here (asserted by alerts-1398.test.ts).
10
+ import { dextermHome, loadConfig, RulesEngine } from '../../custom/src/index.js';
11
+ import { openHistoryReadOnly, LARP_PACK } from '../../recorder/src/index.js';
12
+ import { buildNotifier } from "./alerts/notifier.js";
13
+ import { alertToDelivery, proposalToDelivery } from "./alerts/format.js";
14
+ import { redactCreds } from "./alerts/creds.js";
15
+ import { larpGrade } from "./rules.js";
16
+ export const WATCH_USAGE = `usage: dexterm watch [--interval S] [--ticks N | --once] [--replay FILE] [--db PATH] [--verbose]
17
+
18
+ Runs your rules headless and pushes alerts to the channels you enabled with \`dexterm alerts\`
19
+ (terminal line + desktop notification + your Telegram bot / Discord webhook). Ctrl-C to stop.
20
+ `;
21
+ function parseArgs(argv) {
22
+ const a = { intervalMs: 15_000, ticks: Infinity, replay: null, db: null, verbose: false, help: false };
23
+ for (let i = 0; i < argv.length; i++) {
24
+ const t = argv[i];
25
+ if (t === '--help' || t === '-h')
26
+ a.help = true;
27
+ else if (t === '--verbose' || t === '-v')
28
+ a.verbose = true;
29
+ else if (t === '--once')
30
+ a.ticks = 1;
31
+ else if (t === '--interval') {
32
+ const n = Number(argv[++i]);
33
+ if (!Number.isFinite(n) || n < 1)
34
+ return { error: '--interval needs seconds ≥ 1' };
35
+ a.intervalMs = Math.round(n * 1000);
36
+ }
37
+ else if (t === '--ticks') {
38
+ const n = Number(argv[++i]);
39
+ if (!Number.isInteger(n) || n < 1)
40
+ return { error: '--ticks needs an integer ≥ 1' };
41
+ a.ticks = n;
42
+ }
43
+ else if (t === '--replay') {
44
+ a.replay = argv[++i] ?? null;
45
+ if (!a.replay)
46
+ return { error: '--replay needs a FILE' };
47
+ }
48
+ else if (t === '--db') {
49
+ a.db = argv[++i] ?? null;
50
+ if (!a.db)
51
+ return { error: '--db needs a PATH' };
52
+ }
53
+ else
54
+ return { error: `unknown option "${t}"` };
55
+ }
56
+ return a;
57
+ }
58
+ /** Latest LARP verdict per mint from a read-only recorder DB (best effort; empty map on any problem). */
59
+ function larpMapFromDb(path) {
60
+ const map = new Map();
61
+ if (!path || path === ':memory:')
62
+ return map;
63
+ const db = openHistoryReadOnly(path);
64
+ if (!db)
65
+ return map;
66
+ try {
67
+ const rows = db.prepare('SELECT mint, verdict FROM verdicts WHERE pack = ? AND verdict IS NOT NULL ORDER BY t').all(LARP_PACK);
68
+ for (const r of rows)
69
+ map.set(r.mint, r.verdict); // ORDER BY t → last write per mint wins
70
+ }
71
+ catch { /* table may not exist yet */ }
72
+ finally {
73
+ try {
74
+ db.close();
75
+ }
76
+ catch { /* ignore */ }
77
+ }
78
+ return map;
79
+ }
80
+ /** Map a screener row to a rule row (cluster is only known with the wallet-intel scan, absent headless; larp from the DB). */
81
+ export function toRuleRow(r, larp) {
82
+ return Object.assign(Object.create(null), r, { cluster: null, larp: larpGrade(larp.get(r.mint)) });
83
+ }
84
+ /** The production source: refresh the live (or replay) feed, map rows, yield a snapshot. Replay yields once. */
85
+ function createFeedSource(opts) {
86
+ let feed = null;
87
+ let yielded = 0;
88
+ const larp = larpMapFromDb(opts.db);
89
+ return {
90
+ async next() {
91
+ if (opts.stop.aborted)
92
+ return null;
93
+ const { createLiveFeed, createReplayFeed } = await import("./feed.js");
94
+ if (!feed)
95
+ feed = opts.replay ? createReplayFeed(opts.replay, {}) : createLiveFeed({ dbPath: opts.db ?? undefined });
96
+ else {
97
+ if (opts.replay)
98
+ return null; // a recording is a single snapshot: one evaluate, then stop
99
+ await opts.stop.wait(opts.intervalMs);
100
+ if (opts.stop.aborted)
101
+ return null;
102
+ }
103
+ const rows = await feed.refresh();
104
+ yielded++;
105
+ if (opts.verbose)
106
+ opts.io.stderr.write(`watch: tick ${yielded}, ${rows.length} rows\n`);
107
+ return { rows: rows.map((r) => toRuleRow(r, larp)), now: opts.now() };
108
+ },
109
+ close() { try {
110
+ feed?.close();
111
+ }
112
+ catch { /* ignore */ } },
113
+ };
114
+ }
115
+ export async function watchCommand(argv, io, deps = {}) {
116
+ const parsed = parseArgs(argv);
117
+ if ('error' in parsed) {
118
+ io.stderr.write(`dexterm watch: ${parsed.error}\n\n${WATCH_USAGE}`);
119
+ return 2;
120
+ }
121
+ if (parsed.help) {
122
+ io.stdout.write(WATCH_USAGE);
123
+ return 0;
124
+ }
125
+ const now = deps.now ?? (() => Date.now());
126
+ const home = dextermHome(io.env);
127
+ const loaded = loadConfig(home);
128
+ for (const e of loaded.errors)
129
+ io.stderr.write(`dexterm watch: config: ${e}\n`);
130
+ const rules = loaded.config.rules ?? [];
131
+ const engine = new RulesEngine({ rules, now: now() });
132
+ for (const e of engine.errors)
133
+ io.stderr.write(`dexterm watch: rule ${e.id}: ${e.msg}\n`);
134
+ const enabled = engine.rules.filter((r) => r.enabled).length;
135
+ if (enabled === 0)
136
+ io.stderr.write('dexterm watch: no enabled rules (add some with `dexterm rules add`); nothing will fire\n');
137
+ // SIGINT-aware sleeper for the production loop.
138
+ const stop = (() => {
139
+ let aborted = false;
140
+ let wake = () => { };
141
+ return {
142
+ get aborted() { return aborted; },
143
+ abort() { aborted = true; wake(); },
144
+ wake() { wake(); },
145
+ wait(ms) { return new Promise((res) => { const t = setTimeout(res, ms); wake = () => { clearTimeout(t); res(); }; }); },
146
+ };
147
+ })();
148
+ const notifier = deps.notifier ?? buildNotifier(home, loaded.config.alerts, {
149
+ toastSink: (line) => io.stdout.write(`${new Date(now()).toISOString().slice(11, 19)} ${line}\n`),
150
+ });
151
+ const source = deps.source ?? createFeedSource({ replay: parsed.replay, db: parsed.db, intervalMs: parsed.intervalMs, verbose: parsed.verbose, io, now, stop });
152
+ const ready = notifier.readyChannels();
153
+ if (parsed.verbose || !deps.notifier)
154
+ io.stderr.write(`dexterm watch: ${enabled} rule(s), channels: ${ready.length ? ready.join(', ') : 'toast'}\n`);
155
+ const onSigint = () => { io.stderr.write('\ndexterm watch: stopping…\n'); stop.abort(); };
156
+ const addSig = !deps.source; // only own SIGINT for the real loop (tests drive a finite source)
157
+ if (addSig)
158
+ process.on('SIGINT', onSigint);
159
+ let delivered = 0;
160
+ try {
161
+ for (let tick = 0; tick < parsed.ticks; tick++) {
162
+ const snap = await source.next();
163
+ if (!snap)
164
+ break;
165
+ engine.observe(snap.rows, snap.now);
166
+ const res = engine.evaluate(snap.rows, snap.now, { watchlist: snap.watchlist, positions: snap.positions });
167
+ for (const a of res.alerts) {
168
+ await notifier.deliver(alertToDelivery(a));
169
+ delivered++;
170
+ }
171
+ if (loaded.config.alerts?.proposals)
172
+ for (const p of res.proposals) {
173
+ await notifier.deliver(proposalToDelivery(p));
174
+ delivered++;
175
+ }
176
+ }
177
+ }
178
+ catch (e) {
179
+ io.stderr.write(`dexterm watch: ${redactCreds(e.message)}\n`);
180
+ return 1;
181
+ }
182
+ finally {
183
+ if (addSig)
184
+ process.removeListener('SIGINT', onSigint);
185
+ await source.close?.();
186
+ }
187
+ if (parsed.verbose || !deps.notifier)
188
+ io.stderr.write(`dexterm watch: delivered ${delivered} alert(s)\n`);
189
+ return 0;
190
+ }
@@ -0,0 +1,208 @@
1
+ // dexterm — pack WIDGETS in the app (task-1358 Tier 5). A pack widget is an html page the popup host serves in a
2
+ // loopback HUD window under a hash-only CSP (r2: no nonce — every inline block is allow-listed by sha256, nothing is
3
+ // substituted into the pack html), fed PUBLIC view models over the host's socket. This adapter is the whole of what a
4
+ // widget can see:
5
+ // * which widgets exist and are ENABLED (config.plugins['<pack>/<widget>'].enabled — the user's consent from
6
+ // `dexterm pack widget … --enable`; a pack cannot flip it);
7
+ // * the page bytes, read from the installed pack directory only after the pack re-hashes to its install record
8
+ // (a page edited after install is refused), never a symlink, never outside the pack, size-capped, and audited
9
+ // once more for loaders (<script src>, <link>, frames, <meta http-equiv>, @import, the __NONCE__ token);
10
+ // * the per-topic view models (rows · token · narrative · positions · alerts · theme) built from what the
11
+ // terminal already shows. No keys, no signer state, no caps, no mandates, no gate, no pair/source ids.
12
+ // Widgets receive; they cannot ask for anything (the only events a page may send are the generic close/focus/drag).
13
+ import { lstatSync, readFileSync, realpathSync, existsSync } from 'node:fs';
14
+ import { join, resolve, sep } from 'node:path';
15
+ import { checkIntegrity, packsDir, packWidgetKey, WIDGET_REF } from '../../custom/src/index.js';
16
+ import { WIDGET_HTML_MAX, auditWidgetHtml } from '../../popup/src/index.js';
17
+ import { popupThemeFrom } from "./popup.js";
18
+ const REF = new RegExp(WIDGET_REF);
19
+ /** Every widget of every installed pack, with its consent flag. */
20
+ export function installedWidgets(ui) {
21
+ return (ui?.packs?.widgets ?? []).map((w) => ({ key: packWidgetKey(w.pack, w.name), pack: w.pack, name: w.name, dir: w.dir, enabled: w.enabled, manifest: w.manifest }));
22
+ }
23
+ /** Split `<pack>/<widget>[/<id>]`; null when malformed. */
24
+ export function parseWidgetRef(ref) {
25
+ if (!REF.test(ref))
26
+ return null;
27
+ const [pack, name, id] = ref.split('/');
28
+ return { key: `${pack}/${name}`, pack, name, id: id ?? null };
29
+ }
30
+ export function widgetEnabled(ui, ref) {
31
+ const r = parseWidgetRef(ref);
32
+ if (!r)
33
+ return false;
34
+ return installedWidgets(ui).some((w) => w.key === r.key && w.enabled);
35
+ }
36
+ /**
37
+ * The page to serve for a widget ref, or a refusal. Consent (enabled), integrity (the installed pack still hashes to
38
+ * its record), containment (the page is a plain file under the widget directory) and size are all re-checked at
39
+ * open time, not trusted from startup.
40
+ */
41
+ export function widgetPage(ui, ref) {
42
+ const r = parseWidgetRef(ref);
43
+ if (!r)
44
+ return { ok: false, reason: `bad widget ref "${ref}" (<pack>/<widget>[/<id>])` };
45
+ const w = installedWidgets(ui).find((x) => x.key === r.key);
46
+ if (!w)
47
+ return { ok: false, reason: `no installed widget ${r.key} (dexterm pack list)` };
48
+ if (!w.enabled)
49
+ return { ok: false, reason: `${r.key} is not enabled (dexterm pack widget ${r.pack} ${r.name} --enable)` };
50
+ const rec = (ui?.config.packs ?? []).find((p) => p.name === r.pack);
51
+ if (!rec || !ui)
52
+ return { ok: false, reason: `pack ${r.pack} has no install record` };
53
+ const packDir = join(packsDir(ui.home), `${rec.name}@${rec.version}`);
54
+ const integ = checkIntegrity(packDir);
55
+ if (!integ.ok || integ.hash !== rec.hash)
56
+ return { ok: false, reason: `pack ${r.pack} changed since install (${integ.ok ? 'hash differs' : integ.reason}); re-install it` };
57
+ const spec = r.id ? w.manifest.widgets.find((x) => x.id === r.id) : w.manifest.widgets[0];
58
+ if (!spec)
59
+ return { ok: false, reason: `${r.key} has no widget "${r.id}" (${w.manifest.widgets.map((x) => x.id).join(', ')})` };
60
+ const path = resolve(w.dir, spec.page);
61
+ let root;
62
+ try {
63
+ root = realpathSync(w.dir);
64
+ }
65
+ catch {
66
+ return { ok: false, reason: `${r.key}: directory missing` };
67
+ }
68
+ if (!existsSync(path) || lstatSync(path).isSymbolicLink())
69
+ return { ok: false, reason: `${r.key}: page ${spec.page} is missing or a symlink` };
70
+ const real = realpathSync(path);
71
+ if (!real.startsWith(root + sep) || !lstatSync(real).isFile())
72
+ return { ok: false, reason: `${r.key}: page ${spec.page} is outside the widget directory` };
73
+ const html = readFileSync(real, 'utf8');
74
+ if (html.length > WIDGET_HTML_MAX)
75
+ return { ok: false, reason: `${r.key}: page is ${html.length} chars (max ${WIDGET_HTML_MAX})` };
76
+ // r2: install refused loaders already; a pack installed before that check (or a hash-valid page nobody re-inspected)
77
+ // is refused here too, with the reason, before the host ever sees it
78
+ const problems = auditWidgetHtml(html);
79
+ if (problems.length)
80
+ return { ok: false, reason: `${r.key}: page ${spec.page} refused: ${problems.join('; ')}` };
81
+ return { ok: true, page: { key: r.id ? `${r.key}/${r.id}` : `${r.key}/${spec.id}`, title: spec.title, topics: [...spec.topics], html } };
82
+ }
83
+ // ------------------------------------------------------------------ view models (public columns only)
84
+ const FORBIDDEN_KEYS = /^(arm|armed|sign|signer|keys?|caps?|mandates?|gate|secret|token|apiKey|passphrase|seed|privateKey)$/i;
85
+ export function widgetRowsVM(state) {
86
+ const rows = [];
87
+ const seen = new Set();
88
+ for (const r of state.screener.rows) {
89
+ if (!r.mint || r.skeleton || seen.has(r.mint))
90
+ continue;
91
+ seen.add(r.mint);
92
+ rows.push({
93
+ symbol: r.symbol, name: r.name, mint: r.mint, launchpad: r.launchpad,
94
+ price: r.price, mcap: r.mcap, liq: r.liq, vol5m: r.vol5m, vol1h: r.vol1h, vol24: r.vol24,
95
+ chg: { ...r.chg }, ageMin: r.ageMin, holders: r.holders, org: r.org, top10: r.top10,
96
+ flags: r.flags.map((f) => String(f)), verified: r.verified,
97
+ });
98
+ if (rows.length >= 200)
99
+ break;
100
+ }
101
+ return { feed: state.screener.preset, rows, updatedAt: state.now.getTime() };
102
+ }
103
+ export function widgetPositionsVM(state) {
104
+ const rows = [];
105
+ const live = state.live;
106
+ if (live) {
107
+ try {
108
+ for (const p of live.positions.view().rows)
109
+ rows.push({ symbol: p.symbol, mint: p.mint, side: 'long', tokens: p.tokens, valueSol: p.valueSol, unrealizedSol: p.unrealizedSol, unrealizedPct: p.unrealizedPct, book: 'paper' });
110
+ }
111
+ catch { /* no paper book on this run */ }
112
+ const lp = live.livePositions;
113
+ if (lp) {
114
+ try {
115
+ for (const p of lp.positions()) {
116
+ if (p.tokensRaw <= 0n)
117
+ continue;
118
+ const h = lp.held(p.mint);
119
+ rows.push({ symbol: p.symbol, mint: p.mint, side: 'long', tokens: h?.tokens ?? Number(p.tokensRaw) / 10 ** p.decimals, valueSol: null, unrealizedSol: null, unrealizedPct: null, book: 'live' });
120
+ }
121
+ }
122
+ catch { /* ledger unreadable */ }
123
+ }
124
+ }
125
+ return { rows, updatedAt: state.now.getTime() };
126
+ }
127
+ export function widgetAlertsVM(state, limit = 50) {
128
+ const items = [];
129
+ const now = state.now.getTime();
130
+ for (const l of state.rulesUi?.log ?? [])
131
+ if (l.kind === 'alert' || l.kind === 'proposal')
132
+ items.push({ at: l.at, level: l.level, kind: `rule:${l.kind}`, text: l.text, symbol: l.symbol || null });
133
+ const store = state.live?.watch.store;
134
+ if (store) {
135
+ try {
136
+ const symbolOf = (mint) => (mint ? state.screener.rows.find((r) => r.mint === mint)?.symbol ?? null : null);
137
+ for (const e of store.events(limit))
138
+ items.push({ at: e.t, level: e.level, kind: e.kind, text: e.text, symbol: symbolOf(e.mint) });
139
+ }
140
+ catch { /* store closed */ }
141
+ }
142
+ else {
143
+ for (const a of state.fx.alerts)
144
+ items.push({ at: now - a.ago * 1000, level: a.level, kind: a.kind, text: a.text, symbol: null });
145
+ }
146
+ items.sort((a, b) => b.at - a.at);
147
+ return { items: items.slice(0, limit), updatedAt: now };
148
+ }
149
+ /** The payload for one topic right now, or null when that topic has nothing yet (token with no coin open, narrative before the board). */
150
+ export function widgetPayload(state, topic, deps = {}) {
151
+ switch (topic) {
152
+ case 'rows': return widgetRowsVM(state);
153
+ case 'token': return deps.token?.() ?? null;
154
+ case 'narrative': return deps.narrative?.() ?? null;
155
+ case 'positions': return widgetPositionsVM(state);
156
+ case 'alerts': return widgetAlertsVM(state);
157
+ case 'theme': return popupThemeFrom(state.theme);
158
+ default: return null;
159
+ }
160
+ }
161
+ /** Defence in depth for tests and the sync path: no money-shaped key anywhere in a payload. */
162
+ export function payloadKeys(v, acc = []) {
163
+ if (v && typeof v === 'object')
164
+ for (const [k, x] of Object.entries(v)) {
165
+ acc.push(k);
166
+ payloadKeys(x, acc);
167
+ }
168
+ return acc;
169
+ }
170
+ export function payloadIsPublic(v) { return !payloadKeys(v).some((k) => FORBIDDEN_KEYS.test(k)); }
171
+ /** Minimum spacing between pushes of the same topic to the same widget (rows refresh every 10 s anyway). */
172
+ export const WIDGET_SYNC_MS = 1000;
173
+ /**
174
+ * Push fresh view models to every open widget (self-throttled per widget+topic). `force` ignores the throttle
175
+ * (first paint after `ready`, a theme change). Pure over the controller: a missing controller is a no-op.
176
+ */
177
+ export function syncWidgetPopups(state, deps = {}, opts = {}) {
178
+ const p = state.popups;
179
+ if (!p?.openWidgets || !p.syncWidget)
180
+ return 0;
181
+ const open = p.openWidgets();
182
+ if (!open.length)
183
+ return 0;
184
+ const now = state.now.getTime();
185
+ const stamps = (state.widgetSync ??= new Map());
186
+ const cache = new Map();
187
+ let pushed = 0;
188
+ for (const w of open) {
189
+ if (opts.only && w.key !== opts.only)
190
+ continue;
191
+ for (const topic of w.topics) {
192
+ if (opts.topics && !opts.topics.includes(topic))
193
+ continue;
194
+ const stampKey = `${w.key}|${topic}`;
195
+ if (!opts.force && now - (stamps.get(stampKey) ?? -Infinity) < WIDGET_SYNC_MS)
196
+ continue;
197
+ if (!cache.has(topic))
198
+ cache.set(topic, widgetPayload(state, topic, deps));
199
+ const payload = cache.get(topic) ?? null;
200
+ if (!payload || !payloadIsPublic(payload))
201
+ continue;
202
+ p.syncWidget(w.key, topic, payload);
203
+ stamps.set(stampKey, now);
204
+ pushed++;
205
+ }
206
+ }
207
+ return pushed;
208
+ }
@@ -0,0 +1,68 @@
1
+ // `dexterm workspace <list|show|remove|init> …` (task-1358 Tier 1): the workspace files under
2
+ // ~/.dexterm/workspaces/ (DEXTERM_HOME overrides). No network, no model, never touches a key file.
3
+ // list built-ins + files, with errors for files that do not validate
4
+ // show <name> print the workspace JSON (a built-in prints its generated form)
5
+ // remove <name> delete a user file (built-ins cannot be removed)
6
+ // init write `default` as an editable file with the shipped layout trees, if it does not exist
7
+ import { listWorkspaces, readWorkspace, removeWorkspace, writeWorkspace, dextermHome, builtinScreens, defaultWorkspace, workspacesDir } from '../../custom/src/index.js';
8
+ export const WORKSPACE_USAGE = `usage: dexterm workspace list | show <name> | remove <name> | init
9
+ files: ~/.dexterm/workspaces/<name>.json (DEXTERM_HOME overrides); in the app: ctrl+w then w/n/N/s/m/o/d/1-9, or --workspace <name>`;
10
+ export async function workspaceCommand(argv, io) {
11
+ const home = dextermHome(io.env);
12
+ const [cmd, name] = argv;
13
+ const bad = (s) => { io.stderr.write(`dexterm workspace: ${s}\n${WORKSPACE_USAGE}\n`); return 2; };
14
+ switch (cmd) {
15
+ case undefined:
16
+ case 'list': {
17
+ const list = listWorkspaces(home);
18
+ io.stdout.write(`${workspacesDir(home)}\n`);
19
+ for (const w of list) {
20
+ const tag = w.builtin ? 'built-in' : 'file';
21
+ const what = w.error ? `⚠ ${w.error}` : [w.workspace.description, w.workspace.start ? `starts on ${w.workspace.start}` : null, w.workspace.theme ? `theme ${w.workspace.theme}` : null, w.workspace.popups?.length ? `${w.workspace.popups.length} popup(s): ${w.workspace.popups.map((p) => p.kind).join(', ')}` : null].filter(Boolean).join(' · ');
22
+ io.stdout.write(` ${w.name.padEnd(24)} ${tag.padEnd(9)} ${what}\n`);
23
+ }
24
+ return 0;
25
+ }
26
+ case 'show': {
27
+ if (!name)
28
+ return bad('show needs a name');
29
+ const ws = readWorkspace(home, name);
30
+ if (!ws) {
31
+ io.stderr.write(`dexterm workspace: no workspace "${name}" (see: dexterm workspace list)\n`);
32
+ return 1;
33
+ }
34
+ io.stdout.write(JSON.stringify(ws, null, 2) + '\n');
35
+ return 0;
36
+ }
37
+ case 'remove': {
38
+ if (!name)
39
+ return bad('remove needs a name');
40
+ const entry = listWorkspaces(home).find((w) => w.name === name);
41
+ if (!entry) {
42
+ io.stderr.write(`dexterm workspace: no workspace "${name}"\n`);
43
+ return 1;
44
+ }
45
+ if (entry.builtin) {
46
+ io.stderr.write(`dexterm workspace: "${name}" is built in; it cannot be removed (save a file with that name to override it)\n`);
47
+ return 1;
48
+ }
49
+ if (!removeWorkspace(home, name)) {
50
+ io.stderr.write(`dexterm workspace: could not remove ${entry.path}\n`);
51
+ return 1;
52
+ }
53
+ io.stdout.write(`removed ${entry.path}\n`);
54
+ return 0;
55
+ }
56
+ case 'init': {
57
+ const existing = listWorkspaces(home).find((w) => w.name === 'default' && !w.builtin);
58
+ if (existing) {
59
+ io.stdout.write(`${existing.path} already exists\n`);
60
+ return 0;
61
+ }
62
+ const path = writeWorkspace(home, { ...defaultWorkspace(), description: 'The shipped layout as editable pane trees (dexterm workspace init)', screens: builtinScreens() });
63
+ io.stdout.write(`wrote ${path}\n edit the trees (weight / fixed / priority / collapseBelow), then ctrl+w d in the app or --workspace default\n`);
64
+ return 0;
65
+ }
66
+ default: return bad(`unknown subcommand "${cmd}"`);
67
+ }
68
+ }
@@ -0,0 +1,154 @@
1
+ // dexterm — `dexterm x <status|set-client-id|login|logout>` (task-1357, lane I-02). Login with X through
2
+ // OAuth 2.0 PKCE on the fixed loopback callback; each user brings their OWN X app Client ID (their developer
3
+ // account, their rate limits, their bill). Tokens live in ~/.dexterm/x/tokens.json (0600) and are never
4
+ // printed: `status` shows the handle, scopes, ages and file modes only. No `.write`/`dm.*` scope is ever
5
+ // requested; dexterm never posts.
6
+ import { XOAuth, XTokenStore, XAuthError, XApi, XSource, XSpendMeter, KolStore, X_SCOPES, callbackUri, redactXSecrets } from '../../data/src/index.js';
7
+ export const X_HELP = ` dexterm x status Login with X: client id stored? logged in as @handle? scopes, expiry, file modes
8
+ dexterm x set-client-id store YOUR X app's Client ID (prompted on the terminal, or one line on stdin with --stdin)
9
+ dexterm x login OAuth2 PKCE in your browser → tokens in ~/.dexterm/x/tokens.json (0600); read scopes only
10
+ dexterm x logout revoke both tokens at X and delete the local record`;
11
+ const fmtIn = (ms) => {
12
+ if (ms <= 0)
13
+ return 'expired';
14
+ const m = Math.round(ms / 60_000);
15
+ return m < 60 ? `${m} min` : `${Math.round(m / 6) / 10} h`;
16
+ };
17
+ /** Read one line from stdin (piped) or the TTY. Never echoes it back. */
18
+ function readLine(io, prompt) {
19
+ const stdin = io.stdin;
20
+ if (!stdin)
21
+ return Promise.resolve(null);
22
+ return new Promise((resolve) => {
23
+ let buf = '';
24
+ let done = false;
25
+ const finish = (v) => { if (done)
26
+ return; done = true; try {
27
+ stdin.off?.('data', onData);
28
+ }
29
+ catch { /* ignore */ } try {
30
+ stdin.pause?.();
31
+ }
32
+ catch { /* ignore */ } resolve(v); };
33
+ const onData = (chunk) => { buf += chunk.toString(); const nl = buf.indexOf('\n'); if (nl >= 0)
34
+ finish(buf.slice(0, nl).trim()); };
35
+ if (stdin.isTTY)
36
+ io.stderr.write(prompt);
37
+ stdin.setEncoding?.('utf8');
38
+ stdin.on('data', onData);
39
+ stdin.once?.('end', () => finish(buf.trim() || null));
40
+ stdin.resume?.();
41
+ });
42
+ }
43
+ function printStatus(io, store, now) {
44
+ const meta = store.meta();
45
+ const modes = store.modes();
46
+ const clientId = store.clientId(io.env);
47
+ const oct = (m) => (m === null ? '—' : m.toString(8).padStart(3, '0'));
48
+ io.stdout.write(`client id: ${clientId ? `stored (${io.env.DEXTERM_X_CLIENT_ID ? 'DEXTERM_X_CLIENT_ID' : store.clientIdPath})` : 'missing — run `dexterm x set-client-id` (create a Native App with callback ' + callbackUri() + ' in your X developer portal)'}\n`);
49
+ if (!meta.loggedIn) {
50
+ io.stdout.write(`login: not logged in — run \`dexterm x login\`${clientId ? '' : ' after storing the client id'}\n`);
51
+ }
52
+ else {
53
+ io.stdout.write(`login: ${meta.user ? `@${meta.user.username} (id ${meta.user.id})` : 'logged in (handle unknown)'} · scopes ${meta.scopes.join(' ') || '—'} · access token ${meta.expiresAt !== null ? (meta.expiresAt > now ? `valid ${fmtIn(meta.expiresAt - now)}` : 'expired (refreshes on next use)') : '?'} · refresh ${meta.hasRefresh ? 'yes' : 'no (login again)'}\n`);
54
+ }
55
+ io.stdout.write(`files: ${store.dir} (mode ${oct(modes.dir)}) · tokens.json ${modes.tokens === null ? 'absent' : `mode ${oct(modes.tokens)}`} · client-id ${modes.clientId === null ? 'absent' : `mode ${oct(modes.clientId)}`}\n`);
56
+ if ((modes.tokens !== null && modes.tokens !== 0o600) || (modes.dir !== null && modes.dir !== 0o700))
57
+ io.stderr.write('⚠ permissions are looser than 0600/0700; dexterm will tighten them on the next write (or chmod them now)\n');
58
+ io.stdout.write(`callback: ${callbackUri()} · tokens are never printed, logged, or sent to a model · X reads are user-context only (your rate limits, your bill)\n`);
59
+ }
60
+ /** Run the `x` subcommand. Returns a process exit code. */
61
+ export async function runX(argv, io, over = {}) {
62
+ const sub = argv[0] ?? 'status';
63
+ const store = over.store ?? new XTokenStore({ env: io.env });
64
+ const now = over.now ?? (() => Date.now());
65
+ if (sub === 'status') {
66
+ printStatus(io, store, now());
67
+ return 0;
68
+ }
69
+ if (sub === 'set-client-id') {
70
+ const useStdin = argv.includes('--stdin');
71
+ if (!useStdin && !(io.stdin && io.stdin.isTTY)) {
72
+ io.stderr.write('dexterm x set-client-id: paste the id on a terminal, or pipe ONE line with --stdin (never put it on the command line)\n');
73
+ return 2;
74
+ }
75
+ const v = await readLine(io, 'paste your X app Client ID and press enter: ');
76
+ if (!v) {
77
+ io.stderr.write('dexterm x set-client-id: nothing read\n');
78
+ return 2;
79
+ }
80
+ try {
81
+ store.setClientId(v);
82
+ }
83
+ catch (e) {
84
+ io.stderr.write(`dexterm x set-client-id: ${e instanceof Error ? e.message : String(e)}\n`);
85
+ return 2;
86
+ }
87
+ io.stdout.write(`stored (${v.length} chars) at ${store.clientIdPath} (mode 600). Next: dexterm x login\n`);
88
+ return 0;
89
+ }
90
+ if (sub === 'login') {
91
+ const oauth = new XOAuth({ store, endpoints: over.endpoints, open: over.open, port: over.port, loginTimeoutMs: over.loginTimeoutMs, now: over.now });
92
+ if (!store.clientId(io.env)) {
93
+ io.stderr.write(`dexterm x login: no client id stored. Create a Native App (OAuth 2.0, callback ${callbackUri(over.port)}) in your X developer portal, then run \`dexterm x set-client-id\`.\n`);
94
+ return 2;
95
+ }
96
+ io.stdout.write(`Opening your browser to X (scopes: ${X_SCOPES.join(' ')} — read only; dexterm never posts). Callback ${callbackUri(over.port)}.\n`);
97
+ const t = setTimeout(() => { if (oauth.lastAuthorizeUrl)
98
+ io.stderr.write(`If the browser did not open, paste this URL into it:\n${oauth.lastAuthorizeUrl}\n`); }, 4000);
99
+ t.unref?.();
100
+ try {
101
+ const res = await oauth.login({ env: io.env });
102
+ clearTimeout(t);
103
+ io.stdout.write(`✓ logged in${res.user ? ` as @${res.user.username}` : ''} · scopes ${res.scopes.join(' ')} · access token valid ${fmtIn(res.expiresAt - now())} · refresh ${res.hasRefresh ? 'yes' : 'no'} · browser ${Math.round(res.callbackMs / 100) / 10}s · exchange ${res.exchangeMs}ms\n`);
104
+ io.stdout.write(`tokens: ${store.tokensPath} (mode 600). The SOCIAL panel and TRENDING board now read X with your own limits.\n`);
105
+ return 0;
106
+ }
107
+ catch (e) {
108
+ clearTimeout(t);
109
+ const msg = e instanceof Error ? e.message : String(e);
110
+ io.stderr.write(`dexterm x login failed: ${redactXSecrets(msg)}\n`);
111
+ if (e instanceof XAuthError && e.kind === 'exchange' && /invalid_client/.test(msg))
112
+ io.stderr.write(' the Client ID is not one X recognises — check the app in your developer portal and re-run `dexterm x set-client-id`\n');
113
+ if (e instanceof XAuthError && e.kind === 'port-busy')
114
+ io.stderr.write(` port ${over.port ?? 41287} is in use; close the other login (or the process on that port) and retry\n`);
115
+ return 1;
116
+ }
117
+ }
118
+ if (sub === 'logout') {
119
+ const oauth = new XOAuth({ store, endpoints: over.endpoints, open: over.open, port: over.port, now: over.now });
120
+ const out = await oauth.logout(io.env);
121
+ if (!out.hadTokens) {
122
+ io.stdout.write('not logged in; nothing to revoke.\n');
123
+ return 0;
124
+ }
125
+ const parts = Object.entries(out.revoked).map(([k, s]) => `${k.replace('_token', '')} ${s === 200 ? 'revoked' : s === 0 ? 'unreachable' : `HTTP ${s}`}`);
126
+ io.stdout.write(`✓ logged out · ${parts.join(' · ') || 'nothing to revoke'} · local tokens deleted. You can also check x.com/settings/connected_apps.\n`);
127
+ return 0;
128
+ }
129
+ io.stderr.write(`dexterm x: unknown subcommand ${JSON.stringify(sub)} (expected status | set-client-id | login | logout)\n`);
130
+ return 2;
131
+ }
132
+ /**
133
+ * `dexterm kol import-x` (task-1357): the user's OWN X lists → `own-x-list` KOL lists (handles only). Needs a
134
+ * login; a degraded X (401/429/403/budget) is reported as text, never as an empty import.
135
+ */
136
+ export async function importOwnXLists(opts) {
137
+ const store = opts.store ?? new XTokenStore({ env: opts.env });
138
+ if (!store.meta().loggedIn)
139
+ return { ok: false, error: 'not logged in to X — run `dexterm x login` first (your own X app, read scopes only)' };
140
+ const oauth = new XOAuth({ store, endpoints: opts.endpoints, now: opts.now });
141
+ const api = new XApi({ oauth, endpoints: opts.endpoints, cache: opts.cache, spend: new XSpendMeter({ cache: opts.cache, env: opts.env, now: opts.now }), env: opts.env });
142
+ const kol = new KolStore({ cache: opts.cache });
143
+ const x = new XSource({ api, store, kol, now: opts.now });
144
+ try {
145
+ const r = await x.importOwnLists();
146
+ return { ok: true, lists: r.lists };
147
+ }
148
+ catch (e) {
149
+ return { ok: false, error: redactXSecrets(e instanceof Error ? e.message : String(e)) };
150
+ }
151
+ finally {
152
+ x.close();
153
+ }
154
+ }