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,312 @@
|
|
|
1
|
+
// dexterm — Token detail screen: glowing candles + on-chain stats + holders/tape rain + decrypting analyst.
|
|
2
|
+
// Cyberspace styling (task-1297): pump/dump band on the price line, rain zones in the title strip and
|
|
3
|
+
// the blank panel areas, ▲/▼ everywhere a direction is shown, the analyst's verdict and tool lines in
|
|
4
|
+
// coral with the Claude-Code spinner and decrypt stream (VISUAL-V2 §3).
|
|
5
|
+
import { Canvas, candleChart, statGrid, bar, badge, launchpadBadge, pctStyle, tapeRain, decryptText, spinnerGlyph, fmtUsd, fmtPrice, fmtMcap, fmtAge, fmtAgeSec, fmtNum, fmtPctDir, fmtDeltaDir, shortAddr, wrap, header, footer, breakpoint, mix, isCalm, } from '../../../tui/src/index.js';
|
|
6
|
+
import { headerModel } from "../state.js";
|
|
7
|
+
import { renderTokenLive } from "../live/screens/token-live.js";
|
|
8
|
+
import { aiTranscript } from "../fixtures.js";
|
|
9
|
+
import { renderAnalystSession } from "./analyst-session.js";
|
|
10
|
+
import { renderSocialPanel } from "./social-panel.js";
|
|
11
|
+
import { layoutFor } from "../custom.js";
|
|
12
|
+
import { chartMode, priceModeOf } from "../display.js";
|
|
13
|
+
export function renderToken(c, state) {
|
|
14
|
+
if (state.live?.token && state.screener.source !== 'fixtures')
|
|
15
|
+
return renderTokenLive(c, state, state.live.token.view); // task-1298: live data path
|
|
16
|
+
const t = c.theme;
|
|
17
|
+
const bp = breakpoint(c.w);
|
|
18
|
+
header(c, headerModel(state));
|
|
19
|
+
const tk = state.token || state.fx.tokens[state.selected || 0];
|
|
20
|
+
const top = 2, bottom = c.h - 1;
|
|
21
|
+
const lay = layoutFor(state.custom, c.w, c.h); // task-1358: analyst width + chart share from the workspace layout
|
|
22
|
+
const showAi = state.aiOpen !== false && bp !== 'compact' && lay.analystW > 0;
|
|
23
|
+
const aiW = showAi ? lay.analystW : 0;
|
|
24
|
+
const mainW = c.w - aiW;
|
|
25
|
+
const v = state.vfx;
|
|
26
|
+
const pumping = v.mood === 'pump';
|
|
27
|
+
const dumping = v.mood === 'dump';
|
|
28
|
+
// ---- title strip
|
|
29
|
+
let y = top;
|
|
30
|
+
c.fill(0, y, mainW, 2, ' ', { bg: t.bg });
|
|
31
|
+
let x = c.text(1, y, '‹ ', { fg: t.muted, bg: t.bg });
|
|
32
|
+
x = c.text(x, y, `$${tk.symbol}`, { fg: t.borderFocus, bg: t.bg, b: true });
|
|
33
|
+
c.heading(3, y, tk.symbol.length + 1);
|
|
34
|
+
x = c.text(x + 1, y, tk.name, { fg: t.muted, bg: t.bg });
|
|
35
|
+
x = launchpadBadge(c, x + 2, y, tk.launchpad, { on: t.bg });
|
|
36
|
+
if (tk.migrated && tk.launchpad === 'pump.fun')
|
|
37
|
+
x = badge(c, x + 1, y, 'PUMPSWAP', t.pump, { on: t.bg });
|
|
38
|
+
x = c.text(x + 2, y, shortAddr(tk.mint, 6), { fg: t.dim, bg: t.bg });
|
|
39
|
+
x = c.text(x + 2, y, `age ${fmtAge(tk.ageMin)}`, { fg: t.dim, bg: t.bg });
|
|
40
|
+
const socials = [tk.socials.x && 'X', tk.socials.tg && 'TG', tk.socials.web && 'WEB'].filter((s) => !!s);
|
|
41
|
+
let sx = mainW - 2 - socials.reduce((s, k) => s + k.length + 3, 0);
|
|
42
|
+
if (sx > x + 2) {
|
|
43
|
+
c.rainZone(x + 1, y, sx - x - 2, 1, 1);
|
|
44
|
+
for (const s of socials)
|
|
45
|
+
sx = badge(c, sx, y, s, t.muted, { on: t.bg }) + 1;
|
|
46
|
+
}
|
|
47
|
+
y += 1;
|
|
48
|
+
// price line: pump glow = gold bg band, dump = red band (the rain ramp of the moment)
|
|
49
|
+
const priceBg = pumping ? mix(t.bg, t.rainPump[2], 0.3) : dumping ? mix(t.bg, t.rainDump[2], 0.3) : t.bg;
|
|
50
|
+
// task-1366: mcap mode leads with the market cap and keeps the price beside it (fixture tokens always carry a supply)
|
|
51
|
+
const cm = chartMode(priceModeOf(state), tk.price, tk.mcap);
|
|
52
|
+
if (cm.kind === 'mcap') {
|
|
53
|
+
x = c.text(1, y, fmtMcap(tk.mcap), { fg: pumping ? t.rainPump[3] : t.text, bg: priceBg, b: true });
|
|
54
|
+
x = c.text(x, y, ` (${fmtPrice(tk.price)})`, { fg: t.dim, bg: priceBg });
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
x = c.text(1, y, fmtPrice(tk.price), { fg: cm.label ? t.warn : pumping ? t.rainPump[3] : t.text, bg: priceBg, b: true });
|
|
58
|
+
const deltas = [['5m', tk.chg.m5], ['1h', tk.chg.h1], ['6h', tk.chg.h6], ['24h', tk.chg.h24]];
|
|
59
|
+
x = c.text(x, y, ' ', { bg: t.bg });
|
|
60
|
+
for (const [k, vv] of deltas) {
|
|
61
|
+
x = c.text(x, y, k, { fg: t.dim, bg: t.bg });
|
|
62
|
+
x = c.text(x + 1, y, fmtPctDir(vv), { ...pctStyle(c, vv, t.bg), b: k === '24h' });
|
|
63
|
+
x += 2;
|
|
64
|
+
}
|
|
65
|
+
if (pumping)
|
|
66
|
+
x = badge(c, x + 1, y, '▲ PUMPING', t.rainPump[3], { bg: mix(t.bg, t.rainPump[2], 0.35), bold: true }) + 1;
|
|
67
|
+
if (dumping)
|
|
68
|
+
x = badge(c, x + 1, y, '▼ DUMPING', t.rainDump[3], { bg: mix(t.bg, t.rainDump[2], 0.35), bold: true }) + 1;
|
|
69
|
+
c.rainZone(x + 1, y, Math.max(0, mainW - x - 2), 1, 1);
|
|
70
|
+
y += 1;
|
|
71
|
+
// ---- chart
|
|
72
|
+
const chartH = Math.max(10, Math.floor((bottom - y) * lay.chartFrac));
|
|
73
|
+
const cb = c.box(0, y, mainW, chartH, { title: cm.label ? (cm.kind === 'mcap' ? `${tk.symbol} ${cm.label} · 1h` : `${tk.symbol}/SOL · ${cm.label} · 1h`) : `${tk.symbol}/SOL · 1h`, caption: 'ohlcv: geckoterminal' });
|
|
74
|
+
const tfx = cb.x + cb.w - 2;
|
|
75
|
+
const tfs = ['1m', '5m', '15m', '1h', '4h', '1d'];
|
|
76
|
+
let tx = tfx - tfs.reduce((s, f) => s + f.length + 1, 0) - 24;
|
|
77
|
+
for (const f of tfs)
|
|
78
|
+
tx = c.text(tx, cb.y - 1, f, f === '1h' ? { fg: t.accent, bg: cb.bg, b: true } : { fg: t.dim, bg: cb.bg }) + 1;
|
|
79
|
+
candleChart(c, cb.x + 1, cb.y, cb.w - 2, cb.h, tk.candles, { bg: cb.bg, interval: '1h', scale: cm.scale ?? undefined, axisFmt: cm.axisFmt });
|
|
80
|
+
y += chartH;
|
|
81
|
+
// ---- task-1330: the SOCIAL panel (`s`) replaces the ON-CHAIN / HOLDERS band; the chart stays.
|
|
82
|
+
if (state.tokenSocial) {
|
|
83
|
+
renderSocialPanel(c, 0, y, mainW, bottom - y, state, { mint: tk.mint, symbol: tk.symbol, now: state.now.getTime() });
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// ---- stats + holders/trades split
|
|
87
|
+
const lower = bottom - y;
|
|
88
|
+
const statsW = bp === 'compact' ? mainW : Math.min(44, Math.floor(mainW * 0.4));
|
|
89
|
+
const sb = c.box(0, y, statsW, lower, { title: 'ON-CHAIN' });
|
|
90
|
+
let sy = sb.y;
|
|
91
|
+
sy += statGrid(c, sb.x + 1, sy, sb.w - 2, [
|
|
92
|
+
{ label: 'mcap', value: fmtUsd(tk.mcap, { digits: 1 }) },
|
|
93
|
+
{ label: 'fdv', value: fmtUsd(tk.fdv, { digits: 1 }) },
|
|
94
|
+
{ label: 'liquidity', value: fmtUsd(tk.liq, { digits: 1 }), sub: `${(tk.liq / tk.mcap * 100).toFixed(1)}% of mcap` },
|
|
95
|
+
{ label: 'vol 24h', value: fmtUsd(tk.vol24, { digits: 1 }) },
|
|
96
|
+
{ label: 'holders', value: fmtNum(tk.holders), sub: fmtDeltaDir(tk.holders24) + ' 24h' },
|
|
97
|
+
{ label: 'organic', value: `${tk.org}/100`, style: { fg: tk.org >= 70 ? t.up : tk.org >= 40 ? t.warn : t.down, b: true } },
|
|
98
|
+
], { bg: sb.bg, cols: 2 });
|
|
99
|
+
sy += 1;
|
|
100
|
+
const rows = [
|
|
101
|
+
['Top 10 holders', `${tk.top10.toFixed(1)}%`, tk.top10 < 25 ? 'ok' : tk.top10 < 35 ? 'warn' : 'bad'],
|
|
102
|
+
['Dev wallet', `${tk.dev.toFixed(2)}%`, tk.dev < 3 ? 'ok' : 'warn'],
|
|
103
|
+
['Bundled supply', `${tk.bundled.toFixed(1)}%`, tk.bundled < 10 ? 'ok' : 'warn'],
|
|
104
|
+
['Snipers (block 0-2)', String(tk.snipers), tk.snipers < 5 ? 'ok' : 'warn'],
|
|
105
|
+
['LP locked / burned', `${tk.lpLocked}%`, tk.lpLocked >= 90 ? 'ok' : 'bad'],
|
|
106
|
+
['Mint authority', tk.mintAuth ? 'ENABLED' : 'revoked', tk.mintAuth ? 'bad' : 'ok'],
|
|
107
|
+
['Freeze authority', tk.freezeAuth ? 'ENABLED' : 'revoked', tk.freezeAuth ? 'bad' : 'ok'],
|
|
108
|
+
['Dev history', `${tk.devHistory.launches} launches · ${tk.devHistory.rugged} rugs`, tk.devHistory.rugged ? 'warn' : 'ok'],
|
|
109
|
+
['RugCheck score', `${tk.rug.score}/100`, tk.rug.score < 30 ? 'ok' : tk.rug.score < 60 ? 'warn' : 'bad'],
|
|
110
|
+
];
|
|
111
|
+
for (const [label, val, lvl] of rows) {
|
|
112
|
+
if (sy >= sb.y + sb.h)
|
|
113
|
+
break;
|
|
114
|
+
const color = lvl === 'ok' ? t.up : lvl === 'warn' ? t.warn : t.down;
|
|
115
|
+
c.text(sb.x + 1, sy, lvl === 'ok' ? '●' : lvl === 'warn' ? '◐' : '○', { fg: color, bg: sb.bg });
|
|
116
|
+
c.text(sb.x + 3, sy, label, { fg: t.muted, bg: sb.bg }, Math.max(0, sb.w - 4 - val.length - 1));
|
|
117
|
+
c.textR(sb.x + 1, sy, sb.w - 2, val, { fg: lvl === 'ok' ? t.text : color, bg: sb.bg, b: lvl === 'bad' });
|
|
118
|
+
sy += 1;
|
|
119
|
+
}
|
|
120
|
+
if (sy + 1 < sb.y + sb.h)
|
|
121
|
+
c.rainZone(sb.x + 1, sy + 1, sb.w - 2, sb.y + sb.h - sy - 1, 2);
|
|
122
|
+
if (bp !== 'compact') {
|
|
123
|
+
const hx = statsW;
|
|
124
|
+
const hw = mainW - statsW;
|
|
125
|
+
const hb = c.box(hx, y, hw, lower, { title: 'HOLDERS', caption: 'holders · trades · traders (h/t/r)' });
|
|
126
|
+
let hy = hb.y;
|
|
127
|
+
c.text(hb.x + 1, hy, 'WALLET', { fg: t.dim, bg: hb.bg });
|
|
128
|
+
c.textR(hb.x + 1, hy, hb.w - 2, '% SOL', { fg: t.dim, bg: hb.bg });
|
|
129
|
+
hy += 1;
|
|
130
|
+
const maxPct = Math.max(...tk.holdersTop.map((h) => h.pct));
|
|
131
|
+
const barW = Math.max(6, hb.w - 36);
|
|
132
|
+
for (const h of tk.holdersTop) {
|
|
133
|
+
if (hy >= hb.y + hb.h)
|
|
134
|
+
break;
|
|
135
|
+
const cx = c.text(hb.x + 1, hy, shortAddr(h.addr, 4), { fg: t.text, bg: hb.bg });
|
|
136
|
+
if (h.kind)
|
|
137
|
+
badge(c, cx + 1, hy, h.kind.toUpperCase(), h.kind === 'dev' ? t.warn : h.kind === 'sniper' ? t.down : h.kind === 'LP' ? t.accent : t.muted, { on: hb.bg });
|
|
138
|
+
const bx = hb.x + 22;
|
|
139
|
+
bar(c, bx, hy, barW, (h.pct / maxPct) * 100, { bg: hb.bg, color: h.kind === 'LP' ? t.accent : h.pct > 5 ? t.warn : t.up });
|
|
140
|
+
c.textR(hb.x + 1, hy, hb.w - 2, `${h.pct.toFixed(2).padStart(5)} ${fmtNum(h.sol).padStart(6)}`, { fg: h.pct > 5 ? t.warn : t.muted, bg: hb.bg });
|
|
141
|
+
hy += 1;
|
|
142
|
+
}
|
|
143
|
+
// trade tape: text rows on the left, falling rain lines on the right
|
|
144
|
+
if (hy + 3 < hb.y + hb.h) {
|
|
145
|
+
hy += 1;
|
|
146
|
+
c.hline(hb.x, hy - 1, hb.w, { fg: t.border, bg: hb.bg, soft: true });
|
|
147
|
+
c.text(hb.x + 1, hy, 'TAPE', { fg: t.dim, bg: hb.bg });
|
|
148
|
+
c.heading(hb.x + 1, hy, 4);
|
|
149
|
+
c.textR(hb.x + 1, hy, hb.w - 2, 'live · ws', { fg: t.dim, bg: hb.bg });
|
|
150
|
+
hy += 1;
|
|
151
|
+
const tapeTop = hy;
|
|
152
|
+
const textW = 46;
|
|
153
|
+
const rainX = hb.x + textW + 2;
|
|
154
|
+
const rainW = hb.x + hb.w - 1 - rainX;
|
|
155
|
+
for (const tr of tk.trades) {
|
|
156
|
+
if (hy >= hb.y + hb.h)
|
|
157
|
+
break;
|
|
158
|
+
const color = tr.side === 'buy' ? t.up : t.down;
|
|
159
|
+
let cx = c.text(hb.x + 1, hy, fmtAgeSec(tr.ago).padStart(4), { fg: t.dim, bg: hb.bg });
|
|
160
|
+
cx = c.text(cx + 1, hy, tr.side === 'buy' ? '▲BUY ' : '▼SELL', { fg: color, bg: hb.bg, b: true });
|
|
161
|
+
cx = c.textR(cx + 1, hy, 8, fmtUsd(tr.usd), { fg: t.text, bg: hb.bg });
|
|
162
|
+
cx = c.text(cx + 2, hy, fmtPrice(tr.price), { fg: t.muted, bg: hb.bg });
|
|
163
|
+
cx = c.text(cx + 2, hy, shortAddr(tr.addr, 3), { fg: t.dim, bg: hb.bg });
|
|
164
|
+
if (tr.tag)
|
|
165
|
+
badge(c, cx + 1, hy, tr.tag, tr.tag === 'smart' ? t.accent : t.warn, { on: hb.bg });
|
|
166
|
+
hy += 1;
|
|
167
|
+
}
|
|
168
|
+
const tapeH = hb.y + hb.h - tapeTop;
|
|
169
|
+
if (rainW >= 6 && tapeH >= 3)
|
|
170
|
+
tapeRain(c, rainX, tapeTop, rainW, tapeH, tk.trades, v.frame, { bg: hb.bg });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (showAi)
|
|
175
|
+
analystPanel(c, mainW, top, aiW, bottom - top, tk, state);
|
|
176
|
+
footer(c, state.tokenSocial
|
|
177
|
+
? [['esc', 'back'], ['s', 'on-chain'], ['f', 'filter'], ['x', state.socialShowSpam ? 'hide spam' : 'show hidden'], ['K', 'KOL lists'], ['a', showAi ? 'hide analyst' : 'analyst'], ['w', 'watch'], ['q', 'quit']]
|
|
178
|
+
: [['esc', 'back'], ['g', 'go to'], ['a', showAi ? 'hide analyst' : 'analyst'], ['h/t/r', 'holders/trades/traders'], ['w', 'watch'], ['b', 'alert'], ['1-6', 'timeframe'], ['q', 'quit']], state.theme.name, isCalm(v));
|
|
179
|
+
}
|
|
180
|
+
// ---------------- AI analyst panel (coral for the verdict and tool-call lines; answers decrypt in) ----------------
|
|
181
|
+
export function analystPanel(c, x, y, w, h, tk, state) {
|
|
182
|
+
if (state.analyst) {
|
|
183
|
+
renderAnalystSession(c, x, y, w, h, state.analyst, {
|
|
184
|
+
frame: state.vfx.frame, code: state.vfx.code, calm: isCalm(state.vfx),
|
|
185
|
+
chips: [`$${tk.symbol}`, 'holders:20', 'trades:1h', 'rugcheck', 'candles'],
|
|
186
|
+
input: state.aiInput ?? '', inputFocused: state.aiFocused === true,
|
|
187
|
+
scroll: state.aiScroll, revealAll: state.aiRevealAll,
|
|
188
|
+
inputHint: `press i to ask about $${tk.symbol}… read-only · never trades`,
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const t = c.theme;
|
|
193
|
+
const bc = mix(t.border, t.ai, 0.55);
|
|
194
|
+
const box = c.box(x, y, w, h, { title: '✦ ANALYST · claude', titleFg: t.ai, caption: 'claude -p · 4 tools · local', captionFg: t.dim, border: bc });
|
|
195
|
+
const bg = box.bg;
|
|
196
|
+
const inner = box.w - 2;
|
|
197
|
+
let cy = box.y;
|
|
198
|
+
// context chips
|
|
199
|
+
let cx = box.x + 1;
|
|
200
|
+
for (const chip of [`$${tk.symbol}`, 'holders:20', 'trades:1h', 'rugcheck', 'screener']) {
|
|
201
|
+
if (cx + chip.length + 3 > box.x + box.w - 1)
|
|
202
|
+
break;
|
|
203
|
+
cx = badge(c, cx, cy, chip, t.ai, { on: bg }) + 1;
|
|
204
|
+
}
|
|
205
|
+
cy += 2;
|
|
206
|
+
const lines = state.transcript || aiTranscript(tk);
|
|
207
|
+
const inputY = box.y + box.h - 2;
|
|
208
|
+
const ai = state.ai ?? { phase: 'done', cut: lines.length, revealed: 0 };
|
|
209
|
+
const frame = state.vfx.frame;
|
|
210
|
+
const code = state.vfx.code;
|
|
211
|
+
const cut = ai.phase === 'done' ? lines.length : ai.cut;
|
|
212
|
+
let revealedLeft = ai.revealed;
|
|
213
|
+
// text writer that is plain for settled lines and decrypting for the streaming line
|
|
214
|
+
const write = (lx, ly, str, st, maxW, streaming) => {
|
|
215
|
+
if (!streaming) {
|
|
216
|
+
c.text(lx, ly, str, st, maxW);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
const n = Math.min(revealedLeft, str.length);
|
|
220
|
+
decryptText(c, lx, ly, str, n, frame, st, maxW, code);
|
|
221
|
+
revealedLeft -= str.length + 1;
|
|
222
|
+
return revealedLeft > -6; // keep drawing the scramble window
|
|
223
|
+
};
|
|
224
|
+
for (let li = 0; li < lines.length; li++) {
|
|
225
|
+
const ln = lines[li];
|
|
226
|
+
if (cy >= inputY - 1)
|
|
227
|
+
break;
|
|
228
|
+
if (li > cut && revealedLeft <= 0)
|
|
229
|
+
break; // streaming budget spent: later messages are not here yet
|
|
230
|
+
const streaming = li >= cut;
|
|
231
|
+
if (streaming && ai.phase === 'thinking') {
|
|
232
|
+
// Claude-Code-style spinner line
|
|
233
|
+
c.text(box.x + 2, cy, spinnerGlyph(frame), { fg: t.ai, bg, b: true });
|
|
234
|
+
const status = ['Thinking', 'Reading holders', 'Checking rugcheck', 'Decrypting'][Math.floor(frame / 24) % 4];
|
|
235
|
+
c.text(box.x + 4, cy, `${status}…`, { fg: t.ai, bg, b: true });
|
|
236
|
+
c.text(box.x + 5 + status.length, cy, `(esc to interrupt · ${(frame / 30).toFixed(1)}s · ↓ ${Math.floor(frame * 11)} tokens)`, { fg: t.dim, bg }, inner - status.length - 6);
|
|
237
|
+
cy += 1;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
if (ln.role === 'user') {
|
|
241
|
+
c.text(box.x + 1, cy, '›', { fg: t.accent, bg, b: true });
|
|
242
|
+
for (const w2 of wrap(ln.text, inner - 3)) {
|
|
243
|
+
if (cy >= inputY - 1)
|
|
244
|
+
break;
|
|
245
|
+
c.text(box.x + 3, cy, w2, { fg: t.text, bg, b: true });
|
|
246
|
+
cy += 1;
|
|
247
|
+
}
|
|
248
|
+
cy += 1;
|
|
249
|
+
}
|
|
250
|
+
else if (ln.role === 'tool') {
|
|
251
|
+
c.text(box.x + 3, cy, '↳ ' + ln.text, { fg: t.ai, bg, i: true }, inner - 3);
|
|
252
|
+
cy += 1;
|
|
253
|
+
}
|
|
254
|
+
else if ('verdict' in ln && ln.verdict) {
|
|
255
|
+
const vc = ln.verdict === 'watch' ? t.up : ln.verdict === 'avoid' ? t.down : t.warn;
|
|
256
|
+
c.fill(box.x + 1, cy, inner, 1, ' ', { bg: mix(bg, vc, 0.18) });
|
|
257
|
+
c.text(box.x + 2, cy, `● ${ln.verdict.toUpperCase()}`, { fg: vc, bg: mix(bg, vc, 0.18), b: true });
|
|
258
|
+
c.textR(box.x + 1, cy, inner - 1, `conf ${ln.verdict === 'watch' ? '0.62' : ln.verdict === 'avoid' ? '0.81' : '0.44'} `, { fg: t.muted, bg: mix(bg, vc, 0.18) });
|
|
259
|
+
cy += 1;
|
|
260
|
+
for (const w2 of wrap(ln.text, inner - 2)) {
|
|
261
|
+
if (cy >= inputY - 1)
|
|
262
|
+
break;
|
|
263
|
+
if (!write(box.x + 2, cy, w2, { fg: t.ai, bg }, inner - 2, streaming))
|
|
264
|
+
break;
|
|
265
|
+
cy += 1;
|
|
266
|
+
}
|
|
267
|
+
cy += 1;
|
|
268
|
+
}
|
|
269
|
+
else if ('bullets' in ln) {
|
|
270
|
+
let stop = false;
|
|
271
|
+
for (const b of ln.bullets) {
|
|
272
|
+
if (stop)
|
|
273
|
+
break;
|
|
274
|
+
const ws = wrap(b, inner - 4);
|
|
275
|
+
for (let i = 0; i < ws.length; i++) {
|
|
276
|
+
if (cy >= inputY - 1) {
|
|
277
|
+
stop = true;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
c.text(box.x + 2, cy, i === 0 ? '•' : ' ', { fg: t.ai, bg });
|
|
281
|
+
if (!write(box.x + 4, cy, ws[i], { fg: t.text, bg }, inner - 4, streaming)) {
|
|
282
|
+
stop = true;
|
|
283
|
+
cy += 1;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
cy += 1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
cy += 1;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
for (const w2 of wrap(ln.text, inner - 2)) {
|
|
293
|
+
if (cy >= inputY - 1)
|
|
294
|
+
break;
|
|
295
|
+
if (!write(box.x + 2, cy, w2, { fg: t.text, bg }, inner - 2, streaming)) {
|
|
296
|
+
cy += 1;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
cy += 1;
|
|
300
|
+
}
|
|
301
|
+
cy += 1;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (inputY - 2 > cy)
|
|
305
|
+
c.rainZone(box.x + 1, cy, inner, inputY - 2 - cy, 2);
|
|
306
|
+
// guard rail line
|
|
307
|
+
c.hline(box.x, inputY - 1, box.w, { fg: bc, bg, soft: true });
|
|
308
|
+
c.put(box.x, inputY - 1, '├', { fg: bc, bg, soft: true });
|
|
309
|
+
c.put(box.x + box.w - 1, inputY - 1, '┤', { fg: bc, bg, soft: true });
|
|
310
|
+
c.text(box.x + 1, inputY, '▌', { fg: t.ai, bg, b: true, soft: true });
|
|
311
|
+
c.text(box.x + 2, inputY, state.aiInput || `ask about $${tk.symbol}… read-only · never trades`, { fg: state.aiInput ? t.text : t.dim, bg, i: !state.aiInput }, inner - 2);
|
|
312
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// dexterm — TRENDING IN THE SPACE (screen T, task-1330; INTEGRATIONS-DESIGN §5.3, mockups trending-feed-*.txt).
|
|
2
|
+
// Rank = velocity × credibility, computed locally from the keyless venues + whatever posts the feed holds.
|
|
3
|
+
// Sub-panels: NARRATIVES (clusters by shared KOL threads + co-mention; label from the analyst/user, never a
|
|
4
|
+
// single post) and NEWS (official RSS, "context, not signal"). `s` swaps NARRATIVES for the SOURCES panel
|
|
5
|
+
// (health + ages + errors). Without X the header band says `x-stream ○ logged out` and the board still works.
|
|
6
|
+
import { Canvas, table, badge, header, footer, breakpoint, mix, fmtUsd, fmtAgeSec } from '../../../tui/src/index.js';
|
|
7
|
+
import { headerModel } from "../state.js";
|
|
8
|
+
import { socialOf, kolOf, trendingRows, fmtVelocity, sourcesLine, newsTagLabel } from "../social/view.js";
|
|
9
|
+
const dash = '—';
|
|
10
|
+
export function renderTrending(c, state) {
|
|
11
|
+
const t = c.theme;
|
|
12
|
+
const bp = breakpoint(c.w);
|
|
13
|
+
header(c, headerModel(state));
|
|
14
|
+
const now = state.now.getTime();
|
|
15
|
+
const feed = socialOf(state);
|
|
16
|
+
const top = 2, bottom = c.h - 2; // last row above the footer carries the model-output line
|
|
17
|
+
// row 1 band: the ranking rule + source ages
|
|
18
|
+
c.fill(0, 1, c.w, 1, ' ', { bg: t.bg });
|
|
19
|
+
c.noRain(0, 1, c.w, 1);
|
|
20
|
+
let bx = c.text(1, 1, 'TRENDING IN THE SPACE', { fg: t.borderFocus, bg: t.bg, b: true });
|
|
21
|
+
bx = c.text(bx, 1, ' · rank = velocity × credibility · window 1h · ', { fg: t.dim, bg: t.bg }, Math.max(0, c.w - bx - 1));
|
|
22
|
+
if (feed)
|
|
23
|
+
c.text(bx, 1, sourcesLine(feed), { fg: t.muted, bg: t.bg }, Math.max(0, c.w - bx - 1));
|
|
24
|
+
if (!feed) {
|
|
25
|
+
const box = c.box(0, top, c.w, bottom - top, { title: 'TRENDING', caption: 'no social feed in this mode' });
|
|
26
|
+
c.textC(box.x, box.y + 1, box.w, 'the trending board needs live, replay or fixture mode', { fg: t.muted, bg: box.bg, i: true });
|
|
27
|
+
footer(c, [['esc', 'back'], ['q', 'quit']], t.name);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const all = feed.trendRows();
|
|
31
|
+
const rows = trendingRows(state);
|
|
32
|
+
const st = feed.status();
|
|
33
|
+
const nar = state.socialNarrative ?? 0;
|
|
34
|
+
const narratives = feed.narratives();
|
|
35
|
+
const narName = nar > 0 ? (narratives[nar - 1]?.label ?? narratives[nar - 1]?.symbols.slice(0, 3).join(' ') ?? '') : '';
|
|
36
|
+
const filter = state.socialFilter ?? 'all';
|
|
37
|
+
const hiddenCap = `hidden: ${st.hiddenSpam} spam cluster post${st.hiddenSpam === 1 ? '' : 's'}, ${st.hiddenSquats} unverified cashtag squat${st.hiddenSquats === 1 ? '' : 's'}${st.hiddenInjection ? `, ${st.hiddenInjection} withheld (firewall)` : ''}`;
|
|
38
|
+
const title = `TRENDING · ${rows.length} of ${all.length}${filter !== 'all' ? ` · filter ${filter}` : ''}${state.socialAuthor ? ` · @${state.socialAuthor} only` : ''}${narName ? ` · narrative ${narName}` : ''} · ${hiddenCap}`;
|
|
39
|
+
const subH = bp === 'compact' ? Math.min(12, Math.max(6, Math.floor((bottom - top) * 0.4))) : Math.max(6, Math.floor((bottom - top) * 0.36));
|
|
40
|
+
const boardH = bottom - top - subH;
|
|
41
|
+
const box = c.box(0, top, c.w, boardH, { title, caption: bp === 'compact' ? undefined : `${state.socialShowSpam ? 'x=hide' : 'x=show hidden'} · f=filter · n=narrative · s=sources · a=summary`, focus: true });
|
|
42
|
+
const bg = box.bg;
|
|
43
|
+
const sel = Math.max(0, Math.min(rows.length - 1, state.selected || 0));
|
|
44
|
+
const scroll = Math.max(0, sel - (box.h - 3));
|
|
45
|
+
const credStyle = (n) => ({ fg: n >= 70 ? t.up : n >= 40 ? t.text : n >= 20 ? t.muted : t.down, bg });
|
|
46
|
+
const cols = [
|
|
47
|
+
{ key: 'rank', label: '#', w: 2, align: 'right', prio: 0, render: (cv, x, y, w, _r, rb, m) => { cv.textR(x, y, w, String(m.index + 1), { fg: m.selected ? t.borderFocus : t.dim, bg: rb }); } },
|
|
48
|
+
{ key: 'token', label: 'token', w: 8, prio: 0, render: (cv, x, y, w, r, rb, m) => { cv.text(x, y, r.symbol, { fg: m.selected ? t.borderFocus : t.text, bg: rb, b: true }, w); } },
|
|
49
|
+
{ key: 'mint', label: 'mint', w: 11, prio: 3, render: (cv, x, y, w, r, rb) => { cv.text(x, y, `${r.mint.slice(0, 4)}…${r.mint.slice(-4)}`, { fg: t.dim, bg: rb }, w); } },
|
|
50
|
+
{ key: 'narrative', label: bp === 'compact' ? 'narr' : 'narrative', w: bp === 'compact' ? 9 : 12, prio: 2, render: (cv, x, y, w, r, rb) => { cv.text(x, y, r.narrative ?? '', { fg: t.muted, bg: rb, i: true }, w); } },
|
|
51
|
+
{ key: 'vel', label: 'vel', w: 5, align: 'right', prio: 0, render: (cv, x, y, w, r, rb) => { cv.textR(x, y, w, fmtVelocity(r.velocity, r.velocityBasis), { fg: r.velocity === null ? t.dim : r.velocity >= 5 ? t.up : r.velocity >= 2 ? t.text : t.muted, bg: rb, b: (r.velocity ?? 0) >= 5 }); } },
|
|
52
|
+
{ key: 'pph', label: bp === 'compact' ? 'p/h' : 'posts/h', w: bp === 'compact' ? 5 : 7, align: 'right', prio: 1, render: (cv, x, y, w, r, rb) => { cv.textR(x, y, w, r.postsPerHour ? String(r.postsPerHour) : dash, { fg: t.text, bg: rb }); } },
|
|
53
|
+
{ key: 'kol', label: 'kol', w: 3, align: 'right', prio: 1, render: (cv, x, y, w, r, rb) => { cv.textR(x, y, w, String(r.kolCount), { fg: r.kolCount ? t.accent : t.dim, bg: rb }); } },
|
|
54
|
+
{ key: 'cred', label: bp === 'compact' ? 'cr' : 'cred', w: bp === 'compact' ? 3 : 4, align: 'right', prio: 0, render: (cv, x, y, w, r, rb) => { cv.textR(x, y, w, String(r.credibility), { ...credStyle(r.credibility), bg: rb }); } },
|
|
55
|
+
{ key: 'px1h', label: 'px 1h', w: 6, align: 'right', prio: 1, render: (cv, x, y, w, r, rb) => { const v = r.priceChange1h; cv.textR(x, y, w, v === null ? dash : `${v >= 0 ? '+' : ''}${Math.abs(v) >= 100 ? Math.round(v) : v.toFixed(0)}%`, { fg: v === null ? t.dim : v >= 0 ? t.up : t.down, bg: rb }); } },
|
|
56
|
+
{ key: 'liq', label: 'liq', w: 6, align: 'right', prio: 2, render: (cv, x, y, w, r, rb) => { cv.textR(x, y, w, r.liq === null ? dash : fmtUsd(r.liq, { digits: 1 }), { fg: t.muted, bg: rb }); } },
|
|
57
|
+
{ key: 'sources', label: bp === 'compact' ? 'src' : 'sources', w: bp === 'compact' ? 12 : 15, prio: 1, render: (cv, x, y, w, r, rb) => { const s = r.sources.join('·'); cv.text(x, y, s || dash, { fg: r.venue?.boosted ? t.warn : t.muted, bg: rb }, w); } },
|
|
58
|
+
{ key: 'age', label: 'age', w: 4, align: 'right', prio: 2, render: (cv, x, y, w, r, rb) => { cv.textR(x, y, w, fmtAgeSec(Math.max(0, Math.round((now - r.updatedAt) / 1000))), { fg: t.dim, bg: rb }); } },
|
|
59
|
+
{ key: 'latest', label: 'latest credible post (firewalled)', w: 22, prio: 4, flex: true, render: (cv, x, y, w, r, rb) => {
|
|
60
|
+
const p = r.latestCrediblePost;
|
|
61
|
+
if (!p) {
|
|
62
|
+
cv.text(x, y, r.venue ? `— (no credible post; ${r.venue.sources.join('+')} only)` : dash, { fg: t.dim, bg: rb, i: true }, w);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const who = p.platform === 'news' ? p.author.handle : p.author.handle;
|
|
66
|
+
const cx = cv.text(x, y, `${who}: `, { fg: p.author.tier.startsWith('kol') ? t.accent : t.muted, bg: rb }, w);
|
|
67
|
+
cv.text(cx, y, p.text, { fg: p.platform === 'chain' ? t.up : t.text, bg: rb }, Math.max(0, w - (cx - x)));
|
|
68
|
+
} },
|
|
69
|
+
];
|
|
70
|
+
if (rows.length === 0)
|
|
71
|
+
c.textC(box.x, box.y + 2, box.w, all.length ? 'no rows match the filter — f cycles filters, n clears the narrative' : 'nothing trending yet — the keyless venues refresh every 60 s', { fg: t.muted, bg, i: true });
|
|
72
|
+
else
|
|
73
|
+
table(c, box.x + 1, box.y, box.w - 2, box.h, { columns: cols, rows, selected: sel, scroll, bg });
|
|
74
|
+
// sub-panels
|
|
75
|
+
const sy = top + boardH;
|
|
76
|
+
const news = feed.newsItems(12);
|
|
77
|
+
if (bp === 'compact') {
|
|
78
|
+
const half = Math.floor(subH / 2);
|
|
79
|
+
if (state.socialSources)
|
|
80
|
+
renderSources(c, 0, sy, c.w, subH, state, feed);
|
|
81
|
+
else {
|
|
82
|
+
renderNarratives(c, 0, sy, c.w, half, narratives, nar, bg);
|
|
83
|
+
renderNews(c, 0, sy + half, c.w, subH - half, news, now);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const leftW = Math.floor(c.w * 0.5);
|
|
88
|
+
if (state.socialSources)
|
|
89
|
+
renderSources(c, 0, sy, leftW, subH, state, feed);
|
|
90
|
+
else
|
|
91
|
+
renderNarratives(c, 0, sy, leftW, subH, narratives, nar, bg);
|
|
92
|
+
renderNews(c, leftW, sy, c.w - leftW, subH, news, now);
|
|
93
|
+
}
|
|
94
|
+
// the model-output line above the footer
|
|
95
|
+
const kol = kolOf(state);
|
|
96
|
+
c.fill(0, bottom, c.w, 1, ' ', { bg: t.bg });
|
|
97
|
+
c.noRain(0, bottom, c.w, 1);
|
|
98
|
+
c.text(1, bottom, `✦ model output · KOL lists: ${kol?.summary() ?? 'none'} · credibility = early-entry score × follower quality × on-chain confirmation; never from follower counts alone`, { fg: t.dim, bg: t.bg, i: true }, c.w - 2);
|
|
99
|
+
footer(c, [['⏎', 'open token'], ['n', 'narrative'], ['K', 'KOL lists'], ['f', 'filter'], ['w', 'watch'], ['s', state.socialSources ? 'narratives' : 'sources'], ['x', state.socialShowSpam ? 'hide spam' : 'show hidden'], ['a', 'summary'], ['r', 'refresh'], ['esc', 'back'], ['q', 'quit']], t.name);
|
|
100
|
+
}
|
|
101
|
+
function renderNarratives(c, x, y, w, h, narratives, active, _bg) {
|
|
102
|
+
const t = c.theme;
|
|
103
|
+
const box = c.box(x, y, w, h, { title: 'NARRATIVES · 1h', caption: active ? 'n=next · filtering the board' : 'clusters by shared KOL threads + co-mention · label from the analyst, never one post' });
|
|
104
|
+
let cy = box.y;
|
|
105
|
+
if (narratives.length === 0) {
|
|
106
|
+
c.textC(box.x, cy, box.w, 'no cross-token clusters yet (needs ≥ 2 tokens sharing a KOL thread or a post)', { fg: t.dim, bg: box.bg, i: true });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const maxVel = Math.max(1, ...narratives.map((n) => n.velocity ?? 0));
|
|
110
|
+
narratives.forEach((n, i) => {
|
|
111
|
+
if (cy >= box.y + box.h)
|
|
112
|
+
return;
|
|
113
|
+
const sel = active === i + 1;
|
|
114
|
+
const label = (n.label ?? 'unlabelled').padEnd(12).slice(0, 12);
|
|
115
|
+
let cx = c.text(box.x + 1, cy, label, { fg: sel ? t.borderFocus : n.label ? t.text : t.dim, bg: box.bg, b: sel, i: !n.label });
|
|
116
|
+
const barW = Math.max(2, Math.min(10, Math.round(((n.velocity ?? 0) / maxVel) * 10)));
|
|
117
|
+
cx = c.text(cx + 1, cy, '▇'.repeat(barW).padEnd(10), { fg: mix(box.bg, t.accent, 0.8), bg: box.bg, soft: true });
|
|
118
|
+
const d = n.delta24h;
|
|
119
|
+
cx = c.textR(cx + 1, cy, 6, d === null ? dash : `${d >= 0 ? '+' : ''}${d.toFixed(0)}%`, { fg: d === null ? t.dim : d >= 0 ? t.up : t.down, bg: box.bg });
|
|
120
|
+
const tail = `${n.symbols.slice(0, 4).join(' ')} · ${n.kolThreads} KOL thread${n.kolThreads === 1 ? '' : 's'}${n.newsCount ? ` · news: ${n.newsCount}` : ''}${n.velocity !== null ? ` · ${fmtVelocity(n.velocity)}` : ''}`;
|
|
121
|
+
c.text(cx + 1, cy, tail, { fg: t.muted, bg: box.bg }, Math.max(0, box.x + box.w - cx - 2));
|
|
122
|
+
cy += 1;
|
|
123
|
+
});
|
|
124
|
+
if (cy + 1 < box.y + box.h)
|
|
125
|
+
c.rainZone(box.x + 1, cy + 1, box.w - 2, box.y + box.h - cy - 1, 2);
|
|
126
|
+
}
|
|
127
|
+
function renderNews(c, x, y, w, h, news, now) {
|
|
128
|
+
const t = c.theme;
|
|
129
|
+
const box = c.box(x, y, w, h, { title: 'NEWS · official RSS', caption: 'context, not signal' });
|
|
130
|
+
let cy = box.y;
|
|
131
|
+
if (news.length === 0) {
|
|
132
|
+
c.textC(box.x, cy, box.w, 'no headlines yet (RSS polls every 10 min)', { fg: t.dim, bg: box.bg, i: true });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const n of news) {
|
|
136
|
+
if (cy >= box.y + box.h)
|
|
137
|
+
break;
|
|
138
|
+
const tag = newsTagLabel(n);
|
|
139
|
+
let cx = c.text(box.x + 1, cy, fmtAgeSec(Math.max(0, Math.round((now - n.post.t) / 1000))).padStart(3), { fg: t.dim, bg: box.bg });
|
|
140
|
+
cx = c.text(cx + 1, cy, n.domain.replace(/\..*$/, '').slice(0, 10).padEnd(10), { fg: t.muted, bg: box.bg });
|
|
141
|
+
const room = box.x + box.w - 1 - cx - 1 - tag.length - 1;
|
|
142
|
+
const headline = n.post.text.length > room ? n.post.text.slice(0, Math.max(1, room - 2)) + ' …' : n.post.text;
|
|
143
|
+
cx = c.text(cx + 1, cy, headline, { fg: n.tag === 'cross-ref' ? t.text : t.muted, bg: box.bg }, Math.max(0, room));
|
|
144
|
+
c.textR(box.x + 1, cy, box.w - 2, tag, { fg: n.tag === 'security' ? t.down : n.tag === 'cross-ref' ? t.accent : t.dim, bg: box.bg });
|
|
145
|
+
cy += 1;
|
|
146
|
+
}
|
|
147
|
+
if (cy + 1 < box.y + box.h)
|
|
148
|
+
c.rainZone(box.x + 1, cy + 1, box.w - 2, box.y + box.h - cy - 1, 2);
|
|
149
|
+
}
|
|
150
|
+
function renderSources(c, x, y, w, h, state, feed) {
|
|
151
|
+
const t = c.theme;
|
|
152
|
+
const st = feed.status();
|
|
153
|
+
const box = c.box(x, y, w, h, { title: 'SOURCES · keyless', caption: 'health · age · limiter' });
|
|
154
|
+
let cy = box.y;
|
|
155
|
+
const fmt = (ms) => (ms === null ? dash : ms < 60_000 ? `${Math.round(ms / 1000)}s` : ms < 3_600_000 ? `${Math.round(ms / 60_000)}m` : `${Math.round(ms / 3_600_000)}h`);
|
|
156
|
+
const buckets = state.live ? state.live.dl.limiter.stats() : [];
|
|
157
|
+
const bucketOf = { jup: 'jupiter', gecko: 'geckoterminal', boost: 'dexscreener-slow', cg: 'coingecko', news: 'news-rss' };
|
|
158
|
+
for (const s of st.sources) {
|
|
159
|
+
if (cy >= box.y + box.h)
|
|
160
|
+
break;
|
|
161
|
+
const glyph = s.state === 'ok' ? '●' : s.state === 'error' ? '◐' : '○';
|
|
162
|
+
const fg = s.state === 'ok' ? t.up : s.state === 'error' ? t.warn : t.dim;
|
|
163
|
+
let cx = c.text(box.x + 1, cy, glyph, { fg, bg: box.bg });
|
|
164
|
+
cx = c.text(cx + 1, cy, s.label.padEnd(10), { fg: t.text, bg: box.bg });
|
|
165
|
+
const xs = s.id === 'x' ? feed.xStatus() : null;
|
|
166
|
+
const detail = xs ? (xs.state === 'ok' ? `${xs.detail}${s.ageMs !== null ? ` · age ${fmt(s.ageMs)}` : ' · nothing read yet'} · ${xs.requests} req this run` : xs.detail)
|
|
167
|
+
: s.state === 'off' ? 'off in this mode' : s.state === 'error' ? `error: ${s.error ?? '?'} · last ok ${fmt(s.ageMs)}` : s.state === 'never' ? 'not fetched yet' : `age ${fmt(s.ageMs)}`;
|
|
168
|
+
cx = c.text(cx + 1, cy, detail, { fg: s.state === 'error' ? t.warn : t.muted, bg: box.bg }, Math.max(0, box.x + box.w - cx - 2));
|
|
169
|
+
const b = buckets.find((k) => k.bucket === (s.id === 'x' ? 'x-api' : bucketOf[s.id]));
|
|
170
|
+
if (b)
|
|
171
|
+
c.textR(box.x + 1, cy, box.w - 2, `${b.rpmUsed}/${b.budget} rpm${b.pausedUntil ? ' · paused' : ''}`, { fg: t.dim, bg: box.bg });
|
|
172
|
+
cy += 1;
|
|
173
|
+
}
|
|
174
|
+
if (cy < box.y + box.h) {
|
|
175
|
+
c.text(box.x + 1, cy, `posts stored ${st.posts} (24 h) · hidden spam ${st.hiddenSpam} · squats ${st.hiddenSquats} · withheld ${st.hiddenInjection}`, { fg: t.dim, bg: box.bg, i: true }, box.w - 2);
|
|
176
|
+
cy += 1;
|
|
177
|
+
}
|
|
178
|
+
if (cy + 1 < box.y + box.h)
|
|
179
|
+
c.rainZone(box.x + 1, cy + 1, box.w - 2, box.y + box.h - cy - 1, 2);
|
|
180
|
+
}
|
|
181
|
+
export { badge };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// dexterm — Watchlist + alerts log + analyst digest.
|
|
2
|
+
// Cyberspace styling (task-1297): alert glyphs ▲/▼/✦/◆ beside the badges, claude notes and the digest
|
|
3
|
+
// headline in coral, rain zones under the alerts and the digest, ▲ on holder deltas.
|
|
4
|
+
import { Canvas, table, sparkline, heatPct, launchpadBadge, badge, fmtUsd, fmtPrice, fmtAge, wrap, header, footer, breakpoint, mix, isCalm, } from '../../../tui/src/index.js';
|
|
5
|
+
import { priceModeOf, promoteMcap, supplyRatio, mcapAt, priceCell, fallbackCount, fallbackCaption, FALLBACK_MARK } from "../display.js";
|
|
6
|
+
import { headerModel } from "../state.js";
|
|
7
|
+
import { renderWatchlistLive } from "../live/screens/watchlist-live.js";
|
|
8
|
+
export function renderWatchlist(c, state) {
|
|
9
|
+
if (state.live && state.screener.source !== 'fixtures')
|
|
10
|
+
return renderWatchlistLive(c, state, state.live.watch); // task-1298: live data path
|
|
11
|
+
const t = c.theme;
|
|
12
|
+
const bp = breakpoint(c.w);
|
|
13
|
+
header(c, headerModel(state));
|
|
14
|
+
const top = 2, bottom = c.h - 1;
|
|
15
|
+
const sel = state.selected || 0;
|
|
16
|
+
const rows = state.fx.watch;
|
|
17
|
+
const listH = Math.max(8, Math.min(rows.length + 4, Math.floor((bottom - top) * 0.5)));
|
|
18
|
+
const mode = priceModeOf(state); // task-1366
|
|
19
|
+
const fb = mode === 'mcap' ? fallbackCaption(fallbackCount(rows, (r) => ({ price: r.token.price, mcap: r.token.mcap }))) : null;
|
|
20
|
+
const box = c.box(0, top, c.w, listH, { title: 'WATCHLIST', caption: `${rows.length} tokens · PnL vs entry${fb ? ` · ${fb}` : ''}` });
|
|
21
|
+
const cols = [
|
|
22
|
+
{ key: 'token', label: 'TOKEN', w: 22, prio: 0, flex: true, render: (cv, x, y, w, r, bg, m) => {
|
|
23
|
+
const cx = cv.text(x, y, r.token.symbol, { fg: m.selected ? t.borderFocus : t.text, bg, b: true }, 10);
|
|
24
|
+
cv.text(cx + 1, y, r.token.name, { fg: t.muted, bg }, w - (cx - x) - 1);
|
|
25
|
+
} },
|
|
26
|
+
{ key: 'lp', label: 'LP', w: 6, prio: 3, render: (cv, x, y, _w, r, bg) => { launchpadBadge(cv, x, y, r.token.launchpad, { on: bg }); } },
|
|
27
|
+
{ key: 'price', label: 'PRICE', w: 11, align: 'right', prio: 0, render: (cv, x, y, w, r, bg) => { cv.textR(x, y, w, fmtPrice(r.token.price), { fg: t.text, bg }); } },
|
|
28
|
+
// task-1366: in mcap mode the entry reads as the market cap at entry (entry price × the CURRENT supply ratio)
|
|
29
|
+
{ key: 'entry', label: mode === 'mcap' ? 'ENTRY MC' : 'ENTRY', w: 11, align: 'right', prio: 2, render: (cv, x, y, w, r, bg) => {
|
|
30
|
+
const ratio = mode === 'mcap' ? supplyRatio(r.token.price, r.token.mcap) : null;
|
|
31
|
+
cv.textR(x, y, w, ratio !== null ? fmtUsd(mcapAt(r.entry, ratio), { digits: 1 }) : fmtPrice(r.entry) + (mode === 'mcap' ? FALLBACK_MARK : ''), { fg: t.muted, bg });
|
|
32
|
+
} },
|
|
33
|
+
{ key: 'pnl', label: 'VS ENTRY', w: 9, align: 'right', prio: 0, render: (cv, x, y, w, r, bg) => heatPct(cv, x, y, w, (r.token.price / r.entry - 1) * 100, bg) },
|
|
34
|
+
{ key: 'h1', label: '1H', w: 7, align: 'right', prio: 1, render: (cv, x, y, w, r, bg) => heatPct(cv, x, y, w, r.token.chg.h1, bg) },
|
|
35
|
+
{ key: 'h24', label: '24H', w: 8, align: 'right', prio: 1, render: (cv, x, y, w, r, bg) => heatPct(cv, x, y, w, r.token.chg.h24, bg) },
|
|
36
|
+
{ key: 'mcap', label: 'MCAP', w: mode === 'mcap' ? 9 : 8, align: 'right', prio: 1, render: (cv, x, y, w, r, bg) => {
|
|
37
|
+
const pc = priceCell(mode, r.token.price, r.token.mcap);
|
|
38
|
+
if (pc.fallback)
|
|
39
|
+
cv.textR(x, y, w, pc.text + FALLBACK_MARK, { fg: t.warn, bg });
|
|
40
|
+
else
|
|
41
|
+
cv.textR(x, y, w, fmtUsd(r.token.mcap, { digits: 1 }), { fg: t.text, bg });
|
|
42
|
+
} },
|
|
43
|
+
{ key: 'liq', label: 'LIQ', w: 8, align: 'right', prio: 2, render: (cv, x, y, w, r, bg) => { cv.textR(x, y, w, fmtUsd(r.token.liq, { digits: 1 }), { fg: t.text, bg }); } },
|
|
44
|
+
{ key: 'spark', label: '24H TREND', w: 16, prio: 1, render: (cv, x, y, w, r, bg) => sparkline(cv, x, y, w, r.token.spark, { bg }) },
|
|
45
|
+
{ key: 'note', label: 'NOTE', w: 30, prio: 2, render: (cv, x, y, w, r, bg) => { cv.text(x, y, r.note, { fg: r.note.startsWith('claude') ? t.ai : t.muted, bg, i: true }, w); } },
|
|
46
|
+
{ key: 'added', label: 'ADDED', w: 6, align: 'right', prio: 4, render: (cv, x, y, w, r, bg) => { cv.textR(x, y, w, fmtAge(r.added), { fg: t.dim, bg }); } },
|
|
47
|
+
];
|
|
48
|
+
table(c, box.x + 1, box.y, box.w - 2, box.h, { columns: promoteMcap(cols, mode), rows, selected: sel, bg: box.bg });
|
|
49
|
+
const y2 = top + listH;
|
|
50
|
+
const h2 = bottom - y2;
|
|
51
|
+
const alertsW = bp === 'compact' ? c.w : Math.floor(c.w * 0.58);
|
|
52
|
+
const ab = c.box(0, y2, alertsW, h2, { title: 'ALERTS', caption: 'price · holders · risk · smart money · claude' });
|
|
53
|
+
let ay = ab.y;
|
|
54
|
+
for (const a of state.fx.alerts) {
|
|
55
|
+
if (ay >= ab.y + ab.h)
|
|
56
|
+
break;
|
|
57
|
+
const color = a.level === 'up' ? t.up : a.level === 'down' ? t.down : a.level === 'ai' ? t.ai : t.accent;
|
|
58
|
+
const glyph = a.level === 'up' ? '▲' : a.level === 'down' ? '▼' : a.level === 'ai' ? '✦' : '◆';
|
|
59
|
+
let cx = c.text(ab.x + 1, ay, fmtAge(a.ago).padStart(4), { fg: t.dim, bg: ab.bg });
|
|
60
|
+
cx = c.text(cx + 1, ay, glyph, { fg: color, bg: ab.bg, b: true });
|
|
61
|
+
cx = badge(c, cx + 1, ay, a.kind.toUpperCase().padEnd(9), color, { on: ab.bg }) + 1;
|
|
62
|
+
c.text(cx, ay, a.text, { fg: a.level === 'ai' ? t.ai : t.text, bg: ab.bg }, ab.w - (cx - ab.x) - 1);
|
|
63
|
+
ay += 1;
|
|
64
|
+
}
|
|
65
|
+
if (ay + 1 < ab.y + ab.h)
|
|
66
|
+
c.rainZone(ab.x + 1, ay + 1, ab.w - 2, ab.y + ab.h - ay - 1, 2);
|
|
67
|
+
if (bp !== 'compact') {
|
|
68
|
+
const rb = c.box(alertsW, y2, c.w - alertsW, h2, { title: '✦ ANALYST DIGEST', titleFg: t.ai, caption: 'every 15m · claude -p', border: mix(t.border, t.ai, 0.55) });
|
|
69
|
+
const lines = [
|
|
70
|
+
['head', 'Watchlist health: 4 of 6 intact'],
|
|
71
|
+
['', ''],
|
|
72
|
+
['ok', `${rows[0].token.symbol}: momentum intact, holders ▲${Math.abs(rows[0].token.holders24)} 24h`],
|
|
73
|
+
['ok', `${rows[1].token.symbol}: consolidating above entry, no dev sells`],
|
|
74
|
+
['warn', `${rows[2].token.symbol}: volume fading, thesis needs a catalyst`],
|
|
75
|
+
['bad', `${rows[4].token.symbol}: wash-trade signature (organic ${rows[4].token.org}), consider dropping`],
|
|
76
|
+
['ok', `${rows[3].token.symbol}: smart wallets accumulating (3 tagged buys)`],
|
|
77
|
+
['', ''],
|
|
78
|
+
['dim', 'Suggested: alert on CATWIF liq < $80K; review SNAIL at 1.2M'],
|
|
79
|
+
['dim', 'I do not trade. Confirm any action in the Trade panel (v2).'],
|
|
80
|
+
];
|
|
81
|
+
let ry = rb.y;
|
|
82
|
+
for (const [kind, text] of lines) {
|
|
83
|
+
if (ry >= rb.y + rb.h)
|
|
84
|
+
break;
|
|
85
|
+
const fg = kind === 'head' ? t.ai : kind === 'ok' ? t.up : kind === 'warn' ? t.warn : kind === 'bad' ? t.down : t.dim;
|
|
86
|
+
const mark = kind === 'ok' ? '●' : kind === 'warn' ? '◐' : kind === 'bad' ? '○' : ' ';
|
|
87
|
+
const indent = kind && kind !== 'head' && kind !== 'dim' ? 3 : 1;
|
|
88
|
+
const parts = text ? wrap(text, rb.w - 1 - indent) : [''];
|
|
89
|
+
parts.forEach((part, i) => {
|
|
90
|
+
if (ry >= rb.y + rb.h)
|
|
91
|
+
return;
|
|
92
|
+
if (indent === 3 && i === 0)
|
|
93
|
+
c.text(rb.x + 1, ry, mark, { fg, bg: rb.bg });
|
|
94
|
+
c.text(rb.x + indent, ry, part, { fg: indent === 3 ? t.text : fg, bg: rb.bg, b: kind === 'head' }, rb.w - 1 - indent);
|
|
95
|
+
ry += 1;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (ry + 1 < rb.y + rb.h)
|
|
99
|
+
c.rainZone(rb.x + 1, ry + 1, rb.w - 2, rb.y + rb.h - ry - 1, 2);
|
|
100
|
+
}
|
|
101
|
+
// task-1388: `g` opens the go-to prompt here too (keymap + the ? overlay list it); the footer is kept
|
|
102
|
+
// byte-identical to prototype-v2 (snapshot parity), so the hint lives in `?` and on the screener footer.
|
|
103
|
+
footer(c, [['j/k', 'move'], ['⏎', 'open'], ['d', 'remove'], ['n', 'note'], ['b', 'add alert'], ['a', 'ask claude'], ['q', 'quit']], state.theme.name, isCalm(state.vfx));
|
|
104
|
+
}
|