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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// dexterm — the persistent POSITION PnL chip (task-1473). While an auto mode runs and a position is open, one compact
|
|
2
|
+
// readout is on every terminal screen and on the chart popup: coin, mark PnL % with sign (and $ when the size is
|
|
3
|
+
// known), entry → mark, peak, drawdown from the peak, time held and the take-profit. Sources (state.ts pnlChipFor):
|
|
4
|
+
// · following an agent (task-1466 HUD) that holds a PAPER ticket — the folded story, TP = what the agent said;
|
|
5
|
+
// · AI scan mode with the user's OWN paper or live position (OwnPnlTracker; session peak, TP = the ledger target).
|
|
6
|
+
// Where it draws: the HUD dock's signal row when the dock is up (≥ HUD_MIN_H rows while following), else the band
|
|
7
|
+
// under the header (row 1, right-aligned: short terminals, scan mode), and the chart popup's chip strip (ChartVM.chip).
|
|
8
|
+
// It updates on every mark tick, flashes on a new peak (a ★ glyph says the same under calm mode / NO_COLOR) and drops
|
|
9
|
+
// fields from the right as the width shrinks. Display only: it has no input and reads only folded / ledger state.
|
|
10
|
+
import { mix } from '../../../tui/src/index.js';
|
|
11
|
+
import { fmtPx } from '../../../agents/src/index.js';
|
|
12
|
+
import { planFromThoughts, fmtDur } from "./brain.js";
|
|
13
|
+
export const PEAK_FLASH_MS = 1200;
|
|
14
|
+
/** The chip for the followed agent's open PAPER position, or null when nothing is held. */
|
|
15
|
+
export function pnlChipVM(v, now) {
|
|
16
|
+
const a = v.arc;
|
|
17
|
+
if (!a || a.stage !== 'open')
|
|
18
|
+
return null;
|
|
19
|
+
const dd = a.pnlPct !== null && a.peakPct !== null ? (1 + a.pnlPct) / (1 + a.peakPct) - 1 : null;
|
|
20
|
+
return {
|
|
21
|
+
kind: 'PAPER',
|
|
22
|
+
symbol: a.symbol,
|
|
23
|
+
pnlPct: a.pnlPct,
|
|
24
|
+
pnlUsd: a.pnlPct !== null && a.sizeUsd !== null ? a.sizeUsd * a.pnlPct : null,
|
|
25
|
+
entry: a.entryUsd !== null ? `$${fmtPx(a.entryUsd)}` : '—',
|
|
26
|
+
mark: a.mark ? `$${fmtPx(a.mark.priceUsd)}` : '—',
|
|
27
|
+
peakPct: a.peakPct,
|
|
28
|
+
ddPct: dd,
|
|
29
|
+
heldMs: a.filledAt !== null ? now - a.filledAt : null,
|
|
30
|
+
tp: planFromThoughts(v.thoughts, a.openedAt).tp,
|
|
31
|
+
newPeak: a.peakAt !== null && now - a.peakAt < PEAK_FLASH_MS && a.peakPct !== null && a.peakPct > 0,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The user's own position chip in AI scan mode. The ledgers keep no peak, so the peak is this session's highest
|
|
36
|
+
* mark (reset when the held coin or ledger changes, or the position goes flat). Display only.
|
|
37
|
+
*/
|
|
38
|
+
export class OwnPnlTracker {
|
|
39
|
+
cur = null;
|
|
40
|
+
update(s, now) {
|
|
41
|
+
if (!s) {
|
|
42
|
+
this.cur = null;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const seed = !this.cur || this.cur.s.mint !== s.mint || this.cur.s.kind !== s.kind;
|
|
46
|
+
if (seed)
|
|
47
|
+
this.cur = { s, peakPct: s.pnlPct, peakAt: null }; // the first mark is not a "new peak" flash
|
|
48
|
+
const c = this.cur;
|
|
49
|
+
c.s = s;
|
|
50
|
+
if (!seed && s.pnlPct !== null && (c.peakPct === null || s.pnlPct > c.peakPct + 1e-9)) {
|
|
51
|
+
c.peakPct = s.pnlPct;
|
|
52
|
+
c.peakAt = now;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
get mint() { return this.cur?.s.mint ?? null; }
|
|
56
|
+
vm(now) {
|
|
57
|
+
const c = this.cur;
|
|
58
|
+
if (!c)
|
|
59
|
+
return null;
|
|
60
|
+
const s = c.s;
|
|
61
|
+
const dd = s.pnlPct !== null && c.peakPct !== null ? (1 + s.pnlPct) / (1 + c.peakPct) - 1 : null;
|
|
62
|
+
return {
|
|
63
|
+
kind: s.kind === 'live' ? 'LIVE' : 'PAPER',
|
|
64
|
+
symbol: s.symbol,
|
|
65
|
+
pnlPct: s.pnlPct,
|
|
66
|
+
pnlUsd: s.pnlUsd,
|
|
67
|
+
entry: s.entryUsd !== null ? `$${fmtPx(s.entryUsd)}` : '—',
|
|
68
|
+
mark: s.markUsd !== null ? `$${fmtPx(s.markUsd)}` : '—',
|
|
69
|
+
peakPct: c.peakPct,
|
|
70
|
+
ddPct: dd,
|
|
71
|
+
heldMs: s.openedAt !== null ? Math.max(0, now - s.openedAt) : null,
|
|
72
|
+
tp: s.targetUsd !== null ? `$${fmtPx(s.targetUsd)}` : null,
|
|
73
|
+
tpNone: 'not set',
|
|
74
|
+
newPeak: c.peakAt !== null && now - c.peakAt < PEAK_FLASH_MS && c.peakPct !== null && c.peakPct > 0,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const pct = (r) => `${r >= 0 ? '+' : '−'}${Math.abs(r * 100).toFixed(1)}%`;
|
|
79
|
+
const usd = (n) => `${n >= 0 ? '+' : '−'}$${Math.abs(n).toFixed(2)}`;
|
|
80
|
+
/** The chip's segments in priority order (the first ones survive the narrowest widths). */
|
|
81
|
+
export function chipSegments(m) {
|
|
82
|
+
const segs = [];
|
|
83
|
+
const dir = m.pnlPct === null ? '·' : m.pnlPct >= 0 ? '▲' : '▼';
|
|
84
|
+
segs.push({ id: 'head', text: `${m.kind} $${m.symbol}` });
|
|
85
|
+
segs.push({ id: 'pnl', text: `${dir} ${m.pnlPct === null ? '—' : pct(m.pnlPct)}${m.pnlUsd !== null ? ` ${usd(m.pnlUsd)}` : ''}` });
|
|
86
|
+
if (m.peakPct !== null)
|
|
87
|
+
segs.push({ id: 'peak', text: `peak ${pct(m.peakPct)}${m.newPeak ? ' ★' : ''}` });
|
|
88
|
+
segs.push({ id: 'px', text: `${m.entry} → ${m.mark}` });
|
|
89
|
+
if (m.heldMs !== null)
|
|
90
|
+
segs.push({ id: 'held', text: `held ${fmtDur(m.heldMs)}` });
|
|
91
|
+
if (m.ddPct !== null && m.ddPct < -0.0005)
|
|
92
|
+
segs.push({ id: 'dd', text: `dd ${pct(m.ddPct)}` });
|
|
93
|
+
segs.push({ id: 'tp', text: m.tp ? `TP ${m.tp}` : `TP ${m.tpNone ?? 'agent-managed'}` });
|
|
94
|
+
return segs;
|
|
95
|
+
}
|
|
96
|
+
const SEP = ' │ ';
|
|
97
|
+
/** The chip text that fits in `maxW` (segments drop from the end). Pure, for tests. */
|
|
98
|
+
export function chipText(m, maxW) {
|
|
99
|
+
const segs = chipSegments(m);
|
|
100
|
+
let n = segs.length;
|
|
101
|
+
const join = (k) => ` ${segs.slice(0, k).map((s) => s.text).join(SEP)} `;
|
|
102
|
+
while (n > 1 && join(n).length > maxW)
|
|
103
|
+
n--;
|
|
104
|
+
const s = join(n);
|
|
105
|
+
return s.length <= maxW ? s : '';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Draw the chip right-aligned to `right` (exclusive) on row `y`, no wider than `maxW`. Returns the x it starts at
|
|
109
|
+
* (or `right` when nothing fits).
|
|
110
|
+
*/
|
|
111
|
+
export function renderPnlChip(c, right, y, maxW, m, o) {
|
|
112
|
+
const t = c.theme;
|
|
113
|
+
const text = chipText(m, maxW);
|
|
114
|
+
if (!text)
|
|
115
|
+
return right;
|
|
116
|
+
const x0 = right - text.length;
|
|
117
|
+
const bg = mix(t.bg, t.magenta, 0.22);
|
|
118
|
+
const col = m.pnlPct === null ? t.muted : m.pnlPct >= 0 ? t.up : t.down;
|
|
119
|
+
c.text(x0, y, text, { fg: t.text, bg });
|
|
120
|
+
// re-colour the segments: head magenta, PnL red/green bold (flash on a new peak), peak green, dd warn
|
|
121
|
+
let x = x0 + 1;
|
|
122
|
+
for (const s of chipSegments(m)) {
|
|
123
|
+
if (x + s.text.length > right - 1)
|
|
124
|
+
break;
|
|
125
|
+
if (s.id === 'head')
|
|
126
|
+
c.text(x, y, s.text, { fg: t.magenta, bg, b: true });
|
|
127
|
+
else if (s.id === 'pnl') {
|
|
128
|
+
const flash = m.newPeak && !o.reduced;
|
|
129
|
+
c.text(x, y, s.text, flash ? { fg: t.bg, bg: t.up, b: true } : { fg: col, bg, b: true });
|
|
130
|
+
}
|
|
131
|
+
else if (s.id === 'peak')
|
|
132
|
+
c.text(x, y, s.text, { fg: m.peakPct !== null && m.peakPct >= 0 ? t.up : t.down, bg, b: m.newPeak });
|
|
133
|
+
else if (s.id === 'dd')
|
|
134
|
+
c.text(x, y, s.text, { fg: t.warn, bg });
|
|
135
|
+
else
|
|
136
|
+
c.text(x, y, s.text, { fg: t.muted, bg });
|
|
137
|
+
x += s.text.length;
|
|
138
|
+
if (x + SEP.length < right)
|
|
139
|
+
c.text(x, y, SEP, { fg: mix(bg, t.border, 0.8), bg });
|
|
140
|
+
x += SEP.length;
|
|
141
|
+
}
|
|
142
|
+
return x0;
|
|
143
|
+
}
|
|
@@ -195,6 +195,7 @@ export function renderScreener(c, state) {
|
|
|
195
195
|
: null;
|
|
196
196
|
table(c, box.x + 1, box.y, box.w - 2, box.h, {
|
|
197
197
|
columns: screenerColumns(c, bp, sc.gates, state.larpByMint ?? null, icon, mode), rows, selected: sel, scroll: Math.max(0, scroll), sortKey: sc.sort.key, sortDir: sc.sort.dir, bg: box.bg,
|
|
198
|
+
rowKey: (r) => r.mint, // task-1466: the agent HUD finds the rows it scanned
|
|
198
199
|
});
|
|
199
200
|
if (rows.length === 0)
|
|
200
201
|
emptyState(c, box, sc);
|
|
@@ -8,8 +8,9 @@ import { renderTokenLive } from "../live/screens/token-live.js";
|
|
|
8
8
|
import { aiTranscript } from "../fixtures.js";
|
|
9
9
|
import { renderAnalystSession } from "./analyst-session.js";
|
|
10
10
|
import { renderSocialPanel } from "./social-panel.js";
|
|
11
|
+
import { brainFor, factsFromFixture, renderBrainPanel } from "./brain.js";
|
|
11
12
|
import { layoutFor } from "../custom.js";
|
|
12
|
-
import { chartMode, priceModeOf } from "../display.js";
|
|
13
|
+
import { chartMode, chartTitle, chartAxisOf, priceModeOf } from "../display.js";
|
|
13
14
|
export function renderToken(c, state) {
|
|
14
15
|
if (state.live?.token && state.screener.source !== 'fixtures')
|
|
15
16
|
return renderTokenLive(c, state, state.live.token.view); // task-1298: live data path
|
|
@@ -48,13 +49,13 @@ export function renderToken(c, state) {
|
|
|
48
49
|
// price line: pump glow = gold bg band, dump = red band (the rain ramp of the moment)
|
|
49
50
|
const priceBg = pumping ? mix(t.bg, t.rainPump[2], 0.3) : dumping ? mix(t.bg, t.rainDump[2], 0.3) : t.bg;
|
|
50
51
|
// task-1366: mcap mode leads with the market cap and keeps the price beside it (fixture tokens always carry a supply)
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
52
|
+
const pm = chartMode(priceModeOf(state), tk.price, tk.mcap);
|
|
53
|
+
if (pm.kind === 'mcap') {
|
|
53
54
|
x = c.text(1, y, fmtMcap(tk.mcap), { fg: pumping ? t.rainPump[3] : t.text, bg: priceBg, b: true });
|
|
54
55
|
x = c.text(x, y, ` (${fmtPrice(tk.price)})`, { fg: t.dim, bg: priceBg });
|
|
55
56
|
}
|
|
56
57
|
else
|
|
57
|
-
x = c.text(1, y, fmtPrice(tk.price), { fg:
|
|
58
|
+
x = c.text(1, y, fmtPrice(tk.price), { fg: pm.label ? t.warn : pumping ? t.rainPump[3] : t.text, bg: priceBg, b: true });
|
|
58
59
|
const deltas = [['5m', tk.chg.m5], ['1h', tk.chg.h1], ['6h', tk.chg.h6], ['24h', tk.chg.h24]];
|
|
59
60
|
x = c.text(x, y, ' ', { bg: t.bg });
|
|
60
61
|
for (const [k, vv] of deltas) {
|
|
@@ -70,7 +71,9 @@ export function renderToken(c, state) {
|
|
|
70
71
|
y += 1;
|
|
71
72
|
// ---- chart
|
|
72
73
|
const chartH = Math.max(10, Math.floor((bottom - y) * lay.chartFrac));
|
|
73
|
-
|
|
74
|
+
// task-1472: the chart Y axis is its own setting (default MARKET CAP, `A` toggles); the header above keeps `$`
|
|
75
|
+
const cm = chartMode(chartAxisOf(state), tk.price, tk.mcap);
|
|
76
|
+
const cb = c.box(0, y, mainW, chartH, { title: chartTitle(tk.symbol, '1h', cm, 'SOL'), caption: 'ohlcv: geckoterminal' });
|
|
74
77
|
const tfx = cb.x + cb.w - 2;
|
|
75
78
|
const tfs = ['1m', '5m', '15m', '1h', '4h', '1d'];
|
|
76
79
|
let tx = tfx - tfs.reduce((s, f) => s + f.length + 1, 0) - 24;
|
|
@@ -171,8 +174,14 @@ export function renderToken(c, state) {
|
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
|
-
if (showAi)
|
|
175
|
-
|
|
177
|
+
if (showAi) {
|
|
178
|
+
// task-1473: in an auto mode (following an agent / AI scan) the panel is the BRAIN VIEW; otherwise the analyst
|
|
179
|
+
const brain = brainFor(state, factsFromFixture(tk, state.larpByMint?.get(tk.mint)?.verdict ?? null), isCalm(v), state.live?.scanMeterLabel?.() ?? '');
|
|
180
|
+
if (brain)
|
|
181
|
+
renderBrainPanel(c, mainW, top, aiW, bottom - top, brain.model, brain.opts);
|
|
182
|
+
else
|
|
183
|
+
analystPanel(c, mainW, top, aiW, bottom - top, tk, state);
|
|
184
|
+
}
|
|
176
185
|
footer(c, state.tokenSocial
|
|
177
186
|
? [['esc', 'back'], ['s', 'on-chain'], ['f', 'filter'], ['x', state.socialShowSpam ? 'hide spam' : 'show hidden'], ['K', 'KOL lists'], ['a', showAi ? 'hide analyst' : 'analyst'], ['w', 'watch'], ['q', 'quit']]
|
|
178
187
|
: [['esc', 'back'], ['g', 'go to'], ['a', showAi ? 'hide analyst' : 'analyst'], ['h/t/r', 'holders/trades/traders'], ['w', 'watch'], ['b', 'alert'], ['1-6', 'timeframe'], ['q', 'quit']], state.theme.name, isCalm(v));
|
|
@@ -70,7 +70,7 @@ export function renderTrending(c, state) {
|
|
|
70
70
|
if (rows.length === 0)
|
|
71
71
|
c.textC(box.x, box.y + 2, box.w, all.length ? 'no rows match the filter — f cycles filters, n clears the narrative' : 'nothing trending yet — the keyless venues refresh every 60 s', { fg: t.muted, bg, i: true });
|
|
72
72
|
else
|
|
73
|
-
table(c, box.x + 1, box.y, box.w - 2, box.h, { columns: cols, rows, selected: sel, scroll, bg });
|
|
73
|
+
table(c, box.x + 1, box.y, box.w - 2, box.h, { columns: cols, rows, selected: sel, scroll, bg, rowKey: (r) => r.mint });
|
|
74
74
|
// sub-panels
|
|
75
75
|
const sy = top + boardH;
|
|
76
76
|
const news = feed.newsItems(12);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// dexterm — app state consumed by the screens. Screens are pure render(canvas, state) functions;
|
|
2
2
|
// renderFrame() draws the screen, the toast, then runs the effects post-pass (VISUAL-V2 §3).
|
|
3
|
-
import { Canvas, THEMES, applyFx, toast, makeVfx, setSources, marketActivity, candleActivity, isCalm, } from '../../tui/src/index.js';
|
|
3
|
+
import { Canvas, THEMES, applyFx, toast, makeVfx, setSources, marketActivity, candleActivity, isCalm, breakpoint, } from '../../tui/src/index.js';
|
|
4
4
|
import { makeFixtures } from "./fixtures.js";
|
|
5
5
|
import { fromFixture } from "./screener/rows.js";
|
|
6
6
|
import { makeScreenerState, screenerView } from "./screener/view.js";
|
|
7
7
|
import { renderScreener } from "./screens/screener.js";
|
|
8
8
|
import { renderToken } from "./screens/token.js";
|
|
9
|
+
import { agentBrainActive } from "./screens/brain.js";
|
|
10
|
+
import { pnlChipVM, renderPnlChip } from "./screens/pnl-chip.js";
|
|
9
11
|
import { renderNewPairs } from "./screens/newpairs.js";
|
|
10
12
|
import { renderWatchlist } from "./screens/watchlist.js";
|
|
11
13
|
import { renderAnalyst } from "./screens/analyst.js";
|
|
@@ -28,7 +30,9 @@ import { openTicket } from "./trade.js";
|
|
|
28
30
|
import { computeFill } from '../../data/src/index.js';
|
|
29
31
|
import { renderScanTicker, scanIndicator } from "./scan/ui.js";
|
|
30
32
|
import { renderVoiceHud } from "./voice-hud.js";
|
|
33
|
+
import { composeHud, DOCK_ROWS, HUD_MIN_H } from "./screens/agent-hud.js";
|
|
31
34
|
import { workspaceBadge } from "./custom.js";
|
|
35
|
+
import { chartAxisOf, supplyRatio } from "./display.js";
|
|
32
36
|
/** Map key for the Analyst-tab's own conversation (not tied to a single token). */
|
|
33
37
|
export const ANALYST_TAB_KEY = '__analyst_tab__';
|
|
34
38
|
/**
|
|
@@ -426,8 +430,54 @@ export function refreshVfx(state) {
|
|
|
426
430
|
state.vfx.activity = act.activity;
|
|
427
431
|
state.vfx.mood = act.mood;
|
|
428
432
|
}
|
|
429
|
-
/**
|
|
433
|
+
/**
|
|
434
|
+
* Render one frame of the current screen into a fresh canvas: screen → toast → effects. task-1466: while the
|
|
435
|
+
* terminal follows an agent, the screen renders DOCK_ROWS shorter and the agent HUD's dock takes the bottom rows
|
|
436
|
+
* (so no data is hidden), with its overlays (row glow, cursor, cards, flashes, the kill cut) over the screen.
|
|
437
|
+
*/
|
|
430
438
|
export function renderFrame(state, w, h) {
|
|
439
|
+
const hud = state.agentHud;
|
|
440
|
+
if (!hud || state.screen === 'boot' || h < HUD_MIN_H)
|
|
441
|
+
return renderScreenFrame(state, w, h, true);
|
|
442
|
+
const now = hud.clock();
|
|
443
|
+
const screen = renderScreenFrame(state, w, h - DOCK_ROWS, false);
|
|
444
|
+
const view = hud.story.view(now);
|
|
445
|
+
// task-1473: on the token screen the analyst panel is the agent's brain view (same rule the screen used)
|
|
446
|
+
const brain = state.screen === 'token' && state.aiOpen !== false && breakpoint(w) !== 'compact' && !state.aiFocused && agentBrainActive(view, now);
|
|
447
|
+
return composeHud(new Canvas(w, h, state.theme), screen, view, {
|
|
448
|
+
now, reduced: !!hud.reduced || isCalm(state.vfx), depth: state.colorDepth, dataLabel: hud.dataLabel ?? null, screen: state.screen, brain,
|
|
449
|
+
chip: pnlChipFor(state, now, view),
|
|
450
|
+
mcapScale: chartAxisOf(state) === 'mcap' ? (mint) => hudMcapScale(state, mint) : undefined, // task-1472
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/** task-1472: the supply ratio for the HUD's position card: the open token's (live, else fixture), else its screener row. */
|
|
454
|
+
function hudMcapScale(state, mint) {
|
|
455
|
+
const v = state.live?.token?.view;
|
|
456
|
+
if (v && v.mint === mint)
|
|
457
|
+
return supplyRatio(v.price, v.mcap);
|
|
458
|
+
const tk = state.token;
|
|
459
|
+
if (tk && tk.mint === mint)
|
|
460
|
+
return supplyRatio(tk.price, tk.mcap);
|
|
461
|
+
const row = state.screener.rows.find((r) => r.mint === mint); // the card also shows over the lists (task-1473)
|
|
462
|
+
return row ? supplyRatio(row.price, row.mcap) : null;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* task-1473: the POSITION PnL chip for the app's current auto mode, or null. Following an agent: its open PAPER
|
|
466
|
+
* ticket (the folded story). Else, while AI scan mode runs: the user's own held paper / live position (ownPnl).
|
|
467
|
+
* The same chip feeds the HUD dock, the band row (no dock) and the chart popup. Display only.
|
|
468
|
+
*/
|
|
469
|
+
export function pnlChipFor(state, now, view) {
|
|
470
|
+
const hud = state.agentHud;
|
|
471
|
+
if (hud) {
|
|
472
|
+
const m = pnlChipVM(view ?? hud.story.view(now), now);
|
|
473
|
+
if (m)
|
|
474
|
+
return m;
|
|
475
|
+
}
|
|
476
|
+
if (state.scan?.engine.on && state.ownPnl)
|
|
477
|
+
return state.ownPnl.vm(now);
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
function renderScreenFrame(state, w, h, bandChip = false) {
|
|
431
481
|
const c = new Canvas(w, h, state.theme);
|
|
432
482
|
c.fill(0, 0, w, h, ' ', { bg: state.theme.bg });
|
|
433
483
|
c.imageMode = state.images && state.imageProtocol && state.imageProtocol !== 'blocks' ? 'pixels' : 'cells';
|
|
@@ -445,11 +495,25 @@ export function renderFrame(state, w, h) {
|
|
|
445
495
|
renderVoiceHud(c, state.voice, state.theme);
|
|
446
496
|
// task-1358: the workspace badge (non-default name, a pending ctrl+w chord, the save-as input) on the band
|
|
447
497
|
// under the header, right-aligned, drawn after the effects so it is always legible. Nothing on `default`.
|
|
498
|
+
// task-1473 r2: no HUD dock (short terminal, or AI scan mode): the PnL chip rides the band, right-aligned,
|
|
499
|
+
// and the workspace badge moves to its left.
|
|
500
|
+
let bandRight = w - 1;
|
|
501
|
+
if (bandChip && state.screen !== 'boot') {
|
|
502
|
+
const now = state.agentHud?.clock() ?? state.now.getTime();
|
|
503
|
+
const chip = pnlChipFor(state, now);
|
|
504
|
+
if (chip) {
|
|
505
|
+
const x0 = renderPnlChip(c, w - 1, 1, Math.max(36, Math.floor(w * 0.7)), chip, { now, reduced: !!state.agentHud?.reduced || isCalm(state.vfx) });
|
|
506
|
+
if (x0 < w - 1) {
|
|
507
|
+
c.noRain(x0, 1, w - 1 - x0, 1);
|
|
508
|
+
bandRight = x0 - 1;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
448
512
|
const badge = state.screen !== 'boot' ? workspaceBadge(state.custom, state.now.getTime()) : null;
|
|
449
|
-
if (badge) {
|
|
513
|
+
if (badge && bandRight > 4) {
|
|
450
514
|
const t = state.theme;
|
|
451
|
-
const text = badge.text.slice(0, Math.max(0,
|
|
452
|
-
const x = Math.max(0,
|
|
515
|
+
const text = badge.text.slice(0, Math.max(0, bandRight - 1));
|
|
516
|
+
const x = Math.max(0, bandRight - text.length);
|
|
453
517
|
c.noRain(x, 1, text.length, 1);
|
|
454
518
|
c.text(x, 1, text, badge.kind === 'input' ? { fg: t.text, bg: t.accentSoft, b: true } : badge.kind === 'chord' ? { fg: t.warn, bg: t.warnSoft, b: true } : { fg: t.accent, bg: t.headBg, b: true });
|
|
455
519
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// The one place the app's version lives (task-1397). --version, the help banner and `dexterm update` read it;
|
|
2
2
|
// cli.test.ts keeps it equal to packages/cli/package.json (release.yml refuses a tag that differs).
|
|
3
|
-
export const VERSION = '0.1.
|
|
3
|
+
export const VERSION = '0.1.6';
|
|
@@ -3,13 +3,13 @@ export const FIELDS = {
|
|
|
3
3
|
vol5m: (r) => r.vol5m, vol1h: (r) => r.vol1h, vol24: (r) => r.vol24,
|
|
4
4
|
holders: (r) => r.holders, org: (r) => r.org, top10: (r) => r.top10, dev: (r) => r.dev,
|
|
5
5
|
lplocked: (r) => r.lpLocked, bundled: (r) => r.bundled, snipers: (r) => r.snipers,
|
|
6
|
-
cluster: (r) => r.cluster, larp: (r) => r.larp,
|
|
6
|
+
cluster: (r) => r.cluster, larp: (r) => r.larp, devrugs: (r) => r.devRugs,
|
|
7
7
|
fresh: (r) => r.fresh, mintable: (r) => r.mintable, freezable: (r) => r.freezable,
|
|
8
8
|
age: (r) => r.ageMin, m5: (r) => r.chg?.m5, h1: (r) => r.chg?.h1, h6: (r) => r.chg?.h6, h24: (r) => r.chg?.h24,
|
|
9
9
|
buys: (r) => r.buys, sells: (r) => r.sells, bonding: (r) => r.bonding,
|
|
10
10
|
};
|
|
11
11
|
export const FIELD_NAMES = Object.keys(FIELDS);
|
|
12
|
-
const ALIASES = { vol: 'vol24', volume: 'vol24', liquidity: 'liq', cap: 'mcap', mc: 'mcap', holder: 'holders', organic: 'org', top: 'top10', locked: 'lplocked', 'cluster%': 'cluster', clusterpct: 'cluster', freshwallets: 'fresh', mintauth: 'mintable', freezeauth: 'freezable' };
|
|
12
|
+
const ALIASES = { vol: 'vol24', volume: 'vol24', liquidity: 'liq', cap: 'mcap', mc: 'mcap', holder: 'holders', organic: 'org', top: 'top10', locked: 'lplocked', 'cluster%': 'cluster', clusterpct: 'cluster', freshwallets: 'fresh', mintauth: 'mintable', freezeauth: 'freezable', devrug: 'devrugs', serialrugger: 'devrugs' };
|
|
13
13
|
const OPS = { '>': (a, b) => a > b, '>=': (a, b) => a >= b, '<': (a, b) => a < b, '<=': (a, b) => a <= b, '=': (a, b) => a === b, '==': (a, b) => a === b, '!=': (a, b) => a !== b };
|
|
14
14
|
const TAGS = {
|
|
15
15
|
launchpad: (r, v) => (r.launchpad ?? '').toLowerCase().replace('.fun', '') === v.toLowerCase().replace('.fun', ''),
|
|
@@ -140,6 +140,12 @@ export const LAYOUT_SCREENS = ['screener', 'token', 'newpairs', 'watchlist', 'an
|
|
|
140
140
|
*/
|
|
141
141
|
export const PRICE_MODES = ['price', 'mcap'];
|
|
142
142
|
export const DEFAULT_PRICE_MODE = 'price';
|
|
143
|
+
/**
|
|
144
|
+
* task-1472: what the CHART Y axis shows (the terminal token chart and the chart popup), separate from the table/header
|
|
145
|
+
* price mode: `mcap` (default; absent = mcap, so existing configs get it too) or `price`. `A` / the popup control toggle it.
|
|
146
|
+
*/
|
|
147
|
+
export const CHART_AXES = ['mcap', 'price'];
|
|
148
|
+
export const DEFAULT_CHART_AXIS = 'mcap';
|
|
143
149
|
const tokenProps = () => Object.fromEntries([...THEME_TOKENS.map((t) => [t, 'hex']), ...THEME_RAMPS.map((t) => [t, 'ramp'])]);
|
|
144
150
|
export const SCHEMAS = {
|
|
145
151
|
hex: { type: 'string', pattern: HEX },
|
|
@@ -236,8 +242,8 @@ export const SCHEMAS = {
|
|
|
236
242
|
user: { type: 'object', values: 'userTheme' },
|
|
237
243
|
} },
|
|
238
244
|
fx: { type: 'object', additional: false, props: { intensity: { type: 'string', enum: ['off', 'normal', 'fever'] }, sound: { type: 'boolean' } } },
|
|
239
|
-
/** task-1366: display options. `priceMode` price | mcap (the `$` key toggles and persists it). */
|
|
240
|
-
display: { type: 'object', additional: false, props: { priceMode: { type: 'string', enum: PRICE_MODES } } },
|
|
245
|
+
/** task-1366: display options. `priceMode` price | mcap (the `$` key toggles and persists it). task-1472: `chartAxis` mcap | price (`A`; absent = mcap). */
|
|
246
|
+
display: { type: 'object', additional: false, props: { priceMode: { type: 'string', enum: PRICE_MODES }, chartAxis: { type: 'string', enum: CHART_AXES } } },
|
|
241
247
|
layout: { type: 'object', additional: false, props: {
|
|
242
248
|
workspace: { type: 'string', pattern: WS_NAME },
|
|
243
249
|
workspaces: { type: 'object', values: 'workspace' },
|
|
@@ -261,6 +267,10 @@ export const SCHEMAS = {
|
|
|
261
267
|
/** Reserved: the trading gate is NOT here (signer policy owns it); listed so a proposal that tries gets a precise refusal. */
|
|
262
268
|
gate: { type: 'string', enum: ['keypress'] },
|
|
263
269
|
} },
|
|
270
|
+
/** 0.1.5: which of the user's own CLIs runs the analyst. `/analyst` is user-only (DENIED_FOR_MODELS): a model never picks its own engine. */
|
|
271
|
+
analyst: { type: 'object', additional: false, props: {
|
|
272
|
+
engine: { type: 'string', enum: ['auto', 'claude', 'codex'] },
|
|
273
|
+
} },
|
|
264
274
|
/** task-1393: voice-out preferences. `/voice` is user-only (DENIED_FOR_MODELS), so only the keyboard sets it. */
|
|
265
275
|
voice: { type: 'object', additional: false, props: {
|
|
266
276
|
name: { type: 'string', max: 64 },
|
|
@@ -426,7 +436,7 @@ export function applyOps(doc, ops) {
|
|
|
426
436
|
export const DENIED_FOR_MODELS = [
|
|
427
437
|
/^\/signer(\/|$)/, /^\/trading(\/|$)/, /^\/keys(\/|$)/, /^\/caps(\/|$)/, /^\/mandates?(\/|$)/, /^\/wallet(\/|$)/,
|
|
428
438
|
/^\/packs(\/|$)/, /^\/plugins\/[^/]+\/(capabilities|enabled)(\/|$)/, /^\/plugins\/[^/]+$/, /^\/plugins$/,
|
|
429
|
-
/^\/jarvis\/gate(\/|$)/, /^\/jarvis\/configAssistant$/, /^\/voice(\/|$)/, /^\/alerts(\/|$)/, /^\/agents(\/|$)/, /^\/version$/,
|
|
439
|
+
/^\/jarvis\/gate(\/|$)/, /^\/jarvis\/configAssistant$/, /^\/voice(\/|$)/, /^\/analyst(\/|$)/, /^\/alerts(\/|$)/, /^\/agents(\/|$)/, /^\/version$/,
|
|
430
440
|
/^\/rules\/(\d+|-)\/then\/(\d+|-)\/args\/(arm|armed|auto|autoArm|confirm|execute|sign|send)(\/|$)/,
|
|
431
441
|
];
|
|
432
442
|
/** Keys a model-authored VALUE may never contain at any depth (money/secret-shaped), whatever the path. */
|
|
@@ -62,21 +62,43 @@ export class FixtureStore {
|
|
|
62
62
|
}
|
|
63
63
|
save(path, note) {
|
|
64
64
|
mkdirSync(dirname(path), { recursive: true });
|
|
65
|
-
const
|
|
65
|
+
const raw = {
|
|
66
66
|
version: 1,
|
|
67
67
|
recordedAt: new Date().toISOString(),
|
|
68
68
|
note: note ?? this.note,
|
|
69
69
|
entries: this.list(),
|
|
70
70
|
};
|
|
71
71
|
if (this.streams.length)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
72
|
+
raw.streams = this.streams;
|
|
73
|
+
// Request URLs are redacted at capture time, but live response bodies echo third-party URLs with key-shaped
|
|
74
|
+
// params (DexScreener image URLs carry `&key=<cache id>`); refusing those threw away whole live sessions at
|
|
75
|
+
// quit (task-1443). Redact every string instead. Replay still matches: requestKey() redacts the same way.
|
|
76
|
+
const file = scrubSecrets(raw);
|
|
77
|
+
// Belt and braces: never write a file that still carries a key value (checked per string, so escaping is moot).
|
|
78
|
+
if (anyString(file, containsSecret))
|
|
76
79
|
throw new Error('refusing to save a recording that contains an api-key value');
|
|
77
|
-
writeFileSync(path,
|
|
80
|
+
writeFileSync(path, JSON.stringify(file, null, 1) + '\n');
|
|
78
81
|
}
|
|
79
82
|
}
|
|
83
|
+
/** A deep copy of `v` with redactSecrets() applied to every string. */
|
|
84
|
+
function scrubSecrets(v) {
|
|
85
|
+
if (typeof v === 'string')
|
|
86
|
+
return redactSecrets(v);
|
|
87
|
+
if (Array.isArray(v))
|
|
88
|
+
return v.map(scrubSecrets);
|
|
89
|
+
if (v && typeof v === 'object')
|
|
90
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, scrubSecrets(x)]));
|
|
91
|
+
return v;
|
|
92
|
+
}
|
|
93
|
+
function anyString(v, pred) {
|
|
94
|
+
if (typeof v === 'string')
|
|
95
|
+
return pred(v);
|
|
96
|
+
if (Array.isArray(v))
|
|
97
|
+
return v.some((x) => anyString(x, pred));
|
|
98
|
+
if (v && typeof v === 'object')
|
|
99
|
+
return Object.values(v).some((x) => anyString(x, pred));
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
80
102
|
export function toEntry(req, res, recordedAt = new Date().toISOString()) {
|
|
81
103
|
let body = res.body;
|
|
82
104
|
let bodyIsJson = false;
|
|
@@ -38,6 +38,9 @@ export * from "./trade/tx-decode.js";
|
|
|
38
38
|
export * from "./trade/sol-usd.js";
|
|
39
39
|
export * from "./trade/kill-switch.js";
|
|
40
40
|
export * from "./trade/token-delta.js";
|
|
41
|
+
// task-1443: the paper agent desk + its rendezvous (agents paper-trade inside an armed mandate; never sign/send).
|
|
42
|
+
export * from "./trade/agent/paper-desk.js";
|
|
43
|
+
export * from "./trade/agent/rendezvous.js";
|
|
41
44
|
export * from "./trade/signer/protocol.js";
|
|
42
45
|
export * from "./trade/signer/service.js";
|
|
43
46
|
export * from "./trade/signer/host.js";
|
|
@@ -50,6 +53,8 @@ export * from "./larp/links.js";
|
|
|
50
53
|
export * from "./larp/safe-fetch.js";
|
|
51
54
|
export * from "./larp/evidence.js";
|
|
52
55
|
export * from "./larp/link-baseline.js";
|
|
56
|
+
export * from "./larp/x-account.js";
|
|
57
|
+
export * from "./larp/score.js";
|
|
53
58
|
// task-1401: parse a CA / coin link from any input (go-to, analyst chat, filter line, --mint, MCP refs).
|
|
54
59
|
export * from "./coin-target.js";
|
|
55
60
|
export * from "./analyst-actions.js";
|