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,176 @@
1
+ // @dexterm/custom — user themes (task-1358 Tier 2). A theme file is `{ extends: <base>, tokens: {…} }` over
2
+ // the app's colour tokens (SPEC §7.1 + VISUAL-V2 §2); `config.theme.overrides` patches single tokens of
3
+ // whatever theme is active. Semantics never change (green up, red down, amber warn, coral AI): the validator
4
+ // refuses a theme whose text / warnings would fall under a contrast floor against the panel, and one whose
5
+ // `up` reads redder than its `down`, so a SHARED theme cannot hide a warning or make a sell look like a buy.
6
+ // A theme that fails validation is not loaded; the app keeps the current theme and says which token failed.
7
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { validate, THEME_TOKENS, THEME_RAMPS, BASE_THEMES } from "./schema.js";
10
+ export const THEME_FILE = /^[a-z][a-z0-9-]{0,39}\.json$/;
11
+ // ---- contrast (WCAG 2.x relative luminance), a small copy of @dexterm/tui color.ts so the floor is checkable here
12
+ function luminance(hex) {
13
+ const n = parseInt(hex.slice(1), 16);
14
+ const [r, g, b] = [(n >> 16) & 255, (n >> 8) & 255, n & 255].map((c) => { const s = c / 255; return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; });
15
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
16
+ }
17
+ export function contrast(a, b) {
18
+ const x = luminance(a), y = luminance(b);
19
+ return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
20
+ }
21
+ function hue(hex) {
22
+ const n = parseInt(hex.slice(1), 16);
23
+ const r = ((n >> 16) & 255) / 255, g = ((n >> 8) & 255) / 255, b = (n & 255) / 255;
24
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
25
+ if (d === 0)
26
+ return 0;
27
+ let h = max === r ? ((g - b) / d) % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
28
+ h *= 60;
29
+ return h < 0 ? h + 360 : h;
30
+ }
31
+ const isGreenish = (hex) => { const h = hue(hex); return h >= 60 && h <= 200; };
32
+ const isReddish = (hex) => { const h = hue(hex); return h >= 320 || h <= 25; };
33
+ // r5 (verifier): a hue band says nothing about a near-grey tint (#7f807f has hue 120°, #807f7f hue 0°) — up and down must
34
+ // carry real colour and must be far apart to the eye. HSL saturation floor + CIE76 ΔE in Lab between the two.
35
+ function saturation(hex) {
36
+ const n = parseInt(hex.slice(1), 16);
37
+ const r = ((n >> 16) & 255) / 255, g = ((n >> 8) & 255) / 255, b = (n & 255) / 255;
38
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min, l = (max + min) / 2;
39
+ return d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
40
+ }
41
+ function lab(hex) {
42
+ const n = parseInt(hex.slice(1), 16);
43
+ const lin = [(n >> 16) & 255, (n >> 8) & 255, n & 255].map((c) => { const s = c / 255; return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; });
44
+ const [r, g, b] = lin;
45
+ const x = (0.4124 * r + 0.3576 * g + 0.1805 * b) / 0.95047, y = 0.2126 * r + 0.7152 * g + 0.0722 * b, z = (0.0193 * r + 0.1192 * g + 0.9505 * b) / 1.08883;
46
+ const f = (t) => (t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116);
47
+ return [116 * f(y) - 16, 500 * (f(x) - f(y)), 200 * (f(y) - f(z))];
48
+ }
49
+ /** CIE76 colour difference between two hex colours (0 = identical; ~2 just noticeable; built-in up/down pairs are > 80). */
50
+ export function deltaE(a, b) {
51
+ const [l1, a1, b1] = lab(a), [l2, a2, b2] = lab(b);
52
+ return Math.hypot(l1 - l2, a1 - a2, b1 - b2);
53
+ }
54
+ /** The least HSL saturation `up` and `down` may have (the built-ins sit at 0.63–0.91; a grey tint is ≈ 0). */
55
+ export const UP_DOWN_MIN_SATURATION = 0.35;
56
+ /** The least ΔE between `up` and `down` (the built-ins are 80–120; two greys are < 3). */
57
+ export const UP_DOWN_MIN_DELTA_E = 40;
58
+ /** The readability floors every theme must clear (named so a failure can say which pair). */
59
+ export const CONTRAST_FLOORS = [
60
+ { fg: 'text', bg: 'bg', min: 4.5 }, { fg: 'text', bg: 'panel', min: 4.5 },
61
+ { fg: 'muted', bg: 'panel', min: 3 },
62
+ { fg: 'up', bg: 'bg', min: 3 }, { fg: 'down', bg: 'bg', min: 3 }, { fg: 'warn', bg: 'bg', min: 3 },
63
+ { fg: 'ai', bg: 'panel', min: 3 },
64
+ ];
65
+ /** Semantic checks on a fully merged theme: the floors and the up/down hue guard. */
66
+ export function checkThemeSemantics(t) {
67
+ const errors = [];
68
+ const hex = (k) => (typeof t[k] === 'string' && /^#[0-9a-fA-F]{6}$/.test(t[k]) ? t[k] : null);
69
+ for (const f of CONTRAST_FLOORS) {
70
+ const a = hex(f.fg), b = hex(f.bg);
71
+ if (!a || !b)
72
+ continue;
73
+ const c = contrast(a, b);
74
+ if (c < f.min)
75
+ errors.push({ path: `/tokens/${f.fg}`, msg: `${f.fg} on ${f.bg} contrast ${c.toFixed(2)} is under the ${f.min} floor (a theme may not hide ${f.fg === 'warn' ? 'warnings' : f.fg === 'text' ? 'numbers' : f.fg})` });
76
+ }
77
+ // r4 (verifier): the guard used to refuse only up-reddish / down-greenish, so an amber up with an amber down (or any
78
+ // hue outside both bands, or up == down) passed and a SELL could look like a BUY. Now up MUST read green/cyan and down
79
+ // MUST read red/pink — which also forces them apart — exactly what the header of this file promises.
80
+ const up = hex('up'), down = hex('down');
81
+ if (up && !isGreenish(up))
82
+ errors.push({ path: '/tokens/up', msg: `up (${up}, hue ${Math.round(hue(up))}°) must read green/cyan (hue 60–200°) so a buy never looks like a sell${isReddish(up) && down && isGreenish(down) ? ' (up and down are swapped)' : ''}` });
83
+ if (down && !isReddish(down))
84
+ errors.push({ path: '/tokens/down', msg: `down (${down}, hue ${Math.round(hue(down))}°) must read red/pink (hue 320–25°) so a sell never looks like a buy` });
85
+ if (up && down && up.toLowerCase() === down.toLowerCase())
86
+ errors.push({ path: '/tokens/down', msg: `down equals up (${down}): a sell would look like a buy` });
87
+ // r5: the band is not enough — a grey with a green hue is still grey, and two greys read the same
88
+ for (const [k, v] of [['up', up], ['down', down]]) {
89
+ if (v && saturation(v) < UP_DOWN_MIN_SATURATION)
90
+ errors.push({ path: `/tokens/${k}`, msg: `${k} (${v}) is too grey (saturation ${saturation(v).toFixed(2)} < ${UP_DOWN_MIN_SATURATION}): a buy or sell must carry real colour` });
91
+ }
92
+ if (up && down && deltaE(up, down) < UP_DOWN_MIN_DELTA_E)
93
+ errors.push({ path: '/tokens/down', msg: `up (${up}) and down (${down}) are too alike (ΔE ${deltaE(up, down).toFixed(1)} < ${UP_DOWN_MIN_DELTA_E}): a sell would look like a buy` });
94
+ return errors;
95
+ }
96
+ /** Merge a validated user theme over its base. The result carries the user theme's name. */
97
+ export function resolveTheme(doc, bases, overrides = {}) {
98
+ const schemaErrors = validate('userTheme', doc);
99
+ if (schemaErrors.length)
100
+ return { theme: null, errors: schemaErrors };
101
+ const base = bases[doc.extends];
102
+ if (!base)
103
+ return { theme: null, errors: [{ path: '/extends', msg: `unknown base theme ${doc.extends}` }] };
104
+ const merged = { ...structuredClone(base), ...sanitizeTokens(doc.tokens ?? {}), ...sanitizeTokens(overrides), name: doc.name };
105
+ const sem = checkThemeSemantics(merged);
106
+ return sem.length ? { theme: null, errors: sem } : { theme: merged, errors: [] };
107
+ }
108
+ /** Keep only known tokens (schema-validated callers pass clean input; this is defence in depth). */
109
+ function sanitizeTokens(tokens) {
110
+ const out = {};
111
+ for (const [k, v] of Object.entries(tokens)) {
112
+ if (THEME_TOKENS.includes(k) && typeof v === 'string')
113
+ out[k] = v;
114
+ else if (THEME_RAMPS.includes(k) && Array.isArray(v) && v.length === 4)
115
+ out[k] = [...v];
116
+ else if (k === 'logo' && Array.isArray(v) && v.length === 3)
117
+ out[k] = [...v];
118
+ }
119
+ return out;
120
+ }
121
+ /** Apply `config.theme.overrides` to an already-resolved (built-in or user) theme, with the same semantic guard. */
122
+ export function applyOverrides(theme, overrides) {
123
+ if (!overrides || !Object.keys(overrides).length)
124
+ return { theme, errors: [] };
125
+ const errors = validate({ type: 'object', additional: false, props: Object.fromEntries([...THEME_TOKENS.map((t) => [t, 'hex']), ...THEME_RAMPS.map((t) => [t, 'ramp'])]) }, overrides, '/theme/overrides');
126
+ if (errors.length)
127
+ return { theme, errors };
128
+ const merged = { ...theme, ...sanitizeTokens(overrides) };
129
+ const sem = checkThemeSemantics(merged);
130
+ return sem.length ? { theme, errors: sem.map((e) => ({ ...e, path: e.path.replace('/tokens', '/theme/overrides') })) } : { theme: merged, errors: [] };
131
+ }
132
+ export const themesDir = (home) => join(home, 'themes');
133
+ /** User themes: ~/.dexterm/themes/<name>.json (file name = theme name) plus `config.theme.user`. Bad files are reported, not thrown. */
134
+ export function listUserThemes(home, config) {
135
+ const out = [];
136
+ for (const [name, doc] of Object.entries(config?.theme?.user ?? {}))
137
+ out.push({ name, path: null, doc: { ...doc, name } });
138
+ let files = [];
139
+ try {
140
+ files = readdirSync(themesDir(home)).filter((f) => THEME_FILE.test(f)).sort();
141
+ }
142
+ catch {
143
+ return out;
144
+ }
145
+ for (const f of files) {
146
+ const path = join(themesDir(home), f);
147
+ const name = f.slice(0, -5);
148
+ try {
149
+ if (statSync(path).isSymbolicLink())
150
+ continue;
151
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
152
+ if (raw && typeof raw === 'object' && raw.name !== undefined && raw.name !== name)
153
+ throw new Error(`"name" must be "${name}" (the file name)`);
154
+ const doc = { ...raw, name };
155
+ const errors = validate('userTheme', doc);
156
+ if (errors.length)
157
+ throw new Error(`${errors[0].path || '/'} ${errors[0].msg}`);
158
+ if (BASE_THEMES.includes(name))
159
+ throw new Error(`"${name}" is a built-in theme name`);
160
+ const i = out.findIndex((t) => t.name === name);
161
+ const entry = { name, path, doc };
162
+ if (i >= 0)
163
+ out[i] = entry;
164
+ else
165
+ out.push(entry);
166
+ }
167
+ catch (e) {
168
+ out.push({ name, path, doc: { name, extends: 'cyberspace' }, error: e.message });
169
+ }
170
+ }
171
+ return out;
172
+ }
173
+ /** A starter theme document a user can edit (`dexterm theme init <name>`). */
174
+ export function starterTheme(name, base = 'cyberspace') {
175
+ return { name, extends: base, tokens: { accent: '#00f0ff', borderFocus: '#39ff14' } };
176
+ }
@@ -0,0 +1,222 @@
1
+ // @dexterm/custom — PACK WIDGET HTML (task-1358 r2): the install-time copy of the popup host's page audit, so a pack whose
2
+ // widget page carries a loader (the `__NONCE__` token, a <script src>, a <link>, a frame, <meta http-equiv>, `@import`)
3
+ // is refused before it lands — the host refuses the same page again at open and at ticket issue.
4
+ //
5
+ // MIRROR of @dexterm/popup/src/widget-html.ts (the canonical copy, with the reasoning); the two zero-dependency packages
6
+ // do not import each other (like WIDGET_TOPICS), and packages/cli/test/widget-1358.test.ts asserts that everything from
7
+ // the import line down is byte-identical in both files. Edit the popup copy first, then copy it here.
8
+ import { createHash } from 'node:crypto';
9
+ /** Most inline <script> + <style> blocks one widget page may carry (the CSP header lists one hash per block). */
10
+ export const WIDGET_INLINE_MAX = 64;
11
+ /** CR LF and lone CR become LF, as the HTML parser's input-stream preprocessor does before it hashes an element's text. */
12
+ export function normalizeWidgetHtml(html) { return html.replace(/\r\n?/g, '\n'); }
13
+ /** The CSP hash-source for one inline block's exact text. */
14
+ export function cspHash(text) { return `'sha256-${createHash('sha256').update(text, 'utf8').digest('base64')}'`; }
15
+ /** Elements whose whole purpose is loading another document or resource: a widget page has no use for them. */
16
+ const LOADER_TAGS = new Set(['link', 'iframe', 'frame', 'frameset', 'embed', 'object', 'applet', 'base', 'portal', 'fencedframe']);
17
+ const SCRIPT_SRC_ATTRS = new Set(['src', 'href', 'xlink:href']);
18
+ /**
19
+ * Read one start tag at `i` (which points at `<`): tag name + attributes, honouring quoted values (a `>` inside quotes
20
+ * does not end the tag). Returns null when `<` does not open a tag. Mirrors the tokenizer closely enough for the audit:
21
+ * any disagreement makes the browser see an element we did not hash, which the CSP then refuses — never the reverse.
22
+ */
23
+ function readTag(html, i) {
24
+ const m = /^<([a-zA-Z][a-zA-Z0-9:-]*)/.exec(html.slice(i, i + 64));
25
+ if (!m)
26
+ return null;
27
+ const name = m[1].toLowerCase();
28
+ let p = i + m[0].length;
29
+ const attrs = new Map();
30
+ let selfClosing = false;
31
+ for (;;) {
32
+ while (p < html.length && /\s/.test(html[p]))
33
+ p++;
34
+ if (p >= html.length)
35
+ return null;
36
+ if (html[p] === '>') {
37
+ p++;
38
+ break;
39
+ }
40
+ if (html[p] === '/') {
41
+ selfClosing = true;
42
+ p++;
43
+ continue;
44
+ }
45
+ const a = /^([^\s"'>\/=]+)/.exec(html.slice(p, p + 256));
46
+ if (!a) {
47
+ p++;
48
+ continue;
49
+ } // a stray quote or `=`: the tokenizer skips it too
50
+ const key = a[1].toLowerCase();
51
+ p += a[0].length;
52
+ while (p < html.length && /\s/.test(html[p]))
53
+ p++;
54
+ let value = '';
55
+ if (html[p] === '=') {
56
+ p++;
57
+ while (p < html.length && /\s/.test(html[p]))
58
+ p++;
59
+ const q = html[p];
60
+ if (q === '"' || q === "'") {
61
+ const j = html.indexOf(q, p + 1);
62
+ if (j < 0)
63
+ return null;
64
+ value = html.slice(p + 1, j);
65
+ p = j + 1;
66
+ }
67
+ else {
68
+ const v = /^[^\s>]*/.exec(html.slice(p));
69
+ value = v[0];
70
+ p += v[0].length;
71
+ }
72
+ }
73
+ if (!attrs.has(key))
74
+ attrs.set(key, value);
75
+ }
76
+ return { name, attrs, end: p, selfClosing };
77
+ }
78
+ /** Index of the `</name` that closes a raw-text element (script/style), case-insensitive, or -1. */
79
+ function rawTextEnd(html, name, from) {
80
+ const re = new RegExp(`</${name}[\\s/>]`, 'ig');
81
+ re.lastIndex = from;
82
+ const m = re.exec(html);
83
+ return m ? m.index : -1;
84
+ }
85
+ /**
86
+ * The document without its HTML comments — what the parsers actually see. Only top-level `<!-- -->` is a comment: inside
87
+ * <script>/<style> the same characters are raw text (the CSS parser would still honour an `@import` written there).
88
+ */
89
+ function withoutComments(html) {
90
+ let out = '', i = 0;
91
+ while (i < html.length) {
92
+ const lt = html.indexOf('<', i);
93
+ if (lt < 0) {
94
+ out += html.slice(i);
95
+ break;
96
+ }
97
+ out += html.slice(i, lt);
98
+ if (html.startsWith('<!--', lt)) {
99
+ const e = html.indexOf('-->', lt + 4);
100
+ i = e < 0 ? html.length : e + 3;
101
+ continue;
102
+ }
103
+ const tag = readTag(html, lt);
104
+ if (!tag) {
105
+ out += '<';
106
+ i = lt + 1;
107
+ continue;
108
+ }
109
+ if ((tag.name === 'script' || tag.name === 'style') && !tag.selfClosing) {
110
+ const e = rawTextEnd(html, tag.name, tag.end);
111
+ const end = e < 0 ? html.length : e;
112
+ out += html.slice(lt, end);
113
+ i = end;
114
+ continue;
115
+ }
116
+ out += html.slice(lt, tag.end);
117
+ i = tag.end;
118
+ }
119
+ return out;
120
+ }
121
+ /** Every start tag in the document, with the raw text of script/style elements (comments are skipped). */
122
+ function* walk(html) {
123
+ let i = 0;
124
+ while (i < html.length) {
125
+ const lt = html.indexOf('<', i);
126
+ if (lt < 0)
127
+ return;
128
+ if (html.startsWith('<!--', lt)) {
129
+ const e = html.indexOf('-->', lt + 4);
130
+ i = e < 0 ? html.length : e + 3;
131
+ continue;
132
+ }
133
+ const tag = readTag(html, lt);
134
+ if (!tag) {
135
+ i = lt + 1;
136
+ continue;
137
+ }
138
+ if ((tag.name === 'script' || tag.name === 'style') && !tag.selfClosing) {
139
+ const e = rawTextEnd(html, tag.name, tag.end);
140
+ const raw = html.slice(tag.end, e < 0 ? html.length : e);
141
+ yield { tag, raw };
142
+ i = e < 0 ? html.length : e + 1;
143
+ }
144
+ else {
145
+ yield { tag };
146
+ i = tag.end;
147
+ }
148
+ }
149
+ }
150
+ /**
151
+ * The reasons a widget page is refused (empty = acceptable). Pure over the text; the CSP header is still the enforcement
152
+ * for everything this does not model (inline handlers, remote images, forms, runtime injection) — this names the things
153
+ * whose only purpose is loading code or documents from elsewhere, so a pack that tries is refused before it lands.
154
+ */
155
+ export function auditWidgetHtml(html) {
156
+ const out = [];
157
+ const text = withoutComments(html); // an author may write about these in a comment; a parser never sees one
158
+ if (text.includes('__NONCE__'))
159
+ out.push('"__NONCE__" is not substituted into a pack page (write plain <script>/<style>: each block is allow-listed by its hash)');
160
+ let blocks = 0;
161
+ const seen = new Set();
162
+ const push = (s) => { if (!seen.has(s)) {
163
+ seen.add(s);
164
+ out.push(s);
165
+ } };
166
+ // r4: `@import` is a CSS construct — it is refused where CSS is parsed (a <style> block, a style="" attribute), not in
167
+ // a script string or prose (the verifier's probe pages tripped on the literal in JS text). A runtime-built <style> has
168
+ // no hash and an @import inserted through CSSOM is a stylesheet fetch with no style-src source: the header holds there.
169
+ const css = (s, where) => {
170
+ if (/@import\b/i.test(s))
171
+ push(`@import in ${where} (a stylesheet from elsewhere) is refused`);
172
+ if (/@\\/.test(s))
173
+ push(`an escaped CSS at-rule name (@\\…) in ${where} is refused`);
174
+ };
175
+ for (const { tag, raw } of walk(html)) {
176
+ if (tag.name === 'script') {
177
+ for (const a of SCRIPT_SRC_ATTRS)
178
+ if (tag.attrs.has(a))
179
+ push(`<script ${a}=…> (a script from elsewhere) is refused: every script is inline`);
180
+ if (raw !== undefined && raw.length)
181
+ blocks++;
182
+ }
183
+ else if (tag.name === 'style') {
184
+ if (raw !== undefined && raw.length) {
185
+ blocks++;
186
+ css(raw, 'a <style> block');
187
+ }
188
+ }
189
+ else if (LOADER_TAGS.has(tag.name))
190
+ push(`<${tag.name}> is refused (it loads another document or resource)`);
191
+ else if (tag.name === 'meta' && tag.attrs.has('http-equiv'))
192
+ push('<meta http-equiv> is refused (headers are the host\'s)');
193
+ if (tag.attrs.has('style'))
194
+ css(tag.attrs.get('style') ?? '', 'a style attribute');
195
+ }
196
+ if (blocks > WIDGET_INLINE_MAX)
197
+ out.push(`${blocks} inline script/style blocks (max ${WIDGET_INLINE_MAX})`);
198
+ return out;
199
+ }
200
+ /**
201
+ * The CSP hash-sources for the page's inline blocks, deduplicated, in document order. Call it on the EXACT text that is
202
+ * served (after normalizeWidgetHtml); a block the browser sees differently simply does not run.
203
+ */
204
+ export function inlineHashes(html) {
205
+ const scripts = [], styles = [];
206
+ for (const { tag, raw } of walk(html)) {
207
+ if (raw === undefined || !raw.length)
208
+ continue;
209
+ // a <script src> ignores its text in the browser (and the audit refused it anyway): nothing to hash
210
+ if (tag.name === 'script' && !tag.attrs.has('src')) {
211
+ const h = cspHash(raw);
212
+ if (!scripts.includes(h))
213
+ scripts.push(h);
214
+ }
215
+ else if (tag.name === 'style') {
216
+ const h = cspHash(raw);
217
+ if (!styles.includes(h))
218
+ styles.push(h);
219
+ }
220
+ }
221
+ return { scripts, styles };
222
+ }
@@ -0,0 +1,141 @@
1
+ // @dexterm/custom — named workspaces (task-1358 Tier 1). A workspace is a JSON document: where to land
2
+ // (screen), the theme / intensity to use, the screener preset/filter/sort, token-panel toggles, per-screen
3
+ // pane trees for the layout solver, and the HUD popups to open together (task-1290 windows). The shipped
4
+ // `default` reproduces today's screens byte for byte; `morning-hud` is the north-star "show me everything".
5
+ //
6
+ // Workspaces hold NO keys, NO trading settings and NO code: the schema is additional:false and a pack or a
7
+ // Jarvis proposal that tries to smuggle anything else is refused by name (schema.ts). Switching a workspace
8
+ // is a UI action: it can navigate, re-theme and open read-only popups; it can never arm, sign or confirm.
9
+ import { validate, POPUP_KINDS, WORKSPACE_SCREENS } from "./schema.js";
10
+ import { breakpoint, solve, treeFor, widgetsIn, widgetSpec } from "./layout.js";
11
+ export const DEFAULT_WORKSPACE = 'default';
12
+ export const MORNING_HUD = 'morning-hud';
13
+ /**
14
+ * The shipped layout. It carries NO `screens` trees: a workspace without trees means "the renderers' own
15
+ * numbers" (spotlight 44 at ≥ 140 cols, chart 46 %, analyst max(46, 34 %)), which keeps every snapshot golden
16
+ * byte-identical. `builtinScreens()` is the editable tree form of the same layout (`dexterm workspace init`).
17
+ */
18
+ export function defaultWorkspace() {
19
+ return { name: DEFAULT_WORKSPACE, description: 'The shipped layout (SPEC §4)', popups: [], start: 'screener' };
20
+ }
21
+ /**
22
+ * The north-star morning HUD (ROADMAP): "yo jarvis, show me everything" → the narrative board on the
23
+ * terminal, with the narrative board, the social/news feeds, the order plans and the chart of the highlighted
24
+ * coin as HUD windows. (Popup kinds are the host's: there is no separate positions/alerts window; plans
25
+ * carries the PAPER plan rows, the chart carries the held position, feeds carries KOL/news + alerts-worthy posts.)
26
+ */
27
+ export function morningHudWorkspace() {
28
+ return {
29
+ name: MORNING_HUD,
30
+ description: 'Morning HUD: narrative board + feeds + order plans + chart popups, trenches feed on the screener',
31
+ start: 'narrative',
32
+ screener: { preset: 'trench' },
33
+ popups: [
34
+ { kind: 'narrative', x: 0.62, y: 0.04, w: 0.36, h: 0.46 },
35
+ { kind: 'feeds', x: 0.62, y: 0.52, w: 0.36, h: 0.44 },
36
+ { kind: 'plans', x: 0.02, y: 0.56, w: 0.28, h: 0.4 },
37
+ { kind: 'chart', mint: 'selected', x: 0.32, y: 0.56, w: 0.28, h: 0.4 },
38
+ ],
39
+ };
40
+ }
41
+ export function builtinWorkspaces() { return [defaultWorkspace(), morningHudWorkspace()]; }
42
+ /**
43
+ * Schema + semantics: every widget a tree names must exist (built-in, or a `plugin:` id present in `registry`),
44
+ * so a shared workspace cannot name `shell.exec` and hope a renderer picks it up.
45
+ */
46
+ export function validateWorkspace(ws, registry = {}) {
47
+ const errors = validate('workspace', ws);
48
+ if (errors.length)
49
+ return errors;
50
+ const screens = ws.screens ?? {};
51
+ for (const [screen, layout] of Object.entries(screens)) {
52
+ const trees = 'kind' in layout ? [layout] : ['wide', 'standard', 'compact'].map((b) => layout[b]).filter((t) => !!t);
53
+ for (const t of trees)
54
+ for (const id of widgetsIn(t))
55
+ if (!widgetSpec(id, registry))
56
+ errors.push({ path: `/screens/${screen}`, msg: `unknown widget "${id}"` });
57
+ }
58
+ // task-1358 Tier 5: a `widget` popup must name the pack widget; other kinds must not carry one
59
+ (ws.popups ?? []).forEach((p, i) => {
60
+ if (p.kind === 'widget' && !p.widget)
61
+ errors.push({ path: `/popups/${i}`, msg: 'a widget popup needs "widget": "<pack>/<widget>"' });
62
+ if (p.kind !== 'widget' && p.widget !== undefined)
63
+ errors.push({ path: `/popups/${i}/widget`, msg: `only kind "widget" takes a widget ref` });
64
+ });
65
+ return errors;
66
+ }
67
+ /** Parse + validate a workspace document; throws a one-line error naming the first problem. */
68
+ export function parseWorkspace(text, source = 'workspace') {
69
+ let raw;
70
+ try {
71
+ raw = JSON.parse(text);
72
+ }
73
+ catch (e) {
74
+ throw new Error(`${source}: not valid JSON (${e.message})`);
75
+ }
76
+ const errors = validateWorkspace(raw);
77
+ if (errors.length)
78
+ throw new Error(`${source}: ${errors[0].path || '/'} ${errors[0].msg}`);
79
+ return raw;
80
+ }
81
+ /** The screen the workspace lands on (start, else the screener). */
82
+ export function workspaceStart(ws) { return ws.start ?? 'screener'; }
83
+ const DEFAULT_NUMBERS = (w, h) => {
84
+ const bp = breakpoint(w);
85
+ return { spotlightW: bp === 'wide' ? 44 : 0, pairSpotlightW: bp === 'wide' ? 46 : 0, chartFrac: 0.46, analystW: bp === 'compact' ? 0 : bp === 'wide' ? Math.max(46, Math.floor(w * 0.34)) : 40 };
86
+ };
87
+ export function solveScreen(ws, screen, rect) {
88
+ const tree = treeFor(ws?.screens?.[screen], breakpoint(rect.w));
89
+ return tree ? solve(tree, rect) : null;
90
+ }
91
+ /** Solve the workspace for a terminal size and read the renderer numbers off the panes (defaults when a tree is absent). */
92
+ export function layoutNumbers(ws, w, h) {
93
+ const base = DEFAULT_NUMBERS(w, h);
94
+ if (!ws?.screens)
95
+ return base;
96
+ const body = { x: 0, y: 2, w, h: Math.max(1, h - 3) };
97
+ const out = { ...base };
98
+ const scr = solveScreen(ws, 'screener', body);
99
+ if (scr)
100
+ out.spotlightW = scr.panes.find((p) => p.widget === 'screener.spotlight')?.rect.w ?? 0;
101
+ const np = solveScreen(ws, 'newpairs', body);
102
+ if (np)
103
+ out.pairSpotlightW = np.panes.find((p) => p.widget === 'newpairs.spotlight')?.rect.w ?? 0;
104
+ const tk = solveScreen(ws, 'token', body);
105
+ if (tk) {
106
+ const chart = tk.panes.find((p) => p.widget === 'token.chart');
107
+ if (chart)
108
+ out.chartFrac = Math.max(0.2, Math.min(0.85, chart.rect.h / body.h));
109
+ out.analystW = tk.panes.find((p) => p.widget === 'analyst.panel')?.rect.w ?? 0;
110
+ // the analyst panel is a tree choice; the renderer still hides it under --no-ai (state.aiOpen === false)
111
+ }
112
+ return out;
113
+ }
114
+ export function captureWorkspace(name, snap) {
115
+ const start = WORKSPACE_SCREENS.includes(snap.screen) ? snap.screen : 'screener';
116
+ const ws = { name, description: `saved from the ${snap.screen} screen`, start, theme: snap.theme, intensity: snap.intensity };
117
+ if (snap.priceMode && snap.priceMode !== 'price')
118
+ ws.priceMode = snap.priceMode;
119
+ if (snap.screens)
120
+ ws.screens = structuredClone(snap.screens);
121
+ const sc = {};
122
+ if (snap.screener?.preset)
123
+ sc.preset = snap.screener.preset;
124
+ if (snap.screener?.filter)
125
+ sc.filter = snap.screener.filter.slice(0, 200);
126
+ if (snap.screener?.sort)
127
+ sc.sort = snap.screener.sort;
128
+ if (Object.keys(sc).length)
129
+ ws.screener = sc;
130
+ if (snap.token && Object.keys(snap.token).length)
131
+ ws.token = { ...snap.token };
132
+ if (snap.popups?.length || snap.widgets?.length)
133
+ ws.popups = [
134
+ ...(snap.popups ?? []).filter((k) => k !== 'widget').map((kind) => ({ kind, ...(kind === 'chart' || kind === 'bubbles' || kind === 'token' ? { mint: 'selected' } : {}) })),
135
+ ...(snap.widgets ?? []).map((widget) => ({ kind: 'widget', widget })),
136
+ ];
137
+ const errors = validateWorkspace(ws);
138
+ if (errors.length)
139
+ throw new Error(`${errors[0].path || '/'} ${errors[0].msg}`);
140
+ return ws;
141
+ }