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.
- package/LICENSE +661 -0
- package/dist/packages/agents/src/audit.js +122 -0
- package/dist/packages/agents/src/bus.js +394 -0
- package/dist/packages/agents/src/executor.js +202 -0
- package/dist/packages/agents/src/gate.js +179 -0
- package/dist/packages/agents/src/identity.js +218 -0
- package/dist/packages/agents/src/index.js +15 -0
- package/dist/packages/agents/src/proposals.js +153 -0
- package/dist/packages/agents/src/schema.js +87 -0
- package/dist/packages/agents/src/sdk.js +140 -0
- package/dist/packages/agents/src/tiers.js +119 -0
- package/dist/packages/agents/src/tools.js +72 -0
- package/dist/packages/agents/src/ws.js +158 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
- package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +75 -0
- package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +74 -0
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
- package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
- package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
- package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
- package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
- package/dist/packages/cli/bin/dexterm.mjs +11 -0
- package/dist/packages/cli/src/agent-cmd.js +336 -0
- package/dist/packages/cli/src/agent-roster.js +26 -0
- package/dist/packages/cli/src/alerts/adapters/desktop.js +40 -0
- package/dist/packages/cli/src/alerts/adapters/discord.js +36 -0
- package/dist/packages/cli/src/alerts/adapters/telegram.js +43 -0
- package/dist/packages/cli/src/alerts/adapters/toast.js +16 -0
- package/dist/packages/cli/src/alerts/creds.js +83 -0
- package/dist/packages/cli/src/alerts/format.js +41 -0
- package/dist/packages/cli/src/alerts/index.js +10 -0
- package/dist/packages/cli/src/alerts/notifier.js +53 -0
- package/dist/packages/cli/src/alerts/route.js +19 -0
- package/dist/packages/cli/src/alerts/types.js +1 -0
- package/dist/packages/cli/src/alerts-cmd.js +212 -0
- package/dist/packages/cli/src/analyst/backend.js +526 -0
- package/dist/packages/cli/src/analyst/results.js +38 -0
- package/dist/packages/cli/src/analyst/session.js +319 -0
- package/dist/packages/cli/src/audit.js +51 -0
- package/dist/packages/cli/src/backtest-cmd.js +208 -0
- package/dist/packages/cli/src/boot-state.js +62 -0
- package/dist/packages/cli/src/coin-target.js +2 -0
- package/dist/packages/cli/src/custom.js +261 -0
- package/dist/packages/cli/src/display.js +191 -0
- package/dist/packages/cli/src/doctor.js +375 -0
- package/dist/packages/cli/src/export.js +235 -0
- package/dist/packages/cli/src/feed.js +634 -0
- package/dist/packages/cli/src/fixtures.js +275 -0
- package/dist/packages/cli/src/follow-cmd.js +221 -0
- package/dist/packages/cli/src/images-config.js +59 -0
- package/dist/packages/cli/src/index.js +31 -0
- package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
- package/dist/packages/cli/src/larp-record.js +21 -0
- package/dist/packages/cli/src/live/copy.js +80 -0
- package/dist/packages/cli/src/live/flow.js +233 -0
- package/dist/packages/cli/src/live/index.js +511 -0
- package/dist/packages/cli/src/live/intel.js +227 -0
- package/dist/packages/cli/src/live/newpairs.js +334 -0
- package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
- package/dist/packages/cli/src/live/screens/token-live.js +459 -0
- package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
- package/dist/packages/cli/src/live/token.js +585 -0
- package/dist/packages/cli/src/live/trade.js +929 -0
- package/dist/packages/cli/src/live/watchlist.js +178 -0
- package/dist/packages/cli/src/main.js +5785 -0
- package/dist/packages/cli/src/mcp-cmd.js +326 -0
- package/dist/packages/cli/src/narrative-cmd.js +76 -0
- package/dist/packages/cli/src/pack-cmd.js +323 -0
- package/dist/packages/cli/src/plan-cmd.js +278 -0
- package/dist/packages/cli/src/popup.js +810 -0
- package/dist/packages/cli/src/rules-cmd.js +131 -0
- package/dist/packages/cli/src/rules.js +358 -0
- package/dist/packages/cli/src/scan/command.js +57 -0
- package/dist/packages/cli/src/scan/engine.js +419 -0
- package/dist/packages/cli/src/scan/observe.js +64 -0
- package/dist/packages/cli/src/scan/ui.js +98 -0
- package/dist/packages/cli/src/screener/filter.js +241 -0
- package/dist/packages/cli/src/screener/presets.js +80 -0
- package/dist/packages/cli/src/screener/rows.js +146 -0
- package/dist/packages/cli/src/screener/rug.js +50 -0
- package/dist/packages/cli/src/screener/sort.js +52 -0
- package/dist/packages/cli/src/screener/view.js +183 -0
- package/dist/packages/cli/src/screens/analyst-session.js +343 -0
- package/dist/packages/cli/src/screens/analyst.js +326 -0
- package/dist/packages/cli/src/screens/boot.js +74 -0
- package/dist/packages/cli/src/screens/bubbles.js +255 -0
- package/dist/packages/cli/src/screens/coin.js +33 -0
- package/dist/packages/cli/src/screens/follows.js +201 -0
- package/dist/packages/cli/src/screens/kol.js +90 -0
- package/dist/packages/cli/src/screens/narrative.js +90 -0
- package/dist/packages/cli/src/screens/newpairs.js +150 -0
- package/dist/packages/cli/src/screens/overlay.js +257 -0
- package/dist/packages/cli/src/screens/plans.js +126 -0
- package/dist/packages/cli/src/screens/positions.js +176 -0
- package/dist/packages/cli/src/screens/rules.js +170 -0
- package/dist/packages/cli/src/screens/screener.js +459 -0
- package/dist/packages/cli/src/screens/social-panel.js +107 -0
- package/dist/packages/cli/src/screens/ticket.js +342 -0
- package/dist/packages/cli/src/screens/token.js +312 -0
- package/dist/packages/cli/src/screens/trending.js +181 -0
- package/dist/packages/cli/src/screens/watchlist.js +104 -0
- package/dist/packages/cli/src/skill-eval-build.js +109 -0
- package/dist/packages/cli/src/social/demo.js +96 -0
- package/dist/packages/cli/src/social/view.js +250 -0
- package/dist/packages/cli/src/state.js +463 -0
- package/dist/packages/cli/src/theme-cmd.js +126 -0
- package/dist/packages/cli/src/trade.js +319 -0
- package/dist/packages/cli/src/update.js +294 -0
- package/dist/packages/cli/src/version.js +3 -0
- package/dist/packages/cli/src/voice-cmd.js +119 -0
- package/dist/packages/cli/src/voice-hud.js +42 -0
- package/dist/packages/cli/src/wallet-cmd.js +440 -0
- package/dist/packages/cli/src/wallet-command.js +31 -0
- package/dist/packages/cli/src/watch-cmd.js +190 -0
- package/dist/packages/cli/src/widgets.js +208 -0
- package/dist/packages/cli/src/workspace-cmd.js +68 -0
- package/dist/packages/cli/src/x-cmd.js +154 -0
- package/dist/packages/custom/src/config.js +213 -0
- package/dist/packages/custom/src/index.js +10 -0
- package/dist/packages/custom/src/keymap.js +107 -0
- package/dist/packages/custom/src/layout.js +235 -0
- package/dist/packages/custom/src/packs.js +633 -0
- package/dist/packages/custom/src/rules.js +704 -0
- package/dist/packages/custom/src/schema.js +538 -0
- package/dist/packages/custom/src/themes.js +176 -0
- package/dist/packages/custom/src/widget-html.js +222 -0
- package/dist/packages/custom/src/workspace.js +141 -0
- package/dist/packages/data/src/analyst-actions.js +133 -0
- package/dist/packages/data/src/budget.js +88 -0
- package/dist/packages/data/src/cache.js +142 -0
- package/dist/packages/data/src/candles.js +168 -0
- package/dist/packages/data/src/clock.js +64 -0
- package/dist/packages/data/src/coin-target.js +82 -0
- package/dist/packages/data/src/credits.js +137 -0
- package/dist/packages/data/src/detail.js +97 -0
- package/dist/packages/data/src/fast-lane.js +321 -0
- package/dist/packages/data/src/fixtures.js +135 -0
- package/dist/packages/data/src/health.js +245 -0
- package/dist/packages/data/src/http.js +277 -0
- package/dist/packages/data/src/images/cache.js +141 -0
- package/dist/packages/data/src/images/decode-worker.js +28 -0
- package/dist/packages/data/src/images/decode.js +81 -0
- package/dist/packages/data/src/images/fetch.js +162 -0
- package/dist/packages/data/src/images/gif.js +214 -0
- package/dist/packages/data/src/images/index.js +10 -0
- package/dist/packages/data/src/images/jpeg.js +544 -0
- package/dist/packages/data/src/images/png.js +310 -0
- package/dist/packages/data/src/images/service.js +334 -0
- package/dist/packages/data/src/images/types.js +27 -0
- package/dist/packages/data/src/index.js +211 -0
- package/dist/packages/data/src/intel/helius-history.js +70 -0
- package/dist/packages/data/src/intel/holder-class.js +125 -0
- package/dist/packages/data/src/intel/rules.js +421 -0
- package/dist/packages/data/src/intel/scan.js +170 -0
- package/dist/packages/data/src/larp/evidence.js +288 -0
- package/dist/packages/data/src/larp/link-baseline.js +196 -0
- package/dist/packages/data/src/larp/links.js +137 -0
- package/dist/packages/data/src/larp/robots.js +90 -0
- package/dist/packages/data/src/larp/safe-fetch.js +389 -0
- package/dist/packages/data/src/limiter.js +341 -0
- package/dist/packages/data/src/model.js +12 -0
- package/dist/packages/data/src/paper.js +331 -0
- package/dist/packages/data/src/rpc-config.js +126 -0
- package/dist/packages/data/src/rpc-pool.js +166 -0
- package/dist/packages/data/src/social/chain.js +83 -0
- package/dist/packages/data/src/social/dedupe.js +205 -0
- package/dist/packages/data/src/social/feed.js +462 -0
- package/dist/packages/data/src/social/firewall.js +110 -0
- package/dist/packages/data/src/social/index.js +11 -0
- package/dist/packages/data/src/social/kol.js +269 -0
- package/dist/packages/data/src/social/model.js +11 -0
- package/dist/packages/data/src/social/rank.js +184 -0
- package/dist/packages/data/src/social/rss.js +113 -0
- package/dist/packages/data/src/social/summary.js +104 -0
- package/dist/packages/data/src/social/venues.js +186 -0
- package/dist/packages/data/src/sources/dexscreener.js +184 -0
- package/dist/packages/data/src/sources/geckoterminal.js +133 -0
- package/dist/packages/data/src/sources/goplus.js +161 -0
- package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
- package/dist/packages/data/src/sources/jupiter.js +198 -0
- package/dist/packages/data/src/sources/pda.js +113 -0
- package/dist/packages/data/src/sources/pump-curve.js +58 -0
- package/dist/packages/data/src/sources/pumpportal.js +252 -0
- package/dist/packages/data/src/sources/rugcheck.js +71 -0
- package/dist/packages/data/src/sources/solana-rpc.js +299 -0
- package/dist/packages/data/src/sources/solana-ws.js +287 -0
- package/dist/packages/data/src/store.js +289 -0
- package/dist/packages/data/src/trade/bridge.js +464 -0
- package/dist/packages/data/src/trade/caps.js +323 -0
- package/dist/packages/data/src/trade/copy/auto.js +385 -0
- package/dist/packages/data/src/trade/copy/follows.js +103 -0
- package/dist/packages/data/src/trade/copy/index.js +9 -0
- package/dist/packages/data/src/trade/copy/mirror.js +91 -0
- package/dist/packages/data/src/trade/copy/stats.js +120 -0
- package/dist/packages/data/src/trade/copy/tracker.js +158 -0
- package/dist/packages/data/src/trade/copy/trades.js +87 -0
- package/dist/packages/data/src/trade/copy/types.js +6 -0
- package/dist/packages/data/src/trade/executor.js +933 -0
- package/dist/packages/data/src/trade/kill-switch.js +47 -0
- package/dist/packages/data/src/trade/link-store.js +52 -0
- package/dist/packages/data/src/trade/plans/confirm.js +157 -0
- package/dist/packages/data/src/trade/plans/engine.js +784 -0
- package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
- package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
- package/dist/packages/data/src/trade/plans/executor.js +11 -0
- package/dist/packages/data/src/trade/plans/index.js +12 -0
- package/dist/packages/data/src/trade/plans/journal.js +77 -0
- package/dist/packages/data/src/trade/plans/mandate.js +129 -0
- package/dist/packages/data/src/trade/plans/plan.js +179 -0
- package/dist/packages/data/src/trade/plans/service.js +168 -0
- package/dist/packages/data/src/trade/plans/types.js +6 -0
- package/dist/packages/data/src/trade/rpc-exec.js +229 -0
- package/dist/packages/data/src/trade/signer/client.js +197 -0
- package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
- package/dist/packages/data/src/trade/signer/host.js +97 -0
- package/dist/packages/data/src/trade/signer/main.js +121 -0
- package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
- package/dist/packages/data/src/trade/signer/protocol.js +92 -0
- package/dist/packages/data/src/trade/signer/service.js +400 -0
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
- package/dist/packages/data/src/trade/sol-usd.js +86 -0
- package/dist/packages/data/src/trade/swap-build.js +108 -0
- package/dist/packages/data/src/trade/token-delta.js +201 -0
- package/dist/packages/data/src/trade/tx-decode.js +632 -0
- package/dist/packages/data/src/util.js +24 -0
- package/dist/packages/data/src/watchlist.js +176 -0
- package/dist/packages/data/src/x/api.js +256 -0
- package/dist/packages/data/src/x/budget.js +66 -0
- package/dist/packages/data/src/x/config.js +49 -0
- package/dist/packages/data/src/x/index.js +10 -0
- package/dist/packages/data/src/x/mock-server.js +217 -0
- package/dist/packages/data/src/x/oauth.js +242 -0
- package/dist/packages/data/src/x/pkce.js +21 -0
- package/dist/packages/data/src/x/source.js +285 -0
- package/dist/packages/data/src/x/token-store.js +134 -0
- package/dist/packages/data/src/x/transport.js +71 -0
- package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
- package/dist/packages/mcp/assets/cases.json +1194 -0
- package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
- package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
- package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
- package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
- package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
- package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
- package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
- package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
- package/dist/packages/mcp/src/dossier.js +78 -0
- package/dist/packages/mcp/src/firewall.js +21 -0
- package/dist/packages/mcp/src/index.js +22 -0
- package/dist/packages/mcp/src/server.js +936 -0
- package/dist/packages/mcp/src/user-content.js +434 -0
- package/dist/packages/packs/src/backtest.js +183 -0
- package/dist/packages/packs/src/digest.js +95 -0
- package/dist/packages/packs/src/firewall.js +57 -0
- package/dist/packages/packs/src/index.js +20 -0
- package/dist/packages/packs/src/larp-eval.js +100 -0
- package/dist/packages/packs/src/narrative.js +309 -0
- package/dist/packages/packs/src/reason.js +135 -0
- package/dist/packages/packs/src/rug-model.js +137 -0
- package/dist/packages/packs/src/schema.js +54 -0
- package/dist/packages/packs/src/skill-backtest.js +300 -0
- package/dist/packages/packs/src/token-dossier.js +174 -0
- package/dist/packages/packs/src/verdicts.js +17 -0
- package/dist/packages/packs/src/wallet-dossier.js +105 -0
- package/dist/packages/popup/src/config.js +71 -0
- package/dist/packages/popup/src/dock.js +271 -0
- package/dist/packages/popup/src/host.js +210 -0
- package/dist/packages/popup/src/index.js +12 -0
- package/dist/packages/popup/src/loopback.js +53 -0
- package/dist/packages/popup/src/open.js +183 -0
- package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
- package/dist/packages/popup/src/pages/bubbles.css +23 -0
- package/dist/packages/popup/src/pages/bubbles.html +36 -0
- package/dist/packages/popup/src/pages/bubbles.js +214 -0
- package/dist/packages/popup/src/pages/chart-lib.js +262 -0
- package/dist/packages/popup/src/pages/chart.css +50 -0
- package/dist/packages/popup/src/pages/chart.html +56 -0
- package/dist/packages/popup/src/pages/chart.js +554 -0
- package/dist/packages/popup/src/pages/common.css +34 -0
- package/dist/packages/popup/src/pages/common.js +187 -0
- package/dist/packages/popup/src/pages/feeds.html +20 -0
- package/dist/packages/popup/src/pages/feeds.js +77 -0
- package/dist/packages/popup/src/pages/narrative.html +20 -0
- package/dist/packages/popup/src/pages/narrative.js +61 -0
- package/dist/packages/popup/src/pages/plans.css +30 -0
- package/dist/packages/popup/src/pages/plans.html +26 -0
- package/dist/packages/popup/src/pages/plans.js +82 -0
- package/dist/packages/popup/src/pages/roster.css +41 -0
- package/dist/packages/popup/src/pages/roster.html +25 -0
- package/dist/packages/popup/src/pages/roster.js +75 -0
- package/dist/packages/popup/src/pages/token.html +20 -0
- package/dist/packages/popup/src/pages/token.js +112 -0
- package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
- package/dist/packages/popup/src/pages/widget.css +5 -0
- package/dist/packages/popup/src/pages/widget.html +21 -0
- package/dist/packages/popup/src/pages/widget.js +85 -0
- package/dist/packages/popup/src/protocol.js +31 -0
- package/dist/packages/popup/src/selftest.js +156 -0
- package/dist/packages/popup/src/server.js +582 -0
- package/dist/packages/popup/src/sinkproxy.js +167 -0
- package/dist/packages/popup/src/widget-html.js +231 -0
- package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
- package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
- package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
- package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
- package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
- package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
- package/dist/packages/recorder/src/cli.js +113 -0
- package/dist/packages/recorder/src/export.js +71 -0
- package/dist/packages/recorder/src/index.js +20 -0
- package/dist/packages/recorder/src/labels.js +151 -0
- package/dist/packages/recorder/src/open.js +29 -0
- package/dist/packages/recorder/src/recorder.js +553 -0
- package/dist/packages/recorder/src/schedule.js +43 -0
- package/dist/packages/recorder/src/schema.js +135 -0
- package/dist/packages/recorder/src/snapshot.js +191 -0
- package/dist/packages/recorder/src/sweep.js +52 -0
- package/dist/packages/recorder/src/verdicts.js +52 -0
- package/dist/packages/tui/src/bubbles.js +415 -0
- package/dist/packages/tui/src/canvas.js +370 -0
- package/dist/packages/tui/src/chrome.js +85 -0
- package/dist/packages/tui/src/color.js +133 -0
- package/dist/packages/tui/src/flush.js +62 -0
- package/dist/packages/tui/src/fmt.js +188 -0
- package/dist/packages/tui/src/fx-trade.js +278 -0
- package/dist/packages/tui/src/fx.js +527 -0
- package/dist/packages/tui/src/images/blocks.js +141 -0
- package/dist/packages/tui/src/images/coin.js +22 -0
- package/dist/packages/tui/src/images/detect.js +140 -0
- package/dist/packages/tui/src/images/index.js +7 -0
- package/dist/packages/tui/src/images/layer.js +146 -0
- package/dist/packages/tui/src/images/pixels.js +157 -0
- package/dist/packages/tui/src/images/protocols.js +87 -0
- package/dist/packages/tui/src/index.js +18 -0
- package/dist/packages/tui/src/keymap.js +357 -0
- package/dist/packages/tui/src/layout.js +11 -0
- package/dist/packages/tui/src/raster-font.js +195 -0
- package/dist/packages/tui/src/raster.js +213 -0
- package/dist/packages/tui/src/sound.js +177 -0
- package/dist/packages/tui/src/theme.js +182 -0
- package/dist/packages/tui/src/widgets.js +598 -0
- package/dist/packages/voice/src/controller.js +407 -0
- package/dist/packages/voice/src/doctor.js +131 -0
- package/dist/packages/voice/src/download.js +184 -0
- package/dist/packages/voice/src/engines.js +84 -0
- package/dist/packages/voice/src/index.js +20 -0
- package/dist/packages/voice/src/router.js +502 -0
- package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
- package/dist/packages/voice/src/stt/apple.js +179 -0
- package/dist/packages/voice/src/stt/fake.js +34 -0
- package/dist/packages/voice/src/stt/whisper.js +112 -0
- package/dist/packages/voice/src/summary.js +44 -0
- package/dist/packages/voice/src/tts/espeak.js +131 -0
- package/dist/packages/voice/src/tts/fake.js +30 -0
- package/dist/packages/voice/src/tts/piper.js +206 -0
- package/dist/packages/voice/src/tts/say.js +132 -0
- package/dist/packages/voice/src/tts/voices.js +206 -0
- package/dist/packages/voice/src/types.js +3 -0
- package/package.json +38 -4
- package/README.md +0 -3
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// @dexterm/custom — the config loader (task-1358 Tier 1). ~/.dexterm/config.json is the canonical,
|
|
2
|
+
// machine-written config (0600, `version: 4`); workspaces are one JSON file each in ~/.dexterm/workspaces/,
|
|
3
|
+
// user themes in ~/.dexterm/themes/. THIS MODULE NEVER READS A KEY FILE: `<name>.key` files are the data
|
|
4
|
+
// layer's business, and the only thing the loader does with a key is move one OUT of an old config.
|
|
5
|
+
//
|
|
6
|
+
// Failure model: a missing config is the defaults (nothing is written until the user saves something); an
|
|
7
|
+
// unreadable or invalid config keeps the defaults and reports the error so the app can show a toast and
|
|
8
|
+
// `dexterm doctor` can name the file. Nothing here throws on I/O except `saveConfig` / `writeWorkspace`.
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, renameSync, unlinkSync, statSync, appendFileSync } from 'node:fs';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import { CONFIG_VERSION, defaultConfig, migrate, validateConfig } from "./schema.js";
|
|
14
|
+
import { applyOps } from "./schema.js";
|
|
15
|
+
import { builtinWorkspaces, parseWorkspace, validateWorkspace } from "./workspace.js";
|
|
16
|
+
/** `DEXTERM_HOME` overrides `~/.dexterm` (tests point it at a temp dir). Same rule as the CLI's boot-state. */
|
|
17
|
+
export function dextermHome(env = process.env) {
|
|
18
|
+
return env.DEXTERM_HOME && env.DEXTERM_HOME.trim() ? env.DEXTERM_HOME : join(homedir(), '.dexterm');
|
|
19
|
+
}
|
|
20
|
+
export const configPath = (home) => join(home, 'config.json');
|
|
21
|
+
export const journalPath = (home) => join(home, 'config.journal.jsonl');
|
|
22
|
+
export const workspacesDir = (home) => join(home, 'workspaces');
|
|
23
|
+
export const packsDir = (home) => join(home, 'packs');
|
|
24
|
+
const sha = (s) => createHash('sha256').update(s).digest('hex');
|
|
25
|
+
/** Load (and migrate if needed) ~/.dexterm/config.json. Never throws. */
|
|
26
|
+
export function loadConfig(home) {
|
|
27
|
+
const path = configPath(home);
|
|
28
|
+
const base = { config: defaultConfig(), path, missing: false, ok: true, errors: [], notes: [], extractedKeyFiles: [], hash: sha('') };
|
|
29
|
+
let text;
|
|
30
|
+
try {
|
|
31
|
+
text = readFileSync(path, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
if (e.code === 'ENOENT')
|
|
35
|
+
return { ...base, missing: true };
|
|
36
|
+
return { ...base, ok: false, errors: [`${path}: ${e.message}`] };
|
|
37
|
+
}
|
|
38
|
+
base.hash = sha(text);
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = JSON.parse(text);
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return { ...base, ok: false, errors: [`${path}: not valid JSON (${e.message}); keeping defaults`] };
|
|
45
|
+
}
|
|
46
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
47
|
+
return { ...base, ok: false, errors: [`${path}: expected an object; keeping defaults`] };
|
|
48
|
+
const version = Number(raw.version ?? 1);
|
|
49
|
+
if (version === CONFIG_VERSION) {
|
|
50
|
+
const errors = validateConfig(raw);
|
|
51
|
+
if (errors.length)
|
|
52
|
+
return { ...base, ok: false, errors: errors.slice(0, 5).map((e) => `${path}: ${e.path || '/'} ${e.msg}`) };
|
|
53
|
+
return { ...base, config: raw };
|
|
54
|
+
}
|
|
55
|
+
// older (or newer) config: migrate, back up, write the new one, write extracted keys to <name>.key (0600)
|
|
56
|
+
let m;
|
|
57
|
+
try {
|
|
58
|
+
m = migrate(raw);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return { ...base, ok: false, errors: [`${path}: ${e.message}`] };
|
|
62
|
+
}
|
|
63
|
+
if (m.errors.length)
|
|
64
|
+
return { ...base, ok: false, errors: m.errors.slice(0, 5).map((e) => `${path}: after migration ${e.path || '/'} ${e.msg}`) };
|
|
65
|
+
const out = { ...base, config: m.config, notes: [...m.notes] };
|
|
66
|
+
try {
|
|
67
|
+
writeFileSync(`${path}.bak-v${m.from}`, text, { mode: 0o600 });
|
|
68
|
+
for (const [name, value] of Object.entries(m.extractedKeys)) {
|
|
69
|
+
const kf = join(home, `${name}.key`);
|
|
70
|
+
if (!existsSync(kf)) {
|
|
71
|
+
writeFileSync(kf, value + '\n', { mode: 0o600 });
|
|
72
|
+
out.extractedKeyFiles.push(`${name}.key`);
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
out.notes.push(`${name}.key already exists; the key in config.json was dropped, not copied`);
|
|
76
|
+
}
|
|
77
|
+
saveConfig(home, m.config);
|
|
78
|
+
out.notes.push(`migrated config.json v${m.from} → v${CONFIG_VERSION} (backup: config.json.bak-v${m.from})`);
|
|
79
|
+
out.hash = sha(readFileSync(path, 'utf8'));
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
out.ok = false;
|
|
83
|
+
out.errors.push(`${path}: migration could not be written (${e.message}); using the migrated config for this run only`);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** Validate + write config.json atomically (0600, directory 0700). Throws on a schema error or I/O failure. */
|
|
88
|
+
export function saveConfig(home, config) {
|
|
89
|
+
const errors = validateConfig(config);
|
|
90
|
+
if (errors.length)
|
|
91
|
+
throw new Error(`config invalid: ${errors[0].path || '/'} ${errors[0].msg}`);
|
|
92
|
+
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
93
|
+
const path = configPath(home);
|
|
94
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
95
|
+
writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
96
|
+
renameSync(tmp, path);
|
|
97
|
+
}
|
|
98
|
+
export function readJournal(home) {
|
|
99
|
+
try {
|
|
100
|
+
return readFileSync(journalPath(home), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Apply ops to the stored config, journal them with the inverse, and save. Throws (and writes nothing) on a bad op or schema error. */
|
|
107
|
+
export function applyAndJournal(home, ops, meta) {
|
|
108
|
+
const loaded = loadConfig(home);
|
|
109
|
+
if (!loaded.ok)
|
|
110
|
+
throw new Error(loaded.errors[0]);
|
|
111
|
+
const { doc, inverse } = applyOps(loaded.config, ops);
|
|
112
|
+
const errors = validateConfig(doc);
|
|
113
|
+
if (errors.length)
|
|
114
|
+
throw new Error(`refused: ${errors[0].path || '/'} ${errors[0].msg}`);
|
|
115
|
+
saveConfig(home, doc);
|
|
116
|
+
const after = sha(readFileSync(configPath(home), 'utf8'));
|
|
117
|
+
const seq = (readJournal(home).at(-1)?.seq ?? 0) + 1;
|
|
118
|
+
const entry = { seq, at: meta.now ?? Date.now(), kind: meta.kind ?? 'apply', actor: meta.actor, why: meta.why, ops, inverse, hashBefore: loaded.hash, hashAfter: after };
|
|
119
|
+
appendFileSync(journalPath(home), JSON.stringify(entry) + '\n', { mode: 0o600 });
|
|
120
|
+
return { config: doc, entry };
|
|
121
|
+
}
|
|
122
|
+
/** Undo the last applied/redone change if the config has not been edited by hand since. */
|
|
123
|
+
export function undoLast(home, now = Date.now()) {
|
|
124
|
+
const j = readJournal(home);
|
|
125
|
+
// the last entry that can be undone: walk back skipping already-undone applies
|
|
126
|
+
let depth = 0;
|
|
127
|
+
for (let i = j.length - 1; i >= 0; i--) {
|
|
128
|
+
const e = j[i];
|
|
129
|
+
if (e.kind === 'undo') {
|
|
130
|
+
depth++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (depth > 0) {
|
|
134
|
+
depth--;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const current = loadConfig(home);
|
|
138
|
+
if (current.hash !== e.hashAfter)
|
|
139
|
+
return { ok: false, reason: 'cannot undo: config.json changed since (edited by hand?)' };
|
|
140
|
+
try {
|
|
141
|
+
const { entry } = applyAndJournal(home, e.inverse, { actor: 'user', why: `undo #${e.seq}`, kind: 'undo', now });
|
|
142
|
+
return { ok: true, entry };
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
return { ok: false, reason: `cannot undo: ${err.message}` };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { ok: false, reason: 'nothing to undo' };
|
|
149
|
+
}
|
|
150
|
+
// ------------------------------------------------------------------ workspaces on disk
|
|
151
|
+
export const WS_FILE = /^[a-z][a-z0-9-]{0,39}\.json$/;
|
|
152
|
+
/** Built-ins first (default, morning-hud), then the user's files; a user file with a built-in's name REPLACES it. Bad files are listed with their error. */
|
|
153
|
+
export function listWorkspaces(home) {
|
|
154
|
+
const out = builtinWorkspaces().map((w) => ({ name: w.name, path: null, workspace: w, builtin: true }));
|
|
155
|
+
const dir = workspacesDir(home);
|
|
156
|
+
let files = [];
|
|
157
|
+
try {
|
|
158
|
+
files = readdirSync(dir).filter((f) => WS_FILE.test(f)).sort();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
for (const f of files) {
|
|
164
|
+
const path = join(dir, f);
|
|
165
|
+
const name = f.slice(0, -5);
|
|
166
|
+
try {
|
|
167
|
+
if (statSync(path).isSymbolicLink())
|
|
168
|
+
continue;
|
|
169
|
+
const ws = parseWorkspace(readFileSync(path, 'utf8'), f);
|
|
170
|
+
if (ws.name !== name)
|
|
171
|
+
throw new Error(`${f}: "name" must be "${name}" (the file name)`);
|
|
172
|
+
const i = out.findIndex((w) => w.name === name);
|
|
173
|
+
const entry = { name, path, workspace: ws, builtin: false };
|
|
174
|
+
if (i >= 0)
|
|
175
|
+
out[i] = entry;
|
|
176
|
+
else
|
|
177
|
+
out.push(entry);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
out.push({ name, path, workspace: { name }, builtin: false, error: e.message });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
export function readWorkspace(home, name) {
|
|
186
|
+
return listWorkspaces(home).find((w) => w.name === name && !w.error)?.workspace ?? null;
|
|
187
|
+
}
|
|
188
|
+
/** Write a workspace file (validated, 0600). Throws on a bad document or name. */
|
|
189
|
+
export function writeWorkspace(home, ws) {
|
|
190
|
+
const errors = validateWorkspace(ws);
|
|
191
|
+
if (errors.length)
|
|
192
|
+
throw new Error(`workspace invalid: ${errors[0].path || '/'} ${errors[0].msg}`);
|
|
193
|
+
if (ws.name.includes('/'))
|
|
194
|
+
throw new Error('a user workspace name cannot contain "/" (reserved for packs)');
|
|
195
|
+
mkdirSync(workspacesDir(home), { recursive: true, mode: 0o700 });
|
|
196
|
+
const path = join(workspacesDir(home), `${ws.name}.json`);
|
|
197
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
198
|
+
writeFileSync(tmp, JSON.stringify(ws, null, 2) + '\n', { mode: 0o600 });
|
|
199
|
+
renameSync(tmp, path);
|
|
200
|
+
return path;
|
|
201
|
+
}
|
|
202
|
+
export function removeWorkspace(home, name) {
|
|
203
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(name))
|
|
204
|
+
return false;
|
|
205
|
+
const path = join(workspacesDir(home), `${name}.json`);
|
|
206
|
+
try {
|
|
207
|
+
unlinkSync(path);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @dexterm/custom — public surface (task-1358).
|
|
2
|
+
export * from "./schema.js";
|
|
3
|
+
export * from "./layout.js";
|
|
4
|
+
export * from "./workspace.js";
|
|
5
|
+
export * from "./config.js";
|
|
6
|
+
export * from "./themes.js";
|
|
7
|
+
export * from "./keymap.js";
|
|
8
|
+
export * from "./rules.js";
|
|
9
|
+
export * from "./packs.js";
|
|
10
|
+
export * from "./widget-html.js";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// @dexterm/custom — keymap overrides (task-1358 Tier 2). `config.keymap.global[action] = keys[]` and
|
|
2
|
+
// `config.keymap.screens[screen][action] = keys[]` re-bind the app's actions (the AppAction ids of
|
|
3
|
+
// @dexterm/tui) over the shipped defaults. Validation refuses what a shared config must never do:
|
|
4
|
+
// * RESERVED KEYS cannot be assigned to anything: ctrl+c (quit), esc, enter, y / Y (confirm), ! (panic);
|
|
5
|
+
// * RESERVED ACTIONS keep their default keys: quit, back, alert.confirm / alert.dismiss, plans.arm,
|
|
6
|
+
// plans.panic, plans.gate (the money-adjacent confirms and the panic key);
|
|
7
|
+
// * no two actions on the same key within one scope (global, or one screen); a global key may still be
|
|
8
|
+
// shadowed by a screen binding, as the defaults already do;
|
|
9
|
+
// * a chord prefix (the first key of "ctrl+w s") cannot also be bound on its own;
|
|
10
|
+
// * at most 4 keys per action, every key a valid id (the KEY pattern in schema.ts).
|
|
11
|
+
// Pure: takes the default binding table, returns a new one plus the errors. Nothing here reads a file.
|
|
12
|
+
import { validate } from "./schema.js";
|
|
13
|
+
export const RESERVED_KEYS = new Set(['ctrl+c', 'esc', 'enter', 'y', 'Y', '!']);
|
|
14
|
+
export const RESERVED_ACTIONS = new Set(['quit', 'back', 'alert.confirm', 'alert.dismiss', 'plans.arm', 'plans.panic', 'plans.gate']);
|
|
15
|
+
/**
|
|
16
|
+
* Layer `cfg` over `defaults`. On ANY error the defaults are returned untouched (a half-applied keymap is worse
|
|
17
|
+
* than none) with every problem listed, so the user fixes the file once.
|
|
18
|
+
*/
|
|
19
|
+
export function applyKeymapConfig(defaults, cfg) {
|
|
20
|
+
const errors = cfg ? validate('keymap', cfg, '/keymap') : [];
|
|
21
|
+
const overridden = [];
|
|
22
|
+
if (!cfg || errors.length)
|
|
23
|
+
return { bindings: defaults.map((b) => ({ ...b, keys: [...b.keys] })), errors, overridden };
|
|
24
|
+
const bindings = defaults.map((b) => ({ ...b, keys: [...b.keys] }));
|
|
25
|
+
const known = new Set(defaults.map((b) => b.action));
|
|
26
|
+
const globalLabel = new Map();
|
|
27
|
+
for (const b of defaults)
|
|
28
|
+
if (!b.screen)
|
|
29
|
+
globalLabel.set(b.action, b.label);
|
|
30
|
+
const apply = (screen, action, keys) => {
|
|
31
|
+
const where = screen ? `/keymap/screens/${screen}/${action}` : `/keymap/global/${action}`;
|
|
32
|
+
if (!known.has(action)) {
|
|
33
|
+
errors.push({ path: where, msg: `unknown action "${action}"` });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (RESERVED_ACTIONS.has(action)) {
|
|
37
|
+
errors.push({ path: where, msg: `"${action}" keeps its default keys (reserved: confirm / dismiss / panic / quit / back)` });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
for (const k of keys) {
|
|
41
|
+
const parts = k.split(' ');
|
|
42
|
+
if (parts.some((p) => RESERVED_KEYS.has(p)) && !(parts.length === 2 && !RESERVED_KEYS.has(parts[0]))) {
|
|
43
|
+
errors.push({ path: where, msg: `"${k}" is reserved (${[...RESERVED_KEYS].join(' ')})` });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const i = bindings.findIndex((b) => b.action === action && (screen ? b.screen === screen : !b.screen));
|
|
48
|
+
if (i >= 0)
|
|
49
|
+
bindings[i] = { ...bindings[i], keys: [...keys] };
|
|
50
|
+
else
|
|
51
|
+
bindings.push({ keys: [...keys], action: action, label: globalLabel.get(action) ?? defaults.find((b) => b.action === action)?.label, screen: screen ?? undefined });
|
|
52
|
+
overridden.push({ action: action, screen, keys: [...keys] });
|
|
53
|
+
};
|
|
54
|
+
for (const [action, keys] of Object.entries(cfg.global ?? {}))
|
|
55
|
+
apply(null, action, keys);
|
|
56
|
+
for (const [screen, table] of Object.entries(cfg.screens ?? {})) {
|
|
57
|
+
if (!/^[a-z]+$/.test(screen)) {
|
|
58
|
+
errors.push({ path: `/keymap/screens/${screen}`, msg: 'bad screen name' });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (const [action, keys] of Object.entries(table))
|
|
62
|
+
apply(screen, action, keys);
|
|
63
|
+
}
|
|
64
|
+
if (!errors.length)
|
|
65
|
+
errors.push(...collisions(bindings));
|
|
66
|
+
if (errors.length)
|
|
67
|
+
return { bindings: defaults.map((b) => ({ ...b, keys: [...b.keys] })), errors, overridden: [] };
|
|
68
|
+
return { bindings, errors: [], overridden };
|
|
69
|
+
}
|
|
70
|
+
/** Two actions on one key within the same scope, or a chord prefix bound on its own. */
|
|
71
|
+
export function collisions(bindings) {
|
|
72
|
+
const errors = [];
|
|
73
|
+
const byScope = new Map();
|
|
74
|
+
const prefixes = new Set();
|
|
75
|
+
for (const b of bindings)
|
|
76
|
+
for (const k of b.keys) {
|
|
77
|
+
const sp = k.indexOf(' ');
|
|
78
|
+
if (sp > 0)
|
|
79
|
+
prefixes.add(k.slice(0, sp));
|
|
80
|
+
}
|
|
81
|
+
for (const b of bindings) {
|
|
82
|
+
const scope = b.screen ?? '';
|
|
83
|
+
let m = byScope.get(scope);
|
|
84
|
+
if (!m) {
|
|
85
|
+
m = new Map();
|
|
86
|
+
byScope.set(scope, m);
|
|
87
|
+
}
|
|
88
|
+
for (const k of b.keys) {
|
|
89
|
+
const other = m.get(k);
|
|
90
|
+
if (other && other !== b.action)
|
|
91
|
+
errors.push({ path: scope ? `/keymap/screens/${scope}` : '/keymap/global', msg: `"${k}" is bound to both ${other} and ${b.action}` });
|
|
92
|
+
m.set(k, b.action);
|
|
93
|
+
if (prefixes.has(k))
|
|
94
|
+
errors.push({ path: scope ? `/keymap/screens/${scope}` : '/keymap/global', msg: `"${k}" starts a chord and cannot also be bound alone (${b.action})` });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return errors;
|
|
98
|
+
}
|
|
99
|
+
/** `dexterm keymap`: one line per binding, user overrides marked with `·`. */
|
|
100
|
+
export function renderKeymap(r, screen) {
|
|
101
|
+
const ov = new Set(r.overridden.map((o) => `${o.screen ?? ''}|${o.action}`));
|
|
102
|
+
const rows = r.bindings.filter((b) => !screen || !b.screen || b.screen === screen);
|
|
103
|
+
const lines = rows.map((b) => `${ov.has(`${b.screen ?? ''}|${b.action}`) ? '·' : ' '} ${(b.screen ?? 'global').padEnd(10)} ${b.action.padEnd(22)} ${b.keys.join(' / ').padEnd(24)} ${b.label ?? ''}`);
|
|
104
|
+
if (r.errors.length)
|
|
105
|
+
lines.unshift(...r.errors.map((e) => `✗ ${e.path} ${e.msg}`), ' (keymap config refused: the defaults are in use)');
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// @dexterm/custom — layout engine (task-1358, from the task-1288 design): a pane tree (splits + widget panes)
|
|
2
|
+
// solved into integer rects for a screen size and breakpoint. Deterministic, pure, no canvas dependency.
|
|
3
|
+
//
|
|
4
|
+
// Rules (they mirror the SPEC §7.4 column-drop rule so users get one mental model):
|
|
5
|
+
// * breakpoints: compact < 100 cols, standard < 140, wide ≥ 140 (same as @dexterm/tui);
|
|
6
|
+
// * a pane with collapseBelow: 'wide' is dropped at standard/compact, etc.;
|
|
7
|
+
// * every widget has a minimum size; when a split cannot give every child its minimum, the LEAF with the
|
|
8
|
+
// highest priority number among the overflowing children is dropped and the split re-solved (priority 0
|
|
9
|
+
// never drops: it is solved anyway and the renderer clips);
|
|
10
|
+
// * fixed sizes come first, the remainder is shared by weight with largest-remainder rounding so the
|
|
11
|
+
// rects tile exactly (no 1-cell seams); an optional 1-cell gutter between children rains.
|
|
12
|
+
import { BUILTIN_WIDGETS } from "./schema.js";
|
|
13
|
+
export function breakpoint(w) { return w < 100 ? 'compact' : w < 140 ? 'standard' : 'wide'; }
|
|
14
|
+
const BP_RANK = { compact: 0, standard: 1, wide: 2 };
|
|
15
|
+
export function widgetSpec(id, registry = {}) {
|
|
16
|
+
return BUILTIN_WIDGETS[id] ?? registry[id] ?? null;
|
|
17
|
+
}
|
|
18
|
+
/** Pick the tree for this breakpoint: per-breakpoint object (nearest smaller defined, else larger) or the single tree. */
|
|
19
|
+
export function treeFor(layout, bp) {
|
|
20
|
+
if (!layout)
|
|
21
|
+
return null;
|
|
22
|
+
if ('kind' in layout)
|
|
23
|
+
return layout;
|
|
24
|
+
const order = ['wide', 'standard', 'compact'];
|
|
25
|
+
const start = order.indexOf(bp);
|
|
26
|
+
for (let i = start; i < order.length; i++) {
|
|
27
|
+
const t = layout[order[i]];
|
|
28
|
+
if (t)
|
|
29
|
+
return t;
|
|
30
|
+
}
|
|
31
|
+
for (let i = start - 1; i >= 0; i--) {
|
|
32
|
+
const t = layout[order[i]];
|
|
33
|
+
if (t)
|
|
34
|
+
return t;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function visible(node, bp) {
|
|
39
|
+
if (node.kind === 'pane')
|
|
40
|
+
return !node.collapseBelow || BP_RANK[bp] >= BP_RANK[node.collapseBelow];
|
|
41
|
+
return node.children.some((c) => visible(c.node, bp));
|
|
42
|
+
}
|
|
43
|
+
/** Minimum size a node needs (sum along its axis, max across), ignoring collapsed children. */
|
|
44
|
+
function minSize(node, bp, registry) {
|
|
45
|
+
if (node.kind === 'pane') {
|
|
46
|
+
const spec = widgetSpec(node.widget, registry);
|
|
47
|
+
return spec ? { ...spec.min } : { w: 10, h: 3 };
|
|
48
|
+
}
|
|
49
|
+
const kids = node.children.filter((c) => visible(c.node, bp));
|
|
50
|
+
const g = node.gutter ? Math.max(0, kids.length - 1) : 0;
|
|
51
|
+
let along = g, across = 0;
|
|
52
|
+
for (const c of kids) {
|
|
53
|
+
const m = minSize(c.node, bp, registry);
|
|
54
|
+
if (node.dir === 'row') {
|
|
55
|
+
along += c.fixed ?? m.w;
|
|
56
|
+
across = Math.max(across, m.h);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
along += c.fixed ?? m.h;
|
|
60
|
+
across = Math.max(across, m.w);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return node.dir === 'row' ? { w: along, h: across } : { w: across, h: along };
|
|
64
|
+
}
|
|
65
|
+
/** Largest-remainder share of `total` by weights after fixed sizes; integer sizes that sum to total. */
|
|
66
|
+
function share(children, total) {
|
|
67
|
+
const fixedSum = children.reduce((s, c) => s + (c.fixed ?? 0), 0);
|
|
68
|
+
const flex = children.filter((c) => c.fixed === undefined);
|
|
69
|
+
const wsum = flex.reduce((s, c) => s + (c.weight ?? 1), 0) || 1;
|
|
70
|
+
const remain = Math.max(0, total - fixedSum);
|
|
71
|
+
const sizes = children.map((c) => (c.fixed !== undefined ? Math.min(c.fixed, total) : 0));
|
|
72
|
+
const raw = flex.map((c) => (remain * (c.weight ?? 1)) / wsum);
|
|
73
|
+
const floors = raw.map(Math.floor);
|
|
74
|
+
let left = remain - floors.reduce((a, b) => a + b, 0);
|
|
75
|
+
const order = raw.map((r, i) => [r - floors[i], i]).sort((a, b) => b[0] - a[0]);
|
|
76
|
+
for (const [, i] of order) {
|
|
77
|
+
if (left <= 0)
|
|
78
|
+
break;
|
|
79
|
+
floors[i] += 1;
|
|
80
|
+
left -= 1;
|
|
81
|
+
}
|
|
82
|
+
let fi = 0;
|
|
83
|
+
for (let i = 0; i < children.length; i++)
|
|
84
|
+
if (children[i].fixed === undefined)
|
|
85
|
+
sizes[i] = floors[fi++];
|
|
86
|
+
// if fixed sizes overflow, trim from the last fixed child
|
|
87
|
+
let over = sizes.reduce((a, b) => a + b, 0) - total;
|
|
88
|
+
for (let i = children.length - 1; i >= 0 && over > 0; i--) {
|
|
89
|
+
const cut = Math.min(over, sizes[i]);
|
|
90
|
+
sizes[i] -= cut;
|
|
91
|
+
over -= cut;
|
|
92
|
+
}
|
|
93
|
+
return sizes;
|
|
94
|
+
}
|
|
95
|
+
/** Solve a tree into panes for `rect`. */
|
|
96
|
+
export function solve(tree, rect, opts = {}) {
|
|
97
|
+
const bp = opts.breakpoint ?? breakpoint(rect.w);
|
|
98
|
+
const registry = opts.registry ?? {};
|
|
99
|
+
const out = { panes: [], dropped: [], gutters: [], breakpoint: bp };
|
|
100
|
+
solveNode(tree, rect, bp, registry, out);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function solveNode(node, rect, bp, registry, out) {
|
|
104
|
+
if (!visible(node, bp)) {
|
|
105
|
+
collect(node, (p) => out.dropped.push({ widget: p.widget, reason: `collapsed below ${p.collapseBelow}` }));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (node.kind === 'pane') {
|
|
109
|
+
const m = minSize(node, bp, registry);
|
|
110
|
+
if (rect.w < m.w || rect.h < m.h) {
|
|
111
|
+
if ((node.priority ?? 1) === 0)
|
|
112
|
+
out.panes.push({ widget: node.widget, rect, node, clipped: true });
|
|
113
|
+
else
|
|
114
|
+
out.dropped.push({ widget: node.widget, reason: `needs ${m.w}×${m.h}, has ${rect.w}×${rect.h}` });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
out.panes.push({ widget: node.widget, rect, node });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
for (const c of node.children)
|
|
121
|
+
if (!visible(c.node, bp))
|
|
122
|
+
collect(c.node, (p) => out.dropped.push({ widget: p.widget, reason: `collapsed below ${p.collapseBelow}` }));
|
|
123
|
+
let kids = node.children.filter((c) => visible(c.node, bp));
|
|
124
|
+
// Drop the highest-priority LEAF among the children that actually overflow (never an innocent sibling)
|
|
125
|
+
// until the minimums fit. Across-axis overflow (a child too tall for a row) is resolved inside that child;
|
|
126
|
+
// along-axis overflow (the sum too wide) may take a leaf from any child.
|
|
127
|
+
for (;;) {
|
|
128
|
+
const need = minSize({ ...node, children: kids }, bp, registry);
|
|
129
|
+
const fits = need.w <= rect.w && need.h <= rect.h;
|
|
130
|
+
if (fits || kids.length === 0)
|
|
131
|
+
break;
|
|
132
|
+
const across = node.dir === 'row' ? 'h' : 'w';
|
|
133
|
+
const over = kids.filter((c) => minSize(c.node, bp, registry)[across] > rect[across]);
|
|
134
|
+
const victim = highestLeaf({ ...node, children: over.length ? over : kids });
|
|
135
|
+
if (!victim || (victim.priority ?? 1) === 0)
|
|
136
|
+
break;
|
|
137
|
+
out.dropped.push({ widget: victim.widget, reason: `no room at ${rect.w}×${rect.h} (priority ${victim.priority ?? 1})` });
|
|
138
|
+
kids = kids.map((c) => ({ ...c, node: removeLeaf(c.node, victim) })).filter((c) => c.node !== null);
|
|
139
|
+
}
|
|
140
|
+
if (!kids.length)
|
|
141
|
+
return;
|
|
142
|
+
const g = node.gutter ? 1 : 0;
|
|
143
|
+
const along = node.dir === 'row' ? rect.w : rect.h;
|
|
144
|
+
const sizes = share(kids, along - g * (kids.length - 1));
|
|
145
|
+
let pos = node.dir === 'row' ? rect.x : rect.y;
|
|
146
|
+
kids.forEach((c, i) => {
|
|
147
|
+
const r = node.dir === 'row' ? { x: pos, y: rect.y, w: sizes[i], h: rect.h } : { x: rect.x, y: pos, w: rect.w, h: sizes[i] };
|
|
148
|
+
if (r.w > 0 && r.h > 0)
|
|
149
|
+
solveNode(c.node, r, bp, registry, out);
|
|
150
|
+
pos += sizes[i];
|
|
151
|
+
if (g && i < kids.length - 1) {
|
|
152
|
+
out.gutters.push(node.dir === 'row' ? { x: pos, y: rect.y, w: 1, h: rect.h } : { x: rect.x, y: pos, w: rect.w, h: 1 });
|
|
153
|
+
pos += 1;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** The pane with the highest priority number in a subtree (ties: the later one in document order). */
|
|
158
|
+
function highestLeaf(node) {
|
|
159
|
+
let best = null;
|
|
160
|
+
collect(node, (p) => { if (!best || (p.priority ?? 1) >= (best.priority ?? 1))
|
|
161
|
+
best = p; });
|
|
162
|
+
return best;
|
|
163
|
+
}
|
|
164
|
+
/** Remove one pane (by identity) from a subtree; splits left with no children collapse to null. */
|
|
165
|
+
function removeLeaf(node, leaf) {
|
|
166
|
+
if (node.kind === 'pane')
|
|
167
|
+
return node === leaf ? null : node;
|
|
168
|
+
const children = node.children.map((c) => ({ ...c, node: removeLeaf(c.node, leaf) })).filter((c) => c.node !== null);
|
|
169
|
+
return children.length ? { ...node, children } : null;
|
|
170
|
+
}
|
|
171
|
+
function collect(node, fn) { if (node.kind === 'pane')
|
|
172
|
+
fn(node);
|
|
173
|
+
else
|
|
174
|
+
node.children.forEach((c) => collect(c.node, fn)); }
|
|
175
|
+
/** Every widget id a tree references (for validation against the registry). */
|
|
176
|
+
export function widgetsIn(node) { const ids = []; collect(node, (p) => ids.push(p.widget)); return ids; }
|
|
177
|
+
// ------------------------------------------------------------------ plain-text render (previews, tests, proposal cards)
|
|
178
|
+
export function renderPlain(solved, w, h, opts = {}) {
|
|
179
|
+
const grid = Array.from({ length: h }, () => Array(w).fill(' '));
|
|
180
|
+
const put = (x, y, s) => { for (let i = 0; i < s.length; i++)
|
|
181
|
+
if (x + i >= 0 && x + i < w && y >= 0 && y < h)
|
|
182
|
+
grid[y][x + i] = s[i]; };
|
|
183
|
+
for (const gt of solved.gutters)
|
|
184
|
+
for (let y = gt.y; y < gt.y + gt.h; y++)
|
|
185
|
+
for (let x = gt.x; x < gt.x + gt.w; x++)
|
|
186
|
+
if (x < w && y < h)
|
|
187
|
+
grid[y][x] = '·';
|
|
188
|
+
for (const p of solved.panes) {
|
|
189
|
+
const { x, y, w: pw, h: ph } = p.rect;
|
|
190
|
+
if (pw < 2 || ph < 2)
|
|
191
|
+
continue;
|
|
192
|
+
put(x, y, '╭' + '─'.repeat(pw - 2) + '╮');
|
|
193
|
+
for (let yy = y + 1; yy < y + ph - 1; yy++) {
|
|
194
|
+
put(x, yy, '│');
|
|
195
|
+
put(x + pw - 1, yy, '│');
|
|
196
|
+
}
|
|
197
|
+
put(x, y + ph - 1, '╰' + '─'.repeat(pw - 2) + '╯');
|
|
198
|
+
const spec = widgetSpec(p.widget, opts.registry ?? {});
|
|
199
|
+
const title = ` ${(p.node.title ?? spec?.title ?? p.widget).toUpperCase()} `;
|
|
200
|
+
if (title.trim())
|
|
201
|
+
put(x + 2, y, title.slice(0, Math.max(0, pw - 4)));
|
|
202
|
+
const info = `${p.widget} ${pw}×${ph}${p.clipped ? ' (clipped)' : ''}`;
|
|
203
|
+
put(x + 2, y + 1, info.slice(0, Math.max(0, pw - 4)));
|
|
204
|
+
}
|
|
205
|
+
return grid.map((r) => r.join('')).join('\n');
|
|
206
|
+
}
|
|
207
|
+
/** The app's shipped screens expressed as trees (so "default" is a workspace too, and the numbers are the renderers'). */
|
|
208
|
+
export function builtinScreens() {
|
|
209
|
+
return {
|
|
210
|
+
screener: { kind: 'split', dir: 'row', gutter: true, children: [
|
|
211
|
+
{ node: { kind: 'pane', widget: 'screener.table', priority: 0 } },
|
|
212
|
+
{ node: { kind: 'pane', widget: 'screener.spotlight', priority: 2, collapseBelow: 'wide' }, fixed: 44 },
|
|
213
|
+
] },
|
|
214
|
+
token: { kind: 'split', dir: 'row', children: [
|
|
215
|
+
{ node: { kind: 'split', dir: 'col', children: [
|
|
216
|
+
{ node: { kind: 'pane', widget: 'token.chart', priority: 0 }, weight: 46 },
|
|
217
|
+
{ node: { kind: 'split', dir: 'row', children: [
|
|
218
|
+
{ node: { kind: 'pane', widget: 'token.onchain', priority: 1 }, weight: 40 },
|
|
219
|
+
{ node: { kind: 'pane', widget: 'token.flow', priority: 2 }, weight: 60 },
|
|
220
|
+
] }, weight: 54 },
|
|
221
|
+
] } },
|
|
222
|
+
{ node: { kind: 'pane', widget: 'analyst.panel', priority: 3, collapseBelow: 'standard' }, fixed: 46 },
|
|
223
|
+
] },
|
|
224
|
+
newpairs: { kind: 'split', dir: 'row', gutter: true, children: [
|
|
225
|
+
{ node: { kind: 'pane', widget: 'newpairs.table', priority: 0 } },
|
|
226
|
+
{ node: { kind: 'pane', widget: 'newpairs.spotlight', priority: 2, collapseBelow: 'wide' }, fixed: 46 },
|
|
227
|
+
] },
|
|
228
|
+
watchlist: { kind: 'pane', widget: 'watchlist.table', priority: 0 },
|
|
229
|
+
analyst: { kind: 'split', dir: 'row', children: [
|
|
230
|
+
{ node: { kind: 'pane', widget: 'analyst.panel', priority: 0 } },
|
|
231
|
+
{ node: { kind: 'pane', widget: 'analyst.context', priority: 2, collapseBelow: 'wide' }, fixed: 36 },
|
|
232
|
+
] },
|
|
233
|
+
positions: { kind: 'pane', widget: 'positions.table', priority: 0 },
|
|
234
|
+
};
|
|
235
|
+
}
|