sisyphi 1.1.19 → 1.1.24

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.
Files changed (52) hide show
  1. package/deploy/aws/main.tf +121 -0
  2. package/deploy/aws/outputs.tf +18 -0
  3. package/deploy/aws/variables.tf +69 -0
  4. package/deploy/aws/versions.tf +16 -0
  5. package/deploy/hetzner/.terraform.lock.hcl +23 -0
  6. package/deploy/hetzner/main.tf +69 -0
  7. package/deploy/hetzner/outputs.tf +18 -0
  8. package/deploy/hetzner/variables.tf +57 -0
  9. package/deploy/hetzner/versions.tf +15 -0
  10. package/deploy/shared/bin/pbcopy-shim +5 -0
  11. package/deploy/shared/bin/pbpaste-shim +4 -0
  12. package/deploy/shared/cloud-init.yaml.tpl +122 -0
  13. package/deploy/shared/sisyphusd.service.tpl +17 -0
  14. package/deploy/shared/tmux-osc52.conf +10 -0
  15. package/dist/cli.js +3681 -310
  16. package/dist/cli.js.map +1 -1
  17. package/dist/daemon.js +3863 -474
  18. package/dist/daemon.js.map +1 -1
  19. package/dist/deploy/aws/main.tf +121 -0
  20. package/dist/deploy/aws/outputs.tf +18 -0
  21. package/dist/deploy/aws/variables.tf +69 -0
  22. package/dist/deploy/aws/versions.tf +16 -0
  23. package/dist/deploy/hetzner/.terraform.lock.hcl +23 -0
  24. package/dist/deploy/hetzner/main.tf +69 -0
  25. package/dist/deploy/hetzner/outputs.tf +18 -0
  26. package/dist/deploy/hetzner/variables.tf +57 -0
  27. package/dist/deploy/hetzner/versions.tf +15 -0
  28. package/dist/deploy/shared/bin/pbcopy-shim +5 -0
  29. package/dist/deploy/shared/bin/pbpaste-shim +4 -0
  30. package/dist/deploy/shared/cloud-init.yaml.tpl +122 -0
  31. package/dist/deploy/shared/sisyphusd.service.tpl +17 -0
  32. package/dist/deploy/shared/tmux-osc52.conf +10 -0
  33. package/dist/tui.js +2915 -185
  34. package/dist/tui.js.map +1 -1
  35. package/native/build-notify.sh +23 -0
  36. package/package.json +4 -3
  37. package/dist/chunk-36VJ7ZBD.js +0 -1898
  38. package/dist/chunk-36VJ7ZBD.js.map +0 -1
  39. package/dist/chunk-M6Z3KHOH.js +0 -1165
  40. package/dist/chunk-M6Z3KHOH.js.map +0 -1
  41. package/dist/chunk-O4ZHSQ5R.js +0 -544
  42. package/dist/chunk-O4ZHSQ5R.js.map +0 -1
  43. package/dist/chunk-P2HHTIPM.js +0 -478
  44. package/dist/chunk-P2HHTIPM.js.map +0 -1
  45. package/dist/chunk-PNDCVKBN.js +0 -201
  46. package/dist/chunk-PNDCVKBN.js.map +0 -1
  47. package/dist/chunk-SVGIQ2G4.js +0 -1076
  48. package/dist/chunk-SVGIQ2G4.js.map +0 -1
  49. package/dist/paths-JXFLR5BN.js +0 -102
  50. package/dist/paths-JXFLR5BN.js.map +0 -1
  51. package/dist/single-ask-6G4BIVY2.js +0 -132
  52. package/dist/single-ask-6G4BIVY2.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,70 +1,1014 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- KEYMAP,
