vericify 0.1.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 +389 -0
- package/package.json +57 -0
- package/src/adapters/index.js +37 -0
- package/src/adapters/local-state.js +86 -0
- package/src/adapters/registry.js +126 -0
- package/src/api.js +12 -0
- package/src/checkpoints/policy.js +96 -0
- package/src/compare/engine.js +220 -0
- package/src/core/fs.js +86 -0
- package/src/core/util.js +59 -0
- package/src/index.js +464 -0
- package/src/post/process-posts.js +72 -0
- package/src/projection/runs.js +809 -0
- package/src/publish/artifact.js +91 -0
- package/src/similarity/semantic-hash.js +95 -0
- package/src/store/adapter-attachments.js +47 -0
- package/src/store/common.js +38 -0
- package/src/store/handoffs.js +64 -0
- package/src/store/paths.js +40 -0
- package/src/store/run-ledger.js +46 -0
- package/src/store/status-events.js +39 -0
- package/src/store/todo-state.js +49 -0
- package/src/sync/outbox.js +29 -0
- package/src/tui/app.js +571 -0
- package/src/tui/commands.js +224 -0
- package/src/tui/panels.js +440 -0
- package/src/tui/runtime-activity.js +172 -0
package/src/tui/app.js
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import { readFileSync, openSync } from "node:fs";
|
|
2
|
+
import { emitKeypressEvents } from "node:readline";
|
|
3
|
+
import { stdin as processStdin, stdout } from "node:process";
|
|
4
|
+
import tty from "node:tty";
|
|
5
|
+
import { createDebouncedWatch } from "../core/fs.js";
|
|
6
|
+
import { appendProcessPost } from "../post/process-posts.js";
|
|
7
|
+
import { upsertHandoff } from "../store/handoffs.js";
|
|
8
|
+
import { appendRunLedgerEntry } from "../store/run-ledger.js";
|
|
9
|
+
import { appendStatusEvent } from "../store/status-events.js";
|
|
10
|
+
import { upsertTodoNode } from "../store/todo-state.js";
|
|
11
|
+
import { formatTimestamp } from "../core/util.js";
|
|
12
|
+
import { completeCommand, executeCommand } from "./commands.js";
|
|
13
|
+
import {
|
|
14
|
+
buildCompareSections,
|
|
15
|
+
buildHistorySections,
|
|
16
|
+
buildHubMetrics,
|
|
17
|
+
buildHubSections,
|
|
18
|
+
buildInspectSections,
|
|
19
|
+
buildRunBoardSection,
|
|
20
|
+
layoutTierForWidth,
|
|
21
|
+
} from "./panels.js";
|
|
22
|
+
import { createRuntimeRecorder } from "./runtime-activity.js";
|
|
23
|
+
|
|
24
|
+
function altBufferOn() {
|
|
25
|
+
stdout.write("\x1b[?1049h\x1b[?25l\x1b[2J\x1b[H");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function altBufferOff() {
|
|
29
|
+
stdout.write("\x1b[?25h\x1b[?1049l");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function clearScreen() {
|
|
33
|
+
stdout.write("\x1b[2J\x1b[H");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function truncate(text, width) {
|
|
37
|
+
const plain = String(text ?? "");
|
|
38
|
+
return plain.length <= width ? plain : `${plain.slice(0, Math.max(0, width - 1))}…`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function line(text = "") {
|
|
42
|
+
stdout.write(`${text}\n`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function boxTitle(text, width) {
|
|
46
|
+
return `┌${truncate(` ${text} `, Math.max(1, width - 2)).padEnd(Math.max(1, width - 2), "─")}┐`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function boxFooter(width) {
|
|
50
|
+
return `└${"─".repeat(Math.max(1, width - 2))}┘`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function padRight(text, width) {
|
|
54
|
+
return String(text ?? "").padEnd(Math.max(0, width), " ");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function bestCompareTarget(run) {
|
|
58
|
+
return run?.similarity?.neighbors?.[0]?.run_id;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function panelTitle(section) {
|
|
62
|
+
return section.tone ? `${section.title} · ${section.tone}` : section.title;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function renderPanelLines(section, width) {
|
|
66
|
+
const innerWidth = Math.max(1, width - 2);
|
|
67
|
+
const rows = Array.isArray(section.rows) && section.rows.length ? section.rows : ["No data."];
|
|
68
|
+
const lines = [
|
|
69
|
+
boxTitle(panelTitle(section), width),
|
|
70
|
+
];
|
|
71
|
+
for (const row of rows) {
|
|
72
|
+
for (const fragment of String(row ?? "").split("\n")) {
|
|
73
|
+
const content = truncate(` ${fragment}`, innerWidth).padEnd(innerWidth, " ");
|
|
74
|
+
lines.push(`│${content}│`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
lines.push(boxFooter(width));
|
|
78
|
+
return lines;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderPanelRow(sections, width) {
|
|
82
|
+
if (!sections.length) return;
|
|
83
|
+
const columns = sections.length;
|
|
84
|
+
const gap = 2;
|
|
85
|
+
const panelWidth = Math.max(24, Math.floor((width - gap * (columns - 1)) / columns));
|
|
86
|
+
const rendered = sections.map((section) => renderPanelLines(section, panelWidth));
|
|
87
|
+
const rowHeight = Math.max(...rendered.map((panel) => panel.length));
|
|
88
|
+
for (let lineIndex = 0; lineIndex < rowHeight; lineIndex += 1) {
|
|
89
|
+
const row = rendered
|
|
90
|
+
.map((panel) => padRight(panel[lineIndex] ?? "", panelWidth))
|
|
91
|
+
.join(" ".repeat(gap));
|
|
92
|
+
line(truncate(row, width));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderPanelGrid(sections, width, tier) {
|
|
97
|
+
const columns = tier === "narrow" ? 1 : 2;
|
|
98
|
+
let row = [];
|
|
99
|
+
const flush = () => {
|
|
100
|
+
if (!row.length) return;
|
|
101
|
+
renderPanelRow(row, width);
|
|
102
|
+
row = [];
|
|
103
|
+
};
|
|
104
|
+
for (const section of sections) {
|
|
105
|
+
if (section.fullWidth) {
|
|
106
|
+
flush();
|
|
107
|
+
renderPanelRow([section], width);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
row.push(section);
|
|
111
|
+
if (row.length >= columns) flush();
|
|
112
|
+
}
|
|
113
|
+
flush();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function runHub({ workspaceRoot, feedFile, loadState, watchedPaths }) {
|
|
117
|
+
const state = {
|
|
118
|
+
view: "hub",
|
|
119
|
+
mode: "normal",
|
|
120
|
+
selectedIndex: 0,
|
|
121
|
+
compareTarget: undefined,
|
|
122
|
+
commandBuffer: "",
|
|
123
|
+
commandHistory: [],
|
|
124
|
+
commandHistoryIndex: -1,
|
|
125
|
+
message: "Vericify ready.",
|
|
126
|
+
projected: loadState(),
|
|
127
|
+
};
|
|
128
|
+
const hubSourceModule = "vericify-hub";
|
|
129
|
+
const hubTraceId = `hub-${process.pid}-${Date.now()}`;
|
|
130
|
+
let suppressWatchUntil = 0;
|
|
131
|
+
const runtimeRecorder = createRuntimeRecorder({
|
|
132
|
+
workspaceRoot,
|
|
133
|
+
traceId: hubTraceId,
|
|
134
|
+
sourceModule: hubSourceModule,
|
|
135
|
+
isInteractive: !feedFile && Boolean(processStdin.isTTY),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const fallbackRun = {
|
|
139
|
+
run: { title: "No runs", run_id: "-", status: "-", updated_at: "-" },
|
|
140
|
+
branches: [],
|
|
141
|
+
lanes: [],
|
|
142
|
+
nodes: [],
|
|
143
|
+
handoff_timeline: { items: [] },
|
|
144
|
+
recent_checkpoints: [],
|
|
145
|
+
deltas: [],
|
|
146
|
+
activity_items: [],
|
|
147
|
+
process_posts: [],
|
|
148
|
+
similarity: { neighbors: [] },
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const rerender = () => {
|
|
152
|
+
clearScreen();
|
|
153
|
+
const width = Math.max(80, stdout.columns || 80);
|
|
154
|
+
const tier = layoutTierForWidth(width);
|
|
155
|
+
const runs = state.projected.runs;
|
|
156
|
+
const selected = runs[state.selectedIndex] ?? runs[0] ?? fallbackRun;
|
|
157
|
+
const compareRun = state.compareTarget
|
|
158
|
+
? runs.find((run) => run.run.run_id === state.compareTarget)
|
|
159
|
+
: runs.find((run) => run.run.run_id === bestCompareTarget(selected));
|
|
160
|
+
const sections = [
|
|
161
|
+
buildRunBoardSection(state.projected, selected, { tier }),
|
|
162
|
+
...(state.view === "compare"
|
|
163
|
+
? buildCompareSections(selected, compareRun, { tier })
|
|
164
|
+
: state.view === "inspect"
|
|
165
|
+
? buildInspectSections(selected, { tier })
|
|
166
|
+
: state.view === "history"
|
|
167
|
+
? buildHistorySections(state.projected, selected, { tier })
|
|
168
|
+
: buildHubSections(state.projected, selected, { tier })),
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
line(boxTitle(` Vericify Hub | ${state.mode} | ${workspaceRoot} `, width));
|
|
172
|
+
line(truncate(`Runs: ${runs.length} | Generated: ${formatTimestamp(state.projected.generated_at)} | View: ${state.view} | Layout: ${tier} | Message: ${state.message}`, width));
|
|
173
|
+
line(truncate(buildHubMetrics(state.projected, { tier }), width));
|
|
174
|
+
line("─".repeat(width));
|
|
175
|
+
renderPanelGrid(sections, width, tier);
|
|
176
|
+
const prompt = state.mode === "command"
|
|
177
|
+
? `/${state.commandBuffer}`
|
|
178
|
+
: "[q quit] [j/k move] [Enter inspect] [c compare] [y history] [r refresh] [/ command] [h hub]";
|
|
179
|
+
line(truncate(prompt, width));
|
|
180
|
+
line(boxFooter(width));
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const refresh = () => {
|
|
184
|
+
state.projected = loadState();
|
|
185
|
+
const max = Math.max(0, state.projected.runs.length - 1);
|
|
186
|
+
state.selectedIndex = Math.min(state.selectedIndex, max);
|
|
187
|
+
rerender();
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const selectedRunId = () => state.projected.runs[state.selectedIndex]?.run?.run_id;
|
|
191
|
+
|
|
192
|
+
const captureRuntimeState = () => ({
|
|
193
|
+
view: state.view,
|
|
194
|
+
runId: selectedRunId(),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const recordInteractiveTransition = (before, {
|
|
198
|
+
source = "keyboard",
|
|
199
|
+
refreshReason,
|
|
200
|
+
commandActionType,
|
|
201
|
+
} = {}) => {
|
|
202
|
+
const after = captureRuntimeState();
|
|
203
|
+
let wrote = false;
|
|
204
|
+
if (before.view !== after.view) {
|
|
205
|
+
wrote = runtimeRecorder.recordViewChange(after.view, after.runId, { source }) || wrote;
|
|
206
|
+
}
|
|
207
|
+
if (before.runId !== after.runId) {
|
|
208
|
+
wrote = runtimeRecorder.recordFocus(after.runId, { view: after.view, source }) || wrote;
|
|
209
|
+
}
|
|
210
|
+
if (refreshReason) {
|
|
211
|
+
wrote = runtimeRecorder.recordRefresh(after.runId, {
|
|
212
|
+
view: after.view,
|
|
213
|
+
source,
|
|
214
|
+
reason: refreshReason,
|
|
215
|
+
runCount: state.projected.runs.length,
|
|
216
|
+
}) || wrote;
|
|
217
|
+
}
|
|
218
|
+
if (commandActionType) {
|
|
219
|
+
wrote = runtimeRecorder.recordCommandWrite(commandActionType, after.runId, { view: after.view }) || wrote;
|
|
220
|
+
}
|
|
221
|
+
if (wrote) {
|
|
222
|
+
suppressWatch();
|
|
223
|
+
state.projected = loadState();
|
|
224
|
+
const max = Math.max(0, state.projected.runs.length - 1);
|
|
225
|
+
state.selectedIndex = Math.min(state.selectedIndex, max);
|
|
226
|
+
rerender();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const parseOptionalJson = (raw) => {
|
|
231
|
+
if (!raw) return undefined;
|
|
232
|
+
return JSON.parse(raw);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const suppressWatch = (durationMs = 1200) => {
|
|
236
|
+
suppressWatchUntil = Math.max(suppressWatchUntil, Date.now() + durationMs);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const executeStoreAction = async (action) => {
|
|
240
|
+
if (!action) return;
|
|
241
|
+
suppressWatch();
|
|
242
|
+
if (action.type === "post") {
|
|
243
|
+
appendProcessPost(workspaceRoot, action.params);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (action.type === "handoff") {
|
|
247
|
+
upsertHandoff(workspaceRoot, action.params);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (action.type === "todo") {
|
|
251
|
+
upsertTodoNode(workspaceRoot, action.params);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (action.type === "ledger") {
|
|
255
|
+
appendRunLedgerEntry(workspaceRoot, {
|
|
256
|
+
...action.params,
|
|
257
|
+
metadata: parseOptionalJson(action.params.metadata_json),
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (action.type === "event") {
|
|
262
|
+
appendStatusEvent(workspaceRoot, {
|
|
263
|
+
...action.params,
|
|
264
|
+
payload: parseOptionalJson(action.params.payload_json),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const emitHubEvent = async (eventType, status, summary, runId = "workspace:current") => {
|
|
270
|
+
suppressWatch();
|
|
271
|
+
appendStatusEvent(workspaceRoot, {
|
|
272
|
+
trace_id: hubTraceId,
|
|
273
|
+
source_module: hubSourceModule,
|
|
274
|
+
event_type: eventType,
|
|
275
|
+
status,
|
|
276
|
+
payload: {
|
|
277
|
+
summary,
|
|
278
|
+
run_id: runId,
|
|
279
|
+
workspace_root: workspaceRoot,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const applyCommandResult = async (result, { source = "keyboard" } = {}) => {
|
|
285
|
+
if (!result) return false;
|
|
286
|
+
const before = captureRuntimeState();
|
|
287
|
+
if (result.quit) return true;
|
|
288
|
+
if (result.patch?.view) state.view = result.patch.view;
|
|
289
|
+
if (result.patch?.compareTarget !== undefined) state.compareTarget = result.patch.compareTarget;
|
|
290
|
+
if (result.patch?.selectedIndexDelta) {
|
|
291
|
+
const total = state.projected.runs.length || 1;
|
|
292
|
+
state.selectedIndex = (state.selectedIndex + result.patch.selectedIndexDelta + total) % total;
|
|
293
|
+
}
|
|
294
|
+
if (result.patch?.selectTarget) {
|
|
295
|
+
const raw = result.patch.selectTarget;
|
|
296
|
+
const numeric = Number(raw);
|
|
297
|
+
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= state.projected.runs.length) {
|
|
298
|
+
state.selectedIndex = numeric - 1;
|
|
299
|
+
} else {
|
|
300
|
+
const match = state.projected.runs.findIndex((run) => run.run.run_id === raw);
|
|
301
|
+
if (match >= 0) state.selectedIndex = match;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (result.action) {
|
|
305
|
+
await executeStoreAction(result.action);
|
|
306
|
+
}
|
|
307
|
+
if (result.refresh) refresh();
|
|
308
|
+
if (result.message) state.message = result.message;
|
|
309
|
+
recordInteractiveTransition(before, {
|
|
310
|
+
source,
|
|
311
|
+
refreshReason: result.refresh ? "command_result" : undefined,
|
|
312
|
+
commandActionType: result.action?.type,
|
|
313
|
+
});
|
|
314
|
+
return false;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const runFeedCommands = async (lines) => {
|
|
318
|
+
if (!lines.length) return;
|
|
319
|
+
state.mode = "feed";
|
|
320
|
+
rerender();
|
|
321
|
+
for (const rawLine of lines) {
|
|
322
|
+
const trimmed = rawLine.trim();
|
|
323
|
+
if (!trimmed) continue;
|
|
324
|
+
const shouldQuit = await applyCommandResult(executeCommand(trimmed.replace(/^\//, ""), state), { source: "feed" });
|
|
325
|
+
rerender();
|
|
326
|
+
if (shouldQuit) return true;
|
|
327
|
+
}
|
|
328
|
+
state.mode = "normal";
|
|
329
|
+
state.message = "Feed complete. Keyboard control restored.";
|
|
330
|
+
rerender();
|
|
331
|
+
return false;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
altBufferOn();
|
|
335
|
+
rerender();
|
|
336
|
+
await emitHubEvent("HUB_SESSION_STARTED", "started", "Vericify hub started.");
|
|
337
|
+
refresh();
|
|
338
|
+
|
|
339
|
+
const stopWatching = createDebouncedWatch(watchedPaths, () => {
|
|
340
|
+
if (Date.now() < suppressWatchUntil) return;
|
|
341
|
+
const before = captureRuntimeState();
|
|
342
|
+
state.message = "Workspace artifacts changed.";
|
|
343
|
+
refresh();
|
|
344
|
+
recordInteractiveTransition(before, {
|
|
345
|
+
source: "watch",
|
|
346
|
+
refreshReason: "workspace_change",
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
let keyboardInput = processStdin;
|
|
351
|
+
const setupKeyboard = (inputStream) => {
|
|
352
|
+
keyboardInput = inputStream;
|
|
353
|
+
if (!feedFile) runtimeRecorder.setInteractive(true);
|
|
354
|
+
emitKeypressEvents(keyboardInput);
|
|
355
|
+
if (keyboardInput.isTTY) keyboardInput.setRawMode(true);
|
|
356
|
+
keyboardInput.resume();
|
|
357
|
+
keyboardInput.on("keypress", onKeypress);
|
|
358
|
+
if (runtimeRecorder.recordInitialSelection(selectedRunId(), state.view)) {
|
|
359
|
+
suppressWatch();
|
|
360
|
+
refresh();
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const teardownKeyboard = () => {
|
|
365
|
+
if (!keyboardInput) return;
|
|
366
|
+
keyboardInput.off("keypress", onKeypress);
|
|
367
|
+
if (keyboardInput.isTTY) keyboardInput.setRawMode(false);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const restoreKeyboardFromTty = () => {
|
|
371
|
+
if (processStdin.isTTY) {
|
|
372
|
+
setupKeyboard(processStdin);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const fd = openSync("/dev/tty", "r");
|
|
377
|
+
const ttyInput = new tty.ReadStream(fd);
|
|
378
|
+
setupKeyboard(ttyInput);
|
|
379
|
+
} catch {
|
|
380
|
+
state.message = "Feed complete. No interactive TTY available.";
|
|
381
|
+
rerender();
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const exit = async () => {
|
|
386
|
+
try {
|
|
387
|
+
await emitHubEvent("HUB_SESSION_STOPPED", "done", "Vericify hub stopped.");
|
|
388
|
+
} catch {
|
|
389
|
+
// ignore shutdown write failures
|
|
390
|
+
}
|
|
391
|
+
teardownKeyboard();
|
|
392
|
+
stopWatching();
|
|
393
|
+
altBufferOff();
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const onKeypress = async (str, key = {}) => {
|
|
397
|
+
if (key.sequence === "\u001a") {
|
|
398
|
+
state.message = "Suspending Vericify.";
|
|
399
|
+
rerender();
|
|
400
|
+
teardownKeyboard();
|
|
401
|
+
altBufferOff();
|
|
402
|
+
process.kill(process.pid, "SIGSTOP");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (state.mode === "command") {
|
|
406
|
+
if (key.name === "return") {
|
|
407
|
+
const command = state.commandBuffer;
|
|
408
|
+
state.commandHistory.push(command);
|
|
409
|
+
state.commandHistoryIndex = state.commandHistory.length;
|
|
410
|
+
state.commandBuffer = "";
|
|
411
|
+
state.mode = "normal";
|
|
412
|
+
const shouldQuit = await applyCommandResult(executeCommand(command, state), { source: "command" });
|
|
413
|
+
rerender();
|
|
414
|
+
if (shouldQuit) await exit();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (key.name === "escape") {
|
|
418
|
+
state.mode = "normal";
|
|
419
|
+
state.commandBuffer = "";
|
|
420
|
+
state.message = "Command cancelled.";
|
|
421
|
+
rerender();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (key.name === "backspace") {
|
|
425
|
+
state.commandBuffer = state.commandBuffer.slice(0, -1);
|
|
426
|
+
rerender();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (key.name === "up") {
|
|
430
|
+
state.commandHistoryIndex = Math.max(0, state.commandHistoryIndex - 1);
|
|
431
|
+
state.commandBuffer = state.commandHistory[state.commandHistoryIndex] ?? state.commandBuffer;
|
|
432
|
+
rerender();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (key.name === "down") {
|
|
436
|
+
state.commandHistoryIndex = Math.min(state.commandHistory.length, state.commandHistoryIndex + 1);
|
|
437
|
+
state.commandBuffer = state.commandHistory[state.commandHistoryIndex] ?? "";
|
|
438
|
+
rerender();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (key.name === "tab") {
|
|
442
|
+
state.commandBuffer = completeCommand(
|
|
443
|
+
state.commandBuffer,
|
|
444
|
+
state.projected.runs.map((run) => run.run.run_id)
|
|
445
|
+
);
|
|
446
|
+
rerender();
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (key.ctrl && key.name === "r") {
|
|
450
|
+
const query = state.commandBuffer.trim();
|
|
451
|
+
const match = [...state.commandHistory].reverse().find((item) => item.includes(query));
|
|
452
|
+
state.commandBuffer = match ?? state.commandBuffer;
|
|
453
|
+
state.message = match ? "History search matched a prior command." : "History search found no match.";
|
|
454
|
+
rerender();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (str) {
|
|
458
|
+
state.commandBuffer += str;
|
|
459
|
+
rerender();
|
|
460
|
+
}
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (key.name === "q" || (key.ctrl && key.name === "c")) {
|
|
465
|
+
await exit();
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (key.name === "j" || key.name === "down") {
|
|
469
|
+
const before = captureRuntimeState();
|
|
470
|
+
if (state.projected.runs.length > 0) {
|
|
471
|
+
state.selectedIndex = Math.min(state.projected.runs.length - 1, state.selectedIndex + 1);
|
|
472
|
+
}
|
|
473
|
+
rerender();
|
|
474
|
+
recordInteractiveTransition(before, { source: "keyboard" });
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (key.name === "k" || key.name === "up") {
|
|
478
|
+
const before = captureRuntimeState();
|
|
479
|
+
if (state.projected.runs.length > 0) {
|
|
480
|
+
state.selectedIndex = Math.max(0, state.selectedIndex - 1);
|
|
481
|
+
}
|
|
482
|
+
rerender();
|
|
483
|
+
recordInteractiveTransition(before, { source: "keyboard" });
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (key.name === "return") {
|
|
487
|
+
const before = captureRuntimeState();
|
|
488
|
+
state.view = state.view === "inspect" ? "hub" : "inspect";
|
|
489
|
+
rerender();
|
|
490
|
+
recordInteractiveTransition(before, { source: "keyboard" });
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (key.name === "c") {
|
|
494
|
+
const before = captureRuntimeState();
|
|
495
|
+
state.view = "compare";
|
|
496
|
+
state.compareTarget = bestCompareTarget(state.projected.runs[state.selectedIndex]);
|
|
497
|
+
rerender();
|
|
498
|
+
recordInteractiveTransition(before, { source: "keyboard" });
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (key.name === "y") {
|
|
502
|
+
const before = captureRuntimeState();
|
|
503
|
+
state.view = "history";
|
|
504
|
+
rerender();
|
|
505
|
+
recordInteractiveTransition(before, { source: "keyboard" });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (key.name === "h") {
|
|
509
|
+
const before = captureRuntimeState();
|
|
510
|
+
state.view = "hub";
|
|
511
|
+
rerender();
|
|
512
|
+
recordInteractiveTransition(before, { source: "keyboard" });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (key.name === "r") {
|
|
516
|
+
const before = captureRuntimeState();
|
|
517
|
+
refresh();
|
|
518
|
+
recordInteractiveTransition(before, {
|
|
519
|
+
source: "keyboard",
|
|
520
|
+
refreshReason: "manual_refresh",
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (str === "/") {
|
|
525
|
+
state.mode = "command";
|
|
526
|
+
state.commandBuffer = "";
|
|
527
|
+
rerender();
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
process.on("SIGCONT", () => {
|
|
532
|
+
altBufferOn();
|
|
533
|
+
restoreKeyboardFromTty();
|
|
534
|
+
state.message = "Resumed Vericify.";
|
|
535
|
+
suppressWatch();
|
|
536
|
+
appendStatusEvent(workspaceRoot, {
|
|
537
|
+
trace_id: hubTraceId,
|
|
538
|
+
source_module: hubSourceModule,
|
|
539
|
+
event_type: "HUB_SESSION_RESUMED",
|
|
540
|
+
status: "started",
|
|
541
|
+
payload: {
|
|
542
|
+
summary: "Vericify hub resumed.",
|
|
543
|
+
workspace_root: workspaceRoot,
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
refresh();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
if (feedFile) {
|
|
550
|
+
const lines = readFileSync(feedFile, "utf8").split(/\r?\n/);
|
|
551
|
+
const quit = await runFeedCommands(lines);
|
|
552
|
+
if (quit) {
|
|
553
|
+
await exit();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (!processStdin.isTTY) {
|
|
559
|
+
const rl = createInterface({ input: processStdin, terminal: false });
|
|
560
|
+
const lines = [];
|
|
561
|
+
for await (const lineValue of rl) lines.push(lineValue);
|
|
562
|
+
const quit = await runFeedCommands(lines);
|
|
563
|
+
if (quit) {
|
|
564
|
+
await exit();
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
restoreKeyboardFromTty();
|
|
568
|
+
} else {
|
|
569
|
+
setupKeyboard(processStdin);
|
|
570
|
+
}
|
|
571
|
+
}
|