dexterm-cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/dist/packages/agents/src/audit.js +122 -0
- package/dist/packages/agents/src/bus.js +379 -0
- package/dist/packages/agents/src/executor.js +177 -0
- package/dist/packages/agents/src/gate.js +176 -0
- package/dist/packages/agents/src/identity.js +179 -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 +114 -0
- package/dist/packages/agents/src/tools.js +69 -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 +67 -0
- package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -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 +303 -0
- package/dist/packages/cli/src/agent-roster.js +26 -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 +149 -0
- package/dist/packages/cli/src/boot-state.js +62 -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 +361 -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 +509 -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 +5583 -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 +339 -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/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/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/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 +515 -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/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 +208 -0
- package/dist/packages/data/src/intel/helius-history.js +59 -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/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 +170 -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 +247 -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 +849 -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 +19 -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/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 +15 -0
- package/dist/packages/tui/src/keymap.js +355 -0
- package/dist/packages/tui/src/layout.js +11 -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 +395 -0
- package/dist/packages/voice/src/doctor.js +63 -0
- package/dist/packages/voice/src/engines.js +44 -0
- package/dist/packages/voice/src/index.js +16 -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 +102 -0
- package/dist/packages/voice/src/summary.js +44 -0
- package/dist/packages/voice/src/tts/fake.js +30 -0
- package/dist/packages/voice/src/tts/say.js +96 -0
- package/dist/packages/voice/src/types.js +3 -0
- package/package.json +42 -4
- package/README.md +0 -3
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
// dexterm — app state consumed by the screens. Screens are pure render(canvas, state) functions;
|
|
2
|
+
// renderFrame() draws the screen, the toast, then runs the effects post-pass (VISUAL-V2 §3).
|
|
3
|
+
import { Canvas, THEMES, applyFx, toast, makeVfx, setSources, marketActivity, candleActivity, isCalm, } from '../../tui/src/index.js';
|
|
4
|
+
import { makeFixtures } from "./fixtures.js";
|
|
5
|
+
import { fromFixture } from "./screener/rows.js";
|
|
6
|
+
import { makeScreenerState, screenerView } from "./screener/view.js";
|
|
7
|
+
import { renderScreener } from "./screens/screener.js";
|
|
8
|
+
import { renderToken } from "./screens/token.js";
|
|
9
|
+
import { renderNewPairs } from "./screens/newpairs.js";
|
|
10
|
+
import { renderWatchlist } from "./screens/watchlist.js";
|
|
11
|
+
import { renderAnalyst } from "./screens/analyst.js";
|
|
12
|
+
import { renderBoot } from "./screens/boot.js";
|
|
13
|
+
import { renderOverlay, renderGotoPrompt } from "./screens/overlay.js";
|
|
14
|
+
import { renderTicket } from "./screens/ticket.js";
|
|
15
|
+
import { renderPositions } from "./screens/positions.js";
|
|
16
|
+
import { renderTrending } from "./screens/trending.js";
|
|
17
|
+
import { renderKol } from "./screens/kol.js";
|
|
18
|
+
import { renderBubbles } from "./screens/bubbles.js";
|
|
19
|
+
import { renderNarrative } from "./screens/narrative.js";
|
|
20
|
+
import { renderPlans } from "./screens/plans.js";
|
|
21
|
+
import { renderFollows } from "./screens/follows.js";
|
|
22
|
+
import { renderRules } from "./screens/rules.js";
|
|
23
|
+
import { CopyLive } from "./live/copy.js";
|
|
24
|
+
import { CopyAuto, defaultCopyJournalPath, leaderSoldSince } from '../../data/src/trade/copy/index.js';
|
|
25
|
+
import { computeBoard, loadDefaultLexicon, recentNarrativeRows } from '../../packs/src/index.js';
|
|
26
|
+
import { PlanService, defaultPlansJournalPath } from '../../data/src/trade/plans/index.js';
|
|
27
|
+
import { openTicket } from "./trade.js";
|
|
28
|
+
import { computeFill } from '../../data/src/index.js';
|
|
29
|
+
import { renderScanTicker, scanIndicator } from "./scan/ui.js";
|
|
30
|
+
import { renderVoiceHud } from "./voice-hud.js";
|
|
31
|
+
import { workspaceBadge } from "./custom.js";
|
|
32
|
+
/** Map key for the Analyst-tab's own conversation (not tied to a single token). */
|
|
33
|
+
export const ANALYST_TAB_KEY = '__analyst_tab__';
|
|
34
|
+
/**
|
|
35
|
+
* The conversation key for whatever is on screen: the token's mint on the token screen, the shared
|
|
36
|
+
* Analyst-tab key on the analyst screen, else null (no chat surface). Drives `state.analystByMint`.
|
|
37
|
+
*/
|
|
38
|
+
export function analystKey(s) {
|
|
39
|
+
if (s.screen === 'token')
|
|
40
|
+
return s.live?.token?.view.mint ?? s.token?.mint ?? null;
|
|
41
|
+
if (s.screen === 'analyst')
|
|
42
|
+
return ANALYST_TAB_KEY;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/** Point `state.analyst` at the saved conversation for the current screen (task-1311, QA M5). */
|
|
46
|
+
export function syncAnalyst(s) {
|
|
47
|
+
const k = analystKey(s);
|
|
48
|
+
if (!k || !s.analystByMint)
|
|
49
|
+
return;
|
|
50
|
+
s.analyst = s.analystByMint.get(k) ?? null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Keep the New pairs highlight on the coin the cursor picked, not on a row index (task-1311, QA M8):
|
|
54
|
+
* remap `state.selected` to the current position of `state.selectedMint`. Called after the list
|
|
55
|
+
* changes. When the pinned mint has scrolled off, the selection is clamped in place.
|
|
56
|
+
*/
|
|
57
|
+
export function pinNewpairsSelection(s) {
|
|
58
|
+
const live = s.live;
|
|
59
|
+
if (!live || s.screen !== 'newpairs' || !s.selectedMint)
|
|
60
|
+
return;
|
|
61
|
+
const list = live.newpairs.list();
|
|
62
|
+
const i = list.findIndex((r) => r.mint === s.selectedMint);
|
|
63
|
+
if (i >= 0)
|
|
64
|
+
s.selected = i;
|
|
65
|
+
else if (list.length)
|
|
66
|
+
s.selected = Math.max(0, Math.min(list.length - 1, s.selected || 0));
|
|
67
|
+
}
|
|
68
|
+
/** Map the screener's visible rows to narrative rows (the fallback source when there is no recorder DB). */
|
|
69
|
+
export function narrativeRowsFromScreener(rows) {
|
|
70
|
+
return rows.filter((r) => r.mint && !r.skeleton).map((r) => ({
|
|
71
|
+
mint: r.mint, symbol: r.symbol, name: r.name,
|
|
72
|
+
vol_h1: r.vol1h, vol_h24: r.vol24, chg_h1: r.chg.h1, chg_h24: r.chg.h24,
|
|
73
|
+
holder_count: r.holders, liquidity_usd: r.liq, mcap: r.mcap,
|
|
74
|
+
age_sec: r.ageMin == null ? null : Math.round(r.ageMin * 60), feeds: [], t: r.fetchedAt,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Recompute the narrative board for the current state: from the history recorder when its DB is reachable
|
|
79
|
+
* (the design's recorded-history source), else from the visible screener rows so the board is always
|
|
80
|
+
* populated in fixtures / replay / live-without-recorder.
|
|
81
|
+
*/
|
|
82
|
+
export function computeNarrativeView(state, window, now = Date.now()) {
|
|
83
|
+
const lex = loadDefaultLexicon();
|
|
84
|
+
try {
|
|
85
|
+
const db = state.live?.recorder?.database ?? null;
|
|
86
|
+
if (db) {
|
|
87
|
+
const rows = recentNarrativeRows(db, { now });
|
|
88
|
+
if (rows.length)
|
|
89
|
+
return { board: computeBoard(rows, lex, { window, now }), window, computedAt: now, source: 'recorded', selected: 0 };
|
|
90
|
+
}
|
|
91
|
+
const rows = narrativeRowsFromScreener(state.screener.rows);
|
|
92
|
+
if (rows.length)
|
|
93
|
+
return { board: computeBoard(rows, lex, { window, now }), window, computedAt: now, source: 'screen', selected: 0 };
|
|
94
|
+
return { board: null, window, computedAt: now, source: 'none', selected: 0 };
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return { board: null, window, computedAt: now, source: 'none', selected: 0, error: e instanceof Error ? e.message : String(e) };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function newPlansUi() { return { tab: 'plans', selected: 0, notice: null, arming: null, riskModal: false, liveModal: false }; }
|
|
101
|
+
/**
|
|
102
|
+
* task-1363: how old a board price may be and still fire a plan. The terminal feeds the plan engine from its
|
|
103
|
+
* screener/watchlist refresh (about every 10 s) and the token screen, so the engine's 3 s default would never
|
|
104
|
+
* see two consecutive fresh ticks; 15 s keeps the two-tick debounce meaningful at that cadence.
|
|
105
|
+
*/
|
|
106
|
+
export const PLANS_TICK_MAX_AGE_MS = 15_000;
|
|
107
|
+
/**
|
|
108
|
+
* The plan service for this run, created on first open and recovered from its journal. Held on AppState so
|
|
109
|
+
* the screen works in fixtures/replay/live alike; a test can inject one via `state.plansSvc`.
|
|
110
|
+
*/
|
|
111
|
+
export function plansService(state, env = process.env) {
|
|
112
|
+
if (!state.plansSvc)
|
|
113
|
+
state.plansSvc = new PlanService({ journalPath: defaultPlansJournalPath(env), priceMaxAgeMs: PLANS_TICK_MAX_AGE_MS });
|
|
114
|
+
return state.plansSvc;
|
|
115
|
+
}
|
|
116
|
+
/** The rows the plans screen shows for the active tab (plans or mandates). */
|
|
117
|
+
export function plansRows(state) {
|
|
118
|
+
const svc = state.plansSvc;
|
|
119
|
+
if (!svc)
|
|
120
|
+
return [];
|
|
121
|
+
return state.plansUi?.tab === 'mandates' ? svc.listMandates() : svc.listPlans();
|
|
122
|
+
}
|
|
123
|
+
export function newFollowsUi() { return { selected: 0, input: null, notice: null, mirror: null, autoModal: null }; }
|
|
124
|
+
/**
|
|
125
|
+
* Tier 3: the auto-mirror service for this run, attached to the copy binding on first use. It sits on the plan
|
|
126
|
+
* service (the signer trust domain) and the copy binding's market resolver; its journal is ~/.dexterm/copy.journal.
|
|
127
|
+
* Auto is OFF after every start; enabling is a keypress on the follows screen.
|
|
128
|
+
*/
|
|
129
|
+
export function copyAutoService(state, env = process.env) {
|
|
130
|
+
const copy = copyService(state, env);
|
|
131
|
+
if (!copy.auto) {
|
|
132
|
+
const plans = plansService(state, env);
|
|
133
|
+
const live = state.live ?? null;
|
|
134
|
+
copy.auto = new CopyAuto({
|
|
135
|
+
plans, journalPath: defaultCopyJournalPath(env),
|
|
136
|
+
market: (mint) => copy.mirrorMarket(mint, {}),
|
|
137
|
+
trades: (wallet) => copy.trades(wallet),
|
|
138
|
+
isFollowed: (wallet) => copy.store.has(wallet),
|
|
139
|
+
leaderIntel: (mint, wallet) => {
|
|
140
|
+
const res = live?.intel.entry(mint)?.result;
|
|
141
|
+
if (!res || res.state !== 'ok')
|
|
142
|
+
return null;
|
|
143
|
+
const h = res.analysis.holders.find((x) => x.owner === wallet);
|
|
144
|
+
return { inDevCluster: res.analysis.clusters.some((c) => c.kind === 'dev-linked' && c.members.includes(wallet)) || !!h?.labels.some((l) => l.label === 'dev'), holdsPct: h ? h.pct : null };
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return copy.auto;
|
|
149
|
+
}
|
|
150
|
+
export { leaderSoldSince };
|
|
151
|
+
/** The user's own cap for ONE mirror ticket (USD). The mandate's per-trade cap still applies at arming. */
|
|
152
|
+
export const MIRROR_DEFAULT_CAP_USD = 20;
|
|
153
|
+
/**
|
|
154
|
+
* The copy-trading binding for this run: the live context's (polling Helius history) when there is one, else a
|
|
155
|
+
* store-only binding whose tracker says "unavailable" (fixtures / one-shot). A test injects `state.copy`.
|
|
156
|
+
*/
|
|
157
|
+
export function copyService(state, env = process.env) {
|
|
158
|
+
if (!state.copy)
|
|
159
|
+
state.copy = state.live?.copy ?? new CopyLive(null, { env });
|
|
160
|
+
return state.copy;
|
|
161
|
+
}
|
|
162
|
+
export const RENDERERS = {
|
|
163
|
+
screener: renderScreener,
|
|
164
|
+
token: renderToken,
|
|
165
|
+
newpairs: renderNewPairs,
|
|
166
|
+
watchlist: renderWatchlist,
|
|
167
|
+
analyst: renderAnalyst,
|
|
168
|
+
boot: renderBoot,
|
|
169
|
+
ticket: renderTicket,
|
|
170
|
+
positions: renderPositions,
|
|
171
|
+
trending: renderTrending,
|
|
172
|
+
kol: renderKol,
|
|
173
|
+
bubbles: renderBubbles,
|
|
174
|
+
narrative: renderNarrative,
|
|
175
|
+
plans: renderPlans,
|
|
176
|
+
follows: renderFollows,
|
|
177
|
+
rules: renderRules,
|
|
178
|
+
};
|
|
179
|
+
export const SCREEN_NAMES = Object.keys(RENDERERS);
|
|
180
|
+
export function isScreenKey(s) {
|
|
181
|
+
return SCREEN_NAMES.includes(s);
|
|
182
|
+
}
|
|
183
|
+
/** Screens that render fixtures while the app runs on real data (the analyst until the Claude lane; any live screen without its binding). */
|
|
184
|
+
export function isDemoScreen(s) {
|
|
185
|
+
if (s.screener.source === 'fixtures')
|
|
186
|
+
return false;
|
|
187
|
+
if (s.screen === 'screener')
|
|
188
|
+
return false;
|
|
189
|
+
if (s.screen === 'token')
|
|
190
|
+
return !s.live?.token;
|
|
191
|
+
if (s.screen === 'newpairs' || s.screen === 'watchlist')
|
|
192
|
+
return !s.live;
|
|
193
|
+
// PAPER trade screens carry their own banner; the header shows a PAPER status, not "demo data".
|
|
194
|
+
if (s.screen === 'ticket' || s.screen === 'positions')
|
|
195
|
+
return false;
|
|
196
|
+
// task-1330: the social screens read real keyless venues/news in live mode (fixtures mode is demo like the rest).
|
|
197
|
+
if (s.screen === 'trending' || s.screen === 'kol')
|
|
198
|
+
return false;
|
|
199
|
+
// task-1340: the bubble map reads the live scan (its own screen says loading / unavailable when it has nothing)
|
|
200
|
+
if (s.screen === 'bubbles')
|
|
201
|
+
return false;
|
|
202
|
+
// task-1360: the follows screen shows the user's own list + the tracker's real state (unavailable when keyless).
|
|
203
|
+
if (s.screen === 'follows')
|
|
204
|
+
return false;
|
|
205
|
+
// analyst screen: a live Claude session (even mid-run or failed) is real output, not demo data
|
|
206
|
+
return !s.analyst;
|
|
207
|
+
}
|
|
208
|
+
/** The header's view of the state. Screens still on fixtures say so when the app runs on real data. */
|
|
209
|
+
export function headerModel(s) {
|
|
210
|
+
const demo = isDemoScreen(s);
|
|
211
|
+
let status = s.screener.headerStatus;
|
|
212
|
+
let feedLabel = s.feedLabel;
|
|
213
|
+
if (s.live && !demo) {
|
|
214
|
+
if (s.screen === 'newpairs') {
|
|
215
|
+
const st = s.live.newpairs.status();
|
|
216
|
+
status = st.headerStatus ?? status;
|
|
217
|
+
feedLabel = st.caption;
|
|
218
|
+
}
|
|
219
|
+
else if (s.screen === 'watchlist') {
|
|
220
|
+
status = s.live.watch.status().headerStatus ?? status;
|
|
221
|
+
feedLabel = 'dexscreener · jup · sqlite watchlist';
|
|
222
|
+
}
|
|
223
|
+
else if (s.screen === 'token')
|
|
224
|
+
feedLabel = `${s.live.rpc.provider === 'helius' ? 'helius · ' : ''}gecko · rugcheck · goplus · dexscreener · jup`;
|
|
225
|
+
status = s.live.creditsStatus() ?? status;
|
|
226
|
+
}
|
|
227
|
+
if (s.screen === 'ticket' || s.screen === 'positions') {
|
|
228
|
+
status = '◈ PAPER · simulation';
|
|
229
|
+
feedLabel = 'jupiter keyless quote · paper wallet';
|
|
230
|
+
}
|
|
231
|
+
if (s.screen === 'trending' && s.live)
|
|
232
|
+
feedLabel = 'jupiter · gecko · boosts · coingecko · rss · keyless';
|
|
233
|
+
if (s.screen === 'kol')
|
|
234
|
+
feedLabel = 'kol lists · sqlite';
|
|
235
|
+
// task-1355: the plans screen is the signer trust domain; it is paper by default and says so.
|
|
236
|
+
// task-1363: once the fire path is LIVE (devnet fast wallet, keypress opt-in) the header says so; anything not
|
|
237
|
+
// provably paper renders LIVE (fail-safe).
|
|
238
|
+
if (s.screen === 'plans') {
|
|
239
|
+
const paper = s.plansSvc ? s.plansSvc.mode() === 'paper' : true;
|
|
240
|
+
status = paper ? '◈ PAPER · plans journal' : '◉ LIVE · DEVNET · fast wallet';
|
|
241
|
+
feedLabel = paper ? 'order plans · standing mandates · hash-chained journal' : 'order plans · signer process · fast wallet · devnet';
|
|
242
|
+
}
|
|
243
|
+
// task-1360: the follows screen reads public history for the user's own list; it never trades.
|
|
244
|
+
if (s.screen === 'follows')
|
|
245
|
+
feedLabel = `${s.live?.rpc.provider === 'helius' ? 'helius history · ' : ''}your follow list (local) · alerts`;
|
|
246
|
+
// task-1358: the rules screen evaluates the screener rows locally; rules only alert or PROPOSE.
|
|
247
|
+
if (s.screen === 'rules')
|
|
248
|
+
feedLabel = 'rules · config.json · alerts + proposals only · nothing arms';
|
|
249
|
+
if (s.screen === 'bubbles')
|
|
250
|
+
feedLabel = `${s.live?.rpc.provider === 'helius' ? 'helius history · ' : ''}rpc holders · rules · sqlite`;
|
|
251
|
+
// task-1328: a LIVE ticket says so in the header, with the SIGNER's network (never a UI flag).
|
|
252
|
+
if (s.screen === 'ticket' && s.live?.ticket?.ticket.mode === 'live') {
|
|
253
|
+
const net = s.live.ticket.view().live?.signer?.network ?? null;
|
|
254
|
+
status = net === 'mainnet' ? '⚠ LIVE · MAINNET · real funds' : net === 'devnet' ? '◉ LIVE · DEVNET' : '◉ LIVE · signer unreachable';
|
|
255
|
+
feedLabel = 'signer process · jupiter · phantom';
|
|
256
|
+
}
|
|
257
|
+
// task-1324: while AI scan mode is on the header ALWAYS says so (state + the scan's analyst meter +
|
|
258
|
+
// any Helius credit warning), on every screen — the running indicator wins over every other status.
|
|
259
|
+
let scanStatus = null;
|
|
260
|
+
if (s.scan?.engine.on)
|
|
261
|
+
scanStatus = s.live?.meterStatus(scanIndicator(s.scan)) ?? scanIndicator(s.scan);
|
|
262
|
+
return {
|
|
263
|
+
screen: s.screen, paused: s.paused,
|
|
264
|
+
// live mode never shows the fixture SOL quote; it shows the real one when the feed carried it, else nothing
|
|
265
|
+
sol: s.screener.source === 'fixtures' ? s.fx.sol : (s.sol ?? null),
|
|
266
|
+
now: s.now, timeZone: s.timeZone, feedLabel,
|
|
267
|
+
status: scanStatus ?? (demo ? '◌ demo data' : status),
|
|
268
|
+
calm: isCalm(s.vfx),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/** Wrapped SOL mint: when it is among the enriched rows the header can quote it for free. */
|
|
272
|
+
export const SOL_MINT = 'So11111111111111111111111111111111111111112';
|
|
273
|
+
/** The prototype's frozen clock: 2026-10-05 23:04:11 UTC (19:04:11 in New York). */
|
|
274
|
+
export const FIXTURE_NOW = new Date(Date.UTC(2026, 9, 5, 23, 4, 11));
|
|
275
|
+
export function makeFixtureState(opts = {}) {
|
|
276
|
+
const seed = opts.seed ?? 7;
|
|
277
|
+
const fx = makeFixtures(seed);
|
|
278
|
+
const screen = opts.screen ?? 'screener';
|
|
279
|
+
const selected = opts.selected ?? 0;
|
|
280
|
+
const vfx = makeVfx({ seed, intensity: opts.intensity, calm: opts.calm, ascii: opts.asciiRain });
|
|
281
|
+
vfx.frame = opts.frame ?? 0;
|
|
282
|
+
const state = {
|
|
283
|
+
fx,
|
|
284
|
+
screen,
|
|
285
|
+
theme: opts.theme ?? THEMES.cyberspace,
|
|
286
|
+
selected,
|
|
287
|
+
token: screen === 'token' ? fx.tokens[selected] ?? fx.tokens[0] : null,
|
|
288
|
+
aiOpen: opts.aiOpen ?? true,
|
|
289
|
+
paused: false,
|
|
290
|
+
now: new Date(FIXTURE_NOW.getTime()),
|
|
291
|
+
timeZone: opts.timeZone === null ? undefined : (opts.timeZone ?? 'America/New_York'),
|
|
292
|
+
refreshedAgo: '2s',
|
|
293
|
+
screener: makeScreenerState('fixtures', fx.tokens.map(fromFixture)),
|
|
294
|
+
vfx,
|
|
295
|
+
boot: { t: 1 },
|
|
296
|
+
ai: null,
|
|
297
|
+
analystByMint: new Map(),
|
|
298
|
+
larpByMint: new Map(),
|
|
299
|
+
colorDepth: opts.colorDepth ?? 'truecolor',
|
|
300
|
+
};
|
|
301
|
+
refreshVfx(state);
|
|
302
|
+
if (screen === 'ticket' || screen === 'positions')
|
|
303
|
+
seedDemoTrade(state);
|
|
304
|
+
return state;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Seed a demo PAPER ticket / positions view for one-shot fixture frames (`--screen ticket/positions
|
|
308
|
+
* --fixtures`) so the screens render without a live binding. The real data comes from the live
|
|
309
|
+
* bindings (live/trade.ts); this is only the fixtures/evidence path.
|
|
310
|
+
*/
|
|
311
|
+
export function seedDemoTrade(state) {
|
|
312
|
+
const mint = 'FTREEabc1111111111111111111111111111111111x';
|
|
313
|
+
const decimals = 6;
|
|
314
|
+
if (state.screen === 'ticket') {
|
|
315
|
+
const ticket = openTicket({ mint, symbol: 'FTREE', decimals, side: 'buy' });
|
|
316
|
+
ticket.sizeInput = '0.5';
|
|
317
|
+
const quote = {
|
|
318
|
+
inputMint: SOL_MINT, outputMint: mint, inAmount: 500000000n, outAmount: 1250000000n,
|
|
319
|
+
otherAmountThreshold: 1243750000n, mode: 'ExactIn', slippageBps: 50, priceImpactPct: 0.42,
|
|
320
|
+
route: ['Meteora', 'Whirlpool'], platformFeeBps: null, platformFeeAmount: null, usdValue: 75,
|
|
321
|
+
contextSlot: 1, source: 'jupiter-swap', fetchedAt: FIXTURE_NOW.getTime(),
|
|
322
|
+
};
|
|
323
|
+
const order = { mint, symbol: 'FTREE', side: 'buy', decimals, amountRaw: 500000000n, slippageBps: 50 };
|
|
324
|
+
state.ticket = {
|
|
325
|
+
ticket, balanceSol: 10, positionTokens: 0, positionValueSol: null, quote,
|
|
326
|
+
preview: computeFill(order, quote, FIXTURE_NOW.getTime()), quoteAgeMs: 2000, loading: false, error: null, receipt: null, rateLimited: false,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
state.positions = {
|
|
331
|
+
balanceSol: 8.5, startSol: 10, equitySol: 10.42, totalUnrealizedSol: 0.42, totalRealizedSol: 0.12, totalPct: 0.042,
|
|
332
|
+
solPriceUsd: 150,
|
|
333
|
+
rows: [{ mint, symbol: 'FTREE', tokens: 1250, costSol: 1.5, avgEntrySol: 0.0012, priceSol: 0.00154, valueSol: 1.92, unrealizedSol: 0.42, unrealizedPct: 0.28, realizedSol: 0.12 }],
|
|
334
|
+
history: [
|
|
335
|
+
{ id: 2, t: FIXTURE_NOW.getTime() - 30_000, mint, symbol: 'FTREE', side: 'sell', decimals, solDelta: 300000000n, tokenDelta: -200000000n, grossLamports: 300005000n, feeLamports: 5000n, priceSol: 0.0015, priceUsd: 0.225, priceImpactPct: 0.3, slippageBps: 50, route: ['Meteora'], realizedLamports: 60000000n, paper: true },
|
|
336
|
+
{ id: 1, t: FIXTURE_NOW.getTime() - 120_000, mint, symbol: 'FTREE', side: 'buy', decimals, solDelta: -1500005000n, tokenDelta: 1250000000n, grossLamports: 1500000000n, feeLamports: 5000n, priceSol: 0.0012, priceUsd: 0.18, priceImpactPct: 0.42, slippageBps: 50, route: ['Meteora', 'Whirlpool'], realizedLamports: 0n, paper: true },
|
|
337
|
+
],
|
|
338
|
+
loading: false,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* State over a data-store feed (live or replayed recording). The fixtures still back the screens
|
|
344
|
+
* that have not been ported (token detail D-05, new pairs D-10, watchlist D-11); the header marks
|
|
345
|
+
* them as demo data. Call syncFeed() after every refresh.
|
|
346
|
+
*/
|
|
347
|
+
export function makeFeedState(feed, source, opts = {}) {
|
|
348
|
+
const state = makeFixtureState(opts);
|
|
349
|
+
state.feed = feed;
|
|
350
|
+
state.screener = makeScreenerState(source, [], { presets: feed.presets(), gates: feed.gates() });
|
|
351
|
+
state.screener.preset = feed.preset;
|
|
352
|
+
syncFeed(state);
|
|
353
|
+
return state;
|
|
354
|
+
}
|
|
355
|
+
/** Copy the feed's rows, gates, health status and clock into the state, then rebuild the rain sources and activity. */
|
|
356
|
+
export function syncFeed(state) {
|
|
357
|
+
const feed = state.feed;
|
|
358
|
+
if (!feed)
|
|
359
|
+
return;
|
|
360
|
+
const sc = state.screener;
|
|
361
|
+
sc.rows = feed.rows;
|
|
362
|
+
sc.gates = feed.gates();
|
|
363
|
+
sc.presets = feed.presets();
|
|
364
|
+
sc.preset = feed.preset;
|
|
365
|
+
sc.pool = feed.pool;
|
|
366
|
+
sc.slowing = feed.slowing;
|
|
367
|
+
const st = feed.status();
|
|
368
|
+
sc.headerStatus = st.headerStatus;
|
|
369
|
+
sc.errors = st.errors;
|
|
370
|
+
state.feedLabel = st.feedLabel;
|
|
371
|
+
const sol = feed.rows.find((r) => r.mint === SOL_MINT);
|
|
372
|
+
if (sol && sol.price !== null)
|
|
373
|
+
state.sol = { price: sol.price, chg24: sol.chg.h24 ?? 0 };
|
|
374
|
+
state.refreshedAgo = feed.refreshedAgo();
|
|
375
|
+
state.now = new Date(feed.now());
|
|
376
|
+
refreshVfx(state);
|
|
377
|
+
}
|
|
378
|
+
/** Rain columns are the user's own data: watchlist + screener tickers, mint prefixes, trade address tails (VISUAL-V2 §7). */
|
|
379
|
+
export function rainSources(state) {
|
|
380
|
+
const s = [];
|
|
381
|
+
const fx = state.fx;
|
|
382
|
+
for (const w of fx.watch)
|
|
383
|
+
s.push(w.token.symbol);
|
|
384
|
+
const live = state.screener.source !== 'fixtures' ? state.screener.rows : [];
|
|
385
|
+
if (live.length) {
|
|
386
|
+
for (const r of live.slice(0, 16))
|
|
387
|
+
s.push(r.symbol.toUpperCase());
|
|
388
|
+
for (const r of live.slice(0, 10))
|
|
389
|
+
s.push(r.mint.slice(0, 10).toUpperCase());
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
for (const t of fx.tokens.slice(0, 12))
|
|
393
|
+
s.push(t.symbol);
|
|
394
|
+
for (const t of fx.tokens.slice(0, 8))
|
|
395
|
+
s.push(t.mint.slice(0, 10).toUpperCase());
|
|
396
|
+
}
|
|
397
|
+
for (const t of fx.tokens.slice(0, 4))
|
|
398
|
+
for (const tr of t.trades.slice(0, 3))
|
|
399
|
+
s.push(tr.addr.slice(-8).toUpperCase());
|
|
400
|
+
return s;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Market activity for the rain (VISUAL-V2 §3, §7): on the token screen the selected token's own
|
|
404
|
+
* candles drive colour and speed; elsewhere the cross-section of the rows on screen does (live
|
|
405
|
+
* rows when the feed has them, fixtures otherwise), with volume velocity when the source reports it.
|
|
406
|
+
*/
|
|
407
|
+
export function activityFor(state) {
|
|
408
|
+
if (state.screen === 'token') {
|
|
409
|
+
const tk = state.token || state.fx.tokens[state.selected || 0];
|
|
410
|
+
if (tk)
|
|
411
|
+
return candleActivity(tk.candles, tk.chg.m5);
|
|
412
|
+
}
|
|
413
|
+
const live = state.screener.source !== 'fixtures' ? state.screener.rows : [];
|
|
414
|
+
if (live.length) {
|
|
415
|
+
const rows = state.screen === 'screener' ? screenerView(state.screener) : live;
|
|
416
|
+
const use = rows.length ? rows : live;
|
|
417
|
+
return marketActivity(use.map((r) => r.chg.m5), use.map((r) => (r.vol5m !== null && r.vol24 !== null && r.vol24 > 0 ? (r.vol5m * 288) / r.vol24 : null)));
|
|
418
|
+
}
|
|
419
|
+
const all = [...state.fx.tokens, ...state.fx.newPairs];
|
|
420
|
+
return marketActivity(all.map((t) => t.chg.m5));
|
|
421
|
+
}
|
|
422
|
+
/** Rebuild rain sources and set activity/mood directly from the data (one-shot renders and feed syncs). */
|
|
423
|
+
export function refreshVfx(state) {
|
|
424
|
+
setSources(state.vfx, rainSources(state));
|
|
425
|
+
const act = activityFor(state);
|
|
426
|
+
state.vfx.activity = act.activity;
|
|
427
|
+
state.vfx.mood = act.mood;
|
|
428
|
+
}
|
|
429
|
+
/** Render one frame of the current screen into a fresh canvas: screen → toast → effects. */
|
|
430
|
+
export function renderFrame(state, w, h) {
|
|
431
|
+
const c = new Canvas(w, h, state.theme);
|
|
432
|
+
c.fill(0, 0, w, h, ' ', { bg: state.theme.bg });
|
|
433
|
+
c.imageMode = state.images && state.imageProtocol && state.imageProtocol !== 'blocks' ? 'pixels' : 'cells';
|
|
434
|
+
(RENDERERS[state.screen] || renderScreener)(c, state);
|
|
435
|
+
// task-1324: the SCANNING ticker on row 1 (the blank band under the header) of every list screen; the
|
|
436
|
+
// live token screen draws its own WHY IT TRIGGERED strip there (token-live.ts).
|
|
437
|
+
if (state.scan?.engine.on && state.screen !== 'boot' && state.screen !== 'token')
|
|
438
|
+
renderScanTicker(c, state.scan, state.live?.scanMeterLabel() ?? '');
|
|
439
|
+
if (state.screen !== 'boot')
|
|
440
|
+
toast(c, state.vfx.toast);
|
|
441
|
+
applyFx(c, state.vfx, { scan: state.screen !== 'boot' });
|
|
442
|
+
// The voice indicator (task-1282) draws after the effects so the mic-live badge is always crisp and
|
|
443
|
+
// unmistakable. Suppressed on the boot screen, where row 1 is part of the boot art.
|
|
444
|
+
if (state.voice?.enabled && state.screen !== 'boot')
|
|
445
|
+
renderVoiceHud(c, state.voice, state.theme);
|
|
446
|
+
// task-1358: the workspace badge (non-default name, a pending ctrl+w chord, the save-as input) on the band
|
|
447
|
+
// under the header, right-aligned, drawn after the effects so it is always legible. Nothing on `default`.
|
|
448
|
+
const badge = state.screen !== 'boot' ? workspaceBadge(state.custom, state.now.getTime()) : null;
|
|
449
|
+
if (badge) {
|
|
450
|
+
const t = state.theme;
|
|
451
|
+
const text = badge.text.slice(0, Math.max(0, w - 2));
|
|
452
|
+
const x = Math.max(0, w - text.length - 1);
|
|
453
|
+
c.noRain(x, 1, text.length, 1);
|
|
454
|
+
c.text(x, 1, text, badge.kind === 'input' ? { fg: t.text, bg: t.accentSoft, b: true } : badge.kind === 'chord' ? { fg: t.warn, bg: t.warnSoft, b: true } : { fg: t.accent, bg: t.headBg, b: true });
|
|
455
|
+
}
|
|
456
|
+
// Overlays (task-1305) draw last, over the effects, so the welcome/help card is always crisp.
|
|
457
|
+
if (state.overlay)
|
|
458
|
+
renderOverlay(c, state);
|
|
459
|
+
// task-1388: the GO-TO prompt draws on top of everything (but is mutually exclusive with an overlay).
|
|
460
|
+
if (state.goto)
|
|
461
|
+
renderGotoPrompt(c, state);
|
|
462
|
+
return c;
|
|
463
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// `dexterm theme <list|show|init|check> …` and `dexterm keymap [screen]` (task-1358 Tier 2). No network, no
|
|
2
|
+
// model, never touches a key file.
|
|
3
|
+
// theme list built-ins + ~/.dexterm/themes/*.json + config.theme.user, with errors for bad ones
|
|
4
|
+
// theme show <name> the resolved tokens (user theme merged over its base, overrides applied)
|
|
5
|
+
// theme init <name> [--extends cyberspace|midnight|ember|paper] write a starter theme file to edit
|
|
6
|
+
// theme check <file> validate a theme file (schema, contrast floor, up/down hue) without installing it
|
|
7
|
+
// keymap [screen] the effective bindings (defaults + config.keymap), user overrides marked with ·
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { THEMES, THEME_NAMES, DEFAULT_KEYMAP } from '../../tui/src/index.js';
|
|
11
|
+
import { dextermHome, loadConfig, listUserThemes, resolveTheme, applyOverrides, starterTheme, themesDir, validate, applyKeymapConfig, renderKeymap, BASE_THEMES } from '../../custom/src/index.js';
|
|
12
|
+
export const THEME_USAGE = `usage: dexterm theme list | show <name> | init <name> [--extends ${BASE_THEMES.join('|')}] | check <file>
|
|
13
|
+
files: ~/.dexterm/themes/<name>.json = { "extends": "<built-in>", "tokens": { "<token>": "#rrggbb", "<ramp>": [4 × "#rrggbb"] } }
|
|
14
|
+
guard: text/warn/up/down must keep a contrast floor against bg/panel; up stays green/cyan, down stays red/pink`;
|
|
15
|
+
const bases = THEMES;
|
|
16
|
+
export async function themeCommand(argv, io) {
|
|
17
|
+
const home = dextermHome(io.env);
|
|
18
|
+
const [cmd, name] = argv;
|
|
19
|
+
const bad = (s) => { io.stderr.write(`dexterm theme: ${s}\n${THEME_USAGE}\n`); return 2; };
|
|
20
|
+
const cfg = loadConfig(home);
|
|
21
|
+
switch (cmd) {
|
|
22
|
+
case undefined:
|
|
23
|
+
case 'list': {
|
|
24
|
+
io.stdout.write(`${themesDir(home)}\n`);
|
|
25
|
+
for (const n of THEME_NAMES)
|
|
26
|
+
io.stdout.write(` ${n.padEnd(24)} built-in\n`);
|
|
27
|
+
for (const t of listUserThemes(home, cfg.config)) {
|
|
28
|
+
if (t.error) {
|
|
29
|
+
io.stdout.write(` ${t.name.padEnd(24)} ⚠ ${t.error}\n`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const r = resolveTheme(t.doc, bases);
|
|
33
|
+
io.stdout.write(` ${t.name.padEnd(24)} ${t.path ? 'file' : 'config'} · extends ${t.doc.extends} · ${Object.keys(t.doc.tokens ?? {}).length} token(s)${r.theme ? '' : ` · ⚠ ${r.errors[0].path} ${r.errors[0].msg}`}\n`);
|
|
34
|
+
}
|
|
35
|
+
const ov = cfg.config.theme?.overrides ?? {};
|
|
36
|
+
if (Object.keys(ov).length)
|
|
37
|
+
io.stdout.write(` overrides (config.theme.overrides): ${Object.keys(ov).join(', ')}\n`);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
case 'show': {
|
|
41
|
+
if (!name)
|
|
42
|
+
return bad('show needs a name');
|
|
43
|
+
let doc = bases[name] ?? null;
|
|
44
|
+
if (!doc) {
|
|
45
|
+
const t = listUserThemes(home, cfg.config).find((x) => x.name === name);
|
|
46
|
+
if (!t) {
|
|
47
|
+
io.stderr.write(`dexterm theme: no theme "${name}"\n`);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
if (t.error) {
|
|
51
|
+
io.stderr.write(`dexterm theme: ${name}: ${t.error}\n`);
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
const r = resolveTheme(t.doc, bases);
|
|
55
|
+
if (!r.theme) {
|
|
56
|
+
io.stderr.write(`dexterm theme: ${name} refused: ${r.errors.map((e) => `${e.path} ${e.msg}`).join('; ')}\n`);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
doc = r.theme;
|
|
60
|
+
}
|
|
61
|
+
const o = applyOverrides(doc, cfg.config.theme?.overrides);
|
|
62
|
+
for (const e of o.errors)
|
|
63
|
+
io.stderr.write(`dexterm theme: override ignored: ${e.path} ${e.msg}\n`);
|
|
64
|
+
io.stdout.write(JSON.stringify(o.theme, null, 2) + '\n');
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
case 'init': {
|
|
68
|
+
if (!name)
|
|
69
|
+
return bad('init needs a name');
|
|
70
|
+
if (!/^[a-z][a-z0-9-]{0,39}$/.test(name))
|
|
71
|
+
return bad('name: lowercase letters, digits and dashes');
|
|
72
|
+
if (BASE_THEMES.includes(name))
|
|
73
|
+
return bad(`"${name}" is a built-in theme`);
|
|
74
|
+
const ei = argv.indexOf('--extends');
|
|
75
|
+
const base = ei >= 0 ? argv[ei + 1] : 'cyberspace';
|
|
76
|
+
if (!BASE_THEMES.includes(base))
|
|
77
|
+
return bad(`--extends expects ${BASE_THEMES.join('|')}`);
|
|
78
|
+
mkdirSync(themesDir(home), { recursive: true, mode: 0o700 });
|
|
79
|
+
const path = join(themesDir(home), `${name}.json`);
|
|
80
|
+
if (existsSync(path)) {
|
|
81
|
+
io.stdout.write(`${path} already exists\n`);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
const { name: _n, ...body } = starterTheme(name, base);
|
|
85
|
+
void _n;
|
|
86
|
+
writeFileSync(path, JSON.stringify(body, null, 2) + '\n', { mode: 0o600 });
|
|
87
|
+
io.stdout.write(`wrote ${path}\n edit the tokens, then: dexterm --theme ${name} (or t in the app cycles to it)\n`);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
case 'check': {
|
|
91
|
+
if (!name)
|
|
92
|
+
return bad('check needs a file');
|
|
93
|
+
let raw;
|
|
94
|
+
try {
|
|
95
|
+
raw = JSON.parse(readFileSync(name, 'utf8'));
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
io.stderr.write(`dexterm theme: ${name}: ${e.message}\n`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const doc = { name: 'check', ...raw };
|
|
102
|
+
const errors = validate('userTheme', doc);
|
|
103
|
+
const r = errors.length ? { theme: null, errors } : resolveTheme(doc, bases);
|
|
104
|
+
if (r.theme) {
|
|
105
|
+
io.stdout.write(`ok: ${name} extends ${doc.extends} (${Object.keys(doc.tokens ?? {}).length} token(s)); contrast floor and the up/down guard (hue, saturation, distance) pass\n`);
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
for (const e of r.errors)
|
|
109
|
+
io.stdout.write(`✗ ${e.path || '/'} ${e.msg}\n`);
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
default: return bad(`unknown subcommand "${cmd}"`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export async function keymapCommand(argv, io) {
|
|
116
|
+
const home = dextermHome(io.env);
|
|
117
|
+
const cfg = loadConfig(home);
|
|
118
|
+
for (const e of cfg.errors)
|
|
119
|
+
io.stderr.write(`dexterm keymap: config: ${e}\n`);
|
|
120
|
+
const r = applyKeymapConfig(DEFAULT_KEYMAP.bindings, cfg.config.keymap);
|
|
121
|
+
const screen = argv[0] && /^[a-z]+$/.test(argv[0]) ? argv[0] : undefined;
|
|
122
|
+
io.stdout.write(`${cfg.path}${cfg.missing ? ' (no config: defaults)' : ''} · config.keymap.global[action] = ["key", …] · config.keymap.screens[screen][action] = [...]\n`);
|
|
123
|
+
io.stdout.write(`reserved keys: ctrl+c esc enter y Y ! · reserved actions keep their keys: quit back alert.confirm alert.dismiss plans.arm plans.panic plans.gate\n`);
|
|
124
|
+
io.stdout.write(renderKeymap(r, screen) + '\n');
|
|
125
|
+
return r.errors.length ? 1 : 0;
|
|
126
|
+
}
|