dexterm-cli 0.1.2 → 0.1.3
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/bus.js +37 -1
- package/dist/packages/agents/src/follow.js +105 -0
- package/dist/packages/agents/src/index.js +5 -0
- package/dist/packages/agents/src/spectator-server.js +153 -0
- package/dist/packages/agents/src/spectator.js +212 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +6 -1
- package/dist/packages/cli/src/agent-cmd.js +71 -7
- package/dist/packages/cli/src/agent-spectator.js +37 -0
- package/dist/packages/cli/src/alerts/format.js +25 -0
- package/dist/packages/cli/src/app-help-gen.js +365 -0
- package/dist/packages/cli/src/doctor.js +6 -5
- package/dist/packages/cli/src/follow-cmd.js +107 -1
- package/dist/packages/cli/src/help-cmd.js +48 -0
- package/dist/packages/cli/src/live/index.js +64 -3
- package/dist/packages/cli/src/live/screens/token-live.js +24 -0
- package/dist/packages/cli/src/live/signals.js +109 -0
- package/dist/packages/cli/src/live/trade.js +24 -1
- package/dist/packages/cli/src/main.js +355 -10
- package/dist/packages/cli/src/mcp-cmd.js +2 -7
- package/dist/packages/cli/src/pnl-app.js +221 -0
- package/dist/packages/cli/src/pnl-card.js +615 -0
- package/dist/packages/cli/src/record-cmd.js +455 -0
- package/dist/packages/cli/src/rules.js +50 -2
- package/dist/packages/cli/src/screens/follows.js +91 -2
- package/dist/packages/cli/src/screens/overlay.js +3 -0
- package/dist/packages/cli/src/screens/positions.js +1 -1
- package/dist/packages/cli/src/screens/rules.js +1 -1
- package/dist/packages/cli/src/spectate-cmd.js +104 -0
- package/dist/packages/cli/src/state.js +1 -1
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/cli/src/voice-cmd.js +86 -11
- package/dist/packages/cli/src/voice-hud.js +1 -1
- package/dist/packages/custom/src/rules.js +2 -1
- package/dist/packages/data/src/analyst-actions.js +130 -3
- package/dist/packages/data/src/fast-lane.js +16 -2
- package/dist/packages/data/src/index.js +1 -0
- package/dist/packages/data/src/intel/live-signals.js +185 -0
- package/dist/packages/data/src/trade/copy/index.js +1 -1
- package/dist/packages/data/src/trade/copy/stats.js +36 -2
- package/dist/packages/data/src/trade/copy/tracker.js +3 -2
- package/dist/packages/mcp/assets/kb/app-help/app-help-command.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/ask-the-analyst.md +43 -0
- package/dist/packages/mcp/assets/kb/app-help/backtest.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/bubble-map.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/calm-mode.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/change-voice-speed.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/change-voice.md +30 -0
- package/dist/packages/mcp/assets/kb/app-help/chart-popup-selftest.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/coin-images.md +29 -0
- package/dist/packages/mcp/assets/kb/app-help/configure-rpc-key.md +28 -0
- package/dist/packages/mcp/assets/kb/app-help/connect-an-agent-registry.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/connect-an-agent.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/custom-keymap.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/edit-kb-or-skill.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/edit-rules.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/export-a-view.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/follow-a-wallet.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/getting-started.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/kol-lists.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/larp-check.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +41 -0
- package/dist/packages/mcp/assets/kb/app-help/login-with-x.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/narrative-board.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/open-a-coin.md +43 -0
- package/dist/packages/mcp/assets/kb/app-help/open-chart-popup.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/order-plans.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/paper-trade.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/record-headless.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/recorder-history.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/rug-check.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/run-doctor.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +190 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +180 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +199 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +240 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +192 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/set-a-filter.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/setup-neural-voice.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/share-packs.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/sort-the-screener.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/switch-theme.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/turn-on-alerts.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/update-dexterm.md +30 -0
- package/dist/packages/mcp/assets/kb/app-help/use-presets.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/use-voice-mode.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/watch-a-coin.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/watch-headless.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/what-dexterm-shows-agents.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/workspaces.md +38 -0
- package/dist/packages/mcp/src/app-help.js +135 -0
- package/dist/packages/mcp/src/index.js +3 -0
- package/dist/packages/mcp/src/server.js +90 -3
- package/dist/packages/mcp/src/spectator-sink.js +72 -0
- package/dist/packages/packs/src/index.js +2 -0
- package/dist/packages/packs/src/skill-care.js +206 -0
- package/dist/packages/popup/src/pages/spectator.css +45 -0
- package/dist/packages/popup/src/pages/spectator.html +25 -0
- package/dist/packages/popup/src/pages/spectator.js +74 -0
- package/dist/packages/popup/src/selftest.js +19 -0
- package/dist/packages/popup/src/server.js +7 -1
- package/dist/packages/tui/src/keymap.js +10 -0
- package/dist/packages/voice/src/controller.js +42 -1
- package/dist/packages/voice/src/doctor.js +25 -8
- package/dist/packages/voice/src/engines.js +8 -1
- package/dist/packages/voice/src/index.js +3 -1
- package/dist/packages/voice/src/speakable.js +111 -0
- package/dist/packages/voice/src/tts/piper-setup.js +229 -0
- package/dist/packages/voice/src/tts/piper.js +309 -66
- package/dist/packages/voice/src/tts/say.js +3 -2
- package/dist/packages/voice/src/tts/voices.js +8 -5
- package/package.json +1 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// dexterm — PNL CARD glue for the app (task-1407). `H` on the token, positions and followed-wallets screens opens a
|
|
2
|
+
// preview of the coin's PnL card built from dexterm's own records:
|
|
3
|
+
// token → that coin: LIVE when the signer's ledger has fills for it, else PAPER
|
|
4
|
+
// positions → every coin with fills (newest activity first; ← → cycle), LIVE or PAPER as above
|
|
5
|
+
// follows → the selected followed wallet's coins (TRACKED), newest first
|
|
6
|
+
// In the preview: x share to X · t copy as text · s save only · a show/hide wallet · z exact size/band ·
|
|
7
|
+
// f 16:9 ⇄ 1:1 · ← → other coin · esc close. Share = save + clipboard image + X compose intent in the user's own
|
|
8
|
+
// browser (pnl-card.ts shareCard); dexterm posts nothing. Nothing here trades, signs or edits a number.
|
|
9
|
+
import { mix } from '../../tui/src/index.js';
|
|
10
|
+
import { computeCard, renderCard, previewCanvas, cardText, paperCardInput, liveCardInput, trackedCardInput, CardError, } from "./pnl-card.js";
|
|
11
|
+
const MARK_VIA = 'dexscreener';
|
|
12
|
+
function coinMeta(state, mint) {
|
|
13
|
+
const tv = state.live?.token?.view;
|
|
14
|
+
if (tv && tv.mint === mint)
|
|
15
|
+
return { symbol: tv.symbol, name: tv.name, image: tv.image };
|
|
16
|
+
const row = state.screener.rows.find((r) => r.mint === mint);
|
|
17
|
+
return { symbol: row?.symbol ?? null, name: row?.name ?? null, image: row?.image ?? null };
|
|
18
|
+
}
|
|
19
|
+
function imageOf(state, url) {
|
|
20
|
+
if (!url || !state.images)
|
|
21
|
+
return null;
|
|
22
|
+
const st = state.images.get(url, 2);
|
|
23
|
+
return st.state === 'ready' ? st.img : null;
|
|
24
|
+
}
|
|
25
|
+
/** The newest price mark for a mint: the positions mark pass, else the open token's market panel ÷ SOL/USD. */
|
|
26
|
+
export function markFor(state, mint) {
|
|
27
|
+
const pos = state.live?.positions;
|
|
28
|
+
const p = pos?.markOf(mint) ?? null, at = pos?.markAt(mint) ?? null;
|
|
29
|
+
if (p !== null && at !== null)
|
|
30
|
+
return { priceSol: p, at, via: MARK_VIA };
|
|
31
|
+
const tv = state.live?.token?.view;
|
|
32
|
+
const solUsd = pos?.solUsd() ?? null;
|
|
33
|
+
const m = tv?.panels.market;
|
|
34
|
+
if (tv && tv.mint === mint && tv.price && solUsd && m?.fetchedAt)
|
|
35
|
+
return { priceSol: tv.price / solUsd, at: m.fetchedAt, via: `${m.via ?? 'market'} ÷ SOL/USD` };
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/** Coins with paper or live fills, newest activity first. */
|
|
39
|
+
function tradedCoins(state) {
|
|
40
|
+
const live = state.live;
|
|
41
|
+
if (!live)
|
|
42
|
+
return [];
|
|
43
|
+
const last = new Map();
|
|
44
|
+
const see = (mint, t, symbol) => { const e = last.get(mint); if (!e || t > e.t)
|
|
45
|
+
last.set(mint, { t, symbol }); };
|
|
46
|
+
for (const r of live.wallet.trades(1000))
|
|
47
|
+
see(r.mint, r.t, r.symbol);
|
|
48
|
+
for (const r of live.livePositions?.records(1000) ?? [])
|
|
49
|
+
see(r.mint, r.t, r.symbol);
|
|
50
|
+
return [...last.entries()].sort((a, b) => b[1].t - a[1].t).map(([mint, v]) => ({ mint, symbol: v.symbol }));
|
|
51
|
+
}
|
|
52
|
+
/** Open the preview for the current screen. Returns a reason when there is nothing to card. */
|
|
53
|
+
export function openPnl(state) {
|
|
54
|
+
if (!state.live)
|
|
55
|
+
return 'PnL cards are made from recorded fills: run dexterm live (not --fixtures / --replay)';
|
|
56
|
+
const base = { idx: 0, stats: null, error: null, busy: false, notice: null, opts: { aspect: 'wide', showWallet: false, exactSize: false } };
|
|
57
|
+
if (state.screen === 'follows') {
|
|
58
|
+
const follows = state.copy?.follows() ?? [];
|
|
59
|
+
const f = follows[Math.max(0, Math.min(follows.length - 1, state.copyUi?.selected ?? 0))];
|
|
60
|
+
if (!f)
|
|
61
|
+
return 'follow a wallet first (i), then H cards its coins';
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
const choices = [];
|
|
64
|
+
for (const tr of [...(state.copy?.trades(f.addr) ?? [])].sort((a, b) => b.t - a.t)) {
|
|
65
|
+
if (seen.has(tr.mint) || tr.side === 'transfer')
|
|
66
|
+
continue;
|
|
67
|
+
seen.add(tr.mint);
|
|
68
|
+
choices.push({ mint: tr.mint, symbol: coinMeta(state, tr.mint).symbol ?? tr.mint.slice(0, 4) });
|
|
69
|
+
}
|
|
70
|
+
if (!choices.length)
|
|
71
|
+
return 'no decoded swaps for this wallet yet (r polls now)';
|
|
72
|
+
state.pnl = { origin: 'follows', choices, wallet: f.addr, ...base };
|
|
73
|
+
}
|
|
74
|
+
else if (state.screen === 'token') {
|
|
75
|
+
const mint = state.live.token?.view?.mint ?? state.token?.mint ?? null;
|
|
76
|
+
if (!mint)
|
|
77
|
+
return 'open a coin first';
|
|
78
|
+
const has = (state.live.livePositions?.records(1, mint).length ?? 0) > 0 || state.live.wallet.trades(1000).some((r) => r.mint === mint);
|
|
79
|
+
if (!has)
|
|
80
|
+
return 'no paper or live fills for this coin yet: PnL cards come from your own recorded trades';
|
|
81
|
+
state.pnl = { origin: 'token', choices: [{ mint, symbol: coinMeta(state, mint).symbol ?? mint.slice(0, 4) }], wallet: null, ...base };
|
|
82
|
+
}
|
|
83
|
+
else if (state.screen === 'positions') {
|
|
84
|
+
const choices = tradedCoins(state);
|
|
85
|
+
if (!choices.length)
|
|
86
|
+
return 'no paper or live fills yet: B / S on a token to trade, then H here';
|
|
87
|
+
state.pnl = { origin: 'positions', choices, wallet: null, ...base };
|
|
88
|
+
}
|
|
89
|
+
else
|
|
90
|
+
return 'PnL cards open from the token, positions (P) or followed wallets (U) screens';
|
|
91
|
+
state.overlay = 'pnl';
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/** The card input for the current choice, from the right ledger. Async only for a tracked coin that needs decimals / a price. */
|
|
95
|
+
export async function pnlInput(state, ui) {
|
|
96
|
+
const live = state.live;
|
|
97
|
+
if (!live)
|
|
98
|
+
throw new CardError('no live context');
|
|
99
|
+
const ch = ui.choices[ui.idx];
|
|
100
|
+
const meta = coinMeta(state, ch.mint);
|
|
101
|
+
const image = imageOf(state, meta.image);
|
|
102
|
+
if (ui.origin === 'follows' && ui.wallet && state.copy) {
|
|
103
|
+
const copy = state.copy;
|
|
104
|
+
const st = copy.status();
|
|
105
|
+
const stats = copy.stats(ui.wallet);
|
|
106
|
+
let mark = markFor(state, ch.mint);
|
|
107
|
+
let decimals = null;
|
|
108
|
+
// decimals + a price for a public mint: the same keyless reads the mirror checks make (no wallet, no user data)
|
|
109
|
+
const m = await copy.mirrorMarket(ch.mint, { symbol: meta.symbol });
|
|
110
|
+
decimals = m.decimals;
|
|
111
|
+
const solUsd = live.positions.solUsd();
|
|
112
|
+
if (!mark && m.priceUsd && m.priceAt && solUsd)
|
|
113
|
+
mark = { priceSol: m.priceUsd / solUsd, at: m.priceAt, via: `${MARK_VIA} ÷ SOL/USD` };
|
|
114
|
+
return trackedCardInput(copy.trades(ui.wallet), {
|
|
115
|
+
wallet: ui.wallet, mint: ch.mint, symbol: meta.symbol ?? m.symbol ?? ch.symbol, name: meta.name, decimals, mark,
|
|
116
|
+
historyAt: st.lastPollAt, complete: stats.sample.complete, image,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const liveRows = live.livePositions?.fills(ch.mint) ?? [];
|
|
120
|
+
if (liveRows.length) {
|
|
121
|
+
return liveCardInput(liveRows, { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, wallet: live.linkedWallet?.publicKey ?? null, mark: markFor(state, ch.mint), image });
|
|
122
|
+
}
|
|
123
|
+
return paperCardInput(live.wallet.trades(1000), { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, mark: markFor(state, ch.mint), image });
|
|
124
|
+
}
|
|
125
|
+
/** (Re)build the card for the current choice; on refusal the preview shows the reason instead of a card. */
|
|
126
|
+
export async function loadPnl(state, now = Date.now()) {
|
|
127
|
+
const ui = state.pnl;
|
|
128
|
+
if (!ui)
|
|
129
|
+
return;
|
|
130
|
+
try {
|
|
131
|
+
ui.stats = computeCard(await pnlInput(state, ui), now);
|
|
132
|
+
ui.error = null;
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
ui.stats = null;
|
|
136
|
+
ui.error = e instanceof Error ? e.message : String(e);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Keys while the preview is open. Pure: the loop performs reload / share. */
|
|
140
|
+
export function pnlKey(ui, key) {
|
|
141
|
+
if (key.name === 'esc' || (key.name === 'char' && key.ch === 'q'))
|
|
142
|
+
return { kind: 'close' };
|
|
143
|
+
if (ui.busy)
|
|
144
|
+
return { kind: 'none' };
|
|
145
|
+
const ch = key.name === 'char' && !key.ctrl ? key.ch : undefined;
|
|
146
|
+
if (key.name === 'right' || ch === ']' || ch === 'l') {
|
|
147
|
+
if (ui.choices.length > 1) {
|
|
148
|
+
ui.idx = (ui.idx + 1) % ui.choices.length;
|
|
149
|
+
return { kind: 'reload' };
|
|
150
|
+
}
|
|
151
|
+
return { kind: 'none' };
|
|
152
|
+
}
|
|
153
|
+
if (key.name === 'left' || ch === '[' || ch === 'h') {
|
|
154
|
+
if (ui.choices.length > 1) {
|
|
155
|
+
ui.idx = (ui.idx - 1 + ui.choices.length) % ui.choices.length;
|
|
156
|
+
return { kind: 'reload' };
|
|
157
|
+
}
|
|
158
|
+
return { kind: 'none' };
|
|
159
|
+
}
|
|
160
|
+
if (ch === 'a') {
|
|
161
|
+
ui.opts.showWallet = !ui.opts.showWallet;
|
|
162
|
+
ui.notice = ui.opts.showWallet ? 'wallet + tx signatures SHOWN on the card' : 'wallet hidden';
|
|
163
|
+
return { kind: 'none' };
|
|
164
|
+
}
|
|
165
|
+
if (ch === 'z') {
|
|
166
|
+
ui.opts.exactSize = !ui.opts.exactSize;
|
|
167
|
+
ui.notice = ui.opts.exactSize ? 'exact SOL size SHOWN on the card' : 'size shown as a band';
|
|
168
|
+
return { kind: 'none' };
|
|
169
|
+
}
|
|
170
|
+
if (ch === 'f') {
|
|
171
|
+
ui.opts.aspect = ui.opts.aspect === 'wide' ? 'square' : 'wide';
|
|
172
|
+
return { kind: 'none' };
|
|
173
|
+
}
|
|
174
|
+
if (!ui.stats)
|
|
175
|
+
return { kind: 'none' };
|
|
176
|
+
if (ch === 'x')
|
|
177
|
+
return { kind: 'share', action: 'x' };
|
|
178
|
+
if (ch === 't')
|
|
179
|
+
return { kind: 'share', action: 'text' };
|
|
180
|
+
if (ch === 's')
|
|
181
|
+
return { kind: 'share', action: 'save' };
|
|
182
|
+
return { kind: 'none' };
|
|
183
|
+
}
|
|
184
|
+
const HINTS = 'x share to X · t copy text · s save · a wallet · z size · f 16:9/1:1 · ←→ coin · esc';
|
|
185
|
+
/** The preview overlay: the card itself at cell size when the terminal fits it, else its text; then the keys. */
|
|
186
|
+
export function renderPnlOverlay(c, state) {
|
|
187
|
+
const ui = state.pnl;
|
|
188
|
+
if (!ui)
|
|
189
|
+
return;
|
|
190
|
+
const t = c.theme;
|
|
191
|
+
c.noRain(0, 0, c.w, c.h);
|
|
192
|
+
c.fill(0, 0, c.w, c.h, ' ', { bg: mix(t.bg, '#000000', 0.5) });
|
|
193
|
+
const ch = ui.choices[ui.idx];
|
|
194
|
+
const head = `PnL CARD · $${ch?.symbol ?? '?'}${ui.choices.length > 1 ? ` (${ui.idx + 1}/${ui.choices.length})` : ''} · ${ui.opts.aspect === 'wide' ? '16:9' : '1:1'} · wallet ${ui.opts.showWallet ? 'SHOWN' : 'hidden'} · size ${ui.opts.exactSize ? 'EXACT' : 'band'}`;
|
|
195
|
+
c.text(1, 0, head, { fg: t.text, bg: t.headBg, b: true }, c.w - 2);
|
|
196
|
+
c.fill(0, c.h - 1, c.w, 1, ' ', { bg: t.headBg });
|
|
197
|
+
c.text(1, c.h - 1, ui.busy ? 'working… (esc closes)' : (ui.notice ?? HINTS), { fg: ui.notice ? t.warn : t.muted, bg: t.headBg }, c.w - 2);
|
|
198
|
+
const area = { x: 0, y: 1, w: c.w, h: c.h - 2 };
|
|
199
|
+
if (!ui.stats) {
|
|
200
|
+
c.text(2, 3, ui.error ? `no card: ${ui.error}` : 'building the card…', { fg: ui.error ? t.warn : t.muted, bg: c.bg[c.idx(2, 3)] }, c.w - 4);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const r = renderCard(ui.stats, ui.opts);
|
|
204
|
+
const pv = previewCanvas(r);
|
|
205
|
+
if (pv.w <= area.w && pv.h <= area.h) {
|
|
206
|
+
const ox = area.x + Math.floor((area.w - pv.w) / 2), oy = area.y + Math.floor((area.h - pv.h) / 2);
|
|
207
|
+
for (let y = 0; y < pv.h; y++)
|
|
208
|
+
for (let x = 0; x < pv.w; x++) {
|
|
209
|
+
const i = pv.idx(x, y), j = c.idx(ox + x, oy + y);
|
|
210
|
+
c.ch[j] = pv.ch[i];
|
|
211
|
+
c.fg[j] = pv.fg[i];
|
|
212
|
+
c.bg[j] = pv.bg[i];
|
|
213
|
+
c.at[j] = pv.at[i];
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// too small for the card grid: the same facts as text (the PNG is still full size)
|
|
218
|
+
const lines = cardText(ui.stats, ui.opts).trimEnd().split('\n');
|
|
219
|
+
lines.forEach((l, k) => { if (2 + k < c.h - 1)
|
|
220
|
+
c.text(2, 2 + k, l, { fg: k === 0 ? t.warn : t.text, bg: c.bg[c.idx(2, 2 + k)] }, c.w - 4); });
|
|
221
|
+
}
|