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
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// `dexterm overlay` — the STREAMER OVERLAY (task-1427): OBS browser-source scenes fed by the spectator stream.
|
|
2
|
+
//
|
|
3
|
+
// dexterm overlay [--scene activity|coin|pnl|all]… [--theme cyberspace|ice|neon]
|
|
4
|
+
// [--replay FILE [--speed N] [--loop]] [--ledger TRADE_ID [--sol-usd N]]
|
|
5
|
+
// [--port N] [--ticket-ttl MIN] [--json]
|
|
6
|
+
//
|
|
7
|
+
// Data: the live, read-only, redacted spectator stream (the one `dexterm --spectate-mcp` or `dexterm agent serve
|
|
8
|
+
// --spectate` opens; found through spectator.json, token sent in the hello, loopback only), or a saved recording
|
|
9
|
+
// with --replay (every scene then carries a SAMPLE DATA tag). The PnL scene is the agent's PAPER fills on the coin
|
|
10
|
+
// (stamped PAPER), or with --ledger one round trip of the user's own trade ledger (PAPER or LIVE by wallet mode).
|
|
11
|
+
// Scenes: a loopback page per scene with a single-use ticket → token (overlay/server.ts). No network beyond
|
|
12
|
+
// loopback, no inbound control path: the overlay watches; nothing here can arm, sign, size, trade or raise a cap.
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { loadSpectatorRecording, sanitizeDetail } from '../../agents/src/index.js';
|
|
15
|
+
import { spectatorInfoPath } from "./spectate-cmd.js";
|
|
16
|
+
import { emptyOverlayState, foldOverlay, activityView, coinView } from "./overlay/model.js";
|
|
17
|
+
import { pnlView, ledgerOverlayInput } from "./overlay/pnl.js";
|
|
18
|
+
import { createOverlayServer, OVERLAY_PAGES, OVERLAY_TICKET_TTL_MS } from "./overlay/server.js";
|
|
19
|
+
import { OVERLAY_THEME_NAMES } from "./overlay/page.js";
|
|
20
|
+
export const OVERLAY_USAGE = `dexterm overlay [options] streamer overlay: OBS browser-source scenes of the agent's session
|
|
21
|
+
--scene NAME activity | coin | pnl | all (repeatable; default: the three scenes)
|
|
22
|
+
--theme NAME ${OVERLAY_THEME_NAMES.join(' | ')} (default cyberspace)
|
|
23
|
+
--replay FILE play a saved spectator recording instead of the live stream (scenes say SAMPLE DATA)
|
|
24
|
+
--speed N replay speed (default 1) · --loop replay again from the start when it ends
|
|
25
|
+
--ledger TRADE_ID PnL scene from your own trade ledger (PAPER or LIVE stamp by wallet mode) instead of the
|
|
26
|
+
agent's paper fills · --sol-usd N for a USD-booked trade
|
|
27
|
+
--port N fixed loopback port (default random) · --ticket-ttl MIN minutes a URL stays valid (default 10)
|
|
28
|
+
--json print the scene URLs as JSON
|
|
29
|
+
Each URL works ONCE (paste it into an OBS Browser source). Press n for fresh URLs, q to quit.
|
|
30
|
+
Loopback only · read-only · addresses redacted · every scene shows its data age.
|
|
31
|
+
Full docs: https://dexterm.xyz/docs/overlay/`;
|
|
32
|
+
export function parseOverlayArgs(rest) {
|
|
33
|
+
const o = { scenes: [], theme: 'cyberspace', replay: null, speed: 1, loop: false, ledger: null, solUsd: null, port: undefined, ttlMin: OVERLAY_TICKET_TTL_MS / 60_000, json: false };
|
|
34
|
+
for (let i = 0; i < rest.length; i++) {
|
|
35
|
+
const a = rest[i];
|
|
36
|
+
const val = () => { const v = rest[++i]; return v === undefined || v.startsWith('--') ? null : v; };
|
|
37
|
+
switch (a) {
|
|
38
|
+
case '--scene': {
|
|
39
|
+
const v = val();
|
|
40
|
+
if (!v || !OVERLAY_PAGES.includes(v))
|
|
41
|
+
return `--scene is one of ${OVERLAY_PAGES.join(', ')}`;
|
|
42
|
+
if (!o.scenes.includes(v))
|
|
43
|
+
o.scenes.push(v);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case '--theme': {
|
|
47
|
+
const v = val();
|
|
48
|
+
if (!v || !OVERLAY_THEME_NAMES.includes(v))
|
|
49
|
+
return `--theme is one of ${OVERLAY_THEME_NAMES.join(', ')}`;
|
|
50
|
+
o.theme = v;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case '--replay': {
|
|
54
|
+
const v = val();
|
|
55
|
+
if (!v)
|
|
56
|
+
return '--replay needs a FILE';
|
|
57
|
+
o.replay = v;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case '--speed': {
|
|
61
|
+
const n = Number(val());
|
|
62
|
+
if (!(n > 0 && n <= 100))
|
|
63
|
+
return '--speed is a number from 0 to 100';
|
|
64
|
+
o.speed = n;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case '--loop':
|
|
68
|
+
o.loop = true;
|
|
69
|
+
break;
|
|
70
|
+
case '--ledger': {
|
|
71
|
+
const v = val();
|
|
72
|
+
if (!v)
|
|
73
|
+
return '--ledger needs a TRADE_ID (from `dexterm ledger trades`)';
|
|
74
|
+
o.ledger = v;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case '--sol-usd': {
|
|
78
|
+
const n = Number(val());
|
|
79
|
+
if (!(n > 0))
|
|
80
|
+
return '--sol-usd is a positive number';
|
|
81
|
+
o.solUsd = n;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case '--port': {
|
|
85
|
+
const n = Number(val());
|
|
86
|
+
if (!Number.isInteger(n) || n < 1024 || n > 65535)
|
|
87
|
+
return '--port is an integer from 1024 to 65535';
|
|
88
|
+
o.port = n;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case '--ticket-ttl': {
|
|
92
|
+
const n = Number(val());
|
|
93
|
+
if (!(n >= 1 && n <= 240))
|
|
94
|
+
return '--ticket-ttl is minutes from 1 to 240';
|
|
95
|
+
o.ttlMin = n;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case '--json':
|
|
99
|
+
o.json = true;
|
|
100
|
+
break;
|
|
101
|
+
default: return `unknown option ${JSON.stringify(a)}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!o.scenes.length)
|
|
105
|
+
o.scenes = ['activity', 'coin', 'pnl'];
|
|
106
|
+
if (o.loop && !o.replay)
|
|
107
|
+
return '--loop only applies to --replay';
|
|
108
|
+
return o;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Folds events into the overlay state and pushes the scene views to the server. Public for the tests (they drive it
|
|
112
|
+
* with fixture events and a fake clock); the command wires it to the stream or a recording.
|
|
113
|
+
*/
|
|
114
|
+
export class OverlayEngine {
|
|
115
|
+
state;
|
|
116
|
+
server;
|
|
117
|
+
now;
|
|
118
|
+
pnlInput;
|
|
119
|
+
pnlEveryMs;
|
|
120
|
+
lastPnlAt = -Infinity;
|
|
121
|
+
lastPnlKey = '';
|
|
122
|
+
constructor(o) {
|
|
123
|
+
this.state = emptyOverlayState(o.sample);
|
|
124
|
+
this.server = o.server;
|
|
125
|
+
this.now = o.now ?? (() => Date.now());
|
|
126
|
+
this.pnlInput = o.pnlInput;
|
|
127
|
+
this.pnlEveryMs = o.pnlEveryMs ?? 10_000;
|
|
128
|
+
}
|
|
129
|
+
/** Start over (a looped replay): the scenes forget every coin, fill and call. */
|
|
130
|
+
reset() { Object.assign(this.state, emptyOverlayState(this.state.sample)); }
|
|
131
|
+
/** Fold one event read off the stream or a recording (untrusted data: the detail is re-validated first). */
|
|
132
|
+
feed(raw) {
|
|
133
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
134
|
+
return false;
|
|
135
|
+
const e = raw;
|
|
136
|
+
if (typeof e.phase !== 'string' || typeof e.t !== 'number' || !Number.isFinite(e.t))
|
|
137
|
+
return false;
|
|
138
|
+
// the stream and saved files are redacted (mints are head…tail), so the detail is validated in that shape
|
|
139
|
+
const ev = { ...e, detail: sanitizeDetail(e.detail, { masked: true }) };
|
|
140
|
+
foldOverlay(this.state, ev);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
/** Push the current views (activity + coin always; the PnL card when its fills changed or it aged). */
|
|
144
|
+
tick() {
|
|
145
|
+
const now = this.now();
|
|
146
|
+
this.server.push('activity', activityView(this.state, now));
|
|
147
|
+
this.server.push('coin', coinView(this.state, now));
|
|
148
|
+
const p = this.state.paperCurrent ? this.state.paper.get(this.state.paperCurrent) : undefined;
|
|
149
|
+
const key = this.pnlInput ? 'ledger' : `${this.state.paperCurrent}:${p?.fills.length ?? 0}:${this.state.coins.get(this.state.paperCurrent ?? '')?.priceUsd?.at ?? 0}`;
|
|
150
|
+
if (key !== this.lastPnlKey || now - this.lastPnlAt >= this.pnlEveryMs) {
|
|
151
|
+
this.lastPnlKey = key;
|
|
152
|
+
this.lastPnlAt = now;
|
|
153
|
+
let input = null;
|
|
154
|
+
let note = null;
|
|
155
|
+
if (this.pnlInput) {
|
|
156
|
+
try {
|
|
157
|
+
input = this.pnlInput();
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
note = err.message;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const view = pnlView(this.state, now, input);
|
|
164
|
+
this.server.push('pnl', note ? { ...view, note } : view);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function ledgerLoader(io, tradeId, solUsd) {
|
|
169
|
+
let row = null;
|
|
170
|
+
let problem = 'reading the trade ledger…';
|
|
171
|
+
return {
|
|
172
|
+
async load() {
|
|
173
|
+
try {
|
|
174
|
+
const at = await import('../../autotrade/src/index.js');
|
|
175
|
+
const v = await at.loadLedgerView({ env: io.env, now: Date.now() });
|
|
176
|
+
const t = v.trades.find((x) => x.tradeId === tradeId);
|
|
177
|
+
if (!t) {
|
|
178
|
+
row = null;
|
|
179
|
+
problem = `no trade ${tradeId} in the trade ledger`;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
row = t;
|
|
183
|
+
problem = null;
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
row = null;
|
|
187
|
+
problem = `trade ledger: ${e.message}`;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
input() {
|
|
191
|
+
if (!row)
|
|
192
|
+
throw new Error(problem ?? 'no ledger trade');
|
|
193
|
+
return ledgerOverlayInput(row, solUsd ? { usd: solUsd, source: '--sol-usd' } : null);
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
export async function runOverlay(rest, io) {
|
|
198
|
+
const out = (s) => io.stdout.write(s + '\n');
|
|
199
|
+
const errOut = (s) => io.stderr.write(s + '\n');
|
|
200
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
201
|
+
out(OVERLAY_USAGE);
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
const o = parseOverlayArgs(rest);
|
|
205
|
+
if (typeof o === 'string') {
|
|
206
|
+
errOut(`dexterm overlay: ${o}`);
|
|
207
|
+
errOut(OVERLAY_USAGE);
|
|
208
|
+
return 2;
|
|
209
|
+
}
|
|
210
|
+
let recording = null;
|
|
211
|
+
if (o.replay) {
|
|
212
|
+
try {
|
|
213
|
+
recording = loadSpectatorRecording(o.replay);
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
errOut(`dexterm overlay: cannot read ${o.replay}: ${e.message}`);
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
if (!recording.events.length) {
|
|
220
|
+
errOut(`dexterm overlay: ${o.replay} has no events`);
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
let server;
|
|
225
|
+
try {
|
|
226
|
+
server = await createOverlayServer({
|
|
227
|
+
port: o.port, ticketTtlMs: o.ttlMin * 60_000,
|
|
228
|
+
onEvent: (ev) => { if (ev.ev === 'probe.rejected')
|
|
229
|
+
errOut(`overlay: refused ${ev.method} ${ev.path} (${ev.reason})`); },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
errOut(`dexterm overlay: cannot listen on 127.0.0.1${o.port ? `:${o.port}` : ''}: ${e.message}`);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
const ledger = o.ledger ? ledgerLoader(io, o.ledger, o.solUsd) : null;
|
|
237
|
+
if (ledger)
|
|
238
|
+
await ledger.load();
|
|
239
|
+
const engine = new OverlayEngine({ sample: !!recording, server, ...(ledger ? { pnlInput: () => ledger.input() } : {}) });
|
|
240
|
+
const printUrls = () => {
|
|
241
|
+
const urls = o.scenes.map((s) => ({ scene: s, url: server.issueTicket(s, o.theme) }));
|
|
242
|
+
if (o.json) {
|
|
243
|
+
out(JSON.stringify({ theme: o.theme, sample: !!recording, ttlMin: o.ttlMin, scenes: urls }));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
out(`dexterm overlay · theme ${o.theme}${recording ? ' · SAMPLE DATA (replay)' : ''} · each URL works once, for ${o.ttlMin} min`);
|
|
247
|
+
for (const u of urls)
|
|
248
|
+
out(` ${u.scene.padEnd(9)} ${u.url}`);
|
|
249
|
+
};
|
|
250
|
+
printUrls();
|
|
251
|
+
if (!o.json)
|
|
252
|
+
out(recording ? `replaying ${recording.events.length} event(s) from ${o.replay}` : 'watching the live spectator stream (start one with `dexterm --spectate-mcp` or `dexterm agent serve --spectate`)');
|
|
253
|
+
const timers = [];
|
|
254
|
+
let stopped = false;
|
|
255
|
+
let ws = null;
|
|
256
|
+
// ---- data source
|
|
257
|
+
if (recording) {
|
|
258
|
+
const evs = recording.events;
|
|
259
|
+
let i = 0;
|
|
260
|
+
const step = () => {
|
|
261
|
+
if (stopped)
|
|
262
|
+
return;
|
|
263
|
+
if (i >= evs.length) {
|
|
264
|
+
if (!o.loop)
|
|
265
|
+
return;
|
|
266
|
+
i = 0;
|
|
267
|
+
engine.reset();
|
|
268
|
+
}
|
|
269
|
+
const ev = evs[i];
|
|
270
|
+
// replayed events are re-timed to the playback clock (a signal's own `at` shifts with them), so their ages
|
|
271
|
+
// read as "this long ago in the replay"
|
|
272
|
+
const shift = Date.now() - ev.t;
|
|
273
|
+
const flags = ev.detail?.liveFlags;
|
|
274
|
+
engine.feed({ ...ev, t: ev.t + shift, ...(flags ? { detail: { ...ev.detail, liveFlags: flags.map((f) => ({ ...f, at: (typeof f.at === 'number' ? f.at : ev.t) + shift })) } } : {}) });
|
|
275
|
+
const next = evs[i + 1];
|
|
276
|
+
i++;
|
|
277
|
+
// the end of a looped replay holds its final scenes (the PnL card lands last) before it starts again
|
|
278
|
+
const gap = next ? Math.min(5000, Math.max(0, next.t - ev.t)) / o.speed : 10_000;
|
|
279
|
+
timers.push(setTimeout(step, gap));
|
|
280
|
+
};
|
|
281
|
+
step();
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
const connect = () => {
|
|
285
|
+
if (stopped)
|
|
286
|
+
return;
|
|
287
|
+
let info;
|
|
288
|
+
try {
|
|
289
|
+
info = JSON.parse(readFileSync(spectatorInfoPath(io.env), 'utf8'));
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
timers.push(setTimeout(connect, 3000));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (!Number.isInteger(info.port) || typeof info.token !== 'string') {
|
|
296
|
+
timers.push(setTimeout(connect, 3000));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
ws = new WebSocket(`ws://127.0.0.1:${info.port}`);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
timers.push(setTimeout(connect, 3000));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const token = info.token;
|
|
307
|
+
ws.onopen = () => ws?.send(JSON.stringify({ method: 'hello', token }));
|
|
308
|
+
ws.onmessage = (m) => {
|
|
309
|
+
let msg;
|
|
310
|
+
try {
|
|
311
|
+
msg = JSON.parse(String(m.data));
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (msg && msg.type === 'event')
|
|
317
|
+
engine.feed(msg.event);
|
|
318
|
+
};
|
|
319
|
+
ws.onclose = () => { ws = null; if (!stopped)
|
|
320
|
+
timers.push(setTimeout(connect, 3000)); };
|
|
321
|
+
ws.onerror = () => { };
|
|
322
|
+
};
|
|
323
|
+
connect();
|
|
324
|
+
}
|
|
325
|
+
const ticker = setInterval(() => engine.tick(), 1000);
|
|
326
|
+
engine.tick();
|
|
327
|
+
const ledgerTimer = ledger ? setInterval(() => { void ledger.load(); }, 30_000) : null;
|
|
328
|
+
return new Promise((resolve) => {
|
|
329
|
+
const stop = () => {
|
|
330
|
+
if (stopped)
|
|
331
|
+
return;
|
|
332
|
+
stopped = true;
|
|
333
|
+
clearInterval(ticker);
|
|
334
|
+
if (ledgerTimer)
|
|
335
|
+
clearInterval(ledgerTimer);
|
|
336
|
+
for (const t of timers)
|
|
337
|
+
clearTimeout(t);
|
|
338
|
+
try {
|
|
339
|
+
ws?.close();
|
|
340
|
+
}
|
|
341
|
+
catch { /* ignore */ }
|
|
342
|
+
if (io.stdin.isTTY) {
|
|
343
|
+
try {
|
|
344
|
+
io.stdin.setRawMode(false);
|
|
345
|
+
}
|
|
346
|
+
catch { /* ignore */ }
|
|
347
|
+
io.stdin.pause();
|
|
348
|
+
}
|
|
349
|
+
process.off('SIGINT', stop);
|
|
350
|
+
process.off('SIGTERM', stop);
|
|
351
|
+
void server.close().then(() => resolve(0));
|
|
352
|
+
};
|
|
353
|
+
process.on('SIGINT', stop);
|
|
354
|
+
process.on('SIGTERM', stop);
|
|
355
|
+
if (io.stdin.isTTY) {
|
|
356
|
+
io.stdin.setRawMode(true);
|
|
357
|
+
io.stdin.resume();
|
|
358
|
+
io.stdin.on('data', (d) => {
|
|
359
|
+
const k = d.toString();
|
|
360
|
+
if (k === 'q' || k === '\u0003')
|
|
361
|
+
stop();
|
|
362
|
+
else if (k === 'n')
|
|
363
|
+
printUrls();
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
@@ -138,6 +138,10 @@ export function autotradeCardInput(events, o) {
|
|
|
138
138
|
* needs `solUsd`; a fill with no token amount refuses the card rather than guess.
|
|
139
139
|
*/
|
|
140
140
|
export function ledgerCardInput(row, o = {}) {
|
|
141
|
+
// task-1655: a real-mode row is dexterm's model of a trade it signalled; the real fill is the executor's receipt.
|
|
142
|
+
// A card would stamp it PAPER or LIVE, and both are wrong, so there is none.
|
|
143
|
+
if (row.walletMode === 'real')
|
|
144
|
+
throw new CardError('a real-mode trade: dexterm only modelled this fill (the signal it sent your executor); your executor\'s receipt is the record, so dexterm makes no card for it');
|
|
141
145
|
const decimals = row.decimals ?? 6;
|
|
142
146
|
const usdDesk = row.fills.some((f) => f.sol === null);
|
|
143
147
|
if (usdDesk && !(o.solUsd && o.solUsd.usd > 0))
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
1
9
|
// dexterm — `dexterm record` (task-1414): a HEADLESS collector that keeps the history recorder fed when
|
|
2
10
|
// nobody has the TUI open. The recorder (task-1329) only grows while the interactive app runs; the backtest
|
|
3
11
|
// (task-1401) is only worth re-running on fresh data, so this command runs the exact same recorder jobs
|
|
@@ -20,13 +28,17 @@
|
|
|
20
28
|
// re-installed with them: `--retention-mb N` replaces that pause with eviction of the oldest history (observation
|
|
21
29
|
// never pauses), `--retention-days D` is the age rule for snapshot rows, and `--focus-ticks` runs the autotrade
|
|
22
30
|
// tick lane (5–10 s DexScreener samples of watchlist / held / candidate / open coins, plus 1m candles).
|
|
31
|
+
// task-1786 (winner-recall DATA-GAPS items 1-2), opt-in the same way: `--focus-module PATH` loads an ESM module whose
|
|
32
|
+
// `focus(now)` adds focus coins to the tick lane (the challenge / control coins live outside the app; the module that
|
|
33
|
+
// reads them lives with them), and `--exit-probes` adds a keyless Jupiter sell quote for every held focus coin once a
|
|
34
|
+
// minute (exit-probes.ts). Both imply --focus-ticks.
|
|
23
35
|
import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
|
24
36
|
import { spawnSync } from 'node:child_process';
|
|
25
37
|
import { dirname, join, resolve } from 'node:path';
|
|
26
|
-
import { fileURLToPath } from 'node:url';
|
|
38
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
39
|
import { defaultDbPath, resolveRpcUrl, extraRpcUrls, redactSecrets } from '../../data/src/index.js';
|
|
28
40
|
import { PumpPortalClient } from '../../data/src/index.js';
|
|
29
|
-
import { Recorder, recorderEnabled, dbFocus, focusFilePath, readFocusFile, DEFAULT_TICKS } from '../../recorder/src/index.js';
|
|
41
|
+
import { Recorder, recorderEnabled, dbFocus, focusFilePath, readFocusFile, exitProbeDeps, DEFAULT_TICKS } from '../../recorder/src/index.js';
|
|
30
42
|
import { dextermHome } from "./boot-state.js";
|
|
31
43
|
import { createLiveFeed } from "./feed.js";
|
|
32
44
|
import { NewPairsLive } from "./live/index.js";
|
|
@@ -50,13 +62,30 @@ export function buildLiveEngine(env, opts) {
|
|
|
50
62
|
// Focus coins for the tick lane: the watchlist + paper positions in this DB, and whatever the autotrade runner /
|
|
51
63
|
// the app publish in the focus file. Read on every poll (two indexed reads + one small file).
|
|
52
64
|
const focusFile = focusFilePath(dextermHome(env));
|
|
65
|
+
// task-1786: the focus module's coins. A throwing module costs its own coins only (logged at most every 10 min).
|
|
66
|
+
let extraFailedAt = -Infinity;
|
|
67
|
+
const extra = () => {
|
|
68
|
+
if (!opts.extraFocus)
|
|
69
|
+
return [];
|
|
70
|
+
try {
|
|
71
|
+
return [...opts.extraFocus()];
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
if (Date.now() - extraFailedAt > 600_000) {
|
|
75
|
+
extraFailedAt = Date.now();
|
|
76
|
+
opts.onLog?.(`record: focus module failed: ${redactSecrets(e.message)}\n`);
|
|
77
|
+
}
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
};
|
|
53
81
|
const recorder = new Recorder({
|
|
54
82
|
path: opts.dbPath, onLog: opts.verbose ? opts.onLog : undefined,
|
|
55
83
|
// The cap reclaims the data layer's expired cache entries (its own rule: a day past their TTL) before it evicts
|
|
56
84
|
// any history; `feed` is built above, so the hook can reach it.
|
|
57
85
|
...(opts.retentionBytes ? { cap: { maxBytes: opts.retentionBytes, reclaim: () => feed.dl.cache.prune() } } : {}),
|
|
58
86
|
...(opts.retentionDays ? { retention: { columnsDays: opts.retentionDays } } : {}),
|
|
59
|
-
...(opts.focusTicks ? { ticks: { focus: () => [...dbFocus(recorder.database), ...readFocusFile(focusFile, Date.now())] } } : {}),
|
|
87
|
+
...(opts.focusTicks ? { ticks: { focus: () => [...dbFocus(recorder.database), ...readFocusFile(focusFile, Date.now()), ...extra()] } } : {}),
|
|
88
|
+
...(opts.focusTicks && opts.exitProbes ? { exitProbes: {} } : {}),
|
|
60
89
|
});
|
|
61
90
|
recorder.attach(feed.dl.store);
|
|
62
91
|
let newpairs = null;
|
|
@@ -71,6 +100,7 @@ export function buildLiveEngine(env, opts) {
|
|
|
71
100
|
feed.extraMints = () => [...np.rows.keys()];
|
|
72
101
|
}
|
|
73
102
|
const dl = feed.dl;
|
|
103
|
+
const probeDeps = exitProbeDeps(dl);
|
|
74
104
|
return {
|
|
75
105
|
async observe() {
|
|
76
106
|
const before = recorder.counters.written;
|
|
@@ -83,7 +113,14 @@ export function buildLiveEngine(env, opts) {
|
|
|
83
113
|
}
|
|
84
114
|
catch { /* logged inside; never fatal */ } },
|
|
85
115
|
enforceCap() { return recorder.enforceCap(); },
|
|
86
|
-
...(opts.focusTicks ? { pollTicks: () =>
|
|
116
|
+
...(opts.focusTicks ? { pollTicks: async () => {
|
|
117
|
+
const r = await recorder.pollTicks(dl);
|
|
118
|
+
// Detached: a quote waiting on the Jupiter bucket must never delay the next 5 s tick (the lane's own in-flight
|
|
119
|
+
// guard keeps one probe pass at a time; it never throws).
|
|
120
|
+
if (opts.exitProbes)
|
|
121
|
+
void recorder.probeExits(probeDeps).catch(() => { });
|
|
122
|
+
return r;
|
|
123
|
+
} } : {}),
|
|
87
124
|
dbBytes() {
|
|
88
125
|
try {
|
|
89
126
|
const db = recorder.database;
|
|
@@ -261,6 +298,19 @@ function parseRetention(t, next, a) {
|
|
|
261
298
|
a.focusTicks = true;
|
|
262
299
|
return null;
|
|
263
300
|
}
|
|
301
|
+
if (t === '--exit-probes') {
|
|
302
|
+
a.exitProbes = true;
|
|
303
|
+
a.focusTicks = true;
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
if (t === '--focus-module') {
|
|
307
|
+
const v = next();
|
|
308
|
+
if (!v || v.startsWith('--'))
|
|
309
|
+
return '--focus-module needs a PATH';
|
|
310
|
+
a.focusModule = resolve(v);
|
|
311
|
+
a.focusTicks = true;
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
264
314
|
if (t === '--retention-mb') {
|
|
265
315
|
const n = Number(next());
|
|
266
316
|
if (!Number.isFinite(n) || n < 1)
|
|
@@ -278,7 +328,7 @@ function parseRetention(t, next, a) {
|
|
|
278
328
|
return undefined;
|
|
279
329
|
}
|
|
280
330
|
export function parseRun(argv) {
|
|
281
|
-
const a = { intervalMs: DEFAULT_INTERVAL_MS, ticks: Infinity, maintainMs: DEFAULT_MAINTAIN_MS, maxDbBytes: DEFAULT_MAX_DB_MB * 1_000_000, noNewpairs: false, db: null, verbose: false, retentionBytes: null, retentionDays: null, focusTicks: false };
|
|
331
|
+
const a = { intervalMs: DEFAULT_INTERVAL_MS, ticks: Infinity, maintainMs: DEFAULT_MAINTAIN_MS, maxDbBytes: DEFAULT_MAX_DB_MB * 1_000_000, noNewpairs: false, db: null, verbose: false, retentionBytes: null, retentionDays: null, focusTicks: false, focusModule: null, exitProbes: false };
|
|
282
332
|
for (let i = 0; i < argv.length; i++) {
|
|
283
333
|
const t = argv[i];
|
|
284
334
|
const r = parseRetention(t, () => argv[++i], a);
|
|
@@ -329,8 +379,9 @@ export function parseRun(argv) {
|
|
|
329
379
|
export const RECORD_USAGE = `dexterm record: keep the history recorder fed with no TUI open (task-1414).
|
|
330
380
|
|
|
331
381
|
dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--retention-mb N] [--retention-days D]
|
|
332
|
-
[--focus-ticks] [--no-newpairs] [--db PATH] [--verbose]
|
|
333
|
-
dexterm record install [--interval S] [--max-db-mb N] [--retention-mb N] [--retention-days D] [--focus-ticks]
|
|
382
|
+
[--focus-ticks] [--focus-module PATH] [--exit-probes] [--no-newpairs] [--db PATH] [--verbose]
|
|
383
|
+
dexterm record install [--interval S] [--max-db-mb N] [--retention-mb N] [--retention-days D] [--focus-ticks]
|
|
384
|
+
[--focus-module PATH] [--exit-probes] [--load] [--dry-run]
|
|
334
385
|
dexterm record uninstall [--unload]
|
|
335
386
|
dexterm record status [--db PATH]
|
|
336
387
|
|
|
@@ -341,7 +392,10 @@ run the collector loop: one keyless feed refresh per --interval (default
|
|
|
341
392
|
to stay under N MB and observation never pauses. --retention-days D drops snapshot rows (sweep rows
|
|
342
393
|
too) older than D days (default 90). --focus-ticks samples watchlist / held / candidate / open coins
|
|
343
394
|
every 5-10 s (DexScreener batches of 30, a daily call ceiling) with 1m candles; autotrade backtests
|
|
344
|
-
read them. --
|
|
395
|
+
read them. --focus-module PATH adds the coins an ESM module's focus(now) returns
|
|
396
|
+
([{ mint, role }], role held | open | candidate | watch) to that lane. --exit-probes asks Jupiter for
|
|
397
|
+
a keyless $50 sell quote of every held focus coin once a minute (exit_probes; quotes only).
|
|
398
|
+
Both imply --focus-ticks. --no-newpairs drops the PumpPortal stream. Ctrl-C stops cleanly
|
|
345
399
|
(labels whatever is due). Requests honour the data layer's rate limits, health gate and sweep budgets.
|
|
346
400
|
install write a user-level launchd LaunchAgent (no sudo) that runs \`record run\`; prints the launchctl
|
|
347
401
|
command. --load also runs launchctl (off by default). --dry-run prints the plist, writes nothing.
|
|
@@ -404,7 +458,22 @@ async function run(argv, io, deps) {
|
|
|
404
458
|
};
|
|
405
459
|
})();
|
|
406
460
|
const log = (s) => io.stderr.write(s);
|
|
407
|
-
|
|
461
|
+
// task-1786: the focus module is loaded once, up front; a module that does not load is a startup error (exit 2).
|
|
462
|
+
let extraFocus;
|
|
463
|
+
if (parsed.focusModule) {
|
|
464
|
+
try {
|
|
465
|
+
const mod = await import(__rewriteRelativeImportExtension(pathToFileURL(parsed.focusModule).href));
|
|
466
|
+
if (typeof mod.focus !== 'function')
|
|
467
|
+
throw new Error('it exports no focus(now) function');
|
|
468
|
+
const fn = mod.focus;
|
|
469
|
+
extraFocus = () => fn(Date.now());
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
io.stderr.write(`dexterm record: --focus-module ${parsed.focusModule}: ${redactSecrets(e.message)}\n`);
|
|
473
|
+
return 2;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const engine = deps.engine ?? buildLiveEngine(io.env, { dbPath, noNewpairs: parsed.noNewpairs, verbose: parsed.verbose, onLog: log, retentionBytes: parsed.retentionBytes, retentionDays: parsed.retentionDays, focusTicks: parsed.focusTicks, extraFocus, exitProbes: parsed.exitProbes });
|
|
408
477
|
const own = !deps.engine;
|
|
409
478
|
const onSig = () => { io.stderr.write('\ndexterm record: stopping…\n'); stop.abort(); };
|
|
410
479
|
if (own) {
|
|
@@ -415,7 +484,7 @@ async function run(argv, io, deps) {
|
|
|
415
484
|
// alive between passes and node would exit mid-run (task-1504 smoke). Hold it open until the loops finish.
|
|
416
485
|
const hold = own ? setInterval(() => { }, 1 << 30) : null;
|
|
417
486
|
if (parsed.verbose || parsed.ticks !== Infinity)
|
|
418
|
-
io.stderr.write(`dexterm record: db ${dbPath} · interval ${Math.round(parsed.intervalMs / 1000)}s · maintain ${Math.round(parsed.maintainMs / 1000)}s · cap ${parsed.maxDbBytes === null ? 'off' : `${Math.round(parsed.maxDbBytes / 1e6)} MB`} · newpairs ${parsed.noNewpairs ? 'off' : 'on'}${parsed.retentionBytes !== null ? ` · retention ${Math.round(parsed.retentionBytes / 1e6)} MB (evict, never pause)` : ''}${parsed.retentionDays !== null ? ` · rows ${parsed.retentionDays} d` : ''} · focus ticks ${parsed.focusTicks ? 'on' : 'off'}\n`);
|
|
487
|
+
io.stderr.write(`dexterm record: db ${dbPath} · interval ${Math.round(parsed.intervalMs / 1000)}s · maintain ${Math.round(parsed.maintainMs / 1000)}s · cap ${parsed.maxDbBytes === null ? 'off' : `${Math.round(parsed.maxDbBytes / 1e6)} MB`} · newpairs ${parsed.noNewpairs ? 'off' : 'on'}${parsed.retentionBytes !== null ? ` · retention ${Math.round(parsed.retentionBytes / 1e6)} MB (evict, never pause)` : ''}${parsed.retentionDays !== null ? ` · rows ${parsed.retentionDays} d` : ''} · focus ticks ${parsed.focusTicks ? 'on' : 'off'}${parsed.focusModule ? ` · focus module ${parsed.focusModule}` : ''}${parsed.exitProbes ? ' · exit probes on' : ''}\n`);
|
|
419
488
|
let result;
|
|
420
489
|
let tickRes = { polls: 0, calls: 0, written: 0 };
|
|
421
490
|
try {
|
|
@@ -446,7 +515,7 @@ async function run(argv, io, deps) {
|
|
|
446
515
|
}
|
|
447
516
|
function install(argv, io) {
|
|
448
517
|
let intervalMs = DEFAULT_INTERVAL_MS, maxDbMb = DEFAULT_MAX_DB_MB, load = false, dry = false;
|
|
449
|
-
const ret = { retentionBytes: null, retentionDays: null, focusTicks: false };
|
|
518
|
+
const ret = { retentionBytes: null, retentionDays: null, focusTicks: false, focusModule: null, exitProbes: false };
|
|
450
519
|
for (let i = 0; i < argv.length; i++) {
|
|
451
520
|
const t = argv[i];
|
|
452
521
|
const r = parseRetention(t, () => argv[++i], ret);
|
|
@@ -488,6 +557,8 @@ function install(argv, io) {
|
|
|
488
557
|
...(ret.retentionBytes !== null ? ['--retention-mb', String(Math.round(ret.retentionBytes / 1e6))] : []),
|
|
489
558
|
...(ret.retentionDays !== null ? ['--retention-days', String(ret.retentionDays)] : []),
|
|
490
559
|
...(ret.focusTicks ? ['--focus-ticks'] : []),
|
|
560
|
+
...(ret.focusModule ? ['--focus-module', ret.focusModule] : []),
|
|
561
|
+
...(ret.exitProbes ? ['--exit-probes'] : []),
|
|
491
562
|
];
|
|
492
563
|
const plist = renderCollectorPlist({ label: COLLECTOR_LABEL, nodePath: process.execPath, binPath: bin, intervalMs, maxDbMb, extraArgs, logPath, workingDir: resolve(dirname(bin), '..', '..', '..') });
|
|
493
564
|
if (dry) {
|
|
@@ -119,8 +119,9 @@ export async function rulesCommand(argv, io) {
|
|
|
119
119
|
case 'fields': {
|
|
120
120
|
io.stdout.write(`fields: ${FIELD_NAMES.join(' ')}\n`);
|
|
121
121
|
io.stdout.write(`aliases: vol volume liquidity cap mc holder organic top locked cluster% clusterpct\n`);
|
|
122
|
+
io.stdout.write(`narrative (EXPERIMENTAL, measured heat, not advice): narrative_rank narrative_score narrative:spark|ignition|peak|fading|dead|saturated (null until N → n builds the tracker)\n`);
|
|
122
123
|
io.stdout.write(`signals: <field>.velocity (current ÷ mean of earlier samples in the window) · <field>.delta (current − oldest in the window)\n`);
|
|
123
|
-
io.stdout.write(`tags: ${TAG_NAMES.map((t) => t + ':').join(' ')} (e.g. launchpad:pump flag:mint flag:clean verified:yes sus:no; prefix ! to negate)\n`);
|
|
124
|
+
io.stdout.write(`tags: ${TAG_NAMES.map((t) => t + ':').join(' ')} (e.g. launchpad:pump flag:mint flag:clean verified:yes sus:no narrative:ignition narrative:saturated; prefix ! to negate)\n`);
|
|
124
125
|
io.stdout.write(`values: $1.5M 25K 2B 20% 2h 30m 3d · ops > >= < <= = != · and or not ( )\n`);
|
|
125
126
|
io.stdout.write(`actions: ${ACTIONS.join(' ')} alert(level, "text") ticket(buy|sell, size ≤ ${TICKET_SIZE_CAP}, sol|usd|pct) ask(skill) popup(${RULE_POPUP_KINDS.join('|')}) tag("label") sound(name) watch\n`);
|
|
126
127
|
io.stdout.write(`limits: cooldown (default 10m) · max n/h (default 12, cap ${MAX_PER_HOUR_CAP}) · scope ${SCOPES.join('|')}|mint:<addr> · window (default 10m)\n`);
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
// dexterm — the rules engine's app adapter (task-1358 Tier 3). Pure helpers over @dexterm/custom's RulesEngine
|
|
2
|
+
// that the key handler, the interactive loop and the Rules screen call: load `config.rules` into an engine on
|
|
3
|
+
// `state.rulesUi`, feed it the screener rows every few seconds (`rulesTick`), turn its outputs into things the
|
|
4
|
+
// app can show (toasts, the screen's log, PROPOSAL cards, pending UI actions the loop performs), and persist edits
|
|
5
|
+
// through the config journal (so `dexterm config undo` / the journal cover rules too).
|
|
6
|
+
//
|
|
7
|
+
// SAFETY: a rule's outputs are alerts, annotations (watch/tag on the user's own watchlist), UI actions (popup /
|
|
8
|
+
// terminal bell) and PROPOSALS. Acting on a ticket proposal opens the PAPER ticket in its editing phase (the user
|
|
9
|
+
// sizes and confirms there, signer caps apply); acting on an ask proposal sends a question to the analyst. Nothing
|
|
10
|
+
// in this module can arm, sign, confirm, open a live ticket or touch a mandate — there is no code path for it.
|
|
11
|
+
import { classifierLiveTick } from "./classifier-live.js";
|
|
1
12
|
import { devBookOf } from "./live/dev-history.js";
|
|
13
|
+
import { narrativeRuleFields } from "./narratives.js";
|
|
2
14
|
import { RulesEngine, ruleToJson, ruleToText, slug, applyAndJournal, describeActions, } from '../../custom/src/index.js';
|
|
3
15
|
import { alertToDelivery, proposalToDelivery } from "./alerts/format.js";
|
|
4
16
|
export const RULE_INPUT_MAX = 400;
|
|
@@ -56,6 +68,11 @@ export function larpGrade(verdict) {
|
|
|
56
68
|
* `mintable > 0` fires on the real streaming values, not the on-open snapshot. A token opened by `--mint` that is
|
|
57
69
|
* not in the screener feed is appended as its own row so rules still see it.
|
|
58
70
|
*/
|
|
71
|
+
/** task-1822: the classifier's drain estimate for the `rugp` field (null when unscored or the scoring failed). */
|
|
72
|
+
function rugpOf(state, mint) {
|
|
73
|
+
const p = state.classifierByMint?.get(mint)?.p;
|
|
74
|
+
return typeof p === 'number' && Number.isFinite(p) ? p : null;
|
|
75
|
+
}
|
|
59
76
|
export function ruleRowsOf(state) {
|
|
60
77
|
const out = [];
|
|
61
78
|
const seen = new Set();
|
|
@@ -70,11 +87,13 @@ export function ruleRowsOf(state) {
|
|
|
70
87
|
// 0.1.5 dev history: the creator's recorded rugs as the `devrugs` field (local recorder only; null = unknown).
|
|
71
88
|
const book = devBookOf(state);
|
|
72
89
|
const devRugsOf = (mint) => book ? book.rugsFor(mint) : null;
|
|
90
|
+
// task-1818: narrative_rank / narrative_score / narrative:<state> from the ALREADY-built narratives tracker (never builds one)
|
|
91
|
+
const narr = (mint) => narrativeRuleFields(state, mint);
|
|
73
92
|
for (const r of state.screener.rows) {
|
|
74
93
|
if (!r.mint || r.skeleton || seen.has(r.mint))
|
|
75
94
|
continue;
|
|
76
95
|
seen.add(r.mint);
|
|
77
|
-
const row = Object.assign(Object.create(null), r, { cluster: clusterOf(r.mint), larp: larpGrade(state.larpByMint?.get(r.mint)?.verdict), devRugs: devRugsOf(r.mint) });
|
|
96
|
+
const row = Object.assign(Object.create(null), r, { cluster: clusterOf(r.mint), larp: larpGrade(state.larpByMint?.get(r.mint)?.verdict), devRugs: devRugsOf(r.mint), rugP: rugpOf(state, r.mint) }, narr(r.mint));
|
|
78
97
|
if (r.mint === openMint)
|
|
79
98
|
overlaySignalFields(row, state, r.mint);
|
|
80
99
|
out.push(row);
|
|
@@ -85,9 +104,9 @@ export function ruleRowsOf(state) {
|
|
|
85
104
|
const row = Object.assign(Object.create(null), {
|
|
86
105
|
mint: v.mint, symbol: v.symbol, price: v.price, mcap: v.mcap, fdv: v.fdv, liq: v.liq, vol24: v.vol24,
|
|
87
106
|
holders: v.holders, org: v.org, top10: v.top10, dev: v.dev, lpLocked: v.lpLocked,
|
|
88
|
-
cluster: clusterOf(v.mint), larp: larpGrade(state.larpByMint?.get(v.mint)?.verdict), devRugs: devRugsOf(v.mint),
|
|
107
|
+
cluster: clusterOf(v.mint), larp: larpGrade(state.larpByMint?.get(v.mint)?.verdict), devRugs: devRugsOf(v.mint), rugP: rugpOf(state, v.mint),
|
|
89
108
|
ageMin: v.ageMin, chg: v.chg, launchpad: v.launchpad, flags: v.flags, verified: v.verified, sus: v.sus,
|
|
90
|
-
});
|
|
109
|
+
}, narr(v.mint));
|
|
91
110
|
overlaySignalFields(row, state, v.mint);
|
|
92
111
|
out.push(row);
|
|
93
112
|
}
|
|
@@ -107,7 +126,7 @@ export function ruleRowsOf(state) {
|
|
|
107
126
|
out.push(Object.assign(Object.create(null), {
|
|
108
127
|
mint: p.mint, symbol: p.symbol, mcap: p.mcap, liq: p.liq, holders: p.holders, launchpad: p.launchpad,
|
|
109
128
|
ageMin: p.createdAt ? Math.max(0, (now - p.createdAt) / 60_000) : null, bonding: p.bonding, devRugs,
|
|
110
|
-
}));
|
|
129
|
+
}, narr(p.mint)));
|
|
111
130
|
}
|
|
112
131
|
}
|
|
113
132
|
return out;
|
|
@@ -153,6 +172,7 @@ export function rulesTick(state, now) {
|
|
|
153
172
|
const rows = ruleRowsOf(state);
|
|
154
173
|
if (!rows.length)
|
|
155
174
|
return null;
|
|
175
|
+
classifierLiveTick(state, rows, now); // task-1822: fills classifierByMint for `rugp` rules (a no-op without one)
|
|
156
176
|
ui.engine.observe(rows, now);
|
|
157
177
|
const watch = state.live?.watch.store ?? null;
|
|
158
178
|
const positions = state.live?.positions ?? null;
|