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.
Files changed (131) hide show
  1. package/dist/packages/agents/src/index.js +2 -0
  2. package/dist/packages/agents/src/spectator.js +54 -0
  3. package/dist/packages/agents/src/story.js +523 -0
  4. package/dist/packages/agents/src/tiers.js +1 -0
  5. package/dist/packages/agents/src/tools.js +1 -0
  6. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
  7. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
  8. package/dist/packages/cli/src/agent-cmd.js +6 -4
  9. package/dist/packages/cli/src/agent-paper.js +60 -0
  10. package/dist/packages/cli/src/agent-spectator.js +17 -1
  11. package/dist/packages/cli/src/analyst/backend.js +206 -0
  12. package/dist/packages/cli/src/analyst/codex.js +301 -0
  13. package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
  14. package/dist/packages/cli/src/analyst/session.js +21 -3
  15. package/dist/packages/cli/src/app-help-gen.js +21 -12
  16. package/dist/packages/cli/src/backtest-cmd.js +13 -2
  17. package/dist/packages/cli/src/display.js +55 -9
  18. package/dist/packages/cli/src/doctor.js +58 -2
  19. package/dist/packages/cli/src/live/dev-history.js +115 -0
  20. package/dist/packages/cli/src/live/index.js +5 -1
  21. package/dist/packages/cli/src/live/newpairs.js +12 -0
  22. package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
  23. package/dist/packages/cli/src/live/screens/token-live.js +51 -11
  24. package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
  25. package/dist/packages/cli/src/live/token.js +1 -0
  26. package/dist/packages/cli/src/live/trade.js +22 -0
  27. package/dist/packages/cli/src/main.js +267 -19
  28. package/dist/packages/cli/src/mcp-follow.js +137 -10
  29. package/dist/packages/cli/src/plan-cmd.js +15 -5
  30. package/dist/packages/cli/src/popup.js +17 -1
  31. package/dist/packages/cli/src/record-cmd.js +2 -0
  32. package/dist/packages/cli/src/rules.js +25 -2
  33. package/dist/packages/cli/src/scan/ui.js +7 -1
  34. package/dist/packages/cli/src/screens/agent-hud.js +741 -0
  35. package/dist/packages/cli/src/screens/analyst-session.js +9 -3
  36. package/dist/packages/cli/src/screens/analyst.js +4 -3
  37. package/dist/packages/cli/src/screens/brain.js +608 -0
  38. package/dist/packages/cli/src/screens/newpairs.js +1 -1
  39. package/dist/packages/cli/src/screens/pnl-chip.js +143 -0
  40. package/dist/packages/cli/src/screens/screener.js +1 -0
  41. package/dist/packages/cli/src/screens/token.js +16 -7
  42. package/dist/packages/cli/src/screens/trending.js +1 -1
  43. package/dist/packages/cli/src/state.js +69 -5
  44. package/dist/packages/cli/src/version.js +1 -1
  45. package/dist/packages/custom/src/rules.js +2 -2
  46. package/dist/packages/custom/src/schema.js +13 -3
  47. package/dist/packages/data/src/fixtures.js +28 -6
  48. package/dist/packages/data/src/index.js +5 -0
  49. package/dist/packages/data/src/larp/score.js +220 -0
  50. package/dist/packages/data/src/larp/x-account.js +316 -0
  51. package/dist/packages/data/src/sources/jupiter.js +1 -0
  52. package/dist/packages/data/src/trade/agent/paper-desk.js +401 -0
  53. package/dist/packages/data/src/trade/agent/rendezvous.js +161 -0
  54. package/dist/packages/data/src/trade/plans/engine.js +7 -0
  55. package/dist/packages/data/src/trade/plans/mandate.js +28 -15
  56. package/dist/packages/data/src/trade/plans/service.js +1 -1
  57. package/dist/packages/mcp/assets/kb/app-help/agent-paper-trade.md +38 -0
  58. package/dist/packages/mcp/assets/kb/app-help/ask-the-analyst.md +1 -1
  59. package/dist/packages/mcp/assets/kb/app-help/connect-an-agent.md +1 -1
  60. package/dist/packages/mcp/assets/kb/app-help/dev-history.md +32 -0
  61. package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
  62. package/dist/packages/mcp/assets/kb/app-help/paper-trade.md +1 -1
  63. package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
  64. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +4 -1
  65. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +4 -0
  66. package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
  67. package/dist/packages/mcp/assets/kb/app-help/use-codex-analyst.md +31 -0
  68. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +51 -0
  69. package/dist/packages/mcp/assets/kb/docs/docs-agents-directory-metadata-prepared-not-submitted.md +36 -0
  70. package/dist/packages/mcp/assets/kb/docs/docs-agents-install-in-one-command.md +2 -0
  71. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  72. package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
  73. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-codex-instead-of-claude.md +35 -0
  74. package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos-or-linux-npm.md +1 -1
  75. package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-expectations.md +1 -1
  76. package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-powershell-experimental.md +1 -1
  77. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
  78. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +2 -0
  79. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +3 -3
  80. package/dist/packages/mcp/assets/kb/docs/docs-configuration-rules-engine.md +1 -0
  81. package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-analyst-s-launch.md +2 -0
  82. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +13 -3
  83. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  84. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
  85. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-supported-platforms.md +2 -2
  86. package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -2
  87. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
  88. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
  89. package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
  90. package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
  91. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +32 -0
  92. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +1 -9
  93. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +41 -0
  94. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +1 -1
  95. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
  96. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +1 -0
  97. package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
  98. package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +47 -0
  99. package/dist/packages/mcp/assets/kb/docs/docs-screens-how-well-it-predicts-measured-held-out.md +46 -0
  100. package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
  101. package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
  102. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
  103. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
  104. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
  105. package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
  106. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +2 -0
  107. package/dist/packages/mcp/src/index.js +5 -2
  108. package/dist/packages/mcp/src/paper-agent.js +93 -0
  109. package/dist/packages/mcp/src/server.js +354 -19
  110. package/dist/packages/mcp/src/spectator-sink.js +158 -4
  111. package/dist/packages/mcp/src/user-content.js +1 -0
  112. package/dist/packages/packs/src/index.js +1 -0
  113. package/dist/packages/packs/src/larp-score-backtest.js +119 -0
  114. package/dist/packages/popup/src/pages/chart-lib.js +36 -15
  115. package/dist/packages/popup/src/pages/chart.css +9 -0
  116. package/dist/packages/popup/src/pages/chart.html +4 -1
  117. package/dist/packages/popup/src/pages/chart.js +34 -5
  118. package/dist/packages/popup/src/pages/spectator.css +45 -0
  119. package/dist/packages/popup/src/pages/spectator.html +1 -0
  120. package/dist/packages/popup/src/pages/spectator.js +69 -0
  121. package/dist/packages/popup/src/protocol.js +2 -0
  122. package/dist/packages/popup/src/server.js +5 -1
  123. package/dist/packages/recorder/src/dev-history.js +133 -0
  124. package/dist/packages/recorder/src/index.js +1 -0
  125. package/dist/packages/recorder/src/recorder.js +23 -0
  126. package/dist/packages/recorder/src/schema.js +11 -0
  127. package/dist/packages/tui/src/canvas.js +7 -0
  128. package/dist/packages/tui/src/fmt.js +53 -0
  129. package/dist/packages/tui/src/keymap.js +4 -0
  130. package/dist/packages/tui/src/widgets.js +9 -4
  131. 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.hub.on((ev) => this.follow.apply(ev));
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] [--json]
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(`Arm standing mandate ${id}? This lets armed plans fire at machine speed from the capped fast wallet.`);
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' };