@dadado/agent-kit-cli 5.15.1 → 5.17.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/dashboard/dashboard-data.mjs +69 -65
- package/dashboard/lib/guards.d.mts +5 -0
- package/dashboard/lib/guards.mjs +31 -0
- package/dashboard/lib/identity-probe.mjs +56 -0
- package/dashboard/lib/pipeline-cache.mjs +58 -0
- package/dashboard/lib/processes.mjs +71 -0
- package/dashboard/serve.mjs +43 -1
- package/dashboard/start-broadcast.mjs +2 -12
- package/dashboard/start.mjs +2 -13
- package/dist/add-HTYIFKMN.js +150 -0
- package/dist/chunk-2IPXQFJE.js +253 -0
- package/dist/chunk-2RDRMGD3.js +82 -0
- package/dist/chunk-2YU2H7I6.js +42 -0
- package/dist/chunk-4IWG5CVE.js +214 -0
- package/dist/chunk-5VCOWTTP.js +6 -0
- package/dist/chunk-77V75A7O.js +39 -0
- package/dist/chunk-7HDQEK6T.js +26 -0
- package/dist/chunk-AW74INUW.js +1456 -0
- package/dist/chunk-B73KQ5KX.js +972 -0
- package/dist/chunk-CBCW5R5J.js +484 -0
- package/dist/chunk-DE3LQFSJ.js +18 -0
- package/dist/chunk-DQA434Y4.js +106 -0
- package/dist/chunk-F4VMYR7U.js +318 -0
- package/dist/chunk-F6C3GFKV.js +57 -0
- package/dist/chunk-HFUH3HVS.js +282 -0
- package/dist/chunk-HS7LVVLO.js +74 -0
- package/dist/chunk-IHROBTNM.js +135 -0
- package/dist/chunk-KD2MYBB6.js +213 -0
- package/dist/chunk-MAPOQEA2.js +43 -0
- package/dist/chunk-N3HTJVBR.js +341 -0
- package/dist/chunk-NQ7IQ5XT.js +26 -0
- package/dist/chunk-OUQZC744.js +1315 -0
- package/dist/chunk-PCCQUWE5.js +817 -0
- package/dist/chunk-PNFF6OU3.js +273 -0
- package/dist/chunk-PNG7LO2F.js +53 -0
- package/dist/chunk-QTON2GQX.js +203 -0
- package/dist/chunk-S6GMFDKV.js +50 -0
- package/dist/chunk-SMGFN3Z4.js +503 -0
- package/dist/chunk-STGFBAYT.js +641 -0
- package/dist/chunk-TFZ2JUXH.js +1081 -0
- package/dist/chunk-VY7MTPJ2.js +205 -0
- package/dist/chunk-W24CMQ32.js +525 -0
- package/dist/chunk-WW3YZKHW.js +165 -0
- package/dist/chunk-ZB43YMHW.js +20 -0
- package/dist/chunk-ZCTSUQRR.js +874 -0
- package/dist/chunk-ZXKZBTXE.js +17 -0
- package/dist/contribute-C6AW2P6J.js +367 -0
- package/dist/cursor-awareness-KTL2RGKD.js +77 -0
- package/dist/dashboard-YSNUGBFY.js +21 -0
- package/dist/dashboard-broadcast-M4CMW7RX.js +119 -0
- package/dist/diff-VHNR62SI.js +74 -0
- package/dist/doctor-PET2HRRJ.js +236 -0
- package/dist/dogfood-KKFVRWT6.js +366 -0
- package/dist/guard-EHXF76ID.js +530 -0
- package/dist/handoff-3UI6VKVG.js +302 -0
- package/dist/hook-DYIMKJYL.js +1074 -0
- package/dist/index.js +62 -14068
- package/dist/init-Q4LBM6KT.js +127 -0
- package/dist/install-RHOS3OYZ.js +34 -0
- package/dist/mission-control-LJACEVVN.js +89 -0
- package/dist/monitors-QV2IS3MP.js +176 -0
- package/dist/plan-index-JMC7Z4DW.js +44 -0
- package/dist/public-inbound-radar-WP3XF7GS.js +68 -0
- package/dist/remote-issues-SZ4HDQL5.js +44 -0
- package/dist/run-I3BFGULT.js +243 -0
- package/dist/run-plan-FO7CL4M3.js +118 -0
- package/dist/scan-NI2SWJ2E.js +33 -0
- package/dist/setup-global-M42GEODI.js +340 -0
- package/dist/status-XPVQVQMX.js +150 -0
- package/dist/update-FJ2EBZVO.js +283 -0
- package/dist/validate-V2CW7J4B.js +201 -0
- package/package.json +3 -3
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REPO_ROOT_ENV,
|
|
3
|
+
bundledDashboardCandidates,
|
|
4
|
+
findDashboardStart
|
|
5
|
+
} from "./chunk-HFUH3HVS.js";
|
|
6
|
+
import {
|
|
7
|
+
HELMET_ACCENT,
|
|
8
|
+
HELMET_FILL,
|
|
9
|
+
HELMET_OUTLINE,
|
|
10
|
+
LABEL_MUTED,
|
|
11
|
+
SPACE_MARKS,
|
|
12
|
+
STATUS_ERR,
|
|
13
|
+
STATUS_INFO,
|
|
14
|
+
STATUS_OK,
|
|
15
|
+
STATUS_WARN,
|
|
16
|
+
shouldUseVisualMotion,
|
|
17
|
+
shouldUseWelcomeColor,
|
|
18
|
+
spinnerFrame,
|
|
19
|
+
withKoloristColor,
|
|
20
|
+
wrapNarrow
|
|
21
|
+
} from "./chunk-QTON2GQX.js";
|
|
22
|
+
|
|
23
|
+
// src/mission-control/snapshot.ts
|
|
24
|
+
import { execFile } from "child_process";
|
|
25
|
+
import { access } from "fs/promises";
|
|
26
|
+
import path from "path";
|
|
27
|
+
|
|
28
|
+
// ../../dashboard/lib/live-refresh.mjs
|
|
29
|
+
import { join, resolve } from "path";
|
|
30
|
+
var PERIODIC_REFRESH_MS = 15e3;
|
|
31
|
+
var SNAPSHOT_REPO_SOURCE_RELS = Object.freeze([
|
|
32
|
+
".cursor/plans",
|
|
33
|
+
".cursor/HANDOFF.md",
|
|
34
|
+
".cursor/agents",
|
|
35
|
+
".cursor/commands",
|
|
36
|
+
".cursor/memory",
|
|
37
|
+
".cursor/context/config.json",
|
|
38
|
+
".cursor/context/current",
|
|
39
|
+
".cursor/context/readiness.json",
|
|
40
|
+
".cursor/context/field-report-dismissals.json",
|
|
41
|
+
".cursor/context/mission-timing.json",
|
|
42
|
+
".cursor/context/flight-log.json",
|
|
43
|
+
".cursor/context/field-report-cadence.json",
|
|
44
|
+
".cursor/skills",
|
|
45
|
+
"package.json"
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
// src/mission-control/snapshot.ts
|
|
49
|
+
var DEFAULT_DATA_TIMEOUT_MS = 6e4;
|
|
50
|
+
function dataScriptTimeoutMs(env = process.env) {
|
|
51
|
+
const raw = env.AGENT_KIT_DASHBOARD_DATA_TIMEOUT_MS;
|
|
52
|
+
const n = raw != null && raw !== "" ? Number(raw) : DEFAULT_DATA_TIMEOUT_MS;
|
|
53
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_DATA_TIMEOUT_MS;
|
|
54
|
+
}
|
|
55
|
+
async function firstExisting(candidates) {
|
|
56
|
+
for (const candidate of candidates) {
|
|
57
|
+
try {
|
|
58
|
+
await access(candidate);
|
|
59
|
+
return path.resolve(candidate);
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
async function findDashboardDataScript(cwd, env = process.env, options = {}) {
|
|
66
|
+
const startPath = await findDashboardStart(cwd, env, options);
|
|
67
|
+
if (startPath) {
|
|
68
|
+
const sibling = path.join(path.dirname(startPath), "dashboard-data.mjs");
|
|
69
|
+
try {
|
|
70
|
+
await access(sibling);
|
|
71
|
+
return sibling;
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return firstExisting(
|
|
76
|
+
bundledDashboardCandidates("dashboard-data.mjs", options.moduleUrl ?? import.meta.url)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
async function loadDashboardSnapshot(opts) {
|
|
80
|
+
const env = opts.env ?? process.env;
|
|
81
|
+
const timeout = dataScriptTimeoutMs(env);
|
|
82
|
+
const run = opts.execFileFn ?? execFile;
|
|
83
|
+
const snapshotRoot = path.resolve(opts.snapshotRoot);
|
|
84
|
+
return new Promise((resolve2) => {
|
|
85
|
+
run(
|
|
86
|
+
process.execPath,
|
|
87
|
+
[opts.dataScript],
|
|
88
|
+
{
|
|
89
|
+
cwd: snapshotRoot,
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
92
|
+
timeout,
|
|
93
|
+
env: {
|
|
94
|
+
...env,
|
|
95
|
+
[REPO_ROOT_ENV]: snapshotRoot
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
(err, stdout) => {
|
|
99
|
+
if (err) {
|
|
100
|
+
const killed = Boolean(
|
|
101
|
+
err && typeof err === "object" && "killed" in err && err.killed
|
|
102
|
+
);
|
|
103
|
+
const message = killed ? `dashboard-data.mjs timed out after ${timeout}ms (set AGENT_KIT_DASHBOARD_DATA_TIMEOUT_MS to raise)` : err.message;
|
|
104
|
+
resolve2({ ok: false, error: message });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const parsed = JSON.parse(String(stdout ?? ""));
|
|
109
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
110
|
+
resolve2({ ok: false, error: "dashboard-data.mjs returned non-object JSON" });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
resolve2({ ok: true, snapshot: parsed });
|
|
114
|
+
} catch {
|
|
115
|
+
resolve2({ ok: false, error: "dashboard-data.mjs returned invalid JSON" });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/mission-control/view.ts
|
|
123
|
+
var CHECKLIST_CAP = 8;
|
|
124
|
+
var CREW_CAP = 12;
|
|
125
|
+
var EARLIER_CAP = 4;
|
|
126
|
+
var WARNING_CAP = 5;
|
|
127
|
+
function asRecord(value) {
|
|
128
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
function asString(value) {
|
|
132
|
+
if (typeof value !== "string") return null;
|
|
133
|
+
const trimmed = value.trim();
|
|
134
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
135
|
+
}
|
|
136
|
+
function compactTodo(value) {
|
|
137
|
+
const rec = asRecord(value);
|
|
138
|
+
if (!rec) return asString(value);
|
|
139
|
+
return asString(rec.id) ?? asString(rec.content) ?? asString(rec.label);
|
|
140
|
+
}
|
|
141
|
+
function progressLabel(progress) {
|
|
142
|
+
const rec = asRecord(progress);
|
|
143
|
+
if (!rec) return "0 of 0 complete";
|
|
144
|
+
const labeled2 = asString(rec.label);
|
|
145
|
+
if (labeled2) return labeled2;
|
|
146
|
+
const completed = typeof rec.completed === "number" ? rec.completed : 0;
|
|
147
|
+
const total = typeof rec.total === "number" ? rec.total : 0;
|
|
148
|
+
return `${completed} of ${total} complete`;
|
|
149
|
+
}
|
|
150
|
+
function emptyMission() {
|
|
151
|
+
return {
|
|
152
|
+
status: "idle",
|
|
153
|
+
planFile: null,
|
|
154
|
+
mode: null,
|
|
155
|
+
progressLabel: "0 of 0 complete",
|
|
156
|
+
currentTodo: null,
|
|
157
|
+
nextTodo: null,
|
|
158
|
+
gaps: null
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function emptyFlightLog() {
|
|
162
|
+
return { now: null, nowKind: null, earlier: [], warnings: [] };
|
|
163
|
+
}
|
|
164
|
+
function buildMcTuiView(snapshot, error = null) {
|
|
165
|
+
if (error) {
|
|
166
|
+
return {
|
|
167
|
+
mission: emptyMission(),
|
|
168
|
+
flightLog: emptyFlightLog(),
|
|
169
|
+
checklist: [],
|
|
170
|
+
crewMonitor: [],
|
|
171
|
+
error
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const mc = asRecord(snapshot?.missionControl);
|
|
175
|
+
const now = asRecord(mc?.now);
|
|
176
|
+
const mission = now ? {
|
|
177
|
+
status: asString(now.status) ?? "idle",
|
|
178
|
+
planFile: asString(now.planFile),
|
|
179
|
+
mode: asString(now.mode),
|
|
180
|
+
progressLabel: progressLabel(now.progress),
|
|
181
|
+
currentTodo: compactTodo(now.currentTodo),
|
|
182
|
+
nextTodo: compactTodo(now.nextTodo),
|
|
183
|
+
gaps: asString(now.gaps)
|
|
184
|
+
} : emptyMission();
|
|
185
|
+
const flight = asRecord(mc?.flightLog);
|
|
186
|
+
const earlierRaw = Array.isArray(flight?.past) ? flight.past : [];
|
|
187
|
+
const warningRaw = Array.isArray(flight?.warnings) ? flight.warnings : [];
|
|
188
|
+
const flightLog = flight ? {
|
|
189
|
+
now: asString(flight.current),
|
|
190
|
+
nowKind: asString(flight.currentKind),
|
|
191
|
+
earlier: earlierRaw.map((entry) => asString(asRecord(entry)?.text) ?? asString(entry)).filter((text) => Boolean(text)).slice(0, EARLIER_CAP),
|
|
192
|
+
warnings: warningRaw.map((entry) => asString(asRecord(entry)?.text) ?? asString(asRecord(entry)?.title)).filter((text) => Boolean(text)).slice(0, WARNING_CAP)
|
|
193
|
+
} : emptyFlightLog();
|
|
194
|
+
const plansRaw = Array.isArray(mc?.plans) ? mc.plans : [];
|
|
195
|
+
const checklist = plansRaw.map((plan) => {
|
|
196
|
+
const rec = asRecord(plan);
|
|
197
|
+
if (!rec) return null;
|
|
198
|
+
const file = asString(rec.file);
|
|
199
|
+
if (!file) return null;
|
|
200
|
+
const lifecycle = asString(rec.lifecycle) ?? "unknown";
|
|
201
|
+
if (lifecycle === "completed") return null;
|
|
202
|
+
return {
|
|
203
|
+
file,
|
|
204
|
+
lifecycle,
|
|
205
|
+
progressLabel: progressLabel(rec.progress),
|
|
206
|
+
currentTodo: compactTodo(rec.currentTodo)
|
|
207
|
+
};
|
|
208
|
+
}).filter((row) => Boolean(row)).slice(0, CHECKLIST_CAP);
|
|
209
|
+
const feedCap = typeof mc?.monitorFeedCap === "number" && mc.monitorFeedCap > 0 ? Math.min(CREW_CAP, Math.floor(mc.monitorFeedCap)) : CREW_CAP;
|
|
210
|
+
const activityRaw = Array.isArray(mc?.activity) ? mc.activity : [];
|
|
211
|
+
const crewMonitor = activityRaw.map((event) => {
|
|
212
|
+
const rec = asRecord(event);
|
|
213
|
+
if (!rec) return null;
|
|
214
|
+
const label = asString(rec.label) ?? asString(rec.labelFull);
|
|
215
|
+
if (!label) return null;
|
|
216
|
+
return { kind: asString(rec.kind) ?? "activity", label };
|
|
217
|
+
}).filter((row) => Boolean(row)).slice(0, feedCap);
|
|
218
|
+
return { mission, flightLog, checklist, crewMonitor, error: null };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/mission-control/render.ts
|
|
222
|
+
import { trueColor } from "kolorist";
|
|
223
|
+
var DEFAULT_COLUMNS = 80;
|
|
224
|
+
var MIN_COLUMNS = 40;
|
|
225
|
+
var INNER_PAD = 2;
|
|
226
|
+
var ESC = "\x1B";
|
|
227
|
+
var ANSI_RE = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
|
|
228
|
+
var STATUS_OK_KEYS = /* @__PURE__ */ new Set([
|
|
229
|
+
"executing",
|
|
230
|
+
"completed",
|
|
231
|
+
"ok",
|
|
232
|
+
"run_plan",
|
|
233
|
+
"success",
|
|
234
|
+
"done",
|
|
235
|
+
"started",
|
|
236
|
+
"merged"
|
|
237
|
+
]);
|
|
238
|
+
var STATUS_WARN_KEYS = /* @__PURE__ */ new Set([
|
|
239
|
+
"awaiting",
|
|
240
|
+
"awaiting_user",
|
|
241
|
+
"warning",
|
|
242
|
+
"handoff",
|
|
243
|
+
"stopped",
|
|
244
|
+
"residual"
|
|
245
|
+
]);
|
|
246
|
+
var STATUS_ERR_KEYS = /* @__PURE__ */ new Set(["error", "failed", "fail", "blocked", "incomplete", "fix"]);
|
|
247
|
+
var STATUS_INFO_KEYS = /* @__PURE__ */ new Set([
|
|
248
|
+
"backlog",
|
|
249
|
+
"queued",
|
|
250
|
+
"parked",
|
|
251
|
+
"delivery",
|
|
252
|
+
"memory",
|
|
253
|
+
"activity",
|
|
254
|
+
"advice",
|
|
255
|
+
"prompt"
|
|
256
|
+
]);
|
|
257
|
+
function hexPaint(hex, text, color) {
|
|
258
|
+
if (!color) return text;
|
|
259
|
+
return withKoloristColor(
|
|
260
|
+
() => trueColor(
|
|
261
|
+
Number.parseInt(hex.slice(1, 3), 16),
|
|
262
|
+
Number.parseInt(hex.slice(3, 5), 16),
|
|
263
|
+
Number.parseInt(hex.slice(5, 7), 16)
|
|
264
|
+
)(text)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
function muted(text, color) {
|
|
268
|
+
return hexPaint(LABEL_MUTED, text, color);
|
|
269
|
+
}
|
|
270
|
+
function statusTokenHex(token) {
|
|
271
|
+
const key = token.trim().toLowerCase();
|
|
272
|
+
if (STATUS_OK_KEYS.has(key)) return STATUS_OK;
|
|
273
|
+
if (STATUS_WARN_KEYS.has(key)) return STATUS_WARN;
|
|
274
|
+
if (STATUS_ERR_KEYS.has(key)) return STATUS_ERR;
|
|
275
|
+
if (STATUS_INFO_KEYS.has(key)) return STATUS_INFO;
|
|
276
|
+
return HELMET_OUTLINE;
|
|
277
|
+
}
|
|
278
|
+
function paintStatus(text, color) {
|
|
279
|
+
return hexPaint(statusTokenHex(text), text, color);
|
|
280
|
+
}
|
|
281
|
+
function visibleLength(text) {
|
|
282
|
+
return text.replace(ANSI_RE, "").length;
|
|
283
|
+
}
|
|
284
|
+
function clipToVisible(text, max) {
|
|
285
|
+
if (visibleLength(text) <= max) return text;
|
|
286
|
+
return text.replace(ANSI_RE, "").slice(0, max);
|
|
287
|
+
}
|
|
288
|
+
function box(title, bodyLines, width, color) {
|
|
289
|
+
const inner = Math.max(8, width - 2);
|
|
290
|
+
const label = ` ${title} `;
|
|
291
|
+
const dashCount = Math.max(1, inner - label.length);
|
|
292
|
+
const top = `\u250C${label}${"\u2500".repeat(dashCount)}\u2510`;
|
|
293
|
+
const bottom = `\u2514${"\u2500".repeat(inner)}\u2518`;
|
|
294
|
+
const paintedTop = hexPaint(HELMET_FILL, top, color);
|
|
295
|
+
const paintedBottom = hexPaint(HELMET_OUTLINE, bottom, color);
|
|
296
|
+
const max = inner - INNER_PAD;
|
|
297
|
+
const rows = bodyLines.map((line) => {
|
|
298
|
+
const clipped = clipToVisible(line, max);
|
|
299
|
+
const pad = " ".repeat(Math.max(0, max - visibleLength(clipped)));
|
|
300
|
+
const content = ` ${clipped}${pad} `;
|
|
301
|
+
return `${hexPaint(HELMET_OUTLINE, "\u2502", color)}${content}${hexPaint(HELMET_OUTLINE, "\u2502", color)}`;
|
|
302
|
+
});
|
|
303
|
+
return [paintedTop, ...rows, paintedBottom];
|
|
304
|
+
}
|
|
305
|
+
function lineOrQuiet(text, fallback) {
|
|
306
|
+
const trimmed = text?.trim();
|
|
307
|
+
return trimmed ? trimmed : fallback;
|
|
308
|
+
}
|
|
309
|
+
function labeled(label, value, color, valueHex = HELMET_OUTLINE) {
|
|
310
|
+
return `${hexPaint(LABEL_MUTED, label, color)}${hexPaint(valueHex, value, color)}`;
|
|
311
|
+
}
|
|
312
|
+
function paintNowLine(flight, color) {
|
|
313
|
+
if (!flight.now) {
|
|
314
|
+
return labeled("NOW ", "All clear", color, STATUS_OK);
|
|
315
|
+
}
|
|
316
|
+
const kind = flight.nowKind?.trim();
|
|
317
|
+
const prefix = kind ? `${hexPaint(LABEL_MUTED, "NOW ", color)}${hexPaint(statusTokenHex(kind), `(${kind})`, color)}${hexPaint(HELMET_OUTLINE, " ", color)}` : hexPaint(LABEL_MUTED, "NOW ", color);
|
|
318
|
+
return `${prefix}${hexPaint(HELMET_OUTLINE, flight.now, color)}`;
|
|
319
|
+
}
|
|
320
|
+
function renderMcTui(view, opts = {}) {
|
|
321
|
+
const color = shouldUseWelcomeColor(opts);
|
|
322
|
+
const motion = shouldUseVisualMotion(opts);
|
|
323
|
+
const columns = Math.max(
|
|
324
|
+
MIN_COLUMNS,
|
|
325
|
+
Math.floor(opts.columns ?? process.stdout.columns ?? DEFAULT_COLUMNS)
|
|
326
|
+
);
|
|
327
|
+
const mark = motion ? spinnerFrame(opts.frameIndex ?? 0) : SPACE_MARKS.tick;
|
|
328
|
+
const header = hexPaint(
|
|
329
|
+
HELMET_ACCENT,
|
|
330
|
+
`${mark} Mission Control ${SPACE_MARKS.star} terminal view (web dashboard unchanged)`,
|
|
331
|
+
color
|
|
332
|
+
);
|
|
333
|
+
const missionBody = view.error ? [
|
|
334
|
+
hexPaint(
|
|
335
|
+
STATUS_ERR,
|
|
336
|
+
wrapNarrow(view.error, columns - 4).split("\n")[0] ?? view.error,
|
|
337
|
+
color
|
|
338
|
+
)
|
|
339
|
+
] : [
|
|
340
|
+
`${hexPaint(LABEL_MUTED, "status ", color)}${paintStatus(view.mission.status, color)}${view.mission.mode ? hexPaint(HELMET_OUTLINE, ` \xB7 ${view.mission.mode}`, color) : ""}`,
|
|
341
|
+
labeled("plan ", lineOrQuiet(view.mission.planFile, "none"), color),
|
|
342
|
+
labeled("todos ", view.mission.progressLabel, color),
|
|
343
|
+
labeled("now ", lineOrQuiet(view.mission.currentTodo, "none"), color),
|
|
344
|
+
labeled("next ", lineOrQuiet(view.mission.nextTodo, "none"), color)
|
|
345
|
+
];
|
|
346
|
+
const flightBody = [
|
|
347
|
+
paintNowLine(view.flightLog, color),
|
|
348
|
+
...view.flightLog.warnings.map((w) => labeled("warn ", w, color, STATUS_WARN)),
|
|
349
|
+
...view.flightLog.earlier.length > 0 ? view.flightLog.earlier.map((t) => labeled("Earlier ", t, color)) : [muted("Earlier none", color)]
|
|
350
|
+
];
|
|
351
|
+
const checklistBody = view.checklist.length === 0 ? [muted("no open plans", color)] : view.checklist.map((row) => {
|
|
352
|
+
const todo = row.currentTodo ? ` \xB7 ${row.currentTodo}` : "";
|
|
353
|
+
return `${hexPaint(HELMET_OUTLINE, `${row.file} `, color)}${paintStatus(row.lifecycle, color)}${hexPaint(HELMET_OUTLINE, ` ${row.progressLabel}${todo}`, color)}`;
|
|
354
|
+
});
|
|
355
|
+
const crewBody = view.crewMonitor.length === 0 ? [muted("quiet", color)] : view.crewMonitor.map(
|
|
356
|
+
(row) => `${paintStatus(row.kind, color)}${hexPaint(HELMET_OUTLINE, ` ${row.label}`, color)}`
|
|
357
|
+
);
|
|
358
|
+
const panels = [
|
|
359
|
+
...box("Mission", missionBody, columns, color),
|
|
360
|
+
...box("Flight Log", flightBody, columns, color),
|
|
361
|
+
...box("Checklist", checklistBody, columns, color),
|
|
362
|
+
...box("Crew Monitor", crewBody, columns, color)
|
|
363
|
+
];
|
|
364
|
+
const footer = muted("Third surface \xB7 local snapshot \xB7 q / Ctrl-C to quit live view", color);
|
|
365
|
+
return [header, ...panels, footer].join("\n");
|
|
366
|
+
}
|
|
367
|
+
function shouldLiveRefresh(opts) {
|
|
368
|
+
if (opts.once) return false;
|
|
369
|
+
const env = opts.env ?? process.env;
|
|
370
|
+
if (env.CI != null && env.CI !== "") return false;
|
|
371
|
+
const tty = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
372
|
+
return tty;
|
|
373
|
+
}
|
|
374
|
+
function renderProgressBar(opts) {
|
|
375
|
+
const width = Math.max(4, Math.floor(opts.width ?? 20));
|
|
376
|
+
const total = Math.max(0, Math.floor(opts.total));
|
|
377
|
+
const done = Math.min(total, Math.max(0, Math.floor(opts.done)));
|
|
378
|
+
const filled = total === 0 ? 0 : Math.round(done / total * width);
|
|
379
|
+
const color = opts.color ?? false;
|
|
380
|
+
const bar = `${hexPaint(HELMET_ACCENT, "\u2588".repeat(filled), color)}${hexPaint(
|
|
381
|
+
LABEL_MUTED,
|
|
382
|
+
"\u2591".repeat(width - filled),
|
|
383
|
+
color
|
|
384
|
+
)}`;
|
|
385
|
+
return `${hexPaint(HELMET_OUTLINE, "[", color)}${bar}${hexPaint(HELMET_OUTLINE, "]", color)} ${hexPaint(
|
|
386
|
+
HELMET_OUTLINE,
|
|
387
|
+
`${done}/${total}`,
|
|
388
|
+
color
|
|
389
|
+
)}`;
|
|
390
|
+
}
|
|
391
|
+
var ScrollRegion = class {
|
|
392
|
+
lines = [];
|
|
393
|
+
offset = 0;
|
|
394
|
+
capacity;
|
|
395
|
+
constructor(capacity = 500) {
|
|
396
|
+
this.capacity = Math.max(1, Math.floor(capacity));
|
|
397
|
+
}
|
|
398
|
+
get length() {
|
|
399
|
+
return this.lines.length;
|
|
400
|
+
}
|
|
401
|
+
get scrolledUp() {
|
|
402
|
+
return this.offset;
|
|
403
|
+
}
|
|
404
|
+
push(line) {
|
|
405
|
+
this.lines.push(line);
|
|
406
|
+
if (this.lines.length > this.capacity) {
|
|
407
|
+
this.lines.splice(0, this.lines.length - this.capacity);
|
|
408
|
+
}
|
|
409
|
+
if (this.offset > 0) this.offset = Math.min(this.offset, Math.max(0, this.lines.length - 1));
|
|
410
|
+
}
|
|
411
|
+
scrollUp(n = 1) {
|
|
412
|
+
this.offset = Math.min(Math.max(0, this.lines.length - 1), this.offset + Math.max(0, n));
|
|
413
|
+
}
|
|
414
|
+
scrollDown(n = 1) {
|
|
415
|
+
this.offset = Math.max(0, this.offset - Math.max(0, n));
|
|
416
|
+
}
|
|
417
|
+
scrollToBottom() {
|
|
418
|
+
this.offset = 0;
|
|
419
|
+
}
|
|
420
|
+
/** The last `height` lines (minus the scroll offset). */
|
|
421
|
+
viewport(height) {
|
|
422
|
+
const h = Math.max(0, Math.floor(height));
|
|
423
|
+
const end = Math.max(0, this.lines.length - this.offset);
|
|
424
|
+
const start = Math.max(0, end - h);
|
|
425
|
+
return { lines: this.lines.slice(start, end), above: start, below: this.lines.length - end };
|
|
426
|
+
}
|
|
427
|
+
all() {
|
|
428
|
+
return this.lines;
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
var KEY_LEFT = `${ESC}[D`;
|
|
432
|
+
var KEY_RIGHT = `${ESC}[C`;
|
|
433
|
+
var KEY_HOME_SET = /* @__PURE__ */ new Set([`${ESC}[H`, `${ESC}[1~`, `${ESC}OH`]);
|
|
434
|
+
var KEY_END_SET = /* @__PURE__ */ new Set([`${ESC}[F`, `${ESC}[4~`, `${ESC}OF`]);
|
|
435
|
+
var KEY_DELETE = `${ESC}[3~`;
|
|
436
|
+
function splitKeys(chunk) {
|
|
437
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
438
|
+
const keys = [];
|
|
439
|
+
let i = 0;
|
|
440
|
+
while (i < text.length) {
|
|
441
|
+
const ch = text[i] ?? "";
|
|
442
|
+
if (ch === ESC && i + 1 < text.length) {
|
|
443
|
+
const next = text[i + 1];
|
|
444
|
+
if (next === "[" || next === "O") {
|
|
445
|
+
let j = i + 2;
|
|
446
|
+
while (j < text.length && !/[A-Za-z~]/.test(text[j] ?? "")) j += 1;
|
|
447
|
+
keys.push(text.slice(i, Math.min(text.length, j + 1)));
|
|
448
|
+
i = j + 1;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
keys.push(ch);
|
|
453
|
+
i += 1;
|
|
454
|
+
}
|
|
455
|
+
return keys;
|
|
456
|
+
}
|
|
457
|
+
var InputLine = class {
|
|
458
|
+
buffer = "";
|
|
459
|
+
cursorPos = 0;
|
|
460
|
+
get value() {
|
|
461
|
+
return this.buffer;
|
|
462
|
+
}
|
|
463
|
+
get cursor() {
|
|
464
|
+
return this.cursorPos;
|
|
465
|
+
}
|
|
466
|
+
reset() {
|
|
467
|
+
this.buffer = "";
|
|
468
|
+
this.cursorPos = 0;
|
|
469
|
+
}
|
|
470
|
+
handleKey(chunk) {
|
|
471
|
+
let result = "ignored";
|
|
472
|
+
for (const key of splitKeys(chunk)) {
|
|
473
|
+
const r = this.handleOne(key);
|
|
474
|
+
if (r === "submit" || r === "sigint" || r === "eof") return r;
|
|
475
|
+
if (r === "edit") result = "edit";
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
handleOne(key) {
|
|
480
|
+
if (key === "\r" || key === "\n") return "submit";
|
|
481
|
+
if (key === "") return "sigint";
|
|
482
|
+
if (key === "") return this.buffer.length === 0 ? "eof" : "ignored";
|
|
483
|
+
if (key === "\x7F" || key === "\b") {
|
|
484
|
+
if (this.cursorPos === 0) return "ignored";
|
|
485
|
+
this.buffer = this.buffer.slice(0, this.cursorPos - 1) + this.buffer.slice(this.cursorPos);
|
|
486
|
+
this.cursorPos -= 1;
|
|
487
|
+
return "edit";
|
|
488
|
+
}
|
|
489
|
+
if (key === KEY_DELETE) {
|
|
490
|
+
if (this.cursorPos >= this.buffer.length) return "ignored";
|
|
491
|
+
this.buffer = this.buffer.slice(0, this.cursorPos) + this.buffer.slice(this.cursorPos + 1);
|
|
492
|
+
return "edit";
|
|
493
|
+
}
|
|
494
|
+
if (key === KEY_LEFT) {
|
|
495
|
+
if (this.cursorPos === 0) return "ignored";
|
|
496
|
+
this.cursorPos -= 1;
|
|
497
|
+
return "edit";
|
|
498
|
+
}
|
|
499
|
+
if (key === KEY_RIGHT) {
|
|
500
|
+
if (this.cursorPos >= this.buffer.length) return "ignored";
|
|
501
|
+
this.cursorPos += 1;
|
|
502
|
+
return "edit";
|
|
503
|
+
}
|
|
504
|
+
if (KEY_HOME_SET.has(key) || key === "") {
|
|
505
|
+
this.cursorPos = 0;
|
|
506
|
+
return "edit";
|
|
507
|
+
}
|
|
508
|
+
if (KEY_END_SET.has(key) || key === "") {
|
|
509
|
+
this.cursorPos = this.buffer.length;
|
|
510
|
+
return "edit";
|
|
511
|
+
}
|
|
512
|
+
if (key === "") {
|
|
513
|
+
if (this.buffer.length === 0) return "ignored";
|
|
514
|
+
this.reset();
|
|
515
|
+
return "edit";
|
|
516
|
+
}
|
|
517
|
+
if (key === "") {
|
|
518
|
+
if (this.cursorPos === 0) return "ignored";
|
|
519
|
+
const head = this.buffer.slice(0, this.cursorPos).replace(/\S*\s*$/, "");
|
|
520
|
+
this.buffer = head + this.buffer.slice(this.cursorPos);
|
|
521
|
+
this.cursorPos = head.length;
|
|
522
|
+
return "edit";
|
|
523
|
+
}
|
|
524
|
+
if (key.startsWith(ESC)) return "ignored";
|
|
525
|
+
const code = key.codePointAt(0) ?? 0;
|
|
526
|
+
if (code < 32 || code === 127) return "ignored";
|
|
527
|
+
this.buffer = this.buffer.slice(0, this.cursorPos) + key + this.buffer.slice(this.cursorPos);
|
|
528
|
+
this.cursorPos += key.length;
|
|
529
|
+
return "edit";
|
|
530
|
+
}
|
|
531
|
+
/** `> text` with the cursor cell marked by reverse video (plain: `_` at the end). */
|
|
532
|
+
render(width, color, prompt = "> ") {
|
|
533
|
+
const max = Math.max(4, width - prompt.length - 1);
|
|
534
|
+
let start = 0;
|
|
535
|
+
if (this.cursorPos >= max) start = this.cursorPos - max + 1;
|
|
536
|
+
const visible = this.buffer.slice(start, start + max);
|
|
537
|
+
const at = this.cursorPos - start;
|
|
538
|
+
const before = visible.slice(0, at);
|
|
539
|
+
const cell = visible.slice(at, at + 1) || " ";
|
|
540
|
+
const after = visible.slice(at + 1);
|
|
541
|
+
const painted = color ? `${hexPaint(HELMET_OUTLINE, before, true)}${ESC}[7m${cell}${ESC}[0m${hexPaint(HELMET_OUTLINE, after, true)}` : `${before}${cell === " " && after === "" ? "_" : cell}${after}`;
|
|
542
|
+
return `${hexPaint(HELMET_ACCENT, prompt, color)}${painted}`;
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
var LIVE_MIN_LOG_ROWS = 3;
|
|
546
|
+
var LIVE_MAX_LOG_ROWS = 40;
|
|
547
|
+
var LIVE_CHECKLIST_CAP = 4;
|
|
548
|
+
function formatElapsed(ms) {
|
|
549
|
+
const s = Math.max(0, Math.floor(ms / 1e3));
|
|
550
|
+
const m = Math.floor(s / 60);
|
|
551
|
+
const h = Math.floor(m / 60);
|
|
552
|
+
if (h > 0) return `${h}h ${m % 60}m`;
|
|
553
|
+
if (m > 0) return `${m}m ${s % 60}s`;
|
|
554
|
+
return `${s}s`;
|
|
555
|
+
}
|
|
556
|
+
function liveLineHex(kind) {
|
|
557
|
+
switch (kind) {
|
|
558
|
+
case "tool":
|
|
559
|
+
return HELMET_ACCENT;
|
|
560
|
+
case "result":
|
|
561
|
+
return STATUS_OK;
|
|
562
|
+
case "error":
|
|
563
|
+
return STATUS_ERR;
|
|
564
|
+
case "rate_limit":
|
|
565
|
+
return STATUS_WARN;
|
|
566
|
+
case "hitl":
|
|
567
|
+
return HELMET_ACCENT;
|
|
568
|
+
case "hook":
|
|
569
|
+
case "prompt":
|
|
570
|
+
case "note":
|
|
571
|
+
return LABEL_MUTED;
|
|
572
|
+
default:
|
|
573
|
+
return HELMET_OUTLINE;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function renderMcLive(view, opts = {}) {
|
|
577
|
+
const color = shouldUseWelcomeColor(opts);
|
|
578
|
+
const motion = shouldUseVisualMotion(opts);
|
|
579
|
+
const columns = Math.max(
|
|
580
|
+
MIN_COLUMNS,
|
|
581
|
+
Math.floor(opts.columns ?? process.stdout.columns ?? DEFAULT_COLUMNS)
|
|
582
|
+
);
|
|
583
|
+
const rows = Math.max(12, Math.floor(opts.rows ?? process.stdout.rows ?? 24));
|
|
584
|
+
const inner = columns - 2 - INNER_PAD;
|
|
585
|
+
const now = opts.now ?? Date.now();
|
|
586
|
+
const running = view.crew.phase === "running" || view.crew.phase === "gate";
|
|
587
|
+
const mark = motion && running ? spinnerFrame(opts.frameIndex ?? 0) : SPACE_MARKS.tick;
|
|
588
|
+
const header = hexPaint(
|
|
589
|
+
HELMET_ACCENT,
|
|
590
|
+
`${mark} Mission Control ${SPACE_MARKS.star} live run ${SPACE_MARKS.tick} ${view.crew.backend ?? "agent"} ${SPACE_MARKS.tick} ${view.crew.phase}`,
|
|
591
|
+
color
|
|
592
|
+
);
|
|
593
|
+
const barWidth = Math.max(8, Math.min(24, Math.floor(inner / 3)));
|
|
594
|
+
const missionBody = [];
|
|
595
|
+
if (view.error) {
|
|
596
|
+
missionBody.push(
|
|
597
|
+
hexPaint(STATUS_ERR, wrapNarrow(view.error, inner).split("\n")[0] ?? "", color)
|
|
598
|
+
);
|
|
599
|
+
} else {
|
|
600
|
+
const m = view.mission;
|
|
601
|
+
missionBody.push(
|
|
602
|
+
`${labeled("plan ", lineOrQuiet(m.planFile, "none"), color)}${m.mode ? hexPaint(LABEL_MUTED, ` \xB7 ${m.mode}`, color) : ""}`
|
|
603
|
+
);
|
|
604
|
+
const todoTail = m.currentTodo ? ` now ${m.currentTodo}` : "";
|
|
605
|
+
missionBody.push(
|
|
606
|
+
`${hexPaint(LABEL_MUTED, "to-dos ", color)}${renderProgressBar({
|
|
607
|
+
done: m.todos.done,
|
|
608
|
+
total: m.todos.total,
|
|
609
|
+
width: barWidth,
|
|
610
|
+
color
|
|
611
|
+
})}${hexPaint(HELMET_OUTLINE, todoTail, color)}`
|
|
612
|
+
);
|
|
613
|
+
if (m.queue) {
|
|
614
|
+
const tail = m.queue.status ? ` ${m.queue.status}` : "";
|
|
615
|
+
missionBody.push(
|
|
616
|
+
`${hexPaint(LABEL_MUTED, "plans ", color)}${renderProgressBar({
|
|
617
|
+
done: m.queue.done,
|
|
618
|
+
total: m.queue.total,
|
|
619
|
+
width: barWidth,
|
|
620
|
+
color
|
|
621
|
+
})}${hexPaint(HELMET_OUTLINE, tail, color)}`
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
if (m.nextTodo) missionBody.push(labeled("next ", m.nextTodo, color));
|
|
625
|
+
}
|
|
626
|
+
if (view.gate) {
|
|
627
|
+
const g = view.gate;
|
|
628
|
+
const head = g.detection === "fallback" ? `${g.askId} (prose fallback)` : g.askId;
|
|
629
|
+
missionBody.push(
|
|
630
|
+
`${hexPaint(LABEL_MUTED, "gate ", color)}${hexPaint(STATUS_WARN, head, color)}${hexPaint(
|
|
631
|
+
LABEL_MUTED,
|
|
632
|
+
" \xB7 number or label replies; empty, skip, cancel or Ctrl-C stops",
|
|
633
|
+
color
|
|
634
|
+
)}`
|
|
635
|
+
);
|
|
636
|
+
g.labels.forEach((label, i) => {
|
|
637
|
+
missionBody.push(hexPaint(HELMET_OUTLINE, ` ${i + 1}. ${label}`, color));
|
|
638
|
+
});
|
|
639
|
+
missionBody.push(` ${view.gate.input.render(inner - 9, color)}`);
|
|
640
|
+
}
|
|
641
|
+
const checklistBody = view.checklist.length === 0 ? [muted("no queue", color)] : view.checklist.slice(0, LIVE_CHECKLIST_CAP).map((row) => {
|
|
642
|
+
const markOf = row.state === "done" ? SPACE_MARKS.star : row.state === "current" ? "\u25B6" : SPACE_MARKS.tick;
|
|
643
|
+
const hex = row.state === "done" ? STATUS_OK : row.state === "current" ? HELMET_ACCENT : LABEL_MUTED;
|
|
644
|
+
return hexPaint(hex, `${markOf} ${row.label}`, color);
|
|
645
|
+
});
|
|
646
|
+
const c = view.crew;
|
|
647
|
+
const crewLine1 = [
|
|
648
|
+
c.backend ?? "agent",
|
|
649
|
+
c.pid !== void 0 ? `pid ${c.pid}` : "no pid",
|
|
650
|
+
c.phase,
|
|
651
|
+
formatElapsed(now - c.startedAt),
|
|
652
|
+
c.costUsd !== void 0 ? `$${c.costUsd.toFixed(c.costUsd < 0.1 ? 4 : 2)}` : null,
|
|
653
|
+
c.turns !== void 0 ? `${c.turns} turn${c.turns === 1 ? "" : "s"}` : null,
|
|
654
|
+
`${c.toolCalls} tool call${c.toolCalls === 1 ? "" : "s"}`
|
|
655
|
+
].filter((part) => Boolean(part)).join(` ${SPACE_MARKS.tick} `);
|
|
656
|
+
const crewBody = [hexPaint(HELMET_OUTLINE, crewLine1, color)];
|
|
657
|
+
if (c.currentTool) {
|
|
658
|
+
crewBody.push(
|
|
659
|
+
`${hexPaint(LABEL_MUTED, "tool ", color)}${hexPaint(HELMET_ACCENT, `${c.currentTool.name} ${SPACE_MARKS.tick} ${c.currentTool.summary}`, color)}`
|
|
660
|
+
);
|
|
661
|
+
} else if (c.lastResult) {
|
|
662
|
+
crewBody.push(
|
|
663
|
+
`${hexPaint(LABEL_MUTED, "last ", color)}${hexPaint(c.lastResult.ok ? STATUS_OK : STATUS_ERR, `result ${c.lastResult.status}`, color)}`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
if (c.rateLimit) {
|
|
667
|
+
crewBody.push(
|
|
668
|
+
`${hexPaint(LABEL_MUTED, "limit ", color)}${hexPaint(c.rateLimit.severe ? STATUS_ERR : STATUS_WARN, c.rateLimit.line, color)}`
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
if (c.logPath) crewBody.push(labeled("log ", c.logPath, color, LABEL_MUTED));
|
|
672
|
+
let checklist = checklistBody;
|
|
673
|
+
let crew = crewBody;
|
|
674
|
+
const fixed = () => 1 + (missionBody.length + 2) + 2 + (checklist ? checklist.length + 2 : 0) + (crew.length + 2) + 1;
|
|
675
|
+
if (fixed() + LIVE_MIN_LOG_ROWS > rows) checklist = null;
|
|
676
|
+
if (fixed() + LIVE_MIN_LOG_ROWS > rows) crew = crew.slice(0, 1);
|
|
677
|
+
const logRows = Math.max(1, Math.min(LIVE_MAX_LOG_ROWS, rows - fixed()));
|
|
678
|
+
const viewport = view.flightLog.viewport(logRows);
|
|
679
|
+
const flightBody = viewport.lines.map(
|
|
680
|
+
(line) => hexPaint(liveLineHex(line.kind), line.text, color)
|
|
681
|
+
);
|
|
682
|
+
if (flightBody.length === 0) flightBody.push(muted("waiting for the agent stream", color));
|
|
683
|
+
const flightTitle = viewport.above > 0 || viewport.below > 0 ? `Flight Log ${SPACE_MARKS.tick} ${viewport.above} above${viewport.below > 0 ? ` ${SPACE_MARKS.tick} ${viewport.below} below` : ""}` : "Flight Log";
|
|
684
|
+
const panels = [
|
|
685
|
+
...box("Mission", missionBody, columns, color),
|
|
686
|
+
...box(flightTitle, flightBody, columns, color),
|
|
687
|
+
...checklist ? box("Checklist", checklist, columns, color) : [],
|
|
688
|
+
...box("Crew Monitor", crew, columns, color)
|
|
689
|
+
];
|
|
690
|
+
const footer = muted(
|
|
691
|
+
view.gate ? "Third surface \xB7 live feed \xB7 Enter sends the reply \xB7 Ctrl-C stops the run" : "Third surface \xB7 live feed \xB7 q / Ctrl-C stops the run \xB7 log keeps the full NDJSON",
|
|
692
|
+
color
|
|
693
|
+
);
|
|
694
|
+
return [header, ...panels, footer].join("\n");
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// src/mission-control/loop.ts
|
|
698
|
+
var CLEAR_HOME = "\x1B[2J\x1B[H";
|
|
699
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
700
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
701
|
+
function quitKeyOf(chunk) {
|
|
702
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
703
|
+
if (text === "q" || text === "Q") return "q";
|
|
704
|
+
if (text === "") return "Ctrl-C";
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
async function runMcTuiLoop(opts) {
|
|
708
|
+
const render = opts.render ?? ((view, renderOpts) => renderMcTui(view, renderOpts));
|
|
709
|
+
const live = shouldLiveRefresh({
|
|
710
|
+
once: opts.once,
|
|
711
|
+
stdoutIsTTY: opts.stdoutIsTTY,
|
|
712
|
+
env: opts.env
|
|
713
|
+
});
|
|
714
|
+
let frameIndex = 0;
|
|
715
|
+
let inFlight = false;
|
|
716
|
+
let stopped = false;
|
|
717
|
+
const paint = async (clear) => {
|
|
718
|
+
if (inFlight || stopped) return;
|
|
719
|
+
inFlight = true;
|
|
720
|
+
try {
|
|
721
|
+
const view = await opts.loadView();
|
|
722
|
+
if (stopped) return;
|
|
723
|
+
const frame = render(view, {
|
|
724
|
+
...opts.renderOpts,
|
|
725
|
+
stdoutIsTTY: opts.stdoutIsTTY,
|
|
726
|
+
frameIndex
|
|
727
|
+
});
|
|
728
|
+
frameIndex += 1;
|
|
729
|
+
opts.hooks.write(clear ? `${CLEAR_HOME}${frame}
|
|
730
|
+
` : `${frame}
|
|
731
|
+
`);
|
|
732
|
+
} catch (err) {
|
|
733
|
+
if (stopped) return;
|
|
734
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
735
|
+
const frame = renderMcTui(buildMcTuiView(null, message), {
|
|
736
|
+
...opts.renderOpts,
|
|
737
|
+
stdoutIsTTY: opts.stdoutIsTTY,
|
|
738
|
+
frameIndex
|
|
739
|
+
});
|
|
740
|
+
frameIndex += 1;
|
|
741
|
+
opts.hooks.write(clear ? `${CLEAR_HOME}${frame}
|
|
742
|
+
` : `${frame}
|
|
743
|
+
`);
|
|
744
|
+
} finally {
|
|
745
|
+
inFlight = false;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
await paint(false);
|
|
749
|
+
if (!live) return null;
|
|
750
|
+
opts.hooks.write(HIDE_CURSOR);
|
|
751
|
+
const intervalMs = opts.intervalMs ?? PERIODIC_REFRESH_MS;
|
|
752
|
+
const setInt = opts.hooks.setIntervalFn ?? setInterval;
|
|
753
|
+
const clearInt = opts.hooks.clearIntervalFn ?? clearInterval;
|
|
754
|
+
let timer;
|
|
755
|
+
try {
|
|
756
|
+
timer = setInt(() => {
|
|
757
|
+
void paint(true);
|
|
758
|
+
}, intervalMs);
|
|
759
|
+
} catch (err) {
|
|
760
|
+
opts.hooks.write(SHOW_CURSOR);
|
|
761
|
+
throw err;
|
|
762
|
+
}
|
|
763
|
+
const stdin = opts.hooks.stdin;
|
|
764
|
+
const attachQuit = Boolean(stdin?.isTTY);
|
|
765
|
+
let quitListener;
|
|
766
|
+
const restoreStdin = () => {
|
|
767
|
+
if (!stdin || !attachQuit) return;
|
|
768
|
+
if (quitListener) {
|
|
769
|
+
stdin.off?.("data", quitListener);
|
|
770
|
+
quitListener = void 0;
|
|
771
|
+
}
|
|
772
|
+
try {
|
|
773
|
+
stdin.setRawMode?.(false);
|
|
774
|
+
} catch {
|
|
775
|
+
}
|
|
776
|
+
stdin.pause?.();
|
|
777
|
+
};
|
|
778
|
+
const stop = () => {
|
|
779
|
+
if (stopped) return;
|
|
780
|
+
stopped = true;
|
|
781
|
+
clearInt(timer);
|
|
782
|
+
restoreStdin();
|
|
783
|
+
opts.hooks.write(SHOW_CURSOR);
|
|
784
|
+
};
|
|
785
|
+
if (attachQuit && stdin) {
|
|
786
|
+
stdin.setRawMode?.(true);
|
|
787
|
+
stdin.resume?.();
|
|
788
|
+
quitListener = (chunk) => {
|
|
789
|
+
if (stopped) return;
|
|
790
|
+
if (opts.hooks.onKey?.(chunk)) return;
|
|
791
|
+
const key = quitKeyOf(chunk);
|
|
792
|
+
if (!key) return;
|
|
793
|
+
stop();
|
|
794
|
+
if (opts.hooks.onQuit) {
|
|
795
|
+
opts.hooks.onQuit(key);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const exitFn = opts.hooks.exit ?? ((code) => process.exit(code));
|
|
799
|
+
exitFn(0);
|
|
800
|
+
};
|
|
801
|
+
stdin.on("data", quitListener);
|
|
802
|
+
}
|
|
803
|
+
return {
|
|
804
|
+
stop,
|
|
805
|
+
repaint: () => stopped ? Promise.resolve() : paint(true)
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
export {
|
|
810
|
+
ScrollRegion,
|
|
811
|
+
InputLine,
|
|
812
|
+
renderMcLive,
|
|
813
|
+
findDashboardDataScript,
|
|
814
|
+
loadDashboardSnapshot,
|
|
815
|
+
buildMcTuiView,
|
|
816
|
+
runMcTuiLoop
|
|
817
|
+
};
|