dexterm-cli 0.1.5 → 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-spectator.js +17 -1
- package/dist/packages/cli/src/analyst/backend.js +23 -0
- package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
- package/dist/packages/cli/src/analyst/session.js +12 -1
- package/dist/packages/cli/src/app-help-gen.js +7 -7
- package/dist/packages/cli/src/backtest-cmd.js +13 -2
- package/dist/packages/cli/src/display.js +55 -9
- package/dist/packages/cli/src/live/dev-history.js +31 -15
- package/dist/packages/cli/src/live/index.js +5 -1
- package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
- package/dist/packages/cli/src/live/screens/token-live.js +41 -11
- package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
- package/dist/packages/cli/src/live/trade.js +22 -0
- package/dist/packages/cli/src/main.js +146 -12
- package/dist/packages/cli/src/mcp-follow.js +103 -6
- package/dist/packages/cli/src/plan-cmd.js +15 -5
- package/dist/packages/cli/src/popup.js +17 -1
- 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 +4 -0
- 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/schema.js +8 -2
- package/dist/packages/data/src/index.js +2 -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/trade/agent/paper-desk.js +77 -12
- 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/dev-history.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
- package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
- package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +3 -2
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +3 -3
- 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 +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-larp-measured-accuracy.md +1 -0
- 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-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-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 +10 -7
- 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/src/paper-agent.js +3 -2
- package/dist/packages/mcp/src/server.js +162 -21
- package/dist/packages/mcp/src/spectator-sink.js +85 -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 +7 -3
- 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
|
@@ -14,11 +14,52 @@
|
|
|
14
14
|
// kill" are local follow controls: pause/take-over stop the terminal mirroring the agent (the external agent
|
|
15
15
|
// keeps running — we simply stop moving the screen), and kill removes the rendezvous so the server stops
|
|
16
16
|
// emitting. Any user keypress during an agent move takes control, so the human and the agent never fight.
|
|
17
|
-
import { writeFileSync, readFileSync, rmSync, mkdirSync, statSync } from 'node:fs';
|
|
17
|
+
import { writeFileSync, readFileSync, rmSync, mkdirSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
18
18
|
import { dirname } from 'node:path';
|
|
19
19
|
import { randomBytes } from 'node:crypto';
|
|
20
|
-
import { SpectatorHub, FollowController, SpectatorServer, } from '../../agents/src/index.js';
|
|
20
|
+
import { SpectatorHub, FollowController, SpectatorServer, AgentStory, sanitizeDetail, } from '../../agents/src/index.js';
|
|
21
21
|
import { rendezvousPath, writeFollowScreen, clearFollowScreen } from '../../mcp/src/index.js';
|
|
22
|
+
/**
|
|
23
|
+
* task-1466: the agent's own words in one JSONL line of a thoughts feed. Understands Claude Code stream-json
|
|
24
|
+
* (`assistant` messages: text and thinking blocks), the capture harness's brain feed (`{tEpoch, ev}` wrapping one),
|
|
25
|
+
* and plain `{"thought": "…"}` / `{"text": "…"}` lines. Anything else yields nothing. Text only; never acted on.
|
|
26
|
+
*/
|
|
27
|
+
export function thoughtsOf(line) {
|
|
28
|
+
const s = line.trim();
|
|
29
|
+
if (!s || s[0] !== '{' || s.length > 200_000)
|
|
30
|
+
return [];
|
|
31
|
+
let o;
|
|
32
|
+
try {
|
|
33
|
+
o = JSON.parse(s);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
let t = typeof o.tEpoch === 'number' && Number.isFinite(o.tEpoch) ? o.tEpoch : null;
|
|
39
|
+
if (o.ev && typeof o.ev === 'object')
|
|
40
|
+
o = o.ev;
|
|
41
|
+
if (t === null && typeof o.t === 'number' && Number.isFinite(o.t) && o.t > 1e12)
|
|
42
|
+
t = o.t;
|
|
43
|
+
// task-1473: a demo / stand-in feed marks its scripted lines `"demo": true` (the brain view tags them "demo text")
|
|
44
|
+
if (typeof o.thought === 'string')
|
|
45
|
+
return [{ text: o.thought, t, ...(o.demo === true ? { demo: true } : {}) }];
|
|
46
|
+
if (o.type === 'assistant' && o.message && typeof o.message === 'object') {
|
|
47
|
+
const content = o.message.content;
|
|
48
|
+
if (!Array.isArray(content))
|
|
49
|
+
return [];
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const b of content) {
|
|
52
|
+
if (b?.type === 'text' && typeof b.text === 'string')
|
|
53
|
+
out.push({ text: b.text, t });
|
|
54
|
+
else if (b?.type === 'thinking' && typeof b.thinking === 'string' && b.thinking.trim())
|
|
55
|
+
out.push({ text: b.thinking, t });
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
if (typeof o.text === 'string' && !o.type)
|
|
60
|
+
return [{ text: o.text, t }];
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
22
63
|
function navLabel(agent, nav) {
|
|
23
64
|
const coin = nav.mint ? ` ${nav.mint.length > 10 ? nav.mint.slice(0, 4) + '…' + nav.mint.slice(-4) : nav.mint}` : '';
|
|
24
65
|
if (nav.screen === 'token')
|
|
@@ -45,6 +86,8 @@ function navLabel(agent, nav) {
|
|
|
45
86
|
export class McpFollowSession {
|
|
46
87
|
hub;
|
|
47
88
|
follow;
|
|
89
|
+
/** task-1466: the story the spectator v2 HUD draws (phase rail, timeline, thoughts, PAPER trade arc). */
|
|
90
|
+
story;
|
|
48
91
|
opts;
|
|
49
92
|
rvPath;
|
|
50
93
|
channelPath;
|
|
@@ -52,6 +95,7 @@ export class McpFollowSession {
|
|
|
52
95
|
agentLabel;
|
|
53
96
|
currentAgent;
|
|
54
97
|
offset = 0;
|
|
98
|
+
thoughtsOffset = 0;
|
|
55
99
|
timer = null;
|
|
56
100
|
stream = null;
|
|
57
101
|
armed = false;
|
|
@@ -71,7 +115,8 @@ export class McpFollowSession {
|
|
|
71
115
|
kickAgent: () => { this.disarm(); return 1; },
|
|
72
116
|
};
|
|
73
117
|
this.follow = new FollowController({ controls, activityCap: Math.max(1, opts.activityCap ?? 200) });
|
|
74
|
-
this.
|
|
118
|
+
this.story = new AgentStory(this.agentLabel);
|
|
119
|
+
this.hub.on((ev) => { this.follow.apply(ev); this.story.apply(ev); });
|
|
75
120
|
}
|
|
76
121
|
/** Arm the rendezvous (so a `dexterm mcp` server starts emitting) and begin tailing. Idempotent. */
|
|
77
122
|
async start() {
|
|
@@ -133,6 +178,51 @@ export class McpFollowSession {
|
|
|
133
178
|
}
|
|
134
179
|
/** Read and fold any records appended since the last pump. Public so tests can drive it without timers. */
|
|
135
180
|
pump() {
|
|
181
|
+
this.pumpThoughts();
|
|
182
|
+
this.pumpChannel();
|
|
183
|
+
}
|
|
184
|
+
/** task-1466: new lines of the agent's own words → the story's thought ribbon (bounded: ≤ 256 KB per pump). */
|
|
185
|
+
pumpThoughts() {
|
|
186
|
+
const file = this.opts.thoughtsFile;
|
|
187
|
+
if (!file)
|
|
188
|
+
return;
|
|
189
|
+
let text;
|
|
190
|
+
try {
|
|
191
|
+
const st = statSync(file);
|
|
192
|
+
if (st.size < this.thoughtsOffset)
|
|
193
|
+
this.thoughtsOffset = 0;
|
|
194
|
+
if (st.size === this.thoughtsOffset)
|
|
195
|
+
return;
|
|
196
|
+
if (st.size - this.thoughtsOffset > 256 * 1024)
|
|
197
|
+
this.thoughtsOffset = st.size - 256 * 1024; // skip a backlog, keep the tail
|
|
198
|
+
const fd = openSync(file, 'r');
|
|
199
|
+
try {
|
|
200
|
+
const buf = Buffer.alloc(st.size - this.thoughtsOffset);
|
|
201
|
+
readSync(fd, buf, 0, buf.length, this.thoughtsOffset);
|
|
202
|
+
text = buf.toString('utf8');
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
closeSync(fd);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const nl = text.lastIndexOf('\n');
|
|
212
|
+
if (nl < 0)
|
|
213
|
+
return;
|
|
214
|
+
this.thoughtsOffset += Buffer.byteLength(text.slice(0, nl + 1), 'utf8');
|
|
215
|
+
const now = (this.opts.now ?? Date.now)();
|
|
216
|
+
for (const line of text.slice(0, nl).split('\n')) {
|
|
217
|
+
for (const th of thoughtsOf(line))
|
|
218
|
+
this.story.thought(th.text, th.t ?? now, 'agent', th.demo === true);
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
this.opts.onEvent?.();
|
|
222
|
+
}
|
|
223
|
+
catch { /* ignore */ }
|
|
224
|
+
}
|
|
225
|
+
pumpChannel() {
|
|
136
226
|
let text;
|
|
137
227
|
try {
|
|
138
228
|
const st = statSync(this.channelPath);
|
|
@@ -184,6 +274,7 @@ export class McpFollowSession {
|
|
|
184
274
|
ms: rec.ms ?? null,
|
|
185
275
|
dataAgeSec: rec.dataAgeSec ?? null,
|
|
186
276
|
nav: rec.nav ?? null,
|
|
277
|
+
detail: sanitizeDetail(rec.detail),
|
|
187
278
|
ok: rec.ok ?? true,
|
|
188
279
|
t: typeof rec.t === 'number' ? rec.t : undefined,
|
|
189
280
|
});
|
|
@@ -225,10 +316,14 @@ export class McpFollowSession {
|
|
|
225
316
|
takeOver() { const l = this.follow.takeOver(this.currentAgent); this.paused = false; this.control('takeover'); this.opts.onStatus?.(l); return l; }
|
|
226
317
|
resume() { const l = this.follow.resume(this.currentAgent); this.paused = false; this.control('running'); this.opts.onStatus?.(l); return l; }
|
|
227
318
|
kill() { const l = this.follow.kill(this.currentAgent); this.paused = false; this.control('stopped'); this.opts.onStatus?.(l); return l; }
|
|
228
|
-
control(state) {
|
|
229
|
-
this.opts.
|
|
319
|
+
control(state) {
|
|
320
|
+
const now = (this.opts.now ?? Date.now)();
|
|
321
|
+
this.story.setControl(state === 'stopped' ? 'killed' : state, now);
|
|
322
|
+
try {
|
|
323
|
+
this.opts.onControl?.(state);
|
|
324
|
+
}
|
|
325
|
+
catch { /* never breaks the key loop */ }
|
|
230
326
|
}
|
|
231
|
-
catch { /* never breaks the key loop */ } }
|
|
232
327
|
/** Any physical keypress while the agent is driving: the human takes over (no fight over the screen). */
|
|
233
328
|
userKey() { if (this.follow.controlState === 'agent')
|
|
234
329
|
this.takeOver(); }
|
|
@@ -237,6 +332,8 @@ export class McpFollowSession {
|
|
|
237
332
|
get screenModel() { return this.follow.screenModel; }
|
|
238
333
|
get streamPort() { return this.stream?.port ?? 0; }
|
|
239
334
|
view() { return this.follow.view(); }
|
|
335
|
+
/** task-1466: the story at `now` for the HUD. */
|
|
336
|
+
storyView(now = (this.opts.now ?? Date.now)()) { return this.story.view(now); }
|
|
240
337
|
/** Tear down: stop the timer, remove the rendezvous, close the stream. Safe to call more than once. */
|
|
241
338
|
async stop() {
|
|
242
339
|
if (this.timer) {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// dexterm plan journal [--verify] [--export]
|
|
10
10
|
// dexterm mandate draft [--total 100] [--per-trade 20] [--per-coin 40] [--daily-loss 50]
|
|
11
11
|
// [--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4]
|
|
12
|
+
// [--max-open 20] [--max-impact 3] [--paper]
|
|
12
13
|
// dexterm mandate list [--json] | arm <id> | revoke <id>
|
|
13
14
|
//
|
|
14
15
|
// Arming is the only gate: `arm` reads ONE real keystroke in raw mode (ttyKeypress) and refuses a pipe,
|
|
@@ -44,7 +45,10 @@ const PLAN_USAGE = `dexterm plan <command>
|
|
|
44
45
|
journal [--verify] [--export]`;
|
|
45
46
|
const MANDATE_USAGE = `dexterm mandate <command>
|
|
46
47
|
draft [--total 100] [--per-trade 20] [--per-coin 40] [--daily-loss 50]
|
|
47
|
-
[--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4] [--
|
|
48
|
+
[--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4] [--max-open 20] [--max-impact 3]
|
|
49
|
+
[--paper] [--json]
|
|
50
|
+
(--paper: a PAPER-ONLY mandate for the agent paper desk, with its own
|
|
51
|
+
paper ceilings; no plan, copy trade or signer can ever use it)
|
|
48
52
|
list [--json]
|
|
49
53
|
arm <id> (needs a physical keypress on a TTY; a pipe/agent is refused)
|
|
50
54
|
revoke <id>`;
|
|
@@ -203,16 +207,20 @@ export async function runMandate(rest, io) {
|
|
|
203
207
|
const num = (n) => (flag(rest, n) !== undefined ? parseAmount(flag(rest, n)) : undefined);
|
|
204
208
|
const maxTier = flag(rest, '--max-tier');
|
|
205
209
|
const ttlHours = flag(rest, '--ttl-hours') ? Number(flag(rest, '--ttl-hours')) : undefined;
|
|
210
|
+
const plain = (n) => (flag(rest, n) !== undefined ? Number(flag(rest, n)) : undefined);
|
|
206
211
|
const m = svc.draftMandate({
|
|
207
212
|
totalUsd: num('--total'), perTradeUsd: num('--per-trade'), perCoinUsd: num('--per-coin'),
|
|
208
213
|
dailyLossUsd: num('--daily-loss'), maxTier, ttlMs: ttlHours ? ttlHours * 3600_000 : undefined,
|
|
214
|
+
maxOpenPositions: plain('--max-open'), maxPriceImpactPct: plain('--max-impact'),
|
|
215
|
+
...(has(rest, '--paper') ? { paperOnly: true } : {}),
|
|
209
216
|
});
|
|
210
217
|
if (has(rest, '--json')) {
|
|
211
|
-
out(JSON.stringify({ id: m.id, budget: m.budget, maxTier: m.maxTier }));
|
|
218
|
+
out(JSON.stringify({ id: m.id, budget: m.budget, maxTier: m.maxTier, paperOnly: m.paperOnly === true }));
|
|
212
219
|
return 0;
|
|
213
220
|
}
|
|
214
|
-
out(`drafted ${m.id}`);
|
|
221
|
+
out(`drafted ${m.id}${m.paperOnly ? ' (PAPER-ONLY: agent paper desk only)' : ''}`);
|
|
215
222
|
out(` total ${fmtUsd(m.budget.totalUsd)} · per-trade ${fmtUsd(m.budget.perTradeUsd)} · per-coin ${fmtUsd(m.budget.perCoinUsd)} · daily-loss ${fmtUsd(m.budget.dailyLossUsd)} · maxTier ${m.maxTier}`);
|
|
223
|
+
out(` max open ${m.limits.maxOpenPositions} · max price impact ${m.limits.maxPriceImpactPct}%`);
|
|
216
224
|
out(' DRAFT only — not armed. Arm with: dexterm mandate arm ' + m.id + ' (physical keypress)');
|
|
217
225
|
return 0;
|
|
218
226
|
}
|
|
@@ -228,7 +236,7 @@ export async function runMandate(rest, io) {
|
|
|
228
236
|
return 0;
|
|
229
237
|
}
|
|
230
238
|
for (const r of rows)
|
|
231
|
-
out(`${r.id.padEnd(24)} ${r.status.padEnd(7)} total ${fmtUsd(r.totalUsd)} · reserved ${fmtUsd(r.reservedUsd)} · spent ${fmtUsd(r.spentUsd)} · maxTier ${r.maxTier}`);
|
|
239
|
+
out(`${r.id.padEnd(24)} ${r.status.padEnd(7)} total ${fmtUsd(r.totalUsd)} · reserved ${fmtUsd(r.reservedUsd)} · spent ${fmtUsd(r.spentUsd)} · maxTier ${r.maxTier}${r.paperOnly ? ' · PAPER-ONLY' : ''}`);
|
|
232
240
|
return 0;
|
|
233
241
|
}
|
|
234
242
|
case 'arm': {
|
|
@@ -243,7 +251,9 @@ export async function runMandate(rest, io) {
|
|
|
243
251
|
errOut(`no such mandate ${id}`);
|
|
244
252
|
return 1;
|
|
245
253
|
}
|
|
246
|
-
out(
|
|
254
|
+
out(row.paperOnly
|
|
255
|
+
? `Arm PAPER-ONLY mandate ${id}? This lets the agent paper desk book simulated fills under these caps. No plan, copy trade or signer can use it.`
|
|
256
|
+
: `Arm standing mandate ${id}? This lets armed plans fire at machine speed from the capped fast wallet.`);
|
|
247
257
|
out(` total ${fmtUsd(row.totalUsd)} · per-trade ${fmtUsd(row.perTradeUsd)} · per-coin ${fmtUsd(row.perCoinUsd)} · maxTier ${row.maxTier}`);
|
|
248
258
|
out('Hold any key to confirm (physical keypress). A pipe or agent cannot arm. Waiting 15s…');
|
|
249
259
|
const key = await ttyKeypress(io.stdin, 15_000);
|
|
@@ -14,6 +14,7 @@ import { fmtPrice, fmtNum } from '../../tui/src/index.js';
|
|
|
14
14
|
import { positionRow } from "./live/trade.js";
|
|
15
15
|
import { signedUsd, signedPctOf, fmtUsdFine, sol as fmtSol, signedSol } from "./screens/positions.js";
|
|
16
16
|
import { supplyRatio, positionPriceCells } from "./display.js";
|
|
17
|
+
import { chipSegments } from "./screens/pnl-chip.js";
|
|
17
18
|
/** Map the app's active Theme to the popup protocol's theme-token subset. */
|
|
18
19
|
export function popupThemeFrom(t) {
|
|
19
20
|
return {
|
|
@@ -160,6 +161,12 @@ export function tokenDetailVMFromView(v, intel = null) {
|
|
|
160
161
|
flags: v.flags.map((f) => String(f)), holdersSample: v.holdersSample, updatedAt: v.updatedAt,
|
|
161
162
|
};
|
|
162
163
|
}
|
|
164
|
+
/** task-1473 r2: the chip as the chart page draws it (same segments as the terminal). */
|
|
165
|
+
export function chartChipFrom(m) {
|
|
166
|
+
if (!m)
|
|
167
|
+
return null;
|
|
168
|
+
return { kind: m.kind, symbol: m.symbol, tone: m.pnlPct === null ? 'muted' : m.pnlPct >= 0 ? 'up' : 'down', newPeak: m.newPeak, segments: chipSegments(m) };
|
|
169
|
+
}
|
|
163
170
|
/** "docked right of Terminal" / "screen right (unknown terminal)" — for the TUI toast. */
|
|
164
171
|
export function describeDock(p) {
|
|
165
172
|
if (!p)
|
|
@@ -432,6 +439,14 @@ export class TokenPopups {
|
|
|
432
439
|
case 'chart.scale':
|
|
433
440
|
this.prefs = { ...this.prefs, scale: p.scale };
|
|
434
441
|
return;
|
|
442
|
+
case 'chart.axis': { // task-1472: a display preference the terminal saves; nothing else
|
|
443
|
+
if (p.axis !== 'mcap' && p.axis !== 'price')
|
|
444
|
+
return;
|
|
445
|
+
this.source?.setChartAxis?.(p.axis);
|
|
446
|
+
if (this.lastView)
|
|
447
|
+
this.push(this.lastView);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
435
450
|
case 'chart.painted': {
|
|
436
451
|
const ms = Number(p.ms);
|
|
437
452
|
if (p.kind === 'first' && this.metrics.openedAt !== null && this.metrics.openToFirstPaintMs === null)
|
|
@@ -520,8 +535,9 @@ export class TokenPopups {
|
|
|
520
535
|
prefs: this.prefs,
|
|
521
536
|
now: this.now(),
|
|
522
537
|
image: src.imagePath?.(view) ?? null,
|
|
523
|
-
priceMode: src.priceMode?.() ?? 'price',
|
|
538
|
+
priceMode: src.chartAxis?.() ?? src.priceMode?.() ?? 'price', // task-1472: the chart axis drives the popup
|
|
524
539
|
});
|
|
540
|
+
vm.chip = chartChipFrom(src.pnlChip?.() ?? null); // task-1473 r2
|
|
525
541
|
const key = vm.seriesKey ?? null;
|
|
526
542
|
const n = vm.candles.length;
|
|
527
543
|
const canTail = !full && this.ready && key !== null && key === this.lastKey && this.lastLen > 0 && n >= this.lastLen && n <= this.lastLen + 1;
|
|
@@ -11,8 +11,14 @@ export const SCAN_TICK_MS = 2_000;
|
|
|
11
11
|
export const SCAN_RECOMPUTE_MS = 10_000;
|
|
12
12
|
/** The analyst UI actions a scan session may request: NONE (observe-only). */
|
|
13
13
|
export const SCAN_ACTION_TYPES = Object.freeze([]);
|
|
14
|
+
/** task-1473: append one line to the scan's session log (the analyst panel's brain view shows it). */
|
|
15
|
+
export function scanLog(scan, t, kind, symbol, text) {
|
|
16
|
+
scan.log.push({ t, kind, symbol: cleanText(symbol, 12), text });
|
|
17
|
+
if (scan.log.length > 40)
|
|
18
|
+
scan.log.splice(0, scan.log.length - 40);
|
|
19
|
+
}
|
|
14
20
|
export function makeScanUi(cfg = {}) {
|
|
15
|
-
return { engine: new ScanEngine(cfg), sig: '', lastTickAt: 0, lastComputeAt: 0, session: null, blocked: [], returnTo: null };
|
|
21
|
+
return { engine: new ScanEngine(cfg), sig: '', lastTickAt: 0, lastComputeAt: 0, session: null, blocked: [], returnTo: null, log: [] };
|
|
16
22
|
}
|
|
17
23
|
export const MODE_LABEL = { off: 'OFF', scanning: 'SCANNING', locked: 'LOCKED', paused: 'PAUSED' };
|
|
18
24
|
const SHORT = { vol: 'vol', flow: 'flow', fresh: 'fresh', breakout: 'brk', liq: 'liq', holders: 'hold' };
|