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,554 @@
|
|
|
1
|
+
// dexterm popup — CHART v2 (task-1325): TradingView Lightweight Charts™ 5.2.1 (Apache-2.0, vendored and served
|
|
2
|
+
// by our own loopback server — never a CDN) driven ONLY by our data: the ChartVM the terminal pushes over the
|
|
3
|
+
// WS (its cached/derived candles, its trade tape, its position ledger). Inlined after common.js + chart-lib.js
|
|
4
|
+
// under the page's CSP nonce. No innerHTML anywhere in this file: every untrusted string (symbol, name) goes
|
|
5
|
+
// through textContent or the chart's canvas text. The page can only REQUEST things (interval, indicator, style,
|
|
6
|
+
// scale) through allow-listed events; the server validates every payload against closed sets.
|
|
7
|
+
(() => {
|
|
8
|
+
'use strict';
|
|
9
|
+
const DT = window.DT; DT.page = 'chart';
|
|
10
|
+
const C = window.DTChart;
|
|
11
|
+
const L = window.LightweightCharts;
|
|
12
|
+
const $ = (id) => document.getElementById(id);
|
|
13
|
+
const titleEl = $('title'), legendEl = $('legend'), liveEl = $('live'), bannerEl = $('banner'), hintEl = $('hint');
|
|
14
|
+
const posEl = $('pos'), posTitleEl = $('pos-title'), posCapEl = $('pos-cap'), posCellsEl = $('pos-cells');
|
|
15
|
+
const imgSlot = $('coin-img-slot');
|
|
16
|
+
const chartEl = $('chart'), drawCv = $('draw'), dctx = drawCv.getContext('2d');
|
|
17
|
+
const STALE_MS = 20_000;
|
|
18
|
+
const DEFAULT_IND = ['volume', 'tape', 'ema9', 'ema21'];
|
|
19
|
+
/** Exposed for the headless harness (measure/popup-v2-1325): first paint + setData → repaint latency. */
|
|
20
|
+
const perf = window.__dtPerf = { firstPaintMs: null, firstPaintWall: null, dataLatencies: [], intervalLatencies: [], vmCount: 0 };
|
|
21
|
+
|
|
22
|
+
const S = {
|
|
23
|
+
vm: null, bars: [], key: null, interval: null, pendingInterval: null, mint: null,
|
|
24
|
+
style: 'candles', scale: 'auto', ind: new Set(DEFAULT_IND), prefsApplied: false,
|
|
25
|
+
draw: null, tPending: null, hover: null, drawings: [], lastDataAt: 0, firstPainted: false, fitNext: true,
|
|
26
|
+
banners: { fallback: null, data: null },
|
|
27
|
+
display: C.displayOf(null), // task-1366: price | mcap (from the VM's priceMode + supply)
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const banner = () => {
|
|
31
|
+
const lines = [S.banners.fallback, S.banners.data].filter(Boolean);
|
|
32
|
+
bannerEl.hidden = lines.length === 0;
|
|
33
|
+
bannerEl.textContent = lines.join(' · ');
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// The page is useless without the vendored library: say so plainly instead of drawing a fake chart.
|
|
37
|
+
if (!L || !C || typeof L.createChart !== 'function') {
|
|
38
|
+
S.banners.data = 'chart library failed to load (the vendored Lightweight Charts route did not answer)';
|
|
39
|
+
banner(); DT.wireChrome(); DT.connect(); return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const rain = DT.mountRain('rain');
|
|
43
|
+
DT.rainEvery = 2; // rain at half the frame rate behind the chart: atmosphere, not a CPU cost
|
|
44
|
+
let T = DT.theme;
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------- time labels in the viewer's local time zone
|
|
47
|
+
// Bar times are shifted by the page's current UTC offset (chart-lib setTzOffset), so formatting them with the
|
|
48
|
+
// UTC getters prints local wall time and LWC's day/month tick marks fall on LOCAL boundaries.
|
|
49
|
+
C.setTzOffset(-new Date().getTimezoneOffset() * 60);
|
|
50
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
51
|
+
const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
52
|
+
function fmtTime(t) { const d = new Date(t * 1000); return `${pad(d.getUTCDate())} ${MON[d.getUTCMonth()]} '${String(d.getUTCFullYear()).slice(2)} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`; }
|
|
53
|
+
function tickMark(t, type) {
|
|
54
|
+
const d = new Date(t * 1000);
|
|
55
|
+
if (type === 0) return String(d.getUTCFullYear());
|
|
56
|
+
if (type === 1) return MON[d.getUTCMonth()];
|
|
57
|
+
if (type === 2) return String(d.getUTCDate());
|
|
58
|
+
return `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
59
|
+
}
|
|
60
|
+
const volFmt = (v) => C.fmtUsd(v);
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------- chart + series
|
|
63
|
+
const chart = L.createChart(chartEl, {
|
|
64
|
+
autoSize: true,
|
|
65
|
+
layout: {
|
|
66
|
+
background: { type: 'solid', color: 'rgba(0,0,0,0)' }, textColor: T.muted, fontSize: 11,
|
|
67
|
+
fontFamily: 'ui-monospace, "SF Mono", Menlo, monospace',
|
|
68
|
+
attributionLogo: true, // REQUIRED attribution (license): TradingView logo link on the chart
|
|
69
|
+
panes: { separatorColor: T.border, separatorHoverColor: DT.rgba(T.accent, 0.25), enableResize: true },
|
|
70
|
+
},
|
|
71
|
+
grid: { vertLines: { color: DT.rgba(T.border, 0.45) }, horzLines: { color: DT.rgba(T.border, 0.45) } },
|
|
72
|
+
crosshair: {
|
|
73
|
+
mode: L.CrosshairMode.Normal,
|
|
74
|
+
vertLine: { color: DT.rgba(T.accent, 0.45), labelBackgroundColor: T.panel, width: 1, style: L.LineStyle.Dashed },
|
|
75
|
+
horzLine: { color: DT.rgba(T.accent, 0.45), labelBackgroundColor: T.panel, width: 1, style: L.LineStyle.Dashed },
|
|
76
|
+
},
|
|
77
|
+
rightPriceScale: { borderColor: T.border, scaleMargins: { top: 0.1, bottom: 0.08 } },
|
|
78
|
+
timeScale: { borderColor: T.border, timeVisible: true, secondsVisible: false, rightOffset: 6, barSpacing: 7, minBarSpacing: 0.5, tickMarkFormatter: tickMark },
|
|
79
|
+
localization: { timeFormatter: fmtTime, locale: 'en-US' },
|
|
80
|
+
handleScroll: { mouseWheel: true, pressedMouseMove: true, horzTouchDrag: true, vertTouchDrag: false },
|
|
81
|
+
handleScale: { axisPressedMouseMove: { time: true, price: true }, axisDoubleClickReset: true, mouseWheel: true, pinch: true },
|
|
82
|
+
});
|
|
83
|
+
// task-1366: the axis formatter follows the terminal's price mode (mcap = price × the current supply, K/M/B)
|
|
84
|
+
const pricePF = (p) => ({ type: 'custom', formatter: C.axisFormatter(S.display), minMove: C.priceFormatFor(p).minMove });
|
|
85
|
+
// Keep the position levels (entry / stop / target) and the live price in view: autoscale widens to include them
|
|
86
|
+
// when they are within 3× of the last close (a far-away level would squash the candles; its row cell still shows it).
|
|
87
|
+
function levels() {
|
|
88
|
+
const vm = S.vm || {}, pos = vm.position || null, out = [];
|
|
89
|
+
const last = S.bars.length ? S.bars[S.bars.length - 1].close : null;
|
|
90
|
+
for (const v of [pos && pos.avgEntryUsd, pos && pos.stopUsd, pos && pos.targetUsd, vm.feed === 'LIVE' ? vm.livePrice : null]) {
|
|
91
|
+
if (typeof v === 'number' && v > 0 && last && v / last < 3 && last / v < 3) out.push(v);
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
const autoscaleInfoProvider = (original) => {
|
|
96
|
+
const res = original();
|
|
97
|
+
const lv = levels();
|
|
98
|
+
if (!res || !res.priceRange || !lv.length) return res;
|
|
99
|
+
return { ...res, priceRange: { minValue: Math.min(res.priceRange.minValue, ...lv), maxValue: Math.max(res.priceRange.maxValue, ...lv) } };
|
|
100
|
+
};
|
|
101
|
+
const lineOpts = (color, w = 1) => ({ color, lineWidth: w, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: false, priceFormat: pricePF(1) });
|
|
102
|
+
const main = {
|
|
103
|
+
candles: chart.addSeries(L.CandlestickSeries, { upColor: T.up, downColor: T.down, borderUpColor: T.up, borderDownColor: T.down, wickUpColor: T.up, wickDownColor: T.down, priceLineColor: T.accent, priceLineStyle: L.LineStyle.Dashed, priceFormat: pricePF(1), autoscaleInfoProvider }, 0),
|
|
104
|
+
line: chart.addSeries(L.LineSeries, { color: T.accent, lineWidth: 2, visible: false, priceLineColor: T.accent, priceLineStyle: L.LineStyle.Dashed, priceFormat: pricePF(1), autoscaleInfoProvider }, 0),
|
|
105
|
+
area: chart.addSeries(L.AreaSeries, { lineColor: T.accent, topColor: DT.rgba(T.accent, 0.28), bottomColor: DT.rgba(T.accent, 0.0), lineWidth: 2, visible: false, priceLineColor: T.accent, priceLineStyle: L.LineStyle.Dashed, priceFormat: pricePF(1), autoscaleInfoProvider }, 0),
|
|
106
|
+
};
|
|
107
|
+
const markers = { candles: L.createSeriesMarkers(main.candles, []), line: L.createSeriesMarkers(main.line, []), area: L.createSeriesMarkers(main.area, []) };
|
|
108
|
+
const IND_COLORS = () => ({ ema9: T.warn, ema21: T.pump || '#ff2bd6', vwap: T.dbc || '#ff8a3d', volma: DT.rgba(T.accent, 0.8) });
|
|
109
|
+
const ind = {
|
|
110
|
+
ema9: chart.addSeries(L.LineSeries, lineOpts(IND_COLORS().ema9), 0),
|
|
111
|
+
ema21: chart.addSeries(L.LineSeries, lineOpts(IND_COLORS().ema21), 0),
|
|
112
|
+
vwap: chart.addSeries(L.LineSeries, { ...lineOpts(IND_COLORS().vwap, 1), lineStyle: L.LineStyle.Dotted }, 0),
|
|
113
|
+
};
|
|
114
|
+
let vol = null, volMa = null;
|
|
115
|
+
const watermark = L.createTextWatermark(chart.panes()[0], { horzAlign: 'center', vertAlign: 'center', lines: [] });
|
|
116
|
+
const active = () => main[S.style];
|
|
117
|
+
|
|
118
|
+
function ensureVolume(on) {
|
|
119
|
+
if (on && !vol) {
|
|
120
|
+
vol = chart.addSeries(L.HistogramSeries, { priceFormat: { type: 'custom', formatter: volFmt, minMove: 1 }, lastValueVisible: false, priceLineVisible: false }, 1);
|
|
121
|
+
volMa = chart.addSeries(L.LineSeries, { ...lineOpts(IND_COLORS().volma, 1), priceFormat: { type: 'custom', formatter: volFmt, minMove: 1 } }, 1);
|
|
122
|
+
sizePanes();
|
|
123
|
+
} else if (!on && vol) {
|
|
124
|
+
chart.removeSeries(volMa); chart.removeSeries(vol); vol = null; volMa = null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function sizePanes() {
|
|
128
|
+
const panes = chart.panes();
|
|
129
|
+
if (panes.length > 1) { const h = chartEl.clientHeight; panes[1].setHeight(Math.max(48, Math.round(h * 0.2))); }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------- price lines on the active series (entry/stop/target/live/drawn)
|
|
133
|
+
const plines = new Map(); // name → { series, line }
|
|
134
|
+
function setLine(name, opts) {
|
|
135
|
+
const s = active(); const cur = plines.get(name);
|
|
136
|
+
if (!opts) { if (cur) { try { cur.series.removePriceLine(cur.line); } catch { /* gone */ } plines.delete(name); } return; }
|
|
137
|
+
if (cur && cur.series === s) { cur.line.applyOptions(opts); return; }
|
|
138
|
+
if (cur) { try { cur.series.removePriceLine(cur.line); } catch { /* gone */ } }
|
|
139
|
+
plines.set(name, { series: s, line: s.createPriceLine(opts) });
|
|
140
|
+
}
|
|
141
|
+
function rebindLines() { const saved = [...plines.entries()].map(([k, v]) => [k, v.line.options()]); for (const [k] of saved) setLine(k, null); for (const [k, o] of saved) setLine(k, o); }
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------- theme
|
|
144
|
+
function applyChartTheme() {
|
|
145
|
+
T = DT.theme;
|
|
146
|
+
chart.applyOptions({
|
|
147
|
+
layout: { textColor: T.muted, panes: { separatorColor: T.border, separatorHoverColor: DT.rgba(T.accent, 0.25) } },
|
|
148
|
+
grid: { vertLines: { color: DT.rgba(T.border, 0.45) }, horzLines: { color: DT.rgba(T.border, 0.45) } },
|
|
149
|
+
crosshair: { vertLine: { color: DT.rgba(T.accent, 0.45), labelBackgroundColor: T.panel }, horzLine: { color: DT.rgba(T.accent, 0.45), labelBackgroundColor: T.panel } },
|
|
150
|
+
rightPriceScale: { borderColor: T.border }, timeScale: { borderColor: T.border },
|
|
151
|
+
});
|
|
152
|
+
main.candles.applyOptions({ upColor: T.up, downColor: T.down, borderUpColor: T.up, borderDownColor: T.down, wickUpColor: T.up, wickDownColor: T.down, priceLineColor: T.accent });
|
|
153
|
+
main.line.applyOptions({ color: T.accent, priceLineColor: T.accent });
|
|
154
|
+
main.area.applyOptions({ lineColor: T.accent, topColor: DT.rgba(T.accent, 0.28), bottomColor: DT.rgba(T.accent, 0), priceLineColor: T.accent });
|
|
155
|
+
const ic = IND_COLORS(); ind.ema9.applyOptions({ color: ic.ema9 }); ind.ema21.applyOptions({ color: ic.ema21 }); ind.vwap.applyOptions({ color: ic.vwap });
|
|
156
|
+
if (volMa) volMa.applyOptions({ color: ic.volma });
|
|
157
|
+
if (S.bars.length) renderFull();
|
|
158
|
+
}
|
|
159
|
+
DT.onTheme(applyChartTheme);
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------- toolbar (buttons built with textContent only)
|
|
162
|
+
const btns = { tf: new Map(), styles: new Map(), scales: new Map(), inds: new Map(), draws: new Map() };
|
|
163
|
+
function mkBtn(group, id, label, title, onClick) {
|
|
164
|
+
const b = document.createElement('button'); b.type = 'button'; b.className = 'tb'; b.textContent = label; b.title = title;
|
|
165
|
+
b.addEventListener('click', (e) => { e.preventDefault(); onClick(); });
|
|
166
|
+
$(group).appendChild(b); btns[group].set(id, b); return b;
|
|
167
|
+
}
|
|
168
|
+
C.INTERVALS.forEach((iv, i) => mkBtn('tf', iv, iv, `timeframe ${iv} (${i + 1})`, () => requestInterval(iv)));
|
|
169
|
+
mkBtn('styles', 'candles', 'candles', 'candlesticks (c cycles)', () => setStyle('candles', true));
|
|
170
|
+
mkBtn('styles', 'line', 'line', 'line (c cycles)', () => setStyle('line', true));
|
|
171
|
+
mkBtn('styles', 'area', 'area', 'area (c cycles)', () => setStyle('area', true));
|
|
172
|
+
mkBtn('scales', 'auto', 'auto', 'auto-scaled price axis (a)', () => setScale('auto', true));
|
|
173
|
+
mkBtn('scales', 'log', 'log', 'logarithmic price axis (l)', () => setScale('log', true));
|
|
174
|
+
mkBtn('scales', 'percent', '%', 'percent from the first visible bar (p)', () => setScale('percent', true));
|
|
175
|
+
mkBtn('inds', 'ema9', 'EMA9', 'EMA 9 (e toggles 9+21)', () => toggleInd('ema9'));
|
|
176
|
+
mkBtn('inds', 'ema21', 'EMA21', 'EMA 21 (e toggles 9+21)', () => toggleInd('ema21'));
|
|
177
|
+
mkBtn('inds', 'vwap', 'VWAP', 'VWAP, UTC-day anchored (w)', () => toggleInd('vwap'));
|
|
178
|
+
mkBtn('inds', 'volume', 'VOL', 'volume pane (v)', () => toggleInd('volume'));
|
|
179
|
+
mkBtn('inds', 'volma', 'VOL·MA', 'volume 20-bar MA (m)', () => toggleInd('volma'));
|
|
180
|
+
mkBtn('inds', 'tape', 'TAPE', 'buy/sell markers from the trade tape (b)', () => toggleInd('tape'));
|
|
181
|
+
mkBtn('draws', 'h', '─ hline', 'horizontal line: click a price (h)', () => armDraw('h'));
|
|
182
|
+
mkBtn('draws', 't', '╱ trend', 'trend line: click two points (t)', () => armDraw('t'));
|
|
183
|
+
mkBtn('draws', 'x', '✕ clear', 'clear drawings (x; backspace = undo last)', () => clearDrawings());
|
|
184
|
+
function syncToolbar() {
|
|
185
|
+
for (const [iv, b] of btns.tf) { b.classList.toggle('on', iv === S.interval); b.classList.toggle('pending', iv === S.pendingInterval && iv !== S.interval); }
|
|
186
|
+
for (const [k, b] of btns.styles) b.classList.toggle('on', k === S.style);
|
|
187
|
+
for (const [k, b] of btns.scales) b.classList.toggle('on', k === S.scale);
|
|
188
|
+
for (const [k, b] of btns.inds) b.classList.toggle('on', S.ind.has(k));
|
|
189
|
+
for (const [k, b] of btns.draws) b.classList.toggle('on', k === S.draw);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------- requests to the TUI (validated server-side)
|
|
193
|
+
function requestInterval(iv) {
|
|
194
|
+
if (!C.INTERVALS.includes(iv)) return;
|
|
195
|
+
if (iv === S.interval && !S.pendingInterval) return;
|
|
196
|
+
S.pendingInterval = iv; S.reqAt = performance.now(); syncToolbar();
|
|
197
|
+
DT.send({ type: 'event', name: 'chart.interval', payload: { interval: iv } });
|
|
198
|
+
}
|
|
199
|
+
function setStyle(st, user) {
|
|
200
|
+
if (!C.STYLES.includes(st) || st === S.style) return;
|
|
201
|
+
S.style = st;
|
|
202
|
+
for (const k of C.STYLES) main[k].applyOptions({ visible: k === st });
|
|
203
|
+
for (const k of C.STYLES) if (k !== st) { main[k].setData([]); markers[k].setMarkers([]); }
|
|
204
|
+
rebindLines();
|
|
205
|
+
if (S.bars.length) renderFull();
|
|
206
|
+
syncToolbar();
|
|
207
|
+
if (user) DT.send({ type: 'event', name: 'chart.style', payload: { style: st } });
|
|
208
|
+
}
|
|
209
|
+
function setScale(sc, user) {
|
|
210
|
+
if (!C.SCALES.includes(sc)) return;
|
|
211
|
+
S.scale = sc;
|
|
212
|
+
chart.priceScale('right', 0).applyOptions({ mode: sc === 'log' ? L.PriceScaleMode.Logarithmic : sc === 'percent' ? L.PriceScaleMode.Percentage : L.PriceScaleMode.Normal, autoScale: true });
|
|
213
|
+
syncToolbar();
|
|
214
|
+
if (user) DT.send({ type: 'event', name: 'chart.scale', payload: { scale: sc } });
|
|
215
|
+
}
|
|
216
|
+
function toggleInd(id, force) {
|
|
217
|
+
if (!C.INDICATORS.includes(id)) return;
|
|
218
|
+
const on = force === undefined ? !S.ind.has(id) : force;
|
|
219
|
+
if (on) S.ind.add(id); else S.ind.delete(id);
|
|
220
|
+
if (id === 'volume') ensureVolume(on);
|
|
221
|
+
if (S.bars.length) renderFull();
|
|
222
|
+
syncToolbar();
|
|
223
|
+
DT.send({ type: 'event', name: 'chart.indicator', payload: { id, on } });
|
|
224
|
+
}
|
|
225
|
+
function applyPrefs(p) {
|
|
226
|
+
if (!p) return;
|
|
227
|
+
if (Array.isArray(p.indicators)) S.ind = new Set(p.indicators.filter((x) => C.INDICATORS.includes(x)));
|
|
228
|
+
ensureVolume(S.ind.has('volume'));
|
|
229
|
+
if (p.style && p.style !== S.style) setStyle(p.style, false);
|
|
230
|
+
if (p.scale) setScale(p.scale, false);
|
|
231
|
+
S.prefsApplied = true; syncToolbar();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------- data → chart
|
|
235
|
+
const periodMs = () => C.PERIOD_MS[S.interval] || 3_600_000;
|
|
236
|
+
let indCache = { ema9: new Map(), ema21: new Map(), vwap: new Map() };
|
|
237
|
+
const mapOf = (pts) => new Map(pts.map((p) => [p.time, p.value]));
|
|
238
|
+
function seriesData(st) { return st === 'candles' ? S.bars.map((b) => ({ time: b.time, open: b.open, high: b.high, low: b.low, close: b.close })) : S.bars.map((b) => ({ time: b.time, value: b.close })); }
|
|
239
|
+
function safeUpdate(series, point, fallback) { try { series.update(point); } catch { fallback(); } }
|
|
240
|
+
|
|
241
|
+
function renderIndicators(tailOnly) {
|
|
242
|
+
const out = { ema9: S.ind.has('ema9') ? C.ema(S.bars, 9) : [], ema21: S.ind.has('ema21') ? C.ema(S.bars, 21) : [], vwap: S.ind.has('vwap') ? C.vwap(S.bars) : [] };
|
|
243
|
+
for (const k of ['ema9', 'ema21', 'vwap']) {
|
|
244
|
+
indCache[k] = mapOf(out[k]);
|
|
245
|
+
if (tailOnly && out[k].length) { for (const p of out[k].slice(-2)) safeUpdate(ind[k], p, () => ind[k].setData(out[k])); }
|
|
246
|
+
else ind[k].setData(out[k]);
|
|
247
|
+
}
|
|
248
|
+
if (vol) {
|
|
249
|
+
const v = C.volumes(S.bars, DT.rgba(T.up, 0.45), DT.rgba(T.down, 0.45));
|
|
250
|
+
const ma = S.ind.has('volma') ? C.sma(v, 20) : [];
|
|
251
|
+
if (tailOnly && v.length) {
|
|
252
|
+
for (const p of v.slice(-2)) safeUpdate(vol, p, () => vol.setData(v));
|
|
253
|
+
if (ma.length) for (const p of ma.slice(-2)) safeUpdate(volMa, p, () => volMa.setData(ma)); else volMa.setData([]);
|
|
254
|
+
} else { vol.setData(v); volMa.setData(ma); }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function renderMarkers() {
|
|
259
|
+
const vm = S.vm || {};
|
|
260
|
+
const times = new Set(S.bars.map((b) => b.time));
|
|
261
|
+
const list = [];
|
|
262
|
+
if (S.ind.has('tape')) list.push(...C.tapeMarkers(vm.trades, periodMs(), times, { up: DT.rgba(T.up, 0.85), down: DT.rgba(T.down, 0.85) }));
|
|
263
|
+
if (vm.position && vm.position.fills) list.push(...C.fillMarkers(vm.position.fills, periodMs(), times, { buy: T.accent, sell: T.warn }));
|
|
264
|
+
list.sort((a, b) => a.time - b.time);
|
|
265
|
+
markers[S.style].setMarkers(list);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function renderOverlays() {
|
|
269
|
+
const vm = S.vm || {};
|
|
270
|
+
const pos = vm.position || null;
|
|
271
|
+
if (pos && typeof pos.avgEntryUsd === 'number' && pos.avgEntryUsd > 0) {
|
|
272
|
+
const pct = typeof pos.unrealizedPct === 'number' ? ` ${C.fmtPct(pos.unrealizedPct, 1)}` : '';
|
|
273
|
+
setLine('entry', { price: pos.avgEntryUsd, color: T.warn, lineWidth: 1, lineStyle: L.LineStyle.Solid, axisLabelVisible: true, axisLabelColor: T.warn, axisLabelTextColor: T.bg, title: `ENTRY ${pos.sizeLabel}${pct}` });
|
|
274
|
+
} else setLine('entry', null);
|
|
275
|
+
setLine('stop', pos && typeof pos.stopUsd === 'number' && pos.stopUsd > 0 ? { price: pos.stopUsd, color: T.down, lineWidth: 1, lineStyle: L.LineStyle.Dashed, axisLabelVisible: true, title: 'STOP' } : null);
|
|
276
|
+
setLine('target', pos && typeof pos.targetUsd === 'number' && pos.targetUsd > 0 ? { price: pos.targetUsd, color: T.up, lineWidth: 1, lineStyle: L.LineStyle.Dashed, axisLabelVisible: true, title: 'TARGET' } : null);
|
|
277
|
+
const lastClose = S.bars.length ? S.bars[S.bars.length - 1].close : null;
|
|
278
|
+
const lp = vm.feed === 'LIVE' && typeof vm.livePrice === 'number' && vm.livePrice > 0 && vm.livePrice !== lastClose ? vm.livePrice : null;
|
|
279
|
+
setLine('live', lp ? { price: lp, color: T.accent, lineWidth: 1, lineStyle: L.LineStyle.Dotted, axisLabelVisible: true, axisLabelColor: T.accent, axisLabelTextColor: T.bg, title: 'LIVE' } : null);
|
|
280
|
+
for (const d of S.drawings) if (d.kind === 'h') setLine(`hline-${d.id}`, { price: d.price, color: DT.rgba(T.accent, 0.85), lineWidth: 1, lineStyle: L.LineStyle.LargeDashed, axisLabelVisible: true, title: '' });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function applyPriceFormat() {
|
|
284
|
+
const last = S.bars.length ? S.bars[S.bars.length - 1].close : 1;
|
|
285
|
+
const pf = pricePF(last);
|
|
286
|
+
for (const k of C.STYLES) main[k].applyOptions({ priceFormat: pf });
|
|
287
|
+
for (const k of ['ema9', 'ema21', 'vwap']) ind[k].applyOptions({ priceFormat: pf });
|
|
288
|
+
}
|
|
289
|
+
function renderFull() {
|
|
290
|
+
applyPriceFormat();
|
|
291
|
+
active().setData(seriesData(S.style));
|
|
292
|
+
renderIndicators(false);
|
|
293
|
+
renderMarkers();
|
|
294
|
+
renderOverlays();
|
|
295
|
+
if (S.fitNext && S.bars.length) {
|
|
296
|
+
const n = S.bars.length;
|
|
297
|
+
chart.timeScale().setVisibleLogicalRange({ from: Math.max(-2, n - 140), to: n + 5 });
|
|
298
|
+
S.fitNext = false;
|
|
299
|
+
}
|
|
300
|
+
renderLegend(S.hover); // indicator toggles change the legend's second row
|
|
301
|
+
}
|
|
302
|
+
function renderTail(tailBars) {
|
|
303
|
+
const s = active();
|
|
304
|
+
for (const b of tailBars) {
|
|
305
|
+
const pt = S.style === 'candles' ? { time: b.time, open: b.open, high: b.high, low: b.low, close: b.close } : { time: b.time, value: b.close };
|
|
306
|
+
safeUpdate(s, pt, () => s.setData(seriesData(S.style)));
|
|
307
|
+
}
|
|
308
|
+
renderIndicators(true);
|
|
309
|
+
renderMarkers();
|
|
310
|
+
renderOverlays();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ---------------------------------------------------------------- legend (crosshair OHLCV + indicator values)
|
|
314
|
+
function el(tag, cls, text) { const e = document.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; }
|
|
315
|
+
function renderLegend(time) {
|
|
316
|
+
while (legendEl.firstChild) legendEl.removeChild(legendEl.firstChild);
|
|
317
|
+
if (!S.bars.length) return;
|
|
318
|
+
let i = time == null ? S.bars.length - 1 : C.indexOfTime(S.bars, time);
|
|
319
|
+
if (i < 0) i = S.bars.length - 1;
|
|
320
|
+
const b = S.bars[i], lg = C.legendFor(b, i > 0 ? S.bars[i - 1].close : null, S.display);
|
|
321
|
+
const r1 = el('div', 'row1');
|
|
322
|
+
for (const [k, v] of [['O', lg.o], ['H', lg.h], ['L', lg.l], ['C', lg.c]]) { r1.appendChild(el('b', null, k)); r1.appendChild(el('span', lg.up ? 'up' : 'down', ` ${v}`)); }
|
|
323
|
+
r1.appendChild(el('span', lg.up ? 'up' : 'down', ` ${lg.chg}`));
|
|
324
|
+
r1.appendChild(el('b', null, 'V')); r1.appendChild(el('span', null, ` ${lg.v}`));
|
|
325
|
+
legendEl.appendChild(r1);
|
|
326
|
+
const r2 = el('div', 'row2'); const ic = IND_COLORS();
|
|
327
|
+
for (const k of ['ema9', 'ema21', 'vwap']) {
|
|
328
|
+
if (!S.ind.has(k)) continue;
|
|
329
|
+
const v = indCache[k].get(b.time);
|
|
330
|
+
const s = el('span', 'ind', `${k === 'vwap' ? 'VWAP' : k === 'ema9' ? 'EMA 9' : 'EMA 21'} ${v == null ? '—' : C.axisFormatter(S.display)(v)}`); s.style.color = ic[k];
|
|
331
|
+
r2.appendChild(s);
|
|
332
|
+
}
|
|
333
|
+
if (r2.firstChild) legendEl.appendChild(r2);
|
|
334
|
+
}
|
|
335
|
+
chart.subscribeCrosshairMove((p) => { S.hover = p && p.time != null ? p.time : null; renderLegend(S.hover); });
|
|
336
|
+
|
|
337
|
+
// ---------------------------------------------------------------- header, LIVE badge, banner, position row
|
|
338
|
+
function renderHeader() {
|
|
339
|
+
const vm = S.vm; if (!vm) return;
|
|
340
|
+
const sym = String(vm.symbol || '?').replace(/^\$/, '');
|
|
341
|
+
// task-1366: the title says when the axis is market cap (history scaled by the CURRENT supply) or why it is not
|
|
342
|
+
titleEl.textContent = `$${sym} · ${S.interval || vm.interval || ''}${S.display.label ? ' · ' + S.display.label : ''}${vm.name ? ' · ' + String(vm.name).slice(0, 32) : ''}`;
|
|
343
|
+
document.title = `dexterm · ${sym} · ${S.interval || ''}`;
|
|
344
|
+
renderCoinImage(vm.image);
|
|
345
|
+
const px = typeof vm.livePrice === 'number' && vm.livePrice > 0 ? vm.livePrice : vm.price;
|
|
346
|
+
const ch = vm.chg && typeof vm.chg.h24 === 'number' ? vm.chg.h24 : null;
|
|
347
|
+
// TokenView.chg is already in PERCENT (DexScreener priceChange: 42 = +42 %), same as the terminal header.
|
|
348
|
+
const head = S.display.mode === 'mcap' && typeof px === 'number' ? `$${C.fmtMcapAxis(px * S.display.supply)} (${C.fmtPrice(px)})` : C.fmtPrice(px);
|
|
349
|
+
DT.status(`${head}${ch == null ? '' : ` ${ch >= 0 ? '+' : '-'}${Math.abs(ch).toFixed(Math.abs(ch) >= 1000 ? 0 : 1)}% 24h`}`, ch == null ? null : ch >= 0 ? 'up' : 'down');
|
|
350
|
+
watermark.applyOptions({ lines: [{ text: `${sym} · ${S.interval || ''}`, color: DT.rgba(T.accent, 0.06), fontSize: 46, fontStyle: 'bold', fontFamily: 'ui-monospace, Menlo, monospace' }] });
|
|
351
|
+
}
|
|
352
|
+
// task-1326: the coin image comes from OUR loopback server only (`/img/<32 hex>.png`); anything else is ignored,
|
|
353
|
+
// so a hostile VM can never make the page load a remote URL (the CSP would refuse it too).
|
|
354
|
+
let imgShown = null;
|
|
355
|
+
function renderCoinImage(path) {
|
|
356
|
+
const ok = typeof path === 'string' && /^\/img\/[0-9a-f]{32}\.png$/.test(path);
|
|
357
|
+
if (!ok) { if (imgShown !== null) { imgShown = null; while (imgSlot.firstChild) imgSlot.removeChild(imgSlot.firstChild); imgSlot.hidden = true; } return; }
|
|
358
|
+
if (imgShown === path) return;
|
|
359
|
+
imgShown = path;
|
|
360
|
+
while (imgSlot.firstChild) imgSlot.removeChild(imgSlot.firstChild);
|
|
361
|
+
const im = document.createElement('img');
|
|
362
|
+
im.width = 16; im.height = 16; im.alt = ''; im.decoding = 'async'; im.referrerPolicy = 'no-referrer';
|
|
363
|
+
im.src = path; // same-origin path: resolves against this loopback origin
|
|
364
|
+
im.onerror = () => { imgSlot.hidden = true; };
|
|
365
|
+
imgSlot.appendChild(im);
|
|
366
|
+
imgSlot.hidden = false;
|
|
367
|
+
}
|
|
368
|
+
function renderLive() {
|
|
369
|
+
if (!S.lastDataAt) { liveEl.className = 'live wait'; liveEl.textContent = '● …'; return; }
|
|
370
|
+
const age = performance.now() - S.lastDataAt;
|
|
371
|
+
const feed = S.vm && S.vm.feed === 'LIVE' ? 'stream' : 'poll';
|
|
372
|
+
if (age > STALE_MS) { liveEl.className = 'live stale'; liveEl.textContent = `◌ STALE ${Math.round(age / 1000)}s`; liveEl.title = 'no tick from the terminal recently'; }
|
|
373
|
+
else { liveEl.className = 'live on'; liveEl.textContent = '● LIVE'; liveEl.title = `ticks from the terminal (${feed})`; }
|
|
374
|
+
}
|
|
375
|
+
setInterval(renderLive, 1000);
|
|
376
|
+
function renderDataBanner() {
|
|
377
|
+
const vm = S.vm || {};
|
|
378
|
+
if (vm.cold) S.banners.data = `warming ${vm.interval} from the terminal's candle cache — appears as soon as the terminal has it (no extra requests)`;
|
|
379
|
+
else if (!S.bars.length && vm.source && vm.source.state === 'loading') S.banners.data = 'loading candles…';
|
|
380
|
+
else if (!S.bars.length) S.banners.data = `no candles for ${vm.interval || 'this timeframe'}${vm.source && vm.source.reason ? ` · ${vm.source.reason}` : ''}`;
|
|
381
|
+
else if (vm.source && (vm.source.stale || vm.source.state === 'error')) S.banners.data = `candles stale${vm.source.reason ? ` · ${vm.source.reason}` : ''}`;
|
|
382
|
+
else S.banners.data = null;
|
|
383
|
+
banner();
|
|
384
|
+
}
|
|
385
|
+
function renderPosition() {
|
|
386
|
+
const pos = S.vm && S.vm.position;
|
|
387
|
+
if (!pos) { posEl.hidden = true; return; }
|
|
388
|
+
posEl.hidden = false;
|
|
389
|
+
posTitleEl.textContent = pos.kind === 'paper' ? '◈ POSITION · PAPER' : '◈ POSITION';
|
|
390
|
+
posTitleEl.classList.toggle('live-ledger', pos.kind !== 'paper');
|
|
391
|
+
posCapEl.textContent = pos.caption || '';
|
|
392
|
+
while (posCellsEl.firstChild) posCellsEl.removeChild(posCellsEl.firstChild);
|
|
393
|
+
for (const c of pos.cells || []) {
|
|
394
|
+
const d = el('div', 'pos-cell'); d.appendChild(el('span', 'k', c.label));
|
|
395
|
+
d.appendChild(el('span', `v ${['up', 'down', 'muted', 'warn'].includes(c.tone) ? c.tone : ''}`, c.value));
|
|
396
|
+
posCellsEl.appendChild(d);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ---------------------------------------------------------------- incoming view models
|
|
401
|
+
function onData(vm) {
|
|
402
|
+
if (!vm || typeof vm !== 'object') return;
|
|
403
|
+
perf.vmCount++;
|
|
404
|
+
S.lastDataAt = performance.now();
|
|
405
|
+
if (vm.mint !== S.mint) { S.mint = vm.mint; S.fitNext = true; clearDrawings(true); }
|
|
406
|
+
if (!S.prefsApplied) applyPrefs(vm.prefs || { indicators: DEFAULT_IND, style: 'candles', scale: 'auto' });
|
|
407
|
+
const prevKey = S.key;
|
|
408
|
+
const tail = vm.mode === 'tail';
|
|
409
|
+
// task-1366: a mode / supply change re-labels the axis in place (the bars are prices either way)
|
|
410
|
+
const disp = C.displayOf(vm);
|
|
411
|
+
if (disp.key !== S.display.key) { S.display = disp; applyPriceFormat(); }
|
|
412
|
+
if (tail && (!vm.seriesKey || vm.seriesKey !== S.key)) { S.vm = { ...vm, candles: [] }; renderHeader(); renderPosition(); renderOverlays(); return; }
|
|
413
|
+
const intervalChanged = vm.interval !== S.interval;
|
|
414
|
+
S.interval = vm.interval;
|
|
415
|
+
const answered = S.pendingInterval === vm.interval ? S.reqAt : null;
|
|
416
|
+
if (S.pendingInterval === vm.interval) S.pendingInterval = null;
|
|
417
|
+
S.vm = vm;
|
|
418
|
+
if (tail) {
|
|
419
|
+
const before = S.bars.length;
|
|
420
|
+
S.bars = C.mergeTail(S.bars, vm.candles);
|
|
421
|
+
renderTail(S.bars.slice(Math.max(0, before - 1)));
|
|
422
|
+
} else {
|
|
423
|
+
S.bars = C.toBars(vm.candles);
|
|
424
|
+
S.key = vm.seriesKey || null;
|
|
425
|
+
if (intervalChanged || S.key !== prevKey) S.fitNext = true;
|
|
426
|
+
renderFull();
|
|
427
|
+
}
|
|
428
|
+
renderHeader(); renderLegend(S.hover); renderDataBanner(); renderPosition(); syncToolbar(); renderLive();
|
|
429
|
+
// rain mood follows direction (gold on a pump, red-shift on a dump), as in the v1 HUD; chg is in percent
|
|
430
|
+
const dir = vm.chg ? (vm.chg.h24 ?? vm.chg.h1 ?? 0) : 0;
|
|
431
|
+
if (rain) { rain.setMood(dir > 1 ? 'rainPump' : dir < -1 ? 'rainDump' : 'rain'); rain.setActivity(Math.min(1, 0.4 + Math.abs(dir) / 100)); rain.setSources([String(vm.symbol || ''), String(vm.mint || '').slice(0, 6)]); }
|
|
432
|
+
// paint measurement: the frame after next has the chart's render committed
|
|
433
|
+
const sentAt = typeof vm.sentAt === 'number' ? vm.sentAt : null;
|
|
434
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
435
|
+
const wall = Date.now();
|
|
436
|
+
if (answered != null) perf.intervalLatencies.push(+(performance.now() - answered).toFixed(1)); // request → repainted, in-page
|
|
437
|
+
if (!S.firstPainted && S.bars.length) {
|
|
438
|
+
S.firstPainted = true; perf.firstPaintMs = +performance.now().toFixed(1); perf.firstPaintWall = wall;
|
|
439
|
+
DT.send({ type: 'event', name: 'chart.painted', payload: { kind: 'first', ms: perf.firstPaintMs, sentAt: sentAt ?? 0 } });
|
|
440
|
+
} else if (sentAt) {
|
|
441
|
+
const ms = Math.max(0, wall - sentAt); perf.dataLatencies.push(ms);
|
|
442
|
+
if (perf.dataLatencies.length <= 20 || perf.dataLatencies.length % 20 === 0) DT.send({ type: 'event', name: 'chart.painted', payload: { kind: 'data', ms, sentAt } });
|
|
443
|
+
}
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
DT.onData(onData);
|
|
447
|
+
DT.onHello((m) => {
|
|
448
|
+
const p = (m && m.popup && m.popup.params) || {};
|
|
449
|
+
// Browser fallback: say WHY on the page itself (the terminal shows the same reason as a toast).
|
|
450
|
+
if (!DT.native) S.banners.fallback = `opened in your browser, not the dexterm window: ${typeof p.fallback === 'string' ? p.fallback : 'the native popup shell is unavailable'}`;
|
|
451
|
+
banner();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// ---------------------------------------------------------------- drawing tools: horizontal line + trend line
|
|
455
|
+
let drawId = 0, mouse = null;
|
|
456
|
+
function armDraw(kind) {
|
|
457
|
+
S.draw = kind && S.draw !== kind ? kind : null; S.tPending = null; mouse = null;
|
|
458
|
+
drawCv.classList.toggle('armed', !!S.draw);
|
|
459
|
+
hintEl.hidden = !S.draw;
|
|
460
|
+
hintEl.textContent = S.draw === 'h' ? 'click a price for a horizontal line · esc cancel' : S.draw === 't' ? 'click the first point · esc cancel' : '';
|
|
461
|
+
syncToolbar();
|
|
462
|
+
}
|
|
463
|
+
function clearDrawings(silent) {
|
|
464
|
+
for (const d of S.drawings) if (d.kind === 'h') setLine(`hline-${d.id}`, null);
|
|
465
|
+
S.drawings = []; if (!silent) armDraw(null);
|
|
466
|
+
}
|
|
467
|
+
function undoDrawing() { const d = S.drawings.pop(); if (d && d.kind === 'h') setLine(`hline-${d.id}`, null); }
|
|
468
|
+
function pointAt(e) {
|
|
469
|
+
const r = chartEl.getBoundingClientRect(); const x = e.clientX - r.left, y = e.clientY - r.top;
|
|
470
|
+
const logical = chart.timeScale().coordinateToLogical(x); const price = active().coordinateToPrice(y);
|
|
471
|
+
if (logical == null || price == null) return null;
|
|
472
|
+
return { time: C.timeOfLogical(S.bars, logical, periodMs() / 1000), price };
|
|
473
|
+
}
|
|
474
|
+
drawCv.addEventListener('mousedown', (e) => {
|
|
475
|
+
if (!S.draw || e.button !== 0 || !S.bars.length) return;
|
|
476
|
+
e.preventDefault();
|
|
477
|
+
const pt = pointAt(e); if (!pt) return;
|
|
478
|
+
if (S.draw === 'h') { S.drawings.push({ kind: 'h', id: ++drawId, price: pt.price }); renderOverlays(); armDraw(null); return; }
|
|
479
|
+
if (!S.tPending) { S.tPending = pt; hintEl.textContent = 'click the second point · esc cancel'; return; }
|
|
480
|
+
S.drawings.push({ kind: 't', id: ++drawId, a: S.tPending, b: pt }); armDraw(null);
|
|
481
|
+
});
|
|
482
|
+
drawCv.addEventListener('mousemove', (e) => { mouse = S.draw === 't' && S.tPending ? pointAt(e) : null; });
|
|
483
|
+
function resizeDraw() {
|
|
484
|
+
const dpr = Math.min(2, window.devicePixelRatio || 1); const w = chartEl.clientWidth, h = chartEl.clientHeight;
|
|
485
|
+
if (drawCv.width !== Math.floor(w * dpr) || drawCv.height !== Math.floor(h * dpr)) { drawCv.width = Math.floor(w * dpr); drawCv.height = Math.floor(h * dpr); }
|
|
486
|
+
dctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
487
|
+
}
|
|
488
|
+
/** Trend lines live in (time, price) space and are re-projected every frame, so they pan/zoom with the chart. */
|
|
489
|
+
function drawOverlay() {
|
|
490
|
+
resizeDraw();
|
|
491
|
+
const w = chartEl.clientWidth, h = chartEl.clientHeight;
|
|
492
|
+
dctx.clearRect(0, 0, w, h);
|
|
493
|
+
const segs = S.drawings.filter((d) => d.kind === 't').map((d) => [d.a, d.b]);
|
|
494
|
+
if (S.tPending && mouse) segs.push([S.tPending, mouse]);
|
|
495
|
+
if (!segs.length || !S.bars.length) return;
|
|
496
|
+
const ts = chart.timeScale(), s = active(), pS = periodMs() / 1000;
|
|
497
|
+
const paneH = chart.panes()[0] ? chart.panes()[0].getHeight() : h;
|
|
498
|
+
dctx.save(); dctx.beginPath(); dctx.rect(0, 0, ts.width(), paneH); dctx.clip();
|
|
499
|
+
dctx.strokeStyle = T.accent; dctx.fillStyle = T.accent; dctx.lineWidth = 1.5;
|
|
500
|
+
for (const [a, b] of segs) {
|
|
501
|
+
const xa = ts.logicalToCoordinate(C.logicalOf(S.bars, a.time, pS)), xb = ts.logicalToCoordinate(C.logicalOf(S.bars, b.time, pS));
|
|
502
|
+
const ya = s.priceToCoordinate(a.price), yb = s.priceToCoordinate(b.price);
|
|
503
|
+
if ([xa, xb, ya, yb].some((v) => v == null)) continue;
|
|
504
|
+
dctx.beginPath(); dctx.moveTo(xa, ya); dctx.lineTo(xb, yb); dctx.stroke();
|
|
505
|
+
for (const [x, y] of [[xa, ya], [xb, yb]]) { dctx.beginPath(); dctx.arc(x, y, 2.5, 0, Math.PI * 2); dctx.fill(); }
|
|
506
|
+
}
|
|
507
|
+
dctx.restore();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ---------------------------------------------------------------- keyboard (terminal-style, single keys)
|
|
511
|
+
window.addEventListener('keydown', (e) => {
|
|
512
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
513
|
+
const k = e.key;
|
|
514
|
+
const n = parseInt(k, 10);
|
|
515
|
+
if (n >= 1 && n <= 6) { requestInterval(C.INTERVALS[n - 1]); return; }
|
|
516
|
+
switch (k) {
|
|
517
|
+
case 'c': setStyle(C.STYLES[(C.STYLES.indexOf(S.style) + 1) % C.STYLES.length], true); break;
|
|
518
|
+
case 'a': setScale('auto', true); break;
|
|
519
|
+
case 'l': setScale(S.scale === 'log' ? 'auto' : 'log', true); break;
|
|
520
|
+
case 'p': case '%': setScale(S.scale === 'percent' ? 'auto' : 'percent', true); break;
|
|
521
|
+
case 'e': { const on = !(S.ind.has('ema9') && S.ind.has('ema21')); toggleInd('ema9', on); toggleInd('ema21', on); break; }
|
|
522
|
+
case 'w': toggleInd('vwap'); break;
|
|
523
|
+
case 'm': toggleInd('volma'); break;
|
|
524
|
+
case 'v': toggleInd('volume'); break;
|
|
525
|
+
case 'b': toggleInd('tape'); break;
|
|
526
|
+
case 'h': armDraw('h'); break;
|
|
527
|
+
case 't': armDraw('t'); break;
|
|
528
|
+
case 'x': clearDrawings(); break;
|
|
529
|
+
case 'Backspace': undoDrawing(); renderOverlays(); break;
|
|
530
|
+
case 'f': chart.timeScale().fitContent(); break;
|
|
531
|
+
case 'r': chart.timeScale().scrollToRealTime(); break;
|
|
532
|
+
case 'q': DT.requestClose(); break;
|
|
533
|
+
default: return;
|
|
534
|
+
}
|
|
535
|
+
e.preventDefault();
|
|
536
|
+
});
|
|
537
|
+
// Esc cancels an armed drawing tool first; otherwise common.js closes the popup.
|
|
538
|
+
DT.onEscape = () => { if (S.draw) { armDraw(null); return true; } return false; };
|
|
539
|
+
|
|
540
|
+
// In the native window the attribution links must not navigate the HUD (the shell refuses off-origin
|
|
541
|
+
// navigation anyway); in the browser fallback they open normally in a new tab (target=_blank, noopener).
|
|
542
|
+
document.addEventListener('click', (e) => {
|
|
543
|
+
const a = e.target && e.target.closest ? e.target.closest('a') : null;
|
|
544
|
+
if (a && DT.native) e.preventDefault();
|
|
545
|
+
}, true);
|
|
546
|
+
|
|
547
|
+
window.addEventListener('resize', () => { sizePanes(); });
|
|
548
|
+
ensureVolume(S.ind.has('volume'));
|
|
549
|
+
syncToolbar();
|
|
550
|
+
DT.wireChrome();
|
|
551
|
+
DT.connect();
|
|
552
|
+
DT.loop(drawOverlay);
|
|
553
|
+
window.__dtChart = { chart, S, main, ind, get vol() { return vol; } }; // harness hook (read-only use)
|
|
554
|
+
})();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* dexterm popup — shared chrome + cyberspace look (task-1281 palette; approved HUD look from the spike).
|
|
2
|
+
Inlined under a CSP nonce. Transparent body so the frameless shell shows CSS-rounded corners. */
|
|
3
|
+
:root { --bg:#020507; --panel:#03090c; --border:#0c3a24; --borderFocus:#39ff14; --text:#eafff0; --muted:#62c98a; --dim:#3aa866; --accent:#00f0ff; --up:#5dff6e; --down:#ff3b6b; --warn:#f5ff2d; --ai:#d97757; --pump:#ff2bd6; --dbc:#ff8a3d; }
|
|
4
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5
|
+
html, body { width: 100%; height: 100%; background: transparent; overflow: hidden; }
|
|
6
|
+
body { font: 13px/1.35 ui-monospace, "SF Mono", Menlo, monospace; color: var(--text); -webkit-user-select: none; user-select: none; }
|
|
7
|
+
.hud {
|
|
8
|
+
position: absolute; inset: 0; display: flex; flex-direction: column;
|
|
9
|
+
background: radial-gradient(130% 100% at 50% 0%, var(--panel) 0%, var(--bg) 78%);
|
|
10
|
+
border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
|
|
11
|
+
box-shadow: 0 10px 40px rgba(0,0,0,.6), inset 0 0 0 1px rgba(57,255,20,.04);
|
|
12
|
+
}
|
|
13
|
+
.drag { display: flex; align-items: center; gap: 8px; padding: 6px 10px; cursor: default; -webkit-app-region: drag;
|
|
14
|
+
background: linear-gradient(to bottom, rgba(12,58,36,.5), rgba(3,9,12,0)); border-bottom: 1px solid var(--border); }
|
|
15
|
+
.drag .mark { color: var(--accent); }
|
|
16
|
+
.drag .title { font-weight: 700; letter-spacing: .04em; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
17
|
+
.drag .status { margin-left: auto; color: var(--warn); font-size: 11px; }
|
|
18
|
+
.drag .close { -webkit-app-region: no-drag; cursor: pointer; background: none; border: 1px solid var(--border); color: var(--muted);
|
|
19
|
+
border-radius: 6px; width: 22px; height: 20px; line-height: 1; font-size: 12px; }
|
|
20
|
+
.drag .close:hover { color: var(--down); border-color: var(--down); }
|
|
21
|
+
.stage { position: relative; flex: 1 1 auto; min-height: 0; }
|
|
22
|
+
.stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
|
|
23
|
+
.legend { display: flex; flex-wrap: wrap; gap: 10px; padding: 4px 10px; font-size: 11px; color: var(--muted); border-top: 1px solid var(--border); }
|
|
24
|
+
.legend b { color: var(--dim); font-weight: 400; }
|
|
25
|
+
.foot { display: flex; gap: 14px; padding: 4px 10px; font-size: 11px; color: var(--dim); border-top: 1px solid var(--border); }
|
|
26
|
+
.foot .fps { margin-left: auto; color: var(--muted); }
|
|
27
|
+
/* list-style popups (token detail, feeds) */
|
|
28
|
+
.body { position: relative; flex: 1 1 auto; min-height: 0; overflow: auto; }
|
|
29
|
+
.rows { position: relative; z-index: 1; }
|
|
30
|
+
.row { display: flex; gap: 10px; padding: 4px 12px; border-bottom: 1px solid rgba(12,58,36,.3); }
|
|
31
|
+
.row .k { color: var(--muted); min-width: 92px; }
|
|
32
|
+
.row .v { color: var(--text); white-space: pre-wrap; word-break: break-word; }
|
|
33
|
+
.row.sel { background: rgba(0,240,255,.08); }
|
|
34
|
+
.up { color: var(--up); } .down { color: var(--down); } .warn { color: var(--warn); } .muted { color: var(--muted); }
|