rlm-cli 0.2.0
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/README.md +184 -0
- package/bin/rlm.mjs +45 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +185 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +73 -0
- package/dist/env.d.ts +9 -0
- package/dist/env.js +34 -0
- package/dist/interactive.d.ts +10 -0
- package/dist/interactive.js +789 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.js +144 -0
- package/dist/repl.d.ts +47 -0
- package/dist/repl.js +183 -0
- package/dist/rlm.d.ts +55 -0
- package/dist/rlm.js +354 -0
- package/dist/runtime.py +185 -0
- package/dist/viewer.d.ts +12 -0
- package/dist/viewer.js +828 -0
- package/package.json +48 -0
- package/rlm_config.yaml +17 -0
package/dist/viewer.js
ADDED
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* RLM Trajectory Viewer — interactive TUI for browsing saved trajectory JSON files.
|
|
4
|
+
*
|
|
5
|
+
* Navigate through iterations with arrow keys, view code, REPL output,
|
|
6
|
+
* sub-queries, and the final answer in a beautifully formatted display.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* rlm viewer # pick from list
|
|
10
|
+
* rlm viewer trajectories/file.json # open specific file
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
// ── ANSI helpers ────────────────────────────────────────────────────────────
|
|
15
|
+
const c = {
|
|
16
|
+
reset: "\x1b[0m",
|
|
17
|
+
bold: "\x1b[1m",
|
|
18
|
+
dim: "\x1b[2m",
|
|
19
|
+
italic: "\x1b[3m",
|
|
20
|
+
underline: "\x1b[4m",
|
|
21
|
+
inverse: "\x1b[7m",
|
|
22
|
+
red: "\x1b[31m",
|
|
23
|
+
green: "\x1b[32m",
|
|
24
|
+
yellow: "\x1b[33m",
|
|
25
|
+
blue: "\x1b[34m",
|
|
26
|
+
magenta: "\x1b[35m",
|
|
27
|
+
cyan: "\x1b[36m",
|
|
28
|
+
white: "\x1b[37m",
|
|
29
|
+
gray: "\x1b[90m",
|
|
30
|
+
bgBlue: "\x1b[44m",
|
|
31
|
+
bgCyan: "\x1b[46m",
|
|
32
|
+
bgGray: "\x1b[100m",
|
|
33
|
+
clearScreen: "\x1b[2J",
|
|
34
|
+
cursorHome: "\x1b[H",
|
|
35
|
+
hideCursor: "\x1b[?25l",
|
|
36
|
+
showCursor: "\x1b[?25h",
|
|
37
|
+
altScreenOn: "\x1b[?1049h",
|
|
38
|
+
altScreenOff: "\x1b[?1049l",
|
|
39
|
+
};
|
|
40
|
+
function W(...args) {
|
|
41
|
+
process.stdout.write(args.join(""));
|
|
42
|
+
}
|
|
43
|
+
// ── Layout helpers ──────────────────────────────────────────────────────────
|
|
44
|
+
function getWidth() {
|
|
45
|
+
return Math.min(process.stdout.columns || 80, 120);
|
|
46
|
+
}
|
|
47
|
+
function getHeight() {
|
|
48
|
+
return process.stdout.rows || 24;
|
|
49
|
+
}
|
|
50
|
+
function hline(ch = "━", color = c.cyan) {
|
|
51
|
+
return `${color}${ch.repeat(getWidth())}${c.reset}`;
|
|
52
|
+
}
|
|
53
|
+
function centeredHeader(text, color = c.cyan) {
|
|
54
|
+
const w = getWidth();
|
|
55
|
+
const stripped = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
56
|
+
const pad = Math.max(0, w - stripped.length - 4);
|
|
57
|
+
const left = Math.floor(pad / 2);
|
|
58
|
+
const right = pad - left;
|
|
59
|
+
return `${color}${"━".repeat(left)} ${text}${color} ${"━".repeat(right)}${c.reset}`;
|
|
60
|
+
}
|
|
61
|
+
function boxed(title, content, color) {
|
|
62
|
+
const w = getWidth() - 4;
|
|
63
|
+
const display = content;
|
|
64
|
+
W(` ${color}${c.bold}${title}${c.reset}\n`);
|
|
65
|
+
W(` ${color}┌${"─".repeat(w)}┐${c.reset}\n`);
|
|
66
|
+
for (const line of display.split("\n")) {
|
|
67
|
+
const stripped = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
68
|
+
const padding = Math.max(0, w - stripped.length - 1);
|
|
69
|
+
W(` ${color}│${c.reset} ${line}${" ".repeat(padding)}${color}│${c.reset}\n`);
|
|
70
|
+
}
|
|
71
|
+
W(` ${color}└${"─".repeat(w)}┘${c.reset}\n`);
|
|
72
|
+
}
|
|
73
|
+
function kvLine(key, value) {
|
|
74
|
+
W(` ${c.gray}${key}:${c.reset} ${value}\n`);
|
|
75
|
+
}
|
|
76
|
+
function formatSize(chars) {
|
|
77
|
+
if (chars >= 1_000_000)
|
|
78
|
+
return `${(chars / 1_000_000).toFixed(1)}M`;
|
|
79
|
+
if (chars >= 1000)
|
|
80
|
+
return `${(chars / 1000).toFixed(1)}K`;
|
|
81
|
+
return `${chars}`;
|
|
82
|
+
}
|
|
83
|
+
function listTrajectories() {
|
|
84
|
+
const dir = path.resolve(process.cwd(), "trajectories");
|
|
85
|
+
if (!fs.existsSync(dir))
|
|
86
|
+
return [];
|
|
87
|
+
return fs
|
|
88
|
+
.readdirSync(dir)
|
|
89
|
+
.filter((f) => f.endsWith(".json"))
|
|
90
|
+
.map((f) => {
|
|
91
|
+
const full = path.join(dir, f);
|
|
92
|
+
const stat = fs.statSync(full);
|
|
93
|
+
let traj;
|
|
94
|
+
try {
|
|
95
|
+
traj = JSON.parse(fs.readFileSync(full, "utf-8"));
|
|
96
|
+
}
|
|
97
|
+
catch { /* skip */ }
|
|
98
|
+
return { name: f, path: full, size: stat.size, mtime: stat.mtime, traj };
|
|
99
|
+
})
|
|
100
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); // newest first
|
|
101
|
+
}
|
|
102
|
+
async function pickFile(files) {
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
let selected = 0;
|
|
105
|
+
const maxVisible = Math.min(files.length, getHeight() - 10);
|
|
106
|
+
function render() {
|
|
107
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
108
|
+
W(`\n${hline()}\n`);
|
|
109
|
+
W(`${centeredHeader(`${c.bold}${c.white}RLM Trajectory Viewer${c.reset}`)}\n`);
|
|
110
|
+
W(`${hline()}\n\n`);
|
|
111
|
+
W(` ${c.bold}Select a trajectory:${c.reset} ${c.dim}(up/down navigate, enter select, q quit)${c.reset}\n\n`);
|
|
112
|
+
const scrollStart = Math.max(0, selected - Math.floor(maxVisible / 2));
|
|
113
|
+
const scrollEnd = Math.min(files.length, scrollStart + maxVisible);
|
|
114
|
+
for (let i = scrollStart; i < scrollEnd; i++) {
|
|
115
|
+
const f = files[i];
|
|
116
|
+
const isSel = i === selected;
|
|
117
|
+
const sizeKB = (f.size / 1024).toFixed(1);
|
|
118
|
+
// Extract info from trajectory data
|
|
119
|
+
const steps = f.traj?.iterations?.length ?? 0;
|
|
120
|
+
const completed = f.traj?.result?.completed;
|
|
121
|
+
const status = completed === true ? `${c.green}done${c.reset}` : completed === false ? `${c.yellow}partial${c.reset}` : "";
|
|
122
|
+
const queryPreview = f.traj?.query
|
|
123
|
+
? (f.traj.query.length > 40 ? f.traj.query.slice(0, 37) + "..." : f.traj.query)
|
|
124
|
+
: "";
|
|
125
|
+
// Date from filename
|
|
126
|
+
const dateMatch = f.name.match(/(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})/);
|
|
127
|
+
const dateStr = dateMatch ? `${dateMatch[1]} ${dateMatch[2]}:${dateMatch[3]}` : f.name;
|
|
128
|
+
const prefix = isSel ? `${c.cyan}${c.bold} > ` : ` `;
|
|
129
|
+
const nameColor = isSel ? `${c.cyan}${c.bold}` : c.white;
|
|
130
|
+
W(`${prefix}${nameColor}${dateStr}${c.reset}`);
|
|
131
|
+
W(` ${c.dim}${sizeKB}KB${c.reset}`);
|
|
132
|
+
W(` ${c.dim}${steps} step${steps !== 1 ? "s" : ""}${c.reset}`);
|
|
133
|
+
if (status)
|
|
134
|
+
W(` ${status}`);
|
|
135
|
+
if (queryPreview)
|
|
136
|
+
W(` ${c.dim}${queryPreview}${c.reset}`);
|
|
137
|
+
W(`\n`);
|
|
138
|
+
}
|
|
139
|
+
if (files.length > maxVisible) {
|
|
140
|
+
W(`\n ${c.dim}${scrollStart > 0 ? "^ more above" : ""} ${scrollEnd < files.length ? "v more below" : ""}${c.reset}\n`);
|
|
141
|
+
}
|
|
142
|
+
W(`\n`);
|
|
143
|
+
}
|
|
144
|
+
render();
|
|
145
|
+
process.stdin.setRawMode(true);
|
|
146
|
+
process.stdin.resume();
|
|
147
|
+
process.stdin.setEncoding("utf8");
|
|
148
|
+
function onKey(key) {
|
|
149
|
+
if (key === "\x1b[A") {
|
|
150
|
+
selected = Math.max(0, selected - 1);
|
|
151
|
+
render();
|
|
152
|
+
}
|
|
153
|
+
else if (key === "\x1b[B") {
|
|
154
|
+
selected = Math.min(files.length - 1, selected + 1);
|
|
155
|
+
render();
|
|
156
|
+
}
|
|
157
|
+
else if (key === "\r" || key === "\n") {
|
|
158
|
+
process.stdin.removeListener("data", onKey);
|
|
159
|
+
process.stdin.setRawMode(false);
|
|
160
|
+
process.stdin.pause();
|
|
161
|
+
resolve(files[selected].path);
|
|
162
|
+
}
|
|
163
|
+
else if (key === "q" || key === "\x03") {
|
|
164
|
+
W(c.showCursor, c.altScreenOff);
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
process.stdin.on("data", onKey);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function buildIterLine(step, isSelected) {
|
|
172
|
+
const isFinal = step.hasFinal;
|
|
173
|
+
const elapsed = (step.elapsedMs / 1000).toFixed(1);
|
|
174
|
+
const sqCount = step.subQueries.length;
|
|
175
|
+
const bullet = isFinal ? `${c.green}${c.bold}*${c.reset}` : `${c.blue}o${c.reset}`;
|
|
176
|
+
const sel = isSelected ? `${c.inverse}${c.cyan}` : "";
|
|
177
|
+
const codeLen = step.code ? step.code.split("\n").length : 0;
|
|
178
|
+
const outLen = step.stdout ? step.stdout.split("\n").length : 0;
|
|
179
|
+
const sqInfo = sqCount > 0 ? ` | ${c.magenta}${sqCount} sub-quer${sqCount !== 1 ? "ies" : "y"}${c.reset}` : "";
|
|
180
|
+
const errInfo = step.stderr ? ` | ${c.red}stderr${c.reset}` : "";
|
|
181
|
+
let line = ` ${sel} ${bullet} ${c.bold}Iteration ${step.iteration}${c.reset}`;
|
|
182
|
+
line += `${sel ? c.reset : ""} ${c.dim}${elapsed}s${c.reset}`;
|
|
183
|
+
line += ` | ${c.green}${codeLen}L code${c.reset} | ${c.yellow}${outLen}L output${c.reset}${sqInfo}${errInfo}`;
|
|
184
|
+
if (isFinal)
|
|
185
|
+
line += ` | ${c.green}${c.bold}FINAL${c.reset}`;
|
|
186
|
+
return line;
|
|
187
|
+
}
|
|
188
|
+
function renderOverview(state) {
|
|
189
|
+
const { traj } = state;
|
|
190
|
+
const w = getWidth();
|
|
191
|
+
const h = getHeight();
|
|
192
|
+
// Build all lines into a buffer
|
|
193
|
+
const buf = [];
|
|
194
|
+
// Header
|
|
195
|
+
buf.push(``);
|
|
196
|
+
buf.push(hline());
|
|
197
|
+
buf.push(centeredHeader(`${c.bold}${c.white}RLM Trajectory Viewer${c.reset}`));
|
|
198
|
+
buf.push(hline());
|
|
199
|
+
buf.push(``);
|
|
200
|
+
buf.push(` ${c.gray}Model :${c.reset} ${c.bold}${traj.model}${c.reset}`);
|
|
201
|
+
buf.push(` ${c.gray}Query :${c.reset} ${c.yellow}${traj.query}${c.reset}`);
|
|
202
|
+
buf.push(` ${c.gray}Context :${c.reset} ${traj.contextLength.toLocaleString()} chars | ${traj.contextLines.toLocaleString()} lines`);
|
|
203
|
+
buf.push(` ${c.gray}Duration:${c.reset} ${(traj.totalElapsedMs / 1000).toFixed(1)}s ${c.gray}|${c.reset} ${traj.result?.completed ? `${c.green}Completed${c.reset}` : `${c.red}Incomplete${c.reset}`}`);
|
|
204
|
+
buf.push(``);
|
|
205
|
+
buf.push(` ${c.bold}Iterations${c.reset} ${c.dim}(${traj.iterations.length} total)${c.reset}`);
|
|
206
|
+
buf.push(``);
|
|
207
|
+
const headerSize = buf.length;
|
|
208
|
+
const footerSize = 2;
|
|
209
|
+
const answerSize = traj.result ? 4 : 0;
|
|
210
|
+
const iterBudget = h - headerSize - footerSize - answerSize;
|
|
211
|
+
// Build iteration lines (each iteration = summary + separator)
|
|
212
|
+
const flatLines = [];
|
|
213
|
+
const iterStartOffsets = [];
|
|
214
|
+
for (let i = 0; i < traj.iterations.length; i++) {
|
|
215
|
+
const step = traj.iterations[i];
|
|
216
|
+
const isSel = i === state.iterIdx;
|
|
217
|
+
iterStartOffsets.push(flatLines.length);
|
|
218
|
+
flatLines.push(buildIterLine(step, isSel));
|
|
219
|
+
if (i < traj.iterations.length - 1) {
|
|
220
|
+
flatLines.push(` ${c.dim} |${c.reset}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Scroll so selected iteration is visible
|
|
224
|
+
const selStart = iterStartOffsets[state.iterIdx] ?? 0;
|
|
225
|
+
let scrollY = Math.max(0, selStart - 2);
|
|
226
|
+
// If everything fits, no scroll needed
|
|
227
|
+
if (flatLines.length <= iterBudget) {
|
|
228
|
+
scrollY = 0;
|
|
229
|
+
}
|
|
230
|
+
const showFrom = scrollY;
|
|
231
|
+
const showTo = Math.min(flatLines.length, scrollY + iterBudget);
|
|
232
|
+
if (showFrom > 0) {
|
|
233
|
+
buf.push(` ${c.dim} ^ more above${c.reset}`);
|
|
234
|
+
}
|
|
235
|
+
for (let i = showFrom; i < showTo; i++) {
|
|
236
|
+
buf.push(flatLines[i]);
|
|
237
|
+
}
|
|
238
|
+
if (showTo < flatLines.length) {
|
|
239
|
+
buf.push(` ${c.dim} | more below${c.reset}`);
|
|
240
|
+
}
|
|
241
|
+
// Answer preview
|
|
242
|
+
if (traj.result) {
|
|
243
|
+
buf.push(`${c.green}${"─".repeat(w)}${c.reset}`);
|
|
244
|
+
buf.push(` ${c.green}${c.bold}Answer Preview:${c.reset}`);
|
|
245
|
+
const preview = traj.result.answer.split("\n")[0] || "";
|
|
246
|
+
buf.push(` ${c.white}${preview}${c.reset}`);
|
|
247
|
+
if (traj.result.answer.split("\n").length > 1) {
|
|
248
|
+
buf.push(` ${c.dim}... (press 'r' to see full result)${c.reset}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Render
|
|
252
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
253
|
+
for (const l of buf)
|
|
254
|
+
W(l + "\n");
|
|
255
|
+
// Footer
|
|
256
|
+
W(hline("─", c.gray) + "\n");
|
|
257
|
+
W(` ${c.dim}up/down${c.reset} select ${c.dim}enter${c.reset} view ${c.dim}r${c.reset} result ${c.dim}q${c.reset} quit\n`);
|
|
258
|
+
}
|
|
259
|
+
function buildIterationContent(step, traj) {
|
|
260
|
+
const lines = [];
|
|
261
|
+
const w = getWidth() - 4;
|
|
262
|
+
// Title
|
|
263
|
+
lines.push(``);
|
|
264
|
+
lines.push(hline());
|
|
265
|
+
const finalTag = step.hasFinal ? ` ${c.green}${c.bold}FINAL${c.reset}` : "";
|
|
266
|
+
lines.push(centeredHeader(`${c.bold}${c.white}Iteration ${step.iteration} / ${traj.iterations.length}${c.reset}${finalTag}`));
|
|
267
|
+
lines.push(hline());
|
|
268
|
+
lines.push(``);
|
|
269
|
+
// Metadata
|
|
270
|
+
const elapsed = (step.elapsedMs / 1000).toFixed(1);
|
|
271
|
+
lines.push(` ${c.gray}Elapsed :${c.reset} ${elapsed}s`);
|
|
272
|
+
lines.push(` ${c.gray}Sub-queries:${c.reset} ${step.subQueries.length}`);
|
|
273
|
+
lines.push(` ${c.gray}Has Final :${c.reset} ${step.hasFinal ? `${c.green}yes${c.reset}` : `${c.gray}no${c.reset}`}`);
|
|
274
|
+
lines.push(``);
|
|
275
|
+
// Code
|
|
276
|
+
if (step.code) {
|
|
277
|
+
lines.push(` ${c.green}${c.bold}Generated Code${c.reset}`);
|
|
278
|
+
lines.push(` ${c.green}┌${"─".repeat(w)}┐${c.reset}`);
|
|
279
|
+
for (const cl of syntaxHighlight(step.code).split("\n")) {
|
|
280
|
+
const stripped = cl.replace(/\x1b\[[0-9;]*m/g, "");
|
|
281
|
+
const padding = Math.max(0, w - stripped.length - 1);
|
|
282
|
+
lines.push(` ${c.green}│${c.reset} ${cl}${" ".repeat(padding)}${c.green}│${c.reset}`);
|
|
283
|
+
}
|
|
284
|
+
lines.push(` ${c.green}└${"─".repeat(w)}┘${c.reset}`);
|
|
285
|
+
lines.push(``);
|
|
286
|
+
}
|
|
287
|
+
// REPL Output
|
|
288
|
+
if (step.stdout) {
|
|
289
|
+
lines.push(` ${c.yellow}${c.bold}REPL Output${c.reset}`);
|
|
290
|
+
lines.push(` ${c.yellow}┌${"─".repeat(w)}┐${c.reset}`);
|
|
291
|
+
for (const ol of step.stdout.split("\n")) {
|
|
292
|
+
const stripped = ol.replace(/\x1b\[[0-9;]*m/g, "");
|
|
293
|
+
const padding = Math.max(0, w - stripped.length - 1);
|
|
294
|
+
lines.push(` ${c.yellow}│${c.reset} ${ol}${" ".repeat(padding)}${c.yellow}│${c.reset}`);
|
|
295
|
+
}
|
|
296
|
+
lines.push(` ${c.yellow}└${"─".repeat(w)}┘${c.reset}`);
|
|
297
|
+
lines.push(``);
|
|
298
|
+
}
|
|
299
|
+
// Stderr
|
|
300
|
+
if (step.stderr) {
|
|
301
|
+
lines.push(` ${c.red}${c.bold}Stderr${c.reset}`);
|
|
302
|
+
lines.push(` ${c.red}┌${"─".repeat(w)}┐${c.reset}`);
|
|
303
|
+
for (const el of step.stderr.split("\n")) {
|
|
304
|
+
const stripped = el.replace(/\x1b\[[0-9;]*m/g, "");
|
|
305
|
+
const padding = Math.max(0, w - stripped.length - 1);
|
|
306
|
+
lines.push(` ${c.red}│${c.reset} ${el}${" ".repeat(padding)}${c.red}│${c.reset}`);
|
|
307
|
+
}
|
|
308
|
+
lines.push(` ${c.red}└${"─".repeat(w)}┘${c.reset}`);
|
|
309
|
+
lines.push(``);
|
|
310
|
+
}
|
|
311
|
+
// Sub-queries
|
|
312
|
+
if (step.subQueries.length > 0) {
|
|
313
|
+
lines.push(` ${c.magenta}${c.bold}Sub-queries (${step.subQueries.length})${c.reset} ${c.dim}press 's' for details${c.reset}`);
|
|
314
|
+
for (const sq of step.subQueries) {
|
|
315
|
+
const instrPreview = sq.instruction.length > 60 ? sq.instruction.slice(0, 57) + "..." : sq.instruction;
|
|
316
|
+
const sqElapsed = sq.elapsedMs ? ` ${c.dim}${(sq.elapsedMs / 1000).toFixed(1)}s${c.reset}` : "";
|
|
317
|
+
lines.push(` ${c.magenta}#${sq.index}${c.reset} ${c.dim}(${formatSize(sq.contextLength)})${c.reset}${sqElapsed} ${instrPreview}`);
|
|
318
|
+
}
|
|
319
|
+
lines.push(``);
|
|
320
|
+
}
|
|
321
|
+
return lines;
|
|
322
|
+
}
|
|
323
|
+
function renderIteration(state) {
|
|
324
|
+
const { traj, iterIdx } = state;
|
|
325
|
+
const step = traj.iterations[iterIdx];
|
|
326
|
+
if (!step)
|
|
327
|
+
return;
|
|
328
|
+
const allLines = buildIterationContent(step, traj);
|
|
329
|
+
const h = getHeight();
|
|
330
|
+
const footerSize = 2;
|
|
331
|
+
const viewable = h - footerSize;
|
|
332
|
+
// Clamp scrollY
|
|
333
|
+
const maxScroll = Math.max(0, allLines.length - viewable);
|
|
334
|
+
if (state.scrollY > maxScroll)
|
|
335
|
+
state.scrollY = maxScroll;
|
|
336
|
+
if (state.scrollY < 0)
|
|
337
|
+
state.scrollY = 0;
|
|
338
|
+
const from = state.scrollY;
|
|
339
|
+
const to = Math.min(allLines.length, from + viewable);
|
|
340
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
341
|
+
// Scroll indicator at top
|
|
342
|
+
if (from > 0) {
|
|
343
|
+
W(` ${c.dim}^ scroll up (${from} lines above)${c.reset}\n`);
|
|
344
|
+
for (let i = from + 1; i < to; i++)
|
|
345
|
+
W(allLines[i] + "\n");
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
for (let i = from; i < to; i++)
|
|
349
|
+
W(allLines[i] + "\n");
|
|
350
|
+
}
|
|
351
|
+
if (to < allLines.length) {
|
|
352
|
+
// Replace last visible line with scroll indicator
|
|
353
|
+
W(` ${c.dim}v scroll down (${allLines.length - to} lines below)${c.reset}\n`);
|
|
354
|
+
}
|
|
355
|
+
// Footer
|
|
356
|
+
const hints = [];
|
|
357
|
+
if (step.userMessage)
|
|
358
|
+
hints.push(`${c.dim}i${c.reset} input`);
|
|
359
|
+
if (step.rawResponse)
|
|
360
|
+
hints.push(`${c.dim}l${c.reset} response`);
|
|
361
|
+
if (step.systemPrompt || traj.iterations[0]?.systemPrompt)
|
|
362
|
+
hints.push(`${c.dim}p${c.reset} prompt`);
|
|
363
|
+
W(hline("─", c.gray) + "\n");
|
|
364
|
+
W(` ${c.dim}esc${c.reset} back `);
|
|
365
|
+
W(`${c.dim}up/down${c.reset} scroll `);
|
|
366
|
+
W(`${c.dim}n/p${c.reset} next/prev `);
|
|
367
|
+
if (step.subQueries.length > 0)
|
|
368
|
+
W(`${c.dim}s${c.reset} sub-queries `);
|
|
369
|
+
for (const hint of hints)
|
|
370
|
+
W(`${hint} `);
|
|
371
|
+
W(`${c.dim}r${c.reset} result `);
|
|
372
|
+
W(`${c.dim}q${c.reset} quit\n`);
|
|
373
|
+
}
|
|
374
|
+
function renderResult(state) {
|
|
375
|
+
const { traj } = state;
|
|
376
|
+
const result = traj.result;
|
|
377
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
378
|
+
W(`\n${hline("━", c.green)}\n`);
|
|
379
|
+
W(`${centeredHeader(`${c.bold}${c.white}Final Result${c.reset}`, c.green)}\n`);
|
|
380
|
+
W(`${hline("━", c.green)}\n\n`);
|
|
381
|
+
if (!result) {
|
|
382
|
+
W(` ${c.red}${c.bold}No result available${c.reset} — the run may have been interrupted.\n`);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
kvLine("Completed ", result.completed ? `${c.green}yes${c.reset}` : `${c.red}no${c.reset}`);
|
|
386
|
+
kvLine("Iterations ", `${result.iterations}`);
|
|
387
|
+
kvLine("Sub-queries ", `${result.totalSubQueries}`);
|
|
388
|
+
kvLine("Duration ", `${(traj.totalElapsedMs / 1000).toFixed(1)}s`);
|
|
389
|
+
W(`\n`);
|
|
390
|
+
boxed("Answer", result.answer, c.green);
|
|
391
|
+
}
|
|
392
|
+
W(`\n${hline("─", c.gray)}\n`);
|
|
393
|
+
W(` ${c.dim}esc${c.reset} back `);
|
|
394
|
+
W(`${c.dim}q${c.reset} quit\n`);
|
|
395
|
+
}
|
|
396
|
+
function renderSubQueries(state) {
|
|
397
|
+
const { traj, iterIdx } = state;
|
|
398
|
+
const step = traj.iterations[iterIdx];
|
|
399
|
+
if (!step)
|
|
400
|
+
return;
|
|
401
|
+
const h = getHeight();
|
|
402
|
+
// Build buffer
|
|
403
|
+
const buf = [];
|
|
404
|
+
buf.push(``);
|
|
405
|
+
buf.push(hline("━", c.magenta));
|
|
406
|
+
buf.push(centeredHeader(`${c.bold}${c.white}Sub-queries — Iteration ${step.iteration}${c.reset}`, c.magenta));
|
|
407
|
+
buf.push(hline("━", c.magenta));
|
|
408
|
+
buf.push(``);
|
|
409
|
+
if (step.subQueries.length === 0) {
|
|
410
|
+
buf.push(` ${c.dim}No sub-queries in this iteration.${c.reset}`);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// Clamp subQueryIdx
|
|
414
|
+
if (state.subQueryIdx >= step.subQueries.length)
|
|
415
|
+
state.subQueryIdx = step.subQueries.length - 1;
|
|
416
|
+
if (state.subQueryIdx < 0)
|
|
417
|
+
state.subQueryIdx = 0;
|
|
418
|
+
const headerSize = buf.length;
|
|
419
|
+
const footerSize = 2;
|
|
420
|
+
const listBudget = h - headerSize - footerSize;
|
|
421
|
+
// Build list lines (each sub-query = 2 lines: summary + separator)
|
|
422
|
+
const listLines = [];
|
|
423
|
+
const sqStartOffsets = [];
|
|
424
|
+
for (let i = 0; i < step.subQueries.length; i++) {
|
|
425
|
+
const sq = step.subQueries[i];
|
|
426
|
+
const isSel = i === state.subQueryIdx;
|
|
427
|
+
const sqElapsed = sq.elapsedMs ? `${(sq.elapsedMs / 1000).toFixed(1)}s` : "";
|
|
428
|
+
const instrPreview = sq.instruction.length > 50 ? sq.instruction.slice(0, 47) + "..." : sq.instruction;
|
|
429
|
+
sqStartOffsets.push(listLines.length);
|
|
430
|
+
const sel = isSel ? `${c.inverse}${c.magenta}` : "";
|
|
431
|
+
const prefix = isSel ? `${c.magenta}${c.bold} > ` : ` `;
|
|
432
|
+
let line = `${prefix}${sel}#${sq.index}${c.reset}`;
|
|
433
|
+
line += ` ${c.dim}${sqElapsed}${c.reset}`;
|
|
434
|
+
line += ` ${c.dim}${formatSize(sq.contextLength)} in, ${formatSize(sq.resultLength)} out${c.reset}`;
|
|
435
|
+
line += ` ${instrPreview}`;
|
|
436
|
+
listLines.push(line);
|
|
437
|
+
if (i < step.subQueries.length - 1) {
|
|
438
|
+
listLines.push(` ${c.dim} |${c.reset}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Scroll so selected sub-query is visible
|
|
442
|
+
const selStart = sqStartOffsets[state.subQueryIdx] ?? 0;
|
|
443
|
+
let scrollY = Math.max(0, selStart - 2);
|
|
444
|
+
if (listLines.length <= listBudget)
|
|
445
|
+
scrollY = 0;
|
|
446
|
+
const showFrom = scrollY;
|
|
447
|
+
const showTo = Math.min(listLines.length, scrollY + listBudget);
|
|
448
|
+
if (showFrom > 0) {
|
|
449
|
+
buf.push(` ${c.dim} ^ more above${c.reset}`);
|
|
450
|
+
}
|
|
451
|
+
for (let i = showFrom; i < showTo; i++) {
|
|
452
|
+
buf.push(listLines[i]);
|
|
453
|
+
}
|
|
454
|
+
if (showTo < listLines.length) {
|
|
455
|
+
buf.push(` ${c.dim} | more below${c.reset}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Render
|
|
459
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
460
|
+
for (const l of buf)
|
|
461
|
+
W(l + "\n");
|
|
462
|
+
// Footer
|
|
463
|
+
W(hline("─", c.gray) + "\n");
|
|
464
|
+
W(` ${c.dim}up/down${c.reset} select ${c.dim}enter${c.reset} view ${c.dim}esc${c.reset} back ${c.dim}q${c.reset} quit\n`);
|
|
465
|
+
}
|
|
466
|
+
function renderSubQueryDetail(state) {
|
|
467
|
+
const { traj, iterIdx } = state;
|
|
468
|
+
const step = traj.iterations[iterIdx];
|
|
469
|
+
if (!step)
|
|
470
|
+
return;
|
|
471
|
+
// Clamp subQueryIdx
|
|
472
|
+
if (state.subQueryIdx >= step.subQueries.length)
|
|
473
|
+
state.subQueryIdx = step.subQueries.length - 1;
|
|
474
|
+
if (state.subQueryIdx < 0)
|
|
475
|
+
state.subQueryIdx = 0;
|
|
476
|
+
const sq = step.subQueries[state.subQueryIdx];
|
|
477
|
+
if (!sq)
|
|
478
|
+
return;
|
|
479
|
+
const w = getWidth() - 4;
|
|
480
|
+
const h = getHeight();
|
|
481
|
+
// Build all content lines
|
|
482
|
+
const allLines = [];
|
|
483
|
+
allLines.push(``);
|
|
484
|
+
allLines.push(hline("━", c.magenta));
|
|
485
|
+
allLines.push(centeredHeader(`${c.bold}${c.white}Sub-query #${sq.index} — Iteration ${step.iteration}${c.reset}`, c.magenta));
|
|
486
|
+
allLines.push(hline("━", c.magenta));
|
|
487
|
+
allLines.push(``);
|
|
488
|
+
// Metadata
|
|
489
|
+
const sqElapsed = sq.elapsedMs ? `${(sq.elapsedMs / 1000).toFixed(1)}s` : "n/a";
|
|
490
|
+
allLines.push(` ${c.gray}Elapsed :${c.reset} ${sqElapsed}`);
|
|
491
|
+
allLines.push(` ${c.gray}Context length:${c.reset} ${formatSize(sq.contextLength)} chars`);
|
|
492
|
+
allLines.push(` ${c.gray}Result length :${c.reset} ${formatSize(sq.resultLength)} chars`);
|
|
493
|
+
allLines.push(` ${c.gray}Position :${c.reset} ${state.subQueryIdx + 1} of ${step.subQueries.length}`);
|
|
494
|
+
allLines.push(``);
|
|
495
|
+
// Full instruction (boxed, no truncation)
|
|
496
|
+
allLines.push(` ${c.magenta}${c.bold}Instruction${c.reset}`);
|
|
497
|
+
allLines.push(` ${c.magenta}┌${"─".repeat(w)}┐${c.reset}`);
|
|
498
|
+
for (const line of sq.instruction.split("\n")) {
|
|
499
|
+
const stripped = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
500
|
+
const padding = Math.max(0, w - stripped.length - 1);
|
|
501
|
+
allLines.push(` ${c.magenta}│${c.reset} ${line}${" ".repeat(padding)}${c.magenta}│${c.reset}`);
|
|
502
|
+
}
|
|
503
|
+
allLines.push(` ${c.magenta}└${"─".repeat(w)}┘${c.reset}`);
|
|
504
|
+
allLines.push(``);
|
|
505
|
+
// Full result preview (boxed, no truncation)
|
|
506
|
+
allLines.push(` ${c.cyan}${c.bold}Result Preview${c.reset}`);
|
|
507
|
+
allLines.push(` ${c.cyan}┌${"─".repeat(w)}┐${c.reset}`);
|
|
508
|
+
for (const line of sq.resultPreview.split("\n")) {
|
|
509
|
+
const stripped = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
510
|
+
const padding = Math.max(0, w - stripped.length - 1);
|
|
511
|
+
allLines.push(` ${c.cyan}│${c.reset} ${line}${" ".repeat(padding)}${c.cyan}│${c.reset}`);
|
|
512
|
+
}
|
|
513
|
+
allLines.push(` ${c.cyan}└${"─".repeat(w)}┘${c.reset}`);
|
|
514
|
+
allLines.push(``);
|
|
515
|
+
// Scrollable rendering
|
|
516
|
+
const footerSize = 2;
|
|
517
|
+
const viewable = h - footerSize;
|
|
518
|
+
const maxScroll = Math.max(0, allLines.length - viewable);
|
|
519
|
+
if (state.scrollY > maxScroll)
|
|
520
|
+
state.scrollY = maxScroll;
|
|
521
|
+
if (state.scrollY < 0)
|
|
522
|
+
state.scrollY = 0;
|
|
523
|
+
const from = state.scrollY;
|
|
524
|
+
const to = Math.min(allLines.length, from + viewable);
|
|
525
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
526
|
+
if (from > 0) {
|
|
527
|
+
W(` ${c.dim}^ scroll up (${from} lines above)${c.reset}\n`);
|
|
528
|
+
for (let i = from + 1; i < to; i++)
|
|
529
|
+
W(allLines[i] + "\n");
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
for (let i = from; i < to; i++)
|
|
533
|
+
W(allLines[i] + "\n");
|
|
534
|
+
}
|
|
535
|
+
if (to < allLines.length) {
|
|
536
|
+
W(` ${c.dim}v scroll down (${allLines.length - to} lines below)${c.reset}\n`);
|
|
537
|
+
}
|
|
538
|
+
// Footer
|
|
539
|
+
W(hline("─", c.gray) + "\n");
|
|
540
|
+
W(` ${c.dim}up/down${c.reset} scroll ${c.dim}n/p${c.reset} next/prev ${c.dim}esc${c.reset} back ${c.dim}q${c.reset} quit\n`);
|
|
541
|
+
}
|
|
542
|
+
function renderLlmInput(state) {
|
|
543
|
+
const { traj, iterIdx } = state;
|
|
544
|
+
const step = traj.iterations[iterIdx];
|
|
545
|
+
if (!step)
|
|
546
|
+
return;
|
|
547
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
548
|
+
W(`\n${hline("━", c.blue)}\n`);
|
|
549
|
+
W(`${centeredHeader(`${c.bold}${c.white}LLM Input — Iteration ${step.iteration}${c.reset}`, c.blue)}\n`);
|
|
550
|
+
W(`${hline("━", c.blue)}\n\n`);
|
|
551
|
+
if (step.userMessage) {
|
|
552
|
+
kvLine("Length", `${step.userMessage.length.toLocaleString()} chars`);
|
|
553
|
+
W(`\n`);
|
|
554
|
+
boxed("User Message", step.userMessage, c.blue);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
W(` ${c.dim}No user message recorded for this iteration.${c.reset}\n`);
|
|
558
|
+
}
|
|
559
|
+
W(`\n${hline("─", c.gray)}\n`);
|
|
560
|
+
W(` ${c.dim}esc${c.reset} back `);
|
|
561
|
+
W(`${c.dim}q${c.reset} quit\n`);
|
|
562
|
+
}
|
|
563
|
+
function renderLlmResponse(state) {
|
|
564
|
+
const { traj, iterIdx } = state;
|
|
565
|
+
const step = traj.iterations[iterIdx];
|
|
566
|
+
if (!step)
|
|
567
|
+
return;
|
|
568
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
569
|
+
W(`\n${hline("━", c.green)}\n`);
|
|
570
|
+
W(`${centeredHeader(`${c.bold}${c.white}LLM Response — Iteration ${step.iteration}${c.reset}`, c.green)}\n`);
|
|
571
|
+
W(`${hline("━", c.green)}\n\n`);
|
|
572
|
+
if (step.rawResponse) {
|
|
573
|
+
kvLine("Length", `${step.rawResponse.length.toLocaleString()} chars`);
|
|
574
|
+
W(`\n`);
|
|
575
|
+
boxed("Full LLM Response", step.rawResponse, c.green);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
W(` ${c.dim}No response recorded for this iteration.${c.reset}\n`);
|
|
579
|
+
}
|
|
580
|
+
W(`\n${hline("─", c.gray)}\n`);
|
|
581
|
+
W(` ${c.dim}esc${c.reset} back `);
|
|
582
|
+
W(`${c.dim}q${c.reset} quit\n`);
|
|
583
|
+
}
|
|
584
|
+
function renderSystemPrompt(state) {
|
|
585
|
+
const { traj, iterIdx } = state;
|
|
586
|
+
const step = traj.iterations[iterIdx];
|
|
587
|
+
if (!step)
|
|
588
|
+
return;
|
|
589
|
+
W(c.cursorHome, c.clearScreen, c.hideCursor);
|
|
590
|
+
W(`\n${hline("━", c.cyan)}\n`);
|
|
591
|
+
W(`${centeredHeader(`${c.bold}${c.white}System Prompt${c.reset}`, c.cyan)}\n`);
|
|
592
|
+
W(`${hline("━", c.cyan)}\n\n`);
|
|
593
|
+
const sysPrompt = step.systemPrompt || traj.iterations[0]?.systemPrompt;
|
|
594
|
+
if (sysPrompt) {
|
|
595
|
+
boxed("System Prompt", sysPrompt, c.cyan);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
W(` ${c.dim}System prompt not recorded in this trajectory.${c.reset}\n`);
|
|
599
|
+
}
|
|
600
|
+
W(`\n${hline("─", c.gray)}\n`);
|
|
601
|
+
W(` ${c.dim}esc${c.reset} back `);
|
|
602
|
+
W(`${c.dim}q${c.reset} quit\n`);
|
|
603
|
+
}
|
|
604
|
+
// ── Minimal syntax highlighting ─────────────────────────────────────────────
|
|
605
|
+
function syntaxHighlight(code) {
|
|
606
|
+
return code
|
|
607
|
+
.replace(/\b(import|from|def|class|return|if|elif|else|for|while|in|not|and|or|try|except|finally|with|as|raise|pass|break|continue|yield|lambda|True|False|None|await|async)\b/g, `${c.magenta}$1${c.reset}`)
|
|
608
|
+
.replace(/\b(print|len|range|enumerate|sorted|set|list|dict|str|int|float|type|isinstance|zip|map|filter)\b/g, `${c.cyan}$1${c.reset}`)
|
|
609
|
+
.replace(/(#.*)$/gm, `${c.gray}$1${c.reset}`)
|
|
610
|
+
.replace(/("""[\s\S]*?"""|'''[\s\S]*?'''|"[^"]*"|'[^']*')/g, `${c.yellow}$1${c.reset}`)
|
|
611
|
+
.replace(/\b(llm_query|async_llm_query|context|FINAL|FINAL_VAR)\b/g, `${c.green}${c.bold}$1${c.reset}`);
|
|
612
|
+
}
|
|
613
|
+
// ── Main interactive loop ───────────────────────────────────────────────────
|
|
614
|
+
async function main() {
|
|
615
|
+
// Enter alternate screen buffer so output never scrolls the main terminal
|
|
616
|
+
W(c.altScreenOn);
|
|
617
|
+
// Ensure we always leave alt screen on exit
|
|
618
|
+
const cleanup = () => W(c.showCursor, c.altScreenOff);
|
|
619
|
+
process.on("exit", cleanup);
|
|
620
|
+
let filePath = process.argv[2];
|
|
621
|
+
if (!filePath) {
|
|
622
|
+
const files = listTrajectories();
|
|
623
|
+
if (files.length === 0) {
|
|
624
|
+
console.error(`${c.red}No trajectory files found in ./trajectories/${c.reset}\nRun a query first to generate trajectories.`);
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
filePath = await pickFile(files);
|
|
628
|
+
}
|
|
629
|
+
// Load trajectory
|
|
630
|
+
if (!fs.existsSync(filePath)) {
|
|
631
|
+
console.error(`${c.red}File not found: ${filePath}${c.reset}`);
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
const traj = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
635
|
+
if (!traj.iterations || traj.iterations.length === 0) {
|
|
636
|
+
console.error(`${c.red}Trajectory has no iterations (empty run).${c.reset}`);
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
// State
|
|
640
|
+
const state = {
|
|
641
|
+
mode: "overview",
|
|
642
|
+
iterIdx: 0,
|
|
643
|
+
subQueryIdx: 0,
|
|
644
|
+
scrollY: 0,
|
|
645
|
+
traj,
|
|
646
|
+
};
|
|
647
|
+
function render() {
|
|
648
|
+
switch (state.mode) {
|
|
649
|
+
case "overview":
|
|
650
|
+
renderOverview(state);
|
|
651
|
+
break;
|
|
652
|
+
case "iteration":
|
|
653
|
+
renderIteration(state);
|
|
654
|
+
break;
|
|
655
|
+
case "result":
|
|
656
|
+
renderResult(state);
|
|
657
|
+
break;
|
|
658
|
+
case "subqueries":
|
|
659
|
+
renderSubQueries(state);
|
|
660
|
+
break;
|
|
661
|
+
case "subqueryDetail":
|
|
662
|
+
renderSubQueryDetail(state);
|
|
663
|
+
break;
|
|
664
|
+
case "llmInput":
|
|
665
|
+
renderLlmInput(state);
|
|
666
|
+
break;
|
|
667
|
+
case "llmResponse":
|
|
668
|
+
renderLlmResponse(state);
|
|
669
|
+
break;
|
|
670
|
+
case "systemPrompt":
|
|
671
|
+
renderSystemPrompt(state);
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
render();
|
|
676
|
+
// Key handling
|
|
677
|
+
process.stdin.setRawMode(true);
|
|
678
|
+
process.stdin.resume();
|
|
679
|
+
process.stdin.setEncoding("utf8");
|
|
680
|
+
process.stdin.on("data", (key) => {
|
|
681
|
+
const maxIter = traj.iterations.length - 1;
|
|
682
|
+
switch (state.mode) {
|
|
683
|
+
case "overview":
|
|
684
|
+
if (key === "\x1b[A") {
|
|
685
|
+
state.iterIdx = Math.max(0, state.iterIdx - 1);
|
|
686
|
+
}
|
|
687
|
+
else if (key === "\x1b[B") {
|
|
688
|
+
state.iterIdx = Math.min(maxIter, state.iterIdx + 1);
|
|
689
|
+
}
|
|
690
|
+
else if (key === "\r" || key === "\n" || key === "\x1b[C") {
|
|
691
|
+
// Drill into iteration detail
|
|
692
|
+
state.mode = "iteration";
|
|
693
|
+
state.scrollY = 0;
|
|
694
|
+
}
|
|
695
|
+
else if (key === "r") {
|
|
696
|
+
state.mode = "result";
|
|
697
|
+
}
|
|
698
|
+
else if (key === "q" || key === "\x03") {
|
|
699
|
+
W(c.showCursor, "\n");
|
|
700
|
+
process.exit(0);
|
|
701
|
+
}
|
|
702
|
+
break;
|
|
703
|
+
case "iteration":
|
|
704
|
+
if (key === "\x1b[A") {
|
|
705
|
+
state.scrollY = Math.max(0, state.scrollY - 3);
|
|
706
|
+
}
|
|
707
|
+
else if (key === "\x1b[B") {
|
|
708
|
+
state.scrollY += 3;
|
|
709
|
+
}
|
|
710
|
+
else if (key === "n") {
|
|
711
|
+
if (state.iterIdx < maxIter) {
|
|
712
|
+
state.iterIdx++;
|
|
713
|
+
state.scrollY = 0;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else if (key === "N") {
|
|
717
|
+
if (state.iterIdx > 0) {
|
|
718
|
+
state.iterIdx--;
|
|
719
|
+
state.scrollY = 0;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
else if (key === "\x1b[D" || key === "\x1b" || key === "b") {
|
|
723
|
+
state.mode = "overview";
|
|
724
|
+
state.scrollY = 0;
|
|
725
|
+
}
|
|
726
|
+
else if (key === "s" && traj.iterations[state.iterIdx]?.subQueries.length > 0) {
|
|
727
|
+
state.mode = "subqueries";
|
|
728
|
+
state.subQueryIdx = 0;
|
|
729
|
+
}
|
|
730
|
+
else if (key === "i") {
|
|
731
|
+
state.mode = "llmInput";
|
|
732
|
+
}
|
|
733
|
+
else if (key === "l") {
|
|
734
|
+
state.mode = "llmResponse";
|
|
735
|
+
}
|
|
736
|
+
else if (key === "p") {
|
|
737
|
+
state.mode = "systemPrompt";
|
|
738
|
+
}
|
|
739
|
+
else if (key === "r") {
|
|
740
|
+
state.mode = "result";
|
|
741
|
+
}
|
|
742
|
+
else if (key === "q" || key === "\x03") {
|
|
743
|
+
W(c.showCursor, "\n");
|
|
744
|
+
process.exit(0);
|
|
745
|
+
}
|
|
746
|
+
break;
|
|
747
|
+
case "result":
|
|
748
|
+
if (key === "\x1b[D" || key === "\x1b" || key === "b") {
|
|
749
|
+
state.mode = "overview";
|
|
750
|
+
}
|
|
751
|
+
else if (key === "q" || key === "\x03") {
|
|
752
|
+
W(c.showCursor, "\n");
|
|
753
|
+
process.exit(0);
|
|
754
|
+
}
|
|
755
|
+
break;
|
|
756
|
+
case "subqueries": {
|
|
757
|
+
const sqCount = traj.iterations[state.iterIdx]?.subQueries.length ?? 0;
|
|
758
|
+
if (key === "\x1b[A") {
|
|
759
|
+
state.subQueryIdx = Math.max(0, state.subQueryIdx - 1);
|
|
760
|
+
}
|
|
761
|
+
else if (key === "\x1b[B") {
|
|
762
|
+
state.subQueryIdx = Math.min(sqCount - 1, state.subQueryIdx + 1);
|
|
763
|
+
}
|
|
764
|
+
else if (key === "\r" || key === "\n" || key === "\x1b[C") {
|
|
765
|
+
state.mode = "subqueryDetail";
|
|
766
|
+
state.scrollY = 0;
|
|
767
|
+
}
|
|
768
|
+
else if (key === "\x1b[D" || key === "\x1b" || key === "b") {
|
|
769
|
+
state.mode = "iteration";
|
|
770
|
+
}
|
|
771
|
+
else if (key === "q" || key === "\x03") {
|
|
772
|
+
W(c.showCursor, "\n");
|
|
773
|
+
process.exit(0);
|
|
774
|
+
}
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
case "subqueryDetail": {
|
|
778
|
+
const sqMax = (traj.iterations[state.iterIdx]?.subQueries.length ?? 1) - 1;
|
|
779
|
+
if (key === "\x1b[A") {
|
|
780
|
+
state.scrollY = Math.max(0, state.scrollY - 3);
|
|
781
|
+
}
|
|
782
|
+
else if (key === "\x1b[B") {
|
|
783
|
+
state.scrollY += 3;
|
|
784
|
+
}
|
|
785
|
+
else if (key === "n" || key === "\x1b[C") {
|
|
786
|
+
if (state.subQueryIdx < sqMax) {
|
|
787
|
+
state.subQueryIdx++;
|
|
788
|
+
state.scrollY = 0;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
else if (key === "p" || key === "N") {
|
|
792
|
+
if (state.subQueryIdx > 0) {
|
|
793
|
+
state.subQueryIdx--;
|
|
794
|
+
state.scrollY = 0;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else if (key === "\x1b[D" || key === "\x1b" || key === "b") {
|
|
798
|
+
state.mode = "subqueries";
|
|
799
|
+
state.scrollY = 0;
|
|
800
|
+
}
|
|
801
|
+
else if (key === "q" || key === "\x03") {
|
|
802
|
+
W(c.showCursor, "\n");
|
|
803
|
+
process.exit(0);
|
|
804
|
+
}
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
case "llmInput":
|
|
808
|
+
case "llmResponse":
|
|
809
|
+
case "systemPrompt":
|
|
810
|
+
if (key === "\x1b[D" || key === "\x1b" || key === "b") {
|
|
811
|
+
state.mode = "iteration";
|
|
812
|
+
}
|
|
813
|
+
else if (key === "q" || key === "\x03") {
|
|
814
|
+
W(c.showCursor, "\n");
|
|
815
|
+
process.exit(0);
|
|
816
|
+
}
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
render();
|
|
820
|
+
});
|
|
821
|
+
// (cleanup handler already registered at top of main)
|
|
822
|
+
}
|
|
823
|
+
main().catch((err) => {
|
|
824
|
+
W(c.showCursor, c.altScreenOff);
|
|
825
|
+
console.error(`Fatal: ${err}`);
|
|
826
|
+
process.exit(1);
|
|
827
|
+
});
|
|
828
|
+
//# sourceMappingURL=viewer.js.map
|