dexterm-cli 0.1.4 → 0.1.6
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/index.js +2 -0
- package/dist/packages/agents/src/spectator.js +54 -0
- package/dist/packages/agents/src/story.js +523 -0
- package/dist/packages/agents/src/tiers.js +1 -0
- package/dist/packages/agents/src/tools.js +1 -0
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
- package/dist/packages/cli/src/agent-cmd.js +6 -4
- package/dist/packages/cli/src/agent-paper.js +60 -0
- package/dist/packages/cli/src/agent-spectator.js +17 -1
- package/dist/packages/cli/src/analyst/backend.js +206 -0
- package/dist/packages/cli/src/analyst/codex.js +301 -0
- package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
- package/dist/packages/cli/src/analyst/session.js +21 -3
- package/dist/packages/cli/src/app-help-gen.js +21 -12
- package/dist/packages/cli/src/backtest-cmd.js +13 -2
- package/dist/packages/cli/src/display.js +55 -9
- package/dist/packages/cli/src/doctor.js +58 -2
- package/dist/packages/cli/src/live/dev-history.js +115 -0
- package/dist/packages/cli/src/live/index.js +5 -1
- package/dist/packages/cli/src/live/newpairs.js +12 -0
- package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
- package/dist/packages/cli/src/live/screens/token-live.js +51 -11
- package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
- package/dist/packages/cli/src/live/token.js +1 -0
- package/dist/packages/cli/src/live/trade.js +22 -0
- package/dist/packages/cli/src/main.js +267 -19
- package/dist/packages/cli/src/mcp-follow.js +137 -10
- package/dist/packages/cli/src/plan-cmd.js +15 -5
- package/dist/packages/cli/src/popup.js +17 -1
- package/dist/packages/cli/src/record-cmd.js +2 -0
- package/dist/packages/cli/src/rules.js +25 -2
- package/dist/packages/cli/src/scan/ui.js +7 -1
- package/dist/packages/cli/src/screens/agent-hud.js +741 -0
- package/dist/packages/cli/src/screens/analyst-session.js +9 -3
- package/dist/packages/cli/src/screens/analyst.js +4 -3
- package/dist/packages/cli/src/screens/brain.js +608 -0
- package/dist/packages/cli/src/screens/newpairs.js +1 -1
- package/dist/packages/cli/src/screens/pnl-chip.js +143 -0
- package/dist/packages/cli/src/screens/screener.js +1 -0
- package/dist/packages/cli/src/screens/token.js +16 -7
- package/dist/packages/cli/src/screens/trending.js +1 -1
- package/dist/packages/cli/src/state.js +69 -5
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/custom/src/rules.js +2 -2
- package/dist/packages/custom/src/schema.js +13 -3
- package/dist/packages/data/src/fixtures.js +28 -6
- package/dist/packages/data/src/index.js +5 -0
- package/dist/packages/data/src/larp/score.js +220 -0
- package/dist/packages/data/src/larp/x-account.js +316 -0
- package/dist/packages/data/src/sources/jupiter.js +1 -0
- package/dist/packages/data/src/trade/agent/paper-desk.js +401 -0
- package/dist/packages/data/src/trade/agent/rendezvous.js +161 -0
- package/dist/packages/data/src/trade/plans/engine.js +7 -0
- package/dist/packages/data/src/trade/plans/mandate.js +28 -15
- package/dist/packages/data/src/trade/plans/service.js +1 -1
- package/dist/packages/mcp/assets/kb/app-help/agent-paper-trade.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/ask-the-analyst.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/connect-an-agent.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/dev-history.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
- package/dist/packages/mcp/assets/kb/app-help/paper-trade.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +4 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +4 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
- package/dist/packages/mcp/assets/kb/app-help/use-codex-analyst.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +51 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-directory-metadata-prepared-not-submitted.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-install-in-one-command.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-codex-instead-of-claude.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos-or-linux-npm.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-expectations.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-powershell-experimental.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-rules-engine.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-analyst-s-launch.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +13 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-supported-platforms.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +1 -9
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-how-well-it-predicts-measured-held-out.md +46 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +2 -0
- package/dist/packages/mcp/src/index.js +5 -2
- package/dist/packages/mcp/src/paper-agent.js +93 -0
- package/dist/packages/mcp/src/server.js +354 -19
- package/dist/packages/mcp/src/spectator-sink.js +158 -4
- package/dist/packages/mcp/src/user-content.js +1 -0
- package/dist/packages/packs/src/index.js +1 -0
- package/dist/packages/packs/src/larp-score-backtest.js +119 -0
- package/dist/packages/popup/src/pages/chart-lib.js +36 -15
- package/dist/packages/popup/src/pages/chart.css +9 -0
- package/dist/packages/popup/src/pages/chart.html +4 -1
- package/dist/packages/popup/src/pages/chart.js +34 -5
- package/dist/packages/popup/src/pages/spectator.css +45 -0
- package/dist/packages/popup/src/pages/spectator.html +1 -0
- package/dist/packages/popup/src/pages/spectator.js +69 -0
- package/dist/packages/popup/src/protocol.js +2 -0
- package/dist/packages/popup/src/server.js +5 -1
- package/dist/packages/recorder/src/dev-history.js +133 -0
- package/dist/packages/recorder/src/index.js +1 -0
- package/dist/packages/recorder/src/recorder.js +23 -0
- package/dist/packages/recorder/src/schema.js +11 -0
- package/dist/packages/tui/src/canvas.js +7 -0
- package/dist/packages/tui/src/fmt.js +53 -0
- package/dist/packages/tui/src/keymap.js +4 -0
- package/dist/packages/tui/src/widgets.js +9 -4
- package/package.json +1 -1
|
@@ -25,11 +25,13 @@ import { parseMintTarget, parseCoinTarget, resolveCoinTarget, isBareOpen } from
|
|
|
25
25
|
import { THEMES, isThemeName, DEFAULT_THEME, THEME_NAMES, Flusher, ENTER_SCREEN, LEAVE_SCREEN, DEFAULT_KEYMAP, Keymap, nextTheme, parseKeys, keyId, detectColorDepth, parseColorDepth, blinkFrame, blinkFrames, stepVfx, pulse, isCalm, fxBusy, blendActivity, ImageLayer, queryTerminal, resolveProtocol, fireMoment, fireInflight, clearInflight, skipMoment, momentKindFor, playMoment, soundOn, breakpoint, fmtUsd as fmtUsdTui, } from '../../tui/src/index.js';
|
|
26
26
|
import { resolveRpcUrl, redactSecrets, defaultDbPath, extraRpcUrls, wsUrlFor, SOLANA_PUBLIC_RPC_WS, parseHttpUrl, validateAnalystAction, ACTION_RATE, ACTION_TIER, configChangeForAction, ImageService, encodePng, SummaryLimiter, buildSummaryWindow, KolStore, Cache, PackError, normalizeHandle, terminalSafe } from '../../data/src/index.js';
|
|
27
27
|
import { Recorder, recorderEnabled, MaintenanceSchedule } from '../../recorder/src/index.js';
|
|
28
|
-
import { makeFixtureState, makeFeedState, syncFeed, renderFrame, isScreenKey, activityFor, SCREEN_NAMES, analystKey, syncAnalyst, pinNewpairsSelection, ANALYST_TAB_KEY, computeNarrativeView, newPlansUi, plansService, plansRows, newFollowsUi, copyService, copyAutoService, MIRROR_DEFAULT_CAP_USD } from "./state.js";
|
|
28
|
+
import { makeFixtureState, makeFeedState, syncFeed, renderFrame, isScreenKey, activityFor, SCREEN_NAMES, analystKey, syncAnalyst, pinNewpairsSelection, ANALYST_TAB_KEY, computeNarrativeView, newPlansUi, plansService, plansRows, newFollowsUi, copyService, copyAutoService, MIRROR_DEFAULT_CAP_USD, pnlChipFor } from "./state.js";
|
|
29
29
|
import { mulberry32, tick, spawnPair, shock } from "./fixtures.js";
|
|
30
30
|
import { createLiveFeed, createReplayFeed } from "./feed.js";
|
|
31
31
|
import { LiveContext, INTERVALS, WATCH_REFRESH_MS, POSITIONS_REFRESH_MS, popupBubbleVM } from "./live/index.js";
|
|
32
|
+
import { FOLLOW_NAV_ACTIONS } from '../../mcp/src/index.js';
|
|
32
33
|
import { McpFollowSession } from "./mcp-follow.js";
|
|
34
|
+
import { AgentPaperSwitch } from "./agent-paper.js";
|
|
33
35
|
import { TOKEN_REFRESH_MS, CandlePrefetcher } from "./live/token.js";
|
|
34
36
|
import { PRESETS, presetSpec } from "./screener/presets.js";
|
|
35
37
|
import { parseSortArg, SORT_KEYS } from "./screener/sort.js";
|
|
@@ -37,7 +39,9 @@ import { parseFilter } from "./screener/filter.js";
|
|
|
37
39
|
import { screenerView, setFilter, setSort, setPreset, stepPreset, clampSelection, openFilterEditor, openSortMenu, filterEditorKey, sortMenuKey, } from "./screener/view.js";
|
|
38
40
|
import { readBootState, bootPlan, recordBoot, bootTrace, dextermHome } from "./boot-state.js";
|
|
39
41
|
import { socialOf, kolOf, trendingRows, kolRows, nextSocialFilter, feedsPopupModel } from "./social/view.js";
|
|
40
|
-
import { TokenPopups, tokenDetailVMFromView } from "./popup.js";
|
|
42
|
+
import { TokenPopups, tokenDetailVMFromView, currentPositionFills } from "./popup.js";
|
|
43
|
+
import { OwnPnlTracker } from "./screens/pnl-chip.js";
|
|
44
|
+
import { positionRow } from "./live/trade.js";
|
|
41
45
|
import { widgetPage, widgetEnabled, syncWidgetPopups } from "./widgets.js";
|
|
42
46
|
import { recordLarpVerdict } from "./larp-record.js";
|
|
43
47
|
import { rulesUiOf, rulesTick, rulesMove, beginRuleInput, saveRuleInput, dryRunNow, removeSelectedRule, toggleSelectedRule, dismissSelectedProposal, actOnSelectedProposal, RULE_INPUT_MAX } from "./rules.js";
|
|
@@ -46,11 +50,12 @@ import { copyAlertToDelivery } from "./alerts/format.js";
|
|
|
46
50
|
import { readTelegramCreds, readDiscordCreds } from "./alerts/creds.js";
|
|
47
51
|
import { launchIntelCells, launchRetryable } from "./live/intel.js";
|
|
48
52
|
import { AnalystSession } from "./analyst/session.js";
|
|
53
|
+
import { analystCliOnPath, parseEngineChoice, resolveEngine } from "./analyst/codex.js";
|
|
49
54
|
import { resolveAnalystResults } from "./analyst/results.js";
|
|
50
55
|
import { parseScanCommand, SCAN_FLAGS, checkScanFlag, SCAN_HELP } from "./scan/command.js";
|
|
51
56
|
import { parseWalletCommand, WALLET_HELP } from "./wallet-command.js";
|
|
52
57
|
import { X_HELP } from "./x-cmd.js";
|
|
53
|
-
import { makeScanUi, MODE_LABEL, SCAN_ACTION_TYPES, SCAN_TICK_MS, SCAN_RECOMPUTE_MS } from "./scan/ui.js";
|
|
58
|
+
import { makeScanUi, scanLog, MODE_LABEL, SCAN_ACTION_TYPES, SCAN_TICK_MS, SCAN_RECOMPUTE_MS } from "./scan/ui.js";
|
|
54
59
|
import { buildObservations, observeSig } from "./scan/observe.js";
|
|
55
60
|
import { cleanText } from "./screener/rows.js";
|
|
56
61
|
import { resolveImagesConfig } from "./images-config.js";
|
|
@@ -59,7 +64,8 @@ import { VoiceController, selectStt, selectTts, SayTts, voiceStyleDefaults } fro
|
|
|
59
64
|
import { LivePlanExecutor, defaultPlansJournalPath } from '../../data/src/trade/plans/index.js';
|
|
60
65
|
import { isWalletAddress, shortAddr, buildMirror, leaderSoldSince, autoSizeCap } from '../../data/src/trade/copy/index.js';
|
|
61
66
|
import { FOLLOW_INPUT_MAX } from "./screens/follows.js";
|
|
62
|
-
import {
|
|
67
|
+
import { HUD_MIN_H, hudAnimating } from "./screens/agent-hud.js";
|
|
68
|
+
import { isPriceMode, configPriceMode, togglePriceMode, priceModeOf, planLevels, configChartAxis, setChartAxis, chartAxisOf } from "./display.js";
|
|
63
69
|
import { VERSION } from "./version.js";
|
|
64
70
|
import { initCustom, planWorkspace, planPopupOpens, findWorkspace, nextWorkspaceName, workspaceSlots, saveWorkspace, themeFor, themeByName, cycleTheme, themeNames, CHORD_TTL_MS, MAX_WORKSPACE_NAME } from "./custom.js";
|
|
65
71
|
import { DEFAULT_WORKSPACE, MORNING_HUD, applyAndJournal, policyCheck } from '../../custom/src/index.js';
|
|
@@ -101,24 +107,55 @@ export function appStateBlock(state) {
|
|
|
101
107
|
alerts, // booleans only — never a token, webhook or key
|
|
102
108
|
};
|
|
103
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* task-1443: the reduced screen snapshot a terminal following an EXTERNAL agent (`--spectate-mcp`) publishes for
|
|
112
|
+
* that agent's `dexterm mcp` server: what is on screen plus a VIEW-ONLY nav allow-list. Never the watchlist,
|
|
113
|
+
* tickets, positions or app settings, and only the four follow nav action types (no watchlist add, no ticket).
|
|
114
|
+
*/
|
|
115
|
+
export function followSnapshot(state) {
|
|
116
|
+
const ss = buildScreenState(state);
|
|
117
|
+
const a = ss.actions;
|
|
118
|
+
return {
|
|
119
|
+
screen: ss.screen, selected: ss.selected, selectedDetail: ss.selectedDetail, filter: ss.filter, sort: ss.sort,
|
|
120
|
+
visible: ss.visible, visibleMints: ss.visibleMints,
|
|
121
|
+
actions: { enabled: a.enabled, mints: a.mints, feeds: a.feeds, sortKeys: a.sortKeys, screens: a.screens, workspaces: [], themes: [], types: [...FOLLOW_NAV_ACTIONS] },
|
|
122
|
+
now: ss.now,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
104
125
|
export function buildScreenState(state) {
|
|
105
126
|
const live = state.live;
|
|
106
127
|
const sc = state.screener;
|
|
107
128
|
// The screener and the Analyst-tab coin list both show the filtered/sorted screener view, so the
|
|
108
129
|
// selection + visible rows must come from that same view (task-1313); other screens use the raw pool.
|
|
109
|
-
|
|
130
|
+
// The new-pairs screen shows the live PumpPortal list (task-1443 capture r2: it used to report the screener pool
|
|
131
|
+
// here, so an agent watching new pairs could not read what was on screen or what was highlighted).
|
|
132
|
+
const newPairs = state.screen === 'newpairs' && live ? live.newpairs.list() : null;
|
|
133
|
+
const rows = state.screen === 'screener' || state.screen === 'analyst' ? screenerView(sc) : newPairs ?? sc.rows;
|
|
110
134
|
const toRow = (r) => {
|
|
111
135
|
const x = r;
|
|
112
136
|
if (!x?.mint)
|
|
113
137
|
return null;
|
|
114
138
|
return { mint: x.mint, symbol: x.symbol ?? '', priceUsd: x.price ?? null, chg1h: x.chg?.h1 ?? null, chg24h: x.chg?.h24 ?? null, liq: x.liq ?? null, vol24: x.vol24 ?? null };
|
|
115
139
|
};
|
|
116
|
-
const
|
|
140
|
+
const toNewPairRow = (r) => {
|
|
141
|
+
const x = r;
|
|
142
|
+
if (!x?.mint)
|
|
143
|
+
return null;
|
|
144
|
+
return {
|
|
145
|
+
mint: x.mint, symbol: x.symbol ?? '', priceUsd: x.price ?? null, chg1h: x.chg?.h1 ?? null, chg24h: null, liq: x.liq ?? null, vol24: x.vol ?? null,
|
|
146
|
+
ageSec: Number.isFinite(x.createdAt) ? Math.max(0, Math.round((Date.now() - x.createdAt) / 1000)) : null,
|
|
147
|
+
mcap: x.mcap ?? null, chg5m: x.chg?.m5 ?? null, bonding: x.bonding ?? null, buys: x.buys ?? null, sells: x.sells ?? null,
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
const mapRow = newPairs ? toNewPairRow : toRow;
|
|
151
|
+
const visible = rows.slice(0, 12).map(mapRow).filter((r) => r !== null);
|
|
117
152
|
let selected = null;
|
|
118
153
|
if (state.screen === 'token' && live?.token?.view)
|
|
119
154
|
selected = { mint: live.token.view.mint, symbol: live.token.view.symbol };
|
|
120
155
|
else {
|
|
121
|
-
|
|
156
|
+
// Same pick as the new-pairs screen's own highlight (selectedMint first, then the cursor row).
|
|
157
|
+
const hit = newPairs && state.selectedMint ? newPairs.find((x) => x.mint === state.selectedMint) : undefined;
|
|
158
|
+
const r = mapRow(hit ?? rows[state.selected || 0]);
|
|
122
159
|
if (r)
|
|
123
160
|
selected = { mint: r.mint, symbol: r.symbol };
|
|
124
161
|
}
|
|
@@ -249,7 +286,7 @@ export function shouldDrawFrame(g) {
|
|
|
249
286
|
return true;
|
|
250
287
|
if (g.calm)
|
|
251
288
|
return g.onTick;
|
|
252
|
-
if (g.fxBusy)
|
|
289
|
+
if (g.fxBusy || g.hud)
|
|
253
290
|
return true;
|
|
254
291
|
return g.frame % g.idleEvery === 0;
|
|
255
292
|
}
|
|
@@ -288,6 +325,11 @@ export const HELP = `dexterm ${VERSION} (live: screener, token, new pairs, watch
|
|
|
288
325
|
--record FILE save every live response (and the PumpPortal session) to FILE (replay it with --replay)
|
|
289
326
|
--spectate-mcp follow an MCP agent's allowed navigation in this terminal; Ctrl+P pauses, Ctrl+K stops,
|
|
290
327
|
and any normal user keypress takes control (refused actions never move the UI)
|
|
328
|
+
--spectate-thoughts FILE (with --spectate-mcp) stream the agent's own words into the thought ribbon from a JSONL
|
|
329
|
+
file it writes (e.g. claude -p --output-format stream-json); display only, scrubbed
|
|
330
|
+
--agent-paper MANDATE_ID (experimental, with --spectate-mcp) let that MCP agent PAPER-trade inside a mandate you
|
|
331
|
+
armed: simulated fills only, never signs or sends, never raises a cap; Ctrl+P / Ctrl+K /
|
|
332
|
+
any key also pause, stop or take over its paper trading
|
|
291
333
|
--max-requests N hard cap on live requests for this run (default: none)
|
|
292
334
|
--max-credits N hard cap on Helius credits for this run (default: 5000)
|
|
293
335
|
--mint MINT|LINK token to open on the token screen (a CA or a dexscreener / pump.fun / solscan / birdeye link)
|
|
@@ -302,6 +344,8 @@ export const HELP = `dexterm ${VERSION} (live: screener, token, new pairs, watch
|
|
|
302
344
|
--selected N highlighted row / token index
|
|
303
345
|
--seed N fixture PRNG seed (default 7)
|
|
304
346
|
--no-ai hide the analyst panel on the token screen
|
|
347
|
+
--analyst-engine E auto | claude | codex: which of YOUR installed CLIs runs the analyst (default auto = claude
|
|
348
|
+
if installed, else codex; DEXTERM_ANALYST_ENGINE or config analyst.engine too; /engine in chat)
|
|
305
349
|
|
|
306
350
|
--intensity LEVEL off | normal | fever (default fever: rain, trails, glitch, blink, scanlines at 30 fps;
|
|
307
351
|
normal: 15 fps, sparser rain, no idle aberration; off = calm: neon theme, no motion)
|
|
@@ -389,14 +433,14 @@ export function parseArgs(argv) {
|
|
|
389
433
|
screen: null, fixtures: false, replay: null, record: null, filter: null, sort: null, preset: null, maxRequests: 0, maxCredits: 0, db: null,
|
|
390
434
|
mint: null, interval: '1h', listen: 20, verbose: false,
|
|
391
435
|
width: null, height: null, html: null, export: null, theme: DEFAULT_THEME, selected: 0, seed: 7,
|
|
392
|
-
noAi: false, title: null, plain: false, ansi: false, color: null, tz: null,
|
|
436
|
+
noAi: false, analystEngine: null, title: null, plain: false, ansi: false, color: null, tz: null,
|
|
393
437
|
calm: false, intensity: null, fps: null, frame: 0, t: 1, event: null, ai: null, mood: null, activity: null, noBoot: false, fit: null, fontPx: null, asciiRain: false, noImages: false,
|
|
394
438
|
scan: false, scanConfig: {},
|
|
395
439
|
noRecorder: false,
|
|
396
440
|
sound: false,
|
|
397
441
|
trade: [], live: true,
|
|
398
442
|
workspace: null, priceMode: null,
|
|
399
|
-
spectateMcp: false,
|
|
443
|
+
spectateMcp: false, spectateThoughts: null, agentPaper: null,
|
|
400
444
|
help: false, version: false, unknown: [],
|
|
401
445
|
};
|
|
402
446
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -485,6 +529,13 @@ export function parseArgs(argv) {
|
|
|
485
529
|
}
|
|
486
530
|
else if (k === '--no-ai')
|
|
487
531
|
a.noAi = true;
|
|
532
|
+
else if (k === '--analyst-engine') {
|
|
533
|
+
const s = str();
|
|
534
|
+
const e = parseEngineChoice(s);
|
|
535
|
+
if (!e)
|
|
536
|
+
throw new Error(`unknown analyst engine ${s} (expected auto|claude|codex)`);
|
|
537
|
+
a.analystEngine = e;
|
|
538
|
+
}
|
|
488
539
|
else if (k === '--calm')
|
|
489
540
|
a.calm = true;
|
|
490
541
|
else if (k === '--intensity') {
|
|
@@ -581,6 +632,15 @@ export function parseArgs(argv) {
|
|
|
581
632
|
// task-1421: follow an agent driving over MCP (the running terminal moves with it; a read-only stream opens).
|
|
582
633
|
else if (k === '--spectate-mcp')
|
|
583
634
|
a.spectateMcp = true;
|
|
635
|
+
else if (k === '--spectate-thoughts')
|
|
636
|
+
a.spectateThoughts = str();
|
|
637
|
+
// task-1443: agent PAPER trading inside a mandate the user armed (validated against the plans journal at start).
|
|
638
|
+
else if (k === '--agent-paper') {
|
|
639
|
+
const s = str();
|
|
640
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(s))
|
|
641
|
+
throw new Error(`--agent-paper expects a mandate id (see \`dexterm mandate list\`), got ${JSON.stringify(s.slice(0, 20))}`);
|
|
642
|
+
a.agentPaper = s;
|
|
643
|
+
}
|
|
584
644
|
else if (k === '--workspace') {
|
|
585
645
|
const s = str();
|
|
586
646
|
if (!/^[a-z][a-z0-9-]{0,39}(\/[a-z][a-z0-9-]{0,39})?$/.test(s))
|
|
@@ -618,6 +678,12 @@ export function parseArgs(argv) {
|
|
|
618
678
|
throw new Error('--scan needs live data or a recording (drop --fixtures)');
|
|
619
679
|
if (a.scan && a.screen)
|
|
620
680
|
throw new Error('--scan is interactive (drop --screen)');
|
|
681
|
+
if (a.spectateThoughts && !a.spectateMcp)
|
|
682
|
+
throw new Error('--spectate-thoughts needs --spectate-mcp (the thought ribbon is part of the follow view)');
|
|
683
|
+
if (a.agentPaper && !a.spectateMcp)
|
|
684
|
+
throw new Error('--agent-paper needs --spectate-mcp (every agent paper trade must be visible in this terminal)');
|
|
685
|
+
if (a.agentPaper && a.db === ':memory:')
|
|
686
|
+
throw new Error('--agent-paper needs an on-disk --db (the agent books into the same paper wallet)');
|
|
621
687
|
return a;
|
|
622
688
|
}
|
|
623
689
|
/**
|
|
@@ -1019,6 +1085,8 @@ export async function main(argv, io = process, proc = process) {
|
|
|
1019
1085
|
});
|
|
1020
1086
|
recorder.attach(feed.dl.store);
|
|
1021
1087
|
ctx.recorder = recorder;
|
|
1088
|
+
// 0.1.5 dev history: PumpPortal / RugCheck creators of new pairs go into the creator index too.
|
|
1089
|
+
ctx.newpairs.onCreator = (mint, creator, source, createdAt) => { recorder.recordCreator(mint, creator, source, undefined, createdAt); };
|
|
1022
1090
|
}
|
|
1023
1091
|
if (args.verbose)
|
|
1024
1092
|
io.stderr.write(`dexterm: rpc ${rpc.provider} (${rpc.basis}) · db ${dbPath} · recorder ${ctx.recorder ? 'on' : 'off'} · live trading ${args.live && !io.env.DEXTERM_OFFLINE ? 'available' : 'off'}\n`);
|
|
@@ -1084,6 +1152,7 @@ export async function main(argv, io = process, proc = process) {
|
|
|
1084
1152
|
for (const e of state.custom.themeErrors)
|
|
1085
1153
|
io.stderr.write(`dexterm: ${e}\n`);
|
|
1086
1154
|
state.priceMode = args.priceMode ?? configPriceMode(state.custom.config); // task-1366: --price-mode wins for the run
|
|
1155
|
+
state.chartAxis = configChartAxis(state.custom.config); // task-1472: default MARKET CAP, `A` toggles + saves
|
|
1087
1156
|
if (args.workspace) {
|
|
1088
1157
|
const r = switchWorkspace(state, args.workspace, []);
|
|
1089
1158
|
if (!r.ok) {
|
|
@@ -2428,6 +2497,9 @@ export function applyAction(state, action, stack) {
|
|
|
2428
2497
|
case 'display.priceMode':
|
|
2429
2498
|
state.pendingToast = togglePriceMode(state);
|
|
2430
2499
|
break; // task-1366: display only, persisted
|
|
2500
|
+
case 'display.chartAxis':
|
|
2501
|
+
state.pendingToast = setChartAxis(state);
|
|
2502
|
+
break; // task-1472: both charts' Y axis, display only, persisted
|
|
2431
2503
|
case 'theme.next':
|
|
2432
2504
|
state.theme = state.custom ? cycleTheme(state.custom, state.theme) : nextTheme(state.theme);
|
|
2433
2505
|
break; // task-1358: user themes join the cycle
|
|
@@ -2591,6 +2663,7 @@ export function scanCommand(state, cmd, stack, now = Date.now()) {
|
|
|
2591
2663
|
if (!e.start(now))
|
|
2592
2664
|
return `AI scan is already on (${MODE_LABEL[e.mode]})`;
|
|
2593
2665
|
live.scanMeter = scanMeterOf(e, now);
|
|
2666
|
+
scanLog(scan, now, 'start', '', `scan on · observe-only · locks at score ≥ ${e.cfg.minScore} · liquidity ≥ $${Math.round(e.cfg.minLiqUsd / 1000)}K`);
|
|
2594
2667
|
return `AI scan ON · observe-only · warming up ${e.cfg.warmupS}s · locks at score ≥ ${e.cfg.minScore} · ai ≤ ${e.cfg.maxAiPerHour}/h · Z stop`;
|
|
2595
2668
|
case 'off': {
|
|
2596
2669
|
if (!e.on)
|
|
@@ -2598,6 +2671,7 @@ export function scanCommand(state, cmd, stack, now = Date.now()) {
|
|
|
2598
2671
|
stopTurn();
|
|
2599
2672
|
const was = e.lock?.symbol;
|
|
2600
2673
|
e.stop(now);
|
|
2674
|
+
scanLog(scan, now, 'stop', was ?? '', was ? 'scan stopped · released' : 'scan stopped');
|
|
2601
2675
|
live.scanMeter = null;
|
|
2602
2676
|
scan.returnTo = null;
|
|
2603
2677
|
return `AI scan stopped${was ? ` (released ${sym(was)})` : ''}`;
|
|
@@ -2610,6 +2684,7 @@ export function scanCommand(state, cmd, stack, now = Date.now()) {
|
|
|
2610
2684
|
const locked = e.lock;
|
|
2611
2685
|
stopTurn();
|
|
2612
2686
|
e.resume(now);
|
|
2687
|
+
scanLog(scan, now, 'release', locked?.symbol ?? '', `released · cooling down ${Math.round(e.cfg.coinCooldownS / 60)}m · watching for the next coin`);
|
|
2613
2688
|
live.scanMeter = scanMeterOf(e, now);
|
|
2614
2689
|
// back from the coin the lock pulled up to where the user was, so the ticker is in view
|
|
2615
2690
|
if (locked && state.screen === 'token' && live.token?.view.mint === locked.mint)
|
|
@@ -2639,6 +2714,50 @@ export function applyScanAnalystAction(state, raw) {
|
|
|
2639
2714
|
const res = validateAnalystAction(raw, { ...allow, types: SCAN_ACTION_TYPES });
|
|
2640
2715
|
return { ok: false, error: res.ok ? 'AI scan mode is observe-only' : res.error };
|
|
2641
2716
|
}
|
|
2717
|
+
/**
|
|
2718
|
+
* The analyst engine for a run (0.1.5): `--analyst-engine`, then DEXTERM_ANALYST_ENGINE, then config
|
|
2719
|
+
* `analyst.engine`, then auto (claude when installed, else codex). Only the user picks it; models cannot.
|
|
2720
|
+
*/
|
|
2721
|
+
export function analystEngineFor(flag, env, configured, has = (b) => analystCliOnPath(b, env)) {
|
|
2722
|
+
const choice = parseEngineChoice(flag) ?? parseEngineChoice(env.DEXTERM_ANALYST_ENGINE) ?? parseEngineChoice(configured) ?? 'auto';
|
|
2723
|
+
return resolveEngine(choice, has);
|
|
2724
|
+
}
|
|
2725
|
+
/** `/engine [auto|claude|codex]` in the chat box (0.1.5). Null when the input is not this command. */
|
|
2726
|
+
export function parseEngineCommand(q) {
|
|
2727
|
+
const m = /^\/engine(?:\s+(\S+))?\s*$/i.exec(q.trim());
|
|
2728
|
+
if (!m)
|
|
2729
|
+
return null;
|
|
2730
|
+
if (!m[1])
|
|
2731
|
+
return { show: true };
|
|
2732
|
+
const c = parseEngineChoice(m[1]);
|
|
2733
|
+
return c ? { choice: c } : { error: `unknown analyst engine "${m[1].slice(0, 16)}" (use /engine auto|claude|codex)` };
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Apply `/engine`: switch the CLI for NEW conversations this run (cached per-coin chats are dropped so the next
|
|
2737
|
+
* `a` starts on the chosen engine; a running turn finishes where it started). Returns the line to show.
|
|
2738
|
+
*/
|
|
2739
|
+
export function engineCommand(state, cmd, env, has = (b) => analystCliOnPath(b, env)) {
|
|
2740
|
+
const installed = ['claude', 'codex'].filter((b) => has(b));
|
|
2741
|
+
const list = installed.length ? installed.join(' + ') : 'neither claude nor codex';
|
|
2742
|
+
if ('error' in cmd)
|
|
2743
|
+
return cmd.error;
|
|
2744
|
+
const cur = state.analystEngine ?? 'claude';
|
|
2745
|
+
if ('show' in cmd)
|
|
2746
|
+
return `analyst engine: ${cur} · installed: ${list} · /engine claude|codex|auto switches (config analyst.engine keeps it)`;
|
|
2747
|
+
const next = resolveEngine(cmd.choice, has);
|
|
2748
|
+
if (cmd.choice !== 'auto' && !has(next))
|
|
2749
|
+
return `${next} is not installed (installed: ${list}); ${next === 'codex' ? 'npm i -g @openai/codex && codex login' : 'install Claude Code, then run claude once to log in'}`;
|
|
2750
|
+
if (next !== cur) {
|
|
2751
|
+
for (const [k, sess] of state.analystByMint ?? []) {
|
|
2752
|
+
if (!sess.busy)
|
|
2753
|
+
state.analystByMint.delete(k);
|
|
2754
|
+
}
|
|
2755
|
+
if (state.analyst && !state.analyst.busy && state.analyst.engine !== next)
|
|
2756
|
+
state.analyst = null;
|
|
2757
|
+
}
|
|
2758
|
+
state.analystEngine = next;
|
|
2759
|
+
return `analyst engine: ${next} (your own ${next} CLI and login) for new conversations · set analyst.engine in config.json to keep it`;
|
|
2760
|
+
}
|
|
2642
2761
|
/**
|
|
2643
2762
|
* The ONE way a scan-driven analyst conversation is built (task-1324; the loop and the safety tests both
|
|
2644
2763
|
* use it): the locked coin as the selected coin, `actionTypes: []` (observe-only at the validator, the
|
|
@@ -2652,7 +2771,7 @@ export function createScanSession(state, mint, symbol, hooks = {}, deps = {}) {
|
|
|
2652
2771
|
const type = typeof raw.type === 'string' ? raw.type.replace(/[^a-z_]/gi, '').slice(0, 24) : '?';
|
|
2653
2772
|
hooks.onBlocked?.(type, r.error ?? 'refused');
|
|
2654
2773
|
};
|
|
2655
|
-
return new AnalystSession({ skill: 'token-dossier', mint, symbol, screenState: ss, onAction, actionTypes: SCAN_ACTION_TYPES, scan: true }, hooks.onChange ?? (() => { }), deps);
|
|
2774
|
+
return new AnalystSession({ skill: 'token-dossier', mint, symbol, screenState: ss, onAction, actionTypes: SCAN_ACTION_TYPES, scan: true, ...(state.analystEngine === 'codex' ? { engine: 'codex' } : {}) }, hooks.onChange ?? (() => { }), deps);
|
|
2656
2775
|
}
|
|
2657
2776
|
/**
|
|
2658
2777
|
* Resolve a mint to an openable seed from what the app already knows (screener view → new pairs →
|
|
@@ -2908,6 +3027,61 @@ export function applyAnalystAction(state, raw, stack) {
|
|
|
2908
3027
|
times.push(now);
|
|
2909
3028
|
return { ok: true, label: res.label };
|
|
2910
3029
|
}
|
|
3030
|
+
/**
|
|
3031
|
+
* task-1466: the price this terminal itself shows for a coin, to mark the followed agent's open PAPER position (the
|
|
3032
|
+
* HUD's live PnL): the open token's push price (LIVE) or polled price, else the screener row's price. null if the
|
|
3033
|
+
* terminal has no price for it. Never the agent's own number.
|
|
3034
|
+
*/
|
|
3035
|
+
export function markPriceFor(state, mint) {
|
|
3036
|
+
const live = state.live;
|
|
3037
|
+
if (live?.token?.view.mint === mint) {
|
|
3038
|
+
const px = (live.badgeFor(mint) === 'LIVE' ? live.livePrice(mint) : null) ?? live.token.view.price;
|
|
3039
|
+
if (typeof px === 'number' && Number.isFinite(px) && px > 0)
|
|
3040
|
+
return px;
|
|
3041
|
+
}
|
|
3042
|
+
const row = state.screener.rows.find((r) => r.mint === mint);
|
|
3043
|
+
if (row && typeof row.price === 'number' && Number.isFinite(row.price) && row.price > 0)
|
|
3044
|
+
return row.price;
|
|
3045
|
+
// task-1473: the held coin scrolled out of the list (run25): its fast-lane push price (the held mint is pinned there)
|
|
3046
|
+
const fast = live?.livePrice(mint) ?? null;
|
|
3047
|
+
return typeof fast === 'number' && Number.isFinite(fast) && fast > 0 ? fast : null;
|
|
3048
|
+
}
|
|
3049
|
+
let ownOpenedMemo = null;
|
|
3050
|
+
/**
|
|
3051
|
+
* task-1473 r2: the user's OWN held position for the AI-scan PnL chip, marked with this terminal's own price (same
|
|
3052
|
+
* task-1323 position row the token screen uses): the scan's locked coin when held, else the open token, else the
|
|
3053
|
+
* first open mint (live first). null when flat. Reads the ledgers only (memoised); never trades, sizes or signs.
|
|
3054
|
+
*/
|
|
3055
|
+
export function ownHeldSample(state, now) {
|
|
3056
|
+
const live = state.live;
|
|
3057
|
+
if (!live)
|
|
3058
|
+
return null;
|
|
3059
|
+
const src = live.positionSource;
|
|
3060
|
+
const open = src.openMints();
|
|
3061
|
+
if (open.length === 0)
|
|
3062
|
+
return null;
|
|
3063
|
+
const prefer = [state.scan?.engine.lock?.mint, live.token?.view.mint].filter((m) => !!m);
|
|
3064
|
+
const mint = prefer.find((m) => open.includes(m)) ?? open[0];
|
|
3065
|
+
const held = src.held(mint);
|
|
3066
|
+
if (!held)
|
|
3067
|
+
return null;
|
|
3068
|
+
const r = positionRow(held, markPriceFor(state, mint), state.sol?.price ?? src.solUsd());
|
|
3069
|
+
if (!ownOpenedMemo || ownOpenedMemo.mint !== mint || now - ownOpenedMemo.at > 5000) {
|
|
3070
|
+
let t = null;
|
|
3071
|
+
try {
|
|
3072
|
+
t = currentPositionFills([...(live.livePositions?.records(200, mint) ?? []), ...live.wallet.trades(500).filter((x) => x.mint === mint)])[0]?.t ?? null;
|
|
3073
|
+
}
|
|
3074
|
+
catch {
|
|
3075
|
+
t = null;
|
|
3076
|
+
}
|
|
3077
|
+
ownOpenedMemo = { mint, at: now, t };
|
|
3078
|
+
}
|
|
3079
|
+
return {
|
|
3080
|
+
mint, symbol: held.symbol, kind: held.kind === 'live' ? 'live' : 'paper',
|
|
3081
|
+
entryUsd: r.avgEntryUsd, markUsd: r.priceUsd, pnlPct: r.unrealizedPct, pnlUsd: r.unrealizedUsd,
|
|
3082
|
+
openedAt: ownOpenedMemo.t, targetUsd: r.targetUsd,
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
2911
3085
|
/**
|
|
2912
3086
|
* task-1421: move the REAL terminal to mirror an agent's navigation (follow mode, on the owner's own
|
|
2913
3087
|
* machine). This is the same VIEW-ONLY navigation the analyst / go-to path uses — open a token, switch
|
|
@@ -3476,7 +3650,14 @@ export function terminalGuard(io, onFatal = () => { }, proc = process, hooks = {
|
|
|
3476
3650
|
proc.exit(1);
|
|
3477
3651
|
};
|
|
3478
3652
|
const onExit = () => restore();
|
|
3479
|
-
const onSignal = (sig) => {
|
|
3653
|
+
const onSignal = (sig) => {
|
|
3654
|
+
restore();
|
|
3655
|
+
try {
|
|
3656
|
+
hooks.onSignal?.();
|
|
3657
|
+
}
|
|
3658
|
+
catch { /* cleanup is best-effort; the exit still happens */ }
|
|
3659
|
+
proc.exit(sig === 'SIGTERM' ? 143 : 129);
|
|
3660
|
+
};
|
|
3480
3661
|
const onTerm = () => onSignal('SIGTERM');
|
|
3481
3662
|
const onHup = () => onSignal('SIGHUP');
|
|
3482
3663
|
let installed = false;
|
|
@@ -3513,6 +3694,8 @@ export function fpsFor(args, intensity) {
|
|
|
3513
3694
|
const SYNC_ON = '\x1b[?2026h';
|
|
3514
3695
|
const SYNC_OFF = '\x1b[?2026l';
|
|
3515
3696
|
export function interactive(state, io, depth, args, proc = process) {
|
|
3697
|
+
// 0.1.5: the analyst engine for new conversations (the user's own claude or codex CLI; never a shipped key).
|
|
3698
|
+
state.analystEngine ??= analystEngineFor(args.analystEngine, io.env, state.custom?.config.analyst?.engine);
|
|
3516
3699
|
const out = io.stdout;
|
|
3517
3700
|
const inp = io.stdin;
|
|
3518
3701
|
const rnd = mulberry32(99);
|
|
@@ -3816,6 +3999,9 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3816
3999
|
// task-1421: the MCP follow session (set below when `--spectate-mcp` is on). Declared here so the quit /
|
|
3817
4000
|
// terminal-guard teardown closures can stop it.
|
|
3818
4001
|
let followSession = null;
|
|
4002
|
+
let lastHudMark = null; // task-1466: the last terminal price that marked the agent's PAPER position
|
|
4003
|
+
// task-1443: `--agent-paper <mandate-id>` (with --spectate-mcp) lets that agent PAPER-trade inside the mandate.
|
|
4004
|
+
const agentPaper = args.spectateMcp && args.agentPaper ? new AgentPaperSwitch({ env: io.env, mandateId: args.agentPaper, db: args.db ?? defaultDbPath(io.env) }) : null;
|
|
3819
4005
|
// Idle economy (task-1303, retuned for task-1319 AC3): between events the ambient rain redraws at
|
|
3820
4006
|
// IDLE_FPS instead of the full frame cap, cutting idle CPU. A keypress opens a KEY_WAKE_S full-rate
|
|
3821
4007
|
// window (`wakeUntil`) so input and transitions stay smooth; data landings paint at once on their own
|
|
@@ -3844,10 +4030,13 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3844
4030
|
say(`trade moment failed: ${describeError(e)}`, 'warn');
|
|
3845
4031
|
}
|
|
3846
4032
|
};
|
|
3847
|
-
const
|
|
3848
|
-
clearInterval(timer); void followSession?.stop(); state.analyst?.cancel(); if (state.analystByMint)
|
|
4033
|
+
const teardown = () => { done = true; prefetcher?.close(); if (timer)
|
|
4034
|
+
clearInterval(timer); void followSession?.stop(); agentPaper?.stop(); state.analyst?.cancel(); if (state.analystByMint)
|
|
3849
4035
|
for (const s of state.analystByMint.values())
|
|
3850
|
-
s.cancel(); state.popups?.shutdown(); voice?.disable(); images?.close(); live?.close(); feed?.close(); }
|
|
4036
|
+
s.cancel(); state.popups?.shutdown(); voice?.disable(); images?.close(); live?.close(); feed?.close(); };
|
|
4037
|
+
// task-1443: SIGTERM/SIGHUP (a closed terminal window) runs the same teardown as a fatal error, so the agent-paper
|
|
4038
|
+
// rendezvous, the spectator rendezvous and the --record file never outlive the terminal that owns them.
|
|
4039
|
+
const guard = terminalGuard(io, teardown, proc, { beforeLeave: () => layer?.teardown() ?? '', onSignal: teardown });
|
|
3851
4040
|
let lastTokenRefresh = 0;
|
|
3852
4041
|
let lastWatchRefresh = 0;
|
|
3853
4042
|
let lastPositionsRefresh = 0;
|
|
@@ -4041,11 +4230,22 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4041
4230
|
}
|
|
4042
4231
|
scheduleDraw();
|
|
4043
4232
|
},
|
|
4044
|
-
|
|
4233
|
+
// task-1466: the HUD dock says what the agent did; the toast is only the fallback for a terminal too short for it.
|
|
4234
|
+
onStatus: (line) => { if (size()[1] < HUD_MIN_H)
|
|
4235
|
+
say(line, 'ai', 4); scheduleDraw(); },
|
|
4045
4236
|
onEvent: () => scheduleDraw(),
|
|
4237
|
+
onControl: agentPaper ? (st) => agentPaper.control(st) : undefined,
|
|
4238
|
+
snapshot: () => followSnapshot(state),
|
|
4239
|
+
thoughtsFile: args.spectateThoughts ?? undefined,
|
|
4046
4240
|
});
|
|
4241
|
+
// task-1466 spectator v2: the agent HUD (phase rail, heartbeat, thought ribbon, call timeline, the PAPER
|
|
4242
|
+
// execution moment, pause / kill) draws over this terminal while it follows. Calm mode = reduced motion.
|
|
4243
|
+
state.agentHud = { story: followSession.story, clock: () => Date.now(), dataLabel: args.replay ? 'REPLAY DATA' : null };
|
|
4047
4244
|
void followSession.start();
|
|
4048
|
-
|
|
4245
|
+
if (size()[1] < HUD_MIN_H)
|
|
4246
|
+
say('following agents over MCP · ctrl+p pause · ctrl+k stop · any key takes over', 'ai', 6);
|
|
4247
|
+
if (agentPaper)
|
|
4248
|
+
say(agentPaper.start(), 'ai', 8);
|
|
4049
4249
|
}
|
|
4050
4250
|
// Launch a real Claude analyst session when a key asked for one (applyAction cannot spawn). A
|
|
4051
4251
|
// `question` means start it as a chat turn, not a paid structured skill run (task-1311, QA B3).
|
|
@@ -4113,7 +4313,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4113
4313
|
maybeSpeakVoiceAnswer(session); // task-1282: speak a voice-initiated answer when it settles
|
|
4114
4314
|
scheduleDraw();
|
|
4115
4315
|
};
|
|
4116
|
-
const session = new AnalystSession({ skill: p.skill, mint: p.mint, symbol: p.symbol, screenState: buildScreenState(state), model: p.model, onAction }, onChange);
|
|
4316
|
+
const session = new AnalystSession({ skill: p.skill, mint: p.mint, symbol: p.symbol, screenState: buildScreenState(state), model: p.model, onAction, ...(state.analystEngine === 'codex' ? { engine: 'codex' } : {}) }, onChange);
|
|
4117
4317
|
if (p.question)
|
|
4118
4318
|
session.ask(p.question, buildScreenState(state));
|
|
4119
4319
|
else
|
|
@@ -4151,6 +4351,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4151
4351
|
scan.blocked.push(type);
|
|
4152
4352
|
if (scan.blocked.length > 6)
|
|
4153
4353
|
scan.blocked.shift();
|
|
4354
|
+
scanLog(scan, Date.now(), 'blocked', scan.engine.lock?.symbol ?? '', `blocked analyst action "${type}" · observe-only`);
|
|
4154
4355
|
}
|
|
4155
4356
|
scanSay(`AI scan blocked analyst action "${type}" · observe-only (${error})`, 'warn', 5);
|
|
4156
4357
|
scheduleDraw();
|
|
@@ -4182,6 +4383,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4182
4383
|
state.aiFocused = false;
|
|
4183
4384
|
state.aiScroll = 0;
|
|
4184
4385
|
state.aiRevealAll = false;
|
|
4386
|
+
scanLog(scan, Date.now(), 'lock', d.symbol, `locked · score ${d.score} · ${d.hits.map((h) => h.reason).join(' · ')}`);
|
|
4185
4387
|
startScanSession(d.mint, d.symbol, d.question, `AI scan · score ${d.score} · ${d.hits.map((h) => h.id).join(' + ')} → short read`);
|
|
4186
4388
|
syncAnalyst(state);
|
|
4187
4389
|
live.syncFastSet();
|
|
@@ -4199,6 +4401,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4199
4401
|
const sess = scan.session;
|
|
4200
4402
|
if (!sess)
|
|
4201
4403
|
return;
|
|
4404
|
+
scanLog(scan, Date.now(), 'refresh', d.symbol, `moved: ${d.moved.join(', ')} · refreshing the read`);
|
|
4202
4405
|
sess.ask(d.question, scanScreenState(state, d.mint, d.symbol), `AI scan refresh · ${d.moved.join(' · ')}`);
|
|
4203
4406
|
if (state.analyst === sess) {
|
|
4204
4407
|
state.aiScroll = 0;
|
|
@@ -4237,7 +4440,8 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4237
4440
|
return;
|
|
4238
4441
|
if (scan.session?.busy)
|
|
4239
4442
|
scan.session.interrupt();
|
|
4240
|
-
scan.engine.cancelAnalysis(Date.now())
|
|
4443
|
+
if (scan.engine.cancelAnalysis(Date.now()))
|
|
4444
|
+
scanLog(scan, Date.now(), 'cancel', scan.engine.lock?.symbol ?? '', 'analysis cancelled by you · coin held, nothing else locks');
|
|
4241
4445
|
if (live)
|
|
4242
4446
|
live.scanMeter = scanMeterOf(scan.engine, Date.now());
|
|
4243
4447
|
scanSay(`analysis cancelled · scan holds $${cleanText(scan.engine.lock?.symbol ?? '', 12)} · R resume scan · Z stop`, 'ai', 5);
|
|
@@ -4323,6 +4527,10 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4323
4527
|
solUsd: () => state.sol?.price ?? live.positions.solUsd(),
|
|
4324
4528
|
imagePath: (v) => popupImagePath(state, v.image),
|
|
4325
4529
|
priceMode: () => priceModeOf(state), // task-1366
|
|
4530
|
+
chartAxis: () => chartAxisOf(state), // task-1472: the popup's Y axis
|
|
4531
|
+
setChartAxis: (a) => { say(setChartAxis(state, a), 'info', 3); if (!done)
|
|
4532
|
+
draw(); }, // task-1472: the popup's MCAP | PRICE control
|
|
4533
|
+
pnlChip: () => pnlChipFor(state, state.agentHud?.clock() ?? Date.now()), // task-1473 r2: the chip on the chart popup
|
|
4326
4534
|
} : null,
|
|
4327
4535
|
});
|
|
4328
4536
|
}
|
|
@@ -5037,6 +5245,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
5037
5245
|
if (timer)
|
|
5038
5246
|
clearInterval(timer);
|
|
5039
5247
|
void followSession?.stop();
|
|
5248
|
+
agentPaper?.stop();
|
|
5040
5249
|
guard.restore();
|
|
5041
5250
|
guard.uninstall();
|
|
5042
5251
|
cancelAllAnalysts();
|
|
@@ -5635,6 +5844,14 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
5635
5844
|
draw();
|
|
5636
5845
|
continue;
|
|
5637
5846
|
}
|
|
5847
|
+
// 0.1.5: `/engine [auto|claude|codex]` switches the analyst CLI for new conversations; never sent to a model.
|
|
5848
|
+
const en0 = q.startsWith('/') ? parseEngineCommand(q) : null;
|
|
5849
|
+
if (en0) {
|
|
5850
|
+
state.aiInput = '';
|
|
5851
|
+
say(engineCommand(state, en0, io.env), typeof en0 === 'object' && 'error' in en0 ? 'warn' : 'info', 6);
|
|
5852
|
+
draw();
|
|
5853
|
+
continue;
|
|
5854
|
+
}
|
|
5638
5855
|
// task-1324: `/scan …` in the chat input is a command, never sent to claude.
|
|
5639
5856
|
const sc0 = q.startsWith('/') ? parseScanCommand(q) : null;
|
|
5640
5857
|
if (sc0) {
|
|
@@ -6085,6 +6302,36 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
6085
6302
|
}
|
|
6086
6303
|
// market → rain: EMA on activity every tick, mood from the data every 4 s unless an event holds it
|
|
6087
6304
|
blendActivity(v, activityFor(state), v.frame % (tickEvery * 8) === 0 && v.frame >= moodHoldUntil);
|
|
6305
|
+
// task-1466: the agent's open PAPER position is marked with this terminal's own live price (the HUD's PnL).
|
|
6306
|
+
// Only a NEW price marks (a 10 s-old view price must not overwrite a fresher one a tool result carried).
|
|
6307
|
+
if (followSession && state.live && state.live.heldMint !== followSession.story.heldMint) {
|
|
6308
|
+
state.live.heldMint = followSession.story.heldMint;
|
|
6309
|
+
state.live.syncFastSet();
|
|
6310
|
+
} // task-1473
|
|
6311
|
+
if (followSession) {
|
|
6312
|
+
const held = followSession.story.heldMint;
|
|
6313
|
+
const px = held ? markPriceFor(state, held) : null;
|
|
6314
|
+
if (held && px !== null && px !== lastHudMark) {
|
|
6315
|
+
followSession.story.mark(held, px, Date.now());
|
|
6316
|
+
lastHudMark = px;
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
// task-1473 r2: AI scan mode + the user's OWN held position → the PnL chip (band row, dock, chart popup)
|
|
6320
|
+
if (state.scan?.engine.on && state.live) {
|
|
6321
|
+
state.ownPnl ??= new OwnPnlTracker();
|
|
6322
|
+
state.ownPnl.update(ownHeldSample(state, Date.now()), Date.now());
|
|
6323
|
+
if (!followSession && state.live.heldMint !== state.ownPnl.mint) {
|
|
6324
|
+
state.live.heldMint = state.ownPnl.mint;
|
|
6325
|
+
state.live.syncFastSet();
|
|
6326
|
+
} // run25: pin it
|
|
6327
|
+
}
|
|
6328
|
+
else if (state.ownPnl?.mint) {
|
|
6329
|
+
state.ownPnl.update(null, 0);
|
|
6330
|
+
if (!followSession && state.live?.heldMint) {
|
|
6331
|
+
state.live.heldMint = null;
|
|
6332
|
+
state.live.syncFastSet();
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6088
6335
|
}
|
|
6089
6336
|
if (state.ai && state.ai.phase === 'thinking') {
|
|
6090
6337
|
state.ai.ticks += 1;
|
|
@@ -6114,6 +6361,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
6114
6361
|
aiLive: !!(state.ai && state.ai.phase !== 'done'),
|
|
6115
6362
|
awake: v.frame < wakeUntil,
|
|
6116
6363
|
fxBusy: fxBusy(v),
|
|
6364
|
+
hud: followSession !== null && hudAnimating(followSession.story.view(Date.now()), Date.now()),
|
|
6117
6365
|
frame: v.frame,
|
|
6118
6366
|
idleEvery,
|
|
6119
6367
|
}))
|