dexterm-cli 0.1.7 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/agents/src/spectator.js +31 -3
- package/dist/packages/agents/src/tiers.js +12 -0
- package/dist/packages/agents/src/tools.js +13 -5
- package/dist/packages/autotrade/assets/limits.schema.json +569 -0
- package/dist/packages/autotrade/examples/executor/README.md +70 -0
- package/dist/packages/autotrade/examples/executor/jsonl-tail-executor.mjs +57 -0
- package/dist/packages/autotrade/examples/executor/kit.mjs +144 -0
- package/dist/packages/autotrade/examples/executor/signal-cmd-executor.mjs +25 -0
- package/dist/packages/autotrade/examples/executor/telegram-bot-recipe.md +47 -0
- package/dist/packages/autotrade/examples/executor/webhook-executor.mjs +47 -0
- package/dist/packages/autotrade/src/backtest.js +7 -2
- package/dist/packages/autotrade/src/chat/config.js +4 -2
- package/dist/packages/autotrade/src/chat/control.js +1 -0
- package/dist/packages/autotrade/src/chat/notify.js +16 -6
- package/dist/packages/autotrade/src/chat/secrets.js +25 -18
- package/dist/packages/autotrade/src/chat/service.js +3 -0
- package/dist/packages/autotrade/src/engine.js +16 -2
- package/dist/packages/autotrade/src/executor-health.js +217 -0
- package/dist/packages/autotrade/src/fleet.js +5 -2
- package/dist/packages/autotrade/src/forward.js +355 -0
- package/dist/packages/autotrade/src/index.js +21 -0
- package/dist/packages/autotrade/src/journey.js +477 -0
- package/dist/packages/autotrade/src/limits.js +682 -0
- package/dist/packages/autotrade/src/outbox.js +394 -0
- package/dist/packages/autotrade/src/paper-limits.js +87 -0
- package/dist/packages/autotrade/src/real-mode.js +688 -0
- package/dist/packages/autotrade/src/receipts.js +330 -0
- package/dist/packages/autotrade/src/report.js +168 -0
- package/dist/packages/autotrade/src/rugproof-probe.js +415 -0
- package/dist/packages/autotrade/src/rugproof.js +231 -0
- package/dist/packages/autotrade/src/runner.js +194 -28
- package/dist/packages/autotrade/src/service-guard.js +125 -0
- package/dist/packages/autotrade/src/service-install.js +215 -0
- package/dist/packages/autotrade/src/service-recover.js +232 -0
- package/dist/packages/autotrade/src/service.js +289 -0
- package/dist/packages/autotrade/src/sessions.js +16 -7
- package/dist/packages/autotrade/src/signal-channels.js +312 -0
- package/dist/packages/autotrade/src/signal.js +196 -0
- package/dist/packages/autotrade/src/strategy-stats.js +344 -0
- package/dist/packages/autotrade/src/strategy.js +15 -2
- package/dist/packages/autotrade/src/trade-ledger.js +2 -2
- package/dist/packages/classifier/src/calibrate.js +138 -0
- package/dist/packages/classifier/src/classify.js +107 -0
- package/dist/packages/classifier/src/config.js +220 -0
- package/dist/packages/classifier/src/dossier.js +95 -0
- package/dist/packages/classifier/src/hardware.js +63 -0
- package/dist/packages/classifier/src/index.js +18 -0
- package/dist/packages/classifier/src/models.js +111 -0
- package/dist/packages/classifier/src/providers/heuristic.js +77 -0
- package/dist/packages/classifier/src/providers/http.js +63 -0
- package/dist/packages/classifier/src/providers/jev-open.js +86 -0
- package/dist/packages/classifier/src/providers/llm.js +89 -0
- package/dist/packages/classifier/src/providers/systemone-http.js +95 -0
- package/dist/packages/classifier/src/questions.js +97 -0
- package/dist/packages/classifier/src/registry.js +42 -0
- package/dist/packages/classifier/src/snapshot.js +31 -0
- package/dist/packages/classifier/src/systemone.js +88 -0
- package/dist/packages/classifier/src/types.js +18 -0
- package/dist/packages/cli/assets/dash/dash.css +139 -0
- package/dist/packages/cli/assets/dash/dash.js +200 -0
- package/dist/packages/cli/assets/dash/icon.svg +1 -0
- package/dist/packages/cli/assets/dash/index.html +36 -0
- package/dist/packages/cli/src/analyst/backend.js +3 -2
- package/dist/packages/cli/src/analyst/codex.js +3 -2
- package/dist/packages/cli/src/app-help-gen.js +26 -2
- package/dist/packages/cli/src/autotrade-cmd/audit.js +19 -2
- package/dist/packages/cli/src/autotrade-cmd/backtest.js +21 -4
- package/dist/packages/cli/src/autotrade-cmd/executor.js +91 -0
- package/dist/packages/cli/src/autotrade-cmd/fleet.js +16 -5
- package/dist/packages/cli/src/autotrade-cmd/forward.js +283 -0
- package/dist/packages/cli/src/autotrade-cmd/journey.js +208 -0
- package/dist/packages/cli/src/autotrade-cmd/limits.js +393 -0
- package/dist/packages/cli/src/autotrade-cmd/receipt.js +63 -0
- package/dist/packages/cli/src/autotrade-cmd/report.js +79 -0
- package/dist/packages/cli/src/autotrade-cmd/run.js +268 -14
- package/dist/packages/cli/src/autotrade-cmd/service.js +873 -0
- package/dist/packages/cli/src/autotrade-cmd/signal.js +824 -0
- package/dist/packages/cli/src/autotrade-cmd.js +50 -4
- package/dist/packages/cli/src/classifier-cmd.js +353 -0
- package/dist/packages/cli/src/classifier-live.js +81 -0
- package/dist/packages/cli/src/dash/data.js +217 -0
- package/dist/packages/cli/src/dash/server.js +92 -0
- package/dist/packages/cli/src/dash-cmd.js +113 -0
- package/dist/packages/cli/src/doctor.js +34 -0
- package/dist/packages/cli/src/ledger-cmd.js +4 -2
- package/dist/packages/cli/src/live/screens/token-live.js +4 -0
- package/dist/packages/cli/src/live/signals.js +8 -0
- package/dist/packages/cli/src/main.js +98 -6
- package/dist/packages/cli/src/mcp-follow.js +21 -1
- package/dist/packages/cli/src/narratives-cmd.js +147 -0
- package/dist/packages/cli/src/narratives.js +114 -0
- package/dist/packages/cli/src/overlay/model.js +147 -0
- package/dist/packages/cli/src/overlay/page.js +146 -0
- package/dist/packages/cli/src/overlay/pnl.js +75 -0
- package/dist/packages/cli/src/overlay/server.js +170 -0
- package/dist/packages/cli/src/overlay-cmd.js +367 -0
- package/dist/packages/cli/src/pnl-card.js +4 -0
- package/dist/packages/cli/src/record-cmd.js +82 -11
- package/dist/packages/cli/src/rules-cmd.js +2 -1
- package/dist/packages/cli/src/rules.js +24 -4
- package/dist/packages/cli/src/screens/autotrade-journey.js +134 -0
- package/dist/packages/cli/src/screens/autotrade-keys.js +13 -0
- package/dist/packages/cli/src/screens/autotrade-screen.js +57 -6
- package/dist/packages/cli/src/screens/autotrade-ui.js +17 -7
- package/dist/packages/cli/src/screens/autotrade-worker.js +4 -2
- package/dist/packages/cli/src/screens/narrative.js +154 -2
- package/dist/packages/cli/src/state.js +1 -0
- package/dist/packages/cli/src/telegram-cmd.js +4 -1
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/custom/src/rules.js +5 -1
- package/dist/packages/data/src/trade/ledger/schema.js +1 -1
- package/dist/packages/mcp/assets/kb/app-help/autotrade-executor-kit.md +44 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-journey.md +46 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-limits.md +48 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-real-signal.md +45 -0
- package/dist/packages/mcp/assets/kb/app-help/classification-models.md +41 -0
- package/dist/packages/mcp/assets/kb/app-help/live-trading.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/narratives-tracker.md +45 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +12 -0
- package/dist/packages/mcp/assets/kb/app-help/strategy-dashboard.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/streamer-overlay.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-rug-proof-entries-experimental.md +52 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-always-on-service.md +59 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-strategy-dashboard-dexterm-dash.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +9 -2
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +3 -1
- 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-copy-and-paste-without-surprises.md +0 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-dexterm-says-command-not-found.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-reopen-dexterm-later.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-terminal-shell-prompt-command-output-and-path.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-expectations.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-1-ask-for-a-strategy.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-2-backtest-it-held-out-split.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-3-forward-test-it-on-live-data.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-4-promote-it-to-always-on-paper.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-5-real-only-if-you-choose.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-ladder.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-report.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-what-the-analyst-knows.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-hosted-jev-with-your-own-key.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-how-they-measured-on-dexterm-s-own-history.md +52 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-set-up-kev-on-your-own-machine.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-settings.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-the-choices.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-where-it-plugs-in.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-which-model-for-which-machine.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-invocations.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -0
- 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 +1 -0
- 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 +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-supported-platforms.md +2 -11
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-executor-health.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-paper-vs-real.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-digest.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-executor-kit.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-whole-path.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-what-dexterm-does-and-never-does.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real-your-limits.md +77 -0
- package/dist/packages/mcp/assets/kb/docs/docs-going-real.md +24 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-paste-a-coin-on-the-web.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-real-mode-your-strategy-s-trades-to-your.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +0 -2
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-how-coins-are-grouped.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-limits.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-what-is-measured.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-what-testing-showed.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-where-to-find-it.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-narratives.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-before-you-start.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-limits.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-options.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-security-model.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-set-it-up-in-obs-step-by-step.md +49 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-the-pnl-scene-paper-and-live.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-the-three-scenes.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay-what-is-redacted.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-overlay.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-receipts-what-your-executor-did.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-safety.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-set-it-up.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-channels.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-signal.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-is.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-will-not-do.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode-where-you-see-it.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-real-mode.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-bubble-map.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-streaming-it.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +2 -2
- package/dist/packages/mcp/assets/kb/entries/autotrade.md +4 -2
- package/dist/packages/mcp/assets/kb/entries/narratives-tracker.md +45 -0
- package/dist/packages/mcp/src/autotrade-tools.js +168 -5
- package/dist/packages/mcp/src/engine/restrictions.js +3 -3
- package/dist/packages/mcp/src/index.js +1 -1
- package/dist/packages/mcp/src/ledger-tools.js +155 -7
- package/dist/packages/mcp/src/server.js +125 -10
- package/dist/packages/mcp/src/spectator-sink.js +27 -0
- package/dist/packages/narratives/src/cluster.js +193 -0
- package/dist/packages/narratives/src/embed.js +184 -0
- package/dist/packages/narratives/src/export.js +60 -0
- package/dist/packages/narratives/src/index.js +10 -0
- package/dist/packages/narratives/src/label.js +48 -0
- package/dist/packages/narratives/src/normalize.js +179 -0
- package/dist/packages/narratives/src/recorder.js +93 -0
- package/dist/packages/narratives/src/tracker.js +328 -0
- package/dist/packages/recorder/src/exit-probes.js +215 -0
- package/dist/packages/recorder/src/index.js +1 -0
- package/dist/packages/recorder/src/recorder.js +11 -1
- package/dist/packages/recorder/src/retention.js +4 -0
- package/dist/packages/recorder/src/schema.js +20 -1
- package/dist/packages/tui/src/keymap.js +12 -0
- package/package.json +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-powershell-experimental.md +0 -39
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// `dexterm autotrade <sub>` (task-1503 AT-1, AUTOTRADE-SPEC).
|
|
1
|
+
// `dexterm autotrade <sub>` (task-1503 AT-1, AUTOTRADE-SPEC). No keys, no wallet, no signer: paper, or real mode
|
|
2
|
+
// (task-1645: trade-fire signals to the user's own executor, `signal` / `receipt` rows).
|
|
2
3
|
//
|
|
3
4
|
// A dispatcher table: each subcommand is one row that lazy-loads its own module under ./autotrade-cmd/, so
|
|
4
5
|
// later cards add a row (run, service, stop, flatten, audit) without editing anyone else's lines.
|
|
@@ -55,8 +56,8 @@ export const SUBCOMMANDS = {
|
|
|
55
56
|
load: () => import("./autotrade-cmd/requests.js").then((m) => m.requestsCmd),
|
|
56
57
|
},
|
|
57
58
|
run: {
|
|
58
|
-
usage: 'dexterm autotrade run <id|file> --paper [--replay TAPE [--ms-per-tick 2000]] [--model M] [--max-minutes N] [--stub] [--raw] [--quiet] [--no-telegram]',
|
|
59
|
-
summary: 'the
|
|
59
|
+
usage: 'dexterm autotrade run <id|file> --paper|--real [--replay TAPE [--ms-per-tick 2000]] [--model M] [--max-minutes N] [--sol-usd N] [--stub] [--raw] [--quiet] [--no-telegram]',
|
|
60
|
+
summary: 'the runner: the screener hands each candidate to your own Claude (runner.json picks the account), every size clamped; ledger in autotrade/runs/; your paired Telegram bot starts with it (dexterm telegram setup). --real (a strategy you armed with signal arm) runs in the FOREGROUND only, on live data: the always-on service runs paper strategies, and while no real run is running NO signal goes out, sells included',
|
|
60
61
|
load: () => import("./autotrade-cmd/run.js").then((m) => m.runCmd),
|
|
61
62
|
},
|
|
62
63
|
'run --all': {
|
|
@@ -99,6 +100,51 @@ export const SUBCOMMANDS = {
|
|
|
99
100
|
summary: 'list the run ledgers; --verify recomputes each hash chain and names the first edited, missing or reordered event',
|
|
100
101
|
load: () => import("./autotrade-cmd/audit.js").then((m) => m.auditCmd),
|
|
101
102
|
},
|
|
103
|
+
signal: {
|
|
104
|
+
usage: 'dexterm autotrade signal status|channels|secret|arm|disarm|caps|kill|unkill|held|release|test|flush|tail|reconcile …',
|
|
105
|
+
summary: 'real mode: a LIVE TRADE FIRE signal per trade to YOUR executor (webhook, --signal-cmd, JSONL, MCP, Telegram copy); arm on your own terminal; hard caps, kill switch; dexterm holds no key',
|
|
106
|
+
load: () => import("./autotrade-cmd/signal.js").then((m) => m.signalCmd),
|
|
107
|
+
},
|
|
108
|
+
receipt: {
|
|
109
|
+
usage: 'dexterm autotrade receipt <nonce|id> executed|rejected|failed [--price USD] [--size-sol N] [--tokens N] [--tx SIG] [--reason TEXT] | receipt list [--json]',
|
|
110
|
+
summary: 'what your executor did with a trade-fire signal, as data only (positions stay unconfirmed until a receipt); the drift between signals and fills',
|
|
111
|
+
load: () => import("./autotrade-cmd/receipt.js").then((m) => m.receiptCmd),
|
|
112
|
+
},
|
|
113
|
+
service: {
|
|
114
|
+
usage: 'dexterm autotrade service install|uninstall|ensure|run|start|stop|status|logs [--home PATH]',
|
|
115
|
+
summary: 'the always-on service: your PAPER strategies (autotrade.json) start at login, restart after a crash, stop on any kill switch and tell you when they cannot run; real mode does not run under it (run --real, in the foreground)',
|
|
116
|
+
load: () => import("./autotrade-cmd/service.js").then((m) => m.serviceCmd),
|
|
117
|
+
},
|
|
118
|
+
forward: {
|
|
119
|
+
usage: 'dexterm autotrade forward start <id> [--hours H] [--trades N] [--bankroll-usd N] | status [<id>] | stop <id> | report <id> [--json]',
|
|
120
|
+
summary: 'forward test: the strategy on the LIVE tape, on paper, own bankroll and run file, for hours or N trades; the report sets it next to its backtest',
|
|
121
|
+
load: () => import("./autotrade-cmd/forward.js").then((m) => m.forwardCmd),
|
|
122
|
+
},
|
|
123
|
+
journey: {
|
|
124
|
+
usage: 'dexterm autotrade journey [<id>] [--json]',
|
|
125
|
+
summary: 'where a strategy stands on the ladder draft → backtested → forward-tested → always-on → real, and what the next rung needs',
|
|
126
|
+
load: () => import("./autotrade-cmd/journey.js").then((m) => m.journeyCmd),
|
|
127
|
+
},
|
|
128
|
+
promote: {
|
|
129
|
+
usage: 'dexterm autotrade promote <id> --to always-on|real | list | show <pp-id> | apply <pp-id> | dismiss <pp-id>',
|
|
130
|
+
summary: 'a promotion with its evidence card: always-on needs your y on a real terminal; real needs the evidence floor (a completed forward test with minForwardTrades closed paper trades + a backtest) and hands over to signal arm (your caps, a typed phrase); no rung is skipped',
|
|
131
|
+
load: () => import("./autotrade-cmd/journey.js").then((m) => m.promoteCmd),
|
|
132
|
+
},
|
|
133
|
+
limits: {
|
|
134
|
+
usage: 'dexterm autotrade limits show|set|diff|reset|accept|breaker reset|explain|schema …',
|
|
135
|
+
summary: 'real mode: every limit is yours (per trade SOL or % of book, per day, open positions, exposure, loss limits, breaker, rate, slippage, TTL, executor silence), global / per mode / per strategy / per account, the stricter wins; raising is typed on your own terminal',
|
|
136
|
+
load: () => import("./autotrade-cmd/limits.js").then((m) => m.limitsCmd),
|
|
137
|
+
},
|
|
138
|
+
executor: {
|
|
139
|
+
usage: 'dexterm autotrade executor status [--json] | heartbeat [--name N] [--account ID] [--dry-run] [--nonce N] | kit --out DIR',
|
|
140
|
+
summary: 'real mode: is YOUR executor alive (heartbeat, last receipt, lag)? A silent executor pauses new buy signals; `kit --out DIR` copies the dry-run reference executors (webhook, signal-cmd, JSONL tail)',
|
|
141
|
+
load: () => import("./autotrade-cmd/executor.js").then((m) => m.executorCmd),
|
|
142
|
+
},
|
|
143
|
+
report: {
|
|
144
|
+
usage: 'dexterm autotrade report [--period day|week] [--strategy ID] [--json] [--telegram]',
|
|
145
|
+
summary: 'the digest per strategy and mode: trades, win rate with its interval, net, drawdown, refusals, limits hit, executor health, paper vs real fills, an honest small-sample note',
|
|
146
|
+
load: () => import("./autotrade-cmd/report.js").then((m) => m.reportCmd),
|
|
147
|
+
},
|
|
102
148
|
extract: {
|
|
103
149
|
usage: 'dexterm autotrade extract --out FILE [--db PATH] [--max-mints N] [--min-peak-liq USD] [--span-hours H]',
|
|
104
150
|
summary: 'save a replay tape from your recorder history (read-only) to a file you can backtest offline',
|
|
@@ -107,7 +153,7 @@ export const SUBCOMMANDS = {
|
|
|
107
153
|
};
|
|
108
154
|
export function autotradeHelp() {
|
|
109
155
|
return [
|
|
110
|
-
'dexterm autotrade: strategies, backtests and the
|
|
156
|
+
'dexterm autotrade: strategies, backtests and the runner. PAPER by default; mode real sends a LIVE TRADE FIRE signal per trade to YOUR executor (`signal arm`, on your own terminal; dexterm holds no key). Not financial advice.',
|
|
111
157
|
'',
|
|
112
158
|
...Object.values(SUBCOMMANDS).map((s) => ` ${s.usage}\n ${s.summary}`),
|
|
113
159
|
'',
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// task-1822: `dexterm classifier …`: pick and manage the classification model (Kev local, hosted Jev with your own
|
|
2
|
+
// key, JEV students, LLM baselines, or the built-in heuristic). Outputs are model estimates, never advice.
|
|
3
|
+
// Nothing downloads without a typed yes (or --yes) for that model; nothing calls the network without a key/consent.
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import readline from 'node:readline';
|
|
8
|
+
import { CATALOG, COIN_RISK, EXTERNAL_MODELS, PROVIDERS, PROVIDER_IDS, catalogEntry, classify, classifyBatch, coinDossier, describeHardware, describeResult, detectHardware, dextermHome, fits, freeBytes, fromReasonRunner, gb, isInstalled, loadClassifierConfig, modelPath, modelsDir, providerFromConfig, pullModel, readTypesafeKey, recommendTier, removeTypesafeKey, saveClassifierConfig, serveArgv, writeTypesafeKey, } from '../../classifier/src/index.js';
|
|
9
|
+
import { spawnClaudeRunner } from '../../packs/src/index.js';
|
|
10
|
+
export const CLASSIFIER_USAGE = `dexterm classifier [<command>]
|
|
11
|
+
list providers, local models (installed / size / licence / fits this machine) and the current pick
|
|
12
|
+
pick <provider> [model] heuristic | kev-local [kev-0.8b-q8|kev-4b-q4|…] | jev-hosted | jev-open | claude | ollama-llm
|
|
13
|
+
pull <model> [--yes] download a Kev GGUF (shows size + source, asks first, checks disk, verifies sha256)
|
|
14
|
+
serve [--model M] [--port P] [--threads N] run llama-server for the picked Kev model on 127.0.0.1 (foreground)
|
|
15
|
+
doctor [--json] hardware (RAM, CPU, GPU/VRAM), the recommended tier, provider readiness
|
|
16
|
+
bench [--n 20] time the current provider on N synthetic coins (latency per coin, per 1,000)
|
|
17
|
+
try classify one example coin with the current provider (shows the labelled estimate)
|
|
18
|
+
key set|clear|status your own TypeSafe API key for hosted Jev (stored 0600 under ~/.dexterm/keys, never printed)
|
|
19
|
+
Every answer is a model estimate with the model id and a calibration note; never advice. Docs: /docs/classifier/`;
|
|
20
|
+
function flags(argv, bools) {
|
|
21
|
+
const pos = [];
|
|
22
|
+
const opts = {};
|
|
23
|
+
for (let i = 0; i < argv.length; i++) {
|
|
24
|
+
const a = argv[i];
|
|
25
|
+
if (a.startsWith('--')) {
|
|
26
|
+
const k = a.slice(2);
|
|
27
|
+
if (bools.includes(k) || i + 1 >= argv.length)
|
|
28
|
+
opts[k] = true;
|
|
29
|
+
else
|
|
30
|
+
opts[k] = argv[++i];
|
|
31
|
+
}
|
|
32
|
+
else
|
|
33
|
+
pos.push(a);
|
|
34
|
+
}
|
|
35
|
+
return { pos, opts };
|
|
36
|
+
}
|
|
37
|
+
async function ask(io, question) {
|
|
38
|
+
if (!io.stdin.isTTY)
|
|
39
|
+
return '';
|
|
40
|
+
const rl = readline.createInterface({ input: io.stdin, output: io.stdout });
|
|
41
|
+
try {
|
|
42
|
+
return await new Promise((r) => rl.question(question, r));
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
rl.close();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Read a secret from the TTY without echoing it. Non-TTY: read one line from stdin (for `… | dexterm classifier key set`). */
|
|
49
|
+
async function readSecret(io, prompt) {
|
|
50
|
+
if (!io.stdin.isTTY) {
|
|
51
|
+
let s = '';
|
|
52
|
+
for await (const c of io.stdin) {
|
|
53
|
+
s += c.toString();
|
|
54
|
+
if (s.includes('\n'))
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
return s.split('\n')[0];
|
|
58
|
+
}
|
|
59
|
+
io.stdout.write(prompt);
|
|
60
|
+
const stdin = io.stdin;
|
|
61
|
+
stdin.setRawMode?.(true);
|
|
62
|
+
stdin.resume();
|
|
63
|
+
return await new Promise((resolve) => {
|
|
64
|
+
let s = '';
|
|
65
|
+
const on = (b) => {
|
|
66
|
+
for (const ch of b.toString('utf8')) {
|
|
67
|
+
if (ch === '\r' || ch === '\n' || ch === '\u0004') {
|
|
68
|
+
stdin.off('data', on);
|
|
69
|
+
stdin.setRawMode?.(false);
|
|
70
|
+
stdin.pause();
|
|
71
|
+
io.stdout.write('\n');
|
|
72
|
+
resolve(s);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (ch === '\u0003') {
|
|
76
|
+
stdin.off('data', on);
|
|
77
|
+
stdin.setRawMode?.(false);
|
|
78
|
+
stdin.pause();
|
|
79
|
+
io.stdout.write('\n');
|
|
80
|
+
resolve('');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (ch === '\u007f')
|
|
84
|
+
s = s.slice(0, -1);
|
|
85
|
+
else
|
|
86
|
+
s += ch;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
stdin.on('data', on);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function deps(c, io) {
|
|
93
|
+
return { env: io.env, fetch: io.fetch, claudeRunner: fromReasonRunner(spawnClaudeRunner, c.llm.claudeModel) };
|
|
94
|
+
}
|
|
95
|
+
/** Where llama-server is: $DEXTERM_LLAMA_SERVER, else on PATH. */
|
|
96
|
+
export function llamaServerBin(env) {
|
|
97
|
+
if (env.DEXTERM_LLAMA_SERVER && fs.existsSync(env.DEXTERM_LLAMA_SERVER))
|
|
98
|
+
return env.DEXTERM_LLAMA_SERVER;
|
|
99
|
+
for (const dir of (env.PATH ?? '').split(path.delimiter)) {
|
|
100
|
+
const p = path.join(dir, 'llama-server');
|
|
101
|
+
try {
|
|
102
|
+
fs.accessSync(p, fs.constants.X_OK);
|
|
103
|
+
return p;
|
|
104
|
+
}
|
|
105
|
+
catch { /* next */ }
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Does this llama-server serve decision models (`/v1/systemone`, llama.cpp PR #29818, 2026-10-02)? Older builds (e.g.
|
|
111
|
+
* Homebrew's 0.4.1) start fine and then 404 every request, so `serve` and `doctor` check the help text first.
|
|
112
|
+
*/
|
|
113
|
+
export async function llamaSupportsDecisions(bin, exec) {
|
|
114
|
+
const run = exec ?? ((cmd, args) => new Promise((resolve) => {
|
|
115
|
+
const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
116
|
+
let out = '';
|
|
117
|
+
const t = setTimeout(() => child.kill('SIGKILL'), 8000);
|
|
118
|
+
child.stdout.on('data', (d) => (out += d));
|
|
119
|
+
child.stderr.on('data', (d) => (out += d));
|
|
120
|
+
child.on('error', () => { clearTimeout(t); resolve(''); });
|
|
121
|
+
child.on('close', () => { clearTimeout(t); resolve(out); });
|
|
122
|
+
}));
|
|
123
|
+
try {
|
|
124
|
+
return /decision/i.test(await run(bin, ['--help']));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const LLAMA_TOO_OLD = 'this llama-server has no /v1/systemone (decision models need a llama.cpp build from 2026-10-02 or later): update llama.cpp, or point DEXTERM_LLAMA_SERVER at a newer llama-server';
|
|
131
|
+
export async function classifierCommand(argv, io) {
|
|
132
|
+
const out = (s) => io.stdout.write(s + '\n');
|
|
133
|
+
const err = (s) => io.stderr.write(s + '\n');
|
|
134
|
+
const loaded = loadClassifierConfig(io.env);
|
|
135
|
+
for (const e of loaded.errors)
|
|
136
|
+
err(`classifier.json: ${e}`);
|
|
137
|
+
const c = loaded.config;
|
|
138
|
+
const home = dextermHome(io.env);
|
|
139
|
+
const sub = argv[0] ?? 'list';
|
|
140
|
+
const { pos, opts } = flags(argv.slice(1), ['yes', 'json']);
|
|
141
|
+
if (sub === 'help' || sub === '--help' || sub === '-h') {
|
|
142
|
+
out(CLASSIFIER_USAGE);
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
if (sub === 'list') {
|
|
146
|
+
const hw = await detectHardware({ exec: io.exec });
|
|
147
|
+
out(`current: ${c.provider}${c.provider === 'kev-local' ? ` (${c.kev.model})` : ''} · shipped default model: ${c.default}`);
|
|
148
|
+
out('\nproviders:');
|
|
149
|
+
for (const p of PROVIDERS)
|
|
150
|
+
out(` ${p.id.padEnd(11)} ${p.title} [network: ${p.network}; needs ${p.needs}]`);
|
|
151
|
+
out('\nlocal models (llama.cpp GGUF, pinned revisions, sha256-verified):');
|
|
152
|
+
for (const e of CATALOG) {
|
|
153
|
+
const inst = isInstalled(home, e) ? 'installed' : 'not downloaded';
|
|
154
|
+
out(` ${e.id.padEnd(12)} ${gb(e.bytes).padStart(7)} ${e.license} ${inst.padEnd(14)} ${fits(e.id, hw) ? 'fits this machine' : 'too big for this machine'} (${e.authorNote})`);
|
|
155
|
+
}
|
|
156
|
+
out('\nlisted, not pulled by dexterm:');
|
|
157
|
+
for (const x of EXTERNAL_MODELS)
|
|
158
|
+
out(` ${x.id.padEnd(12)} ${x.repo} (${x.license}): ${x.note}`);
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
if (sub === 'pick') {
|
|
162
|
+
const id = pos[0];
|
|
163
|
+
if (!id || !PROVIDER_IDS.includes(id)) {
|
|
164
|
+
err(`pick one of: ${PROVIDER_IDS.join(', ')}`);
|
|
165
|
+
return 2;
|
|
166
|
+
}
|
|
167
|
+
const next = { ...c, provider: id };
|
|
168
|
+
if (id === 'kev-local') {
|
|
169
|
+
const model = pos[1] ?? c.kev.model;
|
|
170
|
+
try {
|
|
171
|
+
catalogEntry(model);
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
err(e.message);
|
|
175
|
+
return 2;
|
|
176
|
+
}
|
|
177
|
+
next.kev = { ...c.kev, model };
|
|
178
|
+
if (!isInstalled(home, catalogEntry(model)))
|
|
179
|
+
out(`note: ${model} is not downloaded yet: dexterm classifier pull ${model}. Until it is served, answers fall back to the heuristic.`);
|
|
180
|
+
}
|
|
181
|
+
if (id === 'jev-hosted' && !readTypesafeKey(io.env))
|
|
182
|
+
out('note: no TypeSafe key yet: dexterm classifier key set. Until then answers fall back to the heuristic.');
|
|
183
|
+
const p = saveClassifierConfig(next, io.env);
|
|
184
|
+
out(`classifier.provider = ${id}${id === 'kev-local' ? ` (${next.kev.model})` : ''} (${p})`);
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
if (sub === 'pull') {
|
|
188
|
+
const id = pos[0] ?? c.kev.model;
|
|
189
|
+
let e;
|
|
190
|
+
try {
|
|
191
|
+
e = catalogEntry(id);
|
|
192
|
+
}
|
|
193
|
+
catch (x) {
|
|
194
|
+
err(x.message);
|
|
195
|
+
return 2;
|
|
196
|
+
}
|
|
197
|
+
if (isInstalled(home, e)) {
|
|
198
|
+
out(`${e.id} is already installed: ${modelPath(home, e)}`);
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
const free = (io.free ?? freeBytes)(modelsDir(home));
|
|
202
|
+
out(`${e.title}: ${gb(e.bytes)} from huggingface.co/${e.repo} (revision ${e.revision.slice(0, 8)}, ${e.license}, base ${e.base})`);
|
|
203
|
+
out(`to ${modelsDir(home)} · ${gb(free)} free · sha256 ${e.sha256.slice(0, 16)}… checked before use`);
|
|
204
|
+
let yes = opts.yes === true;
|
|
205
|
+
if (!yes) {
|
|
206
|
+
const a = (await ask(io, 'Download it now? Type yes to continue: ')).trim().toLowerCase();
|
|
207
|
+
yes = a === 'yes';
|
|
208
|
+
}
|
|
209
|
+
if (!yes) {
|
|
210
|
+
out('not downloaded (no consent). Re-run with --yes to skip the question.');
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
let lastPct = -1;
|
|
214
|
+
try {
|
|
215
|
+
const r = await pullModel(e.id, { home, consent: true, fetch: io.pullFetch, free: io.free, onProgress: (d, t) => { const pct = Math.floor((d / t) * 20) * 5; if (pct !== lastPct) {
|
|
216
|
+
lastPct = pct;
|
|
217
|
+
io.stdout.write(`\r ${pct}%`);
|
|
218
|
+
} } });
|
|
219
|
+
io.stdout.write('\n');
|
|
220
|
+
saveClassifierConfig({ ...c, consent: { ...c.consent, [e.id]: new Date().toISOString() } }, io.env);
|
|
221
|
+
out(`verified ${r.sha256.slice(0, 16)}… → ${r.path}`);
|
|
222
|
+
out(`next: dexterm classifier pick kev-local ${e.id} && dexterm classifier serve`);
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
catch (x) {
|
|
226
|
+
io.stdout.write('\n');
|
|
227
|
+
err(`pull failed: ${x.message}`);
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (sub === 'serve') {
|
|
232
|
+
const id = typeof opts.model === 'string' ? opts.model : c.kev.model;
|
|
233
|
+
let e;
|
|
234
|
+
try {
|
|
235
|
+
e = catalogEntry(id);
|
|
236
|
+
}
|
|
237
|
+
catch (x) {
|
|
238
|
+
err(x.message);
|
|
239
|
+
return 2;
|
|
240
|
+
}
|
|
241
|
+
if (!isInstalled(home, e)) {
|
|
242
|
+
err(`${id} is not downloaded: dexterm classifier pull ${id}`);
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
const bin = llamaServerBin(io.env);
|
|
246
|
+
if (!bin) {
|
|
247
|
+
err('llama-server not found. Install llama.cpp (a build with /v1/systemone, 2026-10-02 or later) or set DEXTERM_LLAMA_SERVER.');
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
if (!(await llamaSupportsDecisions(bin, io.exec))) {
|
|
251
|
+
err(LLAMA_TOO_OLD);
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
const port = Number(typeof opts.port === 'string' ? opts.port : new URL(c.kev.endpoint).port || 8031);
|
|
255
|
+
const threads = typeof opts.threads === 'string' ? Number(opts.threads) : c.kev.threads ?? undefined;
|
|
256
|
+
const args = serveArgv(e, modelPath(home, e), port, threads);
|
|
257
|
+
out(`serving ${e.id} on http://127.0.0.1:${port} (Ctrl+C to stop): ${path.basename(bin)} ${args.join(' ')}`);
|
|
258
|
+
return await new Promise((resolve) => {
|
|
259
|
+
const child = spawn(bin, args, { stdio: 'inherit' });
|
|
260
|
+
child.on('error', (x) => { err(`llama-server: ${x.message}`); resolve(1); });
|
|
261
|
+
child.on('close', (code) => resolve(code ?? 0));
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (sub === 'doctor' || sub === 'status') {
|
|
265
|
+
const hw = await detectHardware({ exec: io.exec });
|
|
266
|
+
const rec = recommendTier(hw);
|
|
267
|
+
let provStatus;
|
|
268
|
+
try {
|
|
269
|
+
provStatus = await providerFromConfig(c, c.provider, deps(c, io)).status();
|
|
270
|
+
}
|
|
271
|
+
catch (x) {
|
|
272
|
+
provStatus = { ready: false, detail: x.message };
|
|
273
|
+
}
|
|
274
|
+
const llama = llamaServerBin(io.env);
|
|
275
|
+
const llamaOk = llama ? await llamaSupportsDecisions(llama, io.exec) : false;
|
|
276
|
+
if (opts.json === true) {
|
|
277
|
+
out(JSON.stringify({ hardware: hw, recommendation: rec, provider: c.provider, model: c.provider === 'kev-local' ? c.kev.model : null, ready: provStatus.ready, detail: provStatus.detail, llamaServer: Boolean(llama), llamaServerDecisions: llamaOk, typesafeKey: Boolean(readTypesafeKey(io.env)) }, null, 2));
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
out(`hardware: ${describeHardware(hw)}`);
|
|
281
|
+
out(`recommended: ${rec.model ?? 'the built-in heuristic'}${rec.bestQuality ? ` (best quality here: ${rec.bestQuality})` : ''}: ${rec.why}`);
|
|
282
|
+
out(`provider: ${c.provider}${c.provider === 'kev-local' ? ` (${c.kev.model})` : ''}: ${provStatus.ready ? 'ready' : 'not ready'}: ${provStatus.detail}`);
|
|
283
|
+
out(`llama-server: ${!llama ? 'not found (needed for kev-local)' : llamaOk ? 'found, serves decision models' : `found but too old: ${LLAMA_TOO_OLD}`} · TypeSafe key: ${readTypesafeKey(io.env) ? 'configured' : 'none (hosted Jev is optional)'}`);
|
|
284
|
+
if (c.fallback)
|
|
285
|
+
out('fallback: the heuristic answers when the provider is unreachable (labelled as a fallback)');
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
if (sub === 'bench') {
|
|
289
|
+
const n = Math.max(1, Math.min(500, Number(opts.n ?? 20) || 20));
|
|
290
|
+
let provider;
|
|
291
|
+
try {
|
|
292
|
+
provider = providerFromConfig(c, c.provider, deps(c, io));
|
|
293
|
+
}
|
|
294
|
+
catch (x) {
|
|
295
|
+
err(x.message);
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
const coins = Array.from({ length: n }, (_, i) => coinDossier({ mint: `Bench${String(i).padStart(4, '0')}`.padEnd(32, '1'), symbol: `B${i}`, age_min: 5 + i * 7, liquidity_usd: 2000 + i * 1500, mcap_usd: 9000 + i * 7000, holders: 50 + i * 20, top10_pct: 10 + (i % 40), mint_authority: i % 9 === 0, freeze_authority: i % 13 === 0 }));
|
|
299
|
+
const r = await classifyBatch(provider, COIN_RISK, coins, { fallback: null });
|
|
300
|
+
const ok = r.results.filter((x) => !('error' in x));
|
|
301
|
+
const per = ok.length ? r.ms / ok.length : NaN;
|
|
302
|
+
out(`${provider.id} ${provider.model()}: ${ok.length}/${n} answered in ${(r.ms / 1000).toFixed(1)} s · ${per.toFixed(0)} ms per coin · ~${((per * 1000) / 60_000).toFixed(1)} min per 1,000 coins (one at a time)`);
|
|
303
|
+
const failed = r.results.find((x) => 'error' in x);
|
|
304
|
+
if (failed)
|
|
305
|
+
err(`first failure: ${failed.error}`);
|
|
306
|
+
return ok.length ? 0 : 1;
|
|
307
|
+
}
|
|
308
|
+
if (sub === 'key') {
|
|
309
|
+
const action = pos[0] ?? 'status';
|
|
310
|
+
if (action === 'status') {
|
|
311
|
+
out(readTypesafeKey(io.env) ? 'TypeSafe key: configured (not shown)' : 'TypeSafe key: none');
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
if (action === 'clear') {
|
|
315
|
+
out(removeTypesafeKey(io.env) ? 'TypeSafe key removed' : 'no key file to remove');
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
if (action === 'set') {
|
|
319
|
+
const k = await readSecret(io, 'Paste your TypeSafe API key (input hidden): ');
|
|
320
|
+
if (!k.trim()) {
|
|
321
|
+
err('no key entered');
|
|
322
|
+
return 1;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const p = writeTypesafeKey(k, io.env);
|
|
326
|
+
out(`saved to ${p} (0600). It is only sent to ${c.jev.endpoint}.`);
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
catch (x) {
|
|
330
|
+
err(x.message);
|
|
331
|
+
return 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
err('key set|clear|status');
|
|
335
|
+
return 2;
|
|
336
|
+
}
|
|
337
|
+
if (sub === 'try') {
|
|
338
|
+
// A one-off classification of a hand-written coin (no network unless the picked provider is remote).
|
|
339
|
+
let provider;
|
|
340
|
+
try {
|
|
341
|
+
provider = providerFromConfig(c, c.provider, deps(c, io));
|
|
342
|
+
}
|
|
343
|
+
catch (x) {
|
|
344
|
+
err(x.message);
|
|
345
|
+
return 1;
|
|
346
|
+
}
|
|
347
|
+
const r = await classify(provider, COIN_RISK, coinDossier({ mint: 'Example11111111111111111111111111', symbol: 'EXMPL', liquidity_usd: 12000, mcap_usd: 40000, age_min: 20, holders: 300, top10_pct: 35, freeze_authority: false, mint_authority: false }), { fallback: c.fallback ? undefined : null });
|
|
348
|
+
out(describeResult(r));
|
|
349
|
+
return 0;
|
|
350
|
+
}
|
|
351
|
+
err(`unknown classifier command: ${sub}\n${CLASSIFIER_USAGE}`);
|
|
352
|
+
return 2;
|
|
353
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// task-1822: keep `state.classifierByMint` filled for the `rugp` watch-rule field. It does nothing (no model call,
|
|
2
|
+
// no network) unless an ENABLED rule mentions `rugp`. Then, each rules tick:
|
|
3
|
+
// - provider `heuristic`: every rule row is scored at once (local arithmetic, H1 p(rug)).
|
|
4
|
+
// - any other provider: one coin at a time in the background, each re-scored at most every RESCORE_MS, the open
|
|
5
|
+
// token first; a failure falls back to the heuristic (labelled by the provider id stored with the estimate).
|
|
6
|
+
// Estimates are model output for alerts only: nothing here trades, sizes, arms or writes config.
|
|
7
|
+
export const RESCORE_MS = 10 * 60_000;
|
|
8
|
+
const LIVE = new WeakMap();
|
|
9
|
+
export function rulesUseRugp(state) {
|
|
10
|
+
return !!state.rulesUi?.engine.rules.some((r) => r.enabled && /\brugp\b/i.test(r.whenText));
|
|
11
|
+
}
|
|
12
|
+
/** The dossier fields a rule row carries (the screener row is structurally a RuleRow). */
|
|
13
|
+
export function coinInputFromRuleRow(r) {
|
|
14
|
+
const n = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
15
|
+
const b = (v) => (v === 1 ? true : v === 0 ? false : null);
|
|
16
|
+
return {
|
|
17
|
+
mint: r.mint, symbol: r.symbol ?? null, age_min: n(r.ageMin), liquidity_usd: n(r.liq), mcap_usd: n(r.mcap),
|
|
18
|
+
vol5m_usd: n(r.vol5m), vol1h_usd: n(r.vol1h), holders: n(r.holders), top10_pct: n(r.top10), dev_pct: n(r.dev),
|
|
19
|
+
organic_score: n(r.org), change5m_pct: n(r.chg?.m5), change1h_pct: n(r.chg?.h1), buys1h: n(r.buys), sells1h: n(r.sells),
|
|
20
|
+
mint_authority: b(r.mintable), freeze_authority: b(r.freezable), launchpad: r.launchpad ?? null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function load(state, live, env) {
|
|
24
|
+
const C = await import('../../classifier/src/index.js');
|
|
25
|
+
const cfg = C.loadClassifierConfig(env).config;
|
|
26
|
+
live.fallback = cfg.fallback;
|
|
27
|
+
live.calibration = cfg.calibration;
|
|
28
|
+
try {
|
|
29
|
+
live.provider = C.providerFromConfig(cfg, cfg.provider, { env });
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
live.provider = C.heuristicProvider();
|
|
33
|
+
}
|
|
34
|
+
void state;
|
|
35
|
+
}
|
|
36
|
+
/** One step; call from the rules tick with the rows it is about to evaluate. Never throws. */
|
|
37
|
+
export function classifierLiveTick(state, rows, now, env = process.env) {
|
|
38
|
+
if (!rows.length || !rulesUseRugp(state))
|
|
39
|
+
return;
|
|
40
|
+
let live = LIVE.get(state);
|
|
41
|
+
if (!live) {
|
|
42
|
+
live = { provider: null, loading: null, inflight: false, fallback: true, calibration: {} };
|
|
43
|
+
LIVE.set(state, live);
|
|
44
|
+
}
|
|
45
|
+
if (!live.provider) {
|
|
46
|
+
live.loading ??= load(state, live, env).catch(() => { live.loading = null; });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const map = (state.classifierByMint ??= new Map());
|
|
50
|
+
const due = (m) => { const e = map.get(m); return !e || now - e.at >= RESCORE_MS; };
|
|
51
|
+
const provider = live.provider;
|
|
52
|
+
if (live.inflight)
|
|
53
|
+
return;
|
|
54
|
+
live.inflight = true;
|
|
55
|
+
const open = state.live?.token?.view.mint ?? null;
|
|
56
|
+
const queue = rows.filter((r) => due(r.mint)).sort((a, b) => (a.mint === open ? -1 : b.mint === open ? 1 : 0));
|
|
57
|
+
const batch = provider.id === 'heuristic' ? queue : queue.slice(0, 1);
|
|
58
|
+
if (!batch.length) {
|
|
59
|
+
live.inflight = false;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
void (async () => {
|
|
63
|
+
try {
|
|
64
|
+
const C = await import('../../classifier/src/index.js');
|
|
65
|
+
for (const r of batch) {
|
|
66
|
+
try {
|
|
67
|
+
const res = await C.classify(provider, C.COIN_RISK, C.coinDossier(coinInputFromRuleRow(r)), { calibration: live.calibration, fallback: live.fallback ? undefined : null });
|
|
68
|
+
const p = res.answers.drain_6h?.probs.yes;
|
|
69
|
+
if (typeof p === 'number')
|
|
70
|
+
map.set(r.mint, { p, model: `${res.provider} ${res.model}`, at: now });
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
map.set(r.mint, { p: NaN, model: 'error', at: now });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
live.inflight = false;
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
}
|