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
|
@@ -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';
|
|
21
|
-
import { rendezvousPath } from '../../mcp/src/index.js';
|
|
20
|
+
import { SpectatorHub, FollowController, SpectatorServer, AgentStory, sanitizeDetail, } from '../../agents/src/index.js';
|
|
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,10 +95,12 @@ 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;
|
|
58
102
|
paused = false;
|
|
103
|
+
lastSnapshot = 0;
|
|
59
104
|
constructor(opts) {
|
|
60
105
|
this.opts = opts;
|
|
61
106
|
this.agentLabel = opts.agentLabel || 'mcp-agent';
|
|
@@ -70,7 +115,8 @@ export class McpFollowSession {
|
|
|
70
115
|
kickAgent: () => { this.disarm(); return 1; },
|
|
71
116
|
};
|
|
72
117
|
this.follow = new FollowController({ controls, activityCap: Math.max(1, opts.activityCap ?? 200) });
|
|
73
|
-
this.
|
|
118
|
+
this.story = new AgentStory(this.agentLabel);
|
|
119
|
+
this.hub.on((ev) => { this.follow.apply(ev); this.story.apply(ev); });
|
|
74
120
|
}
|
|
75
121
|
/** Arm the rendezvous (so a `dexterm mcp` server starts emitting) and begin tailing. Idempotent. */
|
|
76
122
|
async start() {
|
|
@@ -85,10 +131,23 @@ export class McpFollowSession {
|
|
|
85
131
|
}
|
|
86
132
|
}
|
|
87
133
|
if (!this.timer) {
|
|
88
|
-
this.timer = setInterval(() => this.pump(), Math.max(30, this.opts.pollMs ?? 150));
|
|
134
|
+
this.timer = setInterval(() => { this.pump(); this.publish(); }, Math.max(30, this.opts.pollMs ?? 150));
|
|
89
135
|
this.timer.unref?.();
|
|
90
136
|
}
|
|
91
137
|
}
|
|
138
|
+
/** Publish the follow-mode screen snapshot (throttled to ~1/s; only while armed). Never throws. */
|
|
139
|
+
publish(force = false) {
|
|
140
|
+
if (!this.opts.snapshot || !this.armed)
|
|
141
|
+
return;
|
|
142
|
+
const now = (this.opts.now ?? Date.now)();
|
|
143
|
+
if (!force && now - this.lastSnapshot < 1000)
|
|
144
|
+
return;
|
|
145
|
+
this.lastSnapshot = now;
|
|
146
|
+
try {
|
|
147
|
+
writeFollowScreen(this.opts.env, this.opts.snapshot());
|
|
148
|
+
}
|
|
149
|
+
catch { /* fail closed: stale → nav refused */ }
|
|
150
|
+
}
|
|
92
151
|
/** Write (or re-write) the channel + rendezvous so the server emits here. */
|
|
93
152
|
rearm() {
|
|
94
153
|
try {
|
|
@@ -108,14 +167,62 @@ export class McpFollowSession {
|
|
|
108
167
|
// A second follow surface may have replaced the rendezvous after this session started. Never remove
|
|
109
168
|
// another surface's live handle when this one pauses, is killed, or exits.
|
|
110
169
|
const current = JSON.parse(readFileSync(this.rvPath, 'utf8'));
|
|
111
|
-
if (current.token === this.token)
|
|
170
|
+
if (current.token === this.token) {
|
|
112
171
|
rmSync(this.rvPath, { force: true });
|
|
172
|
+
if (this.opts.snapshot)
|
|
173
|
+
clearFollowScreen(this.opts.env);
|
|
174
|
+
}
|
|
113
175
|
}
|
|
114
176
|
catch { /* absent/malformed/another surface owns it */ }
|
|
115
177
|
this.armed = false;
|
|
116
178
|
}
|
|
117
179
|
/** Read and fold any records appended since the last pump. Public so tests can drive it without timers. */
|
|
118
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() {
|
|
119
226
|
let text;
|
|
120
227
|
try {
|
|
121
228
|
const st = statSync(this.channelPath);
|
|
@@ -167,6 +274,7 @@ export class McpFollowSession {
|
|
|
167
274
|
ms: rec.ms ?? null,
|
|
168
275
|
dataAgeSec: rec.dataAgeSec ?? null,
|
|
169
276
|
nav: rec.nav ?? null,
|
|
277
|
+
detail: sanitizeDetail(rec.detail),
|
|
170
278
|
ok: rec.ok ?? true,
|
|
171
279
|
t: typeof rec.t === 'number' ? rec.t : undefined,
|
|
172
280
|
});
|
|
@@ -182,6 +290,15 @@ export class McpFollowSession {
|
|
|
182
290
|
}
|
|
183
291
|
catch { /* ignore */ }
|
|
184
292
|
}
|
|
293
|
+
// task-1443: every agent PAPER write (fill, close, size, or a refusal) is toasted in the terminal, whatever the
|
|
294
|
+
// follow state, so the watcher always sees what the agent did to the simulated ledger.
|
|
295
|
+
if (typeof ev.tool === 'string' && /^paper_(open_ticket|size_ticket|confirm|close)$/.test(ev.tool)) {
|
|
296
|
+
const what = ev.ok === false ? `PAPER ${ev.tool.slice(6).replace('_', ' ')} refused · ${String(ev.summary ?? '').replace(/^error: /, '').slice(0, 70)}` : String(ev.summary ?? ev.tool);
|
|
297
|
+
try {
|
|
298
|
+
this.opts.onStatus?.(`agent ${this.currentAgent}: ${what}`);
|
|
299
|
+
}
|
|
300
|
+
catch { /* ignore */ }
|
|
301
|
+
}
|
|
185
302
|
try {
|
|
186
303
|
this.opts.onEvent?.();
|
|
187
304
|
}
|
|
@@ -195,10 +312,18 @@ export class McpFollowSession {
|
|
|
195
312
|
return !st || st.status === 'active';
|
|
196
313
|
}
|
|
197
314
|
// ----- the owner's controls. Each returns a short line to toast. -----
|
|
198
|
-
pause() { const l = this.follow.pause(this.currentAgent); this.paused = l.startsWith('paused '); this.opts.onStatus?.(l); return l; }
|
|
199
|
-
takeOver() { const l = this.follow.takeOver(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
|
|
200
|
-
resume() { const l = this.follow.resume(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
|
|
201
|
-
kill() { const l = this.follow.kill(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
|
|
315
|
+
pause() { const l = this.follow.pause(this.currentAgent); this.paused = l.startsWith('paused '); this.control('paused'); this.opts.onStatus?.(l); return l; }
|
|
316
|
+
takeOver() { const l = this.follow.takeOver(this.currentAgent); this.paused = false; this.control('takeover'); this.opts.onStatus?.(l); return l; }
|
|
317
|
+
resume() { const l = this.follow.resume(this.currentAgent); this.paused = false; this.control('running'); this.opts.onStatus?.(l); return l; }
|
|
318
|
+
kill() { const l = this.follow.kill(this.currentAgent); this.paused = false; this.control('stopped'); this.opts.onStatus?.(l); return l; }
|
|
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 */ }
|
|
326
|
+
}
|
|
202
327
|
/** Any physical keypress while the agent is driving: the human takes over (no fight over the screen). */
|
|
203
328
|
userKey() { if (this.follow.controlState === 'agent')
|
|
204
329
|
this.takeOver(); }
|
|
@@ -207,6 +332,8 @@ export class McpFollowSession {
|
|
|
207
332
|
get screenModel() { return this.follow.screenModel; }
|
|
208
333
|
get streamPort() { return this.stream?.port ?? 0; }
|
|
209
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); }
|
|
210
337
|
/** Tear down: stop the timer, remove the rendezvous, close the stream. Safe to call more than once. */
|
|
211
338
|
async stop() {
|
|
212
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;
|
|
@@ -46,6 +46,8 @@ export function buildLiveEngine(env, opts) {
|
|
|
46
46
|
let newpairs = null;
|
|
47
47
|
if (!opts.noNewpairs) {
|
|
48
48
|
newpairs = new NewPairsLive(feed.dl, {});
|
|
49
|
+
// 0.1.5 dev history: keep the creator of every new pair in the recorder's creator index.
|
|
50
|
+
newpairs.onCreator = (mint, creator, source, createdAt) => { recorder.recordCreator(mint, creator, source, undefined, createdAt); };
|
|
49
51
|
const client = new PumpPortalClient({ health: feed.dl.health, clock: feed.dl.clock, onEvent: (ev) => newpairs.handleEvent(ev), onStatus: () => { } });
|
|
50
52
|
newpairs.attach(client, 'live');
|
|
51
53
|
newpairs.start();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { devBookOf } from "./live/dev-history.js";
|
|
1
2
|
import { RulesEngine, ruleToJson, ruleToText, slug, applyAndJournal, describeActions, } from '../../custom/src/index.js';
|
|
2
3
|
import { alertToDelivery, proposalToDelivery } from "./alerts/format.js";
|
|
3
4
|
export const RULE_INPUT_MAX = 400;
|
|
@@ -66,11 +67,14 @@ export function ruleRowsOf(state) {
|
|
|
66
67
|
return c ? c.pct : null;
|
|
67
68
|
};
|
|
68
69
|
const openMint = state.live?.token?.view.mint ?? null;
|
|
70
|
+
// 0.1.5 dev history: the creator's recorded rugs as the `devrugs` field (local recorder only; null = unknown).
|
|
71
|
+
const book = devBookOf(state);
|
|
72
|
+
const devRugsOf = (mint) => book ? book.rugsFor(mint) : null;
|
|
69
73
|
for (const r of state.screener.rows) {
|
|
70
74
|
if (!r.mint || r.skeleton || seen.has(r.mint))
|
|
71
75
|
continue;
|
|
72
76
|
seen.add(r.mint);
|
|
73
|
-
const row = Object.assign(Object.create(null), r, { cluster: clusterOf(r.mint), larp: larpGrade(state.larpByMint?.get(r.mint)?.verdict) });
|
|
77
|
+
const row = Object.assign(Object.create(null), r, { cluster: clusterOf(r.mint), larp: larpGrade(state.larpByMint?.get(r.mint)?.verdict), devRugs: devRugsOf(r.mint) });
|
|
74
78
|
if (r.mint === openMint)
|
|
75
79
|
overlaySignalFields(row, state, r.mint);
|
|
76
80
|
out.push(row);
|
|
@@ -81,12 +85,31 @@ export function ruleRowsOf(state) {
|
|
|
81
85
|
const row = Object.assign(Object.create(null), {
|
|
82
86
|
mint: v.mint, symbol: v.symbol, price: v.price, mcap: v.mcap, fdv: v.fdv, liq: v.liq, vol24: v.vol24,
|
|
83
87
|
holders: v.holders, org: v.org, top10: v.top10, dev: v.dev, lpLocked: v.lpLocked,
|
|
84
|
-
cluster: clusterOf(v.mint), larp: larpGrade(state.larpByMint?.get(v.mint)?.verdict),
|
|
88
|
+
cluster: clusterOf(v.mint), larp: larpGrade(state.larpByMint?.get(v.mint)?.verdict), devRugs: devRugsOf(v.mint),
|
|
85
89
|
ageMin: v.ageMin, chg: v.chg, launchpad: v.launchpad, flags: v.flags, verified: v.verified, sus: v.sus,
|
|
86
90
|
});
|
|
87
91
|
overlaySignalFields(row, state, v.mint);
|
|
88
92
|
out.push(row);
|
|
89
93
|
}
|
|
94
|
+
// 0.1.5 dev history: a brand-new pair (PumpPortal / RugCheck) whose creator has a labelled record becomes a rule
|
|
95
|
+
// row too, so `devrugs >= N scope newpairs` can alert on a new coin from a wallet that rugged before. Only pairs
|
|
96
|
+
// with a known record are added (no flood of unknowns); every other field is what the new-pairs feed knows.
|
|
97
|
+
const np = state.live?.newpairs;
|
|
98
|
+
if (book && np) {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
for (const p of np.rows.values()) {
|
|
101
|
+
if (seen.has(p.mint) || !p.creator)
|
|
102
|
+
continue;
|
|
103
|
+
const devRugs = devRugsOf(p.mint);
|
|
104
|
+
if (devRugs === null)
|
|
105
|
+
continue;
|
|
106
|
+
seen.add(p.mint);
|
|
107
|
+
out.push(Object.assign(Object.create(null), {
|
|
108
|
+
mint: p.mint, symbol: p.symbol, mcap: p.mcap, liq: p.liq, holders: p.holders, launchpad: p.launchpad,
|
|
109
|
+
ageMin: p.createdAt ? Math.max(0, (now - p.createdAt) / 60_000) : null, bonding: p.bonding, devRugs,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
90
113
|
return out;
|
|
91
114
|
}
|
|
92
115
|
/** Overlay the live-signals engine's latest values (non-null wins) onto a rule row (task-1404). */
|
|
@@ -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' };
|