dexterm-cli 0.1.6 → 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/schema.js +22 -1
- package/dist/packages/agents/src/story.js +11 -2
- package/dist/packages/agents/src/tiers.js +27 -2
- package/dist/packages/agents/src/tools.js +30 -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/token-dossier/SKILL.md +1 -1
- package/dist/packages/cli/src/ai-cmd.js +412 -0
- package/dist/packages/cli/src/analyst/backend.js +82 -15
- package/dist/packages/cli/src/analyst/codex.js +14 -9
- package/dist/packages/cli/src/analyst/ollama.js +263 -0
- package/dist/packages/cli/src/analyst/session.js +5 -2
- package/dist/packages/cli/src/app-help-gen.js +44 -8
- 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/display.js +156 -17
- 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/index.js +23 -89
- package/dist/packages/cli/src/live/screens/token-live.js +9 -6
- package/dist/packages/cli/src/live/trade.js +12 -457
- package/dist/packages/cli/src/live/watchlist.js +1 -1
- package/dist/packages/cli/src/main.js +147 -190
- package/dist/packages/cli/src/mcp-cmd.js +7 -1
- package/dist/packages/cli/src/plan-cmd.js +1 -1
- 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 +22 -6
- package/dist/packages/cli/src/record-cmd.js +141 -20
- package/dist/packages/cli/src/screens/agent-hud.js +34 -2
- package/dist/packages/cli/src/screens/analyst-session.js +7 -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 +37 -11
- 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 +9 -8
- package/dist/packages/cli/src/screens/positions.js +6 -28
- package/dist/packages/cli/src/screens/ticket.js +38 -205
- package/dist/packages/cli/src/screens/token.js +3 -3
- package/dist/packages/cli/src/state.js +25 -19
- 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 +35 -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 +0 -14
- package/dist/packages/data/src/store.js +14 -5
- package/dist/packages/data/src/trade/agent/paper-desk.js +228 -16
- 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/index.js +0 -1
- package/dist/packages/data/src/trade/plans/service.js +3 -27
- 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/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/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 +3 -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/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 +4 -1
- 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-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 +1 -1
- 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 +8 -6
- 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.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 +3 -4
- 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 +6 -7
- 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-what-it-shows.md +2 -1
- package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
- 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-the-brain-view-and-the-pnl-chip.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 +2 -0
- package/dist/packages/mcp/src/server.js +127 -19
- package/dist/packages/packs/src/backtest.js +3 -1
- package/dist/packages/popup/src/pages/chart-lib.js +134 -21
- package/dist/packages/popup/src/pages/chart.css +9 -0
- package/dist/packages/popup/src/pages/chart.html +3 -1
- package/dist/packages/popup/src/pages/chart.js +104 -7
- package/dist/packages/popup/src/protocol.js +2 -0
- package/dist/packages/popup/src/server.js +4 -0
- 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/index.js +1 -0
- package/dist/packages/tui/src/keymap.js +21 -1
- package/dist/packages/tui/src/markers.js +219 -0
- package/dist/packages/tui/src/widgets.js +149 -2
- 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,292 @@
|
|
|
1
|
+
// dexterm autotrade chat — tier 1, notify (task-1527, AUTOTRADE-SPEC §8.4, §8.6).
|
|
2
|
+
//
|
|
3
|
+
// The bus reads the run ledger as it is written (every event the runner already records) and the runner's state,
|
|
4
|
+
// and turns them into short plain-text messages, each led by its badge (PAPER / DEVNET / LIVE):
|
|
5
|
+
// candidate found a digest every 15 minutes (default), each one, or off
|
|
6
|
+
// entry / exit side, size ($ and % of the book), price, profile, exit reason, the agent's reason (inert)
|
|
7
|
+
// trade closed the PnL card image (when the host can render it) + one line
|
|
8
|
+
// halts and guards state, reason, what clears it (kill, stop, flatten, pause, breaker, stuck bag, guard)
|
|
9
|
+
// spend / allowance once a day each, at 80 % of the day's model spend cap or of a usage window
|
|
10
|
+
// daily summary at dailySummaryAt local: trades, wins/losses, day P/L %, open positions, spend, state,
|
|
11
|
+
// approvals (tapped / skipped / expired)
|
|
12
|
+
// Shown: badge, symbol (inert), short mint, strategy id/version/short hash, the trade's size and % of the book, P/L
|
|
13
|
+
// in % (and $ per trade), day P/L in %. Hidden: keys, the token, wallet addresses, the absolute book (opt-in for
|
|
14
|
+
// paper only), file paths, RPC URLs.
|
|
15
|
+
// Rate: above 20 messages a minute the bus switches to digests (notify-only lines wait for the next flush); quiet
|
|
16
|
+
// hours hold notify-only lines too. Halts always go. The engine never waits on any of this.
|
|
17
|
+
import { localDay as localDayOf } from "./audit.js";
|
|
18
|
+
import { inQuietHours } from "./config.js";
|
|
19
|
+
import { inert, pct, price, shortMint, usdShort } from "./format.js";
|
|
20
|
+
export const DIGEST_EVERY_MS = 15 * 60_000;
|
|
21
|
+
export const DIGEST_ABOVE_PER_MIN = 20;
|
|
22
|
+
const HALT_CLEARS = {
|
|
23
|
+
'kill-switch': 'the panic switch is on: only `dexterm wallet resume` on your computer lifts it',
|
|
24
|
+
stop: 'this run takes no new entries and ends once nothing is held; start a new run on your computer',
|
|
25
|
+
flatten: 'every position sells at its next tick, then this run ends',
|
|
26
|
+
'control-unreadable': 'the control file cannot be read: look at it on your computer',
|
|
27
|
+
'runner-stopped': 'stopped on your computer',
|
|
28
|
+
'daily-loss': 'the daily loss limit: entries stay off for the rest of the day; it clears locally',
|
|
29
|
+
drawdown: 'the drawdown limit: entries stay off; it clears locally',
|
|
30
|
+
};
|
|
31
|
+
export class NotifyBus {
|
|
32
|
+
o;
|
|
33
|
+
now;
|
|
34
|
+
symbols = new Map();
|
|
35
|
+
reasons = new Map();
|
|
36
|
+
profiles = new Map();
|
|
37
|
+
fillsByPosition = new Map();
|
|
38
|
+
candidates = [];
|
|
39
|
+
digest = [];
|
|
40
|
+
lastDigestAt;
|
|
41
|
+
sentAt = [];
|
|
42
|
+
queue = Promise.resolve();
|
|
43
|
+
state = null;
|
|
44
|
+
strategyLine = '';
|
|
45
|
+
/** task-1532: the run's strategy carries the HIGH RISK label (the yolo preset): every entry and the start say so. */
|
|
46
|
+
highRisk = false;
|
|
47
|
+
day = '';
|
|
48
|
+
dayStats = { closed: 0, wins: 0, losses: 0, pnlUsd: 0 };
|
|
49
|
+
warned = new Set();
|
|
50
|
+
summarySentFor = '';
|
|
51
|
+
dayStart;
|
|
52
|
+
constructor(o) {
|
|
53
|
+
this.o = o;
|
|
54
|
+
this.now = o.now ?? Date.now;
|
|
55
|
+
this.lastDigestAt = this.now();
|
|
56
|
+
this.dayStart = () => { const d = new Date(this.now()); d.setHours(0, 0, 0, 0); return d.getTime(); };
|
|
57
|
+
}
|
|
58
|
+
/** Every run ledger event, as it is written. Never throws, never blocks the runner. */
|
|
59
|
+
onEvent(e) {
|
|
60
|
+
try {
|
|
61
|
+
this.handle(e);
|
|
62
|
+
}
|
|
63
|
+
catch { /* a notify failure never reaches the engine */ }
|
|
64
|
+
}
|
|
65
|
+
onState(st) {
|
|
66
|
+
this.state = st;
|
|
67
|
+
this.strategyLine = `${this.highRisk ? '⚠ HIGH RISK · ' : ''}${inert(st.strategy.id, 32)} v${inert(st.strategy.version, 12)} (${st.strategy.hash.slice(0, 8)})`;
|
|
68
|
+
this.spendWarnings(st);
|
|
69
|
+
}
|
|
70
|
+
prefs() { return this.o.config().notify; }
|
|
71
|
+
rollDay() {
|
|
72
|
+
const d = localDayOf(this.now());
|
|
73
|
+
if (d !== this.day) {
|
|
74
|
+
this.day = d;
|
|
75
|
+
this.dayStats = { closed: 0, wins: 0, losses: 0, pnlUsd: 0 };
|
|
76
|
+
this.warned = new Set();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
emit(template, lines, urgent = false) {
|
|
80
|
+
this.post(template, { kind: 'text', badge: this.o.badge, lines }, urgent);
|
|
81
|
+
}
|
|
82
|
+
post(template, msg, urgent) {
|
|
83
|
+
const now = this.now();
|
|
84
|
+
while (this.sentAt.length && now - this.sentAt[0] > 60_000)
|
|
85
|
+
this.sentAt.shift();
|
|
86
|
+
const quiet = inQuietHours(this.o.config().quietHours, now);
|
|
87
|
+
if (!urgent && (quiet || this.sentAt.length >= DIGEST_ABOVE_PER_MIN)) {
|
|
88
|
+
this.digest.push(msg.kind === 'card' ? msg.caption : msg.lines.join(' · '));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.sentAt.push(now);
|
|
92
|
+
this.queue = this.queue.then(() => this.o.send(msg, { urgent, template })).catch(() => { });
|
|
93
|
+
}
|
|
94
|
+
/** Book share of a size, as text ("2.0% of book"). The absolute book only when opted in (paper). */
|
|
95
|
+
ofBook(usd) {
|
|
96
|
+
const eq = this.state?.book.equityUsd ?? 0;
|
|
97
|
+
const share = eq > 0 ? `${((usd / eq) * 100).toFixed(1)}% of book` : '% of book unknown';
|
|
98
|
+
return this.o.config().showAbsoluteBook && this.o.badge === 'PAPER' && eq > 0 ? `${share}, book ${usdShort(eq)}` : share;
|
|
99
|
+
}
|
|
100
|
+
sym(mint, fallback) { return `"${inert(this.symbols.get(mint) ?? fallback ?? '?', 20)}" ${shortMint(mint)}`; }
|
|
101
|
+
handle(e) {
|
|
102
|
+
this.rollDay();
|
|
103
|
+
const p = this.prefs();
|
|
104
|
+
const mint = typeof e.mint === 'string' ? e.mint : '';
|
|
105
|
+
switch (e.kind) {
|
|
106
|
+
case 'run_start': {
|
|
107
|
+
this.highRisk = e.highRisk === true;
|
|
108
|
+
this.strategyLine = `${this.highRisk ? '⚠ HIGH RISK · ' : ''}${inert(e.strategy, 32)} v${inert(e.version, 12)} (${String(e.strategyHash ?? '').slice(0, 8)})`;
|
|
109
|
+
this.emit('run_start', [`RUNNER STARTED · ${this.strategyLine}`, ...(this.highRisk ? ['full-port strategy: one entry can be the whole stake, and one rug can zero it. Experimental; not financial advice.'] : []), `entries: ${e.decide === 'agent+approve' || e.approvals === 'required' ? 'each one waits for your tap' : String(e.decide) === 'auto' ? 'mechanical' : 'your agent decides'} · /status /pause /kill /help`], true);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
case 'candidate': {
|
|
113
|
+
this.symbols.set(mint, String(e.symbol ?? '?'));
|
|
114
|
+
if (p.candidates === 'off')
|
|
115
|
+
return;
|
|
116
|
+
const line = `${this.sym(mint, e.symbol)} matched ${Array.isArray(e.matched) ? e.matched.map((m) => inert(m, 24)).join(', ') : '?'}`;
|
|
117
|
+
if (p.candidates === 'each')
|
|
118
|
+
this.emit('candidate', [`CANDIDATE · ${line}`]);
|
|
119
|
+
else
|
|
120
|
+
this.candidates.push(line);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
case 'reply': {
|
|
124
|
+
const d = e.decision;
|
|
125
|
+
if (mint && d?.ok && d.decision?.reason)
|
|
126
|
+
this.reasons.set(mint, d.decision.reason);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
case 'fill': {
|
|
130
|
+
const side = e.side === 'buy' ? 'buy' : 'sell';
|
|
131
|
+
if (typeof e.positionId === 'string') {
|
|
132
|
+
const fs = this.fillsByPosition.get(e.positionId) ?? [];
|
|
133
|
+
fs.push({ t: e.t, side, usd: Number(e.usd), tokens: Number(e.tokens) });
|
|
134
|
+
this.fillsByPosition.set(e.positionId, fs);
|
|
135
|
+
}
|
|
136
|
+
if (side === 'buy') {
|
|
137
|
+
if (typeof e.profile === 'string')
|
|
138
|
+
this.profiles.set(mint, e.profile);
|
|
139
|
+
if (!p.entries)
|
|
140
|
+
return;
|
|
141
|
+
const why = this.reasons.get(mint);
|
|
142
|
+
this.emit('entry', [
|
|
143
|
+
`${e.reason === 'ladder-add' ? 'ADD' : 'ENTRY'} · ${this.strategyLine}`,
|
|
144
|
+
`${this.sym(mint)} · ${usdShort(Number(e.usd))} (${this.ofBook(Number(e.usd))}) @ ${price(Number(e.price))}${e.impactPct !== undefined ? ` · impact ${Number(e.impactPct).toFixed(2)}%` : ''}${e.partial ? ' · PARTIAL' : ''}`,
|
|
145
|
+
...(typeof e.profile === 'string' ? [`profile ${inert(e.profile, 32)}`] : []),
|
|
146
|
+
...(why && e.reason !== 'ladder-add' ? [`why (the agent's words): "${inert(why, 280, 'agent')}"`] : []),
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
if (!p.entries)
|
|
151
|
+
return;
|
|
152
|
+
this.emit('exit', [`${e.reason === 'cost-recovery' ? 'COST RECOVERED' : 'EXIT'} · ${this.sym(mint)}`, `sold ${usdShort(Number(e.usd))} @ ${price(Number(e.price))} · ${inert(e.reason, 40)}${typeof e.detail === 'string' ? ` · ${inert(e.detail, 120)}` : ''}`]);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
case 'trade_closed': {
|
|
157
|
+
const t0 = e.trade;
|
|
158
|
+
if (!t0)
|
|
159
|
+
return;
|
|
160
|
+
const t = { ...t0, fills: this.fillsByPosition.get(t0.id) ?? [] };
|
|
161
|
+
this.fillsByPosition.delete(t0.id);
|
|
162
|
+
this.dayStats.closed++;
|
|
163
|
+
this.dayStats.pnlUsd += t.pnlUsd;
|
|
164
|
+
if (t.pnlUsd >= 0)
|
|
165
|
+
this.dayStats.wins++;
|
|
166
|
+
else
|
|
167
|
+
this.dayStats.losses++;
|
|
168
|
+
this.symbols.delete(t.mint);
|
|
169
|
+
this.reasons.delete(t.mint);
|
|
170
|
+
if (!p.closes)
|
|
171
|
+
return;
|
|
172
|
+
const caption = `CLOSED · "${inert(t.symbol, 20)}" ${shortMint(t.mint)} · ${pct(t.pnlPct)} (${t.pnlUsd >= 0 ? '+' : ''}${usdShort(t.pnlUsd)}) by ${inert(t.exitReason, 40)} · held ${Math.max(1, Math.round((t.closedAt - t.openedAt) / 60_000))} min`;
|
|
173
|
+
const card = this.o.card;
|
|
174
|
+
if (!card) {
|
|
175
|
+
this.emit('closed', [caption]);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
void card(t).then((png) => {
|
|
179
|
+
if (png)
|
|
180
|
+
this.post('closed_card', { kind: 'card', badge: this.o.badge, png, caption }, false);
|
|
181
|
+
else
|
|
182
|
+
this.emit('closed', [caption]);
|
|
183
|
+
}, () => this.emit('closed', [caption]));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
case 'halt': {
|
|
187
|
+
if (!p.halts)
|
|
188
|
+
return;
|
|
189
|
+
const r = String(e.reason ?? '?');
|
|
190
|
+
this.emit('halt', [`HALT · ${inert(r, 40)}`, HALT_CLEARS[r] ?? 'clears locally', `open positions keep their exits${typeof e.openPositions === 'number' ? ` (${e.openPositions} open)` : ''}`], true);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
case 'flatten': {
|
|
194
|
+
const n = Array.isArray(e.positions) ? e.positions.length : 0;
|
|
195
|
+
this.emit('flatten', [`FLATTEN · ${n} position${n === 1 ? '' : 's'} sell at the next tick`], true);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
case 'entries_paused':
|
|
199
|
+
if (p.halts)
|
|
200
|
+
this.emit('paused', [`PAUSED · no new entries (${inert(e.reason, 40)})`, 'exits keep running · /resume lifts a pause'], true);
|
|
201
|
+
return;
|
|
202
|
+
case 'entries_resumed':
|
|
203
|
+
if (p.halts)
|
|
204
|
+
this.emit('resumed', ['RESUMED · new entries allowed again'], true);
|
|
205
|
+
return;
|
|
206
|
+
case 'guard':
|
|
207
|
+
if (p.halts)
|
|
208
|
+
this.emit('guard', [`GUARD · ${inert(e.state, 40)}`, inert(e.effect, 160)], true);
|
|
209
|
+
return;
|
|
210
|
+
case 'stuck':
|
|
211
|
+
if (p.halts)
|
|
212
|
+
this.emit('stuck', [`STUCK · ${this.sym(mint)} cannot sell (${inert(e.why, 60)})`, 'retried every tick'], true);
|
|
213
|
+
return;
|
|
214
|
+
case 'stopped_flat':
|
|
215
|
+
this.emit('stopped', ['RUNNER STOPPED · nothing held'], true);
|
|
216
|
+
return;
|
|
217
|
+
case 'run_end':
|
|
218
|
+
this.emit('run_end', [`RUNNER ENDED · ${Number(e.trades ?? 0)} closed, ${Number(e.openAtEnd ?? 0)} open`], true);
|
|
219
|
+
return;
|
|
220
|
+
default: return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
spendWarnings(st) {
|
|
224
|
+
if (!this.prefs().spend)
|
|
225
|
+
return;
|
|
226
|
+
this.rollDay();
|
|
227
|
+
const s = st.spend;
|
|
228
|
+
if (Number.isFinite(s.dayCapUsd) && s.dayCapUsd > 0 && s.todayUsd >= 0.8 * s.dayCapUsd && !this.warned.has('spend')) {
|
|
229
|
+
this.warned.add('spend');
|
|
230
|
+
this.emit('spend', [`SPEND · model spend today ${Math.round((s.todayUsd / s.dayCapUsd) * 100)}% of the day cap`, 'new sessions stop at the cap; exits keep running'], true);
|
|
231
|
+
}
|
|
232
|
+
for (const [k, v] of [['five-hour', s.allowance?.fiveHour], ['seven-day', s.allowance?.sevenDay]]) {
|
|
233
|
+
if (typeof v === 'number' && v >= 0.8 && !this.warned.has(k)) {
|
|
234
|
+
this.warned.add(k);
|
|
235
|
+
this.emit('allowance', [`USAGE · your ${k} Claude window is at ${Math.round(v * 100)}%`], true);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Digests, the daily summary. The host calls it every few seconds; tests call it with a moved clock. */
|
|
240
|
+
async tick() {
|
|
241
|
+
const now = this.now();
|
|
242
|
+
this.rollDay();
|
|
243
|
+
if (this.candidates.length && now - this.lastDigestAt >= DIGEST_EVERY_MS) {
|
|
244
|
+
const n = this.candidates.length;
|
|
245
|
+
const lines = this.candidates.splice(0, n);
|
|
246
|
+
this.emit('candidate_digest', [`CANDIDATES · ${n} in the last ${Math.round((now - this.lastDigestAt) / 60_000)} min`, ...lines.slice(0, 10), ...(n > 10 ? [`…and ${n - 10} more`] : [])]);
|
|
247
|
+
this.lastDigestAt = now;
|
|
248
|
+
}
|
|
249
|
+
else if (!this.candidates.length && now - this.lastDigestAt >= DIGEST_EVERY_MS)
|
|
250
|
+
this.lastDigestAt = now;
|
|
251
|
+
const quiet = inQuietHours(this.o.config().quietHours, now);
|
|
252
|
+
while (this.sentAt.length && now - this.sentAt[0] > 60_000)
|
|
253
|
+
this.sentAt.shift();
|
|
254
|
+
if (this.digest.length && !quiet && this.sentAt.length < DIGEST_ABOVE_PER_MIN / 2) {
|
|
255
|
+
const lines = this.digest.splice(0);
|
|
256
|
+
this.post('digest', { kind: 'text', badge: this.o.badge, lines: [`DIGEST · ${lines.length} update${lines.length === 1 ? '' : 's'}`, ...lines.slice(-25)] }, true);
|
|
257
|
+
}
|
|
258
|
+
this.maybeSummary(now);
|
|
259
|
+
await this.queue;
|
|
260
|
+
}
|
|
261
|
+
maybeSummary(now) {
|
|
262
|
+
const at = this.o.config().dailySummaryAt;
|
|
263
|
+
if (!at || !this.prefs().daily)
|
|
264
|
+
return;
|
|
265
|
+
const [h, m] = at.split(':').map(Number);
|
|
266
|
+
const d = new Date(now);
|
|
267
|
+
if (d.getHours() * 60 + d.getMinutes() < h * 60 + m)
|
|
268
|
+
return;
|
|
269
|
+
const day = localDayOf(now);
|
|
270
|
+
if (this.summarySentFor === day)
|
|
271
|
+
return;
|
|
272
|
+
this.summarySentFor = day;
|
|
273
|
+
this.post('daily', { kind: 'text', badge: this.o.badge, lines: this.summaryLines() }, true);
|
|
274
|
+
}
|
|
275
|
+
summaryLines() {
|
|
276
|
+
const st = this.state;
|
|
277
|
+
const ds = this.dayStats;
|
|
278
|
+
const start = st ? st.book.equityUsd - st.book.realizedTodayUsd : 0;
|
|
279
|
+
const dayPct = st && start > 0 ? (st.book.realizedTodayUsd / start) * 100 : null;
|
|
280
|
+
const ap = this.o.approvalCounts?.(this.dayStart()) ?? null;
|
|
281
|
+
const apLine = ap && Object.keys(ap).length ? `approvals: ${Object.entries(ap).map(([k, v]) => `${v} ${k.toLowerCase()}`).join(', ')}` : null;
|
|
282
|
+
return [
|
|
283
|
+
`DAILY SUMMARY · ${this.strategyLine || 'no run'}`,
|
|
284
|
+
`trades closed ${ds.closed} (${ds.wins} won, ${ds.losses} lost) · day P/L ${pct(dayPct)}`,
|
|
285
|
+
`open positions ${st?.positions.length ?? 0} · state ${st ? `${st.state}${st.haltReason ? ` (${inert(st.haltReason, 40)})` : ''}` : 'unknown'}`,
|
|
286
|
+
`model spend today ${st ? `$${st.spend.todayUsd.toFixed(2)}${Number.isFinite(st.spend.dayCapUsd) ? ` of $${st.spend.dayCapUsd}` : ''}` : '?'}`,
|
|
287
|
+
...(apLine ? [apLine] : []),
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
/** Wait for everything queued (end of the host). */
|
|
291
|
+
async flush() { await this.queue; }
|
|
292
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// dexterm autotrade chat — pairing by a local code (task-1527, AUTOTRADE-SPEC §8.3).
|
|
2
|
+
//
|
|
3
|
+
// `dexterm telegram pair` shows a 6-digit code on the local screen that lives 5 minutes. The user sends
|
|
4
|
+
// `/pair <code>` to their bot from their own account in a PRIVATE chat. The code travels through Telegram, so a
|
|
5
|
+
// token holder with a competing poll can read it (and swallow the user's message): a correct code is therefore not
|
|
6
|
+
// enough. Pairing refuses while a webhook or a 409 conflict is seen, and the account that sent the code (its
|
|
7
|
+
// @username and id) is shown on the local screen for a y/N before that sender id + chat id go on the allowlist
|
|
8
|
+
// (telegram-cmd.ts). The code is single use, compared in constant time, and five wrong tries end the window.
|
|
9
|
+
import { randomInt, timingSafeEqual } from 'node:crypto';
|
|
10
|
+
export const PAIR_TTL_MS = 5 * 60_000;
|
|
11
|
+
export const PAIR_MAX_TRIES = 5;
|
|
12
|
+
export class PairingWindow {
|
|
13
|
+
code;
|
|
14
|
+
expiresAt;
|
|
15
|
+
tries = 0;
|
|
16
|
+
done = false;
|
|
17
|
+
now;
|
|
18
|
+
constructor(o = {}) {
|
|
19
|
+
this.now = o.now ?? Date.now;
|
|
20
|
+
this.code = o.code ?? String(randomInt(0, 1_000_000)).padStart(6, '0');
|
|
21
|
+
this.expiresAt = this.now() + (o.ttlMs ?? PAIR_TTL_MS);
|
|
22
|
+
}
|
|
23
|
+
get open() { return !this.done && this.tries < PAIR_MAX_TRIES && this.now() < this.expiresAt; }
|
|
24
|
+
/** Close the window for good (someone else holds the token): the code never pairs anyone after this. */
|
|
25
|
+
abort() { this.done = true; return { ok: false, why: 'conflict' }; }
|
|
26
|
+
attempt(senderId, chatId, chatKind, args) {
|
|
27
|
+
if (this.done)
|
|
28
|
+
return { ok: false, why: 'used' };
|
|
29
|
+
if (this.tries >= PAIR_MAX_TRIES)
|
|
30
|
+
return { ok: false, why: 'locked' };
|
|
31
|
+
if (this.now() >= this.expiresAt)
|
|
32
|
+
return { ok: false, why: 'expired' };
|
|
33
|
+
if (chatKind !== 'private')
|
|
34
|
+
return { ok: false, why: 'group-chat' };
|
|
35
|
+
const given = (args[0] ?? '').replace(/\D/g, '');
|
|
36
|
+
if (!given)
|
|
37
|
+
return { ok: false, why: 'no-code' };
|
|
38
|
+
this.tries++;
|
|
39
|
+
const a = Buffer.from(given.padStart(6, '0').slice(-6));
|
|
40
|
+
const b = Buffer.from(this.code);
|
|
41
|
+
if (given.length !== 6 || !timingSafeEqual(a, b))
|
|
42
|
+
return { ok: false, why: this.tries >= PAIR_MAX_TRIES ? 'locked' : 'wrong-code' };
|
|
43
|
+
this.done = true;
|
|
44
|
+
return { ok: true, senderId, chatId };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// dexterm autotrade chat — the token redactor (task-1527, AUTOTRADE-SPEC §8.3).
|
|
2
|
+
//
|
|
3
|
+
// A bot token is `<digits>:<35 url-safe chars>`, and the Bot API URL carries it (`/bot<token>/sendMessage`): the
|
|
4
|
+
// classic leak is an error message that quotes the URL. Every log line, audit line, ledger field, health string
|
|
5
|
+
// and thrown error from the chat layer passes through redact(). Tests throw a fetch error carrying the URL and
|
|
6
|
+
// look for the token in every sink.
|
|
7
|
+
// The colon may arrive as `:`, `%3A` (a URL inside a URL) or `:` (JSON-escaped).
|
|
8
|
+
const COLON = '(?::|%3[Aa]|\\\\u003[Aa])';
|
|
9
|
+
const URL_FORM = new RegExp(`bot\\d{3,}${COLON}[A-Za-z0-9_-]{20,}`, 'g');
|
|
10
|
+
const BARE_FORM = new RegExp(`\\b\\d{3,}${COLON}[A-Za-z0-9_-]{30,}`, 'g');
|
|
11
|
+
export const REDACTED = 'bot<redacted>';
|
|
12
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
13
|
+
/**
|
|
14
|
+
* The text with every bot token (in a URL or bare) replaced. With the known token (always pass it when you have it)
|
|
15
|
+
* also: the token URL-encoded, the secret half alone, the bot id followed by any part of the secret (a truncated
|
|
16
|
+
* echo), and the first 12 characters of the secret anywhere.
|
|
17
|
+
*/
|
|
18
|
+
export function redact(text, known) {
|
|
19
|
+
let s = typeof text === 'string' ? text : text instanceof Error ? `${text.name}: ${text.message}` : String(text);
|
|
20
|
+
if (known && known.length >= 8) {
|
|
21
|
+
for (const form of [known, encodeURIComponent(known)])
|
|
22
|
+
s = s.split(form).join('<redacted>');
|
|
23
|
+
const i = known.indexOf(':');
|
|
24
|
+
const id = i > 0 ? known.slice(0, i) : '';
|
|
25
|
+
const half = i > 0 ? known.slice(i + 1) : '';
|
|
26
|
+
if (half.length >= 16) {
|
|
27
|
+
s = s.split(half).join('<redacted>');
|
|
28
|
+
if (/^\d+$/.test(id))
|
|
29
|
+
s = s.replace(new RegExp(`${id}${COLON}[A-Za-z0-9_-]+`, 'g'), '<redacted>');
|
|
30
|
+
s = s.replace(new RegExp(`${escapeRe(half.slice(0, 12))}[A-Za-z0-9_-]*`, 'g'), '<redacted>');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return s.replace(URL_FORM, REDACTED).replace(BARE_FORM, '<redacted>');
|
|
34
|
+
}
|
|
35
|
+
/** True when the string looks like a bot token (the setup prompt's check). */
|
|
36
|
+
export const looksLikeBotToken = (s) => /^\d{6,}:[A-Za-z0-9_-]{30,}$/.test(s.trim());
|
|
37
|
+
/** An Error whose message (and cause chain) has been redacted; the original is dropped, never attached. */
|
|
38
|
+
export function redactedError(e, known) {
|
|
39
|
+
const parts = [];
|
|
40
|
+
let cur = e;
|
|
41
|
+
for (let i = 0; cur && i < 3; i++) {
|
|
42
|
+
parts.push(redact(cur instanceof Error ? cur.message : cur, known));
|
|
43
|
+
cur = cur instanceof Error ? cur.cause : null;
|
|
44
|
+
}
|
|
45
|
+
const out = new Error(parts.join(' <- ').slice(0, 300));
|
|
46
|
+
out.name = 'ChatTransportError';
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// dexterm autotrade chat — where the bot token lives (task-1527, AUTOTRADE-SPEC §8.3).
|
|
2
|
+
//
|
|
3
|
+
// The token never goes in connectors.json, a log, argv or the environment. Stores, best first:
|
|
4
|
+
// keychain macOS: the login keychain through `security`. The token goes in on STDIN (`security -i` reads the
|
|
5
|
+
// add command from a pipe), never on the command line, so `ps` cannot see it.
|
|
6
|
+
// libsecret Linux: `secret-tool store` reads the secret from stdin.
|
|
7
|
+
// file a 0600 file under $DEXTERM_HOME/secrets/ (directory 0700), outside every repo, as with the Helius key.
|
|
8
|
+
// The item is named per dexterm home (a hash of its path), so a test home or a second profile never reads or
|
|
9
|
+
// replaces the real one. `DEXTERM_SECRET_STORE=file` forces the file store (CI, containers, tests).
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { join, resolve } from 'node:path';
|
|
14
|
+
export const SECRET_SERVICE = 'xyz.dexterm.telegram';
|
|
15
|
+
export const accountFor = (home) => `bot-token-${createHash('sha256').update(resolve(home)).digest('hex').slice(0, 12)}`;
|
|
16
|
+
const has = (bin) => spawnSync('/usr/bin/env', ['which', bin], { stdio: 'ignore' }).status === 0;
|
|
17
|
+
class KeychainStore {
|
|
18
|
+
kind = 'keychain';
|
|
19
|
+
account;
|
|
20
|
+
constructor(account) { this.account = account; }
|
|
21
|
+
get() {
|
|
22
|
+
const r = spawnSync('security', ['find-generic-password', '-a', this.account, '-s', SECRET_SERVICE, '-w'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
23
|
+
const v = r.status === 0 ? r.stdout.trim() : '';
|
|
24
|
+
return v || null;
|
|
25
|
+
}
|
|
26
|
+
set(value) {
|
|
27
|
+
if (!/^[A-Za-z0-9:_-]+$/.test(value))
|
|
28
|
+
throw new Error('refusing to store a value with characters a bot token never has');
|
|
29
|
+
// The add command (with the value) goes through stdin; argv only says "-i".
|
|
30
|
+
const r = spawnSync('security', ['-i'], { input: `add-generic-password -U -a ${this.account} -s ${SECRET_SERVICE} -l "dexterm telegram bot" -w "${value}"\n`, encoding: 'utf8', stdio: ['pipe', 'ignore', 'pipe'] });
|
|
31
|
+
if (r.status !== 0 || this.get() !== value)
|
|
32
|
+
throw new Error('the macOS keychain did not store the token');
|
|
33
|
+
}
|
|
34
|
+
remove() { spawnSync('security', ['delete-generic-password', '-a', this.account, '-s', SECRET_SERVICE], { stdio: 'ignore' }); }
|
|
35
|
+
}
|
|
36
|
+
class LibsecretStore {
|
|
37
|
+
kind = 'libsecret';
|
|
38
|
+
account;
|
|
39
|
+
constructor(account) { this.account = account; }
|
|
40
|
+
get() {
|
|
41
|
+
const r = spawnSync('secret-tool', ['lookup', 'service', SECRET_SERVICE, 'account', this.account], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
42
|
+
const v = r.status === 0 ? r.stdout.trim() : '';
|
|
43
|
+
return v || null;
|
|
44
|
+
}
|
|
45
|
+
set(value) {
|
|
46
|
+
const r = spawnSync('secret-tool', ['store', '--label=dexterm telegram bot', 'service', SECRET_SERVICE, 'account', this.account], { input: value, encoding: 'utf8', stdio: ['pipe', 'ignore', 'pipe'] });
|
|
47
|
+
if (r.status !== 0 || this.get() !== value)
|
|
48
|
+
throw new Error('libsecret did not store the token');
|
|
49
|
+
}
|
|
50
|
+
remove() { spawnSync('secret-tool', ['clear', 'service', SECRET_SERVICE, 'account', this.account], { stdio: 'ignore' }); }
|
|
51
|
+
}
|
|
52
|
+
class FileStore {
|
|
53
|
+
kind = 'file';
|
|
54
|
+
path;
|
|
55
|
+
constructor(home) { this.path = join(home, 'secrets', 'telegram-bot.token'); }
|
|
56
|
+
get() {
|
|
57
|
+
if (!existsSync(this.path))
|
|
58
|
+
return null;
|
|
59
|
+
const v = readFileSync(this.path, 'utf8').trim();
|
|
60
|
+
return v || null;
|
|
61
|
+
}
|
|
62
|
+
set(value) {
|
|
63
|
+
mkdirSync(join(this.path, '..'), { recursive: true, mode: 0o700 });
|
|
64
|
+
chmodSync(join(this.path, '..'), 0o700);
|
|
65
|
+
writeFileSync(this.path, `${value}\n`, { mode: 0o600 });
|
|
66
|
+
chmodSync(this.path, 0o600);
|
|
67
|
+
}
|
|
68
|
+
remove() { rmSync(this.path, { force: true }); }
|
|
69
|
+
}
|
|
70
|
+
/** In memory (tests). */
|
|
71
|
+
export class MemoryStore {
|
|
72
|
+
kind = 'memory';
|
|
73
|
+
v;
|
|
74
|
+
constructor(v = null) { this.v = v; }
|
|
75
|
+
get() { return this.v; }
|
|
76
|
+
set(value) { this.v = value; }
|
|
77
|
+
remove() { this.v = null; }
|
|
78
|
+
}
|
|
79
|
+
/** The store a kind names (the one setup recorded), for this home. */
|
|
80
|
+
export function secretStore(kind, home) {
|
|
81
|
+
if (kind === 'keychain')
|
|
82
|
+
return new KeychainStore(accountFor(home));
|
|
83
|
+
if (kind === 'libsecret')
|
|
84
|
+
return new LibsecretStore(accountFor(home));
|
|
85
|
+
if (kind === 'memory')
|
|
86
|
+
return new MemoryStore();
|
|
87
|
+
return new FileStore(home);
|
|
88
|
+
}
|
|
89
|
+
/** The best store on this machine: DEXTERM_SECRET_STORE wins, then the OS store, then the 0600 file. */
|
|
90
|
+
export function chooseSecretStore(home, env = process.env, platform = process.platform) {
|
|
91
|
+
const want = env.DEXTERM_SECRET_STORE;
|
|
92
|
+
if (want === 'file' || want === 'keychain' || want === 'libsecret')
|
|
93
|
+
return secretStore(want, home);
|
|
94
|
+
if (platform === 'darwin' && has('security'))
|
|
95
|
+
return secretStore('keychain', home);
|
|
96
|
+
if (platform === 'linux' && has('secret-tool'))
|
|
97
|
+
return secretStore('libsecret', home);
|
|
98
|
+
return secretStore('file', home);
|
|
99
|
+
}
|