4
- MENU_FOR_MODE,
5
- buildSessionContext,
6
- computeActiveTimeMs,
7
- createBadgeGallery,
8
- galleryNext,
9
- galleryPrev,
10
- rawSend,
11
- renderBadgeCard,
12
- resolveReports
13
- } from "./chunk-M6Z3KHOH.js";
14
- import {
15
- ACHIEVEMENTS,
16
- EXEC_ENV,
17
- augmentedPath,
18
- computeLevelProgress,
19
- exec,
20
- execSafe,
21
- exportSessionToZip,
22
- formatDuration,
23
- getMoodAnsiCode,
24
- getMoodFace,
25
- renderCompanion,
26
- statusColor
27
- } from "./chunk-36VJ7ZBD.js";
28
- import {
29
- buildEmptyPanelRows,
30
- buildPanelRows,
31
- clipAnsi,
32
- colorToSGR,
33
- copyRows,
34
- createFrameBuffer,
35
- drawBorder,
36
- flushFrame,
37
- onResize,
38
- renderLine,
39
- setupTerminal,
40
- startKeypressListener,
41
- writeCenter,
42
- writeClipped,
43
- writeToStdout
44
- } from "./chunk-O4ZHSQ5R.js";
45
- import {
46
- loadConfig,
47
- readDecisions,
48
- readMeta,
49
- readProgress,
50
- shellQuote,
51
- updateMeta,
52
- writeOutput
53
- } from "./chunk-SVGIQ2G4.js";
54
- import {
55
- askProgressPath,
56
- companionPath,
57
- contextDir,
58
- digestPath,
59
- globalDir,
60
- goalPath,
61
- logsDir,
62
- reportsDir,
63
- roadmapPath,
64
- sessionDir,
65
- strategyPath,
66
- tmuxSessionName
67
- } from "./chunk-PNDCVKBN.js";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/tui/terminal.ts
13
+ function emptyKey() {
14
+ return {
15
+ upArrow: false,
16
+ downArrow: false,
17
+ leftArrow: false,
18
+ rightArrow: false,
19
+ pageUp: false,
20
+ pageDown: false,
21
+ return: false,
22
+ escape: false,
23
+ ctrl: false,
24
+ shift: false,
25
+ tab: false,
26
+ backspace: false,
27
+ delete: false,
28
+ meta: false
29
+ };
30
+ }
31
+ function setupTerminal() {
32
+ let cleaned = false;
33
+ const cleanup2 = () => {
34
+ if (cleaned) return;
35
+ cleaned = true;
36
+ process.stdout.write("\x1B[?25h\x1B[?1049l");
37
+ process.stdin.setRawMode(false);
38
+ process.stdin.pause();
39
+ };
40
+ process.stdin.setRawMode(true);
41
+ process.stdin.resume();
42
+ process.stdin.setEncoding("utf-8");
43
+ process.stdout.write("\x1B[?1049h\x1B[?25l\x1B[2J");
44
+ process.on("SIGINT", () => {
45
+ cleanup2();
46
+ process.exit(0);
47
+ });
48
+ process.on("SIGTERM", () => {
49
+ cleanup2();
50
+ process.exit(0);
51
+ });
52
+ process.on("exit", cleanup2);
53
+ process.on("uncaughtException", (err) => {
54
+ cleanup2();
55
+ console.error(err);
56
+ process.exit(1);
57
+ });
58
+ return cleanup2;
59
+ }
60
+ function writeToStdout(data) {
61
+ process.stdout.write(data);
62
+ }
63
+ function parseBuffer(buf) {
64
+ const events = [];
65
+ let i = 0;
66
+ while (i < buf.length) {
67
+ const ch = buf[i];
68
+ if (ch === "\x1B") {
69
+ const rest = buf.slice(i + 1);
70
+ if (rest.startsWith("[")) {
71
+ const after = rest.slice(1);
72
+ const shiftArrow = after.match(/^1;2([ABCD])/);
73
+ if (shiftArrow) {
74
+ const key2 = emptyKey();
75
+ key2.shift = true;
76
+ const dir = shiftArrow[1];
77
+ if (dir === "A") key2.upArrow = true;
78
+ else if (dir === "B") key2.downArrow = true;
79
+ else if (dir === "C") key2.rightArrow = true;
80
+ else if (dir === "D") key2.leftArrow = true;
81
+ const seq = `\x1B[1;2${dir}`;
82
+ events.push([seq, key2]);
83
+ i += seq.length;
84
+ continue;
85
+ }
86
+ if (after.length >= 1 && after[0] === "Z") {
87
+ const key2 = emptyKey();
88
+ key2.tab = true;
89
+ key2.shift = true;
90
+ const seq = `\x1B[Z`;
91
+ events.push([seq, key2]);
92
+ i += seq.length;
93
+ continue;
94
+ }
95
+ if (after.length >= 1 && "ABCD".includes(after[0])) {
96
+ const key2 = emptyKey();
97
+ const dir = after[0];
98
+ if (dir === "A") key2.upArrow = true;
99
+ else if (dir === "B") key2.downArrow = true;
100
+ else if (dir === "C") key2.rightArrow = true;
101
+ else if (dir === "D") key2.leftArrow = true;
102
+ const seq = `\x1B[${dir}`;
103
+ events.push([seq, key2]);
104
+ i += seq.length;
105
+ continue;
106
+ }
107
+ const tildeMatch = after.match(/^(\d+)~/);
108
+ if (tildeMatch) {
109
+ const num = tildeMatch[1];
110
+ const key2 = emptyKey();
111
+ if (num === "5") key2.pageUp = true;
112
+ else if (num === "6") key2.pageDown = true;
113
+ else if (num === "3") key2.delete = true;
114
+ const seq = `\x1B[${num}~`;
115
+ events.push([seq, key2]);
116
+ i += seq.length;
117
+ continue;
118
+ }
119
+ return { events, remaining: buf.slice(i) };
120
+ }
121
+ if (rest.length === 0) {
122
+ return { events, remaining: buf.slice(i) };
123
+ }
124
+ const metaCh = rest[0];
125
+ const key = emptyKey();
126
+ key.meta = true;
127
+ events.push([metaCh, key]);
128
+ i += 2;
129
+ continue;
130
+ }
131
+ if (ch === "\r") {
132
+ const key = emptyKey();
133
+ key.return = true;
134
+ events.push([ch, key]);
135
+ i++;
136
+ continue;
137
+ }
138
+ if (ch === " ") {
139
+ const key = emptyKey();
140
+ key.tab = true;
141
+ events.push([ch, key]);
142
+ i++;
143
+ continue;
144
+ }
145
+ if (ch === "\x7F" || ch === "\b") {
146
+ const key = emptyKey();
147
+ key.backspace = true;
148
+ events.push([ch, key]);
149
+ i++;
150
+ continue;
151
+ }
152
+ const code = ch.charCodeAt(0);
153
+ if (code >= 1 && code <= 26) {
154
+ const key = emptyKey();
155
+ key.ctrl = true;
156
+ const letter = String.fromCharCode(code + 96);
157
+ events.push([letter, key]);
158
+ i++;
159
+ continue;
160
+ }
161
+ events.push([ch, emptyKey()]);
162
+ i++;
163
+ }
164
+ return { events, remaining: "" };
165
+ }
166
+ function startKeypressListener(handler) {
167
+ let buffer = "";
168
+ let escTimer = null;
169
+ const onData = (data) => {
170
+ if (rawBypassHandler) {
171
+ const handled = rawBypassHandler(data);
172
+ if (handled) return;
173
+ }
174
+ if (escTimer !== null) {
175
+ clearTimeout(escTimer);
176
+ escTimer = null;
177
+ }
178
+ buffer += data;
179
+ const { events, remaining } = parseBuffer(buffer);
180
+ buffer = remaining;
181
+ for (const [input, key] of events) {
182
+ handler(input, key);
183
+ }
184
+ if (buffer === "\x1B") {
185
+ escTimer = setTimeout(() => {
186
+ escTimer = null;
187
+ buffer = "";
188
+ const key = emptyKey();
189
+ key.escape = true;
190
+ handler("\x1B", key);
191
+ }, 50);
192
+ }
193
+ };
194
+ process.stdin.on("data", onData);
195
+ return () => {
196
+ process.stdin.off("data", onData);
197
+ if (escTimer !== null) {
198
+ clearTimeout(escTimer);
199
+ escTimer = null;
200
+ }
201
+ };
202
+ }
203
+ function onResize(callback) {
204
+ const onStdoutResize = () => callback();
205
+ const onSigwinch = () => callback();
206
+ process.stdout.on("resize", onStdoutResize);
207
+ process.on("SIGWINCH", onSigwinch);
208
+ return () => {
209
+ process.stdout.off("resize", onStdoutResize);
210
+ process.off("SIGWINCH", onSigwinch);
211
+ };
212
+ }
213
+ var rawBypassHandler;
214
+ var init_terminal = __esm({
215
+ "src/tui/terminal.ts"() {
216
+ "use strict";
217
+ rawBypassHandler = null;
218
+ }
219
+ });
220
+
221
+ // src/shared/paths.ts
222
+ import { homedir } from "os";
223
+ import { basename, join } from "path";
224
+ function globalDir() {
225
+ return join(homedir(), ".sisyphus");
226
+ }
227
+ function socketPath() {
228
+ return join(globalDir(), "daemon.sock");
229
+ }
230
+ function globalConfigPath() {
231
+ return join(globalDir(), "config.json");
232
+ }
233
+ function projectDir(cwd2) {
234
+ return join(cwd2, ".sisyphus");
235
+ }
236
+ function projectConfigPath(cwd2) {
237
+ return join(projectDir(cwd2), "config.json");
238
+ }
239
+ function sessionsDir(cwd2) {
240
+ return join(projectDir(cwd2), "sessions");
241
+ }
242
+ function sessionDir(cwd2, sessionId2) {
243
+ return join(sessionsDir(cwd2), sessionId2);
244
+ }
245
+ function statePath(cwd2, sessionId2) {
246
+ return join(sessionDir(cwd2, sessionId2), "state.json");
247
+ }
248
+ function reportsDir(cwd2, sessionId2) {
249
+ return join(sessionDir(cwd2, sessionId2), "reports");
250
+ }
251
+ function contextDir(cwd2, sessionId2) {
252
+ return join(sessionDir(cwd2, sessionId2), "context");
253
+ }
254
+ function roadmapPath(cwd2, sessionId2) {
255
+ return join(sessionDir(cwd2, sessionId2), "roadmap.md");
256
+ }
257
+ function goalPath(cwd2, sessionId2) {
258
+ return join(sessionDir(cwd2, sessionId2), "goal.md");
259
+ }
260
+ function strategyPath(cwd2, sessionId2) {
261
+ return join(sessionDir(cwd2, sessionId2), "strategy.md");
262
+ }
263
+ function digestPath(cwd2, sessionId2) {
264
+ return join(sessionDir(cwd2, sessionId2), "digest.json");
265
+ }
266
+ function logsDir(cwd2, sessionId2) {
267
+ return join(sessionDir(cwd2, sessionId2), "logs");
268
+ }
269
+ function askDir(cwd2, sessionId2) {
270
+ return join(contextDir(cwd2, sessionId2), "ask");
271
+ }
272
+ function askEntryDir(cwd2, sessionId2, askId2) {
273
+ return join(askDir(cwd2, sessionId2), askId2);
274
+ }
275
+ function askMetaPath(cwd2, sessionId2, askId2) {
276
+ return join(askEntryDir(cwd2, sessionId2, askId2), "meta.json");
277
+ }
278
+ function askDecisionsPath(cwd2, sessionId2, askId2) {
279
+ return join(askEntryDir(cwd2, sessionId2, askId2), "decisions.json");
280
+ }
281
+ function askOutputPath(cwd2, sessionId2, askId2) {
282
+ return join(askEntryDir(cwd2, sessionId2, askId2), "output.json");
283
+ }
284
+ function askProgressPath(cwd2, sessionId2, askId2) {
285
+ return join(askEntryDir(cwd2, sessionId2, askId2), "progress.json");
286
+ }
287
+ function tmuxSessionName(cwd2, sessionLabel) {
288
+ return `ssyph_${basename(cwd2)}_${sessionLabel}`;
289
+ }
290
+ function companionPath() {
291
+ return join(globalDir(), "companion.json");
292
+ }
293
+ function historyBaseDir() {
294
+ return join(globalDir(), "history");
295
+ }
296
+ function historySessionDir(sessionId2) {
297
+ return join(historyBaseDir(), sessionId2);
298
+ }
299
+ var init_paths = __esm({
300
+ "src/shared/paths.ts"() {
301
+ "use strict";
302
+ }
303
+ });
304
+
305
+ // src/tui/render.ts
306
+ import stringWidth2 from "string-width";
307
+ function colorToSGR(color) {
308
+ const code = COLOR_SGR[color];
309
+ if (code === void 0) throw new Error(`Unknown color: ${color}`);
310
+ return code;
311
+ }
312
+ function renderLine(segs) {
313
+ let out = "";
314
+ for (const s of segs) {
315
+ const codes = [];
316
+ if (s.bold) codes.push("1");
317
+ if (s.dim) codes.push("2");
318
+ if (s.italic) codes.push("3");
319
+ if (s.strikethrough) codes.push("9");
320
+ if (s.inverse) codes.push("7");
321
+ if (s.fg) codes.push(s.fg);
322
+ else if (s.color) codes.push(String(colorToSGR(s.color)));
323
+ if (s.bg) codes.push(s.bg);
324
+ if (codes.length > 0) {
325
+ out += `\x1B[${codes.join(";")}m${s.text}\x1B[0m`;
326
+ } else {
327
+ out += s.text;
328
+ }
329
+ }
330
+ return out;
331
+ }
332
+ function createFrameBuffer(width, height) {
333
+ if (width !== cachedBlankWidth) {
334
+ cachedBlank = " ".repeat(width);
335
+ cachedBlankWidth = width;
336
+ }
337
+ const lines = new Array(height);
338
+ for (let i = 0; i < height; i++) lines[i] = cachedBlank;
339
+ return { lines, width, height };
340
+ }
341
+ function copyRows(buf, src, startRow, count) {
342
+ for (let i = 0; i < count && startRow + i < buf.height; i++) {
343
+ buf.lines[startRow + i] = src[startRow + i];
344
+ }
345
+ }
346
+ function flushFrame(frame, prevFrame2, suffix) {
347
+ let out = "\x1B[?2026h";
348
+ for (let i = 0; i < frame.length; i++) {
349
+ if (frame[i] !== prevFrame2[i]) {
350
+ out += `\x1B[${i + 1};1H`;
351
+ out += "\x1B[2K";
352
+ out += frame[i];
353
+ }
354
+ }
355
+ if (suffix) out += suffix;
356
+ out += "\x1B[?2026l";
357
+ return out;
358
+ }
359
+ function clipAnsi(content, maxWidth) {
360
+ let out = "";
361
+ let displayWidth = 0;
362
+ let i = 0;
363
+ while (i < content.length) {
364
+ if (content[i] === "\x1B" && content[i + 1] === "[") {
365
+ const seqLen = ansiLen(content, i);
366
+ if (seqLen > 0) {
367
+ out += content.substring(i, i + seqLen);
368
+ i += seqLen;
369
+ continue;
370
+ }
371
+ }
372
+ const cp = content.codePointAt(i);
373
+ if (cp < 32 && cp !== 27) {
374
+ i++;
375
+ continue;
376
+ }
377
+ const ch = String.fromCodePoint(cp);
378
+ const chWidth = cp < 128 ? 1 : stringWidth2(ch);
379
+ if (displayWidth + chWidth > maxWidth) break;
380
+ out += ch;
381
+ displayWidth += chWidth;
382
+ i += ch.length;
383
+ }
384
+ if (out.includes("\x1B[") && !out.endsWith("\x1B[0m")) {
385
+ out += "\x1B[0m";
386
+ }
387
+ const remaining = maxWidth - displayWidth;
388
+ if (remaining > 0) out += " ".repeat(remaining);
389
+ return out;
390
+ }
391
+ function displayWidthFast(s) {
392
+ let w = 0;
393
+ let i = 0;
394
+ while (i < s.length) {
395
+ if (s[i] === "\x1B" && s[i + 1] === "[") {
396
+ const len = ansiLen(s, i);
397
+ if (len > 0) {
398
+ i += len;
399
+ continue;
400
+ }
401
+ }
402
+ const cp = s.codePointAt(i);
403
+ const ch = String.fromCodePoint(cp);
404
+ w += cp < 128 ? 1 : stringWidth2(ch);
405
+ i += ch.length;
406
+ }
407
+ return w;
408
+ }
409
+ function ansiLen(s, i) {
410
+ let j = i + 2;
411
+ const len = s.length;
412
+ while (j < len) {
413
+ const c = s.charCodeAt(j);
414
+ if (c >= 48 && c <= 57 || c === 59) {
415
+ j++;
416
+ } else {
417
+ break;
418
+ }
419
+ }
420
+ if (j < len) {
421
+ const c = s.charCodeAt(j);
422
+ if (c >= 65 && c <= 90 || c >= 97 && c <= 122) {
423
+ return j + 1 - i;
424
+ }
425
+ }
426
+ return 0;
427
+ }
428
+ function writeAt(buf, x, y, content) {
429
+ if (y < 0 || y >= buf.height) return;
430
+ if (x < 0 || x >= buf.width) return;
431
+ const existing = buf.lines[y];
432
+ const contentDisplayWidth = stringWidth2(content.replace(ANSI_RE, ""));
433
+ const prefix = sliceDisplayCols(existing, 0, x);
434
+ const suffix = sliceDisplayCols(existing, x + contentDisplayWidth, buf.width, true);
435
+ const prefixWidth = stringWidth2(prefix.replace(ANSI_RE, ""));
436
+ const paddedPrefix = prefix + " ".repeat(Math.max(0, x - prefixWidth));
437
+ buf.lines[y] = paddedPrefix + content + suffix;
438
+ }
439
+ function writeClipped(buf, x, y, content, maxWidth) {
440
+ if (y < 0 || y >= buf.height) return;
441
+ if (x < 0 || x >= buf.width) return;
442
+ let out = "";
443
+ let displayWidth = 0;
444
+ let i = 0;
445
+ while (i < content.length) {
446
+ if (content[i] === "\x1B" && content[i + 1] === "[") {
447
+ const seqLen = ansiLen(content, i);
448
+ if (seqLen > 0) {
449
+ out += content.substring(i, i + seqLen);
450
+ i += seqLen;
451
+ continue;
452
+ }
453
+ }
454
+ const cp = content.codePointAt(i);
455
+ if (cp < 32 && cp !== 27) {
456
+ i++;
457
+ continue;
458
+ }
459
+ const ch = String.fromCodePoint(cp);
460
+ const chWidth = cp < 128 ? 1 : stringWidth2(ch);
461
+ if (displayWidth + chWidth > maxWidth) break;
462
+ out += ch;
463
+ displayWidth += chWidth;
464
+ i += ch.length;
465
+ }
466
+ if (out.includes("\x1B[") && !out.endsWith("\x1B[0m")) {
467
+ out += "\x1B[0m";
468
+ }
469
+ const remaining = maxWidth - displayWidth;
470
+ if (remaining > 0) {
471
+ out += " ".repeat(remaining);
472
+ }
473
+ const existing = buf.lines[y];
474
+ const prefix = sliceDisplayCols(existing, 0, x);
475
+ const suffix = sliceDisplayCols(existing, x + maxWidth, buf.width, true);
476
+ const prefixDisplayW = displayWidthFast(prefix);
477
+ const paddedPrefix = prefixDisplayW < x ? prefix + " ".repeat(x - prefixDisplayW) : prefix;
478
+ buf.lines[y] = paddedPrefix + out + suffix;
479
+ }
480
+ function writeCenter(buf, row, content) {
481
+ const textWidth = stringWidth2(content.replace(ANSI_RE, ""));
482
+ const x = Math.max(0, Math.floor((buf.width - textWidth) / 2));
483
+ writeAt(buf, x, row, content);
484
+ }
485
+ function drawBorder(buf, x, y, w, h, color) {
486
+ const sgr = `\x1B[${colorToSGR(color)}m`;
487
+ const reset = "\x1B[0m";
488
+ writeAt(buf, x, y, sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset);
489
+ writeAt(buf, x, y + h - 1, sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset);
490
+ for (let row = y + 1; row < y + h - 1; row++) {
491
+ writeAt(buf, x, row, sgr + "\u2502" + reset);
492
+ writeAt(buf, x + w - 1, row, sgr + "\u2502" + reset);
493
+ }
494
+ }
495
+ function buildPanelRows(rect, lines, scroll, focused, borderColor, renderedCache) {
496
+ const { w, h } = rect;
497
+ const rows = new Array(h);
498
+ const color = focused ? "cyan" : "gray";
499
+ const sgr = `\x1B[${colorToSGR(color)}m`;
500
+ const reset = "\x1B[0m";
501
+ const innerW = w - 4;
502
+ const innerH = h - 2;
503
+ const blankInner = " ".repeat(innerW);
504
+ rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
505
+ rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
506
+ const borderL = sgr + "\u2502" + reset + " ";
507
+ const borderR = " " + sgr + "\u2502" + reset;
508
+ const emptyRow = borderL + blankInner + borderR;
509
+ for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
510
+ if (innerW <= 0 || innerH <= 0) return rows;
511
+ let ansiLines;
512
+ if (renderedCache && renderedCache.lines === lines) {
513
+ ansiLines = renderedCache.ansi;
514
+ } else {
515
+ ansiLines = new Array(lines.length);
516
+ for (let i = 0; i < lines.length; i++) {
517
+ ansiLines[i] = renderLine(lines[i]);
518
+ }
519
+ if (renderedCache) {
520
+ renderedCache.lines = lines;
521
+ renderedCache.ansi = ansiLines;
522
+ }
523
+ }
524
+ const hasOverflow = lines.length > innerH;
525
+ const viewableH = hasOverflow ? innerH - 1 : innerH;
526
+ const maxScroll = Math.max(0, lines.length - viewableH);
527
+ scroll.setMax(maxScroll);
528
+ const effectiveOffset = scroll.offset;
529
+ for (let i = 0; i < viewableH && effectiveOffset + i < ansiLines.length; i++) {
530
+ const clipped = clipAnsi(ansiLines[effectiveOffset + i], innerW);
531
+ rows[1 + i] = borderL + clipped + borderR;
532
+ }
533
+ if (hasOverflow) {
534
+ const scrollPct = maxScroll > 0 ? Math.round(effectiveOffset / maxScroll * 100) : 100;
535
+ const indicator = ` \u2195 ${scrollPct}% \xB7 ${lines.length} lines`;
536
+ const clipped = clipAnsi(`\x1B[2m${indicator}\x1B[0m`, innerW);
537
+ rows[1 + viewableH] = borderL + clipped + borderR;
538
+ }
539
+ return rows;
540
+ }
541
+ function buildEmptyPanelRows(rect, focused, borderColor, centerText) {
542
+ const { w, h } = rect;
543
+ const rows = new Array(h);
544
+ const color = focused ? "cyan" : "gray";
545
+ const sgr = `\x1B[${colorToSGR(color)}m`;
546
+ const reset = "\x1B[0m";
547
+ const innerW = w - 4;
548
+ const borderL = sgr + "\u2502" + reset + " ";
549
+ const borderR = " " + sgr + "\u2502" + reset;
550
+ const emptyRow = borderL + " ".repeat(innerW) + borderR;
551
+ rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
552
+ rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
553
+ for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
554
+ if (centerText) {
555
+ const midRow = Math.floor(h / 2);
556
+ if (midRow > 0 && midRow < h - 1) {
557
+ const clipped = clipAnsi(centerText, innerW);
558
+ const textW = displayWidthFast(centerText);
559
+ const pad = Math.max(0, Math.floor((innerW - textW) / 2));
560
+ const centered = " ".repeat(pad) + clipped;
561
+ rows[midRow] = borderL + clipAnsi(centered, innerW) + borderR;
562
+ }
563
+ }
564
+ return rows;
565
+ }
566
+ function sliceDisplayCols(s, start, end, restoreState = false) {
567
+ let out = "";
568
+ let col = 0;
569
+ let i = 0;
570
+ let inSlice = false;
571
+ let hasOpenSGR = false;
572
+ let pendingSGR = "";
573
+ while (i < s.length && col < end) {
574
+ if (s[i] === "\x1B" && s[i + 1] === "[") {
575
+ const seqLen = ansiLen(s, i);
576
+ if (seqLen > 0) {
577
+ const seq = s.substring(i, i + seqLen);
578
+ if (col >= start) {
579
+ out += seq;
580
+ hasOpenSGR = seq !== "\x1B[0m" && seq !== "\x1B[m";
581
+ } else if (restoreState) {
582
+ if (seq === "\x1B[0m" || seq === "\x1B[m") {
583
+ pendingSGR = "";
584
+ } else {
585
+ pendingSGR += seq;
586
+ }
587
+ }
588
+ i += seqLen;
589
+ continue;
590
+ }
591
+ }
592
+ const cp = s.codePointAt(i);
593
+ const ch = String.fromCodePoint(cp);
594
+ const chWidth = cp < 128 ? 1 : stringWidth2(ch);
595
+ if (col >= start) {
596
+ inSlice = true;
597
+ if (col + chWidth > end) break;
598
+ out += ch;
599
+ }
600
+ col += chWidth;
601
+ i += ch.length;
602
+ }
603
+ if (inSlice && hasOpenSGR) {
604
+ out += "\x1B[0m";
605
+ }
606
+ if (restoreState && pendingSGR) {
607
+ out = pendingSGR + out;
608
+ }
609
+ return out;
610
+ }
611
+ var COLOR_SGR, cachedBlank, cachedBlankWidth, ANSI_RE;
612
+ var init_render = __esm({
613
+ "src/tui/render.ts"() {
614
+ "use strict";
615
+ COLOR_SGR = {
616
+ black: 30,
617
+ red: 31,
618
+ green: 32,
619
+ yellow: 33,
620
+ blue: 34,
621
+ magenta: 35,
622
+ cyan: 36,
623
+ white: 37,
624
+ gray: 90
625
+ };
626
+ cachedBlank = "";
627
+ cachedBlankWidth = 0;
628
+ ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
629
+ }
630
+ });
631
+
632
+ // src/shared/config.ts
633
+ import { readFileSync as readFileSync4 } from "fs";
634
+ function readJsonFile(filePath) {
635
+ try {
636
+ const content = readFileSync4(filePath, "utf-8");
637
+ return JSON.parse(content);
638
+ } catch {
639
+ return {};
640
+ }
641
+ }
642
+ function loadConfig(cwd2) {
643
+ const globalConfig = readJsonFile(globalConfigPath());
644
+ const projectConfig = readJsonFile(projectConfigPath(cwd2));
645
+ if (projectConfig.upload !== void 0) {
646
+ console.warn(
647
+ "ignoring `upload` block from project-local .sisyphus/config.json \u2014 only the global config can set upload credentials"
648
+ );
649
+ delete projectConfig.upload;
650
+ }
651
+ const merged = { ...DEFAULT_CONFIG, ...globalConfig, ...projectConfig };
652
+ if (globalConfig.statusBar || projectConfig.statusBar) {
653
+ merged.statusBar = {
654
+ ...merged.statusBar,
655
+ ...globalConfig.statusBar,
656
+ ...projectConfig.statusBar,
657
+ colors: {
658
+ ...merged.statusBar?.colors,
659
+ ...globalConfig.statusBar?.colors,
660
+ ...projectConfig.statusBar?.colors
661
+ },
662
+ segments: {
663
+ ...merged.statusBar?.segments,
664
+ ...globalConfig.statusBar?.segments,
665
+ ...projectConfig.statusBar?.segments
666
+ }
667
+ };
668
+ }
669
+ return merged;
670
+ }
671
+ var DEFAULT_CONFIG;
672
+ var init_config = __esm({
673
+ "src/shared/config.ts"() {
674
+ "use strict";
675
+ init_paths();
676
+ DEFAULT_CONFIG = {
677
+ model: "claude-opus-4-7[1m]",
678
+ pollIntervalMs: 5e3,
679
+ orchestratorEffort: "xhigh",
680
+ agentEffort: "medium",
681
+ notifications: {
682
+ enabled: true,
683
+ sound: "/System/Library/Sounds/Hero.aiff"
684
+ },
685
+ companionPopup: true,
686
+ requiredPlugins: [
687
+ { name: "devcore", marketplace: "crouton-kit" }
688
+ ]
689
+ };
690
+ }
691
+ });
692
+
693
+ // src/daemon/history.ts
694
+ import { appendFileSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, renameSync as renameSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync2, statSync } from "fs";
695
+ import { randomUUID as randomUUID3 } from "crypto";
696
+ import { dirname as dirname3, join as join6 } from "path";
697
+ var init_history = __esm({
698
+ "src/daemon/history.ts"() {
699
+ "use strict";
700
+ init_paths();
701
+ }
702
+ });
703
+
704
+ // src/daemon/lib/atomic.ts
705
+ import { randomUUID as randomUUID4 } from "crypto";
706
+ import { dirname as dirname4, join as join7 } from "path";
707
+ import { renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
708
+ function atomicWrite(filePath, data) {
709
+ const dir = dirname4(filePath);
710
+ const tmpPath = join7(dir, `.atomic.${randomUUID4()}.tmp`);
711
+ writeFileSync5(tmpPath, data, "utf-8");
712
+ renameSync4(tmpPath, filePath);
713
+ }
714
+ async function withLock(key, fn) {
715
+ const prev = locks.get(key) ?? Promise.resolve();
716
+ let resolve2;
717
+ const next = new Promise((r) => {
718
+ resolve2 = r;
719
+ });
720
+ locks.set(key, next);
721
+ await prev;
722
+ try {
723
+ return fn();
724
+ } finally {
725
+ resolve2();
726
+ if (locks.get(key) === next) {
727
+ locks.delete(key);
728
+ }
729
+ }
730
+ }
731
+ var locks;
732
+ var init_atomic = __esm({
733
+ "src/daemon/lib/atomic.ts"() {
734
+ "use strict";
735
+ locks = /* @__PURE__ */ new Map();
736
+ }
737
+ });
738
+
739
+ // src/shared/shell.ts
740
+ function shellQuote(s) {
741
+ return `'${s.replace(/'/g, "'\\''")}'`;
742
+ }
743
+ var init_shell = __esm({
744
+ "src/shared/shell.ts"() {
745
+ "use strict";
746
+ }
747
+ });
748
+
749
+ // src/daemon/notify.ts
750
+ import { spawn, execFile as execFile2 } from "child_process";
751
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync4 } from "fs";
752
+ import { join as join8 } from "path";
753
+ import { homedir as homedir3 } from "os";
754
+ var TMUX_SOCKET, SWITCH_SCRIPT;
755
+ var init_notify = __esm({
756
+ "src/daemon/notify.ts"() {
757
+ "use strict";
758
+ init_shell();
759
+ TMUX_SOCKET = `/tmp/tmux-${process.getuid?.() ?? 0}/default`;
760
+ SWITCH_SCRIPT = [
761
+ "#!/bin/bash",
762
+ 'SESSION="$1"',
763
+ `TMUX_SOCKET="${TMUX_SOCKET}"`,
764
+ "TMUX=/opt/homebrew/bin/tmux",
765
+ "",
766
+ "# Find any attached client (user is likely on a different session)",
767
+ `CLIENT_TTY=$("$TMUX" -S "$TMUX_SOCKET" list-clients -F '#{client_tty}' 2>/dev/null | head -1)`,
768
+ '[ -z "$CLIENT_TTY" ] && exit 0',
769
+ "",
770
+ "# Switch that client to the target session",
771
+ '"$TMUX" -S "$TMUX_SOCKET" switch-client -c "$CLIENT_TTY" -t "$SESSION" 2>/dev/null',
772
+ '"$TMUX" -S "$TMUX_SOCKET" select-window -t "$SESSION" 2>/dev/null',
773
+ "",
774
+ "# Bring iTerm2 to front and select the tab with this client",
775
+ `TTY_SHORT=$(echo "$CLIENT_TTY" | sed 's|/dev/||')`,
776
+ 'osascript -e "',
777
+ ' tell application \\"iTerm2\\"',
778
+ " activate",
779
+ " repeat with w in windows",
780
+ " tell w",
781
+ " repeat with t in tabs",
782
+ " tell t",
783
+ " repeat with s in sessions",
784
+ " tell s",
785
+ ' if tty contains \\"$TTY_SHORT\\" then',
786
+ " select t",
787
+ " return",
788
+ " end if",
789
+ " end tell",
790
+ " end repeat",
791
+ " end tell",
792
+ " end repeat",
793
+ " end tell",
794
+ " end repeat",
795
+ " end tell",
796
+ `" 2>/dev/null || osascript -e 'tell application "iTerm2" to activate' 2>/dev/null`,
797
+ ""
798
+ ].join("\n");
799
+ }
800
+ });
801
+
802
+ // src/shared/gitignore.ts
803
+ import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
804
+ import { join as join9 } from "path";
805
+ var init_gitignore = __esm({
806
+ "src/shared/gitignore.ts"() {
807
+ "use strict";
808
+ }
809
+ });
810
+
811
+ // src/shared/types.ts
812
+ var init_types = __esm({
813
+ "src/shared/types.ts"() {
814
+ "use strict";
815
+ }
816
+ });
817
+
818
+ // src/daemon/state.ts
819
+ import { copyFileSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
820
+ import { join as join10 } from "path";
821
+ var init_state = __esm({
822
+ "src/daemon/state.ts"() {
823
+ "use strict";
824
+ init_atomic();
825
+ init_paths();
826
+ init_gitignore();
827
+ init_types();
828
+ }
829
+ });
830
+
831
+ // src/daemon/ask-store.ts
832
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "fs";
833
+ function readDecisions(cwd2, sessionId2, askId2) {
834
+ const p = askDecisionsPath(cwd2, sessionId2, askId2);
835
+ try {
836
+ return JSON.parse(readFileSync8(p, { encoding: "utf-8" }));
837
+ } catch (_e) {
838
+ return null;
839
+ }
840
+ }
841
+ function readProgress(cwd2, sessionId2, askId2) {
842
+ const p = askProgressPath(cwd2, sessionId2, askId2);
843
+ try {
844
+ const data = JSON.parse(readFileSync8(p, { encoding: "utf-8" }));
845
+ if (!Array.isArray(data["responses"])) return null;
846
+ return { responses: data["responses"], savedAt: data["savedAt"] };
847
+ } catch (_e) {
848
+ return null;
849
+ }
850
+ }
851
+ function writeOutput(cwd2, sessionId2, askId2, responses, completedAt) {
852
+ atomicWrite(askOutputPath(cwd2, sessionId2, askId2), JSON.stringify({
853
+ responses,
854
+ completedAt: completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
855
+ }, null, 2));
856
+ }
857
+ function readMeta(cwd2, sessionId2, askId2) {
858
+ const p = askMetaPath(cwd2, sessionId2, askId2);
859
+ if (!existsSync7(p)) {
860
+ return null;
861
+ }
862
+ return JSON.parse(readFileSync8(p, "utf-8"));
863
+ }
864
+ async function updateMeta(cwd2, sessionId2, askId2, patch) {
865
+ return withLock(askId2, () => {
866
+ const cur = readMeta(cwd2, sessionId2, askId2);
867
+ if (!cur) {
868
+ throw new Error(`updateMeta: askId ${askId2} not found`);
869
+ }
870
+ const next = { ...cur, ...patch };
871
+ atomicWrite(askMetaPath(cwd2, sessionId2, askId2), JSON.stringify(next, null, 2));
872
+ return next;
873
+ });
874
+ }
875
+ var init_ask_store = __esm({
876
+ "src/daemon/ask-store.ts"() {
877
+ "use strict";
878
+ init_paths();
879
+ init_config();
880
+ init_history();
881
+ init_atomic();
882
+ init_notify();
883
+ init_state();
884
+ }
885
+ });
886
+
887
+ // src/tui/single-ask.ts
888
+ var single_ask_exports = {};
889
+ __export(single_ask_exports, {
890
+ runSingleAsk: () => runSingleAsk
891
+ });
892
+ import { existsSync as existsSync11, watchFile, unwatchFile } from "fs";
893
+ import { mountPanel as mountPanel2 } from "@crouton-kit/humanloop";
894
+ async function runSingleAsk(opts) {
895
+ const { cwd: cwd2, sessionId: sessionId2, askId: askId2 } = opts;
896
+ const meta = readMeta(cwd2, sessionId2, askId2);
897
+ if (!meta || meta.status === "answered") {
898
+ return;
899
+ }
900
+ const deck = readDecisions(cwd2, sessionId2, askId2);
901
+ if (!deck) return;
902
+ const cleanupTerminal = setupTerminal();
903
+ let exiting = false;
904
+ let panel = null;
905
+ let prevFrame2 = [];
906
+ let stopKeypress = null;
907
+ let stopResize = null;
908
+ const outputPath = askOutputPath(cwd2, sessionId2, askId2);
909
+ const exit = (code) => {
910
+ if (exiting) return;
911
+ exiting = true;
912
+ try {
913
+ stopKeypress?.();
914
+ } catch {
915
+ }
916
+ try {
917
+ stopResize?.();
918
+ } catch {
919
+ }
920
+ try {
921
+ panel?.unmount();
922
+ } catch {
923
+ }
924
+ try {
925
+ unwatchFile(outputPath, onExternalChange);
926
+ } catch {
927
+ }
928
+ cleanupTerminal();
929
+ process.exit(code);
930
+ };
931
+ const flushHost = (lines) => {
932
+ const out = flushFrame(lines, prevFrame2, "\x1B[?25l");
933
+ writeToStdout(out);
934
+ prevFrame2 = lines;
935
+ };
936
+ const onExternalChange = () => {
937
+ if (exiting) return;
938
+ if (!existsSync11(outputPath)) return;
939
+ exit(0);
940
+ };
941
+ let lastResponses = [];
942
+ const submit = (responses) => {
943
+ if (exiting) return;
944
+ void (async () => {
945
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
946
+ writeOutput(cwd2, sessionId2, askId2, responses, completedAt);
947
+ try {
948
+ await updateMeta(cwd2, sessionId2, askId2, { status: "answered", completedAt });
949
+ } catch {
950
+ }
951
+ exit(0);
952
+ })();
953
+ };
954
+ const cols = process.stdout.columns ?? 80;
955
+ const rows = process.stdout.rows ?? 24;
956
+ panel = mountPanel2({
957
+ deck,
958
+ cols,
959
+ rows,
960
+ progressPath: askProgressPath(cwd2, sessionId2, askId2),
961
+ onProgress: (responses) => {
962
+ lastResponses = responses;
963
+ const cur = readMeta(cwd2, sessionId2, askId2);
964
+ if (cur?.status === "pending") {
965
+ void updateMeta(cwd2, sessionId2, askId2, { status: "in-progress", startedAt: (/* @__PURE__ */ new Date()).toISOString() }).catch(() => {
966
+ });
967
+ }
968
+ if (panel) flushHost(panel.render());
969
+ },
970
+ onComplete: (responses) => {
971
+ submit(responses);
972
+ },
973
+ // Final-phase Enter on an incomplete deck — submit what we have.
974
+ onExit: () => {
975
+ submit(lastResponses);
976
+ }
977
+ });
978
+ stopKeypress = startKeypressListener((input, key) => {
979
+ if (exiting || !panel) return;
980
+ panel.handleKey(input, key);
981
+ flushHost(panel.render());
982
+ });
983
+ stopResize = onResize(() => {
984
+ if (exiting || !panel) return;
985
+ const newCols = process.stdout.columns ?? 80;
986
+ const newRows = process.stdout.rows ?? 24;
987
+ panel.handleResize(newCols, newRows);
988
+ prevFrame2 = [];
989
+ flushHost(panel.render());
990
+ });
991
+ watchFile(outputPath, { interval: 250 }, onExternalChange);
992
+ if (existsSync11(outputPath)) {
993
+ exit(0);
994
+ return;
995
+ }
996
+ flushHost(panel.render());
997
+ await new Promise(() => {
998
+ });
999
+ }
1000
+ var init_single_ask = __esm({
1001
+ "src/tui/single-ask.ts"() {
1002
+ "use strict";
1003
+ init_ask_store();
1004
+ init_paths();
1005
+ init_terminal();
1006
+ init_render();
1007
+ }
1008
+ });
1009
+
1010
+ // src/tui/index.ts
1011
+ init_terminal();
68
1012
 
69
1013
  // src/tui/state.ts
70
1014
  var OPTIONAL_COMPOSE = /* @__PURE__ */ new Set(["resume", "continue"]);
@@ -271,19 +1215,184 @@ function autoExpandCycle(state2) {
271
1215
  }
272
1216
 
273
1217
  // src/tui/app.ts
274
- import { readFileSync as readFileSync5, existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
275
- import { join as join5 } from "path";
1218
+ import { readFileSync as readFileSync15, existsSync as existsSync10, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
1219
+ import { join as join14 } from "path";
276
1220
 
277
1221
  // src/tui/input.ts
278
1222
  import { execSync } from "child_process";
279
- import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
1223
+ import { readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
1224
+ import { join as join11 } from "path";
1225
+
1226
+ // src/shared/session-export.ts
1227
+ init_paths();
1228
+ import { execFile } from "child_process";
1229
+ import { promisify } from "util";
1230
+ import { existsSync, readFileSync, mkdirSync, symlinkSync, rmSync, writeFileSync } from "fs";
1231
+ import { homedir as homedir2 } from "os";
280
1232
  import { join as join2 } from "path";
1233
+ function sanitizeName(name) {
1234
+ return name.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
1235
+ }
1236
+ function buildOutputPath(label, dir) {
1237
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1238
+ mkdirSync(dir, { recursive: true });
1239
+ const base = `sisyphus-${label}-${date}`;
1240
+ let candidate = join2(dir, `${base}.zip`);
1241
+ let counter = 1;
1242
+ while (existsSync(candidate)) {
1243
+ counter++;
1244
+ candidate = join2(dir, `${base}-${counter}.zip`);
1245
+ }
1246
+ return candidate;
1247
+ }
1248
+ function generateGuide() {
1249
+ return `# Sisyphus Session Export
1250
+
1251
+ ## Quick Orientation
1252
+
1253
+ Start with \`session/state.json\` for the full session state, or \`history/session.json\` for a compact summary with metrics.
1254
+
1255
+ ## session/
1256
+
1257
+ Project-local session data \u2014 the orchestrator's working directory.
1258
+
1259
+ ### Top-level files
1260
+ - **state.json** \u2014 Complete session state: id, task, status, timing, and the full \`agents[]\` array (each agent has id, type, instruction, status, reports, Claude session ID, and resume args)
1261
+ - **goal.md** \u2014 The task description; updated if the goal evolves across phases
1262
+ - **initial-prompt.md** \u2014 Verbatim user input that started the session
1263
+ - **roadmap.md** \u2014 Orchestrator's working memory: current stage, exit criteria, active context files, next steps
1264
+ - **strategy.md** \u2014 Work breakdown: completed stages, current stage decomposition (concerns/phases), and what's ahead
1265
+ - **digest.json** \u2014 4-field snapshot: \`recentWork\`, \`unusualEvents\`, \`currentActivity\`, \`whatsNext\`
1266
+
1267
+ ### Subdirectories
1268
+
1269
+ **context/** \u2014 Research artifacts produced by agents and consumed by downstream agents
1270
+ - \`explore-*.md\` \u2014 Codebase exploration findings (key files, architecture notes)
1271
+ - \`requirements*.md/json\` \u2014 Feature requirements (structured + human-readable)
1272
+ - \`design*.md/json\` \u2014 Architecture specs, decision records, diagrams
1273
+ - \`{agent-id}/plan*.md\` \u2014 Implementation plans (tasks, files to touch, dependencies) \u2014 per plan-lead subdirectory
1274
+ - \`e2e-recipe.md\` \u2014 End-to-end validation steps
1275
+ - \`review-*.md\` \u2014 Code review findings (severity-ranked)
1276
+ - \`completion-summary.md\` \u2014 Final handoff document
1277
+
1278
+ **logs/** \u2014 One \`cycle-NNN.md\` per orchestrator cycle. Each logs what happened, agents spawned, user decisions, and key findings.
1279
+
1280
+ **prompts/** \u2014 Full agent configs, one set per agent:
1281
+ - \`agent-NNN-system.md\` \u2014 System prompt (instructions, tools, output format)
1282
+ - \`agent-NNN-run.sh\` \u2014 Executable bash script to resume the agent (contains env, CLI args, instruction)
1283
+ - \`agent-NNN-plugin/\` \u2014 Plugin directory (hooks, sub-agent configs)
1284
+
1285
+ **reports/** \u2014 Agent deliverables:
1286
+ - \`agent-NNN-final.md\` \u2014 Final report (findings, implementation summary, or review results)
1287
+ - \`agent-NNN-00N.md\` \u2014 Interim progress reports (optional)
1288
+
1289
+ **snapshots/** \u2014 Point-in-time checkpoints (\`snapshots/cycle-N/\`). Each contains state.json, roadmap.md, strategy.md, and logs/ as they were at that cycle boundary. Used for rollback.
1290
+
1291
+ **.tui/** \u2014 Lightweight TUI render cache (cycle summaries for display). Regenerable; not primary data.
1292
+
1293
+ ## history/
1294
+
1295
+ Global telemetry from the daemon \u2014 timing, events, and aggregate metrics.
1296
+
1297
+ - **events.jsonl** \u2014 Newline-delimited JSON event stream. Each line: \`{ ts, event, sessionId, data }\`. Events include session-start, agent-spawned, agent-completed, cycle-boundary, signals-snapshot, session-end, etc. Complete audit trail.
1298
+ - **session.json** \u2014 Summary: id, name, task, status, timing (activeMs, wallClockMs, efficiency), agent/cycle counts, crash/rollback counts, completion report, and a compact agents array.
1299
+ `;
1300
+ }
1301
+ var execFileAsync = promisify(execFile);
1302
+ async function exportSessionToZip(sessionId2, cwd2, options) {
1303
+ const reveal = options?.reveal ?? true;
1304
+ const sessDir = sessionDir(cwd2, sessionId2);
1305
+ const histDir = historySessionDir(sessionId2);
1306
+ const sessExists = existsSync(sessDir);
1307
+ const histExists = existsSync(histDir);
1308
+ if (!sessExists && !histExists) {
1309
+ throw new Error(`No data found for session ${sessionId2}`);
1310
+ }
1311
+ let label = sessionId2.slice(0, 8);
1312
+ const stPath = statePath(cwd2, sessionId2);
1313
+ if (existsSync(stPath)) {
1314
+ try {
1315
+ const state2 = JSON.parse(readFileSync(stPath, "utf-8"));
1316
+ if (state2.name) {
1317
+ label = sanitizeName(state2.name);
1318
+ }
1319
+ } catch {
1320
+ }
1321
+ }
1322
+ const dir = options?.outputDir ?? join2(homedir2(), "Downloads");
1323
+ const outputPath = buildOutputPath(label, dir);
1324
+ const tmpDir = `/tmp/sisyphus-export-${sessionId2.slice(0, 8)}-${Date.now()}`;
1325
+ try {
1326
+ mkdirSync(tmpDir, { recursive: true });
1327
+ writeFileSync(join2(tmpDir, "CLAUDE.md"), generateGuide(), "utf-8");
1328
+ if (sessExists) {
1329
+ symlinkSync(sessDir, join2(tmpDir, "session"));
1330
+ }
1331
+ if (histExists) {
1332
+ symlinkSync(histDir, join2(tmpDir, "history"));
1333
+ }
1334
+ const parts = ["CLAUDE.md", sessExists ? "session/" : "", histExists ? "history/" : ""].filter(Boolean);
1335
+ await execFileAsync("zip", ["-rq", outputPath, ...parts], { cwd: tmpDir });
1336
+ } finally {
1337
+ rmSync(tmpDir, { recursive: true, force: true });
1338
+ }
1339
+ if (reveal) {
1340
+ try {
1341
+ await execFileAsync("open", ["-R", outputPath]);
1342
+ } catch {
1343
+ }
1344
+ }
1345
+ return outputPath;
1346
+ }
1347
+
1348
+ // src/tui/input.ts
1349
+ init_paths();
281
1350
 
282
1351
  // src/tui/lib/tree.ts
283
- import { join } from "path";
1352
+ import { join as join3 } from "path";
284
1353
 
285
1354
  // src/tui/lib/format.ts
286
1355
  import stringWidth from "string-width";
1356
+
1357
+ // src/shared/format.ts
1358
+ function formatDuration(startOrMs, endIso) {
1359
+ let totalMs;
1360
+ if (typeof startOrMs === "number") {
1361
+ totalMs = startOrMs;
1362
+ } else {
1363
+ const start = new Date(startOrMs).getTime();
1364
+ const end = endIso ? new Date(endIso).getTime() : Date.now();
1365
+ totalMs = end - start;
1366
+ }
1367
+ const totalSeconds = Math.floor(totalMs / 1e3);
1368
+ if (totalSeconds < 0) return "0s";
1369
+ const hours = Math.floor(totalSeconds / 3600);
1370
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
1371
+ const seconds = totalSeconds % 60;
1372
+ if (hours > 0) return `${hours}h${minutes}m`;
1373
+ if (minutes > 0) return `${minutes}m${seconds}s`;
1374
+ return `${seconds}s`;
1375
+ }
1376
+ function statusColor(status) {
1377
+ switch (status) {
1378
+ case "active":
1379
+ case "running":
1380
+ return "green";
1381
+ case "completed":
1382
+ return "cyan";
1383
+ case "paused":
1384
+ return "yellow";
1385
+ case "killed":
1386
+ case "crashed":
1387
+ return "red";
1388
+ case "lost":
1389
+ return "gray";
1390
+ default:
1391
+ return "white";
1392
+ }
1393
+ }
1394
+
1395
+ // src/tui/lib/format.ts
287
1396
  function formatTimeAgo(iso) {
288
1397
  const diff = Date.now() - new Date(iso).getTime();
289
1398
  const minutes = Math.floor(diff / 6e4);
@@ -493,6 +1602,7 @@ function wrapText(text, width) {
493
1602
  }
494
1603
 
495
1604
  // src/tui/lib/tree.ts
1605
+ init_paths();
496
1606
  function sessionSortKey(s) {
497
1607
  if (s.status === "completed") return 4;
498
1608
  const open = s.windowAlive ?? false;
@@ -669,7 +1779,7 @@ function buildTree(sessions, selectedSession, expanded, cwd2, polledContextFiles
669
1779
  expanded: false,
670
1780
  sessionId: s.id,
671
1781
  label: filename,
672
- filePath: join(contextDir(cwd2, s.id), filename)
1782
+ filePath: join3(contextDir(cwd2, s.id), filename)
673
1783
  });
674
1784
  }
675
1785
  }
@@ -741,6 +1851,1339 @@ function findParentIndex(nodes, index) {
741
1851
  return 0;
742
1852
  }
743
1853
 
1854
+ // src/tui/panels/overlays.ts
1855
+ init_render();
1856
+
1857
+ // src/shared/companion-render.ts
1858
+ import stringWidth3 from "string-width";
1859
+ function sliceToWidth(s, maxCols) {
1860
+ let w = 0;
1861
+ let i = 0;
1862
+ while (i < s.length) {
1863
+ const cp = s.codePointAt(i);
1864
+ const ch = String.fromCodePoint(cp);
1865
+ const cw = stringWidth3(ch);
1866
+ if (w + cw > maxCols) break;
1867
+ w += cw;
1868
+ i += ch.length;
1869
+ }
1870
+ return s.slice(0, i);
1871
+ }
1872
+ var IDLE_HOBBIES = [
1873
+ "reading Camus",
1874
+ "stacking pebbles",
1875
+ "watching clouds",
1876
+ "sketching boulders",
1877
+ "counting stars",
1878
+ "writing haiku",
1879
+ "practicing zen",
1880
+ "studying geology",
1881
+ "polishing rocks",
1882
+ "mapping the hill",
1883
+ "resting",
1884
+ "stargazing",
1885
+ "whittling",
1886
+ "collecting fossils",
1887
+ "napping on summit",
1888
+ "journaling",
1889
+ "stretching",
1890
+ "humming",
1891
+ "doodling",
1892
+ "tending moss",
1893
+ "making tea",
1894
+ "reading Myth of Sisyphus",
1895
+ "reorganizing rocks",
1896
+ "people watching",
1897
+ "whistling"
1898
+ ];
1899
+ var SPINNER_VERBS = [
1900
+ // physical
1901
+ "pushing",
1902
+ "hauling",
1903
+ "heaving",
1904
+ "toiling",
1905
+ "straining",
1906
+ "trudging",
1907
+ "laboring",
1908
+ "rolling",
1909
+ "ascending",
1910
+ "dragging",
1911
+ "shouldering",
1912
+ "hoisting",
1913
+ "lugging",
1914
+ "schlepping",
1915
+ "grinding",
1916
+ "lifting",
1917
+ "bracing",
1918
+ "climbing",
1919
+ "leaning in",
1920
+ "digging in",
1921
+ // philosophical
1922
+ "philosophizing",
1923
+ "contemplating",
1924
+ "pondering",
1925
+ "musing",
1926
+ "ruminating",
1927
+ "reflecting",
1928
+ "meditating",
1929
+ "wondering",
1930
+ "questioning",
1931
+ "theorizing",
1932
+ "considering",
1933
+ "deliberating",
1934
+ "introspecting",
1935
+ "cogitating",
1936
+ "brooding",
1937
+ // endurance
1938
+ "persevering",
1939
+ "enduring",
1940
+ "persisting",
1941
+ "sustaining",
1942
+ "weathering",
1943
+ "carrying on",
1944
+ "pressing on",
1945
+ "holding steady",
1946
+ "keeping at it",
1947
+ "not stopping",
1948
+ // light/silly
1949
+ "napping",
1950
+ "procrastinating",
1951
+ "daydreaming",
1952
+ "vibing",
1953
+ "winging it",
1954
+ "hoping",
1955
+ "improvising",
1956
+ "making do",
1957
+ "whistling"
1958
+ ];
1959
+ function getBaseForm(level) {
1960
+ if (level <= 2) return "(FACE) {BOULDER}";
1961
+ if (level <= 4) return "(FACE)/ {BOULDER}";
1962
+ if (level <= 7) return "/(FACE)/ {BOULDER}";
1963
+ if (level <= 11) return "\\(FACE)/ {BOULDER}";
1964
+ if (level <= 19) return "\u1566(FACE)\u1564 {BOULDER}";
1965
+ return "\u265B\u1566(FACE)\u1564 {BOULDER}";
1966
+ }
1967
+ var MOOD_FACES = {
1968
+ happy: ["^.^", "^\u203F^", "\u2727\u203F\u2727"],
1969
+ grinding: [">.<", ">_<", "\xF2.\xF3"],
1970
+ frustrated: [">.<#", "\u0CA0_\u0CA0", "\u0CA0\u76CA\u0CA0"],
1971
+ zen: ["\u203E.\u203E", "\u203E\u203F\u203E", "\u02D8\u203F\u02D8"],
1972
+ sleepy: ["-.-)zzZ", "-_-)zzZ", "\u02D8.\u02D8)zzZ"],
1973
+ excited: ["*o*", "*\u25E1*", "\u2726\u25E1\u2726"],
1974
+ existential: ["\u25C9_\u25C9", "\u2299_\u2299", "\u25C9\u2038\u25C9"]
1975
+ };
1976
+ function getMoodFace(mood, intensity = 0) {
1977
+ const faces = MOOD_FACES[mood];
1978
+ if (!faces) throw new Error(`Unknown mood: ${mood}`);
1979
+ const tier = intensity < 30 ? 0 : intensity <= 70 ? 1 : 2;
1980
+ return faces[tier];
1981
+ }
1982
+ function getStatCosmetics(stats) {
1983
+ const cosmetics = [];
1984
+ if (stats.wisdom > 5) cosmetics.push("wisps");
1985
+ if (stats.endurance > 36e6) cosmetics.push("trail");
1986
+ if (stats.patience > 50) cosmetics.push("zen-prefix");
1987
+ return cosmetics;
1988
+ }
1989
+ function getBoulderForm(agentCount, repoNickname) {
1990
+ let boulder;
1991
+ if (agentCount === void 0 || agentCount <= 0) {
1992
+ boulder = "";
1993
+ } else if (agentCount <= 2) {
1994
+ boulder = "o";
1995
+ } else if (agentCount <= 6) {
1996
+ boulder = "O";
1997
+ } else if (agentCount <= 15) {
1998
+ boulder = "\u25C9";
1999
+ } else if (agentCount <= 35) {
2000
+ boulder = "@";
2001
+ } else {
2002
+ boulder = "@@";
2003
+ }
2004
+ if (repoNickname !== void 0) {
2005
+ boulder = `${boulder} "${repoNickname}"`;
2006
+ }
2007
+ return boulder;
2008
+ }
2009
+ function composeLine(body, cosmetics, boulder) {
2010
+ let b = boulder;
2011
+ let hasZenPrefix = false;
2012
+ if (boulder !== "") {
2013
+ for (const c of cosmetics) {
2014
+ switch (c) {
2015
+ case "wisps":
2016
+ b = `~${b}~`;
2017
+ break;
2018
+ case "trail":
2019
+ b = `${b} ...`;
2020
+ break;
2021
+ case "zen-prefix":
2022
+ hasZenPrefix = true;
2023
+ break;
2024
+ }
2025
+ }
2026
+ } else {
2027
+ if (cosmetics.includes("zen-prefix")) hasZenPrefix = true;
2028
+ }
2029
+ let line = b === "" ? body.replace(" {BOULDER}", "") : body.replace("{BOULDER}", b);
2030
+ if (hasZenPrefix) line = `\u262F ${line}`;
2031
+ return line;
2032
+ }
2033
+ var MOOD_COLORS = {
2034
+ happy: { ansi: 32, tmux: "green" },
2035
+ grinding: { ansi: 33, tmux: "yellow" },
2036
+ frustrated: { ansi: 31, tmux: "red" },
2037
+ zen: { ansi: 36, tmux: "cyan" },
2038
+ sleepy: { ansi: 90, tmux: "colour245" },
2039
+ excited: { ansi: 97, tmux: "white" },
2040
+ existential: { ansi: 35, tmux: "magenta" }
2041
+ };
2042
+ function getMoodAnsiCode(mood) {
2043
+ return MOOD_COLORS[mood].ansi;
2044
+ }
2045
+ function colorize(text, mood, tmux) {
2046
+ const { ansi, tmux: tmuxColor } = MOOD_COLORS[mood];
2047
+ if (tmux) {
2048
+ return `#[fg=${tmuxColor}]${text}#[fg=default]`;
2049
+ }
2050
+ return `\x1B[${ansi}m${text}\x1B[0m`;
2051
+ }
2052
+ function statSummary(stats) {
2053
+ const endH = Math.floor(stats.endurance / 36e5);
2054
+ return `STR:${stats.strength} END:${endH}h WIS:${stats.wisdom} PAT:${stats.patience}`;
2055
+ }
2056
+ function renderCompanion(companion, fields, opts) {
2057
+ const hasFace = fields.includes("face");
2058
+ const hasBoulder = fields.includes("boulder");
2059
+ const repoNickname = opts?.repoPath !== void 0 ? companion.repos[opts.repoPath]?.nickname ?? void 0 : void 0;
2060
+ const boulder = getBoulderForm(opts?.agentCount, repoNickname);
2061
+ const cosmetics = getStatCosmetics(companion.stats);
2062
+ let facePart = null;
2063
+ let boulderOnlyPart = null;
2064
+ if (hasFace) {
2065
+ const baseForm = getBaseForm(companion.level);
2066
+ const intensity = companion.debugMood?.scores[companion.mood] ?? 0;
2067
+ const face = getMoodFace(companion.mood, intensity);
2068
+ const bodyWithFace = baseForm.replace("FACE", face);
2069
+ facePart = composeLine(bodyWithFace, cosmetics, boulder);
2070
+ } else if (hasBoulder) {
2071
+ boulderOnlyPart = boulder;
2072
+ }
2073
+ let commentary = fields.includes("commentary") ? companion.lastCommentary?.text ?? "" : null;
2074
+ const parts = [];
2075
+ for (const field of fields) {
2076
+ switch (field) {
2077
+ case "face":
2078
+ if (facePart !== null) parts.push(facePart);
2079
+ break;
2080
+ case "boulder":
2081
+ if (!hasFace && boulderOnlyPart !== null) parts.push(boulderOnlyPart);
2082
+ break;
2083
+ case "title":
2084
+ parts.push(companion.title);
2085
+ break;
2086
+ case "commentary":
2087
+ if (commentary !== null) parts.push(commentary);
2088
+ break;
2089
+ case "mood":
2090
+ parts.push(`[${companion.mood}]`);
2091
+ break;
2092
+ case "level":
2093
+ parts.push(`Lv ${companion.level}`);
2094
+ break;
2095
+ case "stats":
2096
+ parts.push(statSummary(companion.stats));
2097
+ break;
2098
+ case "achievements":
2099
+ parts.push(`${companion.achievements.length} achievements`);
2100
+ break;
2101
+ case "verb": {
2102
+ const idx = (opts?.verbIndex ?? companion.spinnerVerbIndex) % SPINNER_VERBS.length;
2103
+ parts.push(SPINNER_VERBS[idx]);
2104
+ break;
2105
+ }
2106
+ case "hobby": {
2107
+ const hobbyIdx = ((/* @__PURE__ */ new Date()).getHours() + companion.level) % IDLE_HOBBIES.length;
2108
+ parts.push(IDLE_HOBBIES[hobbyIdx]);
2109
+ break;
2110
+ }
2111
+ }
2112
+ }
2113
+ if (opts?.maxWidth !== void 0) {
2114
+ const maxWidth = opts.maxWidth;
2115
+ const joined = parts.join(" ");
2116
+ const joinedWidth = stringWidth3(joined);
2117
+ if (joinedWidth > maxWidth && commentary !== null && commentary.length > 0) {
2118
+ const commentaryIdx = parts.indexOf(commentary);
2119
+ if (commentaryIdx !== -1) {
2120
+ const commentaryWidth = stringWidth3(commentary);
2121
+ const overhead = joinedWidth - commentaryWidth;
2122
+ const available = maxWidth - overhead - 2;
2123
+ if (available < 0) {
2124
+ parts[commentaryIdx] = "";
2125
+ } else {
2126
+ parts[commentaryIdx] = sliceToWidth(commentary, available);
2127
+ }
2128
+ commentary = parts[commentaryIdx];
2129
+ }
2130
+ }
2131
+ const result2 = parts.filter((p) => p.length > 0).join(" ");
2132
+ const resultWidth = stringWidth3(result2);
2133
+ const final = resultWidth > maxWidth ? sliceToWidth(result2, maxWidth - 1) + "\u2026" : result2;
2134
+ return applyColor(final, fields, facePart, companion.mood, opts);
2135
+ }
2136
+ const result = parts.filter((p) => p.length > 0).join(" ");
2137
+ return applyColor(result, fields, facePart, companion.mood, opts);
2138
+ }
2139
+ function applyColor(result, fields, facePart, mood, opts) {
2140
+ const useColor = opts?.color === true || opts?.tmuxFormat === true;
2141
+ if (!useColor || facePart === null || !fields.includes("face")) return result;
2142
+ const tmux = opts?.tmuxFormat === true;
2143
+ const coloredFace = colorize(facePart, mood, tmux);
2144
+ return result.replace(facePart, coloredFace);
2145
+ }
2146
+
2147
+ // src/daemon/companion.ts
2148
+ init_paths();
2149
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
2150
+ import { randomUUID as randomUUID2 } from "crypto";
2151
+ import { dirname as dirname2, join as join5 } from "path";
2152
+
2153
+ // src/shared/companion-normalize.ts
2154
+ function emptyStats() {
2155
+ return { count: 0, mean: 0, m2: 0 };
2156
+ }
2157
+ function defaultBaselines() {
2158
+ return {
2159
+ sessionMs: emptyStats(),
2160
+ cycleCount: emptyStats(),
2161
+ agentCount: emptyStats(),
2162
+ sessionsPerDay: emptyStats(),
2163
+ recentAgentThroughput: emptyStats(),
2164
+ lastCountedDay: null,
2165
+ pendingDayCount: 0
2166
+ };
2167
+ }
2168
+ function normalizeCompanion(state2) {
2169
+ if (state2.stats == null) state2.stats = { strength: 0, endurance: 0, wisdom: 0, patience: 0 };
2170
+ if (state2.level == null) state2.level = 1;
2171
+ if (state2.xp == null) state2.xp = 0;
2172
+ if (state2.title == null) state2.title = "Boulder Intern";
2173
+ if (state2.mood == null) state2.mood = "sleepy";
2174
+ if (state2.achievements == null) state2.achievements = [];
2175
+ if (state2.repos == null) state2.repos = {};
2176
+ if (state2.lastCommentary === void 0) state2.lastCommentary = null;
2177
+ if (state2.sessionsCompleted == null) state2.sessionsCompleted = 0;
2178
+ if (state2.sessionsCrashed == null) state2.sessionsCrashed = 0;
2179
+ if (state2.totalActiveMs == null) state2.totalActiveMs = 0;
2180
+ if (state2.consecutiveCleanSessions == null) state2.consecutiveCleanSessions = 0;
2181
+ if (state2.consecutiveDaysActive == null) state2.consecutiveDaysActive = 0;
2182
+ if (state2.lastActiveDate === void 0) state2.lastActiveDate = null;
2183
+ if (state2.taskHistory == null) state2.taskHistory = {};
2184
+ if (state2.dailyRepos == null) state2.dailyRepos = {};
2185
+ if (state2.recentCompletions == null) state2.recentCompletions = [];
2186
+ if (state2.lifetimeAgentsSpawned == null) state2.lifetimeAgentsSpawned = 0;
2187
+ if (state2.consecutiveEfficientSessions == null) state2.consecutiveEfficientSessions = 0;
2188
+ if (state2.consecutiveHighCycleSessions == null) state2.consecutiveHighCycleSessions = 0;
2189
+ if (state2.spinnerVerbIndex == null) state2.spinnerVerbIndex = 0;
2190
+ if (state2.baselines == null) state2.baselines = defaultBaselines();
2191
+ if (state2.baselines.recentAgentThroughput == null) state2.baselines.recentAgentThroughput = emptyStats();
2192
+ if (state2.commentaryHistory == null) state2.commentaryHistory = [];
2193
+ if (state2.feedbackHistory == null) state2.feedbackHistory = [];
2194
+ return state2;
2195
+ }
2196
+
2197
+ // src/shared/companion-types.ts
2198
+ var OBSERVATION_CATEGORIES = ["session-sentiments", "repo-impressions", "user-patterns", "notable-moments"];
2199
+ var ACHIEVEMENTS = [
2200
+ // Milestone (25)
2201
+ { id: "first-blood", name: "First Blood", category: "milestone", description: "Complete your first session.", badge: null },
2202
+ { id: "regular", name: "Regular", category: "milestone", description: "Complete 10 sessions.", badge: null },
2203
+ { id: "centurion", name: "Centurion", category: "milestone", description: "Complete 100 sessions.", badge: null },
2204
+ { id: "veteran", name: "Veteran", category: "milestone", description: "Complete 500 sessions.", badge: null },
2205
+ { id: "thousand-boulder", name: "Thousand Boulder", category: "milestone", description: "Complete 1,000 sessions.", badge: null },
2206
+ { id: "cartographer", name: "Cartographer", category: "milestone", description: "Work in 5 different repos.", badge: "+" },
2207
+ { id: "world-traveler", name: "World Traveler", category: "milestone", description: "Work in 15 different repos.", badge: null },
2208
+ { id: "omnipresent", name: "Omnipresent", category: "milestone", description: "Work in 30 different repos.", badge: null },
2209
+ { id: "swarm-starter", name: "Swarm Starter", category: "milestone", description: "Spawn 50 agents over a lifetime.", badge: null },
2210
+ { id: "hive-mind", name: "Hive Mind", category: "milestone", description: "Spawn 500 agents over a lifetime.", badge: null },
2211
+ { id: "legion", name: "Legion", category: "milestone", description: "Spawn 2,000 agents over a lifetime.", badge: null },
2212
+ { id: "army-of-thousands", name: "Army of Thousands", category: "milestone", description: "Spawn 5,000 agents over a lifetime.", badge: null },
2213
+ { id: "singularity", name: "Singularity", category: "milestone", description: "Spawn 10,000 agents over a lifetime.", badge: null },
2214
+ { id: "first-shift", name: "First Shift", category: "milestone", description: "10 hours of total agent active time.", badge: null },
2215
+ { id: "workaholic", name: "Workaholic", category: "milestone", description: "100 hours of total agent active time.", badge: null },
2216
+ { id: "time-lord", name: "Time Lord", category: "milestone", description: "500 hours of total agent active time.", badge: null },
2217
+ { id: "eternal-grind", name: "Eternal Grind", category: "milestone", description: "2,000 hours of total agent active time.", badge: null },
2218
+ { id: "epoch", name: "Epoch", category: "milestone", description: "5,000 hours of total agent active time.", badge: null },
2219
+ { id: "old-growth", name: "Old Growth", category: "milestone", description: "Companion is 14 days old.", badge: null },
2220
+ { id: "seasoned", name: "Seasoned", category: "milestone", description: "Companion is 90 days old.", badge: null },
2221
+ { id: "ancient", name: "Ancient", category: "milestone", description: "Companion is 365 days old.", badge: null },
2222
+ { id: "apprentice", name: "Apprentice", category: "milestone", description: "Reach level 5.", badge: null },
2223
+ { id: "journeyman", name: "Journeyman", category: "milestone", description: "Reach level 15.", badge: null },
2224
+ { id: "master", name: "Master", category: "milestone", description: "Reach level 30.", badge: null },
2225
+ { id: "grandmaster", name: "Grandmaster", category: "milestone", description: "Reach level 50.", badge: null },
2226
+ // Session (19)
2227
+ { id: "marathon", name: "Marathon", category: "session", description: "Complete a session with 15+ agents.", badge: "~^~" },
2228
+ { id: "squad", name: "Squad Up", category: "session", description: "Complete a session with 10+ agents.", badge: null },
2229
+ { id: "battalion", name: "Battalion", category: "session", description: "Complete a session with 25+ agents.", badge: null },
2230
+ { id: "swarm", name: "The Swarm", category: "session", description: "Complete a session with 50+ agents.", badge: null },
2231
+ { id: "blitz", name: "Blitz", category: "session", description: "Complete a session in under 5 minutes.", badge: null },
2232
+ { id: "speed-run", name: "Speed Run", category: "session", description: "Complete a session in under 15 minutes.", badge: null },
2233
+ { id: "flash", name: "Flash", category: "session", description: "Complete a session in under 2 minutes.", badge: null },
2234
+ { id: "flawless", name: "Flawless", category: "session", description: "Complete a session with 10+ agents and zero crashes.", badge: "*" },
2235
+ { id: "speed-demon", name: "Speed Demon", category: "session", description: "10 consecutive sessions completing in 3 or fewer cycles.", badge: "\u26A1" },
2236
+ { id: "iron-will", name: "Iron Will", category: "session", description: "5 consecutive sessions each with 8+ orchestrator cycles.", badge: "[]" },
2237
+ { id: "glass-cannon", name: "Glass Cannon", category: "session", description: "5+ agents, all crashed, but session completed anyway.", badge: null },
2238
+ { id: "solo", name: "Solo", category: "session", description: "Complete a session with exactly one agent.", badge: null },
2239
+ { id: "one-more-cycle", name: "One More Cycle", category: "session", description: "A session with 10+ orchestrator cycles.", badge: null },
2240
+ { id: "deep-dive", name: "Deep Dive", category: "session", description: "A session with 15+ orchestrator cycles.", badge: null },
2241
+ { id: "abyss", name: "Into the Abyss", category: "session", description: "A session with 25+ orchestrator cycles.", badge: null },
2242
+ { id: "eternal-recurrence", name: "Eternal Recurrence", category: "session", description: "A session with 40+ orchestrator cycles.", badge: null },
2243
+ { id: "endurance", name: "Endurance", category: "session", description: "A single session running 4+ hours.", badge: null },
2244
+ { id: "ultramarathon", name: "Ultramarathon", category: "session", description: "A single session running 6+ hours.", badge: null },
2245
+ { id: "one-shot", name: "One Shot", category: "session", description: "Complete with 5+ agents in exactly 1 orchestrator cycle.", badge: null },
2246
+ { id: "quick-draw", name: "Quick Draw", category: "session", description: "First agent spawned within 20s of session start.", badge: null },
2247
+ // Time (6)
2248
+ { id: "night-owl", name: "Night Owl", category: "time", description: "Complete a session started between 1am and 5am.", badge: ")" },
2249
+ { id: "dawn-patrol", name: "Dawn Patrol", category: "time", description: "Session running 3+ hours that spans midnight to 6am.", badge: null },
2250
+ { id: "early-bird", name: "Early Bird", category: "time", description: "Start a session before 6am.", badge: null },
2251
+ { id: "weekend-warrior", name: "Weekend Warrior", category: "time", description: "Complete a session on a Saturday or Sunday.", badge: null },
2252
+ { id: "all-nighter", name: "All-Nighter", category: "time", description: "Single session running 5+ hours.", badge: null },
2253
+ { id: "witching-hour", name: "Witching Hour", category: "time", description: "Start a session between 3am and 4am.", badge: null },
2254
+ // Behavioral (16)
2255
+ { id: "sisyphean", name: "Sisyphean", category: "behavioral", description: "Restart the same task 3+ times.", badge: ";" },
2256
+ { id: "stubborn", name: "Stubborn", category: "behavioral", description: "Restart the same task 5+ times and eventually complete it.", badge: null },
2257
+ { id: "one-must-imagine", name: "One Must Imagine", category: "behavioral", description: "Restart the same task 10+ times.", badge: null },
2258
+ { id: "creature-of-habit", name: "Creature of Habit", category: "behavioral", description: "Visit the same repo 10 times.", badge: null },
2259
+ { id: "loyal", name: "Loyal", category: "behavioral", description: "Visit the same repo 30 times.", badge: null },
2260
+ { id: "wanderer", name: "Wanderer", category: "behavioral", description: "3+ different repos in a single calendar day.", badge: null },
2261
+ { id: "streak", name: "Streak", category: "behavioral", description: "7 consecutive days with at least one session.", badge: null },
2262
+ { id: "iron-streak", name: "Iron Streak", category: "behavioral", description: "14 consecutive days with at least one session.", badge: null },
2263
+ { id: "hot-streak", name: "Hot Streak", category: "behavioral", description: "15 consecutive clean sessions.", badge: null },
2264
+ { id: "momentum", name: "Momentum", category: "behavioral", description: "5 sessions completed within 4 hours.", badge: null },
2265
+ { id: "overdrive", name: "Overdrive", category: "behavioral", description: "Complete 6+ sessions in a single calendar day.", badge: null },
2266
+ { id: "patient-one", name: "Patient One", category: "behavioral", description: "Idle 30+ minutes between cycles in a session.", badge: null },
2267
+ { id: "message-in-a-bottle", name: "Message in a Bottle", category: "behavioral", description: "10+ messages sent to a single session.", badge: null },
2268
+ { id: "deep-conversation", name: "Deep Conversation", category: "behavioral", description: "Send 20+ messages to a single session.", badge: null },
2269
+ { id: "comeback-kid", name: "Comeback Kid", category: "behavioral", description: "Resume a paused/killed session and complete it.", badge: null },
2270
+ { id: "pair-programming", name: "Pair Programming", category: "behavioral", description: "8+ user messages during a single active session.", badge: null }
2271
+ ];
2272
+
2273
+ // src/daemon/companion-memory.ts
2274
+ init_paths();
2275
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
2276
+ import { dirname, join as join4 } from "path";
2277
+ import { randomUUID } from "crypto";
2278
+ import { z } from "zod";
2279
+
2280
+ // src/daemon/haiku.ts
2281
+ import { query, createSdkMcpServer } from "@r-cli/sdk";
2282
+
2283
+ // src/shared/env.ts
2284
+ import { resolve } from "path";
2285
+ function augmentedPath() {
2286
+ const rawPath = process.env["PATH"];
2287
+ const basePath = rawPath !== void 0 && rawPath.length > 0 ? rawPath : "/usr/bin:/bin";
2288
+ const home = process.env["HOME"];
2289
+ const candidates = [
2290
+ ...home ? [`${home}/.local/bin`] : [],
2291
+ // Claude CLI, pipx, user-local installs
2292
+ resolve(process.execPath, ".."),
2293
+ // Node.js bin dir (ensures node/npm available)
2294
+ "/opt/homebrew/bin",
2295
+ // Homebrew (Apple Silicon macOS)
2296
+ "/opt/homebrew/sbin",
2297
+ // Homebrew sbin
2298
+ "/usr/local/bin",
2299
+ // Homebrew (Intel macOS), manual installs
2300
+ "/usr/local/sbin",
2301
+ // Manual installs
2302
+ "/opt/local/bin",
2303
+ // MacPorts
2304
+ "/opt/local/sbin",
2305
+ // MacPorts
2306
+ "/home/linuxbrew/.linuxbrew/bin"
2307
+ // Linuxbrew
2308
+ ];
2309
+ const nixProfile = process.env["NIX_PROFILES"];
2310
+ if (nixProfile) {
2311
+ for (const p of nixProfile.split(" ").reverse()) {
2312
+ candidates.push(`${p}/bin`);
2313
+ }
2314
+ }
2315
+ const existing = new Set(basePath.split(":"));
2316
+ const prepend = candidates.filter((dir) => !existing.has(dir));
2317
+ return prepend.length > 0 ? `${prepend.join(":")}:${basePath}` : basePath;
2318
+ }
2319
+ function execEnv() {
2320
+ return {
2321
+ ...process.env,
2322
+ PATH: augmentedPath()
2323
+ };
2324
+ }
2325
+
2326
+ // src/daemon/haiku.ts
2327
+ var COOLDOWN_MS = 5 * 60 * 1e3;
2328
+
2329
+ // src/daemon/companion-memory.ts
2330
+ var OBSERVATION_TEXT_REJECT_RE = /[<>]/;
2331
+ var CONTROL_CHARS_DETECT_RE = /[\x00-\x1f\x7f]/;
2332
+ function isSafeObservationText(text) {
2333
+ if (OBSERVATION_TEXT_REJECT_RE.test(text)) return false;
2334
+ if (CONTROL_CHARS_DETECT_RE.test(text)) return false;
2335
+ return true;
2336
+ }
2337
+ var writeQueue = Promise.resolve();
2338
+ var OBSERVATION_JSON_SCHEMA = {
2339
+ type: "object",
2340
+ properties: {
2341
+ category: {
2342
+ type: "string",
2343
+ enum: [...OBSERVATION_CATEGORIES],
2344
+ description: "Which of the four observation categories best fits this observation"
2345
+ },
2346
+ text: {
2347
+ type: "string",
2348
+ minLength: 10,
2349
+ maxLength: 180,
2350
+ description: "One sentence, first-person, no angle brackets or control characters"
2351
+ }
2352
+ },
2353
+ required: ["category", "text"],
2354
+ additionalProperties: false
2355
+ };
2356
+ var ObservationZodSchema = z.object({
2357
+ category: z.enum(OBSERVATION_CATEGORIES),
2358
+ text: z.string().min(10).max(180).refine(isSafeObservationText, "contains unsafe characters")
2359
+ });
2360
+
2361
+ // src/daemon/companion.ts
2362
+ function computeLevelProgress(xp) {
2363
+ let threshold = 150;
2364
+ let cumulative = 0;
2365
+ while (cumulative + threshold <= xp) {
2366
+ cumulative += threshold;
2367
+ threshold = Math.floor(threshold * 1.35);
2368
+ }
2369
+ return { xpIntoLevel: xp - cumulative, xpForNextLevel: threshold };
2370
+ }
2371
+
2372
+ // src/shared/companion-badges.ts
2373
+ var BADGE_ART = {
2374
+ // ── Milestone ─────────────────────────────────────────────────────────────
2375
+ "first-blood": [
2376
+ " \u2571\u2572 ",
2377
+ " \u2571 \u2572 ",
2378
+ " \u2571 \u25C6\u25C6 \u2572 ",
2379
+ " \u2571 \u25C6\u25C6 \u2572 ",
2380
+ " \u2571 \u25C6\u25C6 \u2572 ",
2381
+ " \u2571 \u25C6\u25C6 \u2572 ",
2382
+ " \u2571\u2500\u2500\u2500\u2500\u2500\u25C6\u25C6\u2500\u2500\u2500\u2500\u2500\u2572 ",
2383
+ " \u2572 \u25C6\u25C6 \u2571 ",
2384
+ " \u2572 \u25C6\u25C6 \u2571 ",
2385
+ " \u2572\u2500\u2500\u2500\u25C6\u25C6\u2500\u2500\u2500\u2571 ",
2386
+ " \u2502\u2502 ",
2387
+ " \u2550\u2550\u256A\u256A\u2550\u2550 "
2388
+ ],
2389
+ "centurion": [
2390
+ " \u250C\u2500\u2500\u2550\u2550\u2550\u2550\u2550\u2500\u2500\u2510 ",
2391
+ " \u2502 \u2554\u2550\u2550\u2550\u2557 \u2502 ",
2392
+ " \u2571\u2502 \u2551100\u2551 \u2502\u2572 ",
2393
+ " \u2571 \u2502 \u255A\u2550\u2550\u2550\u255D \u2502 \u2572 ",
2394
+ " \u2571 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2572 ",
2395
+ " \u2572 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2571 ",
2396
+ " \u2572 \u2502 \u2605 \u2605 \u2605 \u2502 \u2571 ",
2397
+ " \u2572 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2571 ",
2398
+ " \u2572\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2571 "
2399
+ ],
2400
+ "thousand-boulder": [
2401
+ " \u256D\u2501\u2501\u2501\u256E ",
2402
+ " \u256D\u2501\u252B1K \u2523\u2501\u256E ",
2403
+ " \u256D\u2501\u252B \u2570\u2501\u2501\u2501\u256F \u2523\u2501\u256E ",
2404
+ " \u2503 \u25C9\u25C9\u25C9\u25C9\u25C9\u25C9 \u2503 ",
2405
+ " \u2503 \u25C9\u25C9\u25C9\u25C9\u25C9\u25C9\u25C9\u25C9 \u2503 ",
2406
+ " \u2503 \u25C9\u25C9\u25C9\u25C9\u25C9\u25C9 \u2503 ",
2407
+ " \u2570\u2501\u252B \u2523\u2501\u256F ",
2408
+ " \u2570\u2501\u252B \u2523\u2501\u256F ",
2409
+ " \u2570\u2501\u2501\u2501\u256F "
2410
+ ],
2411
+ "cartographer": [
2412
+ " N ",
2413
+ " \u25B3 ",
2414
+ " \u256D\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u256E ",
2415
+ " \u2502 \xB7 \u2502 \xB7 \u2502 ",
2416
+ " W\u253C\xB7\xB7\xB7\xB7+\xB7\xB7\xB7\xB7\u253CE ",
2417
+ " \u2502 \xB7 \u2502 \xB7 \u2502 ",
2418
+ " \u2570\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u256F ",
2419
+ " \u25BD ",
2420
+ " S "
2421
+ ],
2422
+ "world-traveler": [
2423
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
2424
+ " \u256D\u2500\u2500\u2524 \u251C\u2500\u2500\u256E ",
2425
+ " \u2571 \xB7\u2502 \u25E0\u25E1\u25E0 \u2502\xB7 \u2572 ",
2426
+ " \u2502 \xB7 \u2502\u25E0 \u25E1\u2502 \xB7 \u2502 ",
2427
+ " \u2502 \xB7 \u2502 \u25E1\u25E0\u25E1 \u2502 \xB7 \u2502 ",
2428
+ " \u2572 \xB7\u2502 \u2502\xB7 \u2571 ",
2429
+ " \u2570\u2500\u2500\u2524 \u251C\u2500\u2500\u256F ",
2430
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F "
2431
+ ],
2432
+ "hive-mind": [
2433
+ " \u2571\u2572 \u2571\u2572 \u2571\u2572 ",
2434
+ " \u2571\u25C6\u25C6\u2572\u2571\u25C6\u25C6\u2572\u2571\u25C6\u25C6\u2572 ",
2435
+ " \u2572\u25C6\u25C6\u2571\u2572\u25C6\u25C6\u2571\u2572\u25C6\u25C6\u2571 ",
2436
+ " \u2571\u2572\u2571\u2571\u25C6\u2572\u2571\u2571\u25C6\u2572\u2572\u2571\u2572 ",
2437
+ " \u2571\u25C6\u25C6\u2572\u2572\u25C6\u25C6\u2572\u2572\u25C6\u25C6\u2571\u25C6\u25C6\u2572 ",
2438
+ " \u2572\u25C6\u25C6\u2571\u2571\u25C6\u25C6\u2571\u2571\u25C6\u25C6\u2572\u25C6\u25C6\u2571 ",
2439
+ " \u2572\u2571\u2572\u2572\u25C6\u2571\u2572\u2572\u25C6\u2571\u2571\u2572\u2571 ",
2440
+ " \u2572\u25C6\u25C6\u2571\u2572\u25C6\u25C6\u2571\u2572\u25C6\u25C6\u2571 ",
2441
+ " \u2572\u2571 \u2572\u2571 \u2572\u2571 "
2442
+ ],
2443
+ "old-growth": [
2444
+ " \u2571\u2572 ",
2445
+ " \u2571\u2571\u2572\u2572 ",
2446
+ " \u2571\u2571 \u2572\u2572 ",
2447
+ " \u2571\u2571\u2571\u2572\u2571\u2572\u2572\u2572 ",
2448
+ " \u2571\u2571\u2571 \u2572\u2572\u2572 ",
2449
+ " \u2571\u2571\u2571\u2571\u2572\u2571\u2572\u2571\u2572\u2572\u2572\u2572 ",
2450
+ " \u2551\u2551\u2551 ",
2451
+ " \u2551\u2551\u2551 ",
2452
+ " \u2550\u2550\u2550\u2569\u2569\u2569\u2550\u2550\u2550 "
2453
+ ],
2454
+ "ancient": [
2455
+ " \u250C\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2510 ",
2456
+ " \u2502 \u2502 \u25C9 \u25C9 \u2502 \u2502 ",
2457
+ " \u2502 \u2502 \u25BD \u2502 \u2502 ",
2458
+ " \u2554\u2550\u2567\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2567\u2550\u2557 ",
2459
+ " \u2551 A N C I E N \u2551 ",
2460
+ " \u2551 T \u2551 ",
2461
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D ",
2462
+ " \u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571 ",
2463
+ " \u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594 "
2464
+ ],
2465
+ "regular": [
2466
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u256E ",
2467
+ " \u256D\u2500\u2524 10 \u251C\u2500\u256E ",
2468
+ " \u2571 \u2570\u2500\u2500\u2500\u2500\u2500\u256F \u2572 ",
2469
+ " \u2502 \u256D\u2500\u2500\u2500\u2500\u2500\u256E \u2502 ",
2470
+ " \u2502 \u2502 \u25C9 \u2502 \u2502 ",
2471
+ " \u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u256F \u2502 ",
2472
+ " \u2572 \u2571 ",
2473
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F "
2474
+ ],
2475
+ "veteran": [
2476
+ " \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 ",
2477
+ " \u2551 \u2554\u2550\u2550\u2550\u2550\u2550\u2557 \u2551 ",
2478
+ " \u2551 \u2551 V \u2551 \u2551 ",
2479
+ " \u2551 \u2551 500 \u2551 \u2551 ",
2480
+ " \u2551 \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u2551 ",
2481
+ " \u2551 \u2605 \u2605 \u2605 \u2605 \u2605 \u2551 ",
2482
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D ",
2483
+ " \u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571 ",
2484
+ " \u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594 "
2485
+ ],
2486
+ "swarm-starter": [
2487
+ " ",
2488
+ " \u25C6 \u25C6 \u25C6 \u25C6 \u25C6 ",
2489
+ " \u25C6 \u25C6 \u25C6 \u25C6 \u25C6 ",
2490
+ " \u25C6 \u25C6 \u25C6 \u25C6 \u25C6 ",
2491
+ " \u25C6 \u25C6 \u25C6 \u25C6 \u25C6 ",
2492
+ " \u25C6 \u25C6 \u25C6 \u25C6 \u25C6 ",
2493
+ " ",
2494
+ " \xB7 50 out \xB7 "
2495
+ ],
2496
+ "legion": [
2497
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 ",
2498
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 ",
2499
+ " \u25C6\u25C6\u25C6 2K \u25C6\u25C6\u25C6\u25C6\u25C6 ",
2500
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 ",
2501
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 ",
2502
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 ",
2503
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 "
2504
+ ],
2505
+ "army-of-thousands": [
2506
+ " \xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7 ",
2507
+ " \xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6 ",
2508
+ " \xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7 ",
2509
+ " \xB7\u25C6\xB7\u25C6\xB75K\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7 ",
2510
+ " \xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7 ",
2511
+ " \xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6 ",
2512
+ " \xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7 ",
2513
+ " \xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6 ",
2514
+ " \xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7 "
2515
+ ],
2516
+ "singularity": [
2517
+ " \u2572 \u2502 \u2571 \u2500 \xB7 ",
2518
+ " \u2572 \u2502 \u2571 ",
2519
+ " \u2572\u2502\u2571 ",
2520
+ " \u2500\u2500\u2500\u2500\u25C6\u2500\u2500\u2500\u2500 ",
2521
+ " \u2571\u2502\u2572 ",
2522
+ " \u2571 \u2502 \u2572 ",
2523
+ " \u2571 \u2502 \u2572 \u2500 \xB7 ",
2524
+ " 10000 agents "
2525
+ ],
2526
+ "first-shift": [
2527
+ " \u250C\u2500\u2500\u2500\u2500\u2500\u2510 ",
2528
+ " \u2502 \u2572 \u2502 ",
2529
+ " \u2571\u2502 \u2572 \u2502\u2572 ",
2530
+ " \u2571 \u2502 \u2572 \u2502 \u2572 ",
2531
+ " \u2572 \u2502 \u2572\u2502 \u2571 ",
2532
+ " \u2572\u2502 \u2502\u2571 ",
2533
+ " \u2502 \u2571 \u2502 ",
2534
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2518 ",
2535
+ " 10 hrs "
2536
+ ],
2537
+ "workaholic": [
2538
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
2539
+ " \u2571 12 \u2572 ",
2540
+ " \u2502 \xB7 \u2502 \u2502 ",
2541
+ " \u25029 \xB7 \u2502 3 \u2502 ",
2542
+ " \u2502 \u2572 \u2502 ",
2543
+ " \u2572 6 \xB7 \u2571 ",
2544
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F ",
2545
+ " 100 hrs "
2546
+ ],
2547
+ "time-lord": [
2548
+ " \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 ",
2549
+ " \u2571 \u2572 ",
2550
+ " \u2502 \xB7 11 12 1 \xB7 \u2502 ",
2551
+ " \u2502 10 \u2572 2 \u2502 ",
2552
+ " \u2502 9 \xB7 3 \u2502 ",
2553
+ " \u2502 8 4 \u2502 ",
2554
+ " \u2572 7 5 \u2571 ",
2555
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D ",
2556
+ " 500 hrs "
2557
+ ],
2558
+ "eternal-grind": [
2559
+ " \u256D\u2500\u2500\u2500\u256E \u256D\u2500\u2500\u2500\u256E ",
2560
+ " \u2571 \u2572 \u2571 \u2572 ",
2561
+ " \u2502 \u221E \u2573 \u221E \u2502 ",
2562
+ " \u2572 \u2571 \u2572 \u2571 ",
2563
+ " \u2570\u2500\u2500\u2500\u256F \u2570\u2500\u2500\u2500\u256F ",
2564
+ " \u250C\u2500\u2500\u2500\u2500\u2500\u2510 ",
2565
+ " \u2502 \u2572 \u2571 \u2502 ",
2566
+ " \u2502 \xD7 \u2502 ",
2567
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2518 "
2568
+ ],
2569
+ "epoch": [
2570
+ " \xB7 \u2605 \xB7 ",
2571
+ " \xB7 \u2571\u2502\u2572 \xB7 ",
2572
+ " \u2572 \u2571 \u2502 \u2572 \u2571 ",
2573
+ " \u2572\u2571 \u2502 \u2572\u2571 ",
2574
+ " \u2500\u2500\u2500\u2500\u25C6\u2500\u2500\u253C\u2500\u2500\u25C6\u2500\u2500\u2500\u2500 ",
2575
+ " \u2571\u2572 \u2502 \u2571\u2572 ",
2576
+ " \u2571 \u2572 \u2502 \u2571 \u2572 ",
2577
+ " \xB7 \u2572\u2502\u2571 \xB7 ",
2578
+ " 5000 hrs "
2579
+ ],
2580
+ "seasoned": [
2581
+ " \u2571\u2572 ",
2582
+ " \u2571 \u2572 ",
2583
+ " \u2571\u2571\u2572\u2571\u2572\u2572 ",
2584
+ " \u2571\u2571 \u2572\u2572 ",
2585
+ " \u2571\u2571\u2571\u2571\u2572\u2571\u2572\u2572\u2572\u2572 ",
2586
+ " \u2551\u2551 ",
2587
+ " \u2500\u2500\u2500\u2500\u2500\u2568\u2568\u2500\u2500\u2500\u2500\u2500 ",
2588
+ " \u2572\u2572\u2572\u2572\u2572\u2572\u2572\u2572\u2572\u2572\u2572\u2572\u2572 ",
2589
+ " 90 day roots "
2590
+ ],
2591
+ "omnipresent": [
2592
+ " N ",
2593
+ " \xB7 \u25B3 \xB7 ",
2594
+ " NW \u256D\u2500\u253C\u2500\u256E NE ",
2595
+ " \xB7 \u256D\u2500\u2524\xB7+\xB7\u251C\u2500\u256E \xB7 ",
2596
+ " W\u2500\u2500\u2524\xB7\u2502 \xB7 \u2502\xB7\u251C\u2500\u2500E ",
2597
+ " \xB7 \u2570\u2500\u2524\xB7+\xB7\u251C\u2500\u256F \xB7 ",
2598
+ " SW \u2570\u2500\u253C\u2500\u256F SE ",
2599
+ " \xB7 \u25BD \xB7 ",
2600
+ " S "
2601
+ ],
2602
+ "apprentice": [
2603
+ " ",
2604
+ " \u2571\u2572 ",
2605
+ " \u2571 \u2572 ",
2606
+ " \u2571 \u2572 ",
2607
+ " \u2571 \u2572 ",
2608
+ " \u2571\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2572 ",
2609
+ " \u2571 level 5 \u2572 ",
2610
+ " \u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594 "
2611
+ ],
2612
+ "journeyman": [
2613
+ " \u2571\u2572 ",
2614
+ " \u2571 \u2572 \u2571\u2572 ",
2615
+ " \u2571 \u2572 \u2571 \u2572 ",
2616
+ " \u2571 \u2573 \u2572 ",
2617
+ " \u2571 \u2571 \u2572 \u2572 ",
2618
+ " \u2571\u2500\u2500\u2500\u2500\u2500\u2500\u2571\u2500\u2500\u2500\u2572\u2500\u2500\u2500\u2500\u2572",
2619
+ " \u2571 level 15 ",
2620
+ " \u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594 "
2621
+ ],
2622
+ "master": [
2623
+ " \u2605\u2572\u2571\u2605 ",
2624
+ " \u2571\u2572 ",
2625
+ " \u2571 \u2572 ",
2626
+ " \u2571\u2571\u2572\u2571\u2572\u2572 ",
2627
+ " \u2571\u2571 \u2572\u2572 ",
2628
+ " \u2571\u2571 \u2572\u2572 ",
2629
+ " \u2571\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2572\u2572 ",
2630
+ " \u2571 level 30 \u2572 ",
2631
+ " \u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594 "
2632
+ ],
2633
+ "grandmaster": [
2634
+ " \u2605 \u2605 \u2605 ",
2635
+ " \u2572\u2502\u2571 ",
2636
+ " \u2571\u2572 ",
2637
+ " \u2571 \u2572 ",
2638
+ " \u2571\u2571\u2572\u2571\u2572\u2572 ",
2639
+ " \u2571\u2571 \u2572\u2572 ",
2640
+ " \u2571\u2571 \u2605 \u2572\u2572 ",
2641
+ " \u2571\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2572\u2572 ",
2642
+ " \u2571 level 50 \u2572 "
2643
+ ],
2644
+ // ── Session ───────────────────────────────────────────────────────────────
2645
+ "marathon": [
2646
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
2647
+ " \u2502 \u2571\u2572 \u2571\u2572 \u2571\u2572 \u2571\u2572 \u2502 ",
2648
+ " \u2502\u2571 \u2573 \u2573 \u2573 \u2572 \u2502 ",
2649
+ " \u2502 \u2571\u2572 \u2571\u2572 \u2571\u2572 \u2502 ",
2650
+ " \u2502 \u2571 \u2573 \u2573 \u2572 \u2502 ",
2651
+ " \u2502 \u2571 \u2571 \u2572\u2571 \u2572 \u2572 \u2502 ",
2652
+ " \u2502\u2571 \u2571 \u2572 \u2572\u2502 ",
2653
+ " \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 ",
2654
+ " \u2502 ~ ^ ~ \u2502 ",
2655
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F "
2656
+ ],
2657
+ "blitz": [
2658
+ " \u2571\u2571 ",
2659
+ " \u2571\u2571 ",
2660
+ " \u2571\u2571 ",
2661
+ " \u2571\u2571\u2571\u2571\u2571\u2571 ",
2662
+ " \u2571\u2571 ",
2663
+ " \u2571\u2571 ",
2664
+ " \u2571\u2571 ",
2665
+ " \u2571\u2571 "
2666
+ ],
2667
+ "speed-run": [
2668
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
2669
+ " \u2571 \u2572 ",
2670
+ " \u2502 10 \u2571\u2571 \u2502 ",
2671
+ " \u2502 \xB7 \u2571\u2571 \u2502 ",
2672
+ " \u2502 \u2571 \u2571\u2571 \u2502 ",
2673
+ " \u2502 \u2571 \u2571\u2571 \u2502 ",
2674
+ " \u2572 \u2571\u2571 \u2571 ",
2675
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F "
2676
+ ],
2677
+ "flawless": [
2678
+ " \u2726 \u2726 ",
2679
+ " \u2726 \u2571\u2572 \u2726 ",
2680
+ " \u2554\u2550\u2550\u2567\u2550\u2550\u2550\u2557 ",
2681
+ " \u2726 \u2551 \u2551 \u2726 ",
2682
+ " \u2551 \u25C6 \u2551 ",
2683
+ " \u2551 \u2551 ",
2684
+ " \u2726 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u2726 ",
2685
+ " \u2726 \u2726 "
2686
+ ],
2687
+ "speed-demon": [
2688
+ " ",
2689
+ " \u2571\u2571 ",
2690
+ " \u2571\u2571 ",
2691
+ " \u2571\u2571\u2571\u2571\u2571\u2571 ",
2692
+ " \u2571\u2571 ",
2693
+ " \u2571\u2571 ",
2694
+ " \u26A1 \u22643 \u26A1 ",
2695
+ " x10 streak "
2696
+ ],
2697
+ "iron-will": [
2698
+ " \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 ",
2699
+ " \u2551 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2551 ",
2700
+ " \u2551 \u2502\u2554\u2550\u2550\u2550\u2550\u2550\u2557\u2502 \u2551 ",
2701
+ " \u2551 \u2502\u2551 8+ \u2551\u2502 \u2551 ",
2702
+ " \u2551 \u2502\u2551cycle\u2551\u2502 \u2551 ",
2703
+ " \u2551 \u2502\u255A\u2550\u2550\u2550\u2550\u2550\u255D\u2502 \u2551 ",
2704
+ " \u2551 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2551 ",
2705
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D "
2706
+ ],
2707
+ "glass-cannon": [
2708
+ " \u2571\u2572 ",
2709
+ " \u2571\u2571\u2572\u2572 ",
2710
+ " \u2571\u2571 \u2572\u2572 ",
2711
+ " \u2571\u2571 \u2573\u2573 \u2572\u2572 ",
2712
+ " \u2571\u2571 \u2573\u2573 \u2572\u2572 ",
2713
+ " \u2572\u2572 \u2573\u2573 \u2571\u2571 ",
2714
+ " \u2572\u2572 \u2571\u2571 ",
2715
+ " \u2572\u2572 \u2571\u2571 ",
2716
+ " \u2572\u2572\u2571\u2571 ",
2717
+ " \u2550\u2550\u2550\u2567\u2567\u2550\u2550\u2550 "
2718
+ ],
2719
+ "solo": [
2720
+ " ",
2721
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
2722
+ " \u2502 \u2502 ",
2723
+ " \u2502 \u25C6 \u2502 ",
2724
+ " \u2502 \u2502 ",
2725
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F ",
2726
+ " "
2727
+ ],
2728
+ "one-more-cycle": [
2729
+ " \u256D\u2500\u2500\u2192\u2500\u2500\u256E ",
2730
+ " \u2502 \u2502 ",
2731
+ " \u2191 \u221E \u2193 ",
2732
+ " \u2502 \u2502 ",
2733
+ " \u2570\u2500\u2500\u2190\u2500\u2500\u256F ",
2734
+ " ",
2735
+ " \u256D\u2500\u2500\u2192\u2500\u2500\u256E ",
2736
+ " \u2502 \u2502 ",
2737
+ " \u2191 \u221E \u2193 ",
2738
+ " \u2502 \u2502 ",
2739
+ " \u2570\u2500\u2500\u2190\u2500\u2500\u256F "
2740
+ ],
2741
+ "quick-draw": [
2742
+ " \u2571\u2502 ",
2743
+ " \u2571 \u2502 ",
2744
+ " \u2500\u2500\u2571\u2500\u2500\u2502\u2500\u2500 ",
2745
+ " \u2571 \u2502 ",
2746
+ " \u2571 \u256D\u2500\u256F ",
2747
+ " \u2571 \u2502 ",
2748
+ " \u2571 \u256D\u2500\u256F ",
2749
+ " \u2502 ",
2750
+ " \u2500\u2500\u2568\u2500\u2500 "
2751
+ ],
2752
+ "squad": [
2753
+ " ",
2754
+ " \u25C6 \u25C6 \u25C6 \u25C6 ",
2755
+ " ",
2756
+ " \u25C6 \u25C6 \u25C6 \u25C6 ",
2757
+ " ",
2758
+ " \u25C6 \u25C6 ",
2759
+ " ",
2760
+ " \xB7 10 strong \xB7 "
2761
+ ],
2762
+ "battalion": [
2763
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 ",
2764
+ " ",
2765
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 ",
2766
+ " ",
2767
+ " \u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6\u25C6 ",
2768
+ " ",
2769
+ " \xB7 25 strong \xB7 "
2770
+ ],
2771
+ "swarm": [
2772
+ " \u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7 ",
2773
+ " \xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6 ",
2774
+ " \u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7 ",
2775
+ " \xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6 ",
2776
+ " \u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7 ",
2777
+ " \xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6\xB7\u25C6 ",
2778
+ " \xB7 50 strong \xB7 "
2779
+ ],
2780
+ "deep-dive": [
2781
+ " \u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248 ",
2782
+ " \u2193 ",
2783
+ " \u2571\u2502\u2572 ",
2784
+ " \u2502 ",
2785
+ " \u2502 ",
2786
+ " \u25BC ",
2787
+ " \xB7 \xB7 \xB7 \xB7 \xB7 \xB7 \xB7 ",
2788
+ " 15 deep "
2789
+ ],
2790
+ "abyss": [
2791
+ " \u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248 ",
2792
+ " \u2502 ",
2793
+ " \u2502 ",
2794
+ " \u2502 ",
2795
+ " \u25BC ",
2796
+ " \u2502 ",
2797
+ " \u2502 ",
2798
+ " \u25BC ",
2799
+ " 25 deep "
2800
+ ],
2801
+ "eternal-recurrence": [
2802
+ " \u256D\u2500\u2500\u2192\u2500\u2500\u256E ",
2803
+ " \u2571 \u221E \u2572 ",
2804
+ " \u2502 \u256D\u2500\u2500\u2500\u256E \u2502 ",
2805
+ " \u2502 \u2502 \u221E \u2502 \u2502 ",
2806
+ " \u2502 \u2570\u2500\u2500\u2500\u256F \u2502 ",
2807
+ " \u2572 \u221E \u2571 ",
2808
+ " \u2570\u2500\u2500\u2190\u2500\u2500\u256F ",
2809
+ " 40 cycles "
2810
+ ],
2811
+ "endurance": [
2812
+ " ",
2813
+ " \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 ",
2814
+ " \u2551\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2551 ",
2815
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D ",
2816
+ " ",
2817
+ " \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 ",
2818
+ " \u2551\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593 \u2551 ",
2819
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D ",
2820
+ " 4 hours "
2821
+ ],
2822
+ "ultramarathon": [
2823
+ " \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 ",
2824
+ " \u2551\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2551 ",
2825
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D ",
2826
+ " \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2192 ",
2827
+ " \xB7 ",
2828
+ " \xB7 ",
2829
+ " \xB7 ",
2830
+ " \xB7 ",
2831
+ " 6 hours "
2832
+ ],
2833
+ "one-shot": [
2834
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
2835
+ " \u2502 \u256D\u2500\u2500\u2500\u256E \u2502 ",
2836
+ " \u2502 \u2502 \u25C9 \u2502 \u2502 ",
2837
+ " \u2502 \u2570\u2500\u2500\u2500\u256F \u2502 ",
2838
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F ",
2839
+ " \u2191 ",
2840
+ " \u2571\u2502 ",
2841
+ " \u2571 \u2502 ",
2842
+ " \u2192 \u25C9 \u2502 "
2843
+ ],
2844
+ "flash": [
2845
+ " \u2571\u2571 ",
2846
+ " \u2571\u2571 ",
2847
+ " \u2571\u2571 ",
2848
+ " \u2571\u2571\u2571\u2571\u2571\u2571 ",
2849
+ " \u2571\u2571 ",
2850
+ " \u2571\u2571 ",
2851
+ " \u2571\u2571 ",
2852
+ " \u2571\u2571 ",
2853
+ " < 2 min "
2854
+ ],
2855
+ // ── Time ──────────────────────────────────────────────────────────────────
2856
+ "night-owl": [
2857
+ " \u263D ",
2858
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u256E \xB7 ",
2859
+ " \u2571 \u25C9 \u25C9 \u2572 \xB7 ",
2860
+ " \u2502 \u25BD \u2502 \xB7 ",
2861
+ " \u2502 \u2571\u2500\u2500\u2500\u2572 \u2502 ",
2862
+ " \u2572\u2571 \u2572\u2571 ",
2863
+ " \u2571\u2572 \u2571\u2572 \xB7 ",
2864
+ " \u2571 \u2572\u2500\u2500\u2500\u2571 \u2572 \xB7 ",
2865
+ " \u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594 "
2866
+ ],
2867
+ "dawn-patrol": [
2868
+ " ",
2869
+ " \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 ",
2870
+ " \u2572 \u2502 \u2571 ",
2871
+ " \u2572 \u2502 \u2571 ",
2872
+ " \u2500\u2500\u2500\u2500\u25D1\u2500\u2500\u2500\u2500 ",
2873
+ " \u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593 ",
2874
+ " \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 ",
2875
+ " "
2876
+ ],
2877
+ "early-bird": [
2878
+ " \u2571\u2571 ",
2879
+ " \u25E0 \u2571\u2571 ",
2880
+ " \u2571 \u2572 \u2571\u2571 ",
2881
+ " \u2502 \u25C9 \u2572>\u2571 ",
2882
+ " \u2502 \u2550\u2571 ",
2883
+ " \u2572 \u2571 ",
2884
+ " \u2572\u2571\u2572 ",
2885
+ " \u2572 \u2572 ",
2886
+ " \u2594\u2594 "
2887
+ ],
2888
+ "weekend-warrior": [
2889
+ " \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 ",
2890
+ " \u2551 S M T W T \u2551 ",
2891
+ " \u2551 \u2551 ",
2892
+ " \u2551 \u25C6 \u2551 ",
2893
+ " \u2551 \u25C6 \u2551 ",
2894
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D "
2895
+ ],
2896
+ "all-nighter": [
2897
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
2898
+ " \u2571 12 \u2572 ",
2899
+ " \u2502 \xB7 \u2502 \u2502 ",
2900
+ " \u25029 \u2502 3 \u2502 ",
2901
+ " \u2502 \xB7 \u2502 ",
2902
+ " \u2572 6 \u2571 ",
2903
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F ",
2904
+ " \u221E \u221E \u221E "
2905
+ ],
2906
+ "witching-hour": [
2907
+ " \xB7 \xB7 \u2726 \xB7 \xB7 ",
2908
+ " \xB7 \u2571\u2572 \xB7 ",
2909
+ " \u2571 \u2572 ",
2910
+ " \u2502 3:00 \u2502 ",
2911
+ " \u2502 AM \u2502 ",
2912
+ " \u2572 \u2571 ",
2913
+ " \xB7 \u2572\u2571 \xB7 ",
2914
+ " \xB7 \xB7 \u2726 \xB7 \xB7 "
2915
+ ],
2916
+ // ── Behavioral ────────────────────────────────────────────────────────────
2917
+ "sisyphean": [
2918
+ " \u2571 ",
2919
+ " \u2571 \u25C9 ",
2920
+ " \u2571 \u25C9\u25C9\u25C9 ",
2921
+ " \u2571 \u25C9\u25C9\u25C9 ",
2922
+ " \u2571 \u25C9 ",
2923
+ " \u2571 ; ",
2924
+ " \u2571 (>.<) ",
2925
+ " \u2571 \u2571\u2571 \u2572\u2572 ",
2926
+ " \u2571 \u2571 \u2572 "
2927
+ ],
2928
+ "stubborn": [
2929
+ " \u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571 ",
2930
+ " \u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571 ",
2931
+ " \u25C9 ",
2932
+ " \u25C9\u25C9\u25C9 ",
2933
+ " (>.<) ",
2934
+ " \u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571 ",
2935
+ " \u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571\u2571 ",
2936
+ " \u2713 "
2937
+ ],
2938
+ "creature-of-habit": [
2939
+ " \u256D\u2500\u2500\u2500\u256E \u256D\u2500\u2500\u2500\u256E ",
2940
+ " \u2502 \u2192 \u2502\u2192\u2502 \u2192 \u2502\u2192 ",
2941
+ " \u2570\u2500\u2500\u2500\u256F \u2570\u2500\u2500\u2500\u256F ",
2942
+ " \u256D\u2500\u2500\u2500\u256E \u256D\u2500\u2500\u2500\u256E ",
2943
+ " \u2502 \u2192 \u2502\u2192\u2502 \u2192 \u2502\u2192 ",
2944
+ " \u2570\u2500\u2500\u2500\u256F \u2570\u2500\u2500\u2500\u256F ",
2945
+ " \u256D\u2500\u2500\u2500\u256E \u256D\u2500\u2500\u2500\u256E ",
2946
+ " \u2502 \u2192 \u2502\u2192\u2502 \u2192 \u2502\u2192 ",
2947
+ " \u2570\u2500\u2500\u2500\u256F \u2570\u2500\u2500\u2500\u256F "
2948
+ ],
2949
+ "loyal": [
2950
+ " \u2571\u2572 ",
2951
+ " \u2571 \u2572 ",
2952
+ " \u2571 \u2665\u2665 \u2572 ",
2953
+ " \u2571 \u2665\u2665 \u2572 ",
2954
+ " \u2572 50 \u2571 ",
2955
+ " \u2572 \u2571 ",
2956
+ " \u2572 \u2571 ",
2957
+ " \u2572\u2571 "
2958
+ ],
2959
+ "wanderer": [
2960
+ " \xB7 \xB7 \xB7 ",
2961
+ " \u2572 \u2502 \u2571 ",
2962
+ " \u2572 \u2502 \u2571 ",
2963
+ " \xB7 + \xB7 ",
2964
+ " \u2571 \u2502 \u2572 ",
2965
+ " \u2571 \u2502 \u2572 ",
2966
+ " \xB7 \xB7 \xB7 "
2967
+ ],
2968
+ "streak": [
2969
+ " \u2554\u2550\u2550\u2557\u2554\u2550\u2550\u2557\u2554\u2550\u2550\u2557\u2554\u2550\u2550\u2557 ",
2970
+ " \u2551M \u2551\u2551T \u2551\u2551W \u2551\u2551T \u2551 ",
2971
+ " \u2551\u25C6 \u2551\u2551\u25C6 \u2551\u2551\u25C6 \u2551\u2551\u25C6 \u2551 ",
2972
+ " \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D ",
2973
+ " \u2554\u2550\u2550\u2557\u2554\u2550\u2550\u2557\u2554\u2550\u2550\u2557 ",
2974
+ " \u2551F \u2551\u2551S \u2551\u2551S \u2551 ",
2975
+ " \u2551\u25C6 \u2551\u2551\u25C6 \u2551\u2551\u25C6 \u2551 ",
2976
+ " \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D "
2977
+ ],
2978
+ "hot-streak": [
2979
+ " \u2571\u2572 ",
2980
+ " \u2571\u2571\u2572\u2572 ",
2981
+ " \u2571\u2571\u25C6\u25C6\u2572\u2572 ",
2982
+ " \u2571\u2571 \u25C6\u25C6 \u2572\u2572 ",
2983
+ " \u2572\u2572 \u25C6\u25C6 \u2571\u2571 ",
2984
+ " \u2572\u2572\u25C6\u25C6\u2571\u2571 ",
2985
+ " \u2572\u2572\u2571\u2571 ",
2986
+ " \xD77 clean "
2987
+ ],
2988
+ "momentum": [
2989
+ " \u25C9 \u25C9 ",
2990
+ " \u2502\u2572 \u2571\u2502 ",
2991
+ " \u2502 \u2572 \u2571 \u2502 ",
2992
+ " \u2502 \u2572 \u2571 \u2502 ",
2993
+ " \u2502 \u25C9\u25C9 \u2502 ",
2994
+ " \u2502 \u2571 \u2572 \u2502 ",
2995
+ " \u2502 \u2571 \u2572 \u2502 ",
2996
+ " \u2502\u2571 \u2572\u2502 ",
2997
+ " \u25C9 \u25C9 "
2998
+ ],
2999
+ "patient-one": [
3000
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
3001
+ " \u2502 \u203E.\u203E \u2502 ",
3002
+ " \u2502 \u2502 ",
3003
+ " \u2502 zzz \u2502 ",
3004
+ " \u2502 zz \u2502 ",
3005
+ " \u2502 z \u2502 ",
3006
+ " \u2502 \u2502 ",
3007
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F ",
3008
+ " 30 min+ "
3009
+ ],
3010
+ "message-in-a-bottle": [
3011
+ " \u256D\u2500\u256E ",
3012
+ " \u2502\xD7\u2502 ",
3013
+ " \u256D\u2500\u2534\u2500\u2534\u2500\u256E ",
3014
+ " \u2502 \u2591\u2591\u2591 \u2502 ",
3015
+ " \u2502 \u2591\u2591\u2591 \u2502 ",
3016
+ " \u2502 \u2591\u2591\u2591 \u2502 ",
3017
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u256F ",
3018
+ " \u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248\u2248 ",
3019
+ " \u2248\u2248\u2248\u2248\u2248\u2248\u2248 "
3020
+ ],
3021
+ "comeback-kid": [
3022
+ " \u2571\u2572 \u2571\u2572 ",
3023
+ " \u2571 \u2572 \u2571 \u2572 ",
3024
+ " \u2571 \u2572 \u2571 \u2572 ",
3025
+ " \u2571 \u2573 \u2572 ",
3026
+ " \u2572 \u2571\u2572 \u2571 ",
3027
+ " \u2572 \u2571 \u2572 \u2571 ",
3028
+ " \u2572 \u2571 \u2572 \u2571 ",
3029
+ " \u2572\u2571 \u2572\u2571 \u2713 "
3030
+ ],
3031
+ "pair-programming": [
3032
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u256E\u256D\u2500\u2500\u2500\u2500\u2500\u256E ",
3033
+ " \u2502 \u25C9 \u25C9 \u2502\u2502 \u25C9 \u25C9 \u2502 ",
3034
+ " \u2502 \u25BD \u2502\u2502 \u25BD \u2502 ",
3035
+ " \u2570\u2500\u2500\u252C\u2500\u2500\u256F\u2570\u2500\u2500\u252C\u2500\u2500\u256F ",
3036
+ " \u2502 \u2571\u2500\u2500\u2572 \u2502 ",
3037
+ " \u2502\u2571 \u2572\u2502 ",
3038
+ " \u2571 \u2550\u2550 \u2572 ",
3039
+ " \u2571 \u2550\u2550\u2550\u2550 \u2572 ",
3040
+ " \u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594 "
3041
+ ],
3042
+ "overdrive": [
3043
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
3044
+ " \u2571 \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u256E \u2572 ",
3045
+ " \u2502 \u2571 \xD76 \u2572 \u2502 ",
3046
+ " \u2502 \u2502 \u25C9 \u2502 \u2502 ",
3047
+ " \u2502 \u2572 \u2571 \u2502 ",
3048
+ " \u2572 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u256F \u2571 ",
3049
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F ",
3050
+ " same day \xD76 "
3051
+ ],
3052
+ "iron-streak": [
3053
+ " \u2554\u2550\u2550\u2557\u2500\u2554\u2550\u2550\u2557\u2500\u2554\u2550\u2550\u2557 ",
3054
+ " \u2551\u25C6 \u2551 \u2551\u25C6 \u2551 \u2551\u25C6 \u2551 ",
3055
+ " \u255A\u2550\u2550\u255D\u2500\u255A\u2550\u2550\u255D\u2500\u255A\u2550\u2550\u255D ",
3056
+ " \u2502 \u2502 ",
3057
+ " \u2554\u2550\u2550\u2557\u2500\u2554\u2550\u2550\u2557\u2500\u2554\u2550\u2550\u2557 ",
3058
+ " \u2551\u25C6 \u2551 \u2551\u25C6 \u2551 \u2551\u25C6 \u2551 ",
3059
+ " \u255A\u2550\u2550\u255D\u2500\u255A\u2550\u2550\u255D\u2500\u255A\u2550\u2550\u255D ",
3060
+ " 14 day chain "
3061
+ ],
3062
+ "deep-conversation": [
3063
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E ",
3064
+ " \u2571 \u256D\u2500\u2500\u256E 20 \u2572 ",
3065
+ " \u2502 \u2502 \u2502 \u2502 ",
3066
+ " \u2502 \u2570\u2500\u2500\u256F \u2502 ",
3067
+ " \u2502 \xB7 \xB7 \xB7 \xB7 \xB7 \u2502 ",
3068
+ " \u2572 \u2571 ",
3069
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F ",
3070
+ " \u2572 ",
3071
+ " \u25CF "
3072
+ ],
3073
+ "one-must-imagine": [
3074
+ " \u25C9\u25C9\u25C9\u25C9\u25C9 ",
3075
+ " \u2571 \u25C9\u25C9\u25C9\u25C9 ",
3076
+ " \u2571 \u25C9 \u2571 \u2190\u2500\u256E ",
3077
+ "\u2571 (>.<) \u2502 ",
3078
+ " \u2571\u2571 \u2572\u2572 \u2502 ",
3079
+ " \u2571 \u2572 \u2502 ",
3080
+ " \u2571 \u2572\u2500\u256F ",
3081
+ " \u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594\u2594 ",
3082
+ " \xD710 restarts "
3083
+ ]
3084
+ };
3085
+ var CARD_WIDTH = 34;
3086
+ var CARD_HEIGHT = 18;
3087
+ var CARD_INNER = CARD_WIDTH - 2;
3088
+ function centerLine(text, width) {
3089
+ const stripped = stripAnsiForWidth(text);
3090
+ if (stripped.length >= width) return text.slice(0, width);
3091
+ const pad = Math.floor((width - stripped.length) / 2);
3092
+ return " ".repeat(pad) + text + " ".repeat(width - stripped.length - pad);
3093
+ }
3094
+ function stripAnsiForWidth(s) {
3095
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
3096
+ }
3097
+ function renderBadgeCard(def, unlock, opts) {
3098
+ const dim = opts?.dim === true || unlock === null;
3099
+ const art = BADGE_ART[def.id] ?? [];
3100
+ const lines = [];
3101
+ const category = def.category.toUpperCase();
3102
+ const borderLabel = ` ${category} `;
3103
+ const topPad = CARD_INNER - borderLabel.length - 2;
3104
+ const topLeft = Math.floor(topPad / 2);
3105
+ const topRight = topPad - topLeft;
3106
+ lines.push(`\u250C${"\u2500".repeat(topLeft)}${borderLabel}${"\u2500".repeat(topRight)}\u2510`);
3107
+ lines.push(`\u2502${" ".repeat(CARD_INNER)}\u2502`);
3108
+ const artMaxLines = 9;
3109
+ const artSlice = art.slice(0, artMaxLines);
3110
+ for (const artLine of artSlice) {
3111
+ const centered = centerLine(artLine, CARD_INNER);
3112
+ lines.push(`\u2502${dim ? dimText(centered) : centered}\u2502`);
3113
+ }
3114
+ for (let i = artSlice.length; i < artMaxLines; i++) {
3115
+ lines.push(`\u2502${" ".repeat(CARD_INNER)}\u2502`);
3116
+ }
3117
+ lines.push(`\u2502${" ".repeat(CARD_INNER)}\u2502`);
3118
+ const nameText = unlock !== null ? def.name : `? ${def.name} ?`;
3119
+ lines.push(`\u2502${centerLine(nameText, CARD_INNER)}\u2502`);
3120
+ const descLines = wrapText2(def.description, CARD_INNER - 4);
3121
+ for (const dl of descLines.slice(0, 2)) {
3122
+ const centered = centerLine(dl, CARD_INNER);
3123
+ lines.push(`\u2502${dim ? dimText(centered) : centered}\u2502`);
3124
+ }
3125
+ const usedContent = 1 + 1 + artMaxLines + 1 + 1 + Math.min(descLines.length, 2);
3126
+ const remaining = CARD_HEIGHT - 2 - usedContent;
3127
+ for (let i = 0; i < remaining; i++) {
3128
+ if (i === remaining - 1 && unlock !== null) {
3129
+ const dateStr = unlock.unlockedAt.slice(0, 10);
3130
+ lines.push(`\u2502${centerLine(dimText(dateStr), CARD_INNER)}\u2502`);
3131
+ } else {
3132
+ lines.push(`\u2502${" ".repeat(CARD_INNER)}\u2502`);
3133
+ }
3134
+ }
3135
+ lines.push(`\u2514${"\u2500".repeat(CARD_INNER)}\u2518`);
3136
+ return { lines, width: CARD_WIDTH, height: lines.length };
3137
+ }
3138
+ function dimText(text) {
3139
+ return `\x1B[2m${text}\x1B[22m`;
3140
+ }
3141
+ function wrapText2(text, maxWidth) {
3142
+ const words = text.split(" ");
3143
+ const lines = [];
3144
+ let current = "";
3145
+ for (const word of words) {
3146
+ if (current.length + word.length + 1 > maxWidth && current.length > 0) {
3147
+ lines.push(current);
3148
+ current = word;
3149
+ } else {
3150
+ current = current.length > 0 ? `${current} ${word}` : word;
3151
+ }
3152
+ }
3153
+ if (current.length > 0) lines.push(current);
3154
+ return lines;
3155
+ }
3156
+ function createBadgeGallery(unlockedAchievements, startIndex) {
3157
+ const unlocked = /* @__PURE__ */ new Map();
3158
+ for (const a of unlockedAchievements) {
3159
+ unlocked.set(a.id, a);
3160
+ }
3161
+ const sorted = [...ACHIEVEMENTS].sort((a, b) => {
3162
+ const aUnlocked = unlocked.has(a.id);
3163
+ const bUnlocked = unlocked.has(b.id);
3164
+ if (aUnlocked && !bUnlocked) return -1;
3165
+ if (!aUnlocked && bUnlocked) return 1;
3166
+ if (aUnlocked && bUnlocked) {
3167
+ const aDate = unlocked.get(a.id).unlockedAt;
3168
+ const bDate = unlocked.get(b.id).unlockedAt;
3169
+ return aDate.localeCompare(bDate);
3170
+ }
3171
+ return 0;
3172
+ });
3173
+ return {
3174
+ achievements: sorted,
3175
+ unlocked,
3176
+ currentIndex: startIndex ?? 0,
3177
+ total: sorted.length
3178
+ };
3179
+ }
3180
+ function galleryNext(gallery) {
3181
+ return (gallery.currentIndex + 1) % gallery.total;
3182
+ }
3183
+ function galleryPrev(gallery) {
3184
+ return (gallery.currentIndex - 1 + gallery.total) % gallery.total;
3185
+ }
3186
+
744
3187
  // src/tui/panels/overlays.ts
745
3188
  var HELP_WIDTH = 62;
746
3189
  var COMPANION_WIDTH = 52;
@@ -865,7 +3308,7 @@ var MOOD_ICONS = {
865
3308
  excited: "\u2726",
866
3309
  existential: "\u25C9"
867
3310
  };
868
- var MOOD_COLORS = {
3311
+ var MOOD_COLORS2 = {
869
3312
  happy: "green",
870
3313
  grinding: "yellow",
871
3314
  frustrated: "red",
@@ -879,7 +3322,7 @@ function statBar(value, max, width, color) {
879
3322
  const bar = ansiColor("\u2593".repeat(filled), color) + ansiDim("\u2591".repeat(width - filled));
880
3323
  return bar;
881
3324
  }
882
- function wrapText2(text, maxWidth) {
3325
+ function wrapText3(text, maxWidth) {
883
3326
  const words = text.split(" ");
884
3327
  const lines = [];
885
3328
  let current = "";
@@ -902,7 +3345,7 @@ function renderProfilePage(buf, rows, cols, companion) {
902
3345
  const endH = Math.floor(companion.stats.endurance / 36e5);
903
3346
  const intensity = companion.debugMood?.scores[companion.mood] ?? 0;
904
3347
  const face = getMoodFace(companion.mood, intensity);
905
- const moodColor = MOOD_COLORS[companion.mood];
3348
+ const moodColor = MOOD_COLORS2[companion.mood];
906
3349
  const moodIcon = MOOD_ICONS[companion.mood];
907
3350
  const faceColored = ansiColor(`(${face})`, moodColor, true);
908
3351
  const repoNicknames = Object.values(companion.repos).map((r) => r.nickname).filter((n) => n !== null);
@@ -933,7 +3376,7 @@ function renderProfilePage(buf, rows, cols, companion) {
933
3376
  contentLines.push(" ".padEnd(innerWidth));
934
3377
  if (companion.lastCommentary) {
935
3378
  const raw = companion.lastCommentary.text;
936
- const wrapped = wrapText2(raw, innerWidth - 6);
3379
+ const wrapped = wrapText3(raw, innerWidth - 6);
937
3380
  contentLines.push(` ${ansiDim("\u250A")} ${ansiColor(wrapped[0] ?? "", "white")}`.padEnd(innerWidth));
938
3381
  for (let i = 1; i < wrapped.length; i++) {
939
3382
  contentLines.push(` ${ansiDim("\u250A")} ${ansiColor(wrapped[i] ?? "", "white")}`.padEnd(innerWidth));
@@ -1144,8 +3587,50 @@ function renderCompanionDebugOverlay(buf, rows, cols, companion) {
1144
3587
  }
1145
3588
 
1146
3589
  // src/tui/panels/mounted-humanloop.ts
1147
- import { readFileSync } from "fs";
3590
+ init_ask_store();
3591
+ init_paths();
3592
+ import { readFileSync as readFileSync9 } from "fs";
1148
3593
  import { mountPanel } from "@crouton-kit/humanloop";
3594
+
3595
+ // src/shared/client.ts
3596
+ init_paths();
3597
+ import { connect } from "net";
3598
+ function rawSend(request, timeoutMs = 1e4) {
3599
+ const sock = socketPath();
3600
+ return new Promise((resolve2, reject) => {
3601
+ const socket = connect(sock);
3602
+ let data = "";
3603
+ const timeout = setTimeout(() => {
3604
+ socket.destroy();
3605
+ reject(new Error(`Request timed out after ${(timeoutMs / 1e3).toFixed(0)}s. The daemon may be overloaded.
3606
+ Check: sisyphus admin doctor
3607
+ Logs: tail -20 ~/.sisyphus/daemon.log`));
3608
+ }, timeoutMs);
3609
+ socket.on("connect", () => {
3610
+ socket.write(JSON.stringify(request) + "\n");
3611
+ });
3612
+ socket.on("data", (chunk) => {
3613
+ data += chunk.toString();
3614
+ const newlineIdx = data.indexOf("\n");
3615
+ if (newlineIdx !== -1) {
3616
+ clearTimeout(timeout);
3617
+ const line = data.slice(0, newlineIdx);
3618
+ socket.destroy();
3619
+ try {
3620
+ resolve2(JSON.parse(line));
3621
+ } catch {
3622
+ reject(new Error(`Invalid JSON response from daemon: ${line}`));
3623
+ }
3624
+ }
3625
+ });
3626
+ socket.on("error", (err) => {
3627
+ clearTimeout(timeout);
3628
+ reject(err);
3629
+ });
3630
+ });
3631
+ }
3632
+
3633
+ // src/tui/panels/mounted-humanloop.ts
1149
3634
  async function dispatchOrphanResolution(orphanTarget, selectedOptionId, deps) {
1150
3635
  if (selectedOptionId === "takeover" && orphanTarget.kind === "agent") {
1151
3636
  await deps.onOrphanTakeover?.({
@@ -1203,7 +3688,7 @@ function mountResolutionPanel(opts, state2) {
1203
3688
  }, 6e4);
1204
3689
  if (res.ok) {
1205
3690
  const ansiPath = res.data.ansiPath;
1206
- const ansi = readFileSync(ansiPath, "utf-8");
3691
+ const ansi = readFileSync9(ansiPath, "utf-8");
1207
3692
  state2.visuals.set(qid, { status: "ready", content: ansi, visible: true });
1208
3693
  } else {
1209
3694
  state2.visuals.set(qid, { status: "error", content: "", visible: true, error: res.error });
@@ -1383,6 +3868,116 @@ function enterResolutionMode(state2, askId2, daemonSend, onOrphanTakeover) {
1383
3868
  requestRender();
1384
3869
  }
1385
3870
 
3871
+ // src/shared/keymap.ts
3872
+ var MENU_FOR_MODE = {
3873
+ "leader": "topLevel",
3874
+ "copy-menu": "copy",
3875
+ "open-menu": "open",
3876
+ "agent-menu": "agent",
3877
+ "session-menu": "session",
3878
+ "go-menu": "go",
3879
+ "companion-menu": "companion"
3880
+ };
3881
+ var KEYMAP = {
3882
+ version: 1,
3883
+ topLevel: {
3884
+ title: " Sisyphus ",
3885
+ items: [
3886
+ { key: "s", label: " Cycle session", action: { type: "script", name: "sisyphus-cycle" } },
3887
+ { key: "h", label: " Home / dashboard", action: { type: "script", name: "sisyphus-home" } },
3888
+ { key: "n", label: " New session", action: { type: "popup", name: "sisyphus-new", popup: { w: "80%", h: "60%", cwd: "current" } } },
3889
+ { key: "m", label: " Message orchestrator", action: { type: "popup", name: "sisyphus-msg", popup: { w: "80%", h: "60%", cwd: "current" } } },
3890
+ { key: "t", label: " Status (where am I?)", action: { type: "popup", name: "sisyphus-status-popup", popup: { w: "90%", h: "90%", cwd: "current" } } },
3891
+ { key: "l", label: " Session picker", action: { type: "popup", name: "sisyphus-pick-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
3892
+ { key: "z", label: " Zoom pane", action: { type: "tmux", cmd: "resize-pane -Z" } },
3893
+ { key: "x", label: " Kill pane (smart)", action: { type: "script", name: "sisyphus-kill-pane" } },
3894
+ { key: "?", label: " Full help reference", action: { type: "popup", name: "sisyphus-help", popup: { w: "80", h: "32", title: " Keybindings " } } },
3895
+ { key: "/", label: " Search / filter", action: { type: "script", name: "sisyphus-search-reports" }, tuiAction: "search" },
3896
+ { key: " ", label: " Open popup explicitly", action: { type: "tui", action: "show-leader" } },
3897
+ { key: "y", label: " Yank \u203A", action: { type: "submenu", ref: "copy" } },
3898
+ { key: "c", label: " Companion \u203A", action: { type: "submenu", ref: "companion" } },
3899
+ { key: "o", label: " Open \u203A", action: { type: "submenu", ref: "open" } },
3900
+ { key: "a", label: " Agent \u203A", action: { type: "submenu", ref: "agent" } },
3901
+ { key: "S", label: " Session \u203A", action: { type: "submenu", ref: "session" } },
3902
+ { key: "g", label: " Go \u203A", action: { type: "submenu", ref: "go" } }
3903
+ ]
3904
+ },
3905
+ submenus: {
3906
+ companion: {
3907
+ title: " Companion ",
3908
+ items: [
3909
+ { key: "p", label: " profile (overlay)", action: { type: "tui", action: "companion-overlay" } },
3910
+ { key: "d", label: " debug (mood signals)", action: { type: "tui", action: "companion-debug" } },
3911
+ { key: "t", label: " open in tmux pane", action: { type: "tui", action: "companion-pane" } }
3912
+ ]
3913
+ },
3914
+ copy: {
3915
+ title: " Copy ",
3916
+ items: [
3917
+ { key: "p", label: " session dir path", action: { type: "script", name: "sisyphus-copy-path" } },
3918
+ { key: "i", label: " session UUID", action: { type: "script", name: "sisyphus-copy-id" } },
3919
+ { key: "c", label: " full session context XML", action: { type: "script", name: "sisyphus-copy-context" } },
3920
+ { key: "l", label: " logs (last 200 lines)", action: { type: "script", name: "sisyphus-copy-logs" } },
3921
+ { key: "r", label: " latest report content", action: { type: "script", name: "sisyphus-copy-latest-report" } },
3922
+ { key: "a", label: " agent ID (picker)", action: { type: "script", name: "sisyphus-copy-agent-id" } }
3923
+ ]
3924
+ },
3925
+ open: {
3926
+ title: " Open ",
3927
+ items: [
3928
+ { key: "g", label: " goal.md", action: { type: "popup", name: "sisyphus-open-goal", popup: { w: "95%", h: "95%", cwd: "current" } } },
3929
+ { key: "r", label: " roadmap.md", action: { type: "popup", name: "sisyphus-open-roadmap", popup: { w: "95%", h: "95%", cwd: "current" } } },
3930
+ { key: "s", label: " strategy.md", action: { type: "popup", name: "sisyphus-open-strategy", popup: { w: "95%", h: "95%", cwd: "current" } } },
3931
+ { key: "l", label: " logs popup (tail)", action: { type: "popup", name: "sisyphus-open-logs", popup: { w: "90%", h: "90%", cwd: "current" } } },
3932
+ { key: "d", label: " session dir in file mgr", action: { type: "script", name: "sisyphus-open-dir" } },
3933
+ { key: "R", label: " latest report file", action: { type: "popup", name: "sisyphus-open-latest-report", popup: { w: "95%", h: "95%", cwd: "current" } } },
3934
+ { key: "c", label: " scratch", action: { type: "popup", name: "sisyphus-open-scratch", popup: { w: "95%", h: "95%", cwd: "current" } } },
3935
+ { key: "e", label: " edit context file", action: { type: "popup", name: "sisyphus-edit-context-file", popup: { w: "95%", h: "95%", cwd: "current" } }, tuiAction: "edit-context-file" }
3936
+ ]
3937
+ },
3938
+ agent: {
3939
+ title: " Agent ",
3940
+ items: [
3941
+ { key: "s", label: " spawn agent", action: { type: "popup", name: "sisyphus-spawn-agent", popup: { w: "80%", h: "70%", cwd: "current" } } },
3942
+ { key: "m", label: " message agent (picker)", action: { type: "popup", name: "sisyphus-msg-agent", popup: { w: "80%", h: "60%", cwd: "current" } } },
3943
+ { key: "r", label: " restart agent (picker)", action: { type: "popup", name: "sisyphus-restart-agent-popup", popup: { w: "70%", h: "50%", cwd: "current" } } },
3944
+ { key: "R", label: " re-run agent (picker)", action: { type: "popup", name: "sisyphus-rerun-agent", popup: { w: "70%", h: "50%", cwd: "current" } } },
3945
+ { key: "j", label: " jump to agent's pane", action: { type: "popup", name: "sisyphus-jump-to-pane", popup: { w: "60%", h: "60%" } } },
3946
+ { key: "o", label: " open claude --resume", action: { type: "popup", name: "sisyphus-open-claude-agent", popup: { w: "60%", h: "60%", cwd: "current" } } },
3947
+ { key: "t", label: " tail agent logs (picker)", action: { type: "popup", name: "sisyphus-tail-agent-logs", popup: { w: "90%", h: "90%", cwd: "current" } } },
3948
+ { key: "k", label: " kill agent (picker)", action: { type: "popup", name: "sisyphus-kill-agent", popup: { w: "60%", h: "40%", cwd: "current" } } },
3949
+ { key: "e", label: " quick-spawn Explore", action: { type: "script", name: "sisyphus-quick-spawn-explore" } },
3950
+ { key: "d", label: " quick-spawn Debug", action: { type: "script", name: "sisyphus-quick-spawn-debug" } }
3951
+ ]
3952
+ },
3953
+ session: {
3954
+ title: " Session ",
3955
+ items: [
3956
+ { key: "n", label: " new session", action: { type: "popup", name: "sisyphus-new", popup: { w: "80%", h: "60%", cwd: "current" } } },
3957
+ { key: "r", label: " resume", action: { type: "popup", name: "sisyphus-resume-session", popup: { w: "80%", h: "60%", cwd: "current" } } },
3958
+ { key: "c", label: " continue", action: { type: "popup", name: "sisyphus-continue-session", popup: { w: "50", h: "5", borderStyle: "fg=yellow", title: " Continue Session ", cwd: "current" } } },
3959
+ { key: "b", label: " rollback (prompts cycle)", action: { type: "popup", name: "sisyphus-rollback-session", popup: { w: "50", h: "5", title: " Rollback ", cwd: "current" } } },
3960
+ { key: "k", label: " kill", action: { type: "popup", name: "sisyphus-kill-session", popup: { w: "40", h: "5", borderStyle: "fg=red", title: " Kill Session ", cwd: "current" } } },
3961
+ { key: "d", label: " delete (confirms)", action: { type: "popup", name: "sisyphus-delete-session", popup: { w: "40", h: "5", borderStyle: "fg=red", title: " Delete Session ", cwd: "current" } } },
3962
+ { key: "e", label: " export to ~/Downloads", action: { type: "popup", name: "sisyphus-export-session", popup: { w: "60", h: "8", title: " Export Session ", cwd: "current" } } },
3963
+ { key: "w", label: " go to session window", action: { type: "popup", name: "sisyphus-go-to-window", popup: { w: "70%", h: "60%", cwd: "current" } } },
3964
+ { key: "C", label: " clone (sisyphus session clone)", action: { type: "popup", name: "sisyphus-clone-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
3965
+ { key: "i", label: " history", action: { type: "popup", name: "sisyphus-history", popup: { w: "95%", h: "95%", cwd: "current" } } }
3966
+ ]
3967
+ },
3968
+ go: {
3969
+ title: " Go ",
3970
+ items: [
3971
+ { key: "w", label: " go to session window", action: { type: "popup", name: "sisyphus-go-to-window", popup: { w: "70%", h: "60%", cwd: "current" } } },
3972
+ { key: "p", label: " jump to pane (picker)", action: { type: "popup", name: "sisyphus-jump-to-pane", popup: { w: "60%", h: "60%" } } },
3973
+ { key: "s", label: " session picker", action: { type: "popup", name: "sisyphus-pick-session", popup: { w: "60%", h: "60%", cwd: "current" } } },
3974
+ { key: "n", label: " next session", action: { type: "script", name: "sisyphus-cycle" } },
3975
+ { key: "r", label: " reconnect", action: { type: "popup", name: "sisyphus-reconnect", popup: { w: "80%", h: "40%", cwd: "current" } } }
3976
+ ]
3977
+ }
3978
+ }
3979
+ };
3980
+
1386
3981
  // src/tui/input.ts
1387
3982
  function dispatchComposeAction(action, content, state2, actions) {
1388
3983
  switch (action.kind) {
@@ -1446,12 +4041,16 @@ var ENTER_FOR_REF = {
1446
4041
  open: "enter-open-menu",
1447
4042
  agent: "enter-agent-menu",
1448
4043
  session: "enter-session-menu",
1449
- go: "enter-go-menu"
4044
+ go: "enter-go-menu",
4045
+ companion: "enter-companion-menu"
1450
4046
  };
1451
4047
  var TUI_ACTION_FOR_NAME = {
1452
4048
  "search": "search",
1453
4049
  "edit-context-file": "edit-context-file",
1454
- "show-leader": "help"
4050
+ "show-leader": "help",
4051
+ "companion-overlay": "companion-overlay",
4052
+ "companion-debug": "companion-debug",
4053
+ "companion-pane": "companion-pane"
1455
4054
  };
1456
4055
  var TUI_HANDLERS = {
1457
4056
  "sisyphus-copy-path": "copy-path",
@@ -1499,18 +4098,18 @@ var TUI_HANDLERS = {
1499
4098
  function findLatestReport(cwd2, sessionId2) {
1500
4099
  const dir = reportsDir(cwd2, sessionId2);
1501
4100
  try {
1502
- const files = readdirSync(dir);
4101
+ const files = readdirSync5(dir);
1503
4102
  if (files.length === 0) return null;
1504
4103
  let latestFile = files[0];
1505
- let latestMtime = statSync(join2(dir, latestFile)).mtimeMs;
4104
+ let latestMtime = statSync3(join11(dir, latestFile)).mtimeMs;
1506
4105
  for (let i = 1; i < files.length; i++) {
1507
- const m = statSync(join2(dir, files[i])).mtimeMs;
4106
+ const m = statSync3(join11(dir, files[i])).mtimeMs;
1508
4107
  if (m > latestMtime) {
1509
4108
  latestMtime = m;
1510
4109
  latestFile = files[i];
1511
4110
  }
1512
4111
  }
1513
- return join2(dir, latestFile);
4112
+ return join11(dir, latestFile);
1514
4113
  } catch {
1515
4114
  return null;
1516
4115
  }
@@ -1758,6 +4357,10 @@ function handleLeaderAction(action, state2, actions) {
1758
4357
  state2.mode = "help";
1759
4358
  requestRender();
1760
4359
  return;
4360
+ case "enter-companion-menu":
4361
+ state2.mode = "companion-menu";
4362
+ requestRender();
4363
+ return;
1761
4364
  case "companion-overlay":
1762
4365
  state2.mode = "companion-overlay";
1763
4366
  requestRender();
@@ -1766,6 +4369,13 @@ function handleLeaderAction(action, state2, actions) {
1766
4369
  state2.mode = "companion-debug";
1767
4370
  requestRender();
1768
4371
  return;
4372
+ case "companion-pane":
4373
+ try {
4374
+ actions.openCompanionPane(state2.cwd);
4375
+ } catch {
4376
+ notify(state2, "Failed to open companion pane");
4377
+ }
4378
+ return;
1769
4379
  case "shell-command": {
1770
4380
  if (!selectedSessionId) {
1771
4381
  notify(state2, "No session selected");
@@ -1849,7 +4459,7 @@ function handleLeaderAction(action, state2, actions) {
1849
4459
  break;
1850
4460
  }
1851
4461
  try {
1852
- const content = readFileSync2(latest, "utf-8");
4462
+ const content = readFileSync10(latest, "utf-8");
1853
4463
  actions.copyToClipboard(content);
1854
4464
  notify(state2, `Copied latest report (${content.length} chars)`);
1855
4465
  } catch {
@@ -1909,7 +4519,7 @@ function handleLeaderAction(action, state2, actions) {
1909
4519
  }
1910
4520
  const editor = actions.resolveEditor();
1911
4521
  try {
1912
- actions.openEditorPopup(state2.cwd, editor, join2(sessionDir(state2.cwd, selectedSessionId), "scratch.md"));
4522
+ actions.openEditorPopup(state2.cwd, editor, join11(sessionDir(state2.cwd, selectedSessionId), "scratch.md"));
1913
4523
  } catch {
1914
4524
  notify(state2, "Failed to open scratch");
1915
4525
  }
@@ -2705,7 +5315,7 @@ function handleKeypress(input, key, state2, actions) {
2705
5315
  }
2706
5316
  if (state2.mode === "search") {
2707
5317
  handleSearchKey(input, key, state2);
2708
- } else if (state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "open-menu" || state2.mode === "agent-menu" || state2.mode === "session-menu" || state2.mode === "go-menu" || state2.mode === "help" || state2.mode === "companion-overlay" || state2.mode === "companion-debug") {
5318
+ } else if (state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "open-menu" || state2.mode === "agent-menu" || state2.mode === "session-menu" || state2.mode === "go-menu" || state2.mode === "companion-menu" || state2.mode === "help" || state2.mode === "companion-overlay" || state2.mode === "companion-debug") {
2709
5319
  handleLeaderKey(input, key, state2, actions);
2710
5320
  } else if (state2.mode === "report-detail") {
2711
5321
  handleReportDetailKey(input, key, state2, actions);
@@ -2714,6 +5324,10 @@ function handleKeypress(input, key, state2, actions) {
2714
5324
  }
2715
5325
  }
2716
5326
 
5327
+ // src/tui/app.ts
5328
+ init_render();
5329
+ init_terminal();
5330
+
2717
5331
  // src/tui/lib/tree-render.ts
2718
5332
  function renderTreePrefix(node, nodes, index) {
2719
5333
  if (node.depth === 0) {
@@ -2784,6 +5398,24 @@ function precomputePrefixes(nodes) {
2784
5398
  }
2785
5399
  }
2786
5400
 
5401
+ // src/tui/lib/reports.ts
5402
+ import { readFileSync as readFileSync11 } from "fs";
5403
+ function loadReportContent(report) {
5404
+ try {
5405
+ return readFileSync11(report.filePath, "utf-8");
5406
+ } catch {
5407
+ return report.summary;
5408
+ }
5409
+ }
5410
+ function resolveReports(reports) {
5411
+ return [...reports].reverse().map((r) => ({
5412
+ type: r.type,
5413
+ timestamp: r.timestamp,
5414
+ content: loadReportContent(r),
5415
+ summary: r.summary
5416
+ }));
5417
+ }
5418
+
2787
5419
  // src/tui/lib/client.ts
2788
5420
  function send(request) {
2789
5421
  return rawSend(request, 8e3);
@@ -2795,10 +5427,28 @@ async function inboxList() {
2795
5427
  }
2796
5428
 
2797
5429
  // src/tui/lib/tmux.ts
2798
- import { execSync as execSync2 } from "child_process";
2799
- import { join as join3 } from "path";
2800
- import { readFileSync as readFileSync3, writeFileSync, mkdtempSync, rmSync, cpSync, existsSync, mkdirSync } from "fs";
5430
+ init_paths();
5431
+ import { execSync as execSync3 } from "child_process";
5432
+ import { join as join12 } from "path";
5433
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdtempSync, rmSync as rmSync4, cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync8 } from "fs";
2801
5434
  import { tmpdir } from "os";
5435
+ init_shell();
5436
+
5437
+ // src/shared/exec.ts
5438
+ import { execSync as execSync2 } from "child_process";
5439
+ var EXEC_ENV = execEnv();
5440
+ function exec(cmd, cwd2, timeoutMs = 3e4) {
5441
+ return execSync2(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd: cwd2, timeout: timeoutMs }).trim();
5442
+ }
5443
+ function execSafe(cmd, cwd2, timeoutMs) {
5444
+ try {
5445
+ return execSync2(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd: cwd2, stdio: ["pipe", "pipe", "pipe"], timeout: timeoutMs }).trim();
5446
+ } catch {
5447
+ return null;
5448
+ }
5449
+ }
5450
+
5451
+ // src/tui/lib/tmux.ts
2802
5452
  function getWindowId() {
2803
5453
  const pane = process.env["TMUX_PANE"];
2804
5454
  if (pane) {
@@ -2814,7 +5464,7 @@ function selectPane(paneId) {
2814
5464
  }
2815
5465
  function listAllWindowIds() {
2816
5466
  try {
2817
- const output = execSync2('tmux list-windows -a -F "#{window_id}"', { encoding: "utf-8", env: EXEC_ENV });
5467
+ const output = execSync3('tmux list-windows -a -F "#{window_id}"', { encoding: "utf-8", env: EXEC_ENV });
2818
5468
  return new Set(output.trim().split("\n").filter(Boolean));
2819
5469
  } catch {
2820
5470
  return /* @__PURE__ */ new Set();
@@ -2834,10 +5484,10 @@ function registerDashboardWindow() {
2834
5484
  }
2835
5485
  var companionPaneId = null;
2836
5486
  function setupCompanionPlugin() {
2837
- const srcDir = join3(import.meta.dirname, "templates", "companion-plugin");
2838
- const destDir = join3(globalDir(), "companion-plugin");
2839
- if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
2840
- cpSync(srcDir, destDir, { recursive: true });
5487
+ const srcDir = join12(import.meta.dirname, "templates", "companion-plugin");
5488
+ const destDir = join12(globalDir(), "companion-plugin");
5489
+ if (!existsSync8(destDir)) mkdirSync8(destDir, { recursive: true });
5490
+ cpSync2(srcDir, destDir, { recursive: true });
2841
5491
  return destDir;
2842
5492
  }
2843
5493
  function paneExists(paneId) {
@@ -2849,18 +5499,18 @@ function openCompanionPane(cwd2) {
2849
5499
  return;
2850
5500
  }
2851
5501
  const pluginDir = setupCompanionPlugin();
2852
- const templatePath = join3(import.meta.dirname, "templates", "dashboard-claude.md");
5502
+ const templatePath = join12(import.meta.dirname, "templates", "dashboard-claude.md");
2853
5503
  let template;
2854
5504
  try {
2855
- template = readFileSync3(templatePath, "utf-8");
5505
+ template = readFileSync12(templatePath, "utf-8");
2856
5506
  } catch {
2857
5507
  template = `You are a Sisyphus dashboard companion. Help the user manage multi-agent sessions.
2858
5508
  Project: ${cwd2}
2859
5509
  Run \`sisyphus list\` and \`sisyphus status\` to see current state.`;
2860
5510
  }
2861
5511
  const rendered = template.replace(/\{\{CWD\}\}/g, cwd2);
2862
- const promptPath = join3(globalDir(), "dashboard-companion-prompt.md");
2863
- writeFileSync(promptPath, rendered, "utf-8");
5512
+ const promptPath = join12(globalDir(), "dashboard-companion-prompt.md");
5513
+ writeFileSync9(promptPath, rendered, "utf-8");
2864
5514
  const pathEnv = augmentedPath();
2865
5515
  const claudeCmd = `SISYPHUS_COMPANION_CWD=${shellQuote(cwd2)} PATH=${shellQuote(pathEnv)} claude --dangerously-skip-permissions --plugin-dir ${shellQuote(pluginDir)} --append-system-prompt "$(cat ${shellQuote(promptPath)})"`;
2866
5516
  const result = exec(
@@ -2873,55 +5523,55 @@ function switchToSession(sessionName) {
2873
5523
  execSafe(`tmux switch-client -t ${shellQuote(sessionName)}`);
2874
5524
  }
2875
5525
  function editInPopup(cwd2, editor, opts) {
2876
- const tmpDir = mkdtempSync(join3(tmpdir(), "sisyphus-"));
2877
- const filePath = join3(tmpDir, "input.md");
5526
+ const tmpDir = mkdtempSync(join12(tmpdir(), "sisyphus-"));
5527
+ const filePath = join12(tmpDir, "input.md");
2878
5528
  try {
2879
- writeFileSync(filePath, opts?.content ? opts.content : "", "utf-8");
5529
+ writeFileSync9(filePath, opts?.content ? opts.content : "", "utf-8");
2880
5530
  openEditorPopup(cwd2, editor, filePath, opts?.size);
2881
- const result = readFileSync3(filePath, "utf-8").trim();
5531
+ const result = readFileSync12(filePath, "utf-8").trim();
2882
5532
  return result || null;
2883
5533
  } finally {
2884
- rmSync(tmpDir, { recursive: true, force: true });
5534
+ rmSync4(tmpDir, { recursive: true, force: true });
2885
5535
  }
2886
5536
  }
2887
5537
  function promptInPopup(prompt, opts) {
2888
5538
  const { w = "50%", h = "3" } = opts ?? {};
2889
- const tmpDir = mkdtempSync(join3(tmpdir(), "sisyphus-"));
2890
- const outFile = join3(tmpDir, "result");
5539
+ const tmpDir = mkdtempSync(join12(tmpdir(), "sisyphus-"));
5540
+ const outFile = join12(tmpDir, "result");
2891
5541
  try {
2892
5542
  const script = `printf ${shellQuote(prompt + " ")} && read -r line && printf '%s' "$line" > ${shellQuote(outFile)}`;
2893
- execSync2(
5543
+ execSync3(
2894
5544
  `tmux display-popup -E -w ${w} -h ${h} ${shellQuote(`bash -c ${shellQuote(script)}`)}`,
2895
5545
  { stdio: "inherit", env: EXEC_ENV }
2896
5546
  );
2897
- if (!existsSync(outFile)) return null;
2898
- const result = readFileSync3(outFile, "utf-8").trim();
5547
+ if (!existsSync8(outFile)) return null;
5548
+ const result = readFileSync12(outFile, "utf-8").trim();
2899
5549
  return result || null;
2900
5550
  } finally {
2901
- rmSync(tmpDir, { recursive: true, force: true });
5551
+ rmSync4(tmpDir, { recursive: true, force: true });
2902
5552
  }
2903
5553
  }
2904
5554
  function openLogPopup() {
2905
- execSync2(
5555
+ execSync3(
2906
5556
  `tmux display-popup -E -w 90% -h 80% ${shellQuote("tail -f ~/.sisyphus/daemon.log")}`,
2907
5557
  { stdio: "inherit", env: EXEC_ENV }
2908
5558
  );
2909
5559
  }
2910
5560
  function openShellPopup(cwd2, command) {
2911
- execSync2(
5561
+ execSync3(
2912
5562
  `tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd2)} ${shellQuote(`sh -c '${command.replace(/'/g, "'\\''")}; echo; echo "Press enter to close"; read'`)}`,
2913
5563
  { stdio: "inherit", env: EXEC_ENV }
2914
5564
  );
2915
5565
  }
2916
5566
  function openInFileManager(path) {
2917
- execSync2(`open ${shellQuote(path)}`, { stdio: "inherit", env: EXEC_ENV });
5567
+ execSync3(`open ${shellQuote(path)}`, { stdio: "inherit", env: EXEC_ENV });
2918
5568
  }
2919
5569
  function openClaudeResumePopup(cwd2, claudeSessionId, resumeEnv, resumeArgs) {
2920
5570
  const pathEnv = augmentedPath();
2921
5571
  const envPrefix = resumeEnv ? `${resumeEnv} && ` : "";
2922
5572
  const args2 = resumeArgs ? `${resumeArgs} --resume ${shellQuote(claudeSessionId)}` : `--resume ${shellQuote(claudeSessionId)}`;
2923
5573
  const cmd = `${envPrefix}PATH=${shellQuote(pathEnv)} claude ${args2}`;
2924
- execSync2(
5574
+ execSync3(
2925
5575
  `tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd2)} ${shellQuote(cmd)}`,
2926
5576
  { stdio: "inherit", env: EXEC_ENV }
2927
5577
  );
@@ -2981,23 +5631,83 @@ function openEditorPopup(cwd2, editor, filePath, size) {
2981
5631
  const { w = "90%", h = "90%" } = size ?? {};
2982
5632
  const editorBin = editor.split(/\s+/)[0].split("/").pop();
2983
5633
  if (TERMINAL_EDITORS.has(editorBin)) {
2984
- execSync2(
5634
+ execSync3(
2985
5635
  `tmux display-popup -E -w ${w} -h ${h} -d ${shellQuote(cwd2)} ${shellQuote(`${editor} ${shellQuote(filePath)}`)}`,
2986
5636
  { stdio: "inherit", env: EXEC_ENV }
2987
5637
  );
2988
5638
  } else {
2989
- execSync2(`${editor} ${shellQuote(filePath)}`, { stdio: "inherit", cwd: cwd2, env: EXEC_ENV });
5639
+ execSync3(`${editor} ${shellQuote(filePath)}`, { stdio: "inherit", cwd: cwd2, env: EXEC_ENV });
2990
5640
  }
2991
5641
  }
2992
5642
 
2993
5643
  // src/tui/lib/clipboard.ts
2994
- import { execSync as execSync3 } from "child_process";
5644
+ import { execSync as execSync4 } from "child_process";
2995
5645
  function copyToClipboard(text) {
2996
- execSync3("pbcopy", { input: text });
5646
+ execSync4("pbcopy", { input: text });
5647
+ }
5648
+
5649
+ // src/tui/lib/context.ts
5650
+ init_paths();
5651
+ import { readFileSync as readFileSync13, readdirSync as readdirSync6 } from "fs";
5652
+ function readFileSafe(filePath) {
5653
+ try {
5654
+ return readFileSync13(filePath, "utf-8");
5655
+ } catch {
5656
+ return null;
5657
+ }
5658
+ }
5659
+ function escapeXml(s) {
5660
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5661
+ }
5662
+ function buildSessionContext(session, cwd2) {
5663
+ const goal = readFileSafe(goalPath(cwd2, session.id));
5664
+ const roadmap = readFileSafe(roadmapPath(cwd2, session.id));
5665
+ const agentsXml = session.agents.map((agent) => {
5666
+ const reportBlocks = resolveReports(agent.reports);
5667
+ const reportsXml = [...reportBlocks].reverse().map((block) => {
5668
+ return ` <report type="${block.type}" time="${escapeXml(block.timestamp)}">${escapeXml(block.content)}</report>`;
5669
+ }).join("\n");
5670
+ return [
5671
+ ` <agent id="${escapeXml(agent.id)}" name="${escapeXml(agent.name)}" type="${escapeXml(agent.agentType)}" status="${escapeXml(agent.status)}">`,
5672
+ ` <instruction>${escapeXml(agent.instruction)}</instruction>`,
5673
+ ...reportsXml ? [reportsXml] : [],
5674
+ ` </agent>`
5675
+ ].join("\n");
5676
+ }).join("\n");
5677
+ const cyclesXml = session.orchestratorCycles.map((cycle) => {
5678
+ const agents = cycle.agentsSpawned.join(", ");
5679
+ const mode = cycle.mode ? ` mode="${escapeXml(cycle.mode)}"` : "";
5680
+ return ` <cycle number="${cycle.cycle}"${mode} agents="${escapeXml(agents)}" />`;
5681
+ }).join("\n");
5682
+ const lines = [
5683
+ "<context>",
5684
+ `<session id="${escapeXml(session.id)}" status="${escapeXml(session.status)}">`,
5685
+ ` <task>${escapeXml(session.task)}</task>`,
5686
+ ` <cwd>${escapeXml(session.cwd)}</cwd>`
5687
+ ];
5688
+ if (goal) lines.push(` <goal>${escapeXml(goal)}</goal>`);
5689
+ if (roadmap) lines.push(` <roadmap>${escapeXml(roadmap)}</roadmap>`);
5690
+ if (session.agents.length > 0) {
5691
+ lines.push(" <agents>");
5692
+ lines.push(agentsXml);
5693
+ lines.push(" </agents>");
5694
+ }
5695
+ if (session.orchestratorCycles.length > 0) {
5696
+ lines.push(" <cycles>");
5697
+ lines.push(cyclesXml);
5698
+ lines.push(" </cycles>");
5699
+ }
5700
+ if (session.completionReport) {
5701
+ lines.push(` <completion-report>${escapeXml(session.completionReport)}</completion-report>`);
5702
+ }
5703
+ lines.push("</session>");
5704
+ lines.push("</context>");
5705
+ return lines.join("\n");
2997
5706
  }
2998
5707
 
2999
5708
  // src/tui/panels/tree.ts
3000
- import stringWidth2 from "string-width";
5709
+ init_render();
5710
+ import stringWidth4 from "string-width";
3001
5711
  function renderNodeContent(node, maxWidth) {
3002
5712
  switch (node.type) {
3003
5713
  case "section": {
@@ -3171,7 +5881,7 @@ function renderTreePanel(buf, rect, nodes, cursorIndex, focused, companion) {
3171
5881
  let current = "";
3172
5882
  let currentWidth = 0;
3173
5883
  for (const word of words) {
3174
- const wordWidth = stringWidth2(word);
5884
+ const wordWidth = stringWidth4(word);
3175
5885
  if (currentWidth + wordWidth + 1 > innerW && currentWidth > 0) {
3176
5886
  _companionCommentaryLines.push(current);
3177
5887
  current = word;
@@ -3266,8 +5976,16 @@ function renderTreePanel(buf, rect, nodes, cursorIndex, focused, companion) {
3266
5976
  }
3267
5977
  }
3268
5978
 
5979
+ // src/tui/panels/detail.ts
5980
+ init_render();
5981
+
5982
+ // src/shared/utils.ts
5983
+ function computeActiveTimeMs(session) {
5984
+ return session.activeMs;
5985
+ }
5986
+
3269
5987
  // src/tui/panels/cycle-flow.ts
3270
- import stringWidth3 from "string-width";
5988
+ import stringWidth5 from "string-width";
3271
5989
  var BG_TINTS = {
3272
5990
  yellow: "48;2;40;35;20",
3273
5991
  blue: "48;2;20;25;45",
@@ -3304,7 +6022,7 @@ function getCurrentPhase(session) {
3304
6022
  return "orchestrator";
3305
6023
  }
3306
6024
  function padTo(text, w) {
3307
- const tw = stringWidth3(text);
6025
+ const tw = stringWidth5(text);
3308
6026
  if (tw >= w) return truncate(text, w);
3309
6027
  return text + " ".repeat(w - tw);
3310
6028
  }
@@ -3327,8 +6045,8 @@ function buildOrchestratorNode(cycle, agents, width, bright, showConnectorBottom
3327
6045
  rightText = `${dur} ${time}`;
3328
6046
  }
3329
6047
  const leftContent = `${icon} ${cycleLabel} ${modeLabel}`;
3330
- const leftW = stringWidth3(leftContent);
3331
- const rightW = stringWidth3(rightText);
6048
+ const leftW = stringWidth5(leftContent);
6049
+ const rightW = stringWidth5(rightText);
3332
6050
  const gap = Math.max(1, inner - 2 - leftW - rightW);
3333
6051
  lines.push([seg("\u256D" + "\u2500".repeat(inner) + "\u256E", { color: "yellow", dim })]);
3334
6052
  const contentSegs = [
@@ -3345,7 +6063,7 @@ function buildOrchestratorNode(cycle, agents, width, bright, showConnectorBottom
3345
6063
  contentSegs.push(seg(rightText, { bg: bg2, dim }));
3346
6064
  }
3347
6065
  contentSegs.push(seg(" ", { bg: bg2 }));
3348
- const usedWidth = 1 + 1 + 1 + 1 + stringWidth3(cycleLabel) + 2 + stringWidth3(modeLabel) + gap + stringWidth3(rightText) + 1;
6066
+ const usedWidth = 1 + 1 + 1 + 1 + stringWidth5(cycleLabel) + 2 + stringWidth5(modeLabel) + gap + stringWidth5(rightText) + 1;
3349
6067
  if (usedWidth < inner) {
3350
6068
  contentSegs.push(seg(" ".repeat(inner - usedWidth), { bg: bg2 }));
3351
6069
  }
@@ -3506,7 +6224,7 @@ function buildAgentBoxRows(agents, boxWidth, totalWidth, bright, maxPerRow) {
3506
6224
  const dim = !bright;
3507
6225
  const icon = agentStatusIcon(a.status);
3508
6226
  const iconColor = statusColor(a.status);
3509
- const idPadded = padTo(` ${a.id}`, innerW - stringWidth3(icon));
6227
+ const idPadded = padTo(` ${a.id}`, innerW - stringWidth5(icon));
3510
6228
  line1Segs.push(seg("\u2502", { color: borderColor, dim }));
3511
6229
  line1Segs.push(seg(icon, { bg: agentBg, color: iconColor, bold: bright }));
3512
6230
  line1Segs.push(seg(idPadded, { bg: agentBg, dim, bold: bright && a.status === "running" }));
@@ -3642,7 +6360,7 @@ function buildCompleteNode(session, width) {
3642
6360
  const cycles = session.orchestratorCycles.length;
3643
6361
  const leftText = "\u25C9 complete";
3644
6362
  const rightText = `${dur} total`;
3645
- const gap = Math.max(1, inner - 2 - stringWidth3(leftText) - stringWidth3(rightText));
6363
+ const gap = Math.max(1, inner - 2 - stringWidth5(leftText) - stringWidth5(rightText));
3646
6364
  let summaryParts = `${cycles} cycle${cycles !== 1 ? "s" : ""} \xB7 ${totalAgents} agent${totalAgents !== 1 ? "s" : ""}`;
3647
6365
  if (totalAgents > 0) {
3648
6366
  const parts = [];
@@ -4558,8 +7276,11 @@ function renderDigestRows(rect, state2) {
4558
7276
  return buildPanelRows(rect, lines, state2.digestScroll, focused, "cyan", state2.digestRenderedCache);
4559
7277
  }
4560
7278
 
7279
+ // src/tui/panels/stacked-detail.ts
7280
+ init_render();
7281
+
4561
7282
  // src/tui/lib/markdown-highlight.ts
4562
- import stringWidth4 from "string-width";
7283
+ import stringWidth6 from "string-width";
4563
7284
 
4564
7285
  // src/tui/lib/gloam.ts
4565
7286
  function fg(r, g, b) {
@@ -4665,7 +7386,7 @@ function tokenizeInline(line, baseFg, baseStyle) {
4665
7386
  }
4666
7387
  function segsDisplayWidth(segs) {
4667
7388
  let w = 0;
4668
- for (const s of segs) w += stringWidth4(s.text);
7389
+ for (const s of segs) w += stringWidth6(s.text);
4669
7390
  return w;
4670
7391
  }
4671
7392
  function segsToAtoms(segs) {
@@ -4678,7 +7399,7 @@ function segsToAtoms(segs) {
4678
7399
  const piece = m[0];
4679
7400
  atoms.push({
4680
7401
  text: piece,
4681
- width: stringWidth4(piece),
7402
+ width: stringWidth6(piece),
4682
7403
  style,
4683
7404
  space: /^\s+$/.test(piece)
4684
7405
  });
@@ -4701,7 +7422,7 @@ function wrapSegs(segs, width, contIndent) {
4701
7422
  while (current.length > 0) {
4702
7423
  const last = current[current.length - 1];
4703
7424
  if (/^\s+$/.test(last.text) && !last.bg) {
4704
- currentWidth -= stringWidth4(last.text);
7425
+ currentWidth -= stringWidth6(last.text);
4705
7426
  current.pop();
4706
7427
  } else break;
4707
7428
  }
@@ -4723,14 +7444,14 @@ function wrapSegs(segs, width, contIndent) {
4723
7444
  flushLine();
4724
7445
  if (contIndent) {
4725
7446
  current.push({ text: contIndent });
4726
- currentWidth = stringWidth4(contIndent);
7447
+ currentWidth = stringWidth6(contIndent);
4727
7448
  }
4728
7449
  continue;
4729
7450
  }
4730
7451
  let cut = 0;
4731
7452
  let cutW = 0;
4732
7453
  for (let k = 0; k < remaining.length; k++) {
4733
- const cw = stringWidth4(remaining[k]);
7454
+ const cw = stringWidth6(remaining[k]);
4734
7455
  if (cutW + cw > spaceLeft) break;
4735
7456
  cutW += cw;
4736
7457
  cut = k + 1;
@@ -4739,7 +7460,7 @@ function wrapSegs(segs, width, contIndent) {
4739
7460
  flushLine();
4740
7461
  if (contIndent) {
4741
7462
  current.push({ text: contIndent });
4742
- currentWidth = stringWidth4(contIndent);
7463
+ currentWidth = stringWidth6(contIndent);
4743
7464
  }
4744
7465
  continue;
4745
7466
  }
@@ -4750,7 +7471,7 @@ function wrapSegs(segs, width, contIndent) {
4750
7471
  flushLine();
4751
7472
  if (contIndent) {
4752
7473
  current.push({ text: contIndent });
4753
- currentWidth = stringWidth4(contIndent);
7474
+ currentWidth = stringWidth6(contIndent);
4754
7475
  }
4755
7476
  }
4756
7477
  }
@@ -4760,7 +7481,7 @@ function wrapSegs(segs, width, contIndent) {
4760
7481
  flushLine();
4761
7482
  if (contIndent) {
4762
7483
  current.push({ text: contIndent });
4763
- currentWidth = stringWidth4(contIndent);
7484
+ currentWidth = stringWidth6(contIndent);
4764
7485
  }
4765
7486
  }
4766
7487
  pushAtom(atom);
@@ -4831,7 +7552,7 @@ function buildCodeFenceLine(fence, innerW) {
4831
7552
  return [
4832
7553
  { text: " ", fg: GLOAM.fg4 },
4833
7554
  {
4834
- text: fence + " ".repeat(Math.max(0, innerW - 2 - stringWidth4(fence))),
7555
+ text: fence + " ".repeat(Math.max(0, innerW - 2 - stringWidth6(fence))),
4835
7556
  fg: GLOAM.fg4,
4836
7557
  bg: GLOAM.bg_bg1
4837
7558
  }
@@ -4839,7 +7560,7 @@ function buildCodeFenceLine(fence, innerW) {
4839
7560
  }
4840
7561
  function buildCodeLine(content, innerW) {
4841
7562
  const cleaned = stripDisplayHazards(content);
4842
- const cw = stringWidth4(cleaned);
7563
+ const cw = stringWidth6(cleaned);
4843
7564
  const padW = Math.max(0, innerW - 2 - cw);
4844
7565
  return [
4845
7566
  { text: " ", bg: GLOAM.bg_bg1 },
@@ -4888,7 +7609,7 @@ function parseTableSeparator(line) {
4888
7609
  return aligns;
4889
7610
  }
4890
7611
  function padCell(text, width, align) {
4891
- const w = stringWidth4(text);
7612
+ const w = stringWidth6(text);
4892
7613
  const pad = Math.max(0, width - w);
4893
7614
  let left = 0;
4894
7615
  let right = 0;
@@ -4913,7 +7634,7 @@ function wrapCell(text, width, align) {
4913
7634
  };
4914
7635
  for (const piece of cleaned.match(/\s+|\S+/g) ?? []) {
4915
7636
  const isSpace = /^\s+$/.test(piece);
4916
- const pw = stringWidth4(piece);
7637
+ const pw = stringWidth6(piece);
4917
7638
  if (isSpace) {
4918
7639
  if (curW === 0) continue;
4919
7640
  if (curW + pw > width) flush();
@@ -4935,7 +7656,7 @@ function wrapCell(text, width, align) {
4935
7656
  let cut = 0;
4936
7657
  let cutW = 0;
4937
7658
  for (let k = 0; k < rem.length; k++) {
4938
- const cw = stringWidth4(rem[k]);
7659
+ const cw = stringWidth6(rem[k]);
4939
7660
  if (cutW + cw > width) break;
4940
7661
  cutW += cw;
4941
7662
  cut = k + 1;
@@ -4944,7 +7665,7 @@ function wrapCell(text, width, align) {
4944
7665
  const slice = rem.slice(0, cut);
4945
7666
  if (cut === rem.length) {
4946
7667
  cur = slice;
4947
- curW = stringWidth4(slice);
7668
+ curW = stringWidth6(slice);
4948
7669
  } else {
4949
7670
  out.push(padCell(slice, width, align));
4950
7671
  }
@@ -4977,7 +7698,7 @@ function buildTableLines(headers, alignsIn, rowsIn, innerW) {
4977
7698
  const naturalW = new Array(ncols).fill(0);
4978
7699
  const measure = (cells) => {
4979
7700
  for (let i = 0; i < ncols; i++) {
4980
- const w = stringWidth4(cleanMarkdown(cells[i]));
7701
+ const w = stringWidth6(cleanMarkdown(cells[i]));
4981
7702
  if (w > naturalW[i]) naturalW[i] = w;
4982
7703
  }
4983
7704
  };
@@ -5348,6 +8069,7 @@ function renderCycleLogMode(rect, state2, focused) {
5348
8069
  }
5349
8070
 
5350
8071
  // src/tui/panels/bottom.ts
8072
+ init_render();
5351
8073
  var B = ansiBold;
5352
8074
  var D = ansiDim;
5353
8075
  var SEP = D("\u2502 ");
@@ -5392,6 +8114,7 @@ function renderStatusLine(buf, y, state2, cursorNodeType) {
5392
8114
  }
5393
8115
 
5394
8116
  // src/tui/panels/cross-session-inbox.ts
8117
+ init_render();
5395
8118
  var KIND_ICON = {
5396
8119
  notify: "\u2709",
5397
8120
  validation: "\u2713",
@@ -5456,38 +8179,42 @@ function renderCrossSessionInboxRows(rect, state2) {
5456
8179
  return buildPanelRows(rect, lines, state2.crossSessionInboxScroll, focused, "red", state2.inboxRenderedCache);
5457
8180
  }
5458
8181
 
8182
+ // src/tui/app.ts
8183
+ init_paths();
8184
+
5459
8185
  // src/tui/lib/popup-compose.ts
5460
- import { execSync as execSync4 } from "child_process";
5461
- import { mkdtempSync as mkdtempSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync4, rmSync as rmSync2, existsSync as existsSync2, cpSync as cpSync2 } from "fs";
5462
- import { join as join4 } from "path";
5463
- import { tmpdir as tmpdir2, homedir } from "os";
8186
+ init_shell();
8187
+ import { execSync as execSync5 } from "child_process";
8188
+ import { mkdtempSync as mkdtempSync2, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync14, rmSync as rmSync5, existsSync as existsSync9, cpSync as cpSync3 } from "fs";
8189
+ import { join as join13 } from "path";
8190
+ import { tmpdir as tmpdir2, homedir as homedir4 } from "os";
5464
8191
  var initLuaEnsured = false;
5465
8192
  function ensureSisyphusInitLua() {
5466
8193
  if (initLuaEnsured) return;
5467
8194
  initLuaEnsured = true;
5468
8195
  try {
5469
- const destDir = join4(homedir(), ".config", "sisyphus");
5470
- const destPath = join4(destDir, "init.lua");
5471
- if (existsSync2(destPath)) return;
5472
- mkdirSync2(destDir, { recursive: true });
5473
- const srcPath = join4(import.meta.dirname, "templates", "sisyphus-init.lua");
5474
- cpSync2(srcPath, destPath);
8196
+ const destDir = join13(homedir4(), ".config", "sisyphus");
8197
+ const destPath = join13(destDir, "init.lua");
8198
+ if (existsSync9(destPath)) return;
8199
+ mkdirSync9(destDir, { recursive: true });
8200
+ const srcPath = join13(import.meta.dirname, "templates", "sisyphus-init.lua");
8201
+ cpSync3(srcPath, destPath);
5475
8202
  } catch {
5476
8203
  }
5477
8204
  }
5478
8205
  function composeViaPopup(action, state2, actions) {
5479
- const tmpDir = mkdtempSync2(join4(tmpdir2(), "sisyphus-popup-"));
5480
- const tempFile = join4(tmpDir, "compose.md");
8206
+ const tmpDir = mkdtempSync2(join13(tmpdir2(), "sisyphus-popup-"));
8207
+ const tempFile = join13(tmpDir, "compose.md");
5481
8208
  try {
5482
- writeFileSync2(tempFile, "", "utf-8");
8209
+ writeFileSync10(tempFile, "", "utf-8");
5483
8210
  const cmd = `NVIM_APPNAME=sisyphus nvim ${shellQuote(tempFile)}`;
5484
- execSync4(
8211
+ execSync5(
5485
8212
  `tmux display-popup -E -w 90% -h 90% -d ${shellQuote(state2.cwd)} ${shellQuote(cmd)}`,
5486
8213
  { stdio: "inherit", env: EXEC_ENV }
5487
8214
  );
5488
8215
  let rawContent = "";
5489
8216
  try {
5490
- rawContent = readFileSync4(tempFile, "utf-8");
8217
+ rawContent = readFileSync14(tempFile, "utf-8");
5491
8218
  } catch {
5492
8219
  }
5493
8220
  const required = !OPTIONAL_COMPOSE.has(action.kind);
@@ -5498,19 +8225,21 @@ function composeViaPopup(action, state2, actions) {
5498
8225
  } catch (err) {
5499
8226
  notify(state2, `Failed to open compose popup: ${err.message}`);
5500
8227
  } finally {
5501
- rmSync2(tmpDir, { recursive: true, force: true });
8228
+ rmSync5(tmpDir, { recursive: true, force: true });
5502
8229
  }
5503
8230
  }
5504
8231
 
5505
8232
  // src/tui/app.ts
8233
+ init_config();
8234
+ init_paths();
5506
8235
  var _cachedCompanion = null;
5507
8236
  var _companionMtime = 0;
5508
8237
  function getCompanion() {
5509
8238
  try {
5510
- const { mtimeMs } = statSync2(companionPath());
8239
+ const { mtimeMs } = statSync4(companionPath());
5511
8240
  if (_cachedCompanion && mtimeMs === _companionMtime) return _cachedCompanion;
5512
8241
  _companionMtime = mtimeMs;
5513
- _cachedCompanion = JSON.parse(readFileSync5(companionPath(), "utf-8"));
8242
+ _cachedCompanion = normalizeCompanion(JSON.parse(readFileSync15(companionPath(), "utf-8")));
5514
8243
  return _cachedCompanion;
5515
8244
  } catch {
5516
8245
  return _cachedCompanion;
@@ -5637,52 +8366,52 @@ function startApp(state2, cleanup2) {
5637
8366
  }
5638
8367
  try {
5639
8368
  const pp = roadmapPath(state2.cwd, state2.selectedSessionId);
5640
- if (existsSync3(pp)) {
5641
- planContent = readFileSync5(pp, "utf-8");
8369
+ if (existsSync10(pp)) {
8370
+ planContent = readFileSync15(pp, "utf-8");
5642
8371
  }
5643
8372
  } catch {
5644
8373
  }
5645
8374
  try {
5646
8375
  const gp = goalPath(state2.cwd, state2.selectedSessionId);
5647
- if (existsSync3(gp)) {
5648
- goalContent = readFileSync5(gp, "utf-8");
8376
+ if (existsSync10(gp)) {
8377
+ goalContent = readFileSync15(gp, "utf-8");
5649
8378
  }
5650
8379
  } catch {
5651
8380
  }
5652
8381
  try {
5653
8382
  const sp = strategyPath(state2.cwd, state2.selectedSessionId);
5654
- if (existsSync3(sp)) {
5655
- strategyContent = readFileSync5(sp, "utf-8");
8383
+ if (existsSync10(sp)) {
8384
+ strategyContent = readFileSync15(sp, "utf-8");
5656
8385
  }
5657
8386
  } catch {
5658
8387
  }
5659
8388
  try {
5660
- const cp = join5(contextDir(state2.cwd, state2.selectedSessionId), "completion-summary.md");
5661
- if (existsSync3(cp)) {
5662
- completionSummaryContent = readFileSync5(cp, "utf-8");
8389
+ const cp = join14(contextDir(state2.cwd, state2.selectedSessionId), "completion-summary.md");
8390
+ if (existsSync10(cp)) {
8391
+ completionSummaryContent = readFileSync15(cp, "utf-8");
5663
8392
  }
5664
8393
  } catch {
5665
8394
  }
5666
8395
  try {
5667
8396
  const ld = logsDir(state2.cwd, state2.selectedSessionId);
5668
- if (existsSync3(ld)) {
8397
+ if (existsSync10(ld)) {
5669
8398
  if (state2.selectedSessionId !== cachedLogSessionId) {
5670
8399
  cachedLogFiles = /* @__PURE__ */ new Map();
5671
8400
  cachedLogSessionId = state2.selectedSessionId;
5672
8401
  }
5673
- const files = readdirSync2(ld).filter((f) => f.startsWith("cycle-")).sort();
8402
+ const files = readdirSync7(ld).filter((f) => f.startsWith("cycle-")).sort();
5674
8403
  const fileSet = new Set(files);
5675
8404
  for (const key of cachedLogFiles.keys()) {
5676
8405
  if (!fileSet.has(key)) cachedLogFiles.delete(key);
5677
8406
  }
5678
8407
  for (const f of files) {
5679
- const filePath = join5(ld, f);
5680
- const mtime = statSync2(filePath).mtimeMs;
8408
+ const filePath = join14(ld, f);
8409
+ const mtime = statSync4(filePath).mtimeMs;
5681
8410
  const cached = cachedLogFiles.get(f);
5682
8411
  if (!cached || cached.mtime !== mtime) {
5683
8412
  const match = f.match(/cycle-(\d+)\.md$/);
5684
8413
  const cycle = match ? parseInt(match[1], 10) : 0;
5685
- const content = readFileSync5(filePath, "utf-8");
8414
+ const content = readFileSync15(filePath, "utf-8");
5686
8415
  cachedLogFiles.set(f, { mtime, cycle, content });
5687
8416
  }
5688
8417
  }
@@ -5696,13 +8425,13 @@ function startApp(state2, cleanup2) {
5696
8425
  }
5697
8426
  try {
5698
8427
  const cd = contextDir(state2.cwd, state2.selectedSessionId);
5699
- if (existsSync3(cd)) {
5700
- const entries = readdirSync2(cd, { withFileTypes: true }).filter((e) => !e.name.startsWith("."));
8428
+ if (existsSync10(cd)) {
8429
+ const entries = readdirSync7(cd, { withFileTypes: true }).filter((e) => !e.name.startsWith("."));
5701
8430
  const flat = [];
5702
8431
  for (const e of entries) {
5703
8432
  if (e.isDirectory()) {
5704
8433
  try {
5705
- const sub = readdirSync2(join5(cd, e.name)).filter((f) => !f.startsWith(".")).map((f) => `${e.name}/${f}`);
8434
+ const sub = readdirSync7(join14(cd, e.name)).filter((f) => !f.startsWith(".")).map((f) => `${e.name}/${f}`);
5706
8435
  flat.push(...sub);
5707
8436
  } catch {
5708
8437
  }
@@ -5716,8 +8445,8 @@ function startApp(state2, cleanup2) {
5716
8445
  }
5717
8446
  try {
5718
8447
  const dp = digestPath(state2.cwd, state2.selectedSessionId);
5719
- if (existsSync3(dp)) {
5720
- const raw = JSON.parse(readFileSync5(dp, "utf-8"));
8448
+ if (existsSync10(dp)) {
8449
+ const raw = JSON.parse(readFileSync15(dp, "utf-8"));
5721
8450
  if (raw && typeof raw.recentWork === "string" && typeof raw.currentActivity === "string" && typeof raw.whatsNext === "string" && Array.isArray(raw.unusualEvents)) {
5722
8451
  digestData = raw;
5723
8452
  }
@@ -5860,8 +8589,8 @@ function startApp(state2, cleanup2) {
5860
8589
  if (cursorNode.filePath !== cachedContextFilePath) {
5861
8590
  cachedContextFilePath = cursorNode.filePath;
5862
8591
  try {
5863
- if (existsSync3(cursorNode.filePath)) {
5864
- cachedContextFileContent = readFileSync5(cursorNode.filePath, "utf-8");
8592
+ if (existsSync10(cursorNode.filePath)) {
8593
+ cachedContextFileContent = readFileSync15(cursorNode.filePath, "utf-8");
5865
8594
  } else {
5866
8595
  cachedContextFileContent = null;
5867
8596
  }
@@ -5877,7 +8606,7 @@ function startApp(state2, cleanup2) {
5877
8606
  const treeFocused = state2.mode === "navigate" && state2.focusPane === "tree";
5878
8607
  const treeInputs = `${state2.treeCacheKey}:${state2.cursorIndex}:${treeFocused}`;
5879
8608
  const bottomInputs = `${state2.notification}:${state2.error}:${state2.mode}:${state2.searchText}:${cursorNode?.type}`;
5880
- const overlayMode = state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "open-menu" || state2.mode === "agent-menu" || state2.mode === "session-menu" || state2.mode === "go-menu" || state2.mode === "help" || state2.mode === "companion-overlay" || state2.mode === "companion-debug" ? state2.mode : "";
8609
+ const overlayMode = state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "open-menu" || state2.mode === "agent-menu" || state2.mode === "session-menu" || state2.mode === "go-menu" || state2.mode === "companion-menu" || state2.mode === "help" || state2.mode === "companion-overlay" || state2.mode === "companion-debug" ? state2.mode : "";
5881
8610
  let companionFP = "";
5882
8611
  if (state2.mode === "companion-overlay" || state2.mode === "companion-debug") {
5883
8612
  const c = getCompanion();
@@ -5947,7 +8676,8 @@ function startApp(state2, cleanup2) {
5947
8676
  open: "green",
5948
8677
  agent: "blue",
5949
8678
  session: "red",
5950
- go: "yellow"
8679
+ go: "yellow",
8680
+ companion: "magenta"
5951
8681
  };
5952
8682
  const menuId = MENU_FOR_MODE[state2.mode];
5953
8683
  if (menuId) {
@@ -6063,8 +8793,8 @@ var cwd = getArg("cwd") ?? process.cwd();
6063
8793
  var askId = getArg("ask");
6064
8794
  var sessionId = getArg("session-id");
6065
8795
  if (askId && sessionId) {
6066
- const { runSingleAsk } = await import("./single-ask-6G4BIVY2.js");
6067
- await runSingleAsk({ cwd, sessionId, askId });
8796
+ const { runSingleAsk: runSingleAsk2 } = await Promise.resolve().then(() => (init_single_ask(), single_ask_exports));
8797
+ await runSingleAsk2({ cwd, sessionId, askId });
6068
8798
  process.exit(0);
6069
8799
  }
6070
8800
  registerDashboardWindow();