dexterm-cli 0.0.0-stage → 0.1.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/LICENSE +661 -0
- package/dist/packages/agents/src/audit.js +122 -0
- package/dist/packages/agents/src/bus.js +379 -0
- package/dist/packages/agents/src/executor.js +177 -0
- package/dist/packages/agents/src/gate.js +176 -0
- package/dist/packages/agents/src/identity.js +179 -0
- package/dist/packages/agents/src/index.js +15 -0
- package/dist/packages/agents/src/proposals.js +153 -0
- package/dist/packages/agents/src/schema.js +87 -0
- package/dist/packages/agents/src/sdk.js +140 -0
- package/dist/packages/agents/src/tiers.js +114 -0
- package/dist/packages/agents/src/tools.js +69 -0
- package/dist/packages/agents/src/ws.js +158 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
- package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +67 -0
- package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -0
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
- package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
- package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
- package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
- package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
- package/dist/packages/cli/bin/dexterm.mjs +11 -0
- package/dist/packages/cli/src/agent-cmd.js +303 -0
- package/dist/packages/cli/src/agent-roster.js +26 -0
- package/dist/packages/cli/src/analyst/backend.js +526 -0
- package/dist/packages/cli/src/analyst/results.js +38 -0
- package/dist/packages/cli/src/analyst/session.js +319 -0
- package/dist/packages/cli/src/audit.js +51 -0
- package/dist/packages/cli/src/backtest-cmd.js +149 -0
- package/dist/packages/cli/src/boot-state.js +62 -0
- package/dist/packages/cli/src/custom.js +261 -0
- package/dist/packages/cli/src/display.js +191 -0
- package/dist/packages/cli/src/doctor.js +361 -0
- package/dist/packages/cli/src/feed.js +634 -0
- package/dist/packages/cli/src/fixtures.js +275 -0
- package/dist/packages/cli/src/follow-cmd.js +221 -0
- package/dist/packages/cli/src/images-config.js +59 -0
- package/dist/packages/cli/src/index.js +31 -0
- package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
- package/dist/packages/cli/src/larp-record.js +21 -0
- package/dist/packages/cli/src/live/copy.js +80 -0
- package/dist/packages/cli/src/live/flow.js +233 -0
- package/dist/packages/cli/src/live/index.js +509 -0
- package/dist/packages/cli/src/live/intel.js +227 -0
- package/dist/packages/cli/src/live/newpairs.js +334 -0
- package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
- package/dist/packages/cli/src/live/screens/token-live.js +459 -0
- package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
- package/dist/packages/cli/src/live/token.js +585 -0
- package/dist/packages/cli/src/live/trade.js +929 -0
- package/dist/packages/cli/src/live/watchlist.js +178 -0
- package/dist/packages/cli/src/main.js +5583 -0
- package/dist/packages/cli/src/narrative-cmd.js +76 -0
- package/dist/packages/cli/src/pack-cmd.js +323 -0
- package/dist/packages/cli/src/plan-cmd.js +278 -0
- package/dist/packages/cli/src/popup.js +810 -0
- package/dist/packages/cli/src/rules-cmd.js +131 -0
- package/dist/packages/cli/src/rules.js +339 -0
- package/dist/packages/cli/src/scan/command.js +57 -0
- package/dist/packages/cli/src/scan/engine.js +419 -0
- package/dist/packages/cli/src/scan/observe.js +64 -0
- package/dist/packages/cli/src/scan/ui.js +98 -0
- package/dist/packages/cli/src/screener/filter.js +241 -0
- package/dist/packages/cli/src/screener/presets.js +80 -0
- package/dist/packages/cli/src/screener/rows.js +146 -0
- package/dist/packages/cli/src/screener/rug.js +50 -0
- package/dist/packages/cli/src/screener/sort.js +52 -0
- package/dist/packages/cli/src/screener/view.js +183 -0
- package/dist/packages/cli/src/screens/analyst-session.js +343 -0
- package/dist/packages/cli/src/screens/analyst.js +326 -0
- package/dist/packages/cli/src/screens/boot.js +74 -0
- package/dist/packages/cli/src/screens/bubbles.js +255 -0
- package/dist/packages/cli/src/screens/coin.js +33 -0
- package/dist/packages/cli/src/screens/follows.js +201 -0
- package/dist/packages/cli/src/screens/kol.js +90 -0
- package/dist/packages/cli/src/screens/narrative.js +90 -0
- package/dist/packages/cli/src/screens/newpairs.js +150 -0
- package/dist/packages/cli/src/screens/overlay.js +257 -0
- package/dist/packages/cli/src/screens/plans.js +126 -0
- package/dist/packages/cli/src/screens/positions.js +176 -0
- package/dist/packages/cli/src/screens/rules.js +170 -0
- package/dist/packages/cli/src/screens/screener.js +459 -0
- package/dist/packages/cli/src/screens/social-panel.js +107 -0
- package/dist/packages/cli/src/screens/ticket.js +342 -0
- package/dist/packages/cli/src/screens/token.js +312 -0
- package/dist/packages/cli/src/screens/trending.js +181 -0
- package/dist/packages/cli/src/screens/watchlist.js +104 -0
- package/dist/packages/cli/src/social/demo.js +96 -0
- package/dist/packages/cli/src/social/view.js +250 -0
- package/dist/packages/cli/src/state.js +463 -0
- package/dist/packages/cli/src/theme-cmd.js +126 -0
- package/dist/packages/cli/src/trade.js +319 -0
- package/dist/packages/cli/src/voice-hud.js +42 -0
- package/dist/packages/cli/src/wallet-cmd.js +440 -0
- package/dist/packages/cli/src/wallet-command.js +31 -0
- package/dist/packages/cli/src/widgets.js +208 -0
- package/dist/packages/cli/src/workspace-cmd.js +68 -0
- package/dist/packages/cli/src/x-cmd.js +154 -0
- package/dist/packages/custom/src/config.js +213 -0
- package/dist/packages/custom/src/index.js +10 -0
- package/dist/packages/custom/src/keymap.js +107 -0
- package/dist/packages/custom/src/layout.js +235 -0
- package/dist/packages/custom/src/packs.js +633 -0
- package/dist/packages/custom/src/rules.js +704 -0
- package/dist/packages/custom/src/schema.js +515 -0
- package/dist/packages/custom/src/themes.js +176 -0
- package/dist/packages/custom/src/widget-html.js +222 -0
- package/dist/packages/custom/src/workspace.js +141 -0
- package/dist/packages/data/src/analyst-actions.js +133 -0
- package/dist/packages/data/src/budget.js +88 -0
- package/dist/packages/data/src/cache.js +142 -0
- package/dist/packages/data/src/candles.js +168 -0
- package/dist/packages/data/src/clock.js +64 -0
- package/dist/packages/data/src/credits.js +137 -0
- package/dist/packages/data/src/detail.js +97 -0
- package/dist/packages/data/src/fast-lane.js +321 -0
- package/dist/packages/data/src/fixtures.js +135 -0
- package/dist/packages/data/src/health.js +245 -0
- package/dist/packages/data/src/http.js +277 -0
- package/dist/packages/data/src/images/cache.js +141 -0
- package/dist/packages/data/src/images/decode-worker.js +28 -0
- package/dist/packages/data/src/images/decode.js +81 -0
- package/dist/packages/data/src/images/fetch.js +162 -0
- package/dist/packages/data/src/images/gif.js +214 -0
- package/dist/packages/data/src/images/index.js +10 -0
- package/dist/packages/data/src/images/jpeg.js +544 -0
- package/dist/packages/data/src/images/png.js +310 -0
- package/dist/packages/data/src/images/service.js +334 -0
- package/dist/packages/data/src/images/types.js +27 -0
- package/dist/packages/data/src/index.js +208 -0
- package/dist/packages/data/src/intel/helius-history.js +59 -0
- package/dist/packages/data/src/intel/holder-class.js +125 -0
- package/dist/packages/data/src/intel/rules.js +421 -0
- package/dist/packages/data/src/intel/scan.js +170 -0
- package/dist/packages/data/src/larp/evidence.js +288 -0
- package/dist/packages/data/src/larp/links.js +137 -0
- package/dist/packages/data/src/larp/robots.js +90 -0
- package/dist/packages/data/src/larp/safe-fetch.js +389 -0
- package/dist/packages/data/src/limiter.js +341 -0
- package/dist/packages/data/src/model.js +12 -0
- package/dist/packages/data/src/paper.js +331 -0
- package/dist/packages/data/src/rpc-config.js +126 -0
- package/dist/packages/data/src/rpc-pool.js +166 -0
- package/dist/packages/data/src/social/chain.js +83 -0
- package/dist/packages/data/src/social/dedupe.js +205 -0
- package/dist/packages/data/src/social/feed.js +462 -0
- package/dist/packages/data/src/social/firewall.js +110 -0
- package/dist/packages/data/src/social/index.js +11 -0
- package/dist/packages/data/src/social/kol.js +269 -0
- package/dist/packages/data/src/social/model.js +11 -0
- package/dist/packages/data/src/social/rank.js +184 -0
- package/dist/packages/data/src/social/rss.js +113 -0
- package/dist/packages/data/src/social/summary.js +104 -0
- package/dist/packages/data/src/social/venues.js +186 -0
- package/dist/packages/data/src/sources/dexscreener.js +170 -0
- package/dist/packages/data/src/sources/geckoterminal.js +133 -0
- package/dist/packages/data/src/sources/goplus.js +161 -0
- package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
- package/dist/packages/data/src/sources/jupiter.js +198 -0
- package/dist/packages/data/src/sources/pda.js +113 -0
- package/dist/packages/data/src/sources/pump-curve.js +58 -0
- package/dist/packages/data/src/sources/pumpportal.js +247 -0
- package/dist/packages/data/src/sources/rugcheck.js +71 -0
- package/dist/packages/data/src/sources/solana-rpc.js +299 -0
- package/dist/packages/data/src/sources/solana-ws.js +287 -0
- package/dist/packages/data/src/store.js +289 -0
- package/dist/packages/data/src/trade/bridge.js +464 -0
- package/dist/packages/data/src/trade/caps.js +323 -0
- package/dist/packages/data/src/trade/copy/auto.js +385 -0
- package/dist/packages/data/src/trade/copy/follows.js +103 -0
- package/dist/packages/data/src/trade/copy/index.js +9 -0
- package/dist/packages/data/src/trade/copy/mirror.js +91 -0
- package/dist/packages/data/src/trade/copy/stats.js +120 -0
- package/dist/packages/data/src/trade/copy/tracker.js +158 -0
- package/dist/packages/data/src/trade/copy/trades.js +87 -0
- package/dist/packages/data/src/trade/copy/types.js +6 -0
- package/dist/packages/data/src/trade/executor.js +933 -0
- package/dist/packages/data/src/trade/kill-switch.js +47 -0
- package/dist/packages/data/src/trade/link-store.js +52 -0
- package/dist/packages/data/src/trade/plans/confirm.js +157 -0
- package/dist/packages/data/src/trade/plans/engine.js +784 -0
- package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
- package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
- package/dist/packages/data/src/trade/plans/executor.js +11 -0
- package/dist/packages/data/src/trade/plans/index.js +12 -0
- package/dist/packages/data/src/trade/plans/journal.js +77 -0
- package/dist/packages/data/src/trade/plans/mandate.js +129 -0
- package/dist/packages/data/src/trade/plans/plan.js +179 -0
- package/dist/packages/data/src/trade/plans/service.js +168 -0
- package/dist/packages/data/src/trade/plans/types.js +6 -0
- package/dist/packages/data/src/trade/rpc-exec.js +229 -0
- package/dist/packages/data/src/trade/signer/client.js +197 -0
- package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
- package/dist/packages/data/src/trade/signer/host.js +97 -0
- package/dist/packages/data/src/trade/signer/main.js +121 -0
- package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
- package/dist/packages/data/src/trade/signer/protocol.js +92 -0
- package/dist/packages/data/src/trade/signer/service.js +400 -0
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
- package/dist/packages/data/src/trade/sol-usd.js +86 -0
- package/dist/packages/data/src/trade/swap-build.js +108 -0
- package/dist/packages/data/src/trade/token-delta.js +201 -0
- package/dist/packages/data/src/trade/tx-decode.js +632 -0
- package/dist/packages/data/src/util.js +24 -0
- package/dist/packages/data/src/watchlist.js +176 -0
- package/dist/packages/data/src/x/api.js +256 -0
- package/dist/packages/data/src/x/budget.js +66 -0
- package/dist/packages/data/src/x/config.js +49 -0
- package/dist/packages/data/src/x/index.js +10 -0
- package/dist/packages/data/src/x/mock-server.js +217 -0
- package/dist/packages/data/src/x/oauth.js +242 -0
- package/dist/packages/data/src/x/pkce.js +21 -0
- package/dist/packages/data/src/x/source.js +285 -0
- package/dist/packages/data/src/x/token-store.js +134 -0
- package/dist/packages/data/src/x/transport.js +71 -0
- package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
- package/dist/packages/mcp/assets/cases.json +1194 -0
- package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
- package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
- package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
- package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
- package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
- package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
- package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
- package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
- package/dist/packages/mcp/src/dossier.js +78 -0
- package/dist/packages/mcp/src/firewall.js +21 -0
- package/dist/packages/mcp/src/index.js +22 -0
- package/dist/packages/mcp/src/server.js +849 -0
- package/dist/packages/mcp/src/user-content.js +434 -0
- package/dist/packages/packs/src/backtest.js +183 -0
- package/dist/packages/packs/src/digest.js +95 -0
- package/dist/packages/packs/src/firewall.js +57 -0
- package/dist/packages/packs/src/index.js +19 -0
- package/dist/packages/packs/src/larp-eval.js +100 -0
- package/dist/packages/packs/src/narrative.js +309 -0
- package/dist/packages/packs/src/reason.js +135 -0
- package/dist/packages/packs/src/rug-model.js +137 -0
- package/dist/packages/packs/src/schema.js +54 -0
- package/dist/packages/packs/src/token-dossier.js +174 -0
- package/dist/packages/packs/src/verdicts.js +17 -0
- package/dist/packages/packs/src/wallet-dossier.js +105 -0
- package/dist/packages/popup/src/config.js +71 -0
- package/dist/packages/popup/src/dock.js +271 -0
- package/dist/packages/popup/src/host.js +210 -0
- package/dist/packages/popup/src/index.js +12 -0
- package/dist/packages/popup/src/loopback.js +53 -0
- package/dist/packages/popup/src/open.js +183 -0
- package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
- package/dist/packages/popup/src/pages/bubbles.css +23 -0
- package/dist/packages/popup/src/pages/bubbles.html +36 -0
- package/dist/packages/popup/src/pages/bubbles.js +214 -0
- package/dist/packages/popup/src/pages/chart-lib.js +262 -0
- package/dist/packages/popup/src/pages/chart.css +50 -0
- package/dist/packages/popup/src/pages/chart.html +56 -0
- package/dist/packages/popup/src/pages/chart.js +554 -0
- package/dist/packages/popup/src/pages/common.css +34 -0
- package/dist/packages/popup/src/pages/common.js +187 -0
- package/dist/packages/popup/src/pages/feeds.html +20 -0
- package/dist/packages/popup/src/pages/feeds.js +77 -0
- package/dist/packages/popup/src/pages/narrative.html +20 -0
- package/dist/packages/popup/src/pages/narrative.js +61 -0
- package/dist/packages/popup/src/pages/plans.css +30 -0
- package/dist/packages/popup/src/pages/plans.html +26 -0
- package/dist/packages/popup/src/pages/plans.js +82 -0
- package/dist/packages/popup/src/pages/roster.css +41 -0
- package/dist/packages/popup/src/pages/roster.html +25 -0
- package/dist/packages/popup/src/pages/roster.js +75 -0
- package/dist/packages/popup/src/pages/token.html +20 -0
- package/dist/packages/popup/src/pages/token.js +112 -0
- package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
- package/dist/packages/popup/src/pages/widget.css +5 -0
- package/dist/packages/popup/src/pages/widget.html +21 -0
- package/dist/packages/popup/src/pages/widget.js +85 -0
- package/dist/packages/popup/src/protocol.js +31 -0
- package/dist/packages/popup/src/selftest.js +156 -0
- package/dist/packages/popup/src/server.js +582 -0
- package/dist/packages/popup/src/sinkproxy.js +167 -0
- package/dist/packages/popup/src/widget-html.js +231 -0
- package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
- package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
- package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
- package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
- package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
- package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
- package/dist/packages/recorder/src/cli.js +113 -0
- package/dist/packages/recorder/src/export.js +71 -0
- package/dist/packages/recorder/src/index.js +20 -0
- package/dist/packages/recorder/src/labels.js +151 -0
- package/dist/packages/recorder/src/open.js +29 -0
- package/dist/packages/recorder/src/recorder.js +553 -0
- package/dist/packages/recorder/src/schedule.js +43 -0
- package/dist/packages/recorder/src/schema.js +135 -0
- package/dist/packages/recorder/src/snapshot.js +191 -0
- package/dist/packages/recorder/src/sweep.js +52 -0
- package/dist/packages/recorder/src/verdicts.js +52 -0
- package/dist/packages/tui/src/bubbles.js +415 -0
- package/dist/packages/tui/src/canvas.js +370 -0
- package/dist/packages/tui/src/chrome.js +85 -0
- package/dist/packages/tui/src/color.js +133 -0
- package/dist/packages/tui/src/flush.js +62 -0
- package/dist/packages/tui/src/fmt.js +188 -0
- package/dist/packages/tui/src/fx-trade.js +278 -0
- package/dist/packages/tui/src/fx.js +527 -0
- package/dist/packages/tui/src/images/blocks.js +141 -0
- package/dist/packages/tui/src/images/coin.js +22 -0
- package/dist/packages/tui/src/images/detect.js +140 -0
- package/dist/packages/tui/src/images/index.js +7 -0
- package/dist/packages/tui/src/images/layer.js +146 -0
- package/dist/packages/tui/src/images/pixels.js +157 -0
- package/dist/packages/tui/src/images/protocols.js +87 -0
- package/dist/packages/tui/src/index.js +15 -0
- package/dist/packages/tui/src/keymap.js +355 -0
- package/dist/packages/tui/src/layout.js +11 -0
- package/dist/packages/tui/src/sound.js +177 -0
- package/dist/packages/tui/src/theme.js +182 -0
- package/dist/packages/tui/src/widgets.js +598 -0
- package/dist/packages/voice/src/controller.js +395 -0
- package/dist/packages/voice/src/doctor.js +63 -0
- package/dist/packages/voice/src/engines.js +44 -0
- package/dist/packages/voice/src/index.js +16 -0
- package/dist/packages/voice/src/router.js +502 -0
- package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
- package/dist/packages/voice/src/stt/apple.js +179 -0
- package/dist/packages/voice/src/stt/fake.js +34 -0
- package/dist/packages/voice/src/stt/whisper.js +102 -0
- package/dist/packages/voice/src/summary.js +44 -0
- package/dist/packages/voice/src/tts/fake.js +30 -0
- package/dist/packages/voice/src/tts/say.js +96 -0
- package/dist/packages/voice/src/types.js +3 -0
- package/package.json +42 -4
- package/README.md +0 -3
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// dexterm — PAPER trade ticket state machine (task-1310 T0, SPEC §6.1). Pure and TTY-free so the
|
|
2
|
+
// ticket maths and the keypress confirm gate are unit-testable. The interactive loop and the live
|
|
3
|
+
// binding drive this reducer and perform the side effects (quote fetch, executor.execute).
|
|
4
|
+
//
|
|
5
|
+
// Safety: this file builds an *order* and reads a *quote*. It never signs, holds a key, or sends a
|
|
6
|
+
// transaction, and it imports no wallet/signing library (SPEC §6.0). Every ticket is labelled PAPER
|
|
7
|
+
// and the confirm gate is ON by default: arming needs a deliberate keypress, and sizes above a
|
|
8
|
+
// configurable threshold must be typed back (SPEC §6.1 step 3).
|
|
9
|
+
import { solToLamports, LAMPORTS_PER_SOL } from '../../data/src/index.js';
|
|
10
|
+
/** Default slippage and the SOL notional above which the confirm requires a typed-back amount. */
|
|
11
|
+
export const DEFAULT_SLIPPAGE_BPS = 50;
|
|
12
|
+
export const DEFAULT_TYPEBACK_THRESHOLD_SOL = 1;
|
|
13
|
+
export const MAX_SLIPPAGE_BPS = 5000;
|
|
14
|
+
export function openTicket(seed) {
|
|
15
|
+
return {
|
|
16
|
+
mint: seed.mint,
|
|
17
|
+
symbol: seed.symbol,
|
|
18
|
+
decimals: typeof seed.decimals === 'number' && Number.isFinite(seed.decimals) && seed.decimals > 0 ? seed.decimals : 9,
|
|
19
|
+
side: seed.side,
|
|
20
|
+
sizeUnit: seed.side === 'buy' ? 'sol' : 'pct',
|
|
21
|
+
sizeInput: '',
|
|
22
|
+
slippageInput: String(seed.slippageBps ?? DEFAULT_SLIPPAGE_BPS),
|
|
23
|
+
field: 'size',
|
|
24
|
+
phase: 'editing',
|
|
25
|
+
mode: seed.mode ?? 'paper',
|
|
26
|
+
gateOn: seed.gateOn ?? true,
|
|
27
|
+
typebackRequired: false,
|
|
28
|
+
typeback: '',
|
|
29
|
+
message: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function parseNum(s) {
|
|
33
|
+
const n = Number(s);
|
|
34
|
+
return Number.isFinite(n) ? n : NaN;
|
|
35
|
+
}
|
|
36
|
+
export function slippageBps(ts) {
|
|
37
|
+
if (ts.slippageInput.trim() === '')
|
|
38
|
+
return DEFAULT_SLIPPAGE_BPS;
|
|
39
|
+
const n = parseNum(ts.slippageInput);
|
|
40
|
+
if (!Number.isFinite(n) || n < 0)
|
|
41
|
+
return DEFAULT_SLIPPAGE_BPS;
|
|
42
|
+
return Math.min(MAX_SLIPPAGE_BPS, Math.round(n));
|
|
43
|
+
}
|
|
44
|
+
/** The raw amount an order would spend, given the live context; 0n when the size is empty/invalid. */
|
|
45
|
+
export function orderAmountRaw(ts, ctx) {
|
|
46
|
+
const n = parseNum(ts.sizeInput);
|
|
47
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
48
|
+
return 0n;
|
|
49
|
+
if (ts.side === 'buy') {
|
|
50
|
+
if (ts.sizeUnit === 'sol')
|
|
51
|
+
return solToLamports(n);
|
|
52
|
+
// % of free SOL balance
|
|
53
|
+
const pct = Math.min(100, n);
|
|
54
|
+
return (ctx.balanceLamports * BigInt(Math.round(pct * 100))) / 10000n;
|
|
55
|
+
}
|
|
56
|
+
// sell: % of the held position
|
|
57
|
+
const pct = Math.min(100, n);
|
|
58
|
+
return (ctx.positionTokensRaw * BigInt(Math.round(pct * 100))) / 10000n;
|
|
59
|
+
}
|
|
60
|
+
/** Build the order the ticket represents (amount already in raw base units). */
|
|
61
|
+
export function toOrder(ts, ctx) {
|
|
62
|
+
return { mint: ts.mint, symbol: ts.symbol, side: ts.side, decimals: ts.decimals, amountRaw: orderAmountRaw(ts, ctx), slippageBps: slippageBps(ts) };
|
|
63
|
+
}
|
|
64
|
+
/** Is the current size a fillable order (positive amount, and for a sell there are tokens to sell)? */
|
|
65
|
+
export function orderValid(ts, ctx) {
|
|
66
|
+
const amt = orderAmountRaw(ts, ctx);
|
|
67
|
+
if (amt <= 0n)
|
|
68
|
+
return false;
|
|
69
|
+
if (ts.side === 'sell' && ctx.positionTokensRaw <= 0n)
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
const DIGIT = /[0-9.]/;
|
|
74
|
+
function editField(ts, key) {
|
|
75
|
+
const cur = ts.field === 'size' ? ts.sizeInput : ts.slippageInput;
|
|
76
|
+
if (key.name === 'backspace') {
|
|
77
|
+
const next = cur.slice(0, -1);
|
|
78
|
+
if (ts.field === 'size')
|
|
79
|
+
ts.sizeInput = next;
|
|
80
|
+
else
|
|
81
|
+
ts.slippageInput = next;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if ((key.name === 'char') && key.ch && DIGIT.test(key.ch) && !key.ctrl && !key.alt) {
|
|
85
|
+
// one decimal point only; slippage is integer bps
|
|
86
|
+
if (key.ch === '.' && (ts.field === 'slippage' || cur.includes('.')))
|
|
87
|
+
return true;
|
|
88
|
+
const next = (cur + key.ch).slice(0, 12);
|
|
89
|
+
if (ts.field === 'size')
|
|
90
|
+
ts.sizeInput = next;
|
|
91
|
+
else
|
|
92
|
+
ts.slippageInput = next;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Advance the ticket by one key. Returns the effect the caller performs:
|
|
99
|
+
* - 'changed' : the order inputs changed → re-fetch the warm quote;
|
|
100
|
+
* - 'confirm' : the gate was satisfied → the caller must execute the order now;
|
|
101
|
+
* - 'close' : leave the ticket (back to the token screen);
|
|
102
|
+
* - 'none' : nothing to do.
|
|
103
|
+
* A stray key during the confirm step can NEVER confirm — it cancels back to editing.
|
|
104
|
+
*/
|
|
105
|
+
export function ticketKey(ts, key, ctx) {
|
|
106
|
+
ts.message = null;
|
|
107
|
+
if (ts.phase === 'filling' || ts.phase === 'signing')
|
|
108
|
+
return 'none'; // in-flight: ignore input
|
|
109
|
+
if (ts.phase === 'planning')
|
|
110
|
+
return 'none'; // the signer is building; esc is handled by the binding (cancel)
|
|
111
|
+
if (ts.phase === 'filled' || ts.phase === 'error' || ts.phase === 'refused') {
|
|
112
|
+
// a receipt is showing: esc closes, any other key starts a fresh ticket for the same mint (same mode)
|
|
113
|
+
if (key.name === 'esc')
|
|
114
|
+
return 'close';
|
|
115
|
+
const side = ts.side;
|
|
116
|
+
Object.assign(ts, openTicket({ mint: ts.mint, symbol: ts.symbol, decimals: ts.decimals, side, gateOn: ts.gateOn, slippageBps: slippageBps(ts), mode: ts.mode }));
|
|
117
|
+
return 'changed';
|
|
118
|
+
}
|
|
119
|
+
// ---- LIVE plan screen (task-1328): the keypress gate for a REAL trade. Y / ⏎ confirms (with the
|
|
120
|
+
// typed-back size above the threshold, exactly like paper); esc returns to editing; ANY other key
|
|
121
|
+
// cancels — a stray key can never confirm, and never navigates mid-plan.
|
|
122
|
+
if (ts.phase === 'plan') {
|
|
123
|
+
if (key.name === 'esc') {
|
|
124
|
+
ts.phase = 'editing';
|
|
125
|
+
ts.typeback = '';
|
|
126
|
+
return 'drop-plan';
|
|
127
|
+
}
|
|
128
|
+
if (ts.typebackRequired && key.name === 'char' && key.ch && DIGIT.test(key.ch)) {
|
|
129
|
+
if (key.ch === '.' && ts.typeback.includes('.'))
|
|
130
|
+
return 'none';
|
|
131
|
+
ts.typeback = (ts.typeback + key.ch).slice(0, 12);
|
|
132
|
+
return 'none';
|
|
133
|
+
}
|
|
134
|
+
if (ts.typebackRequired && key.name === 'backspace') {
|
|
135
|
+
ts.typeback = ts.typeback.slice(0, -1);
|
|
136
|
+
return 'none';
|
|
137
|
+
}
|
|
138
|
+
const confirmKey = (key.name === 'char' && (key.ch === 'y' || key.ch === 'Y')) || key.name === 'enter';
|
|
139
|
+
if (confirmKey) {
|
|
140
|
+
if (ts.typebackRequired) {
|
|
141
|
+
const want = parseNum(ts.sizeInput);
|
|
142
|
+
const got = parseNum(ts.typeback);
|
|
143
|
+
if (!(Number.isFinite(got) && Math.abs(got - want) < 1e-9)) {
|
|
144
|
+
ts.message = `type the size (${ts.sizeInput}) to confirm this LIVE ${ts.side}`;
|
|
145
|
+
return 'none';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
ts.phase = 'signing';
|
|
149
|
+
return 'confirm-live';
|
|
150
|
+
}
|
|
151
|
+
ts.phase = 'editing';
|
|
152
|
+
ts.typeback = '';
|
|
153
|
+
ts.message = 'plan cancelled';
|
|
154
|
+
return 'drop-plan';
|
|
155
|
+
}
|
|
156
|
+
if (ts.phase === 'confirm') {
|
|
157
|
+
if (key.name === 'esc') {
|
|
158
|
+
ts.phase = 'editing';
|
|
159
|
+
ts.typeback = '';
|
|
160
|
+
return 'none';
|
|
161
|
+
}
|
|
162
|
+
if (ts.typebackRequired && key.name === 'char' && key.ch && DIGIT.test(key.ch)) {
|
|
163
|
+
if (key.ch === '.' && ts.typeback.includes('.'))
|
|
164
|
+
return 'none';
|
|
165
|
+
ts.typeback = (ts.typeback + key.ch).slice(0, 12);
|
|
166
|
+
return 'none';
|
|
167
|
+
}
|
|
168
|
+
if (ts.typebackRequired && key.name === 'backspace') {
|
|
169
|
+
ts.typeback = ts.typeback.slice(0, -1);
|
|
170
|
+
return 'none';
|
|
171
|
+
}
|
|
172
|
+
const confirmKey = (key.name === 'char' && (key.ch === 'y' || key.ch === 'Y')) || key.name === 'enter';
|
|
173
|
+
if (confirmKey) {
|
|
174
|
+
if (ts.typebackRequired) {
|
|
175
|
+
const want = parseNum(ts.sizeInput);
|
|
176
|
+
const got = parseNum(ts.typeback);
|
|
177
|
+
if (!(Number.isFinite(got) && Math.abs(got - want) < 1e-9)) {
|
|
178
|
+
ts.message = `type the size (${ts.sizeInput}) to confirm this PAPER ${ts.side}`;
|
|
179
|
+
return 'none';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
ts.phase = 'filling';
|
|
183
|
+
return 'confirm';
|
|
184
|
+
}
|
|
185
|
+
// any other key cancels the arm (a stray key never confirms, and never navigates mid-confirm)
|
|
186
|
+
ts.phase = 'editing';
|
|
187
|
+
ts.typeback = '';
|
|
188
|
+
ts.message = 'confirm cancelled';
|
|
189
|
+
return 'none';
|
|
190
|
+
}
|
|
191
|
+
// ---- editing
|
|
192
|
+
if (key.name === 'esc')
|
|
193
|
+
return 'close';
|
|
194
|
+
if (key.name === 'char' && (key.ch === 'L' || key.ch === 'l') && !key.ctrl && !key.alt)
|
|
195
|
+
return 'toggle-live';
|
|
196
|
+
if (key.name === 'tab') {
|
|
197
|
+
ts.field = ts.field === 'size' ? 'slippage' : 'size';
|
|
198
|
+
return 'none';
|
|
199
|
+
}
|
|
200
|
+
if (key.name === 'left' || key.name === 'right') {
|
|
201
|
+
ts.side = ts.side === 'buy' ? 'sell' : 'buy';
|
|
202
|
+
ts.sizeUnit = ts.side === 'buy' ? ts.sizeUnit : 'pct';
|
|
203
|
+
ts.typeback = '';
|
|
204
|
+
return 'changed';
|
|
205
|
+
}
|
|
206
|
+
if (key.name === 'char' && key.ch === 'u' && !key.ctrl) {
|
|
207
|
+
if (ts.side === 'buy') {
|
|
208
|
+
ts.sizeUnit = ts.sizeUnit === 'sol' ? 'pct' : 'sol';
|
|
209
|
+
return 'changed';
|
|
210
|
+
}
|
|
211
|
+
return 'none'; // sell is always % of the position
|
|
212
|
+
}
|
|
213
|
+
if (key.name === 'enter') {
|
|
214
|
+
if (!orderValid(ts, ctx)) {
|
|
215
|
+
ts.message = ts.side === 'sell' && ctx.positionTokensRaw <= 0n ? `no ${ts.symbol} position to sell` : 'enter a size first';
|
|
216
|
+
return 'none';
|
|
217
|
+
}
|
|
218
|
+
ts.typebackRequired = ts.gateOn && ctx.notionalSol > ctx.typebackThresholdSol;
|
|
219
|
+
ts.typeback = '';
|
|
220
|
+
if (ts.mode === 'live') {
|
|
221
|
+
ts.phase = 'planning';
|
|
222
|
+
return 'plan';
|
|
223
|
+
}
|
|
224
|
+
ts.phase = 'confirm';
|
|
225
|
+
return 'none';
|
|
226
|
+
}
|
|
227
|
+
// task-1368: f on a LIVE buy = size the order to the signer's cap (the binding knows the caps + SOL/USD; the
|
|
228
|
+
// reducer only names the effect). Inert on PAPER (no caps) and on a sell (sized in % of the position).
|
|
229
|
+
if (key.name === 'char' && (key.ch === 'f' || key.ch === 'F') && !key.ctrl && !key.alt) {
|
|
230
|
+
return ts.mode === 'live' && ts.side === 'buy' ? 'fit-cap' : 'none';
|
|
231
|
+
}
|
|
232
|
+
if (editField(ts, key))
|
|
233
|
+
return 'changed';
|
|
234
|
+
return 'none';
|
|
235
|
+
}
|
|
236
|
+
// --------------------------------------------------------------------------- task-1368 preflight + hints (pure)
|
|
237
|
+
/**
|
|
238
|
+
* Fees + token-account rent the signer's simulated SOL-out may add on top of the size of a BUY (a tx fee, a priority
|
|
239
|
+
* fee and one associated-token-account rent of 0.00203928 SOL when the wallet has never held the token). The
|
|
240
|
+
* signer prices the LARGER of the planned SOL leg and the simulated SOL out, so a size that is exactly the cap in
|
|
241
|
+
* USD is refused once rent lands on top; `fitSizeSol` leaves this much room.
|
|
242
|
+
*/
|
|
243
|
+
export const CAP_FIT_OVERHEAD_SOL = 0.0025;
|
|
244
|
+
const usd = (n) => `$${n.toFixed(2).replace(/\.00$/, '')}`;
|
|
245
|
+
/**
|
|
246
|
+
* Size vs the signer's caps BEFORE ⏎ (task-1368 item b): the USD value of the size at the signer's own SOL/USD, the
|
|
247
|
+
* per-trade cap and what is left of the daily cap, a red verdict when the size is over, and the size that fits.
|
|
248
|
+
* Pure: the signer still decides at precheck/confirm — this only stops the user from building a plan that will be
|
|
249
|
+
* refused for a reason they could not see.
|
|
250
|
+
*/
|
|
251
|
+
export function capPreflight(i) {
|
|
252
|
+
const sizeSol = Number.isFinite(i.sizeSol) && i.sizeSol > 0 ? i.sizeSol : 0;
|
|
253
|
+
const price = i.solUsd !== null && Number.isFinite(i.solUsd) && i.solUsd > 0 ? i.solUsd : null;
|
|
254
|
+
const perTradeUsd = Math.max(0, i.perTradeUsd);
|
|
255
|
+
const dailyRemainingUsd = Math.max(0, i.dailyUsd - Math.max(0, i.spentTodayUsd));
|
|
256
|
+
const capUsd = Math.min(perTradeUsd, dailyRemainingUsd);
|
|
257
|
+
const sizeUsd = price !== null && sizeSol > 0 ? sizeSol * price : null;
|
|
258
|
+
const over = sizeUsd !== null && sizeUsd > capUsd + 1e-9;
|
|
259
|
+
const binding = !over ? null : dailyRemainingUsd < perTradeUsd ? 'daily' : 'per-trade';
|
|
260
|
+
const overhead = Math.max(0, i.overheadSol ?? CAP_FIT_OVERHEAD_SOL);
|
|
261
|
+
let fitSizeSol = null;
|
|
262
|
+
if (price !== null && capUsd > 0) {
|
|
263
|
+
const fit = Math.floor((capUsd / price - overhead) * 1000) / 1000;
|
|
264
|
+
fitSizeSol = fit > 0 ? fit : null;
|
|
265
|
+
}
|
|
266
|
+
const capText = `cap ${usd(perTradeUsd)}/trade · ${usd(dailyRemainingUsd)} left of ${usd(Math.max(0, i.dailyUsd))} today`;
|
|
267
|
+
let line;
|
|
268
|
+
if (sizeSol <= 0)
|
|
269
|
+
line = `${capText} · enter a size`;
|
|
270
|
+
else if (price === null)
|
|
271
|
+
line = `${sizeSol} SOL = $? (the signer has no SOL/USD yet; it will refuse an unpriced trade on mainnet) · ${capText}`;
|
|
272
|
+
else if (over) {
|
|
273
|
+
const which = binding === 'daily' ? `${usd(dailyRemainingUsd)} left today` : `${usd(perTradeUsd)} cap`;
|
|
274
|
+
line = `${sizeSol} SOL = ${usd(sizeUsd)} > ${which}: ${fitSizeSol !== null ? `use ≤ ${fitSizeSol} SOL (f fits it; fees + token-account rent counted)` : 'nothing fits — the cap is spent for today'}`;
|
|
275
|
+
}
|
|
276
|
+
else
|
|
277
|
+
line = `${sizeSol} SOL ≈ ${usd(sizeUsd)} at SOL ${usd(price)} · ${capText}`;
|
|
278
|
+
return { sizeSol, sizeUsd, perTradeUsd, dailyRemainingUsd, capUsd, over, fitSizeSol, binding, line };
|
|
279
|
+
}
|
|
280
|
+
const WSOL = 'So11111111111111111111111111111111111111112';
|
|
281
|
+
/**
|
|
282
|
+
* task-1368 item c: a DEVNET signer + a Jupiter swap = a transaction built on MAINNET state (there is no Jupiter
|
|
283
|
+
* devnet): it can never simulate or fill on devnet, so say so BEFORE ⏎ and name the two ways out. Null for the
|
|
284
|
+
* probe (built for devnet on purpose), for wrapped SOL itself, and on mainnet.
|
|
285
|
+
*/
|
|
286
|
+
export function devnetMintWarning(a) {
|
|
287
|
+
if (a.network !== 'devnet' || a.probe || a.mint === WSOL)
|
|
288
|
+
return null;
|
|
289
|
+
const caps = `caps ${usd(a.perTradeUsd ?? 5)}/${usd(a.dailyUsd ?? 20)}`;
|
|
290
|
+
return `DEVNET cannot fill mainnet tokens: paper trade (L) or restart with --mainnet (real funds, ${caps})`;
|
|
291
|
+
}
|
|
292
|
+
const MAINNET_STATE_ERR = /invalid transaction|blockhash|account.*not found|not found|could not find|address (table|lookup)|AccountNotFound|ProgramAccountNotFound|unknown program|InvalidAccountData/i;
|
|
293
|
+
/**
|
|
294
|
+
* task-1368 item f: a plain-English hint under a failed simulation when the failure pattern is the devnet-vs-mainnet
|
|
295
|
+
* mismatch (a mainnet route simulated on a devnet node: accounts, lookup tables or blockhash not found). Null when
|
|
296
|
+
* the simulation passed, was not run, or failed for another reason (the full error is still shown).
|
|
297
|
+
*/
|
|
298
|
+
export function simFailureHint(p, caps) {
|
|
299
|
+
if (p.kind !== 'swap' || !p.sim.ran || p.sim.ok)
|
|
300
|
+
return null;
|
|
301
|
+
const err = p.sim.error ?? '';
|
|
302
|
+
const mainnetState = MAINNET_STATE_ERR.test(err);
|
|
303
|
+
if (p.network === 'devnet' && (mainnetState || p.mintOnNetwork === false)) {
|
|
304
|
+
const mint = p.mintOnNetwork === false ? ' (the mint does not exist on devnet)' : '';
|
|
305
|
+
return `devnet-vs-mainnet wall${mint}: a Jupiter swap is built on MAINNET state (accounts, lookup tables, blockhash) that a devnet node does not have. Paper trade (L) or restart with --mainnet (real funds, caps ${usd(caps?.perTradeUsd ?? 5)}/${usd(caps?.dailyUsd ?? 20)}).`;
|
|
306
|
+
}
|
|
307
|
+
if (mainnetState)
|
|
308
|
+
return 'the node could not find the route\'s accounts or blockhash: a stale plan or the wrong RPC network — rebuild with ⏎, or check --trade-rpc';
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
/** SOL notional of a buy order sized in SOL/%, used to gate the typed-back step without a quote. */
|
|
312
|
+
export function buyNotionalSol(ts, balanceLamports) {
|
|
313
|
+
const n = parseNum(ts.sizeInput);
|
|
314
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
315
|
+
return 0;
|
|
316
|
+
if (ts.sizeUnit === 'sol')
|
|
317
|
+
return n;
|
|
318
|
+
return (Number(balanceLamports) / 1e9) * Math.min(100, n) / 100;
|
|
319
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Transcripts and model text are UNTRUSTED input (task-1282): strip C0/C1 control characters and DEL
|
|
2
|
+
// before they are drawn, so an injected escape sequence in a transcript can never reach the terminal.
|
|
3
|
+
const sanitize = (s) => s.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ');
|
|
4
|
+
/** Render the one-line voice badge at (1,1). Clipped to the width; never wraps. */
|
|
5
|
+
export function renderVoiceHud(c, v, t) {
|
|
6
|
+
if (!v.enabled)
|
|
7
|
+
return;
|
|
8
|
+
const bg = t.bg;
|
|
9
|
+
let x = 1;
|
|
10
|
+
const y = 1;
|
|
11
|
+
const mute = v.muted ? ' ⌀muted' : '';
|
|
12
|
+
const voiceName = v.voice ? ` · ${v.voice}` : '';
|
|
13
|
+
if (v.mode === 'listening') {
|
|
14
|
+
// The loud, unmistakable mic-live state.
|
|
15
|
+
x = c.text(x, y, ' ● LISTENING ', { fg: t.bg, bg: t.down, b: true });
|
|
16
|
+
x = c.text(x, y, ' speak — v to send, esc to cancel', { fg: t.down, bg, b: true });
|
|
17
|
+
}
|
|
18
|
+
else if (v.mode === 'transcribing') {
|
|
19
|
+
x = c.text(x, y, ' ◍ transcribing… ', { fg: t.warn, bg, b: true });
|
|
20
|
+
}
|
|
21
|
+
else if (v.mode === 'thinking') {
|
|
22
|
+
x = c.text(x, y, ' ◌ asking the analyst… ', { fg: t.borderFocus, bg, b: true });
|
|
23
|
+
}
|
|
24
|
+
else if (v.mode === 'speaking') {
|
|
25
|
+
x = c.text(x, y, ' ▸ speaking ', { fg: t.up, bg, b: true });
|
|
26
|
+
x = c.text(x, y, '(v/esc stop)', { fg: t.muted, bg });
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// idle but enabled: a quiet "ready" badge with the key hints.
|
|
30
|
+
x = c.text(x, y, ' ◎ voice ', { fg: t.borderFocus, bg, b: true });
|
|
31
|
+
x = c.text(x, y, `v talk · m ${v.muted ? 'unmute' : 'mute'} · , voice${voiceName}`, { fg: t.muted, bg });
|
|
32
|
+
}
|
|
33
|
+
if (mute && v.mode !== 'off')
|
|
34
|
+
x = c.text(x, y, mute, { fg: t.warn, bg, b: true });
|
|
35
|
+
// A short echo of what was heard / said, to the right, when there is room.
|
|
36
|
+
const echo = v.message ? `⚠ ${sanitize(v.message)}` : v.transcript ? `“${sanitize(v.transcript)}”` : v.say ? `▸ ${sanitize(v.say)}` : '';
|
|
37
|
+
if (echo && x + 3 < c.w) {
|
|
38
|
+
const room = c.w - x - 2;
|
|
39
|
+
const shown = echo.length > room ? echo.slice(0, Math.max(0, room - 1)) + '…' : echo;
|
|
40
|
+
c.text(x + 1, y, shown, { fg: v.message ? t.warn : t.dim, bg });
|
|
41
|
+
}
|
|
42
|
+
}
|