sisyphi 1.2.18 → 1.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -87,8 +87,8 @@ function askReviewSubmitFlagPath(cwd, sessionId, askId) {
87
87
  function askVisualsDir(cwd, sessionId, askId) {
88
88
  return join(askEntryDir(cwd, sessionId, askId), "visuals");
89
89
  }
90
- function tmuxSessionName(cwd, sessionLabel) {
91
- return `ssyph_${basename(cwd)}_${sessionLabel}`;
90
+ function tmuxSessionName(cwd, sessionLabel2) {
91
+ return `ssyph_${basename(cwd)}_${sessionLabel2}`;
92
92
  }
93
93
  function sessionsManifestPath() {
94
94
  return join(globalDir(), "sessions-manifest.json");
@@ -422,7 +422,7 @@ var init_state = __esm({
422
422
  });
423
423
 
424
424
  // src/shared/platform.ts
425
- import { execSync as execSync7 } from "child_process";
425
+ import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
426
426
  import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
427
427
  function detectPlatform() {
428
428
  if (cachedPlatform) return cachedPlatform;
@@ -467,6 +467,20 @@ function hasCommand(cmd) {
467
467
  return false;
468
468
  }
469
469
  }
470
+ function isTerminalFrontmost() {
471
+ if (detectPlatform() !== "darwin") return false;
472
+ try {
473
+ const raw = execFileSync2(
474
+ "osascript",
475
+ ["-e", 'tell application "System Events" to get name of first application process whose frontmost is true'],
476
+ { timeout: 1e3, stdio: ["ignore", "pipe", "ignore"] }
477
+ );
478
+ const name = raw.toString().trim().toLowerCase();
479
+ return TERMINAL_APP_NAMES.has(name);
480
+ } catch {
481
+ return false;
482
+ }
483
+ }
470
484
  function platformLabel() {
471
485
  switch (detectPlatform()) {
472
486
  case "darwin":
@@ -483,11 +497,24 @@ function platformLabel() {
483
497
  return "unknown";
484
498
  }
485
499
  }
486
- var cachedPlatform, cmdCache;
500
+ var cachedPlatform, cmdCache, TERMINAL_APP_NAMES;
487
501
  var init_platform = __esm({
488
502
  "src/shared/platform.ts"() {
489
503
  "use strict";
490
504
  cmdCache = /* @__PURE__ */ new Map();
505
+ TERMINAL_APP_NAMES = /* @__PURE__ */ new Set([
506
+ "iterm2",
507
+ "iterm",
508
+ "terminal",
509
+ "apple_terminal",
510
+ "alacritty",
511
+ "kitty",
512
+ "wezterm",
513
+ "ghostty",
514
+ "hyper",
515
+ "warp",
516
+ "tmux"
517
+ ]);
491
518
  }
492
519
  });
493
520
 
@@ -635,13 +662,14 @@ var init_notify = __esm({
635
662
  });
636
663
 
637
664
  // src/daemon/ask-store.ts
638
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync3 } from "fs";
665
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
639
666
  import { basename as basename3 } from "path";
640
- function maybeNotifyOnAskCreated(cwd, sessionId, meta) {
667
+ function maybeNotifyOnAskCreated(cwd, sessionId, meta, suppress = false) {
641
668
  if (process.env.NODE_ENV === "test" || process.env.SISYPHUS_DISABLE_NOTIFY === "1") return;
642
669
  const isActionable = meta.kind !== void 0 && ACTIONABLE_KINDS.has(meta.kind);
643
670
  const isHeartbeat = meta.askedBy === HEARTBEAT_ASKED_BY;
644
671
  if (!isActionable && !isHeartbeat) return;
672
+ if (suppress) return;
645
673
  try {
646
674
  const config = loadConfig(cwd);
647
675
  if (config.notifications?.enabled === false) return;
@@ -667,8 +695,7 @@ function createAsk(cwd, sessionId, params) {
667
695
  ...params.title !== void 0 ? { title: params.title } : {},
668
696
  ...params.subtitle !== void 0 ? { subtitle: params.subtitle } : {},
669
697
  ...params.kind !== void 0 ? { kind: params.kind } : {},
670
- ...params.orphanTarget !== void 0 ? { orphanTarget: params.orphanTarget } : {},
671
- ...params.modeTransition !== void 0 ? { modeTransition: params.modeTransition } : {}
698
+ ...params.orphanTarget !== void 0 ? { orphanTarget: params.orphanTarget } : {}
672
699
  };
673
700
  atomicWrite(askMetaPath(cwd, sessionId, params.askId), JSON.stringify(meta, null, 2));
674
701
  emitHistoryEvent(sessionId, "ask-issued", {
@@ -677,7 +704,7 @@ function createAsk(cwd, sessionId, params) {
677
704
  blocking: params.blocking,
678
705
  askedAt
679
706
  });
680
- maybeNotifyOnAskCreated(cwd, sessionId, meta);
707
+ maybeNotifyOnAskCreated(cwd, sessionId, meta, params.suppressTerminalNotification === true);
681
708
  return meta;
682
709
  }
683
710
  function writeDecisions(cwd, sessionId, askId, deck) {
@@ -898,9 +925,9 @@ var init_exec = __esm({
898
925
  });
899
926
 
900
927
  // src/daemon/frontmatter.ts
901
- import { readFileSync as readFileSync15, existsSync as existsSync14, readdirSync as readdirSync5 } from "fs";
928
+ import { readFileSync as readFileSync16, existsSync as existsSync14, readdirSync as readdirSync5 } from "fs";
902
929
  import { homedir as homedir8 } from "os";
903
- import { join as join15, basename as basename5 } from "path";
930
+ import { join as join16, basename as basename5 } from "path";
904
931
  function parseAgentFrontmatter(content) {
905
932
  const match = content.match(/^---\n([\s\S]*?)\n---/);
906
933
  if (!match) return {};
@@ -950,7 +977,7 @@ function discoverAgentTypes(pluginDir, cwd) {
950
977
  if (seen.has(qualifiedName)) continue;
951
978
  seen.add(qualifiedName);
952
979
  try {
953
- const content = readFileSync15(join15(dir, file), "utf-8");
980
+ const content = readFileSync16(join16(dir, file), "utf-8");
954
981
  const fm = parseAgentFrontmatter(content);
955
982
  results.push({ qualifiedName, source, description: fm.description, model: fm.model });
956
983
  } catch {
@@ -958,14 +985,14 @@ function discoverAgentTypes(pluginDir, cwd) {
958
985
  }
959
986
  }
960
987
  }
961
- scanDir(join15(projectAgentPluginDir(cwd), "agents"), null, "project-sis");
962
- scanDir(join15(userAgentPluginDir(), "agents"), null, "user-sis");
963
- scanDir(join15(cwd, ".claude", "agents"), null, "project");
964
- scanDir(join15(homedir8(), ".claude", "agents"), null, "user");
965
- scanDir(join15(pluginDir, "agents"), "sisyphus", "bundled");
988
+ scanDir(join16(projectAgentPluginDir(cwd), "agents"), null, "project-sis");
989
+ scanDir(join16(userAgentPluginDir(), "agents"), null, "user-sis");
990
+ scanDir(join16(cwd, ".claude", "agents"), null, "project");
991
+ scanDir(join16(homedir8(), ".claude", "agents"), null, "user");
992
+ scanDir(join16(pluginDir, "agents"), "sisyphus", "bundled");
966
993
  try {
967
- const registryPath2 = join15(homedir8(), ".claude", "plugins", "installed_plugins.json");
968
- const registry = JSON.parse(readFileSync15(registryPath2, "utf-8"));
994
+ const registryPath2 = join16(homedir8(), ".claude", "plugins", "installed_plugins.json");
995
+ const registry = JSON.parse(readFileSync16(registryPath2, "utf-8"));
969
996
  const pluginEntries = registry.plugins ?? registry;
970
997
  for (const key of Object.keys(pluginEntries)) {
971
998
  const atIdx = key.indexOf("@");
@@ -974,7 +1001,7 @@ function discoverAgentTypes(pluginDir, cwd) {
974
1001
  const entry = pluginEntries[key];
975
1002
  const installPath = Array.isArray(entry) ? entry[0]?.installPath : entry?.installPath;
976
1003
  if (installPath) {
977
- scanDir(join15(installPath, "agents"), namespace, "plugin");
1004
+ scanDir(join16(installPath, "agents"), namespace, "plugin");
978
1005
  }
979
1006
  }
980
1007
  } catch {
@@ -996,8 +1023,8 @@ var init_effort_render = __esm({
996
1023
  });
997
1024
 
998
1025
  // src/daemon/extensions.ts
999
- import { existsSync as existsSync15, readFileSync as readFileSync16, readdirSync as readdirSync6, copyFileSync as copyFileSync2, mkdirSync as mkdirSync8, statSync as statSync3, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
1000
- import { resolve as resolve6, join as join16, basename as basename6, relative } from "path";
1026
+ import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync6, copyFileSync as copyFileSync2, mkdirSync as mkdirSync8, statSync as statSync3, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
1027
+ import { resolve as resolve6, join as join17, basename as basename6, relative } from "path";
1001
1028
  function resolveBundledTemplateDir(kind) {
1002
1029
  const built = resolve6(import.meta.dirname, "../templates", kind);
1003
1030
  if (existsSync15(built)) return built;
@@ -1014,12 +1041,12 @@ var init_extensions = __esm({
1014
1041
  });
1015
1042
 
1016
1043
  // src/shared/version.ts
1017
- import { readFileSync as readFileSync21 } from "fs";
1044
+ import { readFileSync as readFileSync22 } from "fs";
1018
1045
  import { resolve as resolve7 } from "path";
1019
1046
  function readSisyphusVersion() {
1020
1047
  for (const rel of ["../package.json", "../../package.json"]) {
1021
1048
  try {
1022
- const raw = readFileSync21(resolve7(import.meta.dirname, rel), "utf-8");
1049
+ const raw = readFileSync22(resolve7(import.meta.dirname, rel), "utf-8");
1023
1050
  const pkg = JSON.parse(raw);
1024
1051
  if (pkg.name === "sisyphi" && pkg.version) return pkg.version;
1025
1052
  } catch {
@@ -1086,9 +1113,9 @@ var init_upload = __esm({
1086
1113
  // src/shared/session-export.ts
1087
1114
  import { execFile as execFile2 } from "child_process";
1088
1115
  import { promisify } from "util";
1089
- import { existsSync as existsSync25, readFileSync as readFileSync25, mkdirSync as mkdirSync11, symlinkSync, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "fs";
1116
+ import { existsSync as existsSync25, readFileSync as readFileSync26, mkdirSync as mkdirSync11, symlinkSync, rmSync as rmSync5, writeFileSync as writeFileSync13 } from "fs";
1090
1117
  import { homedir as homedir12 } from "os";
1091
- import { join as join22 } from "path";
1118
+ import { join as join23 } from "path";
1092
1119
  function sanitizeName(name) {
1093
1120
  return name.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
1094
1121
  }
@@ -1096,11 +1123,11 @@ function buildOutputPath(label, dir) {
1096
1123
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1097
1124
  mkdirSync11(dir, { recursive: true });
1098
1125
  const base = `sisyphus-${label}-${date}`;
1099
- let candidate = join22(dir, `${base}.zip`);
1126
+ let candidate = join23(dir, `${base}.zip`);
1100
1127
  let counter = 1;
1101
1128
  while (existsSync25(candidate)) {
1102
1129
  counter++;
1103
- candidate = join22(dir, `${base}-${counter}.zip`);
1130
+ candidate = join23(dir, `${base}-${counter}.zip`);
1104
1131
  }
1105
1132
  return candidate;
1106
1133
  }
@@ -1170,24 +1197,24 @@ async function exportSessionToZip(sessionId, cwd, options) {
1170
1197
  const stPath = statePath(cwd, sessionId);
1171
1198
  if (existsSync25(stPath)) {
1172
1199
  try {
1173
- const state = JSON.parse(readFileSync25(stPath, "utf-8"));
1200
+ const state = JSON.parse(readFileSync26(stPath, "utf-8"));
1174
1201
  if (state.name) {
1175
1202
  label = sanitizeName(state.name);
1176
1203
  }
1177
1204
  } catch {
1178
1205
  }
1179
1206
  }
1180
- const dir = options?.outputDir ?? join22(homedir12(), "Downloads");
1207
+ const dir = options?.outputDir ?? join23(homedir12(), "Downloads");
1181
1208
  const outputPath = buildOutputPath(label, dir);
1182
1209
  const tmpDir = `/tmp/sisyphus-export-${sessionId.slice(0, 8)}-${Date.now()}`;
1183
1210
  try {
1184
1211
  mkdirSync11(tmpDir, { recursive: true });
1185
- writeFileSync12(join22(tmpDir, "CLAUDE.md"), generateGuide(), "utf-8");
1212
+ writeFileSync13(join23(tmpDir, "CLAUDE.md"), generateGuide(), "utf-8");
1186
1213
  if (sessExists) {
1187
- symlinkSync(sessDir, join22(tmpDir, "session"));
1214
+ symlinkSync(sessDir, join23(tmpDir, "session"));
1188
1215
  }
1189
1216
  if (histExists) {
1190
- symlinkSync(histDir, join22(tmpDir, "history"));
1217
+ symlinkSync(histDir, join23(tmpDir, "history"));
1191
1218
  }
1192
1219
  const parts = ["CLAUDE.md", sessExists ? "session/" : "", histExists ? "history/" : ""].filter(Boolean);
1193
1220
  await execFileAsync("zip", ["-rq", outputPath, ...parts], { cwd: tmpDir });
@@ -1398,9 +1425,9 @@ var init_companion_normalize = __esm({
1398
1425
  });
1399
1426
 
1400
1427
  // src/daemon/companion.ts
1401
- import { existsSync as existsSync27, mkdirSync as mkdirSync12, readFileSync as readFileSync27, renameSync as renameSync4, writeFileSync as writeFileSync14 } from "fs";
1428
+ import { existsSync as existsSync27, mkdirSync as mkdirSync12, readFileSync as readFileSync28, renameSync as renameSync4, writeFileSync as writeFileSync15 } from "fs";
1402
1429
  import { randomUUID as randomUUID3 } from "crypto";
1403
- import { dirname as dirname9, join as join24 } from "path";
1430
+ import { dirname as dirname9, join as join25 } from "path";
1404
1431
  function loadCompanion() {
1405
1432
  const path = companionPath();
1406
1433
  if (!existsSync27(path)) {
@@ -1408,7 +1435,7 @@ function loadCompanion() {
1408
1435
  saveCompanion(state2);
1409
1436
  return state2;
1410
1437
  }
1411
- const raw = readFileSync27(path, "utf-8");
1438
+ const raw = readFileSync28(path, "utf-8");
1412
1439
  const state = JSON.parse(raw);
1413
1440
  return normalizeCompanion(state);
1414
1441
  }
@@ -1416,8 +1443,8 @@ function saveCompanion(state) {
1416
1443
  const path = companionPath();
1417
1444
  const dir = dirname9(path);
1418
1445
  mkdirSync12(dir, { recursive: true });
1419
- const tmp = join24(dir, `.companion.${randomUUID3()}.tmp`);
1420
- writeFileSync14(tmp, JSON.stringify(state, null, 2), "utf-8");
1446
+ const tmp = join25(dir, `.companion.${randomUUID3()}.tmp`);
1447
+ writeFileSync15(tmp, JSON.stringify(state, null, 2), "utf-8");
1421
1448
  renameSync4(tmp, path);
1422
1449
  }
1423
1450
  function createDefaultCompanion() {
@@ -1470,8 +1497,8 @@ var init_companion = __esm({
1470
1497
  });
1471
1498
 
1472
1499
  // src/daemon/companion-memory.ts
1473
- import { existsSync as existsSync28, mkdirSync as mkdirSync13, readFileSync as readFileSync28, renameSync as renameSync5, writeFileSync as writeFileSync15 } from "fs";
1474
- import { dirname as dirname10, join as join25 } from "path";
1500
+ import { existsSync as existsSync28, mkdirSync as mkdirSync13, readFileSync as readFileSync29, renameSync as renameSync5, writeFileSync as writeFileSync16 } from "fs";
1501
+ import { dirname as dirname10, join as join26 } from "path";
1475
1502
  import { randomUUID as randomUUID4 } from "crypto";
1476
1503
  import { z } from "zod";
1477
1504
  function isSafeObservationText(text) {
@@ -1501,7 +1528,7 @@ function loadMemoryStrict() {
1501
1528
  if (!existsSync28(path)) return defaultMemoryState();
1502
1529
  let raw;
1503
1530
  try {
1504
- raw = readFileSync28(path, "utf-8");
1531
+ raw = readFileSync29(path, "utf-8");
1505
1532
  } catch (err) {
1506
1533
  throw new MemoryStoreParseError(err);
1507
1534
  }
@@ -1597,10 +1624,10 @@ var init_companion_render = __esm({
1597
1624
  });
1598
1625
 
1599
1626
  // src/daemon/companion-popup.ts
1600
- import { writeFileSync as writeFileSync16, readFileSync as readFileSync29, unlinkSync as unlinkSync4, existsSync as existsSync29 } from "fs";
1601
- import { tmpdir as tmpdir2 } from "os";
1602
- import { join as join26, resolve as resolve10 } from "path";
1603
- function wrapText(text, width) {
1627
+ import { writeFileSync as writeFileSync17, readFileSync as readFileSync30, unlinkSync as unlinkSync6, existsSync as existsSync29 } from "fs";
1628
+ import { tmpdir as tmpdir3 } from "os";
1629
+ import { join as join27, resolve as resolve10 } from "path";
1630
+ function wrapText2(text, width) {
1604
1631
  const words = text.split(" ");
1605
1632
  const lines = [];
1606
1633
  let current = "";
@@ -1630,16 +1657,16 @@ function showCommentaryPopupQueue(pages) {
1630
1657
  const defaultTitle = ` (${face}) `;
1631
1658
  let maxContentHeight = 0;
1632
1659
  for (let i = 0; i < pages.length; i++) {
1633
- const lines = wrapText(pages[i].text, INNER_WIDTH);
1660
+ const lines = wrapText2(pages[i].text, INNER_WIDTH2);
1634
1661
  const isLast = i === pages.length - 1;
1635
1662
  const hint = isLast ? "[0:ok 1:good 2:bad 3:whip]" : "[enter:next 0-3:rate]";
1636
- const hintPad = Math.max(0, Math.floor((INNER_WIDTH - hint.length) / 2));
1663
+ const hintPad = Math.max(0, Math.floor((INNER_WIDTH2 - hint.length) / 2));
1637
1664
  const hintLine = " ".repeat(hintPad + 2) + hint;
1638
1665
  const content = "\n\n" + lines.map((l) => ` ${l}`).join("\n") + "\n\n" + hintLine + "\n";
1639
1666
  const contentLineCount = content.split("\n").length - 1;
1640
1667
  const contentHeight = Math.max(contentLineCount + 2, 5);
1641
1668
  if (contentHeight > maxContentHeight) maxContentHeight = contentHeight;
1642
- writeFileSync16(`${POPUP_TMP_PREFIX}-${i}.txt`, content);
1669
+ writeFileSync17(`${POPUP_TMP_PREFIX}-${i}.txt`, content);
1643
1670
  }
1644
1671
  const whipAvailable = existsSync29(WHIP_ANIMATION_PATH);
1645
1672
  if (whipAvailable && maxContentHeight < WHIP_ANIMATION_ROWS + 2) {
@@ -1688,9 +1715,9 @@ if [ ! -f "$RESULT_FILE" ]; then
1688
1715
  printf 'neutral' > "$RESULT_FILE"
1689
1716
  fi
1690
1717
  `;
1691
- writeFileSync16(POPUP_SCRIPT, script, { mode: 493 });
1718
+ writeFileSync17(POPUP_SCRIPT, script, { mode: 493 });
1692
1719
  try {
1693
- unlinkSync4(POPUP_RESULT_PREFIX);
1720
+ unlinkSync6(POPUP_RESULT_PREFIX);
1694
1721
  } catch {
1695
1722
  }
1696
1723
  const clientsRaw = execSafe('tmux list-clients -F "#{client_name} #{client_width}"');
@@ -1700,27 +1727,27 @@ fi
1700
1727
  const client = line.slice(0, lastSpace);
1701
1728
  const clientWidth = parseInt(line.slice(lastSpace + 1), 10);
1702
1729
  if (!clientWidth) continue;
1703
- const x = Math.max(0, clientWidth - POPUP_WIDTH);
1730
+ const x = Math.max(0, clientWidth - POPUP_WIDTH2 - 1);
1704
1731
  const args2 = [
1705
1732
  `-c ${shellQuote(client)}`,
1706
1733
  "-E -b rounded",
1707
1734
  `-T ${shellQuote(initialTitle)}`,
1708
1735
  `-S "fg=${moodColor}"`,
1709
1736
  `-s "fg=${moodColor}"`,
1710
- `-x ${x} -y 0`,
1711
- `-w ${POPUP_WIDTH} -h ${maxContentHeight}`,
1737
+ `-x ${x} -y 2`,
1738
+ `-w ${POPUP_WIDTH2} -h ${maxContentHeight}`,
1712
1739
  shellQuote(POPUP_SCRIPT)
1713
1740
  ].join(" ");
1714
1741
  execSafe(`tmux display-popup ${args2}`);
1715
1742
  }
1716
1743
  let raw;
1717
1744
  try {
1718
- raw = readFileSync29(POPUP_RESULT_PREFIX, "utf8").trim();
1745
+ raw = readFileSync30(POPUP_RESULT_PREFIX, "utf8").trim();
1719
1746
  } catch {
1720
1747
  return null;
1721
1748
  } finally {
1722
1749
  try {
1723
- unlinkSync4(POPUP_RESULT_PREFIX);
1750
+ unlinkSync6(POPUP_RESULT_PREFIX);
1724
1751
  } catch {
1725
1752
  }
1726
1753
  }
@@ -1734,7 +1761,7 @@ fi
1734
1761
  }
1735
1762
  return null;
1736
1763
  }
1737
- var POPUP_WIDTH, INNER_WIDTH, POPUP_DURATION, POPUP_TMP_PREFIX, POPUP_SCRIPT, POPUP_RESULT_PREFIX, WHIP_ANIMATION_PATH, WHIP_ANIMATION_ROWS;
1764
+ var POPUP_WIDTH2, INNER_WIDTH2, POPUP_DURATION, POPUP_TMP_PREFIX, POPUP_SCRIPT, POPUP_RESULT_PREFIX, WHIP_ANIMATION_PATH, WHIP_ANIMATION_ROWS;
1738
1765
  var init_companion_popup = __esm({
1739
1766
  "src/daemon/companion-popup.ts"() {
1740
1767
  "use strict";
@@ -1743,12 +1770,12 @@ var init_companion_popup = __esm({
1743
1770
  init_config();
1744
1771
  init_exec();
1745
1772
  init_shell();
1746
- POPUP_WIDTH = 38;
1747
- INNER_WIDTH = POPUP_WIDTH - 6;
1773
+ POPUP_WIDTH2 = 38;
1774
+ INNER_WIDTH2 = POPUP_WIDTH2 - 6;
1748
1775
  POPUP_DURATION = 15;
1749
- POPUP_TMP_PREFIX = join26(tmpdir2(), "sisyphus-popup");
1750
- POPUP_SCRIPT = join26(tmpdir2(), "sisyphus-popup.sh");
1751
- POPUP_RESULT_PREFIX = join26(tmpdir2(), "sisyphus-popup-result");
1776
+ POPUP_TMP_PREFIX = join27(tmpdir3(), "sisyphus-popup");
1777
+ POPUP_SCRIPT = join27(tmpdir3(), "sisyphus-popup.sh");
1778
+ POPUP_RESULT_PREFIX = join27(tmpdir3(), "sisyphus-popup-result");
1752
1779
  WHIP_ANIMATION_PATH = resolve10(import.meta.dirname, "../templates/whip-animation.sh");
1753
1780
  WHIP_ANIMATION_ROWS = 12;
1754
1781
  }
@@ -1776,9 +1803,9 @@ __export(tmux_exports, {
1776
1803
  windowExists: () => windowExists
1777
1804
  });
1778
1805
  import { execSync as execSync17 } from "child_process";
1779
- import { join as join27 } from "path";
1780
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync17, mkdtempSync, rmSync as rmSync7, cpSync as cpSync3, existsSync as existsSync30, mkdirSync as mkdirSync14 } from "fs";
1781
- import { tmpdir as tmpdir3 } from "os";
1806
+ import { join as join28 } from "path";
1807
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync18, mkdtempSync, rmSync as rmSync7, cpSync as cpSync3, existsSync as existsSync30, mkdirSync as mkdirSync14 } from "fs";
1808
+ import { tmpdir as tmpdir4 } from "os";
1782
1809
  function getWindowId() {
1783
1810
  const pane = process.env["TMUX_PANE"];
1784
1811
  if (pane) {
@@ -1829,8 +1856,8 @@ function registerDashboardWindow(cwd) {
1829
1856
  }
1830
1857
  }
1831
1858
  function setupCompanionPlugin() {
1832
- const srcDir = join27(import.meta.dirname, "templates", "companion-plugin");
1833
- const destDir = join27(globalDir(), "companion-plugin");
1859
+ const srcDir = join28(import.meta.dirname, "templates", "companion-plugin");
1860
+ const destDir = join28(globalDir(), "companion-plugin");
1834
1861
  if (!existsSync30(destDir)) mkdirSync14(destDir, { recursive: true });
1835
1862
  cpSync3(srcDir, destDir, { recursive: true });
1836
1863
  return destDir;
@@ -1858,18 +1885,18 @@ function openCompanionPane(cwd) {
1858
1885
  return;
1859
1886
  }
1860
1887
  const pluginDir = setupCompanionPlugin();
1861
- const templatePath2 = join27(import.meta.dirname, "templates", "dashboard-claude.md");
1888
+ const templatePath2 = join28(import.meta.dirname, "templates", "dashboard-claude.md");
1862
1889
  let template;
1863
1890
  try {
1864
- template = readFileSync30(templatePath2, "utf-8");
1891
+ template = readFileSync31(templatePath2, "utf-8");
1865
1892
  } catch {
1866
1893
  template = `You are a Sisyphus dashboard companion. Help the user manage multi-agent sessions.
1867
1894
  Project: ${cwd}
1868
1895
  Run \`sis session inspect list\` and \`sis session inspect status\` to see current state.`;
1869
1896
  }
1870
1897
  const rendered = template.replace(/\{\{CWD\}\}/g, cwd);
1871
- const promptPath = join27(globalDir(), "dashboard-companion-prompt.md");
1872
- writeFileSync17(promptPath, rendered, "utf-8");
1898
+ const promptPath = join28(globalDir(), "dashboard-companion-prompt.md");
1899
+ writeFileSync18(promptPath, rendered, "utf-8");
1873
1900
  const pathEnv = augmentedPath();
1874
1901
  const claudeCmd = `SISYPHUS_COMPANION_CWD=${shellQuote(cwd)} PATH=${shellQuote(pathEnv)} claude --dangerously-skip-permissions --plugin-dir ${shellQuote(pluginDir)} --append-system-prompt "$(cat ${shellQuote(promptPath)})"`;
1875
1902
  const result = exec2(
@@ -1884,12 +1911,12 @@ function switchToSession(sessionName) {
1884
1911
  execSafe(`tmux switch-client -t ${shellQuote(sessionName)}`);
1885
1912
  }
1886
1913
  function editInPopup(cwd, editor, opts) {
1887
- const tmpDir = mkdtempSync(join27(tmpdir3(), "sisyphus-"));
1888
- const filePath = join27(tmpDir, "input.md");
1914
+ const tmpDir = mkdtempSync(join28(tmpdir4(), "sisyphus-"));
1915
+ const filePath = join28(tmpDir, "input.md");
1889
1916
  try {
1890
- writeFileSync17(filePath, opts?.content ? opts.content : "", "utf-8");
1917
+ writeFileSync18(filePath, opts?.content ? opts.content : "", "utf-8");
1891
1918
  openEditorPopup(cwd, editor, filePath, opts?.size);
1892
- const result = readFileSync30(filePath, "utf-8").trim();
1919
+ const result = readFileSync31(filePath, "utf-8").trim();
1893
1920
  return result || null;
1894
1921
  } finally {
1895
1922
  rmSync7(tmpDir, { recursive: true, force: true });
@@ -1897,8 +1924,8 @@ function editInPopup(cwd, editor, opts) {
1897
1924
  }
1898
1925
  function promptInPopup(prompt, opts) {
1899
1926
  const { w = "50%", h = "3" } = opts ?? {};
1900
- const tmpDir = mkdtempSync(join27(tmpdir3(), "sisyphus-"));
1901
- const outFile = join27(tmpDir, "result");
1927
+ const tmpDir = mkdtempSync(join28(tmpdir4(), "sisyphus-"));
1928
+ const outFile = join28(tmpDir, "result");
1902
1929
  try {
1903
1930
  const script = `printf ${shellQuote(prompt + " ")} && read -r line && printf '%s' "$line" > ${shellQuote(outFile)}`;
1904
1931
  execSync17(
@@ -1906,7 +1933,7 @@ function promptInPopup(prompt, opts) {
1906
1933
  { stdio: "inherit", env: EXEC_ENV }
1907
1934
  );
1908
1935
  if (!existsSync30(outFile)) return null;
1909
- const result = readFileSync30(outFile, "utf-8").trim();
1936
+ const result = readFileSync31(outFile, "utf-8").trim();
1910
1937
  return result || null;
1911
1938
  } finally {
1912
1939
  rmSync7(tmpDir, { recursive: true, force: true });
@@ -1937,10 +1964,10 @@ function openClaudeResumePopup(cwd, claudeSessionId, resumeEnv, resumeArgs) {
1937
1964
  { stdio: "inherit", env: EXEC_ENV }
1938
1965
  );
1939
1966
  }
1940
- function openClaudeResumeSession(cwd, sessionId, claudeSessionId, sessionLabel, resumeEnv, resumeArgs, cycleNum, mode) {
1941
- const sessionName = tmuxSessionName(cwd, sessionLabel);
1967
+ function openClaudeResumeSession(cwd, sessionId, claudeSessionId, sessionLabel2, resumeEnv, resumeArgs, cycleNum, mode) {
1968
+ const sessionName = tmuxSessionName(cwd, sessionLabel2);
1942
1969
  const cycleLabel = cycleNum != null ? `c${cycleNum}` : "";
1943
- const paneTitle = cycleLabel ? `ssph:orch ${sessionLabel} ${cycleLabel}` : `ssph:orch ${sessionLabel}`;
1970
+ const paneTitle = cycleLabel ? `ssph:orch ${sessionLabel2} ${cycleLabel}` : `ssph:orch ${sessionLabel2}`;
1944
1971
  const existing = execSafe('tmux list-sessions -F "#{session_id}|#{session_name}"');
1945
1972
  const existingLine = existing?.split("\n").find((line) => line.slice(line.indexOf("|") + 1) === sessionName);
1946
1973
  if (existingLine) {
@@ -1948,7 +1975,7 @@ function openClaudeResumeSession(cwd, sessionId, claudeSessionId, sessionLabel,
1948
1975
  execSafe(`tmux set-option -t ${shellQuote(existingSessId)} @sisyphus_cwd ${shellQuote(cwd.replace(/\/+$/, ""))}`);
1949
1976
  execSafe(`tmux set-option -t ${shellQuote(existingSessId)} @sisyphus_session_id ${shellQuote(sessionId)}`);
1950
1977
  const firstPaneId2 = execSafe(`tmux list-panes -t ${shellQuote(existingSessId)} -F '#{pane_id}'`)?.split("\n")[0];
1951
- if (firstPaneId2) applyOrchestratorPaneStyle(firstPaneId2, paneTitle, sessionLabel, cycleLabel, mode);
1978
+ if (firstPaneId2) applyOrchestratorPaneStyle(firstPaneId2, paneTitle, sessionLabel2, cycleLabel, mode);
1952
1979
  return sessionName;
1953
1980
  }
1954
1981
  const pathEnv = augmentedPath();
@@ -1964,14 +1991,14 @@ function openClaudeResumeSession(cwd, sessionId, claudeSessionId, sessionLabel,
1964
1991
  execSafe(`tmux set -w -t ${shellQuote(newSessId + ":")} pane-border-status top`);
1965
1992
  execSafe(`tmux set -w -t ${shellQuote(newSessId + ":")} allow-rename off`);
1966
1993
  execSafe(`tmux set -w -t ${shellQuote(newSessId + ":")} automatic-rename off`);
1967
- if (firstPaneId) applyOrchestratorPaneStyle(firstPaneId, paneTitle, sessionLabel, cycleLabel, mode);
1994
+ if (firstPaneId) applyOrchestratorPaneStyle(firstPaneId, paneTitle, sessionLabel2, cycleLabel, mode);
1968
1995
  return sessionName;
1969
1996
  }
1970
- function applyOrchestratorPaneStyle(paneId, title, sessionLabel, cycleLabel, mode) {
1997
+ function applyOrchestratorPaneStyle(paneId, title, sessionLabel2, cycleLabel, mode) {
1971
1998
  const color = "yellow";
1972
1999
  execSafe(`tmux select-pane -t ${shellQuote(paneId)} -T ${shellQuote(title)}`);
1973
2000
  execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_role ${shellQuote("orch")}`);
1974
- execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_session ${shellQuote(sessionLabel)}`);
2001
+ execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_session ${shellQuote(sessionLabel2)}`);
1975
2002
  if (cycleLabel) execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_cycle ${shellQuote(cycleLabel)}`);
1976
2003
  if (mode) execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_mode ${shellQuote(mode)}`);
1977
2004
  const gitBranch = `#(cd #{pane_current_path} && git branch --show-current 2>/dev/null)`;
@@ -2024,7 +2051,7 @@ __export(creds_exports, {
2024
2051
  readTailscaleEnv: () => readTailscaleEnv,
2025
2052
  writeTailscaleEnv: () => writeTailscaleEnv
2026
2053
  });
2027
- import { chmodSync as chmodSync3, existsSync as existsSync31, mkdirSync as mkdirSync16, readFileSync as readFileSync32 } from "fs";
2054
+ import { chmodSync as chmodSync3, existsSync as existsSync31, mkdirSync as mkdirSync16, readFileSync as readFileSync33 } from "fs";
2028
2055
  import { createInterface as createInterface4 } from "readline";
2029
2056
  function isValidProvider(value) {
2030
2057
  return PROVIDERS.includes(value);
@@ -2058,7 +2085,7 @@ function serializeEnvFile(values) {
2058
2085
  }
2059
2086
  function readEnvFile(path) {
2060
2087
  if (!existsSync31(path)) return null;
2061
- return parseEnvFile(readFileSync32(path, "utf-8"));
2088
+ return parseEnvFile(readFileSync33(path, "utf-8"));
2062
2089
  }
2063
2090
  function writeEnvFile(path, values) {
2064
2091
  ensureDeployDir();
@@ -2188,12 +2215,12 @@ var init_pricing = __esm({
2188
2215
  });
2189
2216
 
2190
2217
  // src/cli/deploy/runtime.ts
2191
- import { existsSync as existsSync32, readFileSync as readFileSync33, unlinkSync as unlinkSync5 } from "fs";
2218
+ import { existsSync as existsSync32, readFileSync as readFileSync34, unlinkSync as unlinkSync7 } from "fs";
2192
2219
  function readRuntimeState(provider) {
2193
2220
  const path = deployRuntimePath(provider);
2194
2221
  if (!existsSync32(path)) return null;
2195
2222
  try {
2196
- return JSON.parse(readFileSync33(path, "utf-8"));
2223
+ return JSON.parse(readFileSync34(path, "utf-8"));
2197
2224
  } catch {
2198
2225
  return null;
2199
2226
  }
@@ -2203,7 +2230,7 @@ function writeRuntimeState(provider, state) {
2203
2230
  }
2204
2231
  function clearRuntimeState(provider) {
2205
2232
  const path = deployRuntimePath(provider);
2206
- if (existsSync32(path)) unlinkSync5(path);
2233
+ if (existsSync32(path)) unlinkSync7(path);
2207
2234
  }
2208
2235
  var init_runtime = __esm({
2209
2236
  "src/cli/deploy/runtime.ts"() {
@@ -2447,7 +2474,7 @@ var init_tailscale = __esm({
2447
2474
 
2448
2475
  // src/cli/deploy/runner.ts
2449
2476
  import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
2450
- import { copyFileSync as copyFileSync3, existsSync as existsSync34, mkdirSync as mkdirSync17, readFileSync as readFileSync34 } from "fs";
2477
+ import { copyFileSync as copyFileSync3, existsSync as existsSync34, mkdirSync as mkdirSync17, readFileSync as readFileSync35 } from "fs";
2451
2478
  function runTerraform(provider, args2, extraEnv) {
2452
2479
  ensureProviderStateDir(provider);
2453
2480
  ensureTerraformInstalled();
@@ -2485,7 +2512,7 @@ function readSshPubkey(path) {
2485
2512
  or pass --ssh-key <path>.`
2486
2513
  );
2487
2514
  }
2488
- return readFileSync34(path, "utf-8").trim();
2515
+ return readFileSync35(path, "utf-8").trim();
2489
2516
  }
2490
2517
  function readOutputs(provider) {
2491
2518
  const result = spawnSync5("terraform", ["output", "-json", `-state=${deployStatePath(provider)}`], {
@@ -2800,7 +2827,7 @@ var init_grove = __esm({
2800
2827
  // src/cli/cloud/repo.ts
2801
2828
  import { spawnSync as spawnSync7 } from "child_process";
2802
2829
  import { existsSync as existsSync35 } from "fs";
2803
- import { basename as basename8, join as join30 } from "path";
2830
+ import { basename as basename8, join as join31 } from "path";
2804
2831
  function captureGit(args2, cwd) {
2805
2832
  const result = spawnSync7("git", args2, {
2806
2833
  encoding: "utf-8",
@@ -2840,10 +2867,10 @@ function buildRsyncArgs(localDir, remoteTarget) {
2840
2867
  ];
2841
2868
  }
2842
2869
  function detectPackageManager(toplevel) {
2843
- if (existsSync35(join30(toplevel, "pnpm-lock.yaml"))) return "pnpm";
2844
- if (existsSync35(join30(toplevel, "bun.lockb"))) return "bun";
2845
- if (existsSync35(join30(toplevel, "yarn.lock"))) return "yarn";
2846
- if (existsSync35(join30(toplevel, "package-lock.json"))) return "npm";
2870
+ if (existsSync35(join31(toplevel, "pnpm-lock.yaml"))) return "pnpm";
2871
+ if (existsSync35(join31(toplevel, "bun.lockb"))) return "bun";
2872
+ if (existsSync35(join31(toplevel, "yarn.lock"))) return "yarn";
2873
+ if (existsSync35(join31(toplevel, "package-lock.json"))) return "npm";
2847
2874
  return null;
2848
2875
  }
2849
2876
  function packageManagerInstallCmd(pm) {
@@ -3126,7 +3153,7 @@ var init_protocol = __esm({
3126
3153
  });
3127
3154
 
3128
3155
  // src/daemon/spawn-helpers.ts
3129
- import { writeFileSync as writeFileSync20, existsSync as existsSync38 } from "fs";
3156
+ import { writeFileSync as writeFileSync21, existsSync as existsSync38 } from "fs";
3130
3157
  import { resolve as resolve13 } from "path";
3131
3158
  var init_spawn_helpers = __esm({
3132
3159
  "src/daemon/spawn-helpers.ts"() {
@@ -3135,17 +3162,180 @@ var init_spawn_helpers = __esm({
3135
3162
  }
3136
3163
  });
3137
3164
 
3165
+ // src/cli/help-rubric.ts
3166
+ function commandPath(cmd) {
3167
+ const parts = [];
3168
+ let cur = cmd;
3169
+ while (cur !== null) {
3170
+ parts.push(cur.name());
3171
+ cur = cur.parent;
3172
+ }
3173
+ parts.reverse();
3174
+ parts.shift();
3175
+ return parts.join(" ");
3176
+ }
3177
+ function subcommandRubric(cmd) {
3178
+ if (cmd.name() === "help") {
3179
+ return cmd.summary() || cmd.description();
3180
+ }
3181
+ const p = commandPath(cmd);
3182
+ let key = p;
3183
+ const deployProviderMatch = p.match(/^deploy (hetzner|aws) (.+)$/);
3184
+ if (deployProviderMatch) {
3185
+ key = `deploy * ${deployProviderMatch[2]}`;
3186
+ }
3187
+ const r = RUBRICS[key];
3188
+ if (r) {
3189
+ return `${r.short} | use when ${r.useWhen}`;
3190
+ }
3191
+ return cmd.summary() || cmd.description();
3192
+ }
3193
+ var RUBRICS, CONCEPTS_BLOCK, ROOT_AFTER_HELP;
3194
+ var init_help_rubric = __esm({
3195
+ "src/cli/help-rubric.ts"() {
3196
+ "use strict";
3197
+ RUBRICS = {
3198
+ "session": { short: "Manage tracked orchestration sessions", useWhen: "acting on a unit of orchestrated work" },
3199
+ "agent": { short: "Operate on worker agents", useWhen: "directing or reading a spawned agent" },
3200
+ "orch": { short: "Talk to / steer the orchestrator", useWhen: "guiding the session's orchestrator" },
3201
+ "ask": { short: "Human-in-the-loop question I/O", useWhen: "answering or polling a blocking ask" },
3202
+ "ui": { short: "Interactive surfaces", useWhen: "a human wants the dashboard, guide, or scratch" },
3203
+ "segment": { short: "Status-line segment registration", useWhen: "wiring tmux status indicators" },
3204
+ "admin": { short: "Install, verify, and report", useWhen: "setting up or diagnosing the install" },
3205
+ "companion": { short: "Companion-pane helper", useWhen: "driving the companion Claude pane" },
3206
+ "deploy": { short: "Provision cloud boxes (Terraform)", useWhen: "standing up or tearing down infra" },
3207
+ "cloud": { short: "Per-repo workflow on a deployed box", useWhen: "syncing work to/from the box" },
3208
+ "feedback": { short: "Report a problem with sisyphus itself", useWhen: "the user complains about the tool or workflow" },
3209
+ "session lifecycle": { short: "Start/stop/advance a session", useWhen: "changing whether a session runs" },
3210
+ "session inspect": { short: "Read session state", useWhen: "you need status/history/context without mutating" },
3211
+ "session config": { short: "Change a session's settings", useWhen: "adjusting task, effort, or dangerous mode" },
3212
+ "session recover": { short: "Repair or relocate a session", useWhen: "a session is stuck, lost, or needs rollback" },
3213
+ "session scratch": { short: "Open a standalone (non-sisyphus) Claude", useWhen: "you want a throwaway Claude in this repo" },
3214
+ "session lifecycle start": { short: "Start a new session", useWhen: "beginning work on a new task" },
3215
+ "session lifecycle complete": { short: "Mark the current cycle complete", useWhen: "the orchestrator finished its work" },
3216
+ "session lifecycle continue": { short: "Continue past completion into a new cycle", useWhen: "more work follows completion" },
3217
+ "session lifecycle resume": { short: "Resume a paused/handed-off session", useWhen: "bringing a stopped session back" },
3218
+ "session lifecycle kill": { short: "Stop a session, keep its state", useWhen: "halting work but preserving the record" },
3219
+ "session lifecycle delete": { short: "Remove a session and its state", useWhen: "discarding a session permanently" },
3220
+ "session inspect status": { short: "Show live session status", useWhen: "checking what is running now" },
3221
+ "session inspect list": { short: "List sessions", useWhen: "enumerating sessions in this repo" },
3222
+ "session inspect history": { short: "Show past sessions/cycles", useWhen: "reviewing prior runs" },
3223
+ "session inspect context": { short: "Print the orchestrator's context", useWhen: "auditing what the orchestrator sees" },
3224
+ "session inspect export": { short: "Export session transcript/state", useWhen: "archiving or sharing a session" },
3225
+ "session inspect requirements": { short: "Show/export the session requirements", useWhen: "reviewing the locked requirements doc" },
3226
+ "session config task": { short: "Set the session's task text", useWhen: "retargeting what the session works on" },
3227
+ "session config effort": { short: "Set the effort tier", useWhen: "tuning model effort for a session" },
3228
+ "session config dangerous": { short: "Toggle dangerous (skip-permissions) mode", useWhen: "allowing unattended tool use" },
3229
+ "session recover rollback": { short: "Roll back to an earlier cycle", useWhen: "a cycle went wrong" },
3230
+ "session recover reconnect": { short: "Reattach the daemon to a live session", useWhen: "the tmux/daemon link dropped" },
3231
+ "session recover quiesce": { short: "Pause a session at the next safe point", useWhen: "you need it to stop cleanly" },
3232
+ "session recover clone": { short: "Clone a session toward a new goal", useWhen: "forking work from an existing session" },
3233
+ "agent spawn": { short: "Spawn a worker agent", useWhen: "delegating a scoped sub-task" },
3234
+ "agent submit": { short: "Submit this agent's result upstream", useWhen: "from inside an agent, its work is done" },
3235
+ "agent report": { short: "Post a progress report", useWhen: "from inside an agent, updating mid-task" },
3236
+ "agent await": { short: "Block until a spawned agent submits", useWhen: "you need an agent's result before continuing" },
3237
+ "agent ctl": { short: "Agent process control", useWhen: "killing or restarting an agent process" },
3238
+ "agent io": { short: "Agent message I/O", useWhen: "sending text to or reading an agent" },
3239
+ "agent ctl kill": { short: "Kill an agent", useWhen: "an agent must stop immediately" },
3240
+ "agent ctl restart": { short: "Restart an agent", useWhen: "an agent is wedged and should start over" },
3241
+ "agent io tell": { short: "Send text to an agent", useWhen: "injecting instructions into a running agent" },
3242
+ "agent io read": { short: "Read an agent's transcript", useWhen: "inspecting what an agent produced" },
3243
+ "orch yield": { short: "Yield control back to the human/parent", useWhen: "the orchestrator is done or blocked" },
3244
+ "orch tell": { short: "Send text to the orchestrator", useWhen: "injecting guidance into the orchestrator" },
3245
+ "orch message": { short: "Queue a message for the orchestrator", useWhen: "leaving async input for the next turn" },
3246
+ "orch read": { short: "Read the orchestrator's transcript", useWhen: "inspecting orchestrator output" },
3247
+ "ask submit": { short: "Submit an ask deck for a human", useWhen: "an agent needs a human decision" },
3248
+ "ask poll": { short: "Block until an ask is answered", useWhen: "you need the human's answer before continuing" },
3249
+ "ask peek": { short: "Read an ask's state without blocking", useWhen: "checking ask status non-blocking" },
3250
+ "ui dashboard": { short: "Open the TUI dashboard", useWhen: "a human wants the monitoring UI" },
3251
+ "ui guide": { short: "Print the full usage guide", useWhen: "a human needs end-to-end docs" },
3252
+ "segment register": { short: "Register a status-line segment", useWhen: "adding a tmux status indicator" },
3253
+ "segment unregister": { short: "Remove a status-line segment", useWhen: "tearing one down" },
3254
+ "admin install": { short: "Install / uninstall sisyphus bits", useWhen: "first setup or removal" },
3255
+ "admin check": { short: "Verify the installation", useWhen: "diagnosing a broken or partial install" },
3256
+ "admin report": { short: "Diagnostics & telemetry", useWhen: "filing a bug or uploading session data" },
3257
+ "admin install setup": { short: "Run first-time setup", useWhen: "installing sisyphus on this machine" },
3258
+ "admin install setup-keybind": { short: "Install the tmux keybind", useWhen: "wiring the dashboard hotkey" },
3259
+ "admin install init": { short: "Initialize repo-local config", useWhen: "onboarding a new repo" },
3260
+ "admin install uninstall": { short: "Remove sisyphus", useWhen: "fully removing the install" },
3261
+ "admin check doctor": { short: "Run install diagnostics", useWhen: "something is not working and you want a health check" },
3262
+ "admin check check-keybinds": { short: "Verify tmux keybinds", useWhen: "the dashboard hotkey does not fire" },
3263
+ "admin check check-statusbar": { short: "Verify status-line wiring", useWhen: "status segments do not render" },
3264
+ "admin report bug": { short: "File a bug report", useWhen: "reporting a defect with context" },
3265
+ "admin report upload": { short: "Upload session data", useWhen: "sharing a session for support" },
3266
+ "admin report configure-upload": { short: "Configure the upload target", useWhen: "setting where uploads go" },
3267
+ "admin clean-zombies": { short: "Sweep stale zombie processes", useWhen: "cleaning up after a crashed daemon or hung agent" },
3268
+ "cloud box": { short: "Provision / operate the box for this repo", useWhen: "working with the box-side environment" },
3269
+ "cloud handoff": { short: "Move a session between local and box", useWhen: "relocating in-flight work" },
3270
+ "cloud box sync": { short: "Rsync this repo to the box", useWhen: "pushing local code to the box" },
3271
+ "cloud box install": { short: "Run the package install on the box", useWhen: "box deps are missing/stale" },
3272
+ "cloud box session": { short: "Create/refresh the box tmux home", useWhen: "the box-side session is absent" },
3273
+ "cloud box attach": { short: "Attach to the box session", useWhen: "you want to work on the box interactively" },
3274
+ "cloud box status": { short: "Print box-side status", useWhen: "checking box state for this repo" },
3275
+ "cloud box login": { short: "Run claude auth login on the box", useWhen: "the box needs Claude credentials" },
3276
+ "cloud box up": { short: "Sync+install+session in one shot", useWhen: "bringing the box up from cold" },
3277
+ "cloud handoff push": { short: "Hand a live session off to the box", useWhen: "moving local work to the cloud" },
3278
+ "cloud handoff pull": { short: "Reclaim a handed-off session locally", useWhen: "bringing cloud work back home" },
3279
+ "agent types": { short: "Agent-type catalog", useWhen: "discovering installable agent types" },
3280
+ "agent types list": { short: "List agent types", useWhen: "enumerating spawnable agent types" },
3281
+ "deploy list": { short: "List deploy providers", useWhen: "discovering supported providers" },
3282
+ "companion profile": { short: "Print companion profile", useWhen: "reading the combined memory + context + badges blob" },
3283
+ "companion memory": { short: "Show accumulated companion observations", useWhen: "reviewing what the companion noticed" },
3284
+ "companion context": { short: "Emit per-prompt companion context", useWhen: "the companion plugin hook needs context" },
3285
+ "companion pane": { short: "Open/focus the side claude pane", useWhen: "you want the companion pane by the dashboard" },
3286
+ "companion popup-test": { short: "Show a test commentary popup", useWhen: "validating feedback-key handling" },
3287
+ "deploy auth": { short: "Configure provider auth", useWhen: "setting up Tailscale/provider credentials" },
3288
+ "deploy hetzner": { short: "Hetzner box lifecycle", useWhen: "targeting Hetzner" },
3289
+ "deploy aws": { short: "AWS box lifecycle", useWhen: "targeting AWS" },
3290
+ "deploy auth tailscale": { short: "Configure Tailscale OAuth", useWhen: "joining the box to your tailnet" },
3291
+ "deploy * up": { short: "Provision the box", useWhen: "standing the box up" },
3292
+ "deploy * down": { short: "Destroy the box", useWhen: "tearing the box down" },
3293
+ "deploy * status": { short: "Print box outputs", useWhen: "checking IP/cost/instance" },
3294
+ "deploy * ssh": { short: "SSH/mosh into the box", useWhen: "getting a shell on the box" },
3295
+ "deploy * logs": { short: "Tail cloud-init + daemon logs", useWhen: "diagnosing a box-side failure" },
3296
+ "deploy * update": { short: "Upgrade sisyphus on the box", useWhen: "pulling the latest daemon onto the box" }
3297
+ };
3298
+ CONCEPTS_BLOCK = `
3299
+ Concepts
3300
+ session a tracked unit of orchestrated work on one task
3301
+ agent a worker Claude spawned to execute a scoped sub-task
3302
+ orchestrator the Claude that owns a session: decomposes, spawns, advances
3303
+ cycle one discovery\u2192plan\u2192implement\u2192validate iteration; the rollback unit
3304
+ ask a blocking question surfaced for a human to answer
3305
+ mode the session's current phase, driving orchestrator behavior
3306
+ `;
3307
+ ROOT_AFTER_HELP = `
3308
+ I/O contract: flags and positional args on input, JSON on stdout (JSONL for streams).
3309
+
3310
+ Exit codes:
3311
+ 0 success
3312
+ 1 permanent error (fallback)
3313
+ 2 usage error (bad args/shape)
3314
+ 3 not found
3315
+ 4 ambiguous (multiple matches \u2014 see error.candidates)
3316
+ 5 conflict (already-exists, wrong-state)
3317
+ 60 transient (retry-safe: daemon down, timeout, lock contention)
3318
+
3319
+ Errors:
3320
+ {"ok": false,
3321
+ "error": {"code": "<stable-enum>", "kind": "<usage|not_found|ambiguous|conflict|transient|permanent>",
3322
+ "message": "...", "received"?: ..., "expected"?: ..., "next"?: "...", "candidates"?: [...]}}
3323
+ `;
3324
+ }
3325
+ });
3326
+
3138
3327
  // src/daemon/help-inject.ts
3139
3328
  import { execSync as execSync19 } from "child_process";
3140
3329
  var init_help_inject = __esm({
3141
3330
  "src/daemon/help-inject.ts"() {
3142
3331
  "use strict";
3143
3332
  init_spawn_helpers();
3333
+ init_help_rubric();
3144
3334
  }
3145
3335
  });
3146
3336
 
3147
3337
  // src/shared/digest-verbs.ts
3148
- import { existsSync as existsSync39, readFileSync as readFileSync37 } from "fs";
3338
+ import { existsSync as existsSync39, readFileSync as readFileSync38 } from "fs";
3149
3339
  var init_digest_verbs = __esm({
3150
3340
  "src/shared/digest-verbs.ts"() {
3151
3341
  "use strict";
@@ -3161,8 +3351,8 @@ var init_colors = __esm({
3161
3351
  });
3162
3352
 
3163
3353
  // src/daemon/orchestrator-modes.ts
3164
- import { existsSync as existsSync40, readdirSync as readdirSync10, readFileSync as readFileSync38 } from "fs";
3165
- import { resolve as resolve14, join as join32 } from "path";
3354
+ import { existsSync as existsSync40, readdirSync as readdirSync10, readFileSync as readFileSync39 } from "fs";
3355
+ import { resolve as resolve14, join as join33 } from "path";
3166
3356
  import { homedir as homedir14 } from "os";
3167
3357
  var init_orchestrator_modes = __esm({
3168
3358
  "src/daemon/orchestrator-modes.ts"() {
@@ -3173,8 +3363,8 @@ var init_orchestrator_modes = __esm({
3173
3363
  });
3174
3364
 
3175
3365
  // src/daemon/lib/render-plugin.ts
3176
- import { readdirSync as readdirSync11, readFileSync as readFileSync39, writeFileSync as writeFileSync21, mkdirSync as mkdirSync18, copyFileSync as copyFileSync4, rmSync as rmSync8, existsSync as existsSync41 } from "fs";
3177
- import { join as join33 } from "path";
3366
+ import { readdirSync as readdirSync11, readFileSync as readFileSync40, writeFileSync as writeFileSync22, mkdirSync as mkdirSync18, copyFileSync as copyFileSync4, rmSync as rmSync8, existsSync as existsSync41 } from "fs";
3367
+ import { join as join34 } from "path";
3178
3368
  var init_render_plugin = __esm({
3179
3369
  "src/daemon/lib/render-plugin.ts"() {
3180
3370
  "use strict";
@@ -3255,10 +3445,10 @@ var init_orphan_sweep = __esm({
3255
3445
  });
3256
3446
 
3257
3447
  // src/daemon/agent.ts
3258
- import { readFileSync as readFileSync40, writeFileSync as writeFileSync22, mkdirSync as mkdirSync19, readdirSync as readdirSync12, existsSync as existsSync43, unlinkSync as unlinkSync6 } from "fs";
3448
+ import { readFileSync as readFileSync41, writeFileSync as writeFileSync23, mkdirSync as mkdirSync19, readdirSync as readdirSync12, existsSync as existsSync43, unlinkSync as unlinkSync8 } from "fs";
3259
3449
  import { execSync as execSync21 } from "child_process";
3260
3450
  import { randomUUID as randomUUID5 } from "crypto";
3261
- import { resolve as resolve15, relative as relative2, dirname as dirname13, join as join34 } from "path";
3451
+ import { resolve as resolve15, relative as relative2, dirname as dirname13, join as join35 } from "path";
3262
3452
  var init_agent = __esm({
3263
3453
  "src/daemon/agent.ts"() {
3264
3454
  "use strict";
@@ -3336,25 +3526,11 @@ var init_pane_monitor = __esm({
3336
3526
  }
3337
3527
  });
3338
3528
 
3339
- // src/daemon/mode-notify.ts
3340
- import { existsSync as existsSync44 } from "fs";
3341
- import { ulid as ulid3 } from "ulid";
3342
- var init_mode_notify = __esm({
3343
- "src/daemon/mode-notify.ts"() {
3344
- "use strict";
3345
- init_ask_store();
3346
- init_state();
3347
- init_orchestrator_modes();
3348
- init_paths();
3349
- init_types();
3350
- }
3351
- });
3352
-
3353
3529
  // src/daemon/orchestrator.ts
3354
- import { existsSync as existsSync45, readdirSync as readdirSync13, readFileSync as readFileSync41, writeFileSync as writeFileSync23 } from "fs";
3530
+ import { existsSync as existsSync44, readdirSync as readdirSync13, readFileSync as readFileSync42, writeFileSync as writeFileSync24 } from "fs";
3355
3531
  import { execSync as execSync22 } from "child_process";
3356
3532
  import { randomUUID as randomUUID6 } from "crypto";
3357
- import { resolve as resolve16, join as join35, relative as relative3 } from "path";
3533
+ import { resolve as resolve16, join as join36, relative as relative3 } from "path";
3358
3534
  var init_orchestrator = __esm({
3359
3535
  "src/daemon/orchestrator.ts"() {
3360
3536
  "use strict";
@@ -3376,13 +3552,12 @@ var init_orchestrator = __esm({
3376
3552
  init_tmux2();
3377
3553
  init_pane_registry();
3378
3554
  init_pane_monitor();
3379
- init_mode_notify();
3380
3555
  init_plugins();
3381
3556
  }
3382
3557
  });
3383
3558
 
3384
3559
  // src/daemon/status-dots.ts
3385
- import { readFileSync as readFileSync42 } from "fs";
3560
+ import { readFileSync as readFileSync43 } from "fs";
3386
3561
  var COMPLETED_TTL_MS;
3387
3562
  var init_status_dots = __esm({
3388
3563
  "src/daemon/status-dots.ts"() {
@@ -3394,9 +3569,17 @@ var init_status_dots = __esm({
3394
3569
  }
3395
3570
  });
3396
3571
 
3572
+ // src/daemon/mode-transition.ts
3573
+ var init_mode_transition = __esm({
3574
+ "src/daemon/mode-transition.ts"() {
3575
+ "use strict";
3576
+ init_orchestrator_modes();
3577
+ }
3578
+ });
3579
+
3397
3580
  // src/daemon/uploader.ts
3398
3581
  import { rm } from "fs/promises";
3399
- import { tmpdir as tmpdir4 } from "os";
3582
+ import { tmpdir as tmpdir5 } from "os";
3400
3583
  var init_uploader = __esm({
3401
3584
  "src/daemon/uploader.ts"() {
3402
3585
  "use strict";
@@ -3435,7 +3618,7 @@ var init_format = __esm({
3435
3618
 
3436
3619
  // src/daemon/session-manager.ts
3437
3620
  import { v4 as uuidv4 } from "uuid";
3438
- import { existsSync as existsSync46, readFileSync as readFileSync43, readdirSync as readdirSync14, rmSync as rmSync9 } from "fs";
3621
+ import { existsSync as existsSync45, readFileSync as readFileSync44, readdirSync as readdirSync14, rmSync as rmSync9 } from "fs";
3439
3622
  var init_session_manager = __esm({
3440
3623
  "src/daemon/session-manager.ts"() {
3441
3624
  "use strict";
@@ -3458,6 +3641,7 @@ var init_session_manager = __esm({
3458
3641
  init_companion_render();
3459
3642
  init_companion_commentary();
3460
3643
  init_companion_popup();
3644
+ init_mode_transition();
3461
3645
  init_history();
3462
3646
  init_uploader();
3463
3647
  init_upload();
@@ -3472,7 +3656,7 @@ var init_session_manager = __esm({
3472
3656
  // src/daemon/transcript-digest.ts
3473
3657
  import { openSync, fstatSync, readSync, closeSync } from "fs";
3474
3658
  import { homedir as homedir15 } from "os";
3475
- import { join as join36 } from "path";
3659
+ import { join as join37 } from "path";
3476
3660
  var BYTE_CAP, TAIL_BYTES;
3477
3661
  var init_transcript_digest = __esm({
3478
3662
  "src/daemon/transcript-digest.ts"() {
@@ -3486,7 +3670,7 @@ var init_transcript_digest = __esm({
3486
3670
  import {
3487
3671
  closeSync as closeSync2,
3488
3672
  constants,
3489
- existsSync as existsSync47,
3673
+ existsSync as existsSync46,
3490
3674
  fstatSync as fstatSync2,
3491
3675
  lstatSync as lstatSync2,
3492
3676
  openSync as openSync2,
@@ -3522,17 +3706,17 @@ var init_ask_visual = __esm({
3522
3706
 
3523
3707
  // src/daemon/server.ts
3524
3708
  import { createServer } from "net";
3525
- import { unlinkSync as unlinkSync7, existsSync as existsSync48, writeFileSync as writeFileSync24, readFileSync as readFileSync45, mkdirSync as mkdirSync20, readdirSync as readdirSync15, rmSync as rmSync11, chmodSync as chmodSync4 } from "fs";
3526
- import { join as join37, basename as basename10, dirname as dirname15 } from "path";
3709
+ import { unlinkSync as unlinkSync9, existsSync as existsSync47, writeFileSync as writeFileSync25, readFileSync as readFileSync46, mkdirSync as mkdirSync20, readdirSync as readdirSync15, rmSync as rmSync11, chmodSync as chmodSync4 } from "fs";
3710
+ import { join as join38, basename as basename10, dirname as dirname15 } from "path";
3527
3711
  import { scanInbox } from "@crouton-kit/humanloop";
3528
3712
  function registryPath() {
3529
- return join37(globalDir(), "session-registry.json");
3713
+ return join38(globalDir(), "session-registry.json");
3530
3714
  }
3531
3715
  function loadSessionRegistry() {
3532
3716
  const p = registryPath();
3533
- if (!existsSync48(p)) return {};
3717
+ if (!existsSync47(p)) return {};
3534
3718
  try {
3535
- return JSON.parse(readFileSync45(p, "utf-8"));
3719
+ return JSON.parse(readFileSync46(p, "utf-8"));
3536
3720
  } catch (err) {
3537
3721
  console.warn("[sisyphus] Failed to parse session registry:", err instanceof Error ? err.message : err);
3538
3722
  return {};
@@ -3563,8 +3747,8 @@ var init_server = __esm({
3563
3747
 
3564
3748
  // src/cli/index.ts
3565
3749
  import { Command } from "commander";
3566
- import { existsSync as existsSync50, mkdirSync as mkdirSync21, readFileSync as readFileSync46 } from "fs";
3567
- import { dirname as dirname16, join as join38 } from "path";
3750
+ import { existsSync as existsSync49, mkdirSync as mkdirSync21, readFileSync as readFileSync47 } from "fs";
3751
+ import { dirname as dirname16, join as join39 } from "path";
3568
3752
  import { fileURLToPath as fileURLToPath6 } from "url";
3569
3753
 
3570
3754
  // src/cli/commands/start.ts
@@ -5670,6 +5854,10 @@ function assertTmux() {
5670
5854
  throw new Error("Not running inside a tmux pane. Sisyphus requires tmux.");
5671
5855
  }
5672
5856
  }
5857
+ function getTmuxSession() {
5858
+ assertTmux();
5859
+ return execSync4('tmux display-message -p "#{session_name}"', { encoding: "utf8" }).trim();
5860
+ }
5673
5861
  function getTmuxSessionInfo() {
5674
5862
  assertTmux();
5675
5863
  const out = execSync4('tmux display-message -p "#{session_id}|#{session_name}"', { encoding: "utf8" }).trim();
@@ -5686,6 +5874,27 @@ function getCurrentTmuxSessionHome(sessionId) {
5686
5874
  return "";
5687
5875
  }
5688
5876
  }
5877
+ function findHomeSession(cwd) {
5878
+ const normalizedCwd = cwd.replace(/\/+$/, "");
5879
+ let output;
5880
+ try {
5881
+ output = execSync4('tmux list-sessions -F "#{session_id}|#{session_name}"', {
5882
+ encoding: "utf-8",
5883
+ stdio: ["pipe", "pipe", "pipe"]
5884
+ }).trim();
5885
+ } catch {
5886
+ return null;
5887
+ }
5888
+ for (const line of output.split("\n").filter(Boolean)) {
5889
+ const pipeIdx = line.indexOf("|");
5890
+ if (pipeIdx < 0) continue;
5891
+ const sessId = line.slice(0, pipeIdx);
5892
+ const name = line.slice(pipeIdx + 1);
5893
+ if (name.startsWith("ssyph_")) continue;
5894
+ if (getCurrentTmuxSessionHome(sessId) === normalizedCwd) return sessId;
5895
+ }
5896
+ return null;
5897
+ }
5689
5898
 
5690
5899
  // src/cli/commands/start.ts
5691
5900
  init_shell();
@@ -5939,6 +6148,27 @@ function openDashboardWindow(tmuxSession, cwd) {
5939
6148
  }
5940
6149
  } catch {
5941
6150
  }
6151
+ try {
6152
+ const windows = execSync6(
6153
+ `tmux list-windows -t ${shellQuote(tmuxSession)} -F "#{window_id} #{window_name}"`,
6154
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6155
+ ).trim();
6156
+ for (const line of windows.split("\n").filter(Boolean)) {
6157
+ const spaceIdx = line.indexOf(" ");
6158
+ if (spaceIdx < 0) continue;
6159
+ const wid = line.slice(0, spaceIdx);
6160
+ const wname = line.slice(spaceIdx + 1);
6161
+ if (wname === "sisyphus-dashboard") {
6162
+ execSync6(
6163
+ `tmux set-option -t ${shellQuote(tmuxSession)} @sisyphus_dashboard ${shellQuote(wid)}`,
6164
+ { stdio: "pipe" }
6165
+ );
6166
+ execSync6(`tmux select-window -t ${shellQuote(wid)}`, { stdio: "pipe" });
6167
+ return false;
6168
+ }
6169
+ }
6170
+ } catch {
6171
+ }
5942
6172
  const tuiPath = join6(import.meta.dirname, "tui.js");
5943
6173
  const windowId = execSync6(
5944
6174
  `tmux new-window -t ${shellQuote(tmuxSession + ":")} -n "sisyphus-dashboard" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
@@ -6407,8 +6637,8 @@ Exit codes: 0 ok | 2 usage (missing content or --session) | 3 not_found.`
6407
6637
  }
6408
6638
 
6409
6639
  // src/cli/commands/ask.ts
6410
- import { existsSync as existsSync12, readFileSync as readFileSync12, unlinkSync as unlinkSync3, watchFile, unwatchFile } from "fs";
6411
- import { basename as basename4, join as join13, resolve as resolve5 } from "path";
6640
+ import { existsSync as existsSync12, readFileSync as readFileSync13, unlinkSync as unlinkSync5, watchFile, unwatchFile } from "fs";
6641
+ import { basename as basename4, join as join14, resolve as resolve5 } from "path";
6412
6642
  import { ulid } from "ulid";
6413
6643
 
6414
6644
  // src/shared/ask-schema.ts
@@ -6471,6 +6701,125 @@ init_state();
6471
6701
  init_types();
6472
6702
  init_exec();
6473
6703
  init_shell();
6704
+ init_platform();
6705
+
6706
+ // src/cli/commands/ask-popup.ts
6707
+ init_exec();
6708
+ init_shell();
6709
+ import { writeFileSync as writeFileSync8, readFileSync as readFileSync12, unlinkSync as unlinkSync4 } from "fs";
6710
+ import { tmpdir } from "os";
6711
+ import { join as join13 } from "path";
6712
+ var POPUP_WIDTH = 46;
6713
+ var INNER_WIDTH = POPUP_WIDTH - 6;
6714
+ var POPUP_TIMEOUT = 10;
6715
+ var ACCENT_COLOR = "colour214";
6716
+ function wrapText(text, width) {
6717
+ const words = text.split(/\s+/).filter(Boolean);
6718
+ const lines = [];
6719
+ let current = "";
6720
+ for (const word of words) {
6721
+ if (current && current.length + 1 + word.length > width) {
6722
+ lines.push(current);
6723
+ current = word;
6724
+ } else {
6725
+ current = current ? `${current} ${word}` : word;
6726
+ }
6727
+ }
6728
+ if (current) lines.push(current);
6729
+ return lines.length > 0 ? lines : [""];
6730
+ }
6731
+ function hasAttachedClient() {
6732
+ const out = execSafe('tmux list-clients -F "#{client_name}"');
6733
+ return !!out && out.split("\n").filter(Boolean).length > 0;
6734
+ }
6735
+ function showAskTriagePopup(opts) {
6736
+ const scriptPath2 = join13(tmpdir(), `sisyphus-ask-${opts.askId}.sh`);
6737
+ const resultPath = join13(tmpdir(), `sisyphus-ask-${opts.askId}.result`);
6738
+ try {
6739
+ const safeTitle = opts.title.trim().length > 0 ? opts.title : "Question pending";
6740
+ const titleLines = wrapText(safeTitle, INNER_WIDTH);
6741
+ const bodyLines = [
6742
+ "",
6743
+ ...titleLines.map((l) => ` ${l}`),
6744
+ "",
6745
+ " [1] Dismiss",
6746
+ " [2] Open in dashboard",
6747
+ " [3] Open in agent",
6748
+ "",
6749
+ ` (auto-dismiss in ${POPUP_TIMEOUT}s)`,
6750
+ ""
6751
+ ];
6752
+ const content = bodyLines.join("\n") + "\n";
6753
+ const height = bodyLines.length + 2;
6754
+ const script = `#!/bin/sh
6755
+ printf '\\033[?25l'
6756
+ stty -echo 2>/dev/null
6757
+ RESULT_FILE=${shellQuote(resultPath)}
6758
+ printf '\\033[2J\\033[H'
6759
+ cat <<'__SISYPHUS_ASK_EOF__'
6760
+ ${content}__SISYPHUS_ASK_EOF__
6761
+ while IFS= read -r -n1 -t ${POPUP_TIMEOUT} k; do
6762
+ case "$k" in
6763
+ 1|d|D|q|Q|'') printf 'dismiss' > "$RESULT_FILE"; break ;;
6764
+ 2|b|B) printf 'dashboard' > "$RESULT_FILE"; break ;;
6765
+ 3|a|A) printf 'agent' > "$RESULT_FILE"; break ;;
6766
+ esac
6767
+ done
6768
+ if [ ! -f "$RESULT_FILE" ]; then printf 'dismiss' > "$RESULT_FILE"; fi
6769
+ `;
6770
+ writeFileSync8(scriptPath2, script, { mode: 493 });
6771
+ try {
6772
+ unlinkSync4(resultPath);
6773
+ } catch {
6774
+ }
6775
+ const popupTitle = ` \u2691 ${opts.sessionLabel} `;
6776
+ const clientsRaw = execSafe('tmux list-clients -F "#{client_name} #{client_width}"');
6777
+ if (!clientsRaw) return "dismiss";
6778
+ const lines = clientsRaw.split("\n").filter(Boolean);
6779
+ if (lines.length === 0) return "dismiss";
6780
+ let raw = null;
6781
+ for (const line of lines) {
6782
+ const lastSpace = line.lastIndexOf(" ");
6783
+ const client = line.slice(0, lastSpace);
6784
+ const clientWidth = parseInt(line.slice(lastSpace + 1), 10);
6785
+ if (!clientWidth) continue;
6786
+ const x = Math.max(0, clientWidth - POPUP_WIDTH - 1);
6787
+ const args2 = [
6788
+ `-c ${shellQuote(client)}`,
6789
+ "-E -b double",
6790
+ `-T ${shellQuote(popupTitle)}`,
6791
+ `-S "fg=${ACCENT_COLOR}"`,
6792
+ `-s "fg=${ACCENT_COLOR}"`,
6793
+ `-x ${x} -y 2`,
6794
+ `-w ${POPUP_WIDTH} -h ${height}`,
6795
+ shellQuote(scriptPath2)
6796
+ ].join(" ");
6797
+ execSafe(`tmux display-popup ${args2}`);
6798
+ try {
6799
+ raw = readFileSync12(resultPath, "utf8").trim();
6800
+ } catch {
6801
+ raw = null;
6802
+ }
6803
+ if (raw) break;
6804
+ }
6805
+ if (raw === "dashboard") return "dashboard";
6806
+ if (raw === "agent") return "agent";
6807
+ return "dismiss";
6808
+ } catch {
6809
+ return "dismiss";
6810
+ } finally {
6811
+ try {
6812
+ unlinkSync4(resultPath);
6813
+ } catch {
6814
+ }
6815
+ try {
6816
+ unlinkSync4(scriptPath2);
6817
+ } catch {
6818
+ }
6819
+ }
6820
+ }
6821
+
6822
+ // src/cli/commands/ask.ts
6474
6823
  import { approveDeck, notifyDeck, launchReview, display } from "@crouton-kit/humanloop";
6475
6824
  var ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
6476
6825
  function validateAskId(askId) {
@@ -6862,7 +7211,7 @@ async function markAnswered(cwd, sessionId, askId) {
6862
7211
  function waitForOutput(cwd, sessionId, askId, initialPpid) {
6863
7212
  const outputPath = askOutputPath(cwd, sessionId, askId);
6864
7213
  if (existsSync12(outputPath)) {
6865
- return Promise.resolve(JSON.parse(readFileSync12(outputPath, "utf-8")));
7214
+ return Promise.resolve(JSON.parse(readFileSync13(outputPath, "utf-8")));
6866
7215
  }
6867
7216
  return new Promise((res, _rej) => {
6868
7217
  let ppidWatcher;
@@ -6874,7 +7223,7 @@ function waitForOutput(cwd, sessionId, askId, initialPpid) {
6874
7223
  const onChange = () => {
6875
7224
  if (!existsSync12(outputPath)) return;
6876
7225
  try {
6877
- const out = JSON.parse(readFileSync12(outputPath, "utf-8"));
7226
+ const out = JSON.parse(readFileSync13(outputPath, "utf-8"));
6878
7227
  cleanup();
6879
7228
  res(out);
6880
7229
  } catch (err) {
@@ -6901,22 +7250,86 @@ function waitForOutput(cwd, sessionId, askId, initialPpid) {
6901
7250
  process.once("SIGINT", onSigint);
6902
7251
  });
6903
7252
  }
7253
+ function focusAgentPane(callerPane) {
7254
+ const sess = execSafe(`tmux display-message -p -t ${shellQuote(callerPane)} "#{session_name}"`)?.trim();
7255
+ const win = execSafe(`tmux display-message -p -t ${shellQuote(callerPane)} "#{window_id}"`)?.trim();
7256
+ if (sess) switchAllClientsToSession(sess);
7257
+ if (win) execSafe(`tmux select-window -t ${shellQuote(win)}`);
7258
+ }
7259
+ function switchAllClientsToSession(session2) {
7260
+ const clients = execSafe('tmux list-clients -F "#{client_name}"');
7261
+ if (!clients) return;
7262
+ for (const c of clients.split("\n").filter(Boolean)) {
7263
+ execSafe(`tmux switch-client -c ${shellQuote(c)} -t ${shellQuote(session2)}`);
7264
+ }
7265
+ }
6904
7266
  function maybeSpawnAskPane(cwd, sessionId, askId, kind) {
6905
7267
  if (kind === "review") return;
6906
7268
  const callerPane = process.env.TMUX_PANE;
6907
7269
  if (!callerPane) return;
6908
7270
  if (process.env.SISYPHUS_DISABLE_ASK_PANE === "1") return;
6909
- const tuiPath = join13(import.meta.dirname, "tui.js");
7271
+ const tuiPath = join14(import.meta.dirname, "tui.js");
6910
7272
  const cmd = `node ${shellQuote(tuiPath)} --cwd ${shellQuote(cwd)} --session-id ${shellQuote(sessionId)} --ask ${shellQuote(askId)}`;
6911
- execSafe(`tmux split-window -d -h -t ${shellQuote(callerPane)} -c ${shellQuote(cwd)} ${shellQuote(cmd)}`);
7273
+ execSafe(`tmux split-window -h -t ${shellQuote(callerPane)} -c ${shellQuote(cwd)} ${shellQuote(cmd)}`);
7274
+ focusAgentPane(callerPane);
6912
7275
  }
6913
7276
  function maybeSpawnReviewPane(cwd, sessionId, askId) {
6914
7277
  const callerPane = process.env.TMUX_PANE;
6915
7278
  if (!callerPane) return;
6916
7279
  if (process.env.SISYPHUS_DISABLE_ASK_PANE === "1") return;
6917
- const cliPath = join13(import.meta.dirname, "cli.js");
7280
+ const cliPath = join14(import.meta.dirname, "cli.js");
6918
7281
  const cmd = `node ${shellQuote(cliPath)} ask review open ${shellQuote(askId)} --session ${shellQuote(sessionId)}`;
6919
7282
  execSafe(`tmux split-window -h -t ${shellQuote(callerPane)} -c ${shellQuote(cwd)} ${shellQuote(cmd)}`);
7283
+ focusAgentPane(callerPane);
7284
+ }
7285
+ function sessionLabel(cwd, sessionId) {
7286
+ try {
7287
+ if (existsSync12(statePath(cwd, sessionId))) {
7288
+ const s = getSession(cwd, sessionId);
7289
+ if (s.name) return s.name;
7290
+ }
7291
+ } catch {
7292
+ }
7293
+ return sessionId.slice(0, 8);
7294
+ }
7295
+ function shouldSuppressBanner() {
7296
+ const popupWillShow = !!process.env.TMUX_PANE && process.env.SISYPHUS_DISABLE_ASK_PANE !== "1" && hasAttachedClient();
7297
+ return popupWillShow && isTerminalFrontmost();
7298
+ }
7299
+ async function triageAsk(cwd, sessionId, askId, kind, title) {
7300
+ const callerPane = process.env.TMUX_PANE;
7301
+ if (!callerPane) return;
7302
+ if (process.env.SISYPHUS_DISABLE_ASK_PANE === "1") return;
7303
+ const choice = showAskTriagePopup({
7304
+ askId,
7305
+ sessionLabel: sessionLabel(cwd, sessionId),
7306
+ title: title !== void 0 ? title : "Question pending"
7307
+ });
7308
+ if (choice === "dismiss") return;
7309
+ if (choice === "agent") {
7310
+ if (kind === "review") maybeSpawnReviewPane(cwd, sessionId, askId);
7311
+ else maybeSpawnAskPane(cwd, sessionId, askId, kind);
7312
+ return;
7313
+ }
7314
+ await openInDashboard(cwd, sessionId, askId, kind);
7315
+ }
7316
+ async function openInDashboard(cwd, sessionId, askId, kind) {
7317
+ try {
7318
+ assertTmux();
7319
+ const tmuxSession = findHomeSession(cwd) ?? getTmuxSession();
7320
+ if (kind === "review") {
7321
+ openDashboardWindow(tmuxSession, cwd);
7322
+ switchAllClientsToSession(tmuxSession);
7323
+ const cliPath = join14(import.meta.dirname, "cli.js");
7324
+ const cmd = `node ${shellQuote(cliPath)} ask review open ${shellQuote(askId)} --session ${shellQuote(sessionId)}`;
7325
+ execSafe(`tmux display-popup -E -w 90% -h 90% -d ${shellQuote(cwd)} ${shellQuote(cmd)}`);
7326
+ return;
7327
+ }
7328
+ await rawSend({ type: "focus-set", cwd, sessionId, askId });
7329
+ openDashboardWindow(tmuxSession, cwd);
7330
+ switchAllClientsToSession(tmuxSession);
7331
+ } catch {
7332
+ }
6920
7333
  }
6921
7334
  async function submitDeck(deck, opts, options) {
6922
7335
  const blocking = options?.blocking !== false;
@@ -6926,6 +7339,8 @@ async function submitDeck(deck, opts, options) {
6926
7339
  const claudeSessionId = resolveClaudeSessionId(cwd, sessionId, askedBy);
6927
7340
  const askId = mintAskId();
6928
7341
  const q0 = deck.interactions[0];
7342
+ const askTitle = deck.title !== void 0 ? deck.title : q0?.title;
7343
+ const suppressTerminalNotification = blocking ? shouldSuppressBanner() : false;
6929
7344
  createAsk(cwd, sessionId, {
6930
7345
  askId,
6931
7346
  askedBy,
@@ -6933,15 +7348,16 @@ async function submitDeck(deck, opts, options) {
6933
7348
  pid: process.pid,
6934
7349
  claudeSessionId,
6935
7350
  cwd,
6936
- title: deck.title !== void 0 ? deck.title : q0?.title,
7351
+ title: askTitle,
6937
7352
  subtitle: q0?.subtitle,
6938
- kind: options?.kindOverride !== void 0 ? options.kindOverride : q0?.kind
7353
+ kind: options?.kindOverride !== void 0 ? options.kindOverride : q0?.kind,
7354
+ suppressTerminalNotification
6939
7355
  });
6940
7356
  writeDecisions(cwd, sessionId, askId, deck);
6941
7357
  if (!blocking) {
6942
7358
  return { askId };
6943
7359
  }
6944
- maybeSpawnAskPane(cwd, sessionId, askId, q0?.kind);
7360
+ await triageAsk(cwd, sessionId, askId, q0?.kind, askTitle);
6945
7361
  const output = await waitForOutput(cwd, sessionId, askId, initialPpid);
6946
7362
  await markAnswered(cwd, sessionId, askId);
6947
7363
  return { askId, output };
@@ -6994,6 +7410,8 @@ async function submitReview(absFile, opts, options) {
6994
7410
  const initialPpid = process.ppid;
6995
7411
  const claudeSessionId = resolveClaudeSessionId(cwd, sessionId, askedBy);
6996
7412
  const askId = mintAskId();
7413
+ const reviewTitle = `Review ${basename4(absFile)}`;
7414
+ const suppressTerminalNotification = blocking ? shouldSuppressBanner() : false;
6997
7415
  createAsk(cwd, sessionId, {
6998
7416
  askId,
6999
7417
  askedBy,
@@ -7001,12 +7419,13 @@ async function submitReview(absFile, opts, options) {
7001
7419
  pid: process.pid,
7002
7420
  claudeSessionId,
7003
7421
  cwd,
7004
- title: `Review ${basename4(absFile)}`,
7005
- kind: "review"
7422
+ title: reviewTitle,
7423
+ kind: "review",
7424
+ suppressTerminalNotification
7006
7425
  });
7007
7426
  writeReview(cwd, sessionId, askId, { file: absFile });
7008
7427
  if (!blocking) return { askId };
7009
- maybeSpawnReviewPane(cwd, sessionId, askId);
7428
+ await triageAsk(cwd, sessionId, askId, "review", reviewTitle);
7010
7429
  const output = await waitForOutput(cwd, sessionId, askId, initialPpid);
7011
7430
  await markAnswered(cwd, sessionId, askId);
7012
7431
  return { askId, output };
@@ -7038,7 +7457,7 @@ async function reviewOpen(askId, opts) {
7038
7457
  if (!reviewData) exitUsage("missing-review-pointer", `review.json missing for ${askId}`, { received: askId });
7039
7458
  const draftPath = askReviewDraftPath(cwd, sessionId, askId);
7040
7459
  const flagPath = askReviewSubmitFlagPath(cwd, sessionId, askId);
7041
- if (existsSync12(flagPath)) unlinkSync3(flagPath);
7460
+ if (existsSync12(flagPath)) unlinkSync5(flagPath);
7042
7461
  const result = await launchReview(reviewData.file, {
7043
7462
  output: draftPath,
7044
7463
  submitFlagPath: flagPath,
@@ -7064,7 +7483,7 @@ async function reviewSubmit(askId, opts) {
7064
7483
  let comments = [];
7065
7484
  if (existsSync12(draftPath)) {
7066
7485
  try {
7067
- const draft = JSON.parse(readFileSync12(draftPath, "utf-8"));
7486
+ const draft = JSON.parse(readFileSync13(draftPath, "utf-8"));
7068
7487
  if (Array.isArray(draft.comments)) comments = draft.comments;
7069
7488
  } catch {
7070
7489
  }
@@ -7181,7 +7600,7 @@ async function peek(askId, opts) {
7181
7600
  if (meta.completedAt) result.completedAt = meta.completedAt;
7182
7601
  try {
7183
7602
  if (existsSync12(outputPath)) {
7184
- result.output = JSON.parse(readFileSync12(outputPath, "utf-8"));
7603
+ result.output = JSON.parse(readFileSync13(outputPath, "utf-8"));
7185
7604
  }
7186
7605
  } catch (err) {
7187
7606
  if (!(err instanceof SyntaxError)) throw err;
@@ -7593,13 +8012,13 @@ Exit codes: 0 ok | 2 usage (bad state) | 3 not_found.`
7593
8012
 
7594
8013
  // src/tui/lib/context.ts
7595
8014
  init_paths();
7596
- import { readFileSync as readFileSync14, readdirSync as readdirSync4 } from "fs";
8015
+ import { readFileSync as readFileSync15, readdirSync as readdirSync4 } from "fs";
7597
8016
 
7598
8017
  // src/tui/lib/reports.ts
7599
- import { readFileSync as readFileSync13 } from "fs";
8018
+ import { readFileSync as readFileSync14 } from "fs";
7600
8019
  function loadReportContent(report) {
7601
8020
  try {
7602
- return readFileSync13(report.filePath, "utf-8");
8021
+ return readFileSync14(report.filePath, "utf-8");
7603
8022
  } catch {
7604
8023
  return report.summary;
7605
8024
  }
@@ -7616,7 +8035,7 @@ function resolveReports(reports) {
7616
8035
  // src/tui/lib/context.ts
7617
8036
  function readFileSafe(filePath) {
7618
8037
  try {
7619
- return readFileSync14(filePath, "utf-8");
8038
+ return readFileSync15(filePath, "utf-8");
7620
8039
  } catch {
7621
8040
  return null;
7622
8041
  }
@@ -7795,7 +8214,7 @@ Exit codes: 0 ok | 3 not_found (unknown session).`
7795
8214
 
7796
8215
  // src/cli/commands/spawn.ts
7797
8216
  import { existsSync as existsSync13 } from "fs";
7798
- import { join as join14 } from "path";
8217
+ import { join as join15 } from "path";
7799
8218
  function registerSpawn(program2) {
7800
8219
  program2.command("spawn").description("Spawn a new agent (orchestrator only)").option("--agent-type <type>", "Agent type (e.g. sisyphus:debug, devcore:programmer)").option("--name <name>", "Agent name").option("--stdin", "Force-read instruction from stdin (avoids shell escaping for long prompts)").option("--repo <name>", "Repo subdirectory to use for this agent").option("--session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID env var)").addHelpText(
7801
8220
  "after",
@@ -7872,7 +8291,7 @@ Exit codes: 0 ok | 2 usage | 3 not_found (unknown session) | 5 conflict.`
7872
8291
  });
7873
8292
  }
7874
8293
  if (opts.repo && opts.repo !== ".") {
7875
- const repoPath = join14(sisyphusCwd, opts.repo);
8294
+ const repoPath = join15(sisyphusCwd, opts.repo);
7876
8295
  if (!existsSync13(repoPath)) {
7877
8296
  exitError({
7878
8297
  code: "repo_not_found",
@@ -8058,7 +8477,7 @@ Exit codes: 0 ok | 2 usage | 3 not_found | 60 transient.`
8058
8477
  }
8059
8478
 
8060
8479
  // src/cli/commands/await.ts
8061
- import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
8480
+ import { existsSync as existsSync16, readFileSync as readFileSync18 } from "fs";
8062
8481
  var AWAIT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
8063
8482
  function registerAwait(program2) {
8064
8483
  program2.command("await").description("Block until an agent reaches a terminal status, then print its final report inline. Marks the agent as consumed-inline so its report is suppressed from the next cycle.").argument("<agentId>", "Agent ID to await").option("--session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID env var)").addHelpText(
@@ -8098,7 +8517,7 @@ Exit codes: 0 ok | 2 usage (missing --session) | 3 not_found (unknown agent) | 6
8098
8517
  let report = "";
8099
8518
  if (reportPath && existsSync16(reportPath)) {
8100
8519
  try {
8101
- report = readFileSync17(reportPath, "utf-8");
8520
+ report = readFileSync18(reportPath, "utf-8");
8102
8521
  } catch (err) {
8103
8522
  process.stderr.write(`Warning: could not read report at ${reportPath}: ${err instanceof Error ? err.message : err}
8104
8523
  `);
@@ -8303,15 +8722,15 @@ import { execSync as execSync10 } from "child_process";
8303
8722
 
8304
8723
  // src/cli/onboard.ts
8305
8724
  import { execSync as execSync9 } from "child_process";
8306
- import { existsSync as existsSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
8725
+ import { existsSync as existsSync17, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
8307
8726
  import { homedir as homedir9 } from "os";
8308
- import { dirname as dirname5, join as join17 } from "path";
8727
+ import { dirname as dirname5, join as join18 } from "path";
8309
8728
  import { fileURLToPath as fileURLToPath2 } from "url";
8310
8729
  init_platform();
8311
8730
 
8312
8731
  // src/shared/clipboard.ts
8313
8732
  init_platform();
8314
- import { execFileSync as execFileSync2, spawnSync } from "child_process";
8733
+ import { execFileSync as execFileSync3, spawnSync } from "child_process";
8315
8734
  function detectClipboard() {
8316
8735
  const platform = detectPlatform();
8317
8736
  if (platform === "darwin") {
@@ -8402,7 +8821,7 @@ function checkItermOptionKey() {
8402
8821
  if (process.platform !== "darwin") {
8403
8822
  return { checked: false, allCorrect: true, incorrectProfiles: [] };
8404
8823
  }
8405
- const plistPath2 = join17(homedir9(), "Library", "Preferences", "com.googlecode.iterm2.plist");
8824
+ const plistPath2 = join18(homedir9(), "Library", "Preferences", "com.googlecode.iterm2.plist");
8406
8825
  if (!existsSync17(plistPath2)) {
8407
8826
  return { checked: false, allCorrect: false, incorrectProfiles: [] };
8408
8827
  }
@@ -8427,7 +8846,7 @@ function checkItermOptionKey() {
8427
8846
  }
8428
8847
  }
8429
8848
  function hasExistingTmuxConf() {
8430
- return existsSync17(join17(homedir9(), ".tmux.conf")) || existsSync17(join17(homedir9(), ".config", "tmux", "tmux.conf"));
8849
+ return existsSync17(join18(homedir9(), ".tmux.conf")) || existsSync17(join18(homedir9(), ".config", "tmux", "tmux.conf"));
8431
8850
  }
8432
8851
  var SISYPHUS_DEFAULTS_MARKER = "# sisyphus-managed \u2014 do not edit";
8433
8852
  function buildTmuxDefaults() {
@@ -8539,8 +8958,8 @@ source-file -q ${sisyphusConf} ${SISYPHUS_DEFAULTS_MARKER}
8539
8958
  `;
8540
8959
  }
8541
8960
  function writeTmuxDefaults() {
8542
- const confPath = join17(homedir9(), ".tmux.conf");
8543
- writeFileSync9(confPath, buildTmuxDefaults(), "utf8");
8961
+ const confPath = join18(homedir9(), ".tmux.conf");
8962
+ writeFileSync10(confPath, buildTmuxDefaults(), "utf8");
8544
8963
  }
8545
8964
  function isNvimAvailable() {
8546
8965
  try {
@@ -8558,21 +8977,21 @@ function getNvimVersion() {
8558
8977
  }
8559
8978
  }
8560
8979
  function hasLazyVimConfig() {
8561
- return existsSync17(join17(homedir9(), ".config", "nvim", "lazy-lock.json"));
8980
+ return existsSync17(join18(homedir9(), ".config", "nvim", "lazy-lock.json"));
8562
8981
  }
8563
8982
  function bundledBaleiaPluginPath() {
8564
8983
  const distDir = dirname5(fileURLToPath2(import.meta.url));
8565
- return join17(distDir, "templates", "baleia.lua");
8984
+ return join18(distDir, "templates", "baleia.lua");
8566
8985
  }
8567
8986
  function installBaleiaPlugin() {
8568
- const pluginsDir = join17(homedir9(), ".config", "nvim", "lua", "plugins");
8987
+ const pluginsDir = join18(homedir9(), ".config", "nvim", "lua", "plugins");
8569
8988
  if (!existsSync17(pluginsDir)) return false;
8570
- const dest = join17(pluginsDir, "sisyphus-baleia.lua");
8989
+ const dest = join18(pluginsDir, "sisyphus-baleia.lua");
8571
8990
  if (existsSync17(dest)) return true;
8572
8991
  const src = bundledBaleiaPluginPath();
8573
8992
  if (!existsSync17(src)) return false;
8574
8993
  try {
8575
- writeFileSync9(dest, readFileSync18(src, "utf-8"), "utf8");
8994
+ writeFileSync10(dest, readFileSync19(src, "utf-8"), "utf8");
8576
8995
  return true;
8577
8996
  } catch {
8578
8997
  return false;
@@ -8595,7 +9014,7 @@ function tryAutoInstallNvim() {
8595
9014
  if (!isNvimAvailable()) {
8596
9015
  return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false, baleiaInstalled: false };
8597
9016
  }
8598
- const nvimConfigDir = join17(homedir9(), ".config", "nvim");
9017
+ const nvimConfigDir = join18(homedir9(), ".config", "nvim");
8599
9018
  let lazyVimInstalled = false;
8600
9019
  if (!existsSync17(nvimConfigDir)) {
8601
9020
  const cloneCmd = [
@@ -8613,7 +9032,7 @@ function tryAutoInstallNvim() {
8613
9032
  stdio: "inherit",
8614
9033
  env: { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" }
8615
9034
  });
8616
- const gitDir = join17(nvimConfigDir, ".git");
9035
+ const gitDir = join18(nvimConfigDir, ".git");
8617
9036
  if (existsSync17(gitDir)) {
8618
9037
  execSync9(`rm -rf "${gitDir}"`, { stdio: "pipe" });
8619
9038
  }
@@ -8900,7 +9319,7 @@ Exit codes: 0 ok | 1 conflict or requires-force`
8900
9319
 
8901
9320
  // src/cli/commands/check-keybinds.ts
8902
9321
  import { execSync as execSync11 } from "child_process";
8903
- import { readFileSync as readFileSync19 } from "fs";
9322
+ import { readFileSync as readFileSync20 } from "fs";
8904
9323
  function isTmuxInstalled2() {
8905
9324
  try {
8906
9325
  execSync11("which tmux", { stdio: "pipe" });
@@ -8953,7 +9372,7 @@ function runCheck() {
8953
9372
  let userConfAlreadySources = false;
8954
9373
  if (userConfPath !== null) {
8955
9374
  try {
8956
- userConfAlreadySources = readFileSync19(userConfPath, "utf-8").includes(sisyphusConfPath);
9375
+ userConfAlreadySources = readFileSync20(userConfPath, "utf-8").includes(sisyphusConfPath);
8957
9376
  } catch {
8958
9377
  }
8959
9378
  }
@@ -9124,9 +9543,9 @@ Exit codes: 0 ok`
9124
9543
  // src/cli/commands/check-statusbar.ts
9125
9544
  init_paths();
9126
9545
  import { execSync as execSync12 } from "child_process";
9127
- import { existsSync as existsSync18, readFileSync as readFileSync20 } from "fs";
9546
+ import { existsSync as existsSync18, readFileSync as readFileSync21 } from "fs";
9128
9547
  import { homedir as homedir10 } from "os";
9129
- import { join as join18 } from "path";
9548
+ import { join as join19 } from "path";
9130
9549
  var SISYPHUS_LEFT_TOKEN = "@sisyphus_left";
9131
9550
  var SISYPHUS_RIGHT_TOKEN = "@sisyphus_right";
9132
9551
  var TMUX_DEFAULT_STATUS_LEFT = "[#S] ";
@@ -9151,7 +9570,7 @@ function isDaemonRunning() {
9151
9570
  const pidFile = daemonPidPath();
9152
9571
  if (!existsSync18(pidFile)) return false;
9153
9572
  try {
9154
- const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
9573
+ const pid = parseInt(readFileSync21(pidFile, "utf-8").trim(), 10);
9155
9574
  if (Number.isNaN(pid) || pid <= 0) return false;
9156
9575
  process.kill(pid, 0);
9157
9576
  return true;
@@ -9194,8 +9613,8 @@ function probeTmuxOptions(serverRunning) {
9194
9613
  };
9195
9614
  }
9196
9615
  function findUserTmuxConf() {
9197
- const xdg = join18(homedir10(), ".config", "tmux", "tmux.conf");
9198
- const dotfile = join18(homedir10(), ".tmux.conf");
9616
+ const xdg = join19(homedir10(), ".config", "tmux", "tmux.conf");
9617
+ const dotfile = join19(homedir10(), ".tmux.conf");
9199
9618
  if (existsSync18(xdg)) return xdg;
9200
9619
  if (existsSync18(dotfile)) return dotfile;
9201
9620
  return null;
@@ -9207,21 +9626,21 @@ function probeUserConf() {
9207
9626
  }
9208
9627
  let contents = "";
9209
9628
  try {
9210
- contents = readFileSync20(path, "utf-8");
9629
+ contents = readFileSync21(path, "utf-8");
9211
9630
  } catch {
9212
9631
  return { path, setsStatusLeft: false, setsStatusRight: false, sourcesSisyphusManaged: false };
9213
9632
  }
9214
9633
  const lines = contents.split("\n").filter((line) => !line.trim().startsWith("#"));
9215
9634
  const setsStatusLeft = lines.some((line) => /^\s*(set|set-option)\s+-g(?:\s+-\w+)*\s+status-left\b/.test(line));
9216
9635
  const setsStatusRight = lines.some((line) => /^\s*(set|set-option)\s+-g(?:\s+-\w+)*\s+status-right\b/.test(line));
9217
- const sourcesSisyphusManaged = contents.includes(join18(homedir10(), ".sisyphus", "tmux.conf"));
9636
+ const sourcesSisyphusManaged = contents.includes(join19(homedir10(), ".sisyphus", "tmux.conf"));
9218
9637
  return { path, setsStatusLeft, setsStatusRight, sourcesSisyphusManaged };
9219
9638
  }
9220
9639
  function loadGlobalSisyphusConfig() {
9221
9640
  const path = globalConfigPath();
9222
9641
  if (!existsSync18(path)) return null;
9223
9642
  try {
9224
- const parsed = JSON.parse(readFileSync20(path, "utf-8"));
9643
+ const parsed = JSON.parse(readFileSync21(path, "utf-8"));
9225
9644
  return parsed.statusBar === void 0 ? null : parsed.statusBar;
9226
9645
  } catch {
9227
9646
  return null;
@@ -9561,7 +9980,7 @@ init_paths();
9561
9980
  import { execSync as execSync14 } from "child_process";
9562
9981
  import { existsSync as existsSync19, statSync as statSync4 } from "fs";
9563
9982
  import { homedir as homedir11 } from "os";
9564
- import { join as join19 } from "path";
9983
+ import { join as join20 } from "path";
9565
9984
  init_platform();
9566
9985
  init_plugins();
9567
9986
  function checkNodeVersion() {
@@ -9795,7 +10214,7 @@ function checkNvim() {
9795
10214
  }
9796
10215
  function checkNotifyBinary() {
9797
10216
  if (process.platform === "darwin") {
9798
- const binary = join19(homedir11(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
10217
+ const binary = join20(homedir11(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
9799
10218
  if (existsSync19(binary)) {
9800
10219
  return { name: "Notifications", status: "ok", detail: "SisyphusNotify.app built" };
9801
10220
  }
@@ -9909,13 +10328,13 @@ init_version();
9909
10328
  init_platform();
9910
10329
  init_paths();
9911
10330
  init_state();
9912
- import { execFileSync as execFileSync3, spawnSync as spawnSync2 } from "child_process";
9913
- import { existsSync as existsSync20, readFileSync as readFileSync22 } from "fs";
10331
+ import { execFileSync as execFileSync4, spawnSync as spawnSync2 } from "child_process";
10332
+ import { existsSync as existsSync20, readFileSync as readFileSync23 } from "fs";
9914
10333
  import os from "os";
9915
10334
  var REPO = "crouton-labs/sisyphus";
9916
10335
  function tryCmd(bin, args2) {
9917
10336
  try {
9918
- const out = execFileSync3(bin, args2, {
10337
+ const out = execFileSync4(bin, args2, {
9919
10338
  encoding: "utf-8",
9920
10339
  stdio: ["ignore", "pipe", "ignore"],
9921
10340
  timeout: 5e3
@@ -9971,7 +10390,7 @@ function tailLog(lines) {
9971
10390
  const path = daemonLogPath();
9972
10391
  if (!existsSync20(path)) return null;
9973
10392
  try {
9974
- const all = readFileSync22(path, "utf-8").split("\n");
10393
+ const all = readFileSync23(path, "utf-8").split("\n");
9975
10394
  return all.slice(-lines).join("\n").trim() || null;
9976
10395
  } catch {
9977
10396
  return null;
@@ -10260,8 +10679,8 @@ Exit codes: 0 ok | 1 filing error | 2 usage | 60 cloud unreachable (retry-safe)`
10260
10679
  }
10261
10680
 
10262
10681
  // src/cli/commands/init.ts
10263
- import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
10264
- import { join as join20 } from "path";
10682
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
10683
+ import { join as join21 } from "path";
10265
10684
  var DEFAULT_CONFIG2 = {};
10266
10685
  var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
10267
10686
 
@@ -10290,19 +10709,19 @@ Effects
10290
10709
  Exit codes: 0 ok`
10291
10710
  ).action((opts) => {
10292
10711
  const cwd = process.cwd();
10293
- const sisDir = join20(cwd, ".sisyphus");
10294
- const configPath = join20(sisDir, "config.json");
10712
+ const sisDir = join21(cwd, ".sisyphus");
10713
+ const configPath = join21(sisDir, "config.json");
10295
10714
  if (existsSync21(configPath)) {
10296
10715
  console.log(`Already initialized: ${configPath}`);
10297
10716
  return;
10298
10717
  }
10299
10718
  mkdirSync9(sisDir, { recursive: true });
10300
- writeFileSync10(configPath, JSON.stringify(DEFAULT_CONFIG2, null, 2) + "\n", "utf-8");
10719
+ writeFileSync11(configPath, JSON.stringify(DEFAULT_CONFIG2, null, 2) + "\n", "utf-8");
10301
10720
  console.log(`Created ${configPath}`);
10302
10721
  if (opts.orchestrator) {
10303
- const orchPath = join20(sisDir, "orchestrator.md");
10722
+ const orchPath = join21(sisDir, "orchestrator.md");
10304
10723
  if (!existsSync21(orchPath)) {
10305
- writeFileSync10(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
10724
+ writeFileSync11(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
10306
10725
  console.log(`Created ${orchPath}`);
10307
10726
  }
10308
10727
  }
@@ -10362,7 +10781,7 @@ Exit codes: 0 ok`
10362
10781
 
10363
10782
  // src/cli/commands/configure-upload.ts
10364
10783
  init_paths();
10365
- import { chmodSync as chmodSync2, existsSync as existsSync22, mkdirSync as mkdirSync10, readFileSync as readFileSync23, writeFileSync as writeFileSync11 } from "fs";
10784
+ import { chmodSync as chmodSync2, existsSync as existsSync22, mkdirSync as mkdirSync10, readFileSync as readFileSync24, writeFileSync as writeFileSync12 } from "fs";
10366
10785
  import { createInterface as createInterface3 } from "readline";
10367
10786
  import { dirname as dirname6 } from "path";
10368
10787
  async function readUrlFromInput(interactive) {
@@ -10443,7 +10862,7 @@ Exit codes: 0 ok | 1 validation or write error`
10443
10862
  let existing = {};
10444
10863
  if (existsSync22(configPath)) {
10445
10864
  try {
10446
- existing = JSON.parse(readFileSync23(configPath, "utf-8"));
10865
+ existing = JSON.parse(readFileSync24(configPath, "utf-8"));
10447
10866
  } catch {
10448
10867
  console.error(`Error: ${configPath} could not be parsed \u2014 fix or delete it first`);
10449
10868
  process.exit(1);
@@ -10451,7 +10870,7 @@ Exit codes: 0 ok | 1 validation or write error`
10451
10870
  }
10452
10871
  const merged = { ...existing, upload: { url, token } };
10453
10872
  mkdirSync10(dirname6(configPath), { recursive: true });
10454
- writeFileSync11(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
10873
+ writeFileSync12(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
10455
10874
  chmodSync2(configPath, 384);
10456
10875
  console.log(`\u2713 upload configured (${configPath})`);
10457
10876
  });
@@ -10460,10 +10879,10 @@ Exit codes: 0 ok | 1 validation or write error`
10460
10879
  // src/cli/commands/getting-started.ts
10461
10880
  import { execSync as execSync15 } from "child_process";
10462
10881
  import { existsSync as existsSync23 } from "fs";
10463
- import { dirname as dirname7, join as join21 } from "path";
10882
+ import { dirname as dirname7, join as join22 } from "path";
10464
10883
  import { fileURLToPath as fileURLToPath3 } from "url";
10465
10884
  function templatePath(name) {
10466
- return join21(dirname7(fileURLToPath3(import.meta.url)), "templates", name);
10885
+ return join22(dirname7(fileURLToPath3(import.meta.url)), "templates", name);
10467
10886
  }
10468
10887
  function isClaudeCode() {
10469
10888
  return !!process.env["CLAUDECODE"];
@@ -10830,7 +11249,7 @@ sis ui guide --tutorial 4
10830
11249
  `);
10831
11250
  }
10832
11251
  function printStep4() {
10833
- const demoPath = join21(process.cwd(), "tmp", "sisyphus-tutorial-demo");
11252
+ const demoPath = join22(process.cwd(), "tmp", "sisyphus-tutorial-demo");
10834
11253
  console.log(`
10835
11254
  <claude-instructions>
10836
11255
  # Sisyphus Getting Started \u2014 Step 4: Demo Session
@@ -10874,7 +11293,7 @@ Have the user confirm they understand these keybinds before proceeding.
10874
11293
 
10875
11294
  First, copy the demo todo app into ./tmp/ under the user's current directory and init a git repo (sisyphus needs git). Run from the user's current shell \u2014 don't \`cd\` away first:
10876
11295
  \`\`\`
10877
- mkdir -p ${join21(process.cwd(), "tmp")}
11296
+ mkdir -p ${join22(process.cwd(), "tmp")}
10878
11297
  rm -rf ${demoPath}
10879
11298
  cp -r ${templatePath("tutorial-demo")} ${demoPath}
10880
11299
  git -C ${demoPath} init
@@ -10952,7 +11371,7 @@ sis ui guide --tutorial 5
10952
11371
  `);
10953
11372
  }
10954
11373
  function printStep5() {
10955
- const demoPath = join21(process.cwd(), "tmp", "sisyphus-tutorial-demo");
11374
+ const demoPath = join22(process.cwd(), "tmp", "sisyphus-tutorial-demo");
10956
11375
  const demoExists = existsSync23(demoPath);
10957
11376
  let recentCommits = "";
10958
11377
  let topLevelFiles = "";
@@ -11057,9 +11476,9 @@ If they say yes, run:
11057
11476
  rm -rf ${demoPath}
11058
11477
  \`\`\`
11059
11478
 
11060
- Then check whether \`${join21(process.cwd(), "tmp")}\` is now empty, and if so remove it too:
11479
+ Then check whether \`${join22(process.cwd(), "tmp")}\` is now empty, and if so remove it too:
11061
11480
  \`\`\`
11062
- rmdir ${join21(process.cwd(), "tmp")} 2>/dev/null || true
11481
+ rmdir ${join22(process.cwd(), "tmp")} 2>/dev/null || true
11063
11482
  \`\`\`
11064
11483
 
11065
11484
  If they say no or want to explore first, leave it. They can clean up later with the same
@@ -11492,7 +11911,7 @@ Exit codes: 0 ok | 2 usage (invalid --tutorial value)`
11492
11911
 
11493
11912
  // src/cli/commands/history.ts
11494
11913
  init_paths();
11495
- import { readdirSync as readdirSync7, readFileSync as readFileSync24, existsSync as existsSync24 } from "fs";
11914
+ import { readdirSync as readdirSync7, readFileSync as readFileSync25, existsSync as existsSync24 } from "fs";
11496
11915
  import { resolve as resolve8 } from "path";
11497
11916
  function loadAllSummaries() {
11498
11917
  const base = historyBaseDir();
@@ -11502,7 +11921,7 @@ function loadAllSummaries() {
11502
11921
  const summaryPath = historySessionSummaryPath(name);
11503
11922
  if (existsSync24(summaryPath)) {
11504
11923
  try {
11505
- const raw = readFileSync24(summaryPath, "utf-8");
11924
+ const raw = readFileSync25(summaryPath, "utf-8");
11506
11925
  results.push({ id: name, summary: JSON.parse(raw) });
11507
11926
  continue;
11508
11927
  } catch {
@@ -11519,7 +11938,7 @@ function buildLiveSummary(sessionId) {
11519
11938
  if (!existsSync24(eventsPath)) return null;
11520
11939
  let cwd = null;
11521
11940
  try {
11522
- const lines = readFileSync24(eventsPath, "utf-8").split("\n");
11941
+ const lines = readFileSync25(eventsPath, "utf-8").split("\n");
11523
11942
  for (const line of lines) {
11524
11943
  if (!line.trim()) continue;
11525
11944
  try {
@@ -11540,7 +11959,7 @@ function buildLiveSummary(sessionId) {
11540
11959
  if (!existsSync24(sPath)) return null;
11541
11960
  let session2;
11542
11961
  try {
11543
- session2 = JSON.parse(readFileSync24(sPath, "utf-8"));
11962
+ session2 = JSON.parse(readFileSync25(sPath, "utf-8"));
11544
11963
  } catch {
11545
11964
  return null;
11546
11965
  }
@@ -11602,7 +12021,7 @@ function buildLiveSummary(sessionId) {
11602
12021
  function loadEvents(sessionId) {
11603
12022
  const eventsPath = historyEventsPath(sessionId);
11604
12023
  if (!existsSync24(eventsPath)) return [];
11605
- const lines = readFileSync24(eventsPath, "utf-8").split("\n").filter((l) => l.trim());
12024
+ const lines = readFileSync25(eventsPath, "utf-8").split("\n").filter((l) => l.trim());
11606
12025
  const events = [];
11607
12026
  for (const line of lines) {
11608
12027
  try {
@@ -11617,7 +12036,7 @@ function findSession(idOrName) {
11617
12036
  const summaryPath = historySessionSummaryPath(idOrName);
11618
12037
  if (existsSync24(summaryPath)) {
11619
12038
  try {
11620
- return { id: idOrName, summary: JSON.parse(readFileSync24(summaryPath, "utf-8")) };
12039
+ return { id: idOrName, summary: JSON.parse(readFileSync25(summaryPath, "utf-8")) };
11621
12040
  } catch {
11622
12041
  }
11623
12042
  }
@@ -11865,7 +12284,7 @@ Exit codes: 0 ok | 2 usage | 1 export_failed.`).action(async (sessionIdArg, opts
11865
12284
 
11866
12285
  // src/cli/commands/upload.ts
11867
12286
  import { rmSync as rmSync6 } from "fs";
11868
- import { tmpdir } from "os";
12287
+ import { tmpdir as tmpdir2 } from "os";
11869
12288
  init_config();
11870
12289
  init_session_export();
11871
12290
  init_upload();
@@ -11921,7 +12340,7 @@ Exit codes: 0 ok | 1 upload or export error | 2 usage`
11921
12340
  }
11922
12341
  let zipPath;
11923
12342
  try {
11924
- zipPath = await exportSessionToZip(sessionId, cwd, { reveal: false, outputDir: tmpdir() });
12343
+ zipPath = await exportSessionToZip(sessionId, cwd, { reveal: false, outputDir: tmpdir2() });
11925
12344
  } catch (err) {
11926
12345
  exitError({
11927
12346
  code: "export_failed",
@@ -11986,7 +12405,7 @@ Exit codes: 0 ok | 1 upload or export error | 2 usage`
11986
12405
  // src/cli/commands/scratch.ts
11987
12406
  import { execSync as execSync16 } from "child_process";
11988
12407
  init_shell();
11989
- function findHomeSession(cwd) {
12408
+ function findHomeSession2(cwd) {
11990
12409
  const normalizedCwd = cwd.replace(/\/+$/, "");
11991
12410
  let output;
11992
12411
  try {
@@ -12034,7 +12453,7 @@ Effects
12034
12453
  Exit codes: 0 ok | 2 usage.`).action((promptParts, opts) => {
12035
12454
  assertTmux();
12036
12455
  const cwd = opts.cwd ?? process.env["SISYPHUS_CWD"] ?? process.cwd();
12037
- const homeSession = findHomeSession(cwd);
12456
+ const homeSession = findHomeSession2(cwd);
12038
12457
  if (!homeSession) {
12039
12458
  const current = execSync16('tmux display-message -p "#{session_name}"', {
12040
12459
  encoding: "utf-8"
@@ -12062,15 +12481,15 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
12062
12481
 
12063
12482
  // src/cli/commands/review.ts
12064
12483
  init_paths();
12065
- import { join as join23, resolve as resolve9, dirname as dirname8 } from "path";
12066
- import { existsSync as existsSync26, readFileSync as readFileSync26, writeFileSync as writeFileSync13, renameSync as renameSync3, readdirSync as readdirSync8 } from "fs";
12484
+ import { join as join24, resolve as resolve9, dirname as dirname8 } from "path";
12485
+ import { existsSync as existsSync26, readFileSync as readFileSync27, writeFileSync as writeFileSync14, renameSync as renameSync3, readdirSync as readdirSync8 } from "fs";
12067
12486
  var _statusCheck = ["draft", "question", "approved", "rejected", "deferred"];
12068
12487
  function resolveContextArtifact(file, opts, filename, notFoundMessage) {
12069
12488
  const cwd = opts.cwd || process.env.SISYPHUS_CWD || process.cwd();
12070
12489
  if (file) return resolve9(file);
12071
12490
  const sessionId = opts.sessionId || process.env.SISYPHUS_SESSION_ID;
12072
12491
  if (sessionId) {
12073
- const target = join23(contextDir(cwd, sessionId), filename);
12492
+ const target = join24(contextDir(cwd, sessionId), filename);
12074
12493
  if (!existsSync26(target)) {
12075
12494
  exitUsage("file-not-found", `File not found: ${target}`, { received: target });
12076
12495
  }
@@ -12080,7 +12499,7 @@ function resolveContextArtifact(file, opts, filename, notFoundMessage) {
12080
12499
  if (existsSync26(dir)) {
12081
12500
  const sessions = readdirSync8(dir);
12082
12501
  for (const session2 of sessions.reverse()) {
12083
- const candidate = join23(dir, session2, "context", filename);
12502
+ const candidate = join24(dir, session2, "context", filename);
12084
12503
  if (existsSync26(candidate)) return candidate;
12085
12504
  }
12086
12505
  }
@@ -12139,12 +12558,12 @@ Exit codes: 0 ok | 2 usage.`).action(async (file, opts) => {
12139
12558
  if (!existsSync26(targetPath)) {
12140
12559
  exitUsage("file-not-found", `File not found: ${targetPath}`, { received: targetPath });
12141
12560
  }
12142
- const parsed = JSON.parse(readFileSync26(targetPath, "utf-8"));
12561
+ const parsed = JSON.parse(readFileSync27(targetPath, "utf-8"));
12143
12562
  const rendered = renderRequirementsMarkdown(parsed);
12144
- const outPath = join23(dirname8(targetPath), "requirements.md");
12563
+ const outPath = join24(dirname8(targetPath), "requirements.md");
12145
12564
  const tmpPath = outPath + ".tmp";
12146
12565
  if (existsSync26(outPath)) {
12147
- const existing = readFileSync26(outPath, "utf-8");
12566
+ const existing = readFileSync27(outPath, "utf-8");
12148
12567
  if (existing !== rendered) {
12149
12568
  if (!opts.force) {
12150
12569
  exitUsage("conflict", `${outPath} has been hand-edited (differs from rendered output)`, {
@@ -12157,7 +12576,7 @@ Exit codes: 0 ok | 2 usage.`).action(async (file, opts) => {
12157
12576
  `);
12158
12577
  }
12159
12578
  }
12160
- writeFileSync13(tmpPath, rendered, "utf-8");
12579
+ writeFileSync14(tmpPath, rendered, "utf-8");
12161
12580
  renameSync3(tmpPath, outPath);
12162
12581
  emitJsonOk({ path: resolve9(outPath) });
12163
12582
  return;
@@ -12521,8 +12940,8 @@ function renderRequirementsMarkdown(json) {
12521
12940
  }
12522
12941
 
12523
12942
  // src/cli/commands/companion.ts
12524
- import { basename as basename7, dirname as dirname11, join as join28 } from "path";
12525
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync31, writeFileSync as writeFileSync18 } from "fs";
12943
+ import { basename as basename7, dirname as dirname11, join as join29 } from "path";
12944
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync32, writeFileSync as writeFileSync19 } from "fs";
12526
12945
  init_paths();
12527
12946
  init_companion_types();
12528
12947
  init_companion_memory();
@@ -12625,10 +13044,10 @@ Effects
12625
13044
  Writes/updates ~/.sisyphus/companion-context-cache/<session-id>.json on each call that produces output.
12626
13045
 
12627
13046
  Exit codes: 0 ok.`).action((opts) => {
12628
- const cachePath = join28(globalDir(), "companion-context-cache", `${opts.sessionId}.json`);
13047
+ const cachePath = join29(globalDir(), "companion-context-cache", `${opts.sessionId}.json`);
12629
13048
  let prev = {};
12630
13049
  try {
12631
- prev = JSON.parse(readFileSync31(cachePath, "utf-8"));
13050
+ prev = JSON.parse(readFileSync32(cachePath, "utf-8"));
12632
13051
  } catch {
12633
13052
  prev = {};
12634
13053
  }
@@ -12642,7 +13061,7 @@ Exit codes: 0 ok.`).action((opts) => {
12642
13061
  process.stdout.write(renderFullContext(next));
12643
13062
  }
12644
13063
  mkdirSync15(dirname11(cachePath), { recursive: true });
12645
- writeFileSync18(cachePath, JSON.stringify(next), "utf-8");
13064
+ writeFileSync19(cachePath, JSON.stringify(next), "utf-8");
12646
13065
  });
12647
13066
  companion.command("pane").description("Open (or focus) a side claude pane next to the dashboard").option("--cwd <path>", "Project directory (default: current directory)").addHelpText("after", `
12648
13067
  companion pane: open (or focus) the companion side pane next to the dashboard.
@@ -12746,7 +13165,7 @@ Exit codes: 0 ok | 3 not_found.`
12746
13165
  }));
12747
13166
  emitJsonOk({ ...companionData, badges });
12748
13167
  } else {
12749
- emitJsonOk(companionData);
13168
+ emitJsonOk({ ...companionData });
12750
13169
  }
12751
13170
  });
12752
13171
  }
@@ -12756,7 +13175,7 @@ init_runner();
12756
13175
  init_creds();
12757
13176
  init_tailscale();
12758
13177
  import { homedir as homedir13 } from "os";
12759
- import { join as join29 } from "path";
13178
+ import { join as join30 } from "path";
12760
13179
  function assertArch(raw) {
12761
13180
  if (raw === "arm" || raw === "x86") return raw;
12762
13181
  throw new Error(`Invalid --arch: ${raw}. Must be 'arm' or 'x86'.`);
@@ -12802,7 +13221,7 @@ Exit codes: 0 ok | 1 error (bad credentials or API failure).`).action(async () =
12802
13221
  const sub = deploy.command(provider).description(`${provider} commands.`).addHelpText("before", `
12803
13222
  deploy ${provider}: per-provider box lifecycle. up | down | status | ssh | logs | update.
12804
13223
  `);
12805
- sub.command("up").description(`Provision the ${provider} box (terraform init \u2192 plan \u2192 apply).`).option("--region <region>", `Provider region (defaults: hetzner=nbg1, aws=us-east-1).`).option("--arch <arch>", "'arm' (default) or 'x86'. Picks the default --size and image.", "arm").option("--size <size>", "Instance type override (defaults follow --arch).").option("--ssh-key <path>", "Path to SSH public key.", join29(homedir13(), ".ssh", "id_ed25519.pub")).option("--no-chromium", "Skip headless Chromium install.").option("--no-auto-update", "Skip the daily auto-update systemd timer.").option("--name <name>", "Box hostname / Tailscale node name.", "sisyphus").option("--yes", "Skip the re-provision confirmation prompt when state already exists.").addHelpText("after", `
13224
+ sub.command("up").description(`Provision the ${provider} box (terraform init \u2192 plan \u2192 apply).`).option("--region <region>", `Provider region (defaults: hetzner=nbg1, aws=us-east-1).`).option("--arch <arch>", "'arm' (default) or 'x86'. Picks the default --size and image.", "arm").option("--size <size>", "Instance type override (defaults follow --arch).").option("--ssh-key <path>", "Path to SSH public key.", join30(homedir13(), ".ssh", "id_ed25519.pub")).option("--no-chromium", "Skip headless Chromium install.").option("--no-auto-update", "Skip the daily auto-update systemd timer.").option("--name <name>", "Box hostname / Tailscale node name.", "sisyphus").option("--yes", "Skip the re-provision confirmation prompt when state already exists.").addHelpText("after", `
12806
13225
  deploy ${provider} up: provision the ${provider} box via Terraform (init \u2192 plan \u2192 apply).
12807
13226
 
12808
13227
  Input
@@ -12949,14 +13368,14 @@ init_runner2();
12949
13368
 
12950
13369
  // src/cli/cloud/handoff.ts
12951
13370
  import { spawn as spawn5 } from "child_process";
12952
- import { existsSync as existsSync36, readFileSync as readFileSync35, writeFileSync as writeFileSync19 } from "fs";
13371
+ import { existsSync as existsSync36, readFileSync as readFileSync36, writeFileSync as writeFileSync20 } from "fs";
12953
13372
  init_exec();
12954
13373
  init_paths();
12955
13374
  init_shell();
12956
13375
  init_runner();
12957
13376
  init_ssh_exec();
12958
13377
  init_provider_pick();
12959
- import { join as join31 } from "path";
13378
+ import { join as join32 } from "path";
12960
13379
  async function cloudHandoff(sessionId, opts) {
12961
13380
  const cwd = process.env["SISYPHUS_CWD"] ?? process.cwd();
12962
13381
  const request = {
@@ -13001,7 +13420,7 @@ async function waitForSentOrError(cwd, sessionId) {
13001
13420
  if (!existsSync36(path)) continue;
13002
13421
  let session2;
13003
13422
  try {
13004
- session2 = JSON.parse(readFileSync35(path, "utf-8"));
13423
+ session2 = JSON.parse(readFileSync36(path, "utf-8"));
13005
13424
  } catch (err) {
13006
13425
  void err;
13007
13426
  continue;
@@ -13082,16 +13501,16 @@ async function cloudReclaim(sessionId, opts) {
13082
13501
  await rsyncDown(target, `${remoteRepoDir}/`, `${cwd}/`, { withDelete: false, excludeSisyphus: true, update: true });
13083
13502
  for (const name of ["config.json", "orchestrator.md", "orchestrator-settings.json"]) {
13084
13503
  const remotePath = `${remoteRepoDir}/.sisyphus/${name}`;
13085
- const localPath = join31(projectDir(cwd), name);
13504
+ const localPath = join32(projectDir(cwd), name);
13086
13505
  const probe = runOnBox(provider, `test -f ${shellQuote(remotePath.replace(/^~\//, ""))} && echo y || echo n`);
13087
13506
  if (probe.stdout.trim() !== "y") continue;
13088
13507
  await rsyncDown(target, remotePath, localPath, { withDelete: false });
13089
13508
  }
13090
13509
  const localStatePath = statePath(cwd, sessionId);
13091
- const merged = JSON.parse(readFileSync35(localStatePath, "utf-8"));
13510
+ const merged = JSON.parse(readFileSync36(localStatePath, "utf-8"));
13092
13511
  merged.cwd = cwd;
13093
13512
  merged.handoff = local.handoff;
13094
- writeFileSync19(localStatePath, JSON.stringify(merged, null, 2));
13513
+ writeFileSync20(localStatePath, JSON.stringify(merged, null, 2));
13095
13514
  const reclaimMessage = `Session reclaimed from cloud (${provider}:${repo}). Resuming locally.`;
13096
13515
  console.log(`\u2192 local sis resume`);
13097
13516
  const resumeResp = await sendRequest({
@@ -13124,7 +13543,7 @@ function readLocalSession(cwd, sessionId) {
13124
13543
  received: sessionId
13125
13544
  });
13126
13545
  }
13127
- return JSON.parse(readFileSync35(path, "utf-8"));
13546
+ return JSON.parse(readFileSync36(path, "utf-8"));
13128
13547
  }
13129
13548
  async function waitForBoxPaused(provider, remoteSessionDir) {
13130
13549
  const POLL_INTERVAL_MS = 2e3;
@@ -13408,7 +13827,7 @@ function attachNotify(diagnostic2) {
13408
13827
  // src/cli/commands/tmux-sessions.ts
13409
13828
  init_paths();
13410
13829
  import { execSync as execSync18 } from "child_process";
13411
- import { readFileSync as readFileSync36, existsSync as existsSync37 } from "fs";
13830
+ import { readFileSync as readFileSync37, existsSync as existsSync37 } from "fs";
13412
13831
  var DOT_MAP = {
13413
13832
  "orchestrator:processing": { icon: "\u25CF", color: "#d4ad6a" },
13414
13833
  "orchestrator:idle": { icon: "\u25CF", color: "#d47766" },
@@ -13421,7 +13840,7 @@ function readManifest() {
13421
13840
  const p = sessionsManifestPath();
13422
13841
  if (!existsSync37(p)) return null;
13423
13842
  try {
13424
- return JSON.parse(readFileSync36(p, "utf-8"));
13843
+ return JSON.parse(readFileSync37(p, "utf-8"));
13425
13844
  } catch {
13426
13845
  return null;
13427
13846
  }
@@ -13462,7 +13881,7 @@ init_server();
13462
13881
  init_ask_store();
13463
13882
  init_state();
13464
13883
  init_paths();
13465
- import { existsSync as existsSync49 } from "fs";
13884
+ import { existsSync as existsSync48 } from "fs";
13466
13885
  var HEARTBEAT_ASKED_BY2 = "system:heartbeat";
13467
13886
  var ORPHAN_ASKED_BY2 = "system:orphan-handler";
13468
13887
  function isHeartbeatZombie(cwd, sessionId, askId) {
@@ -13470,14 +13889,14 @@ function isHeartbeatZombie(cwd, sessionId, askId) {
13470
13889
  if (!meta) return false;
13471
13890
  if (meta.askedBy !== HEARTBEAT_ASKED_BY2) return false;
13472
13891
  if (meta.status === "answered") return false;
13473
- if (existsSync49(askOutputPath(cwd, sessionId, askId))) return false;
13892
+ if (existsSync48(askOutputPath(cwd, sessionId, askId))) return false;
13474
13893
  for (const candidateId of listAsks(cwd, sessionId)) {
13475
13894
  if (candidateId === askId) continue;
13476
13895
  const candidateMeta = readMeta(cwd, sessionId, candidateId);
13477
13896
  if (!candidateMeta) continue;
13478
13897
  if (candidateMeta.heartbeatAskId !== askId) continue;
13479
13898
  if (candidateMeta.status === "answered") return true;
13480
- if (existsSync49(askOutputPath(cwd, sessionId, candidateId))) return true;
13899
+ if (existsSync48(askOutputPath(cwd, sessionId, candidateId))) return true;
13481
13900
  }
13482
13901
  return false;
13483
13902
  }
@@ -13486,7 +13905,7 @@ function isOrphanZombie(cwd, sessionId, askId) {
13486
13905
  if (!meta) return false;
13487
13906
  if (meta.askedBy !== ORPHAN_ASKED_BY2) return false;
13488
13907
  if (meta.status === "answered") return false;
13489
- if (existsSync49(askOutputPath(cwd, sessionId, askId))) return false;
13908
+ if (existsSync48(askOutputPath(cwd, sessionId, askId))) return false;
13490
13909
  if (meta.orphanTarget?.kind !== "agent") return false;
13491
13910
  let session2;
13492
13911
  try {
@@ -13499,27 +13918,6 @@ function isOrphanZombie(cwd, sessionId, askId) {
13499
13918
  if (!agent2) return false;
13500
13919
  return agent2.status !== "running";
13501
13920
  }
13502
- function isModeGateZombie(cwd, sessionId, askId) {
13503
- const meta = readMeta(cwd, sessionId, askId);
13504
- if (!meta) return false;
13505
- if (meta.modeTransition !== true) return false;
13506
- if (meta.status === "answered") return false;
13507
- if (existsSync49(askOutputPath(cwd, sessionId, askId))) return false;
13508
- const deck = readDecisions(cwd, sessionId, askId);
13509
- if (!deck) return false;
13510
- const source = deck.source;
13511
- const chain = source?.modeChain;
13512
- if (!chain || chain.length === 0) return false;
13513
- let currentMode;
13514
- try {
13515
- const session2 = getSession(cwd, sessionId);
13516
- currentMode = session2.orchestratorCycles[session2.orchestratorCycles.length - 1]?.mode;
13517
- } catch {
13518
- return false;
13519
- }
13520
- if (!currentMode) return false;
13521
- return !chain.some((e) => e.mode === currentMode);
13522
- }
13523
13921
  async function resolveZombie(cwd, sessionId, entry) {
13524
13922
  const { askId, kind } = entry;
13525
13923
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -13537,11 +13935,6 @@ async function resolveZombie(cwd, sessionId, entry) {
13537
13935
  selectedOptionId = "dismiss";
13538
13936
  freetext = "auto-resolved: agent superseded by replacement (clean-zombies sweep)";
13539
13937
  break;
13540
- case "mode-gate":
13541
- interactionId = "mode-transition";
13542
- selectedOptionId = "ack";
13543
- freetext = "auto-resolved: session advanced past mode-transition (clean-zombies sweep)";
13544
- break;
13545
13938
  }
13546
13939
  writeOutput(cwd, sessionId, askId, [{
13547
13940
  id: interactionId,
@@ -13560,14 +13953,12 @@ async function sweepSession(cwd, sessionId) {
13560
13953
  const meta = readMeta(cwd, sessionId, askId);
13561
13954
  const agentId = meta?.orphanTarget?.kind === "agent" ? meta.orphanTarget.agentId : "unknown";
13562
13955
  zombies.push({ sessionId, askId, kind: "orphan", reason: `agent ${agentId} is no longer running` });
13563
- } else if (isModeGateZombie(cwd, sessionId, askId)) {
13564
- zombies.push({ sessionId, askId, kind: "mode-gate", reason: "session advanced past mode-transition" });
13565
13956
  }
13566
13957
  }
13567
13958
  return zombies;
13568
13959
  }
13569
13960
  function registerCleanZombies(program2) {
13570
- program2.command("clean-zombies").description("Sweep all sessions for zombie asks (heartbeats whose original is answered, orphans whose agent is superseded, stale mode-gate notifications) and dismiss them").addHelpText(
13961
+ program2.command("clean-zombies").description("Sweep all sessions for zombie asks (heartbeats whose original is answered, orphans whose agent is superseded) and dismiss them").addHelpText(
13571
13962
  "after",
13572
13963
  `
13573
13964
  clean-zombies: sweep all sessions and dismiss stale ask records.
@@ -13580,8 +13971,8 @@ Output (stdout, plain text \u2014 human-readable maintenance summary; not for ag
13580
13971
 
13581
13972
  Effects
13582
13973
  Mutates ask records: writes response.json and updates status to "answered" for each
13583
- zombie found. Dismisses heartbeat asks whose original was answered, orphan asks whose
13584
- target agent is no longer running, and mode-gate asks the session has advanced past.
13974
+ zombie found. Dismisses heartbeat asks whose original was answered and orphan asks whose
13975
+ target agent is no longer running.
13585
13976
 
13586
13977
  Exit codes: 0 ok`
13587
13978
  ).action(async () => {
@@ -13594,7 +13985,7 @@ Exit codes: 0 ok`
13594
13985
  const allZombies = [];
13595
13986
  const sessionsSweept = /* @__PURE__ */ new Set();
13596
13987
  for (const [sessionId, cwd] of entries) {
13597
- if (!existsSync49(statePath(cwd, sessionId))) continue;
13988
+ if (!existsSync48(statePath(cwd, sessionId))) continue;
13598
13989
  try {
13599
13990
  const zombies = await sweepSession(cwd, sessionId);
13600
13991
  if (zombies.length > 0) {
@@ -13610,8 +14001,7 @@ Exit codes: 0 ok`
13610
14001
  }
13611
14002
  const summary = {
13612
14003
  heartbeats: allZombies.filter((z4) => z4.kind === "heartbeat").length,
13613
- orphans: allZombies.filter((z4) => z4.kind === "orphan").length,
13614
- modeGates: allZombies.filter((z4) => z4.kind === "mode-gate").length
14004
+ orphans: allZombies.filter((z4) => z4.kind === "orphan").length
13615
14005
  };
13616
14006
  const total = allZombies.length;
13617
14007
  if (total === 0) {
@@ -13619,7 +14009,7 @@ Exit codes: 0 ok`
13619
14009
  return;
13620
14010
  }
13621
14011
  for (const [sessionId, cwd] of entries) {
13622
- if (!existsSync49(statePath(cwd, sessionId))) continue;
14012
+ if (!existsSync48(statePath(cwd, sessionId))) continue;
13623
14013
  const sessionZombies = allZombies.filter((z4) => z4.sessionId === sessionId);
13624
14014
  for (const zombie of sessionZombies) {
13625
14015
  try {
@@ -13632,153 +14022,13 @@ Exit codes: 0 ok`
13632
14022
  }
13633
14023
  }
13634
14024
  }
13635
- console.log(`Dismissed ${total} zombie${total === 1 ? "" : "s"} across ${sessionsSweept.size} session${sessionsSweept.size === 1 ? "" : "s"}: ${summary.heartbeats} heartbeat${summary.heartbeats === 1 ? "" : "s"}, ${summary.orphans} orphan${summary.orphans === 1 ? "" : "s"}, ${summary.modeGates} mode-gate${summary.modeGates === 1 ? "" : "s"}.`);
14025
+ console.log(`Dismissed ${total} zombie${total === 1 ? "" : "s"} across ${sessionsSweept.size} session${sessionsSweept.size === 1 ? "" : "s"}: ${summary.heartbeats} heartbeat${summary.heartbeats === 1 ? "" : "s"}, ${summary.orphans} orphan${summary.orphans === 1 ? "" : "s"}.`);
13636
14026
  });
13637
14027
  }
13638
14028
 
13639
14029
  // src/cli/index.ts
13640
14030
  init_paths();
13641
-
13642
- // src/cli/help-rubric.ts
13643
- function commandPath(cmd) {
13644
- const parts = [];
13645
- let cur = cmd;
13646
- while (cur !== null) {
13647
- parts.push(cur.name());
13648
- cur = cur.parent;
13649
- }
13650
- parts.reverse();
13651
- parts.shift();
13652
- return parts.join(" ");
13653
- }
13654
- var RUBRICS = {
13655
- "session": { short: "Manage tracked orchestration sessions", useWhen: "acting on a unit of orchestrated work" },
13656
- "agent": { short: "Operate on worker agents", useWhen: "directing or reading a spawned agent" },
13657
- "orch": { short: "Talk to / steer the orchestrator", useWhen: "guiding the session's orchestrator" },
13658
- "ask": { short: "Human-in-the-loop question I/O", useWhen: "answering or polling a blocking ask" },
13659
- "ui": { short: "Interactive surfaces", useWhen: "a human wants the dashboard, guide, or scratch" },
13660
- "segment": { short: "Status-line segment registration", useWhen: "wiring tmux status indicators" },
13661
- "admin": { short: "Install, verify, and report", useWhen: "setting up or diagnosing the install" },
13662
- "companion": { short: "Companion-pane helper", useWhen: "driving the companion Claude pane" },
13663
- "deploy": { short: "Provision cloud boxes (Terraform)", useWhen: "standing up or tearing down infra" },
13664
- "cloud": { short: "Per-repo workflow on a deployed box", useWhen: "syncing work to/from the box" },
13665
- "feedback": { short: "Report a problem with sisyphus itself", useWhen: "the user complains about the tool or workflow" },
13666
- "session lifecycle": { short: "Start/stop/advance a session", useWhen: "changing whether a session runs" },
13667
- "session inspect": { short: "Read session state", useWhen: "you need status/history/context without mutating" },
13668
- "session config": { short: "Change a session's settings", useWhen: "adjusting task, effort, or dangerous mode" },
13669
- "session recover": { short: "Repair or relocate a session", useWhen: "a session is stuck, lost, or needs rollback" },
13670
- "session scratch": { short: "Open a standalone (non-sisyphus) Claude", useWhen: "you want a throwaway Claude in this repo" },
13671
- "session lifecycle start": { short: "Start a new session", useWhen: "beginning work on a new task" },
13672
- "session lifecycle complete": { short: "Mark the current cycle complete", useWhen: "the orchestrator finished its work" },
13673
- "session lifecycle continue": { short: "Continue past completion into a new cycle", useWhen: "more work follows completion" },
13674
- "session lifecycle resume": { short: "Resume a paused/handed-off session", useWhen: "bringing a stopped session back" },
13675
- "session lifecycle kill": { short: "Stop a session, keep its state", useWhen: "halting work but preserving the record" },
13676
- "session lifecycle delete": { short: "Remove a session and its state", useWhen: "discarding a session permanently" },
13677
- "session inspect status": { short: "Show live session status", useWhen: "checking what is running now" },
13678
- "session inspect list": { short: "List sessions", useWhen: "enumerating sessions in this repo" },
13679
- "session inspect history": { short: "Show past sessions/cycles", useWhen: "reviewing prior runs" },
13680
- "session inspect context": { short: "Print the orchestrator's context", useWhen: "auditing what the orchestrator sees" },
13681
- "session inspect export": { short: "Export session transcript/state", useWhen: "archiving or sharing a session" },
13682
- "session inspect requirements": { short: "Show/export the session requirements", useWhen: "reviewing the locked requirements doc" },
13683
- "session config task": { short: "Set the session's task text", useWhen: "retargeting what the session works on" },
13684
- "session config effort": { short: "Set the effort tier", useWhen: "tuning model effort for a session" },
13685
- "session config dangerous": { short: "Toggle dangerous (skip-permissions) mode", useWhen: "allowing unattended tool use" },
13686
- "session recover rollback": { short: "Roll back to an earlier cycle", useWhen: "a cycle went wrong" },
13687
- "session recover reconnect": { short: "Reattach the daemon to a live session", useWhen: "the tmux/daemon link dropped" },
13688
- "session recover quiesce": { short: "Pause a session at the next safe point", useWhen: "you need it to stop cleanly" },
13689
- "session recover clone": { short: "Clone a session toward a new goal", useWhen: "forking work from an existing session" },
13690
- "agent spawn": { short: "Spawn a worker agent", useWhen: "delegating a scoped sub-task" },
13691
- "agent submit": { short: "Submit this agent's result upstream", useWhen: "from inside an agent, its work is done" },
13692
- "agent report": { short: "Post a progress report", useWhen: "from inside an agent, updating mid-task" },
13693
- "agent await": { short: "Block until a spawned agent submits", useWhen: "you need an agent's result before continuing" },
13694
- "agent ctl": { short: "Agent process control", useWhen: "killing or restarting an agent process" },
13695
- "agent io": { short: "Agent message I/O", useWhen: "sending text to or reading an agent" },
13696
- "agent ctl kill": { short: "Kill an agent", useWhen: "an agent must stop immediately" },
13697
- "agent ctl restart": { short: "Restart an agent", useWhen: "an agent is wedged and should start over" },
13698
- "agent io tell": { short: "Send text to an agent", useWhen: "injecting instructions into a running agent" },
13699
- "agent io read": { short: "Read an agent's transcript", useWhen: "inspecting what an agent produced" },
13700
- "orch yield": { short: "Yield control back to the human/parent", useWhen: "the orchestrator is done or blocked" },
13701
- "orch tell": { short: "Send text to the orchestrator", useWhen: "injecting guidance into the orchestrator" },
13702
- "orch message": { short: "Queue a message for the orchestrator", useWhen: "leaving async input for the next turn" },
13703
- "orch read": { short: "Read the orchestrator's transcript", useWhen: "inspecting orchestrator output" },
13704
- "ask submit": { short: "Submit an ask deck for a human", useWhen: "an agent needs a human decision" },
13705
- "ask poll": { short: "Block until an ask is answered", useWhen: "you need the human's answer before continuing" },
13706
- "ask peek": { short: "Read an ask's state without blocking", useWhen: "checking ask status non-blocking" },
13707
- "ui dashboard": { short: "Open the TUI dashboard", useWhen: "a human wants the monitoring UI" },
13708
- "ui guide": { short: "Print the full usage guide", useWhen: "a human needs end-to-end docs" },
13709
- "segment register": { short: "Register a status-line segment", useWhen: "adding a tmux status indicator" },
13710
- "segment unregister": { short: "Remove a status-line segment", useWhen: "tearing one down" },
13711
- "admin install": { short: "Install / uninstall sisyphus bits", useWhen: "first setup or removal" },
13712
- "admin check": { short: "Verify the installation", useWhen: "diagnosing a broken or partial install" },
13713
- "admin report": { short: "Diagnostics & telemetry", useWhen: "filing a bug or uploading session data" },
13714
- "admin install setup": { short: "Run first-time setup", useWhen: "installing sisyphus on this machine" },
13715
- "admin install setup-keybind": { short: "Install the tmux keybind", useWhen: "wiring the dashboard hotkey" },
13716
- "admin install init": { short: "Initialize repo-local config", useWhen: "onboarding a new repo" },
13717
- "admin install uninstall": { short: "Remove sisyphus", useWhen: "fully removing the install" },
13718
- "admin check doctor": { short: "Run install diagnostics", useWhen: "something is not working and you want a health check" },
13719
- "admin check check-keybinds": { short: "Verify tmux keybinds", useWhen: "the dashboard hotkey does not fire" },
13720
- "admin check check-statusbar": { short: "Verify status-line wiring", useWhen: "status segments do not render" },
13721
- "admin report bug": { short: "File a bug report", useWhen: "reporting a defect with context" },
13722
- "admin report upload": { short: "Upload session data", useWhen: "sharing a session for support" },
13723
- "admin report configure-upload": { short: "Configure the upload target", useWhen: "setting where uploads go" },
13724
- "admin clean-zombies": { short: "Sweep stale zombie processes", useWhen: "cleaning up after a crashed daemon or hung agent" },
13725
- "cloud box": { short: "Provision / operate the box for this repo", useWhen: "working with the box-side environment" },
13726
- "cloud handoff": { short: "Move a session between local and box", useWhen: "relocating in-flight work" },
13727
- "cloud box sync": { short: "Rsync this repo to the box", useWhen: "pushing local code to the box" },
13728
- "cloud box install": { short: "Run the package install on the box", useWhen: "box deps are missing/stale" },
13729
- "cloud box session": { short: "Create/refresh the box tmux home", useWhen: "the box-side session is absent" },
13730
- "cloud box attach": { short: "Attach to the box session", useWhen: "you want to work on the box interactively" },
13731
- "cloud box status": { short: "Print box-side status", useWhen: "checking box state for this repo" },
13732
- "cloud box login": { short: "Run claude auth login on the box", useWhen: "the box needs Claude credentials" },
13733
- "cloud box up": { short: "Sync+install+session in one shot", useWhen: "bringing the box up from cold" },
13734
- "cloud handoff push": { short: "Hand a live session off to the box", useWhen: "moving local work to the cloud" },
13735
- "cloud handoff pull": { short: "Reclaim a handed-off session locally", useWhen: "bringing cloud work back home" },
13736
- "agent types": { short: "Agent-type catalog", useWhen: "discovering installable agent types" },
13737
- "agent types list": { short: "List agent types", useWhen: "enumerating spawnable agent types" },
13738
- "deploy list": { short: "List deploy providers", useWhen: "discovering supported providers" },
13739
- "companion profile": { short: "Print companion profile", useWhen: "reading the combined memory + context + badges blob" },
13740
- "companion memory": { short: "Show accumulated companion observations", useWhen: "reviewing what the companion noticed" },
13741
- "companion context": { short: "Emit per-prompt companion context", useWhen: "the companion plugin hook needs context" },
13742
- "companion pane": { short: "Open/focus the side claude pane", useWhen: "you want the companion pane by the dashboard" },
13743
- "companion popup-test": { short: "Show a test commentary popup", useWhen: "validating feedback-key handling" },
13744
- "deploy auth": { short: "Configure provider auth", useWhen: "setting up Tailscale/provider credentials" },
13745
- "deploy hetzner": { short: "Hetzner box lifecycle", useWhen: "targeting Hetzner" },
13746
- "deploy aws": { short: "AWS box lifecycle", useWhen: "targeting AWS" },
13747
- "deploy auth tailscale": { short: "Configure Tailscale OAuth", useWhen: "joining the box to your tailnet" },
13748
- "deploy * up": { short: "Provision the box", useWhen: "standing the box up" },
13749
- "deploy * down": { short: "Destroy the box", useWhen: "tearing the box down" },
13750
- "deploy * status": { short: "Print box outputs", useWhen: "checking IP/cost/instance" },
13751
- "deploy * ssh": { short: "SSH/mosh into the box", useWhen: "getting a shell on the box" },
13752
- "deploy * logs": { short: "Tail cloud-init + daemon logs", useWhen: "diagnosing a box-side failure" },
13753
- "deploy * update": { short: "Upgrade sisyphus on the box", useWhen: "pulling the latest daemon onto the box" }
13754
- };
13755
- function subcommandRubric(cmd) {
13756
- if (cmd.name() === "help") {
13757
- return cmd.summary() || cmd.description();
13758
- }
13759
- const p = commandPath(cmd);
13760
- let key = p;
13761
- const deployProviderMatch = p.match(/^deploy (hetzner|aws) (.+)$/);
13762
- if (deployProviderMatch) {
13763
- key = `deploy * ${deployProviderMatch[2]}`;
13764
- }
13765
- const r = RUBRICS[key];
13766
- if (r) {
13767
- return `${r.short} | use when ${r.useWhen}`;
13768
- }
13769
- return cmd.summary() || cmd.description();
13770
- }
13771
- var CONCEPTS_BLOCK = `
13772
- Concepts
13773
- session a tracked unit of orchestrated work on one task
13774
- agent a worker Claude spawned to execute a scoped sub-task
13775
- orchestrator the Claude that owns a session: decomposes, spawns, advances
13776
- cycle one discovery\u2192plan\u2192implement\u2192validate iteration; the rollback unit
13777
- ask a blocking question surfaced for a human to answer
13778
- mode the session's current phase, driving orchestrator behavior
13779
- `;
13780
-
13781
- // src/cli/index.ts
14031
+ init_help_rubric();
13782
14032
  var nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
13783
14033
  if (nodeVersion < 22) {
13784
14034
  console.error(`Sisyphus requires Node.js v22+ (current: v${process.versions.node})`);
@@ -13801,7 +14051,7 @@ var sessionCounts = null;
13801
14051
  var program = new Command();
13802
14052
  program.name("sis").description("tmux-integrated orchestration daemon for Claude Code").helpOption("-h, --help", "print -h for any node or leaf").version(
13803
14053
  JSON.parse(
13804
- readFileSync46(join38(dirname16(fileURLToPath6(import.meta.url)), "..", "package.json"), "utf-8")
14054
+ readFileSync47(join39(dirname16(fileURLToPath6(import.meta.url)), "..", "package.json"), "utf-8")
13805
14055
  ).version,
13806
14056
  "--version",
13807
14057
  "output the version number"
@@ -13811,23 +14061,7 @@ program.configureHelp({
13811
14061
  subcommandDescription: (cmd) => subcommandRubric(cmd)
13812
14062
  });
13813
14063
  program.addHelpText("before", CONCEPTS_BLOCK);
13814
- program.addHelpText("after", `
13815
- I/O contract: flags and positional args on input, JSON on stdout (JSONL for streams).
13816
-
13817
- Exit codes:
13818
- 0 success
13819
- 1 permanent error (fallback)
13820
- 2 usage error (bad args/shape)
13821
- 3 not found
13822
- 4 ambiguous (multiple matches \u2014 see error.candidates)
13823
- 5 conflict (already-exists, wrong-state)
13824
- 60 transient (retry-safe: daemon down, timeout, lock contention)
13825
-
13826
- Errors:
13827
- {"ok": false,
13828
- "error": {"code": "<stable-enum>", "kind": "<usage|not_found|ambiguous|conflict|transient|permanent>",
13829
- "message": "...", "received"?: ..., "expected"?: ..., "next"?: "...", "candidates"?: [...]}}
13830
- `);
14064
+ program.addHelpText("after", ROOT_AFTER_HELP);
13831
14065
  if ((process.argv.includes("--help") || process.argv.includes("-h")) && process.argv.some((a) => a === "session")) {
13832
14066
  try {
13833
14067
  const resp = await rawSend2({ type: "list", cwd: process.cwd(), all: true }, 250);
@@ -13929,7 +14163,7 @@ registerHomeInit(diagnostic);
13929
14163
  var args = process.argv.slice(2);
13930
14164
  var firstArg = args[0];
13931
14165
  var skipWelcome = ["session", "start", "dashboard", "agent", "orch", "ask", "ui", "segment", "admin", "feedback", "help", "--help", "--version"];
13932
- if (!existsSync50(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
14166
+ if (!existsSync49(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
13933
14167
  mkdirSync21(globalDir(), { recursive: true });
13934
14168
  console.log("");
13935
14169
  console.log(" Welcome to Sisyphus. Run 'sis admin install setup' to get started.");