dexterm-cli 0.1.5 → 0.1.7
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/dist/packages/agents/src/index.js +2 -0
- package/dist/packages/agents/src/schema.js +22 -1
- package/dist/packages/agents/src/spectator.js +54 -0
- package/dist/packages/agents/src/story.js +532 -0
- package/dist/packages/agents/src/tiers.js +28 -2
- package/dist/packages/agents/src/tools.js +31 -2
- package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
- package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
- package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
- package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
- package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
- package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
- package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
- package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
- package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
- package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
- package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
- package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
- package/dist/packages/autotrade/src/account.js +74 -0
- package/dist/packages/autotrade/src/backtest.js +357 -0
- package/dist/packages/autotrade/src/basis-text.js +32 -0
- package/dist/packages/autotrade/src/chat/approvals.js +484 -0
- package/dist/packages/autotrade/src/chat/audit.js +140 -0
- package/dist/packages/autotrade/src/chat/config.js +93 -0
- package/dist/packages/autotrade/src/chat/connector.js +24 -0
- package/dist/packages/autotrade/src/chat/control.js +103 -0
- package/dist/packages/autotrade/src/chat/fake.js +100 -0
- package/dist/packages/autotrade/src/chat/format.js +71 -0
- package/dist/packages/autotrade/src/chat/hub.js +187 -0
- package/dist/packages/autotrade/src/chat/index.js +18 -0
- package/dist/packages/autotrade/src/chat/notify.js +292 -0
- package/dist/packages/autotrade/src/chat/pairing.js +46 -0
- package/dist/packages/autotrade/src/chat/redact.js +48 -0
- package/dist/packages/autotrade/src/chat/secrets.js +99 -0
- package/dist/packages/autotrade/src/chat/service.js +248 -0
- package/dist/packages/autotrade/src/chat/telegram.js +290 -0
- package/dist/packages/autotrade/src/desk-executor.js +188 -0
- package/dist/packages/autotrade/src/engine.js +387 -0
- package/dist/packages/autotrade/src/execution-port.js +97 -0
- package/dist/packages/autotrade/src/fleet-config.js +169 -0
- package/dist/packages/autotrade/src/fleet-control.js +276 -0
- package/dist/packages/autotrade/src/fleet.js +639 -0
- package/dist/packages/autotrade/src/index.js +53 -0
- package/dist/packages/autotrade/src/ledger.js +108 -0
- package/dist/packages/autotrade/src/live-source.js +182 -0
- package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
- package/dist/packages/autotrade/src/monitor.js +190 -0
- package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
- package/dist/packages/autotrade/src/paper-accounts.js +72 -0
- package/dist/packages/autotrade/src/replay.js +198 -0
- package/dist/packages/autotrade/src/runner-config.js +99 -0
- package/dist/packages/autotrade/src/runner.js +872 -0
- package/dist/packages/autotrade/src/safety.js +243 -0
- package/dist/packages/autotrade/src/session.js +269 -0
- package/dist/packages/autotrade/src/sessions.js +503 -0
- package/dist/packages/autotrade/src/store.js +250 -0
- package/dist/packages/autotrade/src/strategy.js +256 -0
- package/dist/packages/autotrade/src/trade-ledger.js +241 -0
- package/dist/packages/autotrade/src/yolo.js +140 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
- package/dist/packages/cli/src/agent-cmd.js +6 -4
- package/dist/packages/cli/src/agent-spectator.js +17 -1
- package/dist/packages/cli/src/ai-cmd.js +412 -0
- package/dist/packages/cli/src/analyst/backend.js +105 -15
- package/dist/packages/cli/src/analyst/codex.js +14 -9
- package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
- package/dist/packages/cli/src/analyst/ollama.js +263 -0
- package/dist/packages/cli/src/analyst/session.js +17 -3
- package/dist/packages/cli/src/app-help-gen.js +51 -15
- package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
- package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
- package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
- package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
- package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
- package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
- package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
- package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
- package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
- package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
- package/dist/packages/cli/src/autotrade-cmd.js +168 -0
- package/dist/packages/cli/src/backtest-cmd.js +13 -2
- package/dist/packages/cli/src/display.js +208 -23
- package/dist/packages/cli/src/doctor.js +62 -3
- package/dist/packages/cli/src/export.js +10 -2
- package/dist/packages/cli/src/index.js +1 -1
- package/dist/packages/cli/src/ledger-cmd.js +288 -0
- package/dist/packages/cli/src/live/chart-fills.js +46 -0
- package/dist/packages/cli/src/live/dev-history.js +31 -15
- package/dist/packages/cli/src/live/index.js +28 -90
- package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
- package/dist/packages/cli/src/live/screens/token-live.js +48 -15
- package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
- package/dist/packages/cli/src/live/trade.js +30 -453
- package/dist/packages/cli/src/live/watchlist.js +1 -1
- package/dist/packages/cli/src/main.js +290 -199
- package/dist/packages/cli/src/mcp-cmd.js +7 -1
- package/dist/packages/cli/src/mcp-follow.js +103 -6
- package/dist/packages/cli/src/plan-cmd.js +15 -5
- package/dist/packages/cli/src/pnl-app.js +31 -21
- package/dist/packages/cli/src/pnl-card.js +158 -21
- package/dist/packages/cli/src/popup.js +38 -6
- package/dist/packages/cli/src/record-cmd.js +141 -20
- package/dist/packages/cli/src/scan/ui.js +7 -1
- package/dist/packages/cli/src/screens/agent-hud.js +773 -0
- package/dist/packages/cli/src/screens/analyst-session.js +11 -4
- package/dist/packages/cli/src/screens/analyst.js +3 -2
- package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
- package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
- package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
- package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
- package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
- package/dist/packages/cli/src/screens/brain.js +634 -0
- package/dist/packages/cli/src/screens/newpairs.js +1 -1
- package/dist/packages/cli/src/screens/overlay.js +5 -2
- package/dist/packages/cli/src/screens/plans.js +5 -17
- package/dist/packages/cli/src/screens/pnl-chip.js +144 -0
- package/dist/packages/cli/src/screens/positions.js +6 -28
- package/dist/packages/cli/src/screens/screener.js +1 -0
- package/dist/packages/cli/src/screens/ticket.js +38 -205
- package/dist/packages/cli/src/screens/token.js +18 -9
- package/dist/packages/cli/src/screens/trending.js +1 -1
- package/dist/packages/cli/src/state.js +88 -18
- package/dist/packages/cli/src/telegram-cmd.js +451 -0
- package/dist/packages/cli/src/trade.js +3 -128
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/cli/src/wallet-cmd.js +39 -340
- package/dist/packages/cli/src/wallet-command.js +8 -11
- package/dist/packages/cli/src/widgets.js +0 -12
- package/dist/packages/custom/src/config.js +20 -2
- package/dist/packages/custom/src/schema.js +41 -10
- package/dist/packages/custom/src/workspace.js +3 -3
- package/dist/packages/data/src/basis.js +191 -0
- package/dist/packages/data/src/detail.js +53 -2
- package/dist/packages/data/src/index.js +2 -14
- package/dist/packages/data/src/larp/score.js +220 -0
- package/dist/packages/data/src/larp/x-account.js +316 -0
- package/dist/packages/data/src/store.js +14 -5
- package/dist/packages/data/src/trade/agent/paper-desk.js +300 -23
- package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
- package/dist/packages/data/src/trade/bridge.js +4 -102
- package/dist/packages/data/src/trade/ledger/export.js +101 -0
- package/dist/packages/data/src/trade/ledger/file.js +135 -0
- package/dist/packages/data/src/trade/ledger/fold.js +191 -0
- package/dist/packages/data/src/trade/ledger/index.js +9 -0
- package/dist/packages/data/src/trade/ledger/schema.js +83 -0
- package/dist/packages/data/src/trade/ledger/sources.js +307 -0
- package/dist/packages/data/src/trade/ledger/stats.js +123 -0
- package/dist/packages/data/src/trade/ledger/tags.js +116 -0
- package/dist/packages/data/src/trade/ledger/view.js +51 -0
- package/dist/packages/data/src/trade/plans/engine.js +7 -0
- package/dist/packages/data/src/trade/plans/index.js +0 -1
- package/dist/packages/data/src/trade/plans/mandate.js +28 -15
- package/dist/packages/data/src/trade/plans/service.js +4 -28
- package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
- package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/dev-history.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
- package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
- package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
- package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +6 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
- package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
- package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +7 -3
- package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +9 -7
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +4 -13
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +40 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +10 -7
- package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
- package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
- package/dist/packages/mcp/src/app-help.js +1 -1
- package/dist/packages/mcp/src/autotrade-tools.js +944 -0
- package/dist/packages/mcp/src/backtest-requests.js +71 -0
- package/dist/packages/mcp/src/engine/config.js +159 -0
- package/dist/packages/mcp/src/engine/index.js +10 -0
- package/dist/packages/mcp/src/engine/loop.js +101 -0
- package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
- package/dist/packages/mcp/src/engine/ollama.js +258 -0
- package/dist/packages/mcp/src/engine/probe.js +110 -0
- package/dist/packages/mcp/src/engine/reason.js +35 -0
- package/dist/packages/mcp/src/engine/restrictions.js +111 -0
- package/dist/packages/mcp/src/index.js +7 -1
- package/dist/packages/mcp/src/ledger-tools.js +346 -0
- package/dist/packages/mcp/src/paper-agent.js +5 -2
- package/dist/packages/mcp/src/server.js +285 -36
- package/dist/packages/mcp/src/spectator-sink.js +85 -0
- package/dist/packages/packs/src/backtest.js +3 -1
- package/dist/packages/packs/src/index.js +1 -0
- package/dist/packages/packs/src/larp-score-backtest.js +119 -0
- package/dist/packages/popup/src/pages/chart-lib.js +169 -35
- package/dist/packages/popup/src/pages/chart.css +18 -0
- package/dist/packages/popup/src/pages/chart.html +6 -1
- package/dist/packages/popup/src/pages/chart.js +136 -10
- package/dist/packages/popup/src/pages/spectator.css +45 -0
- package/dist/packages/popup/src/pages/spectator.html +1 -0
- package/dist/packages/popup/src/pages/spectator.js +69 -0
- package/dist/packages/popup/src/protocol.js +4 -0
- package/dist/packages/popup/src/server.js +9 -1
- package/dist/packages/recorder/src/dev-history.js +7 -3
- package/dist/packages/recorder/src/index.js +2 -0
- package/dist/packages/recorder/src/recorder.js +63 -6
- package/dist/packages/recorder/src/retention.js +100 -0
- package/dist/packages/recorder/src/schema.js +34 -1
- package/dist/packages/recorder/src/ticks.js +237 -0
- package/dist/packages/tui/src/canvas.js +7 -0
- package/dist/packages/tui/src/fmt.js +53 -0
- package/dist/packages/tui/src/index.js +1 -0
- package/dist/packages/tui/src/keymap.js +25 -1
- package/dist/packages/tui/src/markers.js +219 -0
- package/dist/packages/tui/src/widgets.js +158 -6
- package/package.json +2 -1
- package/dist/packages/data/src/trade/caps.js +0 -323
- package/dist/packages/data/src/trade/executor.js +0 -933
- package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
- package/dist/packages/data/src/trade/rpc-exec.js +0 -229
- package/dist/packages/data/src/trade/signer/client.js +0 -197
- package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
- package/dist/packages/data/src/trade/signer/host.js +0 -97
- package/dist/packages/data/src/trade/signer/main.js +0 -121
- package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
- package/dist/packages/data/src/trade/signer/protocol.js +0 -92
- package/dist/packages/data/src/trade/signer/service.js +0 -400
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
- package/dist/packages/data/src/trade/sol-usd.js +0 -86
- package/dist/packages/data/src/trade/swap-build.js +0 -108
- package/dist/packages/data/src/trade/token-delta.js +0 -201
- package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// LARP detector — keyless X account lookup (task-1471). Read-only, no login, no cookies, no X API key.
|
|
2
|
+
//
|
|
3
|
+
// Why a fallback chain: measured 2026-10-19 (handoff evidence x-repro.md), FxTwitter answers `404 User not found`
|
|
4
|
+
// for accounts that exist on roughly one call in six, a missing handle gets a 302 to the project's GitHub page on
|
|
5
|
+
// some calls, and "suspended" is reported only some of the time. vxtwitter serves a cached copy (its own
|
|
6
|
+
// `fetched_on`, up to about a day old). X's own oEmbed endpoint says exists / missing and the canonical handle, and
|
|
7
|
+
// nothing else. The X syndication timeline (429 / empty) and the Nitter-class mirrors (offline, suspended, or a 200
|
|
8
|
+
// "offline" page) gave nothing usable and are not in the chain.
|
|
9
|
+
//
|
|
10
|
+
// So no single answer is a verdict: `active` needs one source returning the profile; `suspended` needs a source
|
|
11
|
+
// saying so; `not_found` needs TWO independent sources (vxtwitter AND oEmbed) saying missing; anything else is
|
|
12
|
+
// `unavailable`. Every field that could not be read is listed in `unavailable` and is never guessed.
|
|
13
|
+
//
|
|
14
|
+
// Politeness: one handle per user action, at most five calls, a per-host spacing limiter, a TTL cache, and a
|
|
15
|
+
// circuit breaker per source (the shared HealthRegistry: 3 failures or 2× 429 in a minute → degraded, 30 s..10 min
|
|
16
|
+
// backoff). The bio is untrusted text: the caller firewalls it before it reaches a model.
|
|
17
|
+
import { HealthRegistry } from "../health.js";
|
|
18
|
+
import { systemClock } from "../clock.js";
|
|
19
|
+
import { parseHttpUrl } from "./links.js";
|
|
20
|
+
const RESERVED = new Set(['i', 'home', 'search', 'intent', 'hashtag', 'share', 'explore', 'settings', 'messages', 'notifications', 'login', 'signup', 'tos', 'privacy', 'compose']);
|
|
21
|
+
const HANDLE = /^[A-Za-z0-9_]{1,15}$/;
|
|
22
|
+
const X_HOSTS = new Set(['x.com', 'twitter.com', 'mobile.twitter.com', 'mobile.x.com', 'fxtwitter.com', 'vxtwitter.com', 'fixupx.com', 'nitter.net']);
|
|
23
|
+
const SOURCE_ID = { fxtwitter: 'fxtwitter', vxtwitter: 'vxtwitter', 'x-oembed': 'x-oembed' };
|
|
24
|
+
const HOST = { fxtwitter: 'api.fxtwitter.com', vxtwitter: 'api.vxtwitter.com', 'x-oembed': 'publish.x.com' };
|
|
25
|
+
/** Parse an advertised X link into its kind, handle and status id. Pure. */
|
|
26
|
+
export function parseXLink(raw) {
|
|
27
|
+
const u = parseHttpUrl(raw);
|
|
28
|
+
if (!u)
|
|
29
|
+
return { kind: 'other', handle: null, statusId: null };
|
|
30
|
+
const host = u.hostname.toLowerCase().replace(/^www\./, '');
|
|
31
|
+
if (!X_HOSTS.has(host))
|
|
32
|
+
return { kind: 'other', handle: null, statusId: null };
|
|
33
|
+
const parts = u.pathname.split('/').filter(Boolean);
|
|
34
|
+
if (parts[0]?.toLowerCase() === 'i' && parts[1]?.toLowerCase() === 'communities')
|
|
35
|
+
return { kind: 'community', handle: null, statusId: null };
|
|
36
|
+
const h = parts[0] ?? '';
|
|
37
|
+
if (!HANDLE.test(h) || RESERVED.has(h.toLowerCase()))
|
|
38
|
+
return { kind: 'other', handle: null, statusId: null };
|
|
39
|
+
if (parts[1]?.toLowerCase() === 'status' && /^\d{1,25}$/.test(parts[2] ?? ''))
|
|
40
|
+
return { kind: 'status', handle: h, statusId: parts[2] };
|
|
41
|
+
return { kind: parts.length === 1 ? 'profile' : 'other', handle: h, statusId: null };
|
|
42
|
+
}
|
|
43
|
+
const json = (body) => {
|
|
44
|
+
try {
|
|
45
|
+
const v = JSON.parse(body);
|
|
46
|
+
return v && typeof v === 'object' ? v : null;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
53
|
+
const str = (v) => (typeof v === 'string' && v ? v : null);
|
|
54
|
+
const iso = (v) => { const t = typeof v === 'string' ? Date.parse(v) : NaN; return Number.isFinite(t) ? new Date(t).toISOString() : null; };
|
|
55
|
+
const days = (now, at) => (at ? Math.max(0, Math.floor((now - Date.parse(at)) / 86_400_000)) : null);
|
|
56
|
+
const bareHost = (raw) => { const u = raw ? parseHttpUrl(raw) : null; return u ? u.hostname.toLowerCase().replace(/^www\./, '') : null; };
|
|
57
|
+
function mentionsToken(bio, ctx) {
|
|
58
|
+
const sym = (ctx.symbol ?? '').trim();
|
|
59
|
+
const name = (ctx.name ?? '').trim();
|
|
60
|
+
if (!sym && !name && !ctx.mint)
|
|
61
|
+
return null;
|
|
62
|
+
const b = bio.toLowerCase();
|
|
63
|
+
if (ctx.mint && bio.includes(ctx.mint))
|
|
64
|
+
return true;
|
|
65
|
+
if (sym.length >= 2 && new RegExp(`(^|[^a-z0-9])\\$?${sym.toLowerCase().replace(/[^a-z0-9]/g, '')}([^a-z0-9]|$)`).test(b))
|
|
66
|
+
return true;
|
|
67
|
+
if (name.length >= 3 && b.includes(name.toLowerCase()))
|
|
68
|
+
return true;
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/** The lookup service: chain + cache + spacing + circuit breakers. One instance per process. */
|
|
72
|
+
export class XAccountLookup {
|
|
73
|
+
fetchImpl;
|
|
74
|
+
clock;
|
|
75
|
+
health;
|
|
76
|
+
minIntervalMs;
|
|
77
|
+
ttlMs;
|
|
78
|
+
ttlUnavailableMs;
|
|
79
|
+
maxEntries;
|
|
80
|
+
cache = new Map();
|
|
81
|
+
lastCall = new Map();
|
|
82
|
+
constructor(opts) {
|
|
83
|
+
this.fetchImpl = opts.fetch;
|
|
84
|
+
this.clock = opts.clock ?? systemClock;
|
|
85
|
+
this.health = opts.health ?? new HealthRegistry({ clock: this.clock });
|
|
86
|
+
this.minIntervalMs = opts.minIntervalMs ?? 1000;
|
|
87
|
+
this.ttlMs = opts.ttlMs ?? 30 * 60_000;
|
|
88
|
+
this.ttlUnavailableMs = opts.ttlUnavailableMs ?? 10 * 60_000;
|
|
89
|
+
this.maxEntries = opts.maxEntries ?? 500;
|
|
90
|
+
}
|
|
91
|
+
/** Per-host spacing: calls to one host are serialized at least minIntervalMs apart. */
|
|
92
|
+
async spaced(host, fn) {
|
|
93
|
+
const prev = this.lastCall.get(host) ?? Promise.resolve();
|
|
94
|
+
let release;
|
|
95
|
+
const mine = new Promise((r) => { release = r; });
|
|
96
|
+
this.lastCall.set(host, prev.then(() => mine));
|
|
97
|
+
await prev;
|
|
98
|
+
try {
|
|
99
|
+
return await fn();
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
if (this.minIntervalMs > 0)
|
|
103
|
+
void this.clock.sleep(this.minIntervalMs).then(release);
|
|
104
|
+
else
|
|
105
|
+
release();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async get(source, endpoint, url, trace) {
|
|
109
|
+
const sid = SOURCE_ID[source];
|
|
110
|
+
if (!this.health.isAvailable(sid)) {
|
|
111
|
+
trace.push({ source, endpoint, status: null, result: 'skipped: circuit open (source degraded)', ms: 0 });
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const t0 = this.clock.now();
|
|
115
|
+
try {
|
|
116
|
+
const r = await this.spaced(HOST[source], () => this.fetchImpl(url));
|
|
117
|
+
const ms = this.clock.now() - t0;
|
|
118
|
+
// 404 / 3xx are answers (a vote), not source failures; 429 and 5xx other than vxtwitter's suspended body are.
|
|
119
|
+
const failed = r.status === 0 || r.status === 429 || (r.status >= 500 && !/suspended/i.test(r.body));
|
|
120
|
+
this.health.record(sid, failed ? { ok: false, status: r.status, latencyMs: ms, error: `http ${r.status}` } : { ok: true, status: r.status, latencyMs: ms });
|
|
121
|
+
return { status: r.status, body: r.body, ms };
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
const ms = this.clock.now() - t0;
|
|
125
|
+
this.health.record(sid, { ok: false, latencyMs: ms, error: e.message });
|
|
126
|
+
trace.push({ source, endpoint, status: null, result: `error: ${e.message}`.slice(0, 120), ms });
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Look up the account behind an advertised X link. Never throws. */
|
|
131
|
+
async lookup(xUrl, ctx = {}) {
|
|
132
|
+
const link = parseXLink(xUrl);
|
|
133
|
+
const now = this.clock.now();
|
|
134
|
+
const key = `${link.handle?.toLowerCase() ?? xUrl}|${link.statusId ?? ''}|${(ctx.symbol ?? '').toLowerCase()}|${(ctx.name ?? '').toLowerCase()}|${ctx.mint ?? ''}|${bareHost(ctx.website) ?? ''}`;
|
|
135
|
+
const hit = this.cache.get(key);
|
|
136
|
+
if (hit && now - hit.at < (hit.report.status === 'unavailable' ? this.ttlUnavailableMs : this.ttlMs))
|
|
137
|
+
return { ...hit.report, cached: true };
|
|
138
|
+
const report = await this.uncached(xUrl, link, ctx);
|
|
139
|
+
this.cache.delete(key);
|
|
140
|
+
this.cache.set(key, { at: now, report });
|
|
141
|
+
while (this.cache.size > this.maxEntries)
|
|
142
|
+
this.cache.delete(this.cache.keys().next().value);
|
|
143
|
+
return report;
|
|
144
|
+
}
|
|
145
|
+
async uncached(url, link, ctx) {
|
|
146
|
+
const now = this.clock.now();
|
|
147
|
+
const trace = [];
|
|
148
|
+
const base = {
|
|
149
|
+
url, kind: link.kind, handle: link.handle, status: 'unavailable', screenName: null, id: null, renamed: null,
|
|
150
|
+
createdAt: null, ageDays: null, followers: null, following: null, tweets: null, lastPostAt: null, daysSinceLastPost: null,
|
|
151
|
+
bio: null, website: null, bioMentionsToken: null, linksSite: null, countsAsOf: null, countsSource: null,
|
|
152
|
+
unavailable: [], sources: trace, cached: false, fetchedAt: new Date(now).toISOString(),
|
|
153
|
+
};
|
|
154
|
+
if (!link.handle) {
|
|
155
|
+
base.unavailable = ['account (the X link is not an account or a post)'];
|
|
156
|
+
return base;
|
|
157
|
+
}
|
|
158
|
+
let h = link.handle;
|
|
159
|
+
// A status link: who wrote the post now. A different author means a renamed account or a borrowed post.
|
|
160
|
+
if (link.statusId) {
|
|
161
|
+
const r = await this.get('fxtwitter', 'status', `https://api.fxtwitter.com/${h}/status/${link.statusId}`, trace);
|
|
162
|
+
const j = r ? json(r.body) : null;
|
|
163
|
+
const author = str(j?.tweet?.author?.screen_name);
|
|
164
|
+
if (r)
|
|
165
|
+
trace.push({ source: 'fxtwitter', endpoint: 'status', status: r.status, result: author ? `post by @${author}` : `no post (${str(j?.message) ?? r.status})`, ms: r.ms });
|
|
166
|
+
if (author && author.toLowerCase() !== h.toLowerCase()) {
|
|
167
|
+
base.renamed = { from: h, to: author, evidence: `the linked post ${link.statusId} is now shown under @${author}` };
|
|
168
|
+
h = author;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
let profile = null;
|
|
172
|
+
let suspended = false;
|
|
173
|
+
let missingVotes = 0;
|
|
174
|
+
const missingBy = [];
|
|
175
|
+
// 1) FxTwitter profile: fresh counts. A 404 is one vote only (measured false 404s).
|
|
176
|
+
const fx = await this.get('fxtwitter', 'profile', `https://api.fxtwitter.com/${h}`, trace);
|
|
177
|
+
if (fx) {
|
|
178
|
+
const j = json(fx.body);
|
|
179
|
+
const u = j?.user;
|
|
180
|
+
if (fx.status === 200 && u) {
|
|
181
|
+
profile = { screenName: str(u.screen_name), id: str(u.id), createdAt: iso(u.joined), followers: num(u.followers), following: num(u.following), tweets: num(u.tweets), bio: typeof u.description === 'string' ? u.description : null, website: str(u.website?.url), asOf: new Date(now).toISOString() };
|
|
182
|
+
trace.push({ source: 'fxtwitter', endpoint: 'profile', status: 200, result: 'profile', ms: fx.ms });
|
|
183
|
+
}
|
|
184
|
+
else if (str(j?.reason) === 'suspended') {
|
|
185
|
+
suspended = true;
|
|
186
|
+
trace.push({ source: 'fxtwitter', endpoint: 'profile', status: fx.status, result: 'suspended', ms: fx.ms });
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
trace.push({ source: 'fxtwitter', endpoint: 'profile', status: fx.status, result: fx.status >= 300 && fx.status < 400 ? 'redirect (missing handle or error page)' : `no profile (${str(j?.message) ?? fx.status})`, ms: fx.ms });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// 2) vxtwitter: cached copy; says suspended / missing reliably in the probe.
|
|
193
|
+
if (!profile) {
|
|
194
|
+
const vx = await this.get('vxtwitter', 'profile', `https://api.vxtwitter.com/${h}`, trace);
|
|
195
|
+
if (vx) {
|
|
196
|
+
const j = json(vx.body);
|
|
197
|
+
if (vx.status === 200 && j && str(j.screen_name)) {
|
|
198
|
+
const fetchedOn = num(j.fetched_on);
|
|
199
|
+
profile = { screenName: str(j.screen_name), id: j.id === undefined || j.id === null ? null : String(j.id), createdAt: iso(j.created_at), followers: num(j.followers_count), following: num(j.following_count), tweets: num(j.tweet_count), bio: typeof j.description === 'string' ? j.description : null, website: null, asOf: fetchedOn ? new Date(fetchedOn * 1000).toISOString() : null };
|
|
200
|
+
trace.push({ source: 'vxtwitter', endpoint: 'profile', status: 200, result: 'profile (cached copy)', ms: vx.ms });
|
|
201
|
+
}
|
|
202
|
+
else if (/suspended/i.test(str(j?.error) ?? '')) {
|
|
203
|
+
suspended = true;
|
|
204
|
+
trace.push({ source: 'vxtwitter', endpoint: 'profile', status: vx.status, result: 'suspended', ms: vx.ms });
|
|
205
|
+
}
|
|
206
|
+
else if (vx.status === 404) {
|
|
207
|
+
missingVotes++;
|
|
208
|
+
missingBy.push('vxtwitter');
|
|
209
|
+
trace.push({ source: 'vxtwitter', endpoint: 'profile', status: 404, result: 'not found', ms: vx.ms });
|
|
210
|
+
}
|
|
211
|
+
else
|
|
212
|
+
trace.push({ source: 'vxtwitter', endpoint: 'profile', status: vx.status, result: 'no answer', ms: vx.ms });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// 3) X oEmbed: exists vs missing (200 for a suspended account too).
|
|
216
|
+
let oembedExists = null;
|
|
217
|
+
if (!profile && !suspended) {
|
|
218
|
+
const oe = await this.get('x-oembed', 'oembed', `https://publish.x.com/oembed?url=${encodeURIComponent(`https://x.com/${h}`)}`, trace);
|
|
219
|
+
if (oe) {
|
|
220
|
+
const j = json(oe.body);
|
|
221
|
+
if (oe.status === 200 && j) {
|
|
222
|
+
oembedExists = true;
|
|
223
|
+
trace.push({ source: 'x-oembed', endpoint: 'oembed', status: 200, result: 'exists', ms: oe.ms });
|
|
224
|
+
}
|
|
225
|
+
else if (oe.status === 404) {
|
|
226
|
+
oembedExists = false;
|
|
227
|
+
missingVotes++;
|
|
228
|
+
missingBy.push('x-oembed');
|
|
229
|
+
trace.push({ source: 'x-oembed', endpoint: 'oembed', status: 404, result: 'not found', ms: oe.ms });
|
|
230
|
+
}
|
|
231
|
+
else
|
|
232
|
+
trace.push({ source: 'x-oembed', endpoint: 'oembed', status: oe.status, result: 'no answer', ms: oe.ms });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// 4) Last post date (FxTwitter statuses), only for an account that exists.
|
|
236
|
+
let lastPostAt = null;
|
|
237
|
+
let postsRead = false;
|
|
238
|
+
if (profile) {
|
|
239
|
+
const st = await this.get('fxtwitter', 'statuses', `https://api.fxtwitter.com/2/profile/${h}/statuses`, trace);
|
|
240
|
+
const j = st ? json(st.body) : null;
|
|
241
|
+
const results = Array.isArray(j?.results) ? j.results : null;
|
|
242
|
+
if (st?.status === 200 && results) {
|
|
243
|
+
postsRead = true;
|
|
244
|
+
const times = results.map((r) => iso(r.created_at)).filter((t) => !!t).sort();
|
|
245
|
+
lastPostAt = times.at(-1) ?? null;
|
|
246
|
+
trace.push({ source: 'fxtwitter', endpoint: 'statuses', status: 200, result: `${results.length} recent posts`, ms: st.ms });
|
|
247
|
+
}
|
|
248
|
+
else if (st)
|
|
249
|
+
trace.push({ source: 'fxtwitter', endpoint: 'statuses', status: st.status, result: 'posts unavailable', ms: st.ms });
|
|
250
|
+
}
|
|
251
|
+
const out = { ...base };
|
|
252
|
+
if (profile) {
|
|
253
|
+
out.status = 'active';
|
|
254
|
+
Object.assign(out, {
|
|
255
|
+
screenName: profile.screenName, id: profile.id, createdAt: profile.createdAt, ageDays: days(now, profile.createdAt),
|
|
256
|
+
followers: profile.followers, following: profile.following, tweets: profile.tweets,
|
|
257
|
+
bio: profile.bio !== null ? profile.bio.slice(0, 280) : null, website: profile.website,
|
|
258
|
+
countsAsOf: profile.asOf, countsSource: trace.find((t) => t.endpoint === 'profile' && t.status === 200)?.source ?? null,
|
|
259
|
+
lastPostAt, daysSinceLastPost: days(now, lastPostAt),
|
|
260
|
+
});
|
|
261
|
+
// A posts list that came back empty for an account with zero tweets is "no posts", not missing.
|
|
262
|
+
if (postsRead && !lastPostAt && profile.tweets === 0)
|
|
263
|
+
out.daysSinceLastPost = null;
|
|
264
|
+
out.bioMentionsToken = profile.bio !== null ? mentionsToken(profile.bio, ctx) : null;
|
|
265
|
+
const site = bareHost(ctx.website);
|
|
266
|
+
if (site) {
|
|
267
|
+
const bioHit = (profile.bio ?? '').toLowerCase().includes(site);
|
|
268
|
+
const webHit = bareHost(profile.website) === site;
|
|
269
|
+
out.linksSite = bioHit || webHit ? true : (profile.website !== null || profile.bio !== null) ? false : null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else if (suspended)
|
|
273
|
+
out.status = 'suspended';
|
|
274
|
+
else if (missingVotes >= 2)
|
|
275
|
+
out.status = 'not_found';
|
|
276
|
+
else if (oembedExists)
|
|
277
|
+
out.status = 'unavailable'; // exists per X, but no source returned the profile
|
|
278
|
+
const miss = [];
|
|
279
|
+
if (out.status !== 'active') {
|
|
280
|
+
if (out.status === 'unavailable')
|
|
281
|
+
miss.push(`account status (${missingBy.length ? `only ${missingBy.join(' + ')} said missing; ` : ''}no source returned the profile)`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
if (out.createdAt === null)
|
|
285
|
+
miss.push('account created date');
|
|
286
|
+
if (out.followers === null)
|
|
287
|
+
miss.push('follower count');
|
|
288
|
+
if (out.following === null)
|
|
289
|
+
miss.push('following count');
|
|
290
|
+
if (out.tweets === null)
|
|
291
|
+
miss.push('post count');
|
|
292
|
+
if (!postsRead)
|
|
293
|
+
miss.push('last post date');
|
|
294
|
+
if (out.bio === null)
|
|
295
|
+
miss.push('bio');
|
|
296
|
+
if (ctx.website && out.linksSite === null)
|
|
297
|
+
miss.push('site cross-link');
|
|
298
|
+
if (out.bioMentionsToken === null && (ctx.symbol || ctx.name))
|
|
299
|
+
miss.push('bio mentions the token');
|
|
300
|
+
}
|
|
301
|
+
out.unavailable = miss;
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/** Advertised handles per source, and whether they and the site's own X links agree. Pure. */
|
|
306
|
+
export function xHandleAgreement(advertised, siteLinks) {
|
|
307
|
+
const handles = advertised.map((l) => ({ h: parseXLink(l.url), source: l.source })).filter((x) => x.h.handle).map((x) => ({ handle: x.h.handle, source: x.source }));
|
|
308
|
+
const distinct = [...new Set(handles.map((x) => x.handle.toLowerCase()))];
|
|
309
|
+
const siteHandles = siteLinks === null ? null : [...new Set(siteLinks.map((u) => parseXLink(u)).filter((x) => x.kind === 'profile' && x.handle).map((x) => x.handle.toLowerCase()))];
|
|
310
|
+
const siteLinksAdvertised = siteHandles === null || !distinct.length ? null : siteHandles.length === 0 ? false : siteHandles.some((s) => distinct.includes(s));
|
|
311
|
+
const agree = distinct.length === 0 ? null : distinct.length === 1 && (siteHandles === null || siteHandles.length === 0 || siteHandles.includes(distinct[0]));
|
|
312
|
+
const siteAgrees = !distinct.length || !siteHandles?.length ? null : siteHandles.some((s) => distinct.includes(s));
|
|
313
|
+
const note = distinct.length > 1 ? `sources advertise different X accounts: ${distinct.map((d) => `@${d}`).join(', ')}`
|
|
314
|
+
: agree === false && siteHandles?.length ? `the website links @${siteHandles.join(', @')} but the token lists @${distinct[0]}` : null;
|
|
315
|
+
return { handles, agree, siteAgrees, siteHandles, siteLinksAdvertised, note };
|
|
316
|
+
}
|
|
@@ -10,15 +10,24 @@ export const MCAP_AGREE_RATIO = 5;
|
|
|
10
10
|
* (or `fdv` when absent) can reflect only the Solana-side supply of bridged/wrapped
|
|
11
11
|
* tokens. Take the larger of DexScreener and Jupiter `mcap` when they agree within
|
|
12
12
|
* 5x; otherwise keep DexScreener's number and flag the row. One side only: use it.
|
|
13
|
+
*
|
|
14
|
+
* task-1518: when both sources' prices are known, Jupiter's cap is first moved to DexScreener's price (its implied
|
|
15
|
+
* supply x the newer price), so "agree" compares the SUPPLY each source counts, which is what the rule is about. A
|
|
16
|
+
* Jupiter record served from the cache can be minutes old: on a coin that ran 6x since, the raw caps are 6x apart
|
|
17
|
+
* while both count the same supply. Comparing the raw caps flagged every fast runner (and the autotrade thesis-split
|
|
18
|
+
* exit sold it, "market-cap sources 1.0x apart"), and on a coin that dumped it showed the stale, larger cap.
|
|
13
19
|
*/
|
|
14
|
-
export function resolveMcap(ds, jupMcap) {
|
|
20
|
+
export function resolveMcap(ds, jupMcap, prices) {
|
|
15
21
|
const d = ds ? (ds.mcap ?? ds.fdv) : null;
|
|
16
22
|
const j = jupMcap ?? null;
|
|
17
|
-
const pos = (n) => n !== null && Number.isFinite(n) && n > 0;
|
|
23
|
+
const pos = (n) => n !== null && n !== undefined && Number.isFinite(n) && n > 0;
|
|
18
24
|
if (pos(d) && pos(j)) {
|
|
19
|
-
const
|
|
25
|
+
const dp = prices?.dexscreener, jp = prices?.jupiter;
|
|
26
|
+
// Jupiter's cap at DexScreener's (newer) price: its supply x the price the row shows.
|
|
27
|
+
const jNow = pos(dp) && pos(jp) ? j * (dp / jp) : j;
|
|
28
|
+
const ratio = Math.max(d, jNow) / Math.min(d, jNow);
|
|
20
29
|
if (ratio <= MCAP_AGREE_RATIO) {
|
|
21
|
-
return
|
|
30
|
+
return jNow > d ? { value: jNow, basis: 'jupiter', flagged: false, dexscreener: d, jupiter: j } : { value: d, basis: 'dexscreener', flagged: false, dexscreener: d, jupiter: j };
|
|
22
31
|
}
|
|
23
32
|
return { value: d, basis: 'dexscreener', flagged: true, dexscreener: d, jupiter: j };
|
|
24
33
|
}
|
|
@@ -104,7 +113,7 @@ export class Store {
|
|
|
104
113
|
price: p,
|
|
105
114
|
sources,
|
|
106
115
|
stale: ds.stale.has(mint) || jup.stale.has(mint) || price.stale.has(mint),
|
|
107
|
-
mcap: resolveMcap(d?.pair, j?.pair.mcap),
|
|
116
|
+
mcap: resolveMcap(d?.pair, j?.pair.mcap, { dexscreener: d?.pair.priceUsd, jupiter: j?.pair.priceUsd }),
|
|
108
117
|
});
|
|
109
118
|
}
|
|
110
119
|
const latencyMs = {};
|