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
|
@@ -47,6 +47,7 @@ import { recordLarpVerdict } from "./larp-record.js";
|
|
|
47
47
|
import { newAutotradeUi, defaultDeps as autotradeDeps, autotradeCapturing, autotradeTick, reloadList as autotradeReload, refreshRunner as autotradeRefreshRunner } from "./screens/autotrade-ui.js";
|
|
48
48
|
import { autotradeAction, autotradeKey } from "./screens/autotrade-keys.js";
|
|
49
49
|
import { workerJobs as autotradeWorkerJobs } from "./screens/autotrade-worker.js";
|
|
50
|
+
import { narrativesUiOf, kickNarratives, heatRows, drillDetail } from "./narratives.js";
|
|
50
51
|
import { rulesUiOf, rulesTick, rulesMove, beginRuleInput, saveRuleInput, dryRunNow, removeSelectedRule, toggleSelectedRule, dismissSelectedProposal, actOnSelectedProposal, RULE_INPUT_MAX } from "./rules.js";
|
|
51
52
|
import { buildNotifier, hasPushChannel } from "./alerts/notifier.js";
|
|
52
53
|
import { copyAlertToDelivery } from "./alerts/format.js";
|
|
@@ -306,12 +307,16 @@ export const HELP = `dexterm ${VERSION} (live: screener, token, new pairs, watch
|
|
|
306
307
|
dexterm kb list|show|new|edit|check|reset|path your knowledge-base entries (~/.dexterm/kb/entries) over the bundled 18
|
|
307
308
|
dexterm skill list|show|new|edit|check|reset|path your analyst skills (~/.dexterm/skills) over the bundled ones (read tools only)
|
|
308
309
|
dexterm narrative [--window 1h|24h] [--summary] the narrative board over recorded history (no network, no model)
|
|
310
|
+
dexterm narratives [top|detail ID|of MINT|export] EXPERIMENTAL memes + narratives tracker over recorded history: themes, heat, lifecycle (measured heat, not advice)
|
|
309
311
|
dexterm backtest [--subject heuristic|pack_verdicts] [--horizon SEC] honest rug-model metrics over recorded outcomes (no network, no model)
|
|
310
312
|
dexterm ledger trades|positions|summary|equity|compare|export|card|sync|verify the unified trade ledger: paper, agent desk, autotrade runs (no network; 'dexterm ledger help')
|
|
313
|
+
dexterm dash [--port N] [--home DIR] [--baseline ID] [--export DIR] your strategies' performance in a browser on 127.0.0.1 (read-only, no network; 'dexterm dash --help')
|
|
311
314
|
dexterm wallet link|unlink|status|panic|resume link a Phantom wallet via a local bridge (pubkey only); panic halts autotrade + the paper desk, resume needs a TTY keypress
|
|
312
315
|
dexterm plan propose|list|arm|cancel|journal conditional order plans (entry/ladder/take-profit/moon bag/stop); arming needs a physical keypress
|
|
313
316
|
dexterm mandate draft|list|arm|revoke standing capped mandates (paper fills); arming needs a physical keypress — voice/agents can never arm
|
|
314
317
|
dexterm follow add|remove|list|stats|trades|mirror|purge copy trading tier 1: follow a wallet (your list, local labels), its decoded swaps + FIFO PnL + win rate/early-entry; 'mirror' previews a one-key MIRROR TICKET from the wallet's latest buy (inert; arm in the app)
|
|
318
|
+
dexterm classifier [list|pick|pull|serve|doctor|bench|try|key] classification models (EXPERIMENTAL): Kev on your own machine
|
|
319
|
+
(llama.cpp), hosted Jev with your own key, or the built-in heuristic; model estimates, never advice
|
|
315
320
|
dexterm ai [status|engine|model|host|restrictions|probe|ask|agent] the AI engine: your claude / codex CLI or a free LOCAL model on
|
|
316
321
|
your own Ollama (localhost); restriction profiles observe|propose|paper|full ('dexterm ai help')
|
|
317
322
|
dexterm mcp [install <client>|doctor] read-only MCP server for your own agent; 'install' writes the config for claude-code|claude-desktop|cursor|codex|generic (asks first, idempotent, backs up); 'doctor' verifies the connection
|
|
@@ -849,6 +854,12 @@ export async function main(argv, io = process, proc = process) {
|
|
|
849
854
|
const { runSpectate } = await import("./spectate-cmd.js");
|
|
850
855
|
return runSpectate(argv.slice(1), io);
|
|
851
856
|
}
|
|
857
|
+
// `dexterm overlay` → the streamer overlay (task-1427): OBS browser-source scenes (agent activity, the coin card,
|
|
858
|
+
// the PAPER / LIVE PnL card) fed by the read-only spectator stream or a replay; loopback, single-use ticket → token.
|
|
859
|
+
if (argv[0] === 'overlay') {
|
|
860
|
+
const { runOverlay } = await import("./overlay-cmd.js");
|
|
861
|
+
return runOverlay(argv.slice(1), io);
|
|
862
|
+
}
|
|
852
863
|
// `dexterm recorder <cmd>` → the history recorder's tables: stats, label, prune, export (task-1329). No network.
|
|
853
864
|
if (argv[0] === 'recorder') {
|
|
854
865
|
const { recorderCli } = await import('../../recorder/src/index.js');
|
|
@@ -859,11 +870,21 @@ export async function main(argv, io = process, proc = process) {
|
|
|
859
870
|
const { narrativeCommand } = await import("./narrative-cmd.js");
|
|
860
871
|
return narrativeCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
861
872
|
}
|
|
873
|
+
// `dexterm narratives [top|detail ID|of MINT|export]` → the MEMES + NARRATIVES tracker (task-1818, EXPERIMENTAL). No network, no model.
|
|
874
|
+
if (argv[0] === 'narratives') {
|
|
875
|
+
const { narrativesCommand } = await import("./narratives-cmd.js");
|
|
876
|
+
return narrativesCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
877
|
+
}
|
|
862
878
|
// `dexterm ledger …` → the unified trade ledger (task-1530): reads fold the file + the sources; only `sync` writes. No network, no model, no keys.
|
|
863
879
|
if (argv[0] === 'ledger') {
|
|
864
880
|
const { ledgerCommand } = await import("./ledger-cmd.js");
|
|
865
881
|
return ledgerCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
866
882
|
}
|
|
883
|
+
// `dexterm dash` → the strategy performance dashboard (task-1749): run ledgers → a page on 127.0.0.1 only. Read-only, no network, no model, no keys.
|
|
884
|
+
if (argv[0] === 'dash') {
|
|
885
|
+
const { dashCommand } = await import("./dash-cmd.js");
|
|
886
|
+
return dashCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
887
|
+
}
|
|
867
888
|
// `dexterm autotrade validate|list|backtest|sweep …` and `dexterm backtest --subject strategy` → the paper autotrade engine (task-1503). No network, no model, no keys.
|
|
868
889
|
if (argv[0] === 'autotrade' || argv[0] === 'backtest') {
|
|
869
890
|
const at = await import("./autotrade-cmd.js");
|
|
@@ -1001,6 +1022,11 @@ export async function main(argv, io = process, proc = process) {
|
|
|
1001
1022
|
const { aiCommand } = await import("./ai-cmd.js");
|
|
1002
1023
|
return aiCommand(argv[0] === 'ask' ? ['ask', ...argv.slice(1)] : argv.slice(1), { stdin: io.stdin, stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
1003
1024
|
}
|
|
1025
|
+
// task-1822: `dexterm classifier …` (pick / pull / serve the classification model; hardware tiers; hosted key).
|
|
1026
|
+
if (argv[0] === 'classifier') {
|
|
1027
|
+
const { classifierCommand } = await import("./classifier-cmd.js");
|
|
1028
|
+
return classifierCommand(argv.slice(1), { stdin: io.stdin, stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
1029
|
+
}
|
|
1004
1030
|
// `dexterm doctor` → a pre-flight that says whether dexterm will run here and how to fix what won't (task-1305).
|
|
1005
1031
|
if (argv[0] === 'doctor') {
|
|
1006
1032
|
const { defaultDeps, runDoctor, renderDoctor } = await import("./doctor.js");
|
|
@@ -1433,6 +1459,20 @@ function rulesScreenMove(state, delta) {
|
|
|
1433
1459
|
rulesMove(state.rulesUi, delta);
|
|
1434
1460
|
return true;
|
|
1435
1461
|
}
|
|
1462
|
+
// task-1818: j/k on the N screen's EXPERIMENTAL heat table move its cursor, or the member cursor while drilled in. The theme
|
|
1463
|
+
// board (the default mode) keeps the shared state.selected cursor.
|
|
1464
|
+
function narrativesMove(state, delta) {
|
|
1465
|
+
const ui = state.narrativesUi;
|
|
1466
|
+
if (state.screen !== 'narrative' || ui?.mode !== 'heat')
|
|
1467
|
+
return false;
|
|
1468
|
+
if (ui.drill) {
|
|
1469
|
+
const n = drillDetail(state)?.memberList.length ?? 0;
|
|
1470
|
+
ui.drill.selected = Math.max(0, Math.min(n - 1, ui.drill.selected + delta));
|
|
1471
|
+
return true;
|
|
1472
|
+
}
|
|
1473
|
+
ui.selected = Math.max(0, Math.min(heatRows(state).length - 1, ui.selected + delta));
|
|
1474
|
+
return true;
|
|
1475
|
+
}
|
|
1436
1476
|
function plansMove(state, delta) {
|
|
1437
1477
|
if (state.screen !== 'plans' || !state.plansUi)
|
|
1438
1478
|
return false;
|
|
@@ -1519,7 +1559,7 @@ export function applyAction(state, action, stack) {
|
|
|
1519
1559
|
// updates the context injected next turn); PgUp/PgDn scroll the chat transcript. On the token
|
|
1520
1560
|
// screen (QA M12) the movement keys scroll the answer. Keys reach here only when input is NOT focused.
|
|
1521
1561
|
case 'move.down':
|
|
1522
|
-
if (plansMove(state, 1) || followsMove(state, 1) || rulesScreenMove(state, 1))
|
|
1562
|
+
if (narrativesMove(state, 1) || plansMove(state, 1) || followsMove(state, 1) || rulesScreenMove(state, 1))
|
|
1523
1563
|
break;
|
|
1524
1564
|
if (bubbleMove(state, 1))
|
|
1525
1565
|
break;
|
|
@@ -1528,7 +1568,7 @@ export function applyAction(state, action, stack) {
|
|
|
1528
1568
|
state.selected = Math.min(rows().length - 1, (state.selected || 0) + 1);
|
|
1529
1569
|
break;
|
|
1530
1570
|
case 'move.up':
|
|
1531
|
-
if (plansMove(state, -1) || followsMove(state, -1) || rulesScreenMove(state, -1))
|
|
1571
|
+
if (narrativesMove(state, -1) || plansMove(state, -1) || followsMove(state, -1) || rulesScreenMove(state, -1))
|
|
1532
1572
|
break;
|
|
1533
1573
|
if (bubbleMove(state, -1))
|
|
1534
1574
|
break;
|
|
@@ -1537,7 +1577,7 @@ export function applyAction(state, action, stack) {
|
|
|
1537
1577
|
state.selected = Math.max(0, (state.selected || 0) - 1);
|
|
1538
1578
|
break;
|
|
1539
1579
|
case 'move.pagedown':
|
|
1540
|
-
if (plansMove(state, PAGE_ROWS) || followsMove(state, PAGE_ROWS) || rulesScreenMove(state, PAGE_ROWS))
|
|
1580
|
+
if (narrativesMove(state, PAGE_ROWS) || plansMove(state, PAGE_ROWS) || followsMove(state, PAGE_ROWS) || rulesScreenMove(state, PAGE_ROWS))
|
|
1541
1581
|
break;
|
|
1542
1582
|
if (analystChatScroll(state, PAGE_ROWS))
|
|
1543
1583
|
break;
|
|
@@ -1546,7 +1586,7 @@ export function applyAction(state, action, stack) {
|
|
|
1546
1586
|
state.selected = Math.min(rows().length - 1, (state.selected || 0) + PAGE_ROWS);
|
|
1547
1587
|
break;
|
|
1548
1588
|
case 'move.pageup':
|
|
1549
|
-
if (plansMove(state, -PAGE_ROWS) || followsMove(state, -PAGE_ROWS) || rulesScreenMove(state, -PAGE_ROWS))
|
|
1589
|
+
if (narrativesMove(state, -PAGE_ROWS) || plansMove(state, -PAGE_ROWS) || followsMove(state, -PAGE_ROWS) || rulesScreenMove(state, -PAGE_ROWS))
|
|
1550
1590
|
break;
|
|
1551
1591
|
if (analystChatScroll(state, -PAGE_ROWS))
|
|
1552
1592
|
break;
|
|
@@ -1555,14 +1595,14 @@ export function applyAction(state, action, stack) {
|
|
|
1555
1595
|
state.selected = Math.max(0, (state.selected || 0) - PAGE_ROWS);
|
|
1556
1596
|
break;
|
|
1557
1597
|
case 'move.top':
|
|
1558
|
-
if (plansMove(state, -1e9) || followsMove(state, -1e9))
|
|
1598
|
+
if (narrativesMove(state, -1e9) || plansMove(state, -1e9) || followsMove(state, -1e9))
|
|
1559
1599
|
break;
|
|
1560
1600
|
if (chatScroll(state, -1e9))
|
|
1561
1601
|
break;
|
|
1562
1602
|
state.selected = 0;
|
|
1563
1603
|
break;
|
|
1564
1604
|
case 'move.bottom':
|
|
1565
|
-
if (plansMove(state, 1e9) || followsMove(state, 1e9))
|
|
1605
|
+
if (narrativesMove(state, 1e9) || plansMove(state, 1e9) || followsMove(state, 1e9))
|
|
1566
1606
|
break;
|
|
1567
1607
|
if (chatScroll(state, 1e9))
|
|
1568
1608
|
break;
|
|
@@ -1715,6 +1755,10 @@ export function applyAction(state, action, stack) {
|
|
|
1715
1755
|
state.copyUi.detail = null;
|
|
1716
1756
|
break;
|
|
1717
1757
|
} // task-1399: esc closes the wallet detail view before leaving the screen
|
|
1758
|
+
if (state.screen === 'narrative' && state.narrativesUi?.drill) {
|
|
1759
|
+
state.narrativesUi.drill = null;
|
|
1760
|
+
break;
|
|
1761
|
+
} // task-1818: esc in a drill-down returns to the heat table
|
|
1718
1762
|
const prev = stack.pop();
|
|
1719
1763
|
if (prev)
|
|
1720
1764
|
state.screen = prev;
|
|
@@ -1982,6 +2026,46 @@ export function applyAction(state, action, stack) {
|
|
|
1982
2026
|
stack.push(state.screen);
|
|
1983
2027
|
state.screen = 'narrative';
|
|
1984
2028
|
state.narrative = computeNarrativeView(state, state.narrative?.window ?? '1h');
|
|
2029
|
+
if (state.narrativesUi?.mode === 'heat')
|
|
2030
|
+
kickNarratives(state); // task-1818: back on the heat table → refresh if it is older than 60 s
|
|
2031
|
+
break;
|
|
2032
|
+
}
|
|
2033
|
+
// task-1818: n flips the theme board ⇄ the EXPERIMENTAL heat table. The first flip starts the tracker build (the recorder's
|
|
2034
|
+
// last 26 h, ~30 s; never at app start); the table shows "building…" until it lands, later refreshes come from the tick.
|
|
2035
|
+
case 'narrative.view': {
|
|
2036
|
+
if (state.screen !== 'narrative')
|
|
2037
|
+
break;
|
|
2038
|
+
const ui = narrativesUiOf(state);
|
|
2039
|
+
ui.mode = ui.mode === 'heat' ? 'board' : 'heat';
|
|
2040
|
+
ui.drill = null;
|
|
2041
|
+
if (ui.mode === 'heat')
|
|
2042
|
+
kickNarratives(state);
|
|
2043
|
+
break;
|
|
2044
|
+
}
|
|
2045
|
+
// task-1818: ⏎ on the heat table drills into the selected narrative; ⏎ on a member opens that coin's token screen (live
|
|
2046
|
+
// mode; fixtures say so). On the theme board ⏎ keeps its old meaning (the shared `open`).
|
|
2047
|
+
case 'narrative.drill': {
|
|
2048
|
+
if (state.screen !== 'narrative')
|
|
2049
|
+
break;
|
|
2050
|
+
const ui = state.narrativesUi;
|
|
2051
|
+
if (ui?.mode !== 'heat')
|
|
2052
|
+
return applyAction(state, 'open', stack);
|
|
2053
|
+
if (!ui.drill) {
|
|
2054
|
+
const r = heatRows(state)[ui.selected];
|
|
2055
|
+
if (r)
|
|
2056
|
+
ui.drill = { id: r.id, selected: 0 };
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2059
|
+
const m = drillDetail(state)?.memberList[ui.drill.selected];
|
|
2060
|
+
if (!m)
|
|
2061
|
+
break;
|
|
2062
|
+
if (!live) {
|
|
2063
|
+
state.pendingToast = `token detail needs live mode (${m.symbol} ${m.mint.slice(0, 8)}…)`;
|
|
2064
|
+
break;
|
|
2065
|
+
}
|
|
2066
|
+
live.openToken(seedForMint(state, live, m.mint, m.symbol));
|
|
2067
|
+
stack.push(state.screen);
|
|
2068
|
+
state.screen = 'token';
|
|
1985
2069
|
break;
|
|
1986
2070
|
}
|
|
1987
2071
|
case 'narrative.window': {
|
|
@@ -4283,6 +4367,11 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4283
4367
|
// execution moment, pause / kill) draws over this terminal while it follows. Calm mode = reduced motion.
|
|
4284
4368
|
state.agentHud = { story: followSession.story, clock: () => Date.now(), dataLabel: args.replay ? 'REPLAY DATA' : null };
|
|
4285
4369
|
void followSession.start();
|
|
4370
|
+
// task-1427: the open coin's live holder/rug signals ride the spectator stream too (the streamer overlay's coin card).
|
|
4371
|
+
{
|
|
4372
|
+
const fs = followSession;
|
|
4373
|
+
state.live?.signals.listeners.add((sig) => fs.liveFlag(sig));
|
|
4374
|
+
}
|
|
4286
4375
|
if (size()[1] < HUD_MIN_H)
|
|
4287
4376
|
say('following agents over MCP · ctrl+p pause · ctrl+k stop · any key takes over', 'ai', 6);
|
|
4288
4377
|
if (agentPaper)
|
|
@@ -6169,6 +6258,9 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
6169
6258
|
say(state.updateNotice, 'info', 8);
|
|
6170
6259
|
state.updateNotice = null;
|
|
6171
6260
|
}
|
|
6261
|
+
// task-1818: refresh the EXPERIMENTAL narratives tracker at most every 60 s while its heat table is on screen.
|
|
6262
|
+
if (state.screen === 'narrative' && state.narrativesUi?.mode === 'heat' && !state.paused)
|
|
6263
|
+
kickNarratives(state);
|
|
6172
6264
|
// task-1508: the autotrade screen re-reads the paper runner's status file every 2 s while it is open.
|
|
6173
6265
|
if (state.screen === 'autotrade' && state.autotradeUi)
|
|
6174
6266
|
autotradeTick(state.autotradeUi);
|
|
@@ -116,7 +116,10 @@ export class McpFollowSession {
|
|
|
116
116
|
};
|
|
117
117
|
this.follow = new FollowController({ controls, activityCap: Math.max(1, opts.activityCap ?? 200) });
|
|
118
118
|
this.story = new AgentStory(this.agentLabel);
|
|
119
|
-
|
|
119
|
+
// task-1427: a live signal the TERMINAL fired (liveFlag) rides the hub for the stream only; it is not an agent call
|
|
120
|
+
// or an agent thought, so the follow view and the story never fold it (the story would show a note as a thought).
|
|
121
|
+
this.hub.on((ev) => { if (ev.phase === 'note' && ev.tool === 'live_signal')
|
|
122
|
+
return; this.follow.apply(ev); this.story.apply(ev); });
|
|
120
123
|
}
|
|
121
124
|
/** Arm the rendezvous (so a `dexterm mcp` server starts emitting) and begin tailing. Idempotent. */
|
|
122
125
|
async start() {
|
|
@@ -304,6 +307,23 @@ export class McpFollowSession {
|
|
|
304
307
|
}
|
|
305
308
|
catch { /* ignore */ }
|
|
306
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* task-1427: a live holder / rug signal the owner's terminal fired for a coin (the task-1404 engine) → one `note`
|
|
312
|
+
* event in the hub, so the streamer overlay's coin card can show it with its age. Only while armed (an agent is
|
|
313
|
+
* being followed); it describes data the terminal already read, and carries nothing anyone can act on.
|
|
314
|
+
*/
|
|
315
|
+
liveFlag(s) {
|
|
316
|
+
if (!this.armed)
|
|
317
|
+
return;
|
|
318
|
+
const detail = sanitizeDetail({ coin: { mint: s.mint }, liveFlags: [{ kind: s.kind, level: s.level, title: s.title, text: s.text, source: s.source, at: s.at, ageSec: Math.max(0, s.dataAgeMs / 1000), stale: s.stale }] });
|
|
319
|
+
if (!detail?.liveFlags)
|
|
320
|
+
return;
|
|
321
|
+
this.hub.emit({ phase: 'note', agent: this.currentAgent, transport: 'mcp', tool: 'live_signal', summary: `${s.title}: ${s.text}`.slice(0, 140), detail, ok: true, t: s.at });
|
|
322
|
+
try {
|
|
323
|
+
this.opts.onEvent?.();
|
|
324
|
+
}
|
|
325
|
+
catch { /* ignore */ }
|
|
326
|
+
}
|
|
307
327
|
/** True when the terminal should mirror the agent: the agent still has control and is active. */
|
|
308
328
|
canFollow() {
|
|
309
329
|
if (this.follow.controlState !== 'agent')
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// `dexterm narratives [top|detail ID|of MINT|export] [--db PATH] [--json] [--at ISO] [--lookback-h N] [--out FILE]`
|
|
2
|
+
// (task-1818, EXPERIMENTAL). The MEMES + NARRATIVES tracker over the history recorder: coins clustered by the theme
|
|
3
|
+
// their name/ticker carries, with per-window heat metrics and lifecycle states. Keyless, local, read-only on the DB.
|
|
4
|
+
// Measured heat, never advice.
|
|
5
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
6
|
+
import { writeFileSync } from 'node:fs';
|
|
7
|
+
import { defaultDbPath } from '../../data/src/index.js';
|
|
8
|
+
import { trackerFromRecorder, toExport, renderTable, coinField, embedderFromEnv, HEAT_NOTE, LIFECYCLE_RULES } from '../../narratives/src/index.js';
|
|
9
|
+
export const NARRATIVES_HELP = `dexterm narratives: the MEMES + NARRATIVES tracker (task-1818, EXPERIMENTAL). No network, no model, no key.
|
|
10
|
+
|
|
11
|
+
dexterm narratives [top] the hottest narratives (rank = memetic traction score)
|
|
12
|
+
dexterm narratives detail ID one narrative: windows, lifecycle rule, member coins in spawn order
|
|
13
|
+
dexterm narratives of MINT which narrative a coin belongs to
|
|
14
|
+
dexterm narratives export [--out FILE] the dexterm-narratives/1 JSON the strategy tracker page reads
|
|
15
|
+
|
|
16
|
+
--db PATH defaults to DEXTERM_DB_PATH, else ~/.local/share/dexterm/dexterm.db (opened read-only)
|
|
17
|
+
--at ISO replay up to this time instead of now (point-in-time: only rows recorded before it)
|
|
18
|
+
--lookback-h N hours of history to replay (default 26)
|
|
19
|
+
--limit N rows (default 20)
|
|
20
|
+
--json machine-readable output
|
|
21
|
+
|
|
22
|
+
Coins are grouped by what their name/ticker says (ticker puns, animals, celebrities/politics, AI agents,
|
|
23
|
+
seasonal, copy-cats of a runner); a coin needs no real project to belong. Lifecycle states:
|
|
24
|
+
${LIFECYCLE_RULES.map(([s, r]) => ` ${s.padEnd(9)} ${r}`).join('\n')}
|
|
25
|
+
${HEAT_NOTE}
|
|
26
|
+
Embedder: DEXTERM_NARRATIVE_EMBEDDER=lexical (default, built in) | minilm (needs DEXTERM_EMBED_MODULE_DIR) | ollama[:model].`;
|
|
27
|
+
function parse(argv) {
|
|
28
|
+
const opts = {};
|
|
29
|
+
const pos = [];
|
|
30
|
+
for (let i = 0; i < argv.length; i++) {
|
|
31
|
+
const a = argv[i];
|
|
32
|
+
if (!a.startsWith('--')) {
|
|
33
|
+
pos.push(a);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const k = a.slice(2);
|
|
37
|
+
const v = argv[i + 1];
|
|
38
|
+
if (v !== undefined && !v.startsWith('--') && k !== 'json' && k !== 'help') {
|
|
39
|
+
opts[k] = v;
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
opts[k] = true;
|
|
44
|
+
}
|
|
45
|
+
return { pos, opts };
|
|
46
|
+
}
|
|
47
|
+
export async function narrativesCommand(argv, io) {
|
|
48
|
+
const { pos, opts } = parse(argv);
|
|
49
|
+
if (opts.help || pos[0] === 'help') {
|
|
50
|
+
io.stdout.write(NARRATIVES_HELP + '\n');
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
const cmd = pos[0] ?? 'top';
|
|
54
|
+
if (!['top', 'detail', 'of', 'export'].includes(cmd)) {
|
|
55
|
+
io.stderr.write(`dexterm narratives: unknown command "${cmd}" (top | detail ID | of MINT | export)\n`);
|
|
56
|
+
return 2;
|
|
57
|
+
}
|
|
58
|
+
const path = typeof opts.db === 'string' ? opts.db : (io.env.DEXTERM_DB_PATH || defaultDbPath(io.env));
|
|
59
|
+
if (path === ':memory:') {
|
|
60
|
+
io.stderr.write('dexterm narratives: an in-memory DB has no history\n');
|
|
61
|
+
return 2;
|
|
62
|
+
}
|
|
63
|
+
const at = typeof opts.at === 'string' ? Date.parse(opts.at) : Date.now();
|
|
64
|
+
if (!Number.isFinite(at)) {
|
|
65
|
+
io.stderr.write('dexterm narratives: --at needs an ISO time\n');
|
|
66
|
+
return 2;
|
|
67
|
+
}
|
|
68
|
+
const lookbackH = typeof opts['lookback-h'] === 'string' ? Number(opts['lookback-h']) : 26;
|
|
69
|
+
if (!(Number.isFinite(lookbackH) && lookbackH > 0)) {
|
|
70
|
+
io.stderr.write('dexterm narratives: --lookback-h needs a number of hours above 0\n');
|
|
71
|
+
return 2;
|
|
72
|
+
}
|
|
73
|
+
const limitN = typeof opts.limit === 'string' ? Math.floor(Number(opts.limit)) : 20;
|
|
74
|
+
if (!Number.isFinite(limitN) || limitN < 1) {
|
|
75
|
+
io.stderr.write('dexterm narratives: --limit needs a whole number of 1 or more\n');
|
|
76
|
+
return 2;
|
|
77
|
+
}
|
|
78
|
+
const limit = Math.min(200, limitN);
|
|
79
|
+
let db = null;
|
|
80
|
+
try {
|
|
81
|
+
db = new DatabaseSync(path, { readOnly: true });
|
|
82
|
+
if (!db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'token_snapshots'").get()) {
|
|
83
|
+
io.stderr.write(`dexterm narratives: no recorded history in ${path} (run the app with the recorder on first)\n`);
|
|
84
|
+
return 2;
|
|
85
|
+
}
|
|
86
|
+
const t0 = performance.now();
|
|
87
|
+
const tr = await trackerFromRecorder(db, { now: at, lookbackMs: lookbackH * 3_600_000, embedder: embedderFromEnv(io.env) });
|
|
88
|
+
const ms = Math.round(performance.now() - t0);
|
|
89
|
+
if (cmd === 'export') {
|
|
90
|
+
const ex = toExport(tr, { limit: Math.max(limit, 50), now: at });
|
|
91
|
+
const json = JSON.stringify(ex, null, 1) + '\n';
|
|
92
|
+
if (typeof opts.out === 'string') {
|
|
93
|
+
writeFileSync(opts.out, json);
|
|
94
|
+
io.stdout.write(`wrote ${opts.out}: ${ex.narratives.length} narratives over ${ex.coins} coins (${ms} ms)\n`);
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
io.stdout.write(json);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
if (cmd === 'detail' || cmd === 'of') {
|
|
101
|
+
const key = pos[1];
|
|
102
|
+
if (!key) {
|
|
103
|
+
io.stderr.write(`dexterm narratives ${cmd}: needs ${cmd === 'detail' ? 'a narrative id' : 'a mint'}\n`);
|
|
104
|
+
return 2;
|
|
105
|
+
}
|
|
106
|
+
const n = cmd === 'of' ? tr.narrativeOf(key) : null;
|
|
107
|
+
if (cmd === 'of' && !n) {
|
|
108
|
+
io.stdout.write(opts.json ? JSON.stringify({ mint: key, narrative: null, note: 'not recorded in the lookback window' }) + '\n' : `${coinField(null)} (no recorded sighting in the last ${lookbackH} h)\n`);
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
const d = tr.detail(cmd === 'of' ? n.narrative : key);
|
|
112
|
+
if (!d) {
|
|
113
|
+
io.stderr.write(`dexterm narratives: unknown narrative id ${key}\n`);
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
116
|
+
if (opts.json) {
|
|
117
|
+
io.stdout.write(JSON.stringify(cmd === 'of' ? { ...n, detail: d } : d, null, 1) + '\n');
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
if (n)
|
|
121
|
+
io.stdout.write(coinField(n) + '\n');
|
|
122
|
+
io.stdout.write(renderTable([d], 150) + '\n\n');
|
|
123
|
+
io.stdout.write(`lifecycle rule: ${d.lifecycleRule}\nmembers (spawn order, newest ${Math.min(limit, d.memberList.length)} of ${d.memberList.length}):\n`);
|
|
124
|
+
for (const m of d.memberList.slice(-limit))
|
|
125
|
+
io.stdout.write(` #${String(m.spawn).padEnd(4)} ${m.symbol.padEnd(12)} ${m.name.slice(0, 28).padEnd(28)} ${m.x == null ? ' —' : (m.x.toFixed(2) + 'x').padStart(7)} ${m.mint}\n`);
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
const rows = tr.top(limit);
|
|
129
|
+
if (opts.json) {
|
|
130
|
+
io.stdout.write(JSON.stringify(toExport(tr, { limit, now: at }), null, 1) + '\n');
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
io.stdout.write(`NARRATIVES (EXPERIMENTAL) · as of ${tr.lastTickAt ? new Date(tr.lastTickAt).toISOString() : '—'} · ${tr.coins.size} coins over ${lookbackH} h · embedder ${tr.embedder.id} · ${ms} ms\n`);
|
|
134
|
+
io.stdout.write(renderTable(rows, 150) + '\n');
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
io.stderr.write(`dexterm narratives: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
try {
|
|
143
|
+
db?.close();
|
|
144
|
+
}
|
|
145
|
+
catch { /* ignore */ }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { NarrativeTracker, RecorderNarratives, coinField, embedderFromEnv, sanitize } from '../../narratives/src/index.js';
|
|
2
|
+
/** Later refreshes run at most this often (the first one runs on open). */
|
|
3
|
+
export const NARRATIVES_REFRESH_MS = 60_000;
|
|
4
|
+
/** Rows in the heat table. */
|
|
5
|
+
export const HEAT_LIMIT = 50;
|
|
6
|
+
export const UNTRUSTED_NOTE = 'names are untrusted, shown control-stripped';
|
|
7
|
+
const services = new WeakMap();
|
|
8
|
+
const screenTrackers = new WeakMap();
|
|
9
|
+
export function narrativesUiOf(state) {
|
|
10
|
+
if (!state.narrativesUi)
|
|
11
|
+
state.narrativesUi = { mode: 'board', selected: 0, drill: null, busy: false, error: null, source: null, refreshedAt: 0, buildMs: null };
|
|
12
|
+
return state.narrativesUi;
|
|
13
|
+
}
|
|
14
|
+
const dbOf = (state) => state.live?.recorder?.database ?? null;
|
|
15
|
+
export const narrativesSource = (state) => (dbOf(state) ? 'recorded' : 'screen');
|
|
16
|
+
/** The tracker ALREADY built for this state, or null. Never builds (the coin pane / rules / renders call this). */
|
|
17
|
+
export function currentTracker(state) {
|
|
18
|
+
const db = dbOf(state);
|
|
19
|
+
return (db ? services.get(db)?.tracker ?? null : null) ?? screenTrackers.get(state) ?? null;
|
|
20
|
+
}
|
|
21
|
+
/** Install a tracker for a state with no recorder DB (tests; the screen-rows fallback uses the same slot). */
|
|
22
|
+
export function setNarrativeTracker(state, tr) { if (tr)
|
|
23
|
+
screenTrackers.set(state, tr);
|
|
24
|
+
else
|
|
25
|
+
screenTrackers.delete(state); }
|
|
26
|
+
/**
|
|
27
|
+
* Build or refresh the tracker. Recorder DB: the per-process RecorderNarratives (first call = the 26 h replay, later
|
|
28
|
+
* calls replay only new rows; it self-throttles to NARRATIVES_REFRESH_MS). No DB: the visible screener rows are fed
|
|
29
|
+
* to a per-state tracker (first sightings once, a sample per row each call) and ticked now.
|
|
30
|
+
*/
|
|
31
|
+
export async function ensureNarratives(state, now = Date.now()) {
|
|
32
|
+
const db = dbOf(state);
|
|
33
|
+
if (db) {
|
|
34
|
+
let svc = services.get(db);
|
|
35
|
+
if (!svc)
|
|
36
|
+
services.set(db, (svc = new RecorderNarratives(db, { embedder: embedderFromEnv(process.env), minRefreshMs: NARRATIVES_REFRESH_MS })));
|
|
37
|
+
return svc.refresh(now);
|
|
38
|
+
}
|
|
39
|
+
const rows = state.screener.rows.filter((r) => r.mint && !r.skeleton);
|
|
40
|
+
let tr = screenTrackers.get(state) ?? null;
|
|
41
|
+
if (!rows.length)
|
|
42
|
+
return tr;
|
|
43
|
+
if (!tr)
|
|
44
|
+
screenTrackers.set(state, (tr = new NarrativeTracker()));
|
|
45
|
+
const at = (r) => Math.min(now, r.fetchedAt ?? now);
|
|
46
|
+
await tr.addCoins(rows.map((r) => ({ mint: r.mint, t: at(r), symbol: r.symbol, name: r.name })));
|
|
47
|
+
for (const r of rows)
|
|
48
|
+
tr.addSample({ mint: r.mint, t: at(r), price: r.price, liq: r.liq, mcap: r.mcap, vol5m: r.vol5m, vol1h: r.vol1h, vol6h: r.vol6h, vol24h: r.vol24 });
|
|
49
|
+
tr.tick(now, true);
|
|
50
|
+
return tr;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Start a build/refresh in the background unless one is running or the last finished < NARRATIVES_REFRESH_MS ago
|
|
54
|
+
* (`force` skips the age check). The work starts on a short timer so the "building…" frame paints first; the recorder
|
|
55
|
+
* replay then yields to the event loop every 40 ms (RecorderNarratives), so frames keep drawing. Returns whether it started.
|
|
56
|
+
*/
|
|
57
|
+
export function kickNarratives(state, opts = {}) {
|
|
58
|
+
const ui = narrativesUiOf(state);
|
|
59
|
+
const now = opts.now ?? Date.now();
|
|
60
|
+
if (ui.busy)
|
|
61
|
+
return false;
|
|
62
|
+
if (!opts.force && ui.refreshedAt && now - ui.refreshedAt < NARRATIVES_REFRESH_MS)
|
|
63
|
+
return false;
|
|
64
|
+
ui.busy = true;
|
|
65
|
+
ui.error = null;
|
|
66
|
+
const run = () => {
|
|
67
|
+
const t0 = Date.now();
|
|
68
|
+
void ensureNarratives(state, Date.now())
|
|
69
|
+
.then(() => { ui.source = narrativesSource(state); ui.buildMs = Date.now() - t0; })
|
|
70
|
+
.catch((e) => { ui.error = e instanceof Error ? e.message : String(e); })
|
|
71
|
+
.finally(() => { ui.busy = false; ui.refreshedAt = Date.now(); opts.onDone?.(); });
|
|
72
|
+
};
|
|
73
|
+
const tm = setTimeout(run, opts.delayMs ?? 40);
|
|
74
|
+
tm.unref?.();
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/** The heat table's rows (control-stripped labels). The screen-rows fallback has no history, so solo coins show too. */
|
|
78
|
+
export function heatRows(state) {
|
|
79
|
+
const tr = currentTracker(state);
|
|
80
|
+
if (!tr)
|
|
81
|
+
return [];
|
|
82
|
+
return tr.top(HEAT_LIMIT, { minMembers: narrativesSource(state) === 'screen' ? 1 : 2 }).map((s) => sanitize(s));
|
|
83
|
+
}
|
|
84
|
+
/** The drilled narrative (with its member list in spawn order), or null. */
|
|
85
|
+
export function drillDetail(state) {
|
|
86
|
+
const id = state.narrativesUi?.drill?.id;
|
|
87
|
+
const tr = currentTracker(state);
|
|
88
|
+
if (!id || !tr)
|
|
89
|
+
return null;
|
|
90
|
+
const d = tr.detail(id);
|
|
91
|
+
if (d)
|
|
92
|
+
d.memberList.sort((a, b) => a.spawn - b.spawn);
|
|
93
|
+
return d;
|
|
94
|
+
}
|
|
95
|
+
/** One coin's narrative from the already-built tracker (null = not built or not tracked). */
|
|
96
|
+
export function narrativeFor(state, mint) {
|
|
97
|
+
return currentTracker(state)?.narrativeOf(mint) ?? null;
|
|
98
|
+
}
|
|
99
|
+
/** The coin pane's Narrative row value + level: warn when copy-cat saturated or fading/dead, else the neutral na (heat is
|
|
100
|
+
* never shown as a green "ok": it is not a quality mark of the coin). */
|
|
101
|
+
export function narrativeCell(state, mint) {
|
|
102
|
+
if (!currentTracker(state))
|
|
103
|
+
return ['open N → n to build', 'na'];
|
|
104
|
+
const n = narrativeFor(state, mint);
|
|
105
|
+
const text = coinField(n).replace(/^narrative: /, '');
|
|
106
|
+
if (!n)
|
|
107
|
+
return [text, 'na'];
|
|
108
|
+
return [text, n.saturated || n.lifecycle === 'fading' || n.lifecycle === 'dead' ? 'warn' : 'na'];
|
|
109
|
+
}
|
|
110
|
+
/** The rules' narrative fields for one coin (all null when not built / not tracked; a rule clause on them is then false). */
|
|
111
|
+
export function narrativeRuleFields(state, mint) {
|
|
112
|
+
const n = narrativeFor(state, mint);
|
|
113
|
+
return { narrativeRank: n?.rank ?? null, narrativeScore: n?.score ?? null, narrativeLifecycle: n?.lifecycle ?? null, narrativeSaturated: n ? n.saturated : null };
|
|
114
|
+
}
|