sisyphi 1.1.38 → 1.1.40

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
@@ -249,13 +249,13 @@ var init_env = __esm({
249
249
  });
250
250
 
251
251
  // src/shared/exec.ts
252
- import { execSync as execSync8 } from "child_process";
252
+ import { execSync as execSync9 } from "child_process";
253
253
  function exec2(cmd, cwd, timeoutMs = 3e4) {
254
- return execSync8(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, timeout: timeoutMs }).trim();
254
+ return execSync9(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, timeout: timeoutMs }).trim();
255
255
  }
256
256
  function execSafe(cmd, cwd, timeoutMs) {
257
257
  try {
258
- return execSync8(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, stdio: ["pipe", "pipe", "pipe"], timeout: timeoutMs }).trim();
258
+ return execSync9(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, stdio: ["pipe", "pipe", "pipe"], timeout: timeoutMs }).trim();
259
259
  } catch {
260
260
  return null;
261
261
  }
@@ -290,9 +290,9 @@ __export(tmux_exports, {
290
290
  switchToSession: () => switchToSession,
291
291
  windowExists: () => windowExists
292
292
  });
293
- import { execSync as execSync17 } from "child_process";
293
+ import { execSync as execSync18 } from "child_process";
294
294
  import { join as join27 } from "path";
295
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync16, mkdtempSync, rmSync as rmSync6, cpSync as cpSync3, existsSync as existsSync26, mkdirSync as mkdirSync13 } from "fs";
295
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync16, mkdtempSync, rmSync as rmSync6, cpSync as cpSync3, existsSync as existsSync28, mkdirSync as mkdirSync13 } from "fs";
296
296
  import { tmpdir as tmpdir3 } from "os";
297
297
  function getWindowId() {
298
298
  const pane = process.env["TMUX_PANE"];
@@ -312,7 +312,7 @@ function windowExists(windowId) {
312
312
  }
313
313
  function listAllWindowIds() {
314
314
  try {
315
- const output = execSync17('tmux list-windows -a -F "#{window_id}"', { encoding: "utf-8", env: EXEC_ENV });
315
+ const output = execSync18('tmux list-windows -a -F "#{window_id}"', { encoding: "utf-8", env: EXEC_ENV });
316
316
  return new Set(output.trim().split("\n").filter(Boolean));
317
317
  } catch {
318
318
  return /* @__PURE__ */ new Set();
@@ -346,7 +346,7 @@ function registerDashboardWindow(cwd) {
346
346
  function setupCompanionPlugin() {
347
347
  const srcDir = join27(import.meta.dirname, "templates", "companion-plugin");
348
348
  const destDir = join27(globalDir(), "companion-plugin");
349
- if (!existsSync26(destDir)) mkdirSync13(destDir, { recursive: true });
349
+ if (!existsSync28(destDir)) mkdirSync13(destDir, { recursive: true });
350
350
  cpSync3(srcDir, destDir, { recursive: true });
351
351
  return destDir;
352
352
  }
@@ -376,7 +376,7 @@ function openCompanionPane(cwd) {
376
376
  const templatePath2 = join27(import.meta.dirname, "templates", "dashboard-claude.md");
377
377
  let template;
378
378
  try {
379
- template = readFileSync29(templatePath2, "utf-8");
379
+ template = readFileSync30(templatePath2, "utf-8");
380
380
  } catch {
381
381
  template = `You are a Sisyphus dashboard companion. Help the user manage multi-agent sessions.
382
382
  Project: ${cwd}
@@ -404,7 +404,7 @@ function editInPopup(cwd, editor, opts) {
404
404
  try {
405
405
  writeFileSync16(filePath, opts?.content ? opts.content : "", "utf-8");
406
406
  openEditorPopup(cwd, editor, filePath, opts?.size);
407
- const result = readFileSync29(filePath, "utf-8").trim();
407
+ const result = readFileSync30(filePath, "utf-8").trim();
408
408
  return result || null;
409
409
  } finally {
410
410
  rmSync6(tmpDir, { recursive: true, force: true });
@@ -416,38 +416,38 @@ function promptInPopup(prompt, opts) {
416
416
  const outFile = join27(tmpDir, "result");
417
417
  try {
418
418
  const script = `printf ${shellQuote(prompt + " ")} && read -r line && printf '%s' "$line" > ${shellQuote(outFile)}`;
419
- execSync17(
419
+ execSync18(
420
420
  `tmux display-popup -E -w ${w} -h ${h} ${shellQuote(`bash -c ${shellQuote(script)}`)}`,
421
421
  { stdio: "inherit", env: EXEC_ENV }
422
422
  );
423
- if (!existsSync26(outFile)) return null;
424
- const result = readFileSync29(outFile, "utf-8").trim();
423
+ if (!existsSync28(outFile)) return null;
424
+ const result = readFileSync30(outFile, "utf-8").trim();
425
425
  return result || null;
426
426
  } finally {
427
427
  rmSync6(tmpDir, { recursive: true, force: true });
428
428
  }
429
429
  }
430
430
  function openLogPopup() {
431
- execSync17(
431
+ execSync18(
432
432
  `tmux display-popup -E -w 90% -h 80% ${shellQuote("tail -f ~/.sisyphus/daemon.log")}`,
433
433
  { stdio: "inherit", env: EXEC_ENV }
434
434
  );
435
435
  }
436
436
  function openShellPopup(cwd, command) {
437
- execSync17(
437
+ execSync18(
438
438
  `tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd)} ${shellQuote(`sh -c '${command.replace(/'/g, "'\\''")}; echo; echo "Press enter to close"; read'`)}`,
439
439
  { stdio: "inherit", env: EXEC_ENV }
440
440
  );
441
441
  }
442
442
  function openInFileManager(path) {
443
- execSync17(`open ${shellQuote(path)}`, { stdio: "inherit", env: EXEC_ENV });
443
+ execSync18(`open ${shellQuote(path)}`, { stdio: "inherit", env: EXEC_ENV });
444
444
  }
445
445
  function openClaudeResumePopup(cwd, claudeSessionId, resumeEnv, resumeArgs) {
446
446
  const pathEnv = augmentedPath();
447
447
  const envPrefix = resumeEnv ? `${resumeEnv} && ` : "";
448
448
  const args2 = resumeArgs ? `${resumeArgs} --resume ${shellQuote(claudeSessionId)}` : `--resume ${shellQuote(claudeSessionId)}`;
449
449
  const cmd = `${envPrefix}PATH=${shellQuote(pathEnv)} claude ${args2}`;
450
- execSync17(
450
+ execSync18(
451
451
  `tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd)} ${shellQuote(cmd)}`,
452
452
  { stdio: "inherit", env: EXEC_ENV }
453
453
  );
@@ -507,12 +507,12 @@ function openEditorPopup(cwd, editor, filePath, size) {
507
507
  const { w = "90%", h = "90%" } = size ?? {};
508
508
  const editorBin = editor.split(/\s+/)[0].split("/").pop();
509
509
  if (TERMINAL_EDITORS.has(editorBin)) {
510
- execSync17(
510
+ execSync18(
511
511
  `tmux display-popup -E -w ${w} -h ${h} -d ${shellQuote(cwd)} ${shellQuote(`${editor} ${shellQuote(filePath)}`)}`,
512
512
  { stdio: "inherit", env: EXEC_ENV }
513
513
  );
514
514
  } else {
515
- execSync17(`${editor} ${shellQuote(filePath)}`, { stdio: "inherit", cwd, env: EXEC_ENV });
515
+ execSync18(`${editor} ${shellQuote(filePath)}`, { stdio: "inherit", cwd, env: EXEC_ENV });
516
516
  }
517
517
  }
518
518
  var TERMINAL_EDITORS;
@@ -539,14 +539,14 @@ __export(creds_exports, {
539
539
  readTailscaleEnv: () => readTailscaleEnv,
540
540
  writeTailscaleEnv: () => writeTailscaleEnv
541
541
  });
542
- import { chmodSync as chmodSync3, existsSync as existsSync27, mkdirSync as mkdirSync15, readFileSync as readFileSync31 } from "fs";
542
+ import { chmodSync as chmodSync3, existsSync as existsSync29, mkdirSync as mkdirSync15, readFileSync as readFileSync32 } from "fs";
543
543
  import { createInterface as createInterface4 } from "readline";
544
544
  function isValidProvider(value) {
545
545
  return PROVIDERS.includes(value);
546
546
  }
547
547
  function ensureDeployDir() {
548
548
  const dir = deployDir();
549
- if (!existsSync27(dir)) mkdirSync15(dir, { recursive: true, mode: 448 });
549
+ if (!existsSync29(dir)) mkdirSync15(dir, { recursive: true, mode: 448 });
550
550
  }
551
551
  function parseEnvFile(text) {
552
552
  const out = {};
@@ -572,8 +572,8 @@ function serializeEnvFile(values) {
572
572
  return lines.join("\n") + "\n";
573
573
  }
574
574
  function readEnvFile(path) {
575
- if (!existsSync27(path)) return null;
576
- return parseEnvFile(readFileSync31(path, "utf-8"));
575
+ if (!existsSync29(path)) return null;
576
+ return parseEnvFile(readFileSync32(path, "utf-8"));
577
577
  }
578
578
  function writeEnvFile(path, values) {
579
579
  ensureDeployDir();
@@ -668,7 +668,7 @@ var init_creds = __esm({
668
668
 
669
669
  // src/cli/index.ts
670
670
  import { Command } from "commander";
671
- import { existsSync as existsSync34, mkdirSync as mkdirSync17, readFileSync as readFileSync36 } from "fs";
671
+ import { existsSync as existsSync36, mkdirSync as mkdirSync17, readFileSync as readFileSync37 } from "fs";
672
672
  import { dirname as dirname13, join as join32 } from "path";
673
673
  import { fileURLToPath as fileURLToPath5 } from "url";
674
674
 
@@ -970,6 +970,97 @@ function userTmuxConfPath() {
970
970
  if (existsSync2(dotfile)) return dotfile;
971
971
  return null;
972
972
  }
973
+ var SISYPHUS_CLIP_SCRIPT = `#!/bin/bash
974
+ # Cross-platform clipboard wrapper.
975
+ # sisyphus-clip # read stdin, write to clipboard
976
+ # sisyphus-clip copy # same as default
977
+ # sisyphus-clip paste # write clipboard contents to stdout
978
+
979
+ mode="\${1:-copy}"
980
+ uname_s="$(uname -s 2>/dev/null || echo unknown)"
981
+
982
+ is_wsl=0
983
+ if [ "$uname_s" = "Linux" ]; then
984
+ if [ -n "\${WSL_DISTRO_NAME:-}\${WSL_INTEROP:-}" ]; then is_wsl=1; fi
985
+ if [ -r /proc/version ] && grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then is_wsl=1; fi
986
+ fi
987
+
988
+ case "$mode" in
989
+ copy)
990
+ if [ "$uname_s" = "Darwin" ]; then
991
+ exec pbcopy
992
+ elif [ "$is_wsl" = "1" ] && command -v clip.exe >/dev/null 2>&1; then
993
+ exec clip.exe
994
+ elif [ -n "\${WAYLAND_DISPLAY:-}" ] && command -v wl-copy >/dev/null 2>&1; then
995
+ exec wl-copy
996
+ elif command -v xclip >/dev/null 2>&1; then
997
+ exec xclip -selection clipboard
998
+ elif command -v xsel >/dev/null 2>&1; then
999
+ exec xsel --clipboard --input
1000
+ else
1001
+ echo "sisyphus-clip: no clipboard backend (install xclip / wl-clipboard / clip.exe)" >&2
1002
+ exit 1
1003
+ fi
1004
+ ;;
1005
+ paste)
1006
+ if [ "$uname_s" = "Darwin" ]; then
1007
+ exec pbpaste
1008
+ elif [ "$is_wsl" = "1" ] && command -v powershell.exe >/dev/null 2>&1; then
1009
+ # Get-Clipboard appends \\r\\n; strip the trailing CR.
1010
+ powershell.exe -NoProfile -Command Get-Clipboard | sed 's/\\r$//'
1011
+ exit "\${PIPESTATUS[0]:-0}"
1012
+ elif [ -n "\${WAYLAND_DISPLAY:-}" ] && command -v wl-paste >/dev/null 2>&1; then
1013
+ exec wl-paste --no-newline
1014
+ elif command -v xclip >/dev/null 2>&1; then
1015
+ exec xclip -selection clipboard -o
1016
+ elif command -v xsel >/dev/null 2>&1; then
1017
+ exec xsel --clipboard --output
1018
+ else
1019
+ echo "sisyphus-clip: no clipboard backend (install xclip / wl-clipboard / powershell.exe)" >&2
1020
+ exit 1
1021
+ fi
1022
+ ;;
1023
+ *)
1024
+ echo "Usage: sisyphus-clip [copy|paste]" >&2
1025
+ exit 2
1026
+ ;;
1027
+ esac
1028
+ `;
1029
+ var SISYPHUS_OPEN_SCRIPT = `#!/bin/bash
1030
+ # Cross-platform "open path in default file manager / handler".
1031
+ # sisyphus-open <path>
1032
+
1033
+ if [ $# -lt 1 ]; then
1034
+ echo "Usage: sisyphus-open <path>" >&2
1035
+ exit 2
1036
+ fi
1037
+ target="$1"
1038
+
1039
+ uname_s="$(uname -s 2>/dev/null || echo unknown)"
1040
+ is_wsl=0
1041
+ if [ "$uname_s" = "Linux" ]; then
1042
+ if [ -n "\${WSL_DISTRO_NAME:-}\${WSL_INTEROP:-}" ]; then is_wsl=1; fi
1043
+ if [ -r /proc/version ] && grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then is_wsl=1; fi
1044
+ fi
1045
+
1046
+ if [ "$uname_s" = "Darwin" ]; then
1047
+ exec open "$target"
1048
+ elif [ "$is_wsl" = "1" ] && command -v explorer.exe >/dev/null 2>&1; then
1049
+ win="$target"
1050
+ if command -v wslpath >/dev/null 2>&1; then
1051
+ win=$(wslpath -w "$target" 2>/dev/null || echo "$target")
1052
+ fi
1053
+ # explorer.exe returns 1 even on success when launching a new window \u2014 ignore.
1054
+ explorer.exe "$win" >/dev/null 2>&1 || true
1055
+ elif command -v xdg-open >/dev/null 2>&1; then
1056
+ exec xdg-open "$target"
1057
+ else
1058
+ echo "sisyphus-open: no opener available (install xdg-utils on Linux)" >&2
1059
+ exit 1
1060
+ fi
1061
+ `;
1062
+ var CLIP_BIN = "$HOME/.sisyphus/bin/sisyphus-clip";
1063
+ var OPEN_BIN = "$HOME/.sisyphus/bin/sisyphus-open";
973
1064
  var CYCLE_SCRIPT = `#!/bin/bash
974
1065
  # Target by $N session ID (column 5 in TSV) \u2014 tmux -t <name> can substring-match
975
1066
  # the wrong session under sparse env.
@@ -1298,7 +1389,7 @@ read -n 1 -s -r -p "Press a key to close."
1298
1389
  `;
1299
1390
  var RESTART_AGENT_SCRIPT = `#!/bin/bash
1300
1391
  # Pick a sisyphus agent and restart it (fzf picker with confirm for running agents).
1301
- # Assumes macOS (fzf optional). Requires \`sis status --json\`.
1392
+ # fzf optional. Requires \`sis status --json\`.
1302
1393
  ${SESSION_RESOLVE}
1303
1394
 
1304
1395
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1354,14 +1445,13 @@ file="$cwd/.sisyphus/sessions/$session_id/goal.md"
1354
1445
  exec nvim "$file"
1355
1446
  `;
1356
1447
  var OPEN_DIR_SCRIPT = `#!/bin/bash
1357
- # Open session dir in Finder (macOS).
1358
- # macOS-only \u2014 Linux/Windows port deferred.
1448
+ # Open session dir in the platform file manager (Finder/Explorer/xdg-open).
1359
1449
  # Run from a sisyphus session pane, not the home dashboard.
1360
1450
  ${SESSION_RESOLVE}
1361
1451
 
1362
1452
  dir="$cwd/.sisyphus/sessions/$session_id"
1363
1453
  [ ! -d "$dir" ] && { tmux display-message "Session dir not found: $dir"; exit 0; }
1364
- exec open "$dir"
1454
+ exec ${OPEN_BIN} "$dir"
1365
1455
  `;
1366
1456
  var OPEN_LOGS_SCRIPT = `#!/bin/bash
1367
1457
  # Tail the newest cycle log for this session in a popup.
@@ -1522,7 +1612,7 @@ fi
1522
1612
  `;
1523
1613
  var JUMP_TO_PANE_SCRIPT = `#!/bin/bash
1524
1614
  # Pick a sisyphus agent and jump to its tmux pane.
1525
- # Assumes macOS (pbcopy, fzf optional). Requires \`sis status --json\`.
1615
+ # fzf optional. Requires \`sis status --json\`.
1526
1616
  ${SESSION_RESOLVE}
1527
1617
 
1528
1618
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1568,7 +1658,7 @@ tmux select-pane -t "$target_pane"
1568
1658
  `;
1569
1659
  var MSG_AGENT_SCRIPT = `#!/bin/bash
1570
1660
  # Pick a sisyphus agent and send it a message via nvim.
1571
- # Assumes macOS (fzf optional). Requires \`sis status --json\` and \`--agent\` on message.
1661
+ # fzf optional. Requires \`sis status --json\` and \`--agent\` on message.
1572
1662
  ${SESSION_RESOLVE}
1573
1663
 
1574
1664
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1610,7 +1700,7 @@ exec sis message --session "$session_id" --agent "\${ids[$idx]}" "$(cat "$tmpfil
1610
1700
  `;
1611
1701
  var RERUN_AGENT_SCRIPT = `#!/bin/bash
1612
1702
  # Pick a sisyphus agent and spawn a retry with its original instruction.
1613
- # Assumes macOS (fzf optional). Requires \`sis status --json\`.
1703
+ # fzf optional. Requires \`sis status --json\`.
1614
1704
  ${SESSION_RESOLVE}
1615
1705
 
1616
1706
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1661,7 +1751,7 @@ exec sis agent spawn --session "$session_id" --agent-type "\${atypes[$idx]}" --n
1661
1751
  `;
1662
1752
  var OPEN_CLAUDE_AGENT_SCRIPT = `#!/bin/bash
1663
1753
  # Pick a sisyphus agent or orchestrator cycle and resume its Claude session.
1664
- # Assumes macOS (fzf optional). Requires \`sis status --json\`.
1754
+ # fzf optional. Requires \`sis status --json\`.
1665
1755
  ${SESSION_RESOLVE}
1666
1756
 
1667
1757
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1709,7 +1799,7 @@ cd "$cwd" && exec claude --resume "$cid"
1709
1799
  var TAIL_AGENT_LOGS_SCRIPT = `#!/bin/bash
1710
1800
  # Pick a sisyphus agent and view its tmux pane scrollback (last 2000 lines) in less.
1711
1801
  # Uses tmux capture-pane \u2014 no tail -f, no pipe-pane side effects.
1712
- # Assumes macOS (fzf optional). Requires \`sis status --json\`.
1802
+ # fzf optional. Requires \`sis status --json\`.
1713
1803
  ${SESSION_RESOLVE}
1714
1804
 
1715
1805
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1751,7 +1841,7 @@ tmux capture-pane -t "$target_pane" -p -S -2000 | less +G
1751
1841
  `;
1752
1842
  var KILL_AGENT_SCRIPT = `#!/bin/bash
1753
1843
  # Pick a sisyphus agent and kill it (with red confirmation prompt).
1754
- # Assumes macOS (fzf optional). Requires \`sis status --json\` and \`sis agent kill\`.
1844
+ # fzf optional. Requires \`sis status --json\` and \`sis agent kill\`.
1755
1845
  ${SESSION_RESOLVE}
1756
1846
 
1757
1847
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1794,7 +1884,7 @@ read -n 1 -s -r -p "Press a key to close."
1794
1884
  `;
1795
1885
  var COPY_AGENT_ID_SCRIPT = `#!/bin/bash
1796
1886
  # Pick a sisyphus agent and copy its ID to clipboard.
1797
- # Assumes macOS (pbcopy, fzf optional). Requires \`sis status --json\`.
1887
+ # Requires \`sis status --json\`. fzf optional.
1798
1888
  ${SESSION_RESOLVE}
1799
1889
 
1800
1890
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1829,60 +1919,56 @@ else
1829
1919
  fi
1830
1920
 
1831
1921
  aid="\${ids[$idx]}"
1832
- printf '%s' "$aid" | pbcopy
1922
+ printf '%s' "$aid" | ${CLIP_BIN}
1833
1923
  tmux display-message "Copied $aid"
1834
1924
  `;
1835
1925
  var COPY_LOGS_SCRIPT = `#!/bin/bash
1836
1926
  # Copy last 200 lines of the newest cycle log to clipboard.
1837
- # Assumes macOS (pbcopy).
1838
1927
  ${SESSION_RESOLVE}
1839
1928
 
1840
1929
  dir="$cwd/.sisyphus/sessions/$session_id/logs"
1841
1930
  [ -d "$dir" ] || { tmux display-message "No logs dir"; exit 0; }
1842
1931
  latest=$(ls -t "$dir"/cycle-*.md 2>/dev/null | head -1)
1843
1932
  [ -z "$latest" ] && { tmux display-message "No cycle logs yet"; exit 0; }
1844
- tail -n 200 "$latest" | pbcopy
1933
+ tail -n 200 "$latest" | ${CLIP_BIN}
1845
1934
  tmux display-message "Copied last 200 lines of $(basename "$latest")"
1846
1935
  `;
1847
1936
  var COPY_LATEST_REPORT_SCRIPT = `#!/bin/bash
1848
1937
  # Copy the newest report file to clipboard.
1849
- # Assumes macOS (pbcopy).
1850
1938
  ${SESSION_RESOLVE}
1851
1939
 
1852
1940
  dir="$cwd/.sisyphus/sessions/$session_id/reports"
1853
1941
  [ -d "$dir" ] || { tmux display-message "No reports dir"; exit 0; }
1854
1942
  latest=$(ls -t "$dir" 2>/dev/null | head -1)
1855
1943
  [ -z "$latest" ] && { tmux display-message "No reports yet"; exit 0; }
1856
- cat "$dir/$latest" | pbcopy
1944
+ cat "$dir/$latest" | ${CLIP_BIN}
1857
1945
  tmux display-message "Copied $latest"
1858
1946
  `;
1859
1947
  var COPY_PATH_SCRIPT = `#!/bin/bash
1860
1948
  # Copy the session directory path to clipboard.
1861
- # Assumes macOS (pbcopy).
1862
1949
  ${SESSION_RESOLVE}
1863
1950
 
1864
- printf '%s' "$cwd/.sisyphus/sessions/$session_id" | pbcopy
1951
+ printf '%s' "$cwd/.sisyphus/sessions/$session_id" | ${CLIP_BIN}
1865
1952
  tmux display-message "Copied session path"
1866
1953
  `;
1867
1954
  var COPY_ID_SCRIPT = `#!/bin/bash
1868
1955
  # Copy the session ID to clipboard.
1869
- # Assumes macOS (pbcopy).
1870
1956
  ${SESSION_RESOLVE}
1871
1957
 
1872
- printf '%s' "$session_id" | pbcopy
1958
+ printf '%s' "$session_id" | ${CLIP_BIN}
1873
1959
  tmux display-message "Copied session ID"
1874
1960
  `;
1875
1961
  var COPY_CONTEXT_SCRIPT = `#!/bin/bash
1876
1962
  # Copy the session context XML to clipboard.
1877
- # Assumes macOS (pbcopy). Requires \`sis session context\`.
1963
+ # Requires \`sis session context\`.
1878
1964
  ${SESSION_RESOLVE}
1879
1965
 
1880
- sis session context "$session_id" --cwd "$cwd" | pbcopy
1966
+ sis session context "$session_id" --cwd "$cwd" | ${CLIP_BIN}
1881
1967
  tmux display-message "Copied session context (XML)"
1882
1968
  `;
1883
1969
  var EDIT_CONTEXT_FILE_SCRIPT = `#!/bin/bash
1884
1970
  # Pick a context file for the current session and open it in nvim.
1885
- # Excludes archive/ subdirectory. Assumes macOS (fzf optional).
1971
+ # Excludes archive/ subdirectory. fzf optional.
1886
1972
  ${SESSION_RESOLVE}
1887
1973
 
1888
1974
  ctx_dir="$cwd/.sisyphus/sessions/$session_id/context"
@@ -1914,17 +2000,10 @@ file="$ctx_dir/$picked"
1914
2000
  exec nvim "$file"
1915
2001
  `;
1916
2002
  var QUICK_SPAWN_EXPLORE_SCRIPT = `#!/bin/bash
1917
- # Spawn an Explore agent with the macOS clipboard contents as the instruction.
1918
- # macOS only \u2014 pbpaste hard dependency.
2003
+ # Spawn an Explore agent with the clipboard contents as the instruction.
1919
2004
  ${SESSION_RESOLVE}
1920
2005
 
1921
- if ! command -v pbpaste >/dev/null 2>&1; then
1922
- echo "pbpaste not found \u2014 macOS only for now"
1923
- read -n 1 -s -r -p "Press a key to close."
1924
- exit 1
1925
- fi
1926
-
1927
- instruction=$(pbpaste)
2006
+ instruction=$(${CLIP_BIN} paste 2>/dev/null)
1928
2007
  if [ -z "\${instruction// }" ]; then
1929
2008
  echo "Clipboard is empty \u2014 copy a task description first"
1930
2009
  read -n 1 -s -r -p "Press a key to close."
@@ -1948,17 +2027,10 @@ exit_code=$?
1948
2027
  exit $exit_code
1949
2028
  `;
1950
2029
  var QUICK_SPAWN_DEBUG_SCRIPT = `#!/bin/bash
1951
- # Spawn a Debug agent with the macOS clipboard contents as the instruction.
1952
- # macOS only \u2014 pbpaste hard dependency.
2030
+ # Spawn a Debug agent with the clipboard contents as the instruction.
1953
2031
  ${SESSION_RESOLVE}
1954
2032
 
1955
- if ! command -v pbpaste >/dev/null 2>&1; then
1956
- echo "pbpaste not found \u2014 macOS only for now"
1957
- read -n 1 -s -r -p "Press a key to close."
1958
- exit 1
1959
- fi
1960
-
1961
- instruction=$(pbpaste)
2033
+ instruction=$(${CLIP_BIN} paste 2>/dev/null)
1962
2034
  if [ -z "\${instruction// }" ]; then
1963
2035
  echo "Clipboard is empty \u2014 copy a task description first"
1964
2036
  read -n 1 -s -r -p "Press a key to close."
@@ -2056,6 +2128,8 @@ function installAllScripts() {
2056
2128
  installScript("sisyphus-kill-pane", KILL_PANE_SCRIPT);
2057
2129
  installScript("sisyphus-new", NEW_PROMPT_SCRIPT);
2058
2130
  installScript("sisyphus-msg", MESSAGE_SCRIPT);
2131
+ installScript("sisyphus-clip", SISYPHUS_CLIP_SCRIPT);
2132
+ installScript("sisyphus-open", SISYPHUS_OPEN_SCRIPT);
2059
2133
  installScript("sisyphus-kill-session", KILL_SESSION_SCRIPT);
2060
2134
  installScript("sisyphus-delete-session", DELETE_SESSION_SCRIPT);
2061
2135
  installScript("sisyphus-status-popup", STATUS_POPUP_SCRIPT);
@@ -2346,7 +2420,7 @@ var DEFAULT_CONFIG = {
2346
2420
  },
2347
2421
  companionPopup: true,
2348
2422
  requiredPlugins: [
2349
- { name: "devcore", marketplace: "crouton-kit" }
2423
+ { name: "devcore", marketplace: "crouton-kit", owner: "crouton-labs" }
2350
2424
  ]
2351
2425
  };
2352
2426
  function readJsonFile(filePath) {
@@ -2420,7 +2494,7 @@ function isMarketplaceInstalled(marketplace) {
2420
2494
  const output = exec("claude plugins marketplace list");
2421
2495
  return output.includes(marketplace);
2422
2496
  }
2423
- function installMarketplace(marketplace, owner = "CaptainCrouton89") {
2497
+ function installMarketplace(marketplace, owner) {
2424
2498
  console.log(`Adding marketplace: ${owner}/${marketplace}`);
2425
2499
  execSync2(`claude plugins marketplace add ${owner}/${marketplace}`, { stdio: "inherit" });
2426
2500
  }
@@ -2458,7 +2532,7 @@ async function ensureRequiredPlugins(cwd) {
2458
2532
  if (existing) continue;
2459
2533
  console.log(`Required plugin ${key} not found \u2014 installing...`);
2460
2534
  if (!isMarketplaceInstalled(plugin.marketplace)) {
2461
- installMarketplace(plugin.marketplace);
2535
+ installMarketplace(plugin.marketplace, plugin.owner);
2462
2536
  }
2463
2537
  installPlugin(key);
2464
2538
  const verified = resolveInstalledPlugin(key);
@@ -2513,6 +2587,7 @@ function isInstalled() {
2513
2587
  async function ensureDaemonInstalled() {
2514
2588
  if (process.platform !== "darwin") return;
2515
2589
  const sisyphusPlugin = ensureSisyphusPluginInstalled();
2590
+ await ensureRequiredPlugins(process.cwd());
2516
2591
  if (!isInstalled()) {
2517
2592
  const nodePath = process.execPath;
2518
2593
  const daemonPath = daemonBinPath();
@@ -2523,7 +2598,6 @@ async function ensureDaemonInstalled() {
2523
2598
  writeFileSync2(plistPath(), plist, "utf8");
2524
2599
  execSync3(`launchctl load -w ${plistPath()}`);
2525
2600
  const keybindResult = await setupTmuxKeybind();
2526
- await ensureRequiredPlugins(process.cwd());
2527
2601
  printGettingStarted(keybindResult, sisyphusPlugin);
2528
2602
  }
2529
2603
  await waitForDaemon();
@@ -2861,14 +2935,14 @@ function registerStart(program2) {
2861
2935
  process.exit(1);
2862
2936
  }
2863
2937
  const sessionId = response.data?.sessionId;
2864
- console.log(`Task handed off to sisyphus orchestrator (session ${sessionId})`);
2938
+ console.error(`Task handed off to sisyphus orchestrator (session ${sessionId})`);
2865
2939
  if (opts.tmuxCheck === false) {
2866
2940
  const tmuxSessionName2 = response.data?.tmuxSessionName;
2867
2941
  if (tmuxSessionName2) {
2868
- console.log(`Tmux session: ${tmuxSessionName2}`);
2869
- console.log(` tmux attach -t ${tmuxSessionName2}`);
2942
+ console.error(`Tmux session: ${tmuxSessionName2}`);
2943
+ console.error(` tmux attach -t ${tmuxSessionName2}`);
2870
2944
  }
2871
- console.log(`Monitor: sis status ${sessionId}`);
2945
+ console.error(`Monitor: sis status ${sessionId}`);
2872
2946
  return;
2873
2947
  }
2874
2948
  let tmuxSession;
@@ -2912,7 +2986,7 @@ function registerStart(program2) {
2912
2986
  if (!process.env["TMUX"]) {
2913
2987
  attachToTmuxSession(tmuxSession);
2914
2988
  }
2915
- console.log(`Monitor: sis status ${sessionId}`);
2989
+ console.error(`Monitor: sis status ${sessionId}`);
2916
2990
  });
2917
2991
  }
2918
2992
 
@@ -2965,24 +3039,38 @@ function statusColor(status) {
2965
3039
  return "white";
2966
3040
  }
2967
3041
  }
3042
+ var COLOR_ENABLED = process.env["FORCE_COLOR"] === "1" || process.stdout.isTTY === true && process.env["NO_COLOR"] === void 0 && process.env["TERM"] !== "dumb";
3043
+ function wrap(open, close = "\x1B[0m") {
3044
+ return (s) => COLOR_ENABLED ? `${open}${s}${close}` : s;
3045
+ }
3046
+ var bold = wrap("\x1B[1m");
3047
+ var dim = wrap("\x1B[2m");
3048
+ var red = wrap("\x1B[31m");
3049
+ var green = wrap("\x1B[32m");
3050
+ var yellow = wrap("\x1B[33m");
3051
+ var cyan = wrap("\x1B[36m");
3052
+ var gray = wrap("\x1B[90m");
3053
+ var magenta = wrap("\x1B[35m");
3054
+ var white = wrap("\x1B[37m");
3055
+ var COLOR_FNS = {
3056
+ red,
3057
+ green,
3058
+ yellow,
3059
+ cyan,
3060
+ gray,
3061
+ magenta,
3062
+ white,
3063
+ bold,
3064
+ dim
3065
+ };
3066
+ function colorize(text, colorName) {
3067
+ const fn = COLOR_FNS[colorName];
3068
+ return fn ? fn(text) : text;
3069
+ }
2968
3070
 
2969
3071
  // src/cli/commands/status.ts
2970
- var COLOR_CODES = {
2971
- green: "\x1B[32m",
2972
- yellow: "\x1B[33m",
2973
- cyan: "\x1B[36m",
2974
- red: "\x1B[31m",
2975
- gray: "\x1B[90m",
2976
- white: "\x1B[37m"
2977
- };
2978
- var RESET = "\x1B[0m";
2979
- var BOLD = "\x1B[1m";
2980
- var DIM = "\x1B[2m";
2981
- function colorize(text, status) {
2982
- const colorName = statusColor(status);
2983
- const code = COLOR_CODES[colorName];
2984
- if (!code) return `${text}\x1B[0m`;
2985
- return `${code}${text}\x1B[0m`;
3072
+ function colorize2(text, status) {
3073
+ return colorize(text, statusColor(status));
2986
3074
  }
2987
3075
  function inferOrchestratorPhase(session2) {
2988
3076
  const cycles = session2.orchestratorCycles;
@@ -3003,15 +3091,15 @@ function inferOrchestratorPhase(session2) {
3003
3091
  }
3004
3092
  }
3005
3093
  function formatAgent(agent2, verbose) {
3006
- const status = colorize(agent2.status, agent2.status);
3007
- const name = `${BOLD}${agent2.name}${RESET}`;
3008
- const type = `${DIM}(${agent2.agentType})${RESET}`;
3094
+ const status = colorize2(agent2.status, agent2.status);
3095
+ const name = bold(agent2.name);
3096
+ const type = dim(`(${agent2.agentType})`);
3009
3097
  const duration = formatDuration(agent2.activeMs);
3010
- let line = ` ${agent2.id} ${name} ${type} \u2014 ${status} ${DIM}(${duration})${RESET}`;
3098
+ let line = ` ${agent2.id} ${name} ${type} \u2014 ${status} ${dim(`(${duration})`)}`;
3011
3099
  if (verbose && agent2.instruction) {
3012
3100
  const truncated = agent2.instruction.length > 200 ? agent2.instruction.slice(0, 200) + "..." : agent2.instruction;
3013
3101
  line += `
3014
- ${DIM}Instruction: ${truncated}${RESET}`;
3102
+ ${dim(`Instruction: ${truncated}`)}`;
3015
3103
  }
3016
3104
  if (agent2.reports.length > 0) {
3017
3105
  for (const r of agent2.reports) {
@@ -3029,10 +3117,10 @@ function formatAgent(agent2, verbose) {
3029
3117
  function formatCycle(cycle, phase) {
3030
3118
  let duration;
3031
3119
  if (cycle.completedAt) {
3032
- duration = ` ${DIM}(${formatDuration(cycle.activeMs)})${RESET}`;
3120
+ duration = ` ${dim(`(${formatDuration(cycle.activeMs)})`)}`;
3033
3121
  } else {
3034
3122
  const elapsed = formatDuration(cycle.activeMs);
3035
- duration = ` ${DIM}(running, ${elapsed})${RESET}`;
3123
+ duration = ` ${dim(`(running, ${elapsed})`)}`;
3036
3124
  }
3037
3125
  const agents = cycle.agentsSpawned.length > 0 ? ` \u2014 agents: ${cycle.agentsSpawned.join(", ")}` : "";
3038
3126
  const phaseStr = phase ? ` \u2014 orchestrator: ${phase}` : "";
@@ -3081,10 +3169,10 @@ function capturePaneOutput(paneId, lines = 50) {
3081
3169
  }
3082
3170
  }
3083
3171
  function printSession(session2, verbose) {
3084
- const status = colorize(session2.status, session2.status);
3172
+ const status = colorize2(session2.status, session2.status);
3085
3173
  const sessionDuration = formatDuration(session2.createdAt, session2.completedAt);
3086
3174
  console.log(`
3087
- ${BOLD}Session: ${session2.id}${RESET}`);
3175
+ ${bold(`Session: ${session2.id}`)}`);
3088
3176
  console.log(` Status: ${status}`);
3089
3177
  const effortLabel = session2.effort != null ? session2.effort : "high (default)";
3090
3178
  console.log(` Effort: ${effortLabel}`);
@@ -3105,7 +3193,7 @@ ${BOLD}Session: ${session2.id}${RESET}`);
3105
3193
  if (session2.handoff) {
3106
3194
  const h = session2.handoff;
3107
3195
  if (h.lastError) {
3108
- console.log(` Handoff: ${COLOR_CODES.red}error${RESET} \u2014 ${h.lastError}`);
3196
+ console.log(` Handoff: ${red("error")} \u2014 ${h.lastError}`);
3109
3197
  } else if (h.reclaimedAt) {
3110
3198
  const where = h.target ? `${h.target.provider}:${h.target.repo}` : "cloud";
3111
3199
  console.log(` Handoff: reclaimed from ${where} at ${h.reclaimedAt}`);
@@ -3120,27 +3208,27 @@ ${BOLD}Session: ${session2.id}${RESET}`);
3120
3208
  const runningAgents = session2.agents.filter((a) => a.status === "running");
3121
3209
  if (runningAgents.length > 0) {
3122
3210
  console.log(`
3123
- ${BOLD}Active agents (${runningAgents.length}):${RESET}`);
3211
+ ${bold(`Active agents (${runningAgents.length}):`)}`);
3124
3212
  for (const agent2 of runningAgents) {
3125
- const name = `${BOLD}${agent2.name}${RESET}`;
3126
- const type = `${DIM}(${agent2.agentType})${RESET}`;
3213
+ const name = bold(agent2.name);
3214
+ const type = dim(`(${agent2.agentType})`);
3127
3215
  const duration = formatDuration(agent2.activeMs);
3128
3216
  console.log(` ${agent2.id} ${name} ${type} running ${duration}`);
3129
3217
  if (verbose && agent2.instruction) {
3130
3218
  const truncated = agent2.instruction.length > 200 ? agent2.instruction.slice(0, 200) + "..." : agent2.instruction;
3131
- console.log(` ${DIM}Instruction: ${truncated}${RESET}`);
3219
+ console.log(` ${dim(`Instruction: ${truncated}`)}`);
3132
3220
  }
3133
3221
  }
3134
3222
  }
3135
3223
  const roadmap = readRoadmap(session2.cwd, session2.id);
3136
3224
  if (roadmap) {
3137
3225
  console.log(`
3138
- ${BOLD}Roadmap:${RESET}`);
3226
+ ${bold("Roadmap:")}`);
3139
3227
  console.log(roadmap);
3140
3228
  }
3141
3229
  if (session2.orchestratorCycles.length > 0) {
3142
3230
  console.log(`
3143
- ${BOLD}Cycles:${RESET}`);
3231
+ ${bold("Cycles:")}`);
3144
3232
  const cycles = session2.orchestratorCycles;
3145
3233
  for (let i = 0; i < cycles.length; i++) {
3146
3234
  const isLast = i === cycles.length - 1;
@@ -3151,12 +3239,12 @@ ${BOLD}Roadmap:${RESET}`);
3151
3239
  if (log) {
3152
3240
  const lines = log.split("\n");
3153
3241
  const preview = lines.slice(0, 20).join("\n");
3154
- console.log(` ${DIM}--- cycle log ---${RESET}`);
3242
+ console.log(` ${dim("--- cycle log ---")}`);
3155
3243
  for (const line of preview.split("\n")) {
3156
- console.log(` ${DIM}${line}${RESET}`);
3244
+ console.log(` ${dim(line)}`);
3157
3245
  }
3158
3246
  if (lines.length > 20) {
3159
- console.log(` ${DIM}... (${lines.length - 20} more lines)${RESET}`);
3247
+ console.log(` ${dim(`... (${lines.length - 20} more lines)`)}`);
3160
3248
  }
3161
3249
  }
3162
3250
  }
@@ -3164,7 +3252,7 @@ ${BOLD}Roadmap:${RESET}`);
3164
3252
  }
3165
3253
  if (session2.agents.length > 0) {
3166
3254
  console.log(`
3167
- ${BOLD}Agents:${RESET}`);
3255
+ ${bold("Agents:")}`);
3168
3256
  for (const agent2 of session2.agents) {
3169
3257
  console.log(formatAgent(agent2, verbose));
3170
3258
  }
@@ -3194,7 +3282,7 @@ ${BOLD}Roadmap:${RESET}`);
3194
3282
  }
3195
3283
  if (verbose && session2.completionReport) {
3196
3284
  console.log(`
3197
- ${BOLD}Completion Report:${RESET}`);
3285
+ ${bold("Completion Report:")}`);
3198
3286
  console.log(session2.completionReport);
3199
3287
  }
3200
3288
  }
@@ -3226,38 +3314,45 @@ function registerStatus(program2) {
3226
3314
 
3227
3315
  // src/cli/commands/list.ts
3228
3316
  import { basename as basename3 } from "path";
3229
- var STATUS_COLORS = {
3230
- active: "\x1B[32m",
3231
- paused: "\x1B[33m",
3232
- completed: "\x1B[36m"
3233
- };
3234
- var RESET2 = "\x1B[0m";
3235
- var BOLD2 = "\x1B[1m";
3236
- var DIM2 = "\x1B[2m";
3237
- var CLOUD = "\x1B[35m";
3238
- var RED = "\x1B[31m";
3317
+ function statusColor2(s) {
3318
+ switch (s) {
3319
+ case "active":
3320
+ return "green";
3321
+ case "paused":
3322
+ return "yellow";
3323
+ case "completed":
3324
+ return "cyan";
3325
+ default:
3326
+ return "";
3327
+ }
3328
+ }
3329
+ function colorStatus(s) {
3330
+ const name = statusColor2(s);
3331
+ if (!name) return s;
3332
+ return colorize(s, name);
3333
+ }
3239
3334
  function handoffAnnotation(h) {
3240
3335
  if (!h) return "";
3241
3336
  if (h.lastError) {
3242
- return ` ${RED}handoff error: ${h.lastError}${RESET2}`;
3337
+ return ` ${red(`handoff error: ${h.lastError}`)}`;
3243
3338
  }
3244
3339
  if (h.reclaimedAt) {
3245
- return ` ${DIM2}(reclaimed)${RESET2}`;
3340
+ return ` ${dim("(reclaimed)")}`;
3246
3341
  }
3247
3342
  if (h.sentAt && h.target) {
3248
- return ` ${CLOUD}\u2192 ${h.target.provider}:${h.target.repo}${RESET2}`;
3343
+ return ` ${magenta(`\u2192 ${h.target.provider}:${h.target.repo}`)}`;
3249
3344
  }
3250
3345
  if (h.target) {
3251
- return ` ${CLOUD}handoff queued \u2192 ${h.target.provider}:${h.target.repo}${RESET2}`;
3346
+ return ` ${magenta(`handoff queued \u2192 ${h.target.provider}:${h.target.repo}`)}`;
3252
3347
  }
3253
- return ` ${CLOUD}quiesce queued${RESET2}`;
3348
+ return ` ${magenta("quiesce queued")}`;
3254
3349
  }
3255
3350
  function truncateTask(task, max) {
3256
3351
  if (task.length <= max) return task;
3257
3352
  return task.slice(0, max - 1) + "\u2026";
3258
3353
  }
3259
3354
  function registerList(program2) {
3260
- program2.command("list").description("List sessions (defaults to current project)").option("-a, --all", "Show sessions from all projects").option("--cwd <path>", "Project directory to list sessions for (overrides SISYPHUS_CWD)").action(async (opts) => {
3355
+ program2.command("list").description("List sessions (defaults to current project)").option("-a, --all", "Show sessions from all projects").option("--cwd <path>", "Project directory to list sessions for (overrides SISYPHUS_CWD)").option("-j, --json", "Output raw JSON").action(async (opts) => {
3261
3356
  const cwd = opts.cwd ?? process.env["SISYPHUS_CWD"] ?? process.cwd();
3262
3357
  const request = { type: "list", cwd, all: opts.all };
3263
3358
  const response = await sendRequest(request);
@@ -3265,29 +3360,32 @@ function registerList(program2) {
3265
3360
  const sessions = response.data?.sessions ?? [];
3266
3361
  const totalCount = response.data?.totalCount;
3267
3362
  const filtered = response.data?.filtered;
3363
+ if (opts.json) {
3364
+ console.log(JSON.stringify(sessions));
3365
+ return;
3366
+ }
3268
3367
  if (sessions.length === 0) {
3269
3368
  if (filtered && totalCount && totalCount > 0) {
3270
3369
  console.log(`No sessions in this project. ${totalCount} session(s) in other projects.`);
3271
- console.log(`${DIM2}Run ${RESET2}sis list --all${DIM2} to show all.${RESET2}`);
3370
+ console.log(`${dim("Run ")}sis list --all${dim(" to show all.")}`);
3272
3371
  } else {
3273
3372
  console.log("No sessions");
3274
3373
  }
3275
3374
  return;
3276
3375
  }
3277
3376
  for (const s of sessions) {
3278
- const color = STATUS_COLORS[s.status] ?? "";
3279
- const status = `${color}${s.status}${RESET2}`;
3280
- const agents = `${DIM2}${s.agentCount} agent(s)${RESET2}`;
3377
+ const status = colorStatus(s.status);
3378
+ const agents = dim(`${s.agentCount} agent(s)`);
3281
3379
  const task = truncateTask(s.task, 60);
3282
- const label = s.name ? `${s.name} ${DIM2}(${s.id.slice(0, 8)})${RESET2}` : s.id;
3283
- const cwdLabel = opts.all && s.cwd ? ` ${DIM2}${basename3(s.cwd)}${RESET2}` : "";
3380
+ const label = s.name ? `${s.name} ${dim(`(${s.id.slice(0, 8)})`)}` : s.id;
3381
+ const cwdLabel = opts.all && s.cwd ? ` ${dim(basename3(s.cwd))}` : "";
3284
3382
  const handoffLabel = handoffAnnotation(s.handoff);
3285
- console.log(` ${BOLD2}${label}${RESET2} ${status} ${agents} ${task}${cwdLabel}${handoffLabel}`);
3383
+ console.log(` ${bold(label)} ${status} ${agents} ${task}${cwdLabel}${handoffLabel}`);
3286
3384
  }
3287
3385
  if (filtered && totalCount && totalCount > sessions.length) {
3288
3386
  const otherCount = totalCount - sessions.length;
3289
3387
  console.log(`
3290
- ${DIM2}${otherCount} more session(s) in other projects. Run ${RESET2}sis list --all${DIM2} to show all.${RESET2}`);
3388
+ ${dim(`${otherCount} more session(s) in other projects. Run `)}sis list --all${dim(" to show all.")}`);
3291
3389
  }
3292
3390
  } else {
3293
3391
  console.error(`Error: ${response.error}`);
@@ -3349,7 +3447,7 @@ function registerTell(program2) {
3349
3447
  };
3350
3448
  const response = await sendRequest(request);
3351
3449
  if (response.ok) {
3352
- console.log(`Sent to ${targetRaw}${submit2 ? "" : " (not submitted)"}`);
3450
+ console.error(`Sent to ${targetRaw}${submit2 ? "" : " (not submitted)"}`);
3353
3451
  } else {
3354
3452
  console.error(`Error: ${response.error}`);
3355
3453
  process.exit(1);
@@ -3426,7 +3524,7 @@ function summaryLine(entry) {
3426
3524
  return `${role} ${ts} ${preview}`;
3427
3525
  }
3428
3526
  function registerRead(program2) {
3429
- program2.command("read <target>").description("Print the Claude conversation transcript for a target ('orchestrator' or 'agent-NNN').").option("--session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID)").option("--cycle <n>", "Orchestrator cycle number (default: most recent live, else last completed)").option("--tail <n>", "Show last N turns", void 0).option("--head <n>", "Show first N turns", void 0).option("--raw", "Print raw JSONL (no formatting, no filtering)").option("--summary", "One-line-per-turn summary instead of full content").option("--tool-detail", "Include full tool inputs/outputs (default: truncated to 400/600 chars)").action(async (targetRaw, opts) => {
3527
+ program2.command("read <target>").description("Print the Claude conversation transcript for a target ('orchestrator' or 'agent-NNN').").option("--session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID)").option("--cycle <n>", "Orchestrator cycle number (default: most recent live, else last completed)").option("--tail <n>", "Show last N turns", void 0).option("--head <n>", "Show first N turns", void 0).option("--raw", "Print raw JSONL (no formatting, no filtering)").option("--summary", "One-line-per-turn summary instead of full content").option("--tool-detail", "Include full tool inputs/outputs (default: truncated to 400/600 chars)").option("-j, --json", "Output JSONL \u2014 one JSON object per turn (mutually exclusive with --raw and --summary)").action(async (targetRaw, opts) => {
3430
3528
  const sessionId = opts.session ?? process.env.SISYPHUS_SESSION_ID;
3431
3529
  if (!sessionId) {
3432
3530
  console.error("Error: provide --session or set SISYPHUS_SESSION_ID");
@@ -3488,6 +3586,14 @@ function registerRead(program2) {
3488
3586
  process.exit(1);
3489
3587
  }
3490
3588
  const raw = readFileSync7(path, "utf-8");
3589
+ if (opts.json && opts.raw) {
3590
+ console.error("Error: --json and --raw are mutually exclusive");
3591
+ process.exit(1);
3592
+ }
3593
+ if (opts.json && opts.summary) {
3594
+ console.error("Error: --json and --summary are mutually exclusive");
3595
+ process.exit(1);
3596
+ }
3491
3597
  if (opts.raw) {
3492
3598
  process.stdout.write(raw);
3493
3599
  return;
@@ -3512,6 +3618,16 @@ function registerRead(program2) {
3512
3618
  entries = entries.slice(-tail);
3513
3619
  sliceNote = sliceNote ? `${sliceNote}, then last ${tail}` : `last ${tail} of ${totalTurns}`;
3514
3620
  }
3621
+ if (opts.json) {
3622
+ for (const e of entries) {
3623
+ console.log(JSON.stringify({
3624
+ role: e.type,
3625
+ timestamp: e.timestamp,
3626
+ content: e.message?.content
3627
+ }));
3628
+ }
3629
+ return;
3630
+ }
3515
3631
  console.log(`=== ${label} \u2014 ${entries.length} turn(s)${sliceNote ? ` (${sliceNote})` : ""} ===`);
3516
3632
  console.log(`transcript: ${path}
3517
3633
  `);
@@ -3562,7 +3678,7 @@ function registerMessage(program2) {
3562
3678
  const request = { type: "message", sessionId, content, source, ...opts.agent ? { agentId: opts.agent } : {} };
3563
3679
  const response = await sendRequest(request);
3564
3680
  if (response.ok) {
3565
- console.log("Message queued");
3681
+ console.error("Message queued");
3566
3682
  } else {
3567
3683
  console.error(`Error: ${response.error}`);
3568
3684
  process.exit(1);
@@ -3571,7 +3687,7 @@ function registerMessage(program2) {
3571
3687
  }
3572
3688
 
3573
3689
  // src/cli/commands/ask.ts
3574
- import { existsSync as existsSync11, readFileSync as readFileSync13, watchFile, unwatchFile } from "fs";
3690
+ import { existsSync as existsSync12, readFileSync as readFileSync14, watchFile, unwatchFile } from "fs";
3575
3691
  import { join as join13, resolve as resolve4 } from "path";
3576
3692
  import { ulid } from "ulid";
3577
3693
 
@@ -3692,7 +3808,7 @@ function parseDeck(deckPath) {
3692
3808
 
3693
3809
  // src/daemon/ask-store.ts
3694
3810
  init_paths();
3695
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync12, readdirSync as readdirSync3 } from "fs";
3811
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync13, readdirSync as readdirSync3 } from "fs";
3696
3812
 
3697
3813
  // src/daemon/history.ts
3698
3814
  init_paths();
@@ -3786,47 +3902,115 @@ init_atomic();
3786
3902
  // src/daemon/notify.ts
3787
3903
  init_shell();
3788
3904
  import { spawn, execFile } from "child_process";
3789
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync9 } from "fs";
3905
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync10 } from "fs";
3790
3906
  import { join as join12 } from "path";
3791
3907
  import { homedir as homedir7 } from "os";
3908
+
3909
+ // src/shared/platform.ts
3910
+ import { execSync as execSync8 } from "child_process";
3911
+ import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
3912
+ var cachedPlatform;
3913
+ function detectPlatform() {
3914
+ if (cachedPlatform) return cachedPlatform;
3915
+ if (process.platform === "darwin") {
3916
+ cachedPlatform = "darwin";
3917
+ } else if (process.platform === "win32") {
3918
+ cachedPlatform = "win32";
3919
+ } else if (process.platform === "linux") {
3920
+ cachedPlatform = isWsl() ? "wsl" : "linux";
3921
+ } else {
3922
+ cachedPlatform = "unknown";
3923
+ }
3924
+ return cachedPlatform;
3925
+ }
3926
+ function isWsl() {
3927
+ if (process.env["WSL_DISTRO_NAME"] || process.env["WSL_INTEROP"]) return true;
3928
+ try {
3929
+ if (existsSync9("/proc/version")) {
3930
+ const v = readFileSync12("/proc/version", "utf-8").toLowerCase();
3931
+ if (v.includes("microsoft") || v.includes("wsl")) return true;
3932
+ }
3933
+ } catch {
3934
+ }
3935
+ return false;
3936
+ }
3937
+ function isWslHost() {
3938
+ return detectPlatform() === "wsl";
3939
+ }
3940
+ function isLinuxLike() {
3941
+ const p = detectPlatform();
3942
+ return p === "linux" || p === "wsl";
3943
+ }
3944
+ var cmdCache = /* @__PURE__ */ new Map();
3945
+ function hasCommand(cmd) {
3946
+ const cached = cmdCache.get(cmd);
3947
+ if (cached !== void 0) return cached;
3948
+ try {
3949
+ execSync8(`command -v ${cmd}`, { stdio: "pipe", shell: "/bin/sh" });
3950
+ cmdCache.set(cmd, true);
3951
+ return true;
3952
+ } catch {
3953
+ cmdCache.set(cmd, false);
3954
+ return false;
3955
+ }
3956
+ }
3957
+ function platformLabel() {
3958
+ switch (detectPlatform()) {
3959
+ case "darwin":
3960
+ return "macOS";
3961
+ case "wsl": {
3962
+ const distro = process.env["WSL_DISTRO_NAME"];
3963
+ return distro ? `WSL (${distro})` : "WSL";
3964
+ }
3965
+ case "linux":
3966
+ return "Linux";
3967
+ case "win32":
3968
+ return "Windows (native)";
3969
+ default:
3970
+ return "unknown";
3971
+ }
3972
+ }
3973
+
3974
+ // src/daemon/notify.ts
3792
3975
  var TMUX_SOCKET = `/tmp/tmux-${process.getuid?.() ?? 0}/default`;
3793
3976
  var SWITCH_SCRIPT = [
3794
3977
  "#!/bin/bash",
3795
3978
  'SESSION="$1"',
3796
3979
  `TMUX_SOCKET="${TMUX_SOCKET}"`,
3797
- "TMUX=/opt/homebrew/bin/tmux",
3798
3980
  "",
3799
3981
  "# Find any attached client (user is likely on a different session)",
3800
- `CLIENT_TTY=$("$TMUX" -S "$TMUX_SOCKET" list-clients -F '#{client_tty}' 2>/dev/null | head -1)`,
3982
+ `CLIENT_TTY=$(tmux -S "$TMUX_SOCKET" list-clients -F '#{client_tty}' 2>/dev/null | head -1)`,
3801
3983
  '[ -z "$CLIENT_TTY" ] && exit 0',
3802
3984
  "",
3803
3985
  "# Switch that client to the target session",
3804
- '"$TMUX" -S "$TMUX_SOCKET" switch-client -c "$CLIENT_TTY" -t "$SESSION" 2>/dev/null',
3805
- '"$TMUX" -S "$TMUX_SOCKET" select-window -t "$SESSION" 2>/dev/null',
3986
+ 'tmux -S "$TMUX_SOCKET" switch-client -c "$CLIENT_TTY" -t "$SESSION" 2>/dev/null',
3987
+ 'tmux -S "$TMUX_SOCKET" select-window -t "$SESSION" 2>/dev/null',
3806
3988
  "",
3807
- "# Bring iTerm2 to front and select the tab with this client",
3808
- `TTY_SHORT=$(echo "$CLIENT_TTY" | sed 's|/dev/||')`,
3809
- 'osascript -e "',
3810
- ' tell application \\"iTerm2\\"',
3811
- " activate",
3812
- " repeat with w in windows",
3813
- " tell w",
3814
- " repeat with t in tabs",
3815
- " tell t",
3816
- " repeat with s in sessions",
3817
- " tell s",
3818
- ' if tty contains \\"$TTY_SHORT\\" then',
3819
- " select t",
3820
- " return",
3821
- " end if",
3822
- " end tell",
3823
- " end repeat",
3824
- " end tell",
3825
- " end repeat",
3826
- " end tell",
3827
- " end repeat",
3828
- " end tell",
3829
- `" 2>/dev/null || osascript -e 'tell application "iTerm2" to activate' 2>/dev/null`,
3989
+ "# macOS-only: bring iTerm2 to front and select the tab with this client",
3990
+ 'if [ "$(uname -s)" = "Darwin" ] && command -v osascript >/dev/null 2>&1; then',
3991
+ ` TTY_SHORT=$(echo "$CLIENT_TTY" | sed 's|/dev/||')`,
3992
+ ' osascript -e "',
3993
+ ' tell application \\"iTerm2\\"',
3994
+ " activate",
3995
+ " repeat with w in windows",
3996
+ " tell w",
3997
+ " repeat with t in tabs",
3998
+ " tell t",
3999
+ " repeat with s in sessions",
4000
+ " tell s",
4001
+ ' if tty contains \\"$TTY_SHORT\\" then',
4002
+ " select t",
4003
+ " return",
4004
+ " end if",
4005
+ " end tell",
4006
+ " end repeat",
4007
+ " end tell",
4008
+ " end repeat",
4009
+ " end tell",
4010
+ " end repeat",
4011
+ " end tell",
4012
+ ` " 2>/dev/null || osascript -e 'tell application "iTerm2" to activate' 2>/dev/null`,
4013
+ "fi",
3830
4014
  ""
3831
4015
  ].join("\n");
3832
4016
  function ensureSwitchScript() {
@@ -3847,7 +4031,7 @@ function ensureNotifyProcess() {
3847
4031
  return notifyProcess;
3848
4032
  }
3849
4033
  const binary = getNotifyBinary();
3850
- if (!existsSync9(binary)) {
4034
+ if (!existsSync10(binary)) {
3851
4035
  return null;
3852
4036
  }
3853
4037
  notifyProcess = spawn(binary, [], {
@@ -3865,6 +4049,13 @@ function ensureNotifyProcess() {
3865
4049
  notifyProcess.stderr?.unref();
3866
4050
  return notifyProcess;
3867
4051
  }
4052
+ function sendLinuxNotification(title, msg, level) {
4053
+ if (!hasCommand("notify-send")) return false;
4054
+ const urgency = level === "urgent" ? "critical" : "normal";
4055
+ execFile("notify-send", ["--app-name=Sisyphus", `--urgency=${urgency}`, title, msg], () => {
4056
+ });
4057
+ return true;
4058
+ }
3868
4059
  function sendTerminalNotification(titleOrOpts, message, tmuxSession, level) {
3869
4060
  let title;
3870
4061
  let msg;
@@ -3874,33 +4065,41 @@ function sendTerminalNotification(titleOrOpts, message, tmuxSession, level) {
3874
4065
  title = titleOrOpts.title;
3875
4066
  msg = titleOrOpts.message;
3876
4067
  tmuxSess = titleOrOpts.tmuxSession;
3877
- lvl = titleOrOpts.level ?? "urgent";
4068
+ lvl = titleOrOpts.level === void 0 ? "urgent" : titleOrOpts.level;
3878
4069
  } else {
3879
4070
  title = titleOrOpts;
3880
4071
  msg = message;
3881
4072
  tmuxSess = tmuxSession;
3882
- lvl = level ?? "urgent";
4073
+ lvl = level === void 0 ? "urgent" : level;
3883
4074
  }
3884
4075
  if (tmuxSess) ensureSwitchScript();
3885
- const proc = ensureNotifyProcess();
3886
- if (proc?.stdin?.writable) {
3887
- const payload = { title, message: msg, level: lvl };
3888
- if (tmuxSess) payload.tmuxSession = tmuxSess;
3889
- proc.stdin.write(JSON.stringify(payload) + "\n");
4076
+ const platform = detectPlatform();
4077
+ if (platform === "darwin") {
4078
+ const proc = ensureNotifyProcess();
4079
+ if (proc?.stdin?.writable) {
4080
+ const payload = { title, message: msg, level: lvl };
4081
+ if (tmuxSess) payload.tmuxSession = tmuxSess;
4082
+ proc.stdin.write(JSON.stringify(payload) + "\n");
4083
+ return;
4084
+ }
4085
+ const tnArgs = ["-title", title, "-message", msg];
4086
+ if (lvl === "urgent") tnArgs.push("-sound", "default");
4087
+ execFile("terminal-notifier", tnArgs, (err) => {
4088
+ if (err) {
4089
+ const soundClause = lvl === "urgent" ? ' sound name "default"' : "";
4090
+ execFile("osascript", [
4091
+ "-e",
4092
+ `display notification "${escapeAppleScript(msg)}" with title "${escapeAppleScript(title)}"${soundClause}`
4093
+ ], () => {
4094
+ });
4095
+ }
4096
+ });
4097
+ return;
4098
+ }
4099
+ if (platform === "linux" || platform === "wsl") {
4100
+ sendLinuxNotification(title, msg, lvl);
3890
4101
  return;
3891
4102
  }
3892
- const tnArgs = ["-title", title, "-message", msg];
3893
- if (lvl === "urgent") tnArgs.push("-sound", "default");
3894
- execFile("terminal-notifier", tnArgs, (err) => {
3895
- if (err) {
3896
- const soundClause = lvl === "urgent" ? ' sound name "default"' : "";
3897
- execFile("osascript", [
3898
- "-e",
3899
- `display notification "${escapeAppleScript(msg)}" with title "${escapeAppleScript(title)}"${soundClause}`
3900
- ], () => {
3901
- });
3902
- }
3903
- });
3904
4103
  }
3905
4104
 
3906
4105
  // src/daemon/ask-store.ts
@@ -3962,7 +4161,7 @@ function writeDecisions(cwd, sessionId, askId, deck) {
3962
4161
  function readDecisions(cwd, sessionId, askId) {
3963
4162
  const p = askDecisionsPath(cwd, sessionId, askId);
3964
4163
  try {
3965
- return JSON.parse(readFileSync12(p, { encoding: "utf-8" }));
4164
+ return JSON.parse(readFileSync13(p, { encoding: "utf-8" }));
3966
4165
  } catch (_e) {
3967
4166
  return null;
3968
4167
  }
@@ -3975,10 +4174,10 @@ function writeOutput(cwd, sessionId, askId, responses, completedAt) {
3975
4174
  }
3976
4175
  function readMeta(cwd, sessionId, askId) {
3977
4176
  const p = askMetaPath(cwd, sessionId, askId);
3978
- if (!existsSync10(p)) {
4177
+ if (!existsSync11(p)) {
3979
4178
  return null;
3980
4179
  }
3981
- return JSON.parse(readFileSync12(p, "utf-8"));
4180
+ return JSON.parse(readFileSync13(p, "utf-8"));
3982
4181
  }
3983
4182
  async function updateMeta(cwd, sessionId, askId, patch) {
3984
4183
  return withLock(askId, () => {
@@ -4002,7 +4201,7 @@ function buildAutoResponses(deck) {
4002
4201
  }
4003
4202
  async function autoResolveAsk(cwd, sessionId, askId, deck) {
4004
4203
  try {
4005
- if (existsSync10(askOutputPath(cwd, sessionId, askId))) return false;
4204
+ if (existsSync11(askOutputPath(cwd, sessionId, askId))) return false;
4006
4205
  const d = deck ?? readDecisions(cwd, sessionId, askId);
4007
4206
  if (!d) return false;
4008
4207
  const responses = buildAutoResponses(d);
@@ -4099,7 +4298,7 @@ function mintAskId() {
4099
4298
  return ulid();
4100
4299
  }
4101
4300
  function resolveClaudeSessionId(cwd, sessionId, askedBy) {
4102
- if (!existsSync11(statePath(cwd, sessionId))) return void 0;
4301
+ if (!existsSync12(statePath(cwd, sessionId))) return void 0;
4103
4302
  const session2 = getSession(cwd, sessionId);
4104
4303
  if (askedBy === ORCHESTRATOR_ASKED_BY) {
4105
4304
  const last = session2.orchestratorCycles[session2.orchestratorCycles.length - 1];
@@ -4136,7 +4335,7 @@ async function markAnswered(cwd, sessionId, askId) {
4136
4335
  });
4137
4336
  if (meta.blocking && durationMs > 0) {
4138
4337
  try {
4139
- if (existsSync11(statePath(cwd, sessionId))) {
4338
+ if (existsSync12(statePath(cwd, sessionId))) {
4140
4339
  await incrementUserBlockedMs(cwd, sessionId, durationMs, meta.askedAt, meta.askedBy);
4141
4340
  }
4142
4341
  } catch {
@@ -4145,8 +4344,8 @@ async function markAnswered(cwd, sessionId, askId) {
4145
4344
  }
4146
4345
  function waitForOutput(cwd, sessionId, askId, initialPpid) {
4147
4346
  const outputPath = askOutputPath(cwd, sessionId, askId);
4148
- if (existsSync11(outputPath)) {
4149
- return Promise.resolve(JSON.parse(readFileSync13(outputPath, "utf-8")));
4347
+ if (existsSync12(outputPath)) {
4348
+ return Promise.resolve(JSON.parse(readFileSync14(outputPath, "utf-8")));
4150
4349
  }
4151
4350
  return new Promise((res, _rej) => {
4152
4351
  let ppidWatcher;
@@ -4156,9 +4355,9 @@ function waitForOutput(cwd, sessionId, askId, initialPpid) {
4156
4355
  process.removeListener("SIGINT", onSigint);
4157
4356
  };
4158
4357
  const onChange = () => {
4159
- if (!existsSync11(outputPath)) return;
4358
+ if (!existsSync12(outputPath)) return;
4160
4359
  try {
4161
- const out = JSON.parse(readFileSync13(outputPath, "utf-8"));
4360
+ const out = JSON.parse(readFileSync14(outputPath, "utf-8"));
4162
4361
  cleanup();
4163
4362
  res(out);
4164
4363
  } catch (err) {
@@ -4197,7 +4396,7 @@ async function submit(file, opts) {
4197
4396
  const { cwd, sessionId } = resolveSessionEnv(opts);
4198
4397
  const askedBy = process.env.SISYPHUS_AGENT_ID ?? ORCHESTRATOR_ASKED_BY;
4199
4398
  const deckPath = resolve4(file);
4200
- if (!existsSync11(deckPath)) {
4399
+ if (!existsSync12(deckPath)) {
4201
4400
  console.error(`Error: deck file not found: ${deckPath}`);
4202
4401
  process.exit(1);
4203
4402
  }
@@ -4256,8 +4455,8 @@ async function peek(askId, opts) {
4256
4455
  };
4257
4456
  if (meta.completedAt) result.completedAt = meta.completedAt;
4258
4457
  try {
4259
- if (existsSync11(outputPath)) {
4260
- result.output = JSON.parse(readFileSync13(outputPath, "utf-8"));
4458
+ if (existsSync12(outputPath)) {
4459
+ result.output = JSON.parse(readFileSync14(outputPath, "utf-8"));
4261
4460
  }
4262
4461
  } catch (err) {
4263
4462
  if (!(err instanceof SyntaxError)) throw err;
@@ -4564,13 +4763,13 @@ function registerSessionDangerous(program2) {
4564
4763
 
4565
4764
  // src/tui/lib/context.ts
4566
4765
  init_paths();
4567
- import { readFileSync as readFileSync15, readdirSync as readdirSync4 } from "fs";
4766
+ import { readFileSync as readFileSync16, readdirSync as readdirSync4 } from "fs";
4568
4767
 
4569
4768
  // src/tui/lib/reports.ts
4570
- import { readFileSync as readFileSync14 } from "fs";
4769
+ import { readFileSync as readFileSync15 } from "fs";
4571
4770
  function loadReportContent(report) {
4572
4771
  try {
4573
- return readFileSync14(report.filePath, "utf-8");
4772
+ return readFileSync15(report.filePath, "utf-8");
4574
4773
  } catch {
4575
4774
  return report.summary;
4576
4775
  }
@@ -4587,7 +4786,7 @@ function resolveReports(reports) {
4587
4786
  // src/tui/lib/context.ts
4588
4787
  function readFileSafe(filePath) {
4589
4788
  try {
4590
- return readFileSync15(filePath, "utf-8");
4789
+ return readFileSync16(filePath, "utf-8");
4591
4790
  } catch {
4592
4791
  return null;
4593
4792
  }
@@ -4747,12 +4946,12 @@ function registerSessionContext(program2) {
4747
4946
  }
4748
4947
 
4749
4948
  // src/cli/commands/spawn.ts
4750
- import { existsSync as existsSync13 } from "fs";
4949
+ import { existsSync as existsSync14 } from "fs";
4751
4950
  import { join as join15, resolve as resolve5 } from "path";
4752
4951
 
4753
4952
  // src/daemon/frontmatter.ts
4754
4953
  init_paths();
4755
- import { readFileSync as readFileSync16, existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
4954
+ import { readFileSync as readFileSync17, existsSync as existsSync13, readdirSync as readdirSync5 } from "fs";
4756
4955
  import { homedir as homedir8 } from "os";
4757
4956
  import { join as join14, basename as basename4 } from "path";
4758
4957
  function parseAgentFrontmatter(content) {
@@ -4804,7 +5003,7 @@ function discoverAgentTypes(pluginDir, cwd) {
4804
5003
  if (seen.has(qualifiedName)) continue;
4805
5004
  seen.add(qualifiedName);
4806
5005
  try {
4807
- const content = readFileSync16(join14(dir, file), "utf-8");
5006
+ const content = readFileSync17(join14(dir, file), "utf-8");
4808
5007
  const fm = parseAgentFrontmatter(content);
4809
5008
  results.push({ qualifiedName, source, description: fm.description, model: fm.model });
4810
5009
  } catch {
@@ -4819,7 +5018,7 @@ function discoverAgentTypes(pluginDir, cwd) {
4819
5018
  scanDir(join14(pluginDir, "agents"), "sisyphus", "bundled");
4820
5019
  try {
4821
5020
  const registryPath = join14(homedir8(), ".claude", "plugins", "installed_plugins.json");
4822
- const registry = JSON.parse(readFileSync16(registryPath, "utf-8"));
5021
+ const registry = JSON.parse(readFileSync17(registryPath, "utf-8"));
4823
5022
  const pluginEntries = registry.plugins ?? registry;
4824
5023
  for (const key of Object.keys(pluginEntries)) {
4825
5024
  const atIdx = key.indexOf("@");
@@ -4899,7 +5098,7 @@ function registerSpawn(program2) {
4899
5098
  }
4900
5099
  if (opts.repo && opts.repo !== ".") {
4901
5100
  const repoPath = join15(sisyphusCwd, opts.repo);
4902
- if (!existsSync13(repoPath)) {
5101
+ if (!existsSync14(repoPath)) {
4903
5102
  console.error(`Error: repo directory does not exist: ${repoPath}`);
4904
5103
  process.exit(1);
4905
5104
  }
@@ -5006,10 +5205,10 @@ function registerReport(program2) {
5006
5205
  }
5007
5206
 
5008
5207
  // src/cli/commands/await.ts
5009
- import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
5208
+ import { existsSync as existsSync15, readFileSync as readFileSync18 } from "fs";
5010
5209
  var AWAIT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
5011
5210
  function registerAwait(program2) {
5012
- 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)").action(async (agentId, opts) => {
5211
+ 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)").option("-j, --json", "Output result as a single JSON object (report as string field)").action(async (agentId, opts) => {
5013
5212
  assertTmux();
5014
5213
  const sessionId = opts.session ?? process.env.SISYPHUS_SESSION_ID;
5015
5214
  if (!sessionId) {
@@ -5027,19 +5226,24 @@ function registerAwait(program2) {
5027
5226
  const reportPath = data.reportPath;
5028
5227
  const agentName = data.agentName;
5029
5228
  const agentType = data.agentType;
5030
- const shortType = agentType && agentType !== "worker" ? agentType.replace(/^sisyphus:/, "") : "";
5031
- const label = shortType ? `${shortType}-${agentName}` : agentName;
5032
- console.log(`[${status}] ${agentId} (${label})`);
5033
- if (reportPath && existsSync14(reportPath)) {
5229
+ let report = "";
5230
+ if (reportPath && existsSync15(reportPath)) {
5034
5231
  try {
5035
- const body = readFileSync17(reportPath, "utf-8");
5036
- if (body.length > 0) {
5037
- process.stdout.write(body.endsWith("\n") ? body : body + "\n");
5038
- }
5232
+ report = readFileSync18(reportPath, "utf-8");
5039
5233
  } catch (err) {
5040
5234
  console.error(`Warning: could not read report at ${reportPath}: ${err instanceof Error ? err.message : err}`);
5041
5235
  }
5042
5236
  }
5237
+ if (opts.json) {
5238
+ console.log(JSON.stringify({ agentId, status, agentName, agentType, reportPath, report }));
5239
+ return;
5240
+ }
5241
+ const shortType = agentType && agentType !== "worker" ? agentType.replace(/^sisyphus:/, "") : "";
5242
+ const label = shortType ? `${shortType}-${agentName}` : agentName;
5243
+ console.log(`[${status}] ${agentId} (${label})`);
5244
+ if (report.length > 0) {
5245
+ process.stdout.write(report.endsWith("\n") ? report : report + "\n");
5246
+ }
5043
5247
  });
5044
5248
  }
5045
5249
 
@@ -5156,14 +5360,71 @@ function registerSegmentUnregister(program2) {
5156
5360
  }
5157
5361
 
5158
5362
  // src/cli/commands/setup.ts
5159
- import { execSync as execSync10 } from "child_process";
5363
+ import { execSync as execSync11 } from "child_process";
5160
5364
 
5161
5365
  // src/cli/onboard.ts
5162
- import { execSync as execSync9 } from "child_process";
5163
- import { existsSync as existsSync15, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
5366
+ import { execSync as execSync10 } from "child_process";
5367
+ import { existsSync as existsSync16, readFileSync as readFileSync19, writeFileSync as writeFileSync8 } from "fs";
5164
5368
  import { homedir as homedir9 } from "os";
5165
5369
  import { dirname as dirname5, join as join16 } from "path";
5166
5370
  import { fileURLToPath as fileURLToPath2 } from "url";
5371
+
5372
+ // src/shared/clipboard.ts
5373
+ import { execFileSync as execFileSync2, spawnSync as spawnSync3 } from "child_process";
5374
+ function detectClipboard() {
5375
+ const platform = detectPlatform();
5376
+ if (platform === "darwin") {
5377
+ return {
5378
+ copy: { cmd: "pbcopy", args: [] },
5379
+ paste: { cmd: "pbpaste", args: [] },
5380
+ hint: null
5381
+ };
5382
+ }
5383
+ if (platform === "wsl") {
5384
+ const copy = hasCommand("clip.exe") ? { cmd: "clip.exe", args: [] } : null;
5385
+ const paste = hasCommand("powershell.exe") ? { cmd: "powershell.exe", args: ["-NoProfile", "-Command", "Get-Clipboard"] } : null;
5386
+ return {
5387
+ copy,
5388
+ paste,
5389
+ hint: copy && paste ? null : "WSL clipboard needs Windows interop. Ensure /mnt/c/Windows/System32 is on PATH (it is by default)."
5390
+ };
5391
+ }
5392
+ if (platform === "linux") {
5393
+ if (process.env["WAYLAND_DISPLAY"] && hasCommand("wl-copy") && hasCommand("wl-paste")) {
5394
+ return {
5395
+ copy: { cmd: "wl-copy", args: [] },
5396
+ paste: { cmd: "wl-paste", args: ["--no-newline"] },
5397
+ hint: null
5398
+ };
5399
+ }
5400
+ if (hasCommand("xclip")) {
5401
+ return {
5402
+ copy: { cmd: "xclip", args: ["-selection", "clipboard"] },
5403
+ paste: { cmd: "xclip", args: ["-selection", "clipboard", "-o"] },
5404
+ hint: null
5405
+ };
5406
+ }
5407
+ if (hasCommand("xsel")) {
5408
+ return {
5409
+ copy: { cmd: "xsel", args: ["--clipboard", "--input"] },
5410
+ paste: { cmd: "xsel", args: ["--clipboard", "--output"] },
5411
+ hint: null
5412
+ };
5413
+ }
5414
+ return {
5415
+ copy: null,
5416
+ paste: null,
5417
+ hint: "Install a clipboard tool: `sudo apt install xclip` (or wl-clipboard for Wayland)."
5418
+ };
5419
+ }
5420
+ return {
5421
+ copy: null,
5422
+ paste: null,
5423
+ hint: "Clipboard not supported on this platform."
5424
+ };
5425
+ }
5426
+
5427
+ // src/cli/onboard.ts
5167
5428
  function detectTerminal() {
5168
5429
  const termProgram = process.env["TERM_PROGRAM"] || "";
5169
5430
  const isIterm = termProgram === "iTerm.app" || !!process.env["ITERM_SESSION_ID"];
@@ -5171,7 +5432,7 @@ function detectTerminal() {
5171
5432
  }
5172
5433
  function isTmuxAvailable() {
5173
5434
  try {
5174
- execSync9("which tmux", { stdio: "pipe" });
5435
+ execSync10("which tmux", { stdio: "pipe" });
5175
5436
  return true;
5176
5437
  } catch {
5177
5438
  return false;
@@ -5179,7 +5440,7 @@ function isTmuxAvailable() {
5179
5440
  }
5180
5441
  function isBrewAvailable() {
5181
5442
  try {
5182
- execSync9("which brew", { stdio: "pipe" });
5443
+ execSync10("which brew", { stdio: "pipe" });
5183
5444
  return true;
5184
5445
  } catch {
5185
5446
  return false;
@@ -5189,7 +5450,7 @@ function tryAutoInstallTmux() {
5189
5450
  if (!isBrewAvailable()) return false;
5190
5451
  try {
5191
5452
  console.log(" Installing tmux via Homebrew...");
5192
- execSync9("brew install tmux", { stdio: "inherit" });
5453
+ execSync10("brew install tmux", { stdio: "inherit" });
5193
5454
  return isTmuxAvailable();
5194
5455
  } catch {
5195
5456
  return false;
@@ -5200,11 +5461,11 @@ function checkItermOptionKey() {
5200
5461
  return { checked: false, allCorrect: true, incorrectProfiles: [] };
5201
5462
  }
5202
5463
  const plistPath2 = join16(homedir9(), "Library", "Preferences", "com.googlecode.iterm2.plist");
5203
- if (!existsSync15(plistPath2)) {
5464
+ if (!existsSync16(plistPath2)) {
5204
5465
  return { checked: false, allCorrect: false, incorrectProfiles: [] };
5205
5466
  }
5206
5467
  try {
5207
- const json = execSync9(
5468
+ const json = execSync10(
5208
5469
  `plutil -extract "New Bookmarks" json -o - "${plistPath2}"`,
5209
5470
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5210
5471
  );
@@ -5224,7 +5485,7 @@ function checkItermOptionKey() {
5224
5485
  }
5225
5486
  }
5226
5487
  function hasExistingTmuxConf() {
5227
- return existsSync15(join16(homedir9(), ".tmux.conf")) || existsSync15(join16(homedir9(), ".config", "tmux", "tmux.conf"));
5488
+ return existsSync16(join16(homedir9(), ".tmux.conf")) || existsSync16(join16(homedir9(), ".config", "tmux", "tmux.conf"));
5228
5489
  }
5229
5490
  var SISYPHUS_DEFAULTS_MARKER = "# sisyphus-managed \u2014 do not edit";
5230
5491
  function buildTmuxDefaults() {
@@ -5341,7 +5602,7 @@ function writeTmuxDefaults() {
5341
5602
  }
5342
5603
  function isNvimAvailable() {
5343
5604
  try {
5344
- execSync9("which nvim", { stdio: "pipe" });
5605
+ execSync10("which nvim", { stdio: "pipe" });
5345
5606
  return true;
5346
5607
  } catch {
5347
5608
  return false;
@@ -5349,13 +5610,13 @@ function isNvimAvailable() {
5349
5610
  }
5350
5611
  function getNvimVersion() {
5351
5612
  try {
5352
- return execSync9("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "") || "unknown";
5613
+ return execSync10("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "") || "unknown";
5353
5614
  } catch {
5354
5615
  return "unknown";
5355
5616
  }
5356
5617
  }
5357
5618
  function hasLazyVimConfig() {
5358
- return existsSync15(join16(homedir9(), ".config", "nvim", "lazy-lock.json"));
5619
+ return existsSync16(join16(homedir9(), ".config", "nvim", "lazy-lock.json"));
5359
5620
  }
5360
5621
  function bundledBaleiaPluginPath() {
5361
5622
  const distDir = dirname5(fileURLToPath2(import.meta.url));
@@ -5363,13 +5624,13 @@ function bundledBaleiaPluginPath() {
5363
5624
  }
5364
5625
  function installBaleiaPlugin() {
5365
5626
  const pluginsDir = join16(homedir9(), ".config", "nvim", "lua", "plugins");
5366
- if (!existsSync15(pluginsDir)) return false;
5627
+ if (!existsSync16(pluginsDir)) return false;
5367
5628
  const dest = join16(pluginsDir, "sisyphus-baleia.lua");
5368
- if (existsSync15(dest)) return true;
5629
+ if (existsSync16(dest)) return true;
5369
5630
  const src = bundledBaleiaPluginPath();
5370
- if (!existsSync15(src)) return false;
5631
+ if (!existsSync16(src)) return false;
5371
5632
  try {
5372
- writeFileSync8(dest, readFileSync18(src, "utf-8"), "utf8");
5633
+ writeFileSync8(dest, readFileSync19(src, "utf-8"), "utf8");
5373
5634
  return true;
5374
5635
  } catch {
5375
5636
  return false;
@@ -5385,7 +5646,7 @@ function tryAutoInstallNvim() {
5385
5646
  }
5386
5647
  try {
5387
5648
  console.log(" Installing neovim via Homebrew...");
5388
- execSync9("brew install neovim", { stdio: "inherit" });
5649
+ execSync10("brew install neovim", { stdio: "inherit" });
5389
5650
  } catch {
5390
5651
  return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false, baleiaInstalled: false };
5391
5652
  }
@@ -5394,7 +5655,7 @@ function tryAutoInstallNvim() {
5394
5655
  }
5395
5656
  const nvimConfigDir = join16(homedir9(), ".config", "nvim");
5396
5657
  let lazyVimInstalled = false;
5397
- if (!existsSync15(nvimConfigDir)) {
5658
+ if (!existsSync16(nvimConfigDir)) {
5398
5659
  const cloneCmd = [
5399
5660
  "git",
5400
5661
  "-c core.hooksPath=/dev/null",
@@ -5406,13 +5667,13 @@ function tryAutoInstallNvim() {
5406
5667
  ].join(" ");
5407
5668
  try {
5408
5669
  console.log(" Cloning LazyVim starter config...");
5409
- execSync9(cloneCmd, {
5670
+ execSync10(cloneCmd, {
5410
5671
  stdio: "inherit",
5411
5672
  env: { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" }
5412
5673
  });
5413
5674
  const gitDir = join16(nvimConfigDir, ".git");
5414
- if (existsSync15(gitDir)) {
5415
- execSync9(`rm -rf "${gitDir}"`, { stdio: "pipe" });
5675
+ if (existsSync16(gitDir)) {
5676
+ execSync10(`rm -rf "${gitDir}"`, { stdio: "pipe" });
5416
5677
  }
5417
5678
  lazyVimInstalled = true;
5418
5679
  } catch (err) {
@@ -5427,7 +5688,7 @@ function tryAutoInstallNvim() {
5427
5688
  }
5428
5689
  function isTermrenderAvailable() {
5429
5690
  try {
5430
- execSync9("which termrender", { stdio: "pipe" });
5691
+ execSync10("which termrender", { stdio: "pipe" });
5431
5692
  return true;
5432
5693
  } catch {
5433
5694
  return false;
@@ -5435,7 +5696,7 @@ function isTermrenderAvailable() {
5435
5696
  }
5436
5697
  function isPipxAvailable() {
5437
5698
  try {
5438
- execSync9("which pipx", { stdio: "pipe" });
5699
+ execSync10("which pipx", { stdio: "pipe" });
5439
5700
  return true;
5440
5701
  } catch {
5441
5702
  return false;
@@ -5443,11 +5704,11 @@ function isPipxAvailable() {
5443
5704
  }
5444
5705
  function isPipAvailable() {
5445
5706
  try {
5446
- execSync9("which pip3", { stdio: "pipe" });
5707
+ execSync10("which pip3", { stdio: "pipe" });
5447
5708
  return true;
5448
5709
  } catch {
5449
5710
  try {
5450
- execSync9("which pip", { stdio: "pipe" });
5711
+ execSync10("which pip", { stdio: "pipe" });
5451
5712
  return true;
5452
5713
  } catch {
5453
5714
  return false;
@@ -5461,7 +5722,7 @@ function tryAutoInstallTermrender() {
5461
5722
  if (isPipxAvailable()) {
5462
5723
  try {
5463
5724
  console.log(" Installing termrender via pipx...");
5464
- execSync9("pipx install termrender", { stdio: "inherit" });
5725
+ execSync10("pipx install termrender", { stdio: "inherit" });
5465
5726
  if (isTermrenderAvailable()) return { installed: true, autoInstalled: true };
5466
5727
  } catch {
5467
5728
  }
@@ -5471,19 +5732,45 @@ function tryAutoInstallTermrender() {
5471
5732
  console.log(" Installing termrender via pip...");
5472
5733
  const pip = (() => {
5473
5734
  try {
5474
- execSync9("which pip3", { stdio: "pipe" });
5735
+ execSync10("which pip3", { stdio: "pipe" });
5475
5736
  return "pip3";
5476
5737
  } catch {
5477
5738
  return "pip";
5478
5739
  }
5479
5740
  })();
5480
- execSync9(`${pip} install termrender`, { stdio: "inherit" });
5741
+ execSync10(`${pip} install termrender`, { stdio: "inherit" });
5481
5742
  if (isTermrenderAvailable()) return { installed: true, autoInstalled: true };
5482
5743
  } catch {
5483
5744
  }
5484
5745
  }
5485
5746
  return { installed: false, autoInstalled: false };
5486
5747
  }
5748
+ function detectPlatformReadiness() {
5749
+ const clip = detectClipboard();
5750
+ const linuxLike = isLinuxLike();
5751
+ const wsl = isWslHost();
5752
+ let wslSystemdEnabled = null;
5753
+ if (wsl) {
5754
+ try {
5755
+ execSync10("systemctl is-system-running --quiet 2>/dev/null", { stdio: "pipe" });
5756
+ wslSystemdEnabled = true;
5757
+ } catch {
5758
+ try {
5759
+ execSync10("systemctl --user --version", { stdio: "pipe" });
5760
+ wslSystemdEnabled = true;
5761
+ } catch {
5762
+ wslSystemdEnabled = false;
5763
+ }
5764
+ }
5765
+ }
5766
+ return {
5767
+ label: platformLabel(),
5768
+ clipboardTool: clip.copy === null ? null : clip.copy.cmd,
5769
+ clipboardHint: clip.hint,
5770
+ notifySendAvailable: linuxLike ? hasCommand("notify-send") : true,
5771
+ wslSystemdEnabled
5772
+ };
5773
+ }
5487
5774
  function runOnboarding() {
5488
5775
  const terminal = detectTerminal();
5489
5776
  const tmuxAlreadyInstalled = isTmuxAvailable();
@@ -5493,10 +5780,10 @@ function runOnboarding() {
5493
5780
  if (!tmuxAlreadyInstalled && process.platform === "darwin") {
5494
5781
  tmuxAutoInstalled = tryAutoInstallTmux();
5495
5782
  tmuxInstalled = tmuxAutoInstalled;
5496
- if (tmuxAutoInstalled && !hasExistingTmuxConf()) {
5497
- writeTmuxDefaults();
5498
- tmuxDefaultsWritten = true;
5499
- }
5783
+ }
5784
+ if (tmuxInstalled && !hasExistingTmuxConf()) {
5785
+ writeTmuxDefaults();
5786
+ tmuxDefaultsWritten = true;
5500
5787
  }
5501
5788
  let itermOptionKey = { checked: false, allCorrect: true, incorrectProfiles: [] };
5502
5789
  if (terminal.isIterm) {
@@ -5505,13 +5792,14 @@ function runOnboarding() {
5505
5792
  const nvim = tryAutoInstallNvim();
5506
5793
  const sisyphusPlugin = ensureSisyphusPluginInstalled();
5507
5794
  const termrender = tryAutoInstallTermrender();
5508
- return { tmuxInstalled, tmuxAutoInstalled, terminal, itermOptionKey, tmuxDefaultsWritten, nvim, sisyphusPlugin, termrender };
5795
+ const platform = detectPlatformReadiness();
5796
+ return { tmuxInstalled, tmuxAutoInstalled, terminal, itermOptionKey, tmuxDefaultsWritten, nvim, sisyphusPlugin, termrender, platform };
5509
5797
  }
5510
5798
 
5511
5799
  // src/cli/commands/setup.ts
5512
5800
  function getTmuxVersion() {
5513
5801
  try {
5514
- return execSync10("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
5802
+ return execSync11("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
5515
5803
  } catch {
5516
5804
  return "installed";
5517
5805
  }
@@ -5520,6 +5808,7 @@ function printResults(result, daemonOk, keybindMsg) {
5520
5808
  console.log("");
5521
5809
  console.log("Setting up Sisyphus...");
5522
5810
  console.log("");
5811
+ console.log(` \u2713 Platform: ${result.platform.label}`);
5523
5812
  if (result.tmuxInstalled) {
5524
5813
  const detail = getTmuxVersion();
5525
5814
  console.log(` \u2713 tmux: ${detail}${result.tmuxAutoInstalled ? " (just installed)" : ""}`);
@@ -5527,6 +5816,23 @@ function printResults(result, daemonOk, keybindMsg) {
5527
5816
  const hint = process.platform === "darwin" ? "Install Homebrew (https://brew.sh) then: brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
5528
5817
  console.log(` \u2717 tmux: Not installed \u2014 ${hint}`);
5529
5818
  }
5819
+ if (process.platform !== "darwin") {
5820
+ if (result.platform.clipboardTool !== null) {
5821
+ console.log(` \u2713 Clipboard: ${result.platform.clipboardTool}`);
5822
+ } else {
5823
+ const hint = result.platform.clipboardHint === null ? "Install xclip / wl-clipboard" : result.platform.clipboardHint;
5824
+ console.log(` \u2717 Clipboard: no backend \u2014 ${hint}`);
5825
+ }
5826
+ if (result.platform.notifySendAvailable) {
5827
+ console.log(" \u2713 Notifications: notify-send");
5828
+ } else {
5829
+ console.log(" \u26A0 Notifications: notify-send missing \u2014 sudo apt install libnotify-bin");
5830
+ }
5831
+ }
5832
+ if (result.platform.wslSystemdEnabled === false) {
5833
+ console.log(" \u26A0 WSL systemd: disabled \u2014 add `[boot]\\nsystemd=true` to /etc/wsl.conf");
5834
+ console.log(" then run `wsl --shutdown` in PowerShell. The daemon can still be started manually with `sisyphusd &`.");
5835
+ }
5530
5836
  if (result.tmuxDefaultsWritten) {
5531
5837
  console.log(" \u2713 tmux config: Sensible defaults written to ~/.tmux.conf");
5532
5838
  }
@@ -5656,11 +5962,11 @@ function registerSetupKeybind(program2) {
5656
5962
  }
5657
5963
 
5658
5964
  // src/cli/commands/check-keybinds.ts
5659
- import { execSync as execSync11 } from "child_process";
5660
- import { readFileSync as readFileSync19 } from "fs";
5965
+ import { execSync as execSync12 } from "child_process";
5966
+ import { readFileSync as readFileSync20 } from "fs";
5661
5967
  function isTmuxInstalled2() {
5662
5968
  try {
5663
- execSync11("which tmux", { stdio: "pipe" });
5969
+ execSync12("which tmux", { stdio: "pipe" });
5664
5970
  return true;
5665
5971
  } catch {
5666
5972
  return false;
@@ -5668,14 +5974,14 @@ function isTmuxInstalled2() {
5668
5974
  }
5669
5975
  function getTmuxVersion2() {
5670
5976
  try {
5671
- return execSync11("tmux -V", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
5977
+ return execSync12("tmux -V", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
5672
5978
  } catch {
5673
5979
  return null;
5674
5980
  }
5675
5981
  }
5676
5982
  function isTmuxServerRunning() {
5677
5983
  try {
5678
- execSync11("tmux list-sessions", { stdio: ["pipe", "pipe", "pipe"] });
5984
+ execSync12("tmux list-sessions", { stdio: ["pipe", "pipe", "pipe"] });
5679
5985
  return true;
5680
5986
  } catch {
5681
5987
  return false;
@@ -5683,7 +5989,7 @@ function isTmuxServerRunning() {
5683
5989
  }
5684
5990
  function getTmuxPrefix() {
5685
5991
  try {
5686
- return execSync11("tmux show-options -gv prefix", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim() || null;
5992
+ return execSync12("tmux show-options -gv prefix", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim() || null;
5687
5993
  } catch {
5688
5994
  return null;
5689
5995
  }
@@ -5710,7 +6016,7 @@ function runCheck() {
5710
6016
  let userConfAlreadySources = false;
5711
6017
  if (userConfPath !== null) {
5712
6018
  try {
5713
- userConfAlreadySources = readFileSync19(userConfPath, "utf-8").includes(sisyphusConfPath);
6019
+ userConfAlreadySources = readFileSync20(userConfPath, "utf-8").includes(sisyphusConfPath);
5714
6020
  } catch {
5715
6021
  }
5716
6022
  }
@@ -5868,8 +6174,8 @@ function registerCheckKeybinds(program2) {
5868
6174
 
5869
6175
  // src/cli/commands/check-statusbar.ts
5870
6176
  init_paths();
5871
- import { execSync as execSync12 } from "child_process";
5872
- import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
6177
+ import { execSync as execSync13 } from "child_process";
6178
+ import { existsSync as existsSync17, readFileSync as readFileSync21 } from "fs";
5873
6179
  import { homedir as homedir10 } from "os";
5874
6180
  import { join as join17 } from "path";
5875
6181
  var SISYPHUS_LEFT_TOKEN = "@sisyphus_left";
@@ -5878,7 +6184,7 @@ var TMUX_DEFAULT_STATUS_LEFT = "[#S] ";
5878
6184
  var TMUX_DEFAULT_STATUS_RIGHT_PREFIX = '"#{=21:pane_title}"';
5879
6185
  function isTmuxInstalled3() {
5880
6186
  try {
5881
- execSync12("which tmux", { stdio: "pipe" });
6187
+ execSync13("which tmux", { stdio: "pipe" });
5882
6188
  return true;
5883
6189
  } catch {
5884
6190
  return false;
@@ -5886,7 +6192,7 @@ function isTmuxInstalled3() {
5886
6192
  }
5887
6193
  function isTmuxServerRunning2() {
5888
6194
  try {
5889
- execSync12("tmux list-sessions", { stdio: ["pipe", "pipe", "pipe"] });
6195
+ execSync13("tmux list-sessions", { stdio: ["pipe", "pipe", "pipe"] });
5890
6196
  return true;
5891
6197
  } catch {
5892
6198
  return false;
@@ -5894,9 +6200,9 @@ function isTmuxServerRunning2() {
5894
6200
  }
5895
6201
  function isDaemonRunning() {
5896
6202
  const pidFile = daemonPidPath();
5897
- if (!existsSync16(pidFile)) return false;
6203
+ if (!existsSync17(pidFile)) return false;
5898
6204
  try {
5899
- const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
6205
+ const pid = parseInt(readFileSync21(pidFile, "utf-8").trim(), 10);
5900
6206
  if (Number.isNaN(pid) || pid <= 0) return false;
5901
6207
  process.kill(pid, 0);
5902
6208
  return true;
@@ -5906,7 +6212,7 @@ function isDaemonRunning() {
5906
6212
  }
5907
6213
  function showOption(name) {
5908
6214
  try {
5909
- const out = execSync12(`tmux show-options -g ${name}`, { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
6215
+ const out = execSync13(`tmux show-options -g ${name}`, { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
5910
6216
  if (out.length === 0) return null;
5911
6217
  const prefix = `${name} `;
5912
6218
  const stripped = out.startsWith(prefix) ? out.slice(prefix.length) : out;
@@ -5941,8 +6247,8 @@ function probeTmuxOptions(serverRunning) {
5941
6247
  function findUserTmuxConf() {
5942
6248
  const xdg = join17(homedir10(), ".config", "tmux", "tmux.conf");
5943
6249
  const dotfile = join17(homedir10(), ".tmux.conf");
5944
- if (existsSync16(xdg)) return xdg;
5945
- if (existsSync16(dotfile)) return dotfile;
6250
+ if (existsSync17(xdg)) return xdg;
6251
+ if (existsSync17(dotfile)) return dotfile;
5946
6252
  return null;
5947
6253
  }
5948
6254
  function probeUserConf() {
@@ -5952,7 +6258,7 @@ function probeUserConf() {
5952
6258
  }
5953
6259
  let contents = "";
5954
6260
  try {
5955
- contents = readFileSync20(path, "utf-8");
6261
+ contents = readFileSync21(path, "utf-8");
5956
6262
  } catch {
5957
6263
  return { path, setsStatusLeft: false, setsStatusRight: false, sourcesSisyphusManaged: false };
5958
6264
  }
@@ -5964,9 +6270,9 @@ function probeUserConf() {
5964
6270
  }
5965
6271
  function loadGlobalSisyphusConfig() {
5966
6272
  const path = globalConfigPath();
5967
- if (!existsSync16(path)) return null;
6273
+ if (!existsSync17(path)) return null;
5968
6274
  try {
5969
- const parsed = JSON.parse(readFileSync20(path, "utf-8"));
6275
+ const parsed = JSON.parse(readFileSync21(path, "utf-8"));
5970
6276
  return parsed.statusBar === void 0 ? null : parsed.statusBar;
5971
6277
  } catch {
5972
6278
  return null;
@@ -6222,7 +6528,7 @@ function registerCheckStatusbar(program2) {
6222
6528
 
6223
6529
  // src/cli/commands/home-init.ts
6224
6530
  init_shell();
6225
- import { execSync as execSync13 } from "child_process";
6531
+ import { execSync as execSync14 } from "child_process";
6226
6532
  function registerHomeInit(parent) {
6227
6533
  parent.command("home-init <name> <cwd>").description("Bootstrap a tmux home session with the sisyphus dashboard.").action((name, cwd) => {
6228
6534
  ensureSession(name, cwd);
@@ -6232,7 +6538,7 @@ function registerHomeInit(parent) {
6232
6538
  }
6233
6539
  function sessionExists(name) {
6234
6540
  try {
6235
- execSync13(`tmux has-session -t ${shellQuote(name)}`, { stdio: "pipe" });
6541
+ execSync14(`tmux has-session -t ${shellQuote(name)}`, { stdio: "pipe" });
6236
6542
  return true;
6237
6543
  } catch {
6238
6544
  return false;
@@ -6240,13 +6546,13 @@ function sessionExists(name) {
6240
6546
  }
6241
6547
  function ensureSession(name, cwd) {
6242
6548
  if (sessionExists(name)) return;
6243
- execSync13(
6549
+ execSync14(
6244
6550
  `tmux new-session -d -s ${shellQuote(name)} -c ${shellQuote(cwd)}`,
6245
6551
  { stdio: "pipe" }
6246
6552
  );
6247
6553
  }
6248
6554
  function setSessionCwd(name, cwd) {
6249
- execSync13(
6555
+ execSync14(
6250
6556
  `tmux set-option -t ${shellQuote(name)} @sisyphus_cwd ${shellQuote(cwd.replace(/\/+$/, ""))}`,
6251
6557
  { stdio: "pipe" }
6252
6558
  );
@@ -6278,8 +6584,8 @@ function registerQuiesce(parent) {
6278
6584
 
6279
6585
  // src/cli/commands/doctor.ts
6280
6586
  init_paths();
6281
- import { execSync as execSync14 } from "child_process";
6282
- import { existsSync as existsSync17, statSync as statSync3 } from "fs";
6587
+ import { execSync as execSync15 } from "child_process";
6588
+ import { existsSync as existsSync18, statSync as statSync3 } from "fs";
6283
6589
  import { homedir as homedir11 } from "os";
6284
6590
  import { join as join18 } from "path";
6285
6591
  function checkNodeVersion() {
@@ -6291,7 +6597,7 @@ function checkNodeVersion() {
6291
6597
  }
6292
6598
  function checkClaudeCli() {
6293
6599
  try {
6294
- execSync14("which claude", { stdio: "pipe" });
6600
+ execSync15("which claude", { stdio: "pipe" });
6295
6601
  return { name: "Claude CLI", status: "ok", detail: "Found on PATH" };
6296
6602
  } catch {
6297
6603
  return {
@@ -6304,7 +6610,7 @@ function checkClaudeCli() {
6304
6610
  }
6305
6611
  function checkGit() {
6306
6612
  try {
6307
- const version = execSync14("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
6613
+ const version = execSync15("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
6308
6614
  return { name: "git", status: "ok", detail: version };
6309
6615
  } catch {
6310
6616
  return { name: "git", status: "fail", detail: "Not found on PATH", fix: "Install git: https://git-scm.com/downloads" };
@@ -6312,7 +6618,7 @@ function checkGit() {
6312
6618
  }
6313
6619
  function checkTmuxVersion() {
6314
6620
  try {
6315
- const version = execSync14("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
6621
+ const version = execSync15("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
6316
6622
  const match = version.match(/(\d+\.\d+)/);
6317
6623
  if (!match) return { name: "tmux version", status: "warn", detail: `Could not parse version: ${version}` };
6318
6624
  const ver = parseFloat(match[1]);
@@ -6338,7 +6644,7 @@ function checkDaemonInstalled() {
6338
6644
  };
6339
6645
  }
6340
6646
  const pid = daemonPidPath();
6341
- if (existsSync17(pid)) {
6647
+ if (existsSync18(pid)) {
6342
6648
  return { name: "Daemon setup", status: "ok", detail: `PID file found at ${pid}` };
6343
6649
  }
6344
6650
  return {
@@ -6350,7 +6656,7 @@ function checkDaemonInstalled() {
6350
6656
  }
6351
6657
  function checkDaemonRunning() {
6352
6658
  const pid = daemonPidPath();
6353
- if (!existsSync17(pid)) {
6659
+ if (!existsSync18(pid)) {
6354
6660
  const fix = process.platform === "darwin" ? "launchctl load -w ~/Library/LaunchAgents/com.sisyphus.daemon.plist" : "sisyphusd & \u2014 or check if the process is running";
6355
6661
  return {
6356
6662
  name: "Daemon process",
@@ -6361,7 +6667,7 @@ function checkDaemonRunning() {
6361
6667
  }
6362
6668
  try {
6363
6669
  const sock = socketPath();
6364
- execSync14(`test -S "${sock}"`, { stdio: "pipe" });
6670
+ execSync15(`test -S "${sock}"`, { stdio: "pipe" });
6365
6671
  return { name: "Daemon process", status: "ok", detail: `Socket at ${sock}` };
6366
6672
  } catch {
6367
6673
  return {
@@ -6374,13 +6680,13 @@ function checkDaemonRunning() {
6374
6680
  }
6375
6681
  function checkTmux() {
6376
6682
  try {
6377
- execSync14("which tmux", { stdio: "pipe" });
6683
+ execSync15("which tmux", { stdio: "pipe" });
6378
6684
  } catch {
6379
6685
  const installHint = process.platform === "darwin" ? "brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
6380
6686
  return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: installHint };
6381
6687
  }
6382
6688
  try {
6383
- execSync14("tmux list-sessions", { stdio: "pipe" });
6689
+ execSync15("tmux list-sessions", { stdio: "pipe" });
6384
6690
  return { name: "tmux", status: "ok", detail: "Running" };
6385
6691
  } catch {
6386
6692
  return { name: "tmux", status: "warn", detail: "Installed but no server running" };
@@ -6388,7 +6694,7 @@ function checkTmux() {
6388
6694
  }
6389
6695
  function checkCycleScript() {
6390
6696
  const path = cycleScriptPath();
6391
- if (!existsSync17(path)) {
6697
+ if (!existsSync18(path)) {
6392
6698
  return {
6393
6699
  name: "Cycle script",
6394
6700
  status: "fail",
@@ -6413,7 +6719,7 @@ function checkCycleScript() {
6413
6719
  function checkTmuxKeybind() {
6414
6720
  const existing = getExistingBinding(DEFAULT_CYCLE_KEY);
6415
6721
  if (existing === null) {
6416
- if (existsSync17(sisyphusTmuxConfPath())) {
6722
+ if (existsSync18(sisyphusTmuxConfPath())) {
6417
6723
  return {
6418
6724
  name: `Tmux keybind (${DEFAULT_CYCLE_KEY})`,
6419
6725
  status: "warn",
@@ -6439,7 +6745,7 @@ function checkTmuxKeybind() {
6439
6745
  }
6440
6746
  function checkGlobalDir() {
6441
6747
  const dir = globalDir();
6442
- if (existsSync17(dir)) {
6748
+ if (existsSync18(dir)) {
6443
6749
  return { name: "Data directory", status: "ok", detail: dir };
6444
6750
  }
6445
6751
  return { name: "Data directory", status: "warn", detail: `${dir} does not exist (created on first use)` };
@@ -6491,7 +6797,7 @@ function checkSisyphusPlugin() {
6491
6797
  function checkTermrender() {
6492
6798
  if (isTermrenderAvailable()) {
6493
6799
  try {
6494
- const version = execSync14("termrender --version", { encoding: "utf-8", stdio: "pipe" }).trim();
6800
+ const version = execSync15("termrender --version", { encoding: "utf-8", stdio: "pipe" }).trim();
6495
6801
  return { name: "termrender", status: "ok", detail: version };
6496
6802
  } catch {
6497
6803
  return { name: "termrender", status: "ok", detail: "installed" };
@@ -6510,31 +6816,67 @@ function checkNvim() {
6510
6816
  return { name: "nvim", status: "warn", detail: "Not installed", fix };
6511
6817
  }
6512
6818
  try {
6513
- const version = execSync14("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
6819
+ const version = execSync15("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
6514
6820
  return { name: "nvim", status: "ok", detail: version ?? "installed" };
6515
6821
  } catch {
6516
6822
  return { name: "nvim", status: "ok", detail: "installed" };
6517
6823
  }
6518
6824
  }
6519
6825
  function checkNotifyBinary() {
6520
- if (process.platform !== "darwin") return null;
6521
- const binary = join18(homedir11(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
6522
- if (existsSync17(binary)) {
6523
- return { name: "Notifications", status: "ok", detail: "SisyphusNotify.app built" };
6826
+ if (process.platform === "darwin") {
6827
+ const binary = join18(homedir11(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
6828
+ if (existsSync18(binary)) {
6829
+ return { name: "Notifications", status: "ok", detail: "SisyphusNotify.app built" };
6830
+ }
6831
+ return {
6832
+ name: "Notifications",
6833
+ status: "warn",
6834
+ detail: "SisyphusNotify.app not built (click-to-switch unavailable)",
6835
+ fix: "Requires Xcode CLI tools: xcode-select --install, then reinstall sisyphus"
6836
+ };
6837
+ }
6838
+ const ready = detectPlatformReadiness();
6839
+ if (ready.notifySendAvailable) {
6840
+ return { name: "Notifications", status: "ok", detail: "notify-send (libnotify)" };
6524
6841
  }
6525
6842
  return {
6526
6843
  name: "Notifications",
6527
6844
  status: "warn",
6528
- detail: "SisyphusNotify.app not built (click-to-switch unavailable)",
6529
- fix: "Requires Xcode CLI tools: xcode-select --install, then reinstall sisyphus"
6845
+ detail: "notify-send not found \u2014 OS banners disabled",
6846
+ fix: "sudo apt install libnotify-bin (or your distro's equivalent)"
6530
6847
  };
6531
6848
  }
6849
+ function checkClipboard() {
6850
+ const ready = detectPlatformReadiness();
6851
+ if (ready.clipboardTool !== null) {
6852
+ return { name: "Clipboard", status: "ok", detail: ready.clipboardTool };
6853
+ }
6854
+ const fix = ready.clipboardHint === null ? "Install xclip or wl-clipboard" : ready.clipboardHint;
6855
+ return {
6856
+ name: "Clipboard",
6857
+ status: "fail",
6858
+ detail: "No clipboard backend detected",
6859
+ fix
6860
+ };
6861
+ }
6862
+ function checkPlatform() {
6863
+ const ready = detectPlatformReadiness();
6864
+ if (ready.wslSystemdEnabled === false) {
6865
+ return {
6866
+ name: "Platform",
6867
+ status: "warn",
6868
+ detail: `${platformLabel()} \u2014 systemd disabled (daemon needs manual start)`,
6869
+ fix: "Add `[boot]\\nsystemd=true` to /etc/wsl.conf, then `wsl --shutdown` from PowerShell"
6870
+ };
6871
+ }
6872
+ return { name: "Platform", status: "ok", detail: platformLabel() };
6873
+ }
6532
6874
  var SYMBOLS = { ok: "\u2713", warn: "!", fail: "\u2717" };
6533
6875
  function registerDoctor(program2) {
6534
6876
  program2.command("doctor").description("Check sisyphus installation health").action(() => {
6535
6877
  const itermCheck = checkItermRightOptionKey();
6536
- const notifyCheck = checkNotifyBinary();
6537
6878
  const checks = [
6879
+ checkPlatform(),
6538
6880
  checkNodeVersion(),
6539
6881
  checkClaudeCli(),
6540
6882
  checkGit(),
@@ -6549,7 +6891,8 @@ function registerDoctor(program2) {
6549
6891
  checkTmuxKeybind(),
6550
6892
  checkSisyphusPlugin(),
6551
6893
  checkNvim(),
6552
- ...notifyCheck ? [notifyCheck] : [],
6894
+ checkNotifyBinary(),
6895
+ checkClipboard(),
6553
6896
  checkTermrender()
6554
6897
  ];
6555
6898
  let hasIssues = false;
@@ -6572,7 +6915,7 @@ function registerDoctor(program2) {
6572
6915
  }
6573
6916
 
6574
6917
  // src/cli/commands/init.ts
6575
- import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
6918
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
6576
6919
  import { join as join19 } from "path";
6577
6920
  var DEFAULT_CONFIG2 = {};
6578
6921
  var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
@@ -6586,7 +6929,7 @@ function registerInit(program2) {
6586
6929
  const cwd = process.cwd();
6587
6930
  const sisDir = join19(cwd, ".sisyphus");
6588
6931
  const configPath = join19(sisDir, "config.json");
6589
- if (existsSync18(configPath)) {
6932
+ if (existsSync19(configPath)) {
6590
6933
  console.log(`Already initialized: ${configPath}`);
6591
6934
  return;
6592
6935
  }
@@ -6595,7 +6938,7 @@ function registerInit(program2) {
6595
6938
  console.log(`Created ${configPath}`);
6596
6939
  if (opts.orchestrator) {
6597
6940
  const orchPath = join19(sisDir, "orchestrator.md");
6598
- if (!existsSync18(orchPath)) {
6941
+ if (!existsSync19(orchPath)) {
6599
6942
  writeFileSync9(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
6600
6943
  console.log(`Created ${orchPath}`);
6601
6944
  }
@@ -6638,7 +6981,7 @@ function registerUninstall(program2) {
6638
6981
 
6639
6982
  // src/cli/commands/configure-upload.ts
6640
6983
  init_paths();
6641
- import { chmodSync as chmodSync2, existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
6984
+ import { chmodSync as chmodSync2, existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync22, writeFileSync as writeFileSync10 } from "fs";
6642
6985
  import { createInterface as createInterface3 } from "readline";
6643
6986
  import { dirname as dirname6 } from "path";
6644
6987
  async function readUrlFromInput(interactive) {
@@ -6696,9 +7039,9 @@ function registerConfigureUpload(program2) {
6696
7039
  const url = parsed.origin + (strippedPath.length > 0 ? strippedPath : "");
6697
7040
  const configPath = globalConfigPath();
6698
7041
  let existing = {};
6699
- if (existsSync19(configPath)) {
7042
+ if (existsSync20(configPath)) {
6700
7043
  try {
6701
- existing = JSON.parse(readFileSync21(configPath, "utf-8"));
7044
+ existing = JSON.parse(readFileSync22(configPath, "utf-8"));
6702
7045
  } catch {
6703
7046
  console.error(`Error: ${configPath} could not be parsed \u2014 fix or delete it first`);
6704
7047
  process.exit(1);
@@ -6713,7 +7056,8 @@ function registerConfigureUpload(program2) {
6713
7056
  }
6714
7057
 
6715
7058
  // src/cli/commands/getting-started.ts
6716
- import { execSync as execSync15 } from "child_process";
7059
+ import { execSync as execSync16 } from "child_process";
7060
+ import { existsSync as existsSync21 } from "fs";
6717
7061
  import { dirname as dirname7, join as join20 } from "path";
6718
7062
  import { fileURLToPath as fileURLToPath3 } from "url";
6719
7063
  function templatePath(name) {
@@ -7084,6 +7428,7 @@ sis admin getting-started --tutorial 4
7084
7428
  `);
7085
7429
  }
7086
7430
  function printStep4() {
7431
+ const demoPath = join20(process.cwd(), "tmp", "sisyphus-tutorial-demo");
7087
7432
  console.log(`
7088
7433
  <claude-instructions>
7089
7434
  # Sisyphus Getting Started \u2014 Step 4: Demo Session
@@ -7125,26 +7470,29 @@ Have the user confirm they understand these keybinds before proceeding.
7125
7470
 
7126
7471
  ### 3. Set expectations, copy demo app, and launch
7127
7472
 
7128
- First, copy the demo todo app to a temp directory and init a git repo (sisyphus needs git):
7473
+ 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:
7129
7474
  \`\`\`
7130
- rm -rf /tmp/sisyphus-tutorial-demo
7131
- cp -r ${templatePath("tutorial-demo")} /tmp/sisyphus-tutorial-demo
7132
- git -C /tmp/sisyphus-tutorial-demo init
7133
- git -C /tmp/sisyphus-tutorial-demo add -A
7134
- git -C /tmp/sisyphus-tutorial-demo commit -m "Initial todo app"
7475
+ mkdir -p ${join20(process.cwd(), "tmp")}
7476
+ rm -rf ${demoPath}
7477
+ cp -r ${templatePath("tutorial-demo")} ${demoPath}
7478
+ git -C ${demoPath} init
7479
+ git -C ${demoPath} add -A
7480
+ git -C ${demoPath} commit -m "Initial todo app"
7135
7481
  \`\`\`
7136
7482
 
7137
7483
  Tell the user:
7138
7484
 
7139
- > I've set up a small todo app in /tmp/sisyphus-tutorial-demo \u2014 a Node.js API
7140
- > with a few files. I'm going to launch sisyphus on it. Here's what will happen:
7485
+ > I've set up a small todo app in ${demoPath} \u2014 a Node.js API
7486
+ > with a few files. It lives under \`tmp/\` in your current directory so sisyphus
7487
+ > runs somewhere you can see, not in system /tmp. I'm going to launch sisyphus
7488
+ > on it. Here's what will happen:
7141
7489
  > 1. The dashboard opens automatically (you'll be switched to it)
7142
7490
  > 2. Press **Ctrl+p** to come back here to Claude \u2014 I'll guide you through what to watch
7143
7491
  > 3. The session takes a few minutes. You can watch agents work live!
7144
7492
 
7145
- Then launch from the demo directory:
7493
+ Then launch from the demo directory (sisyphus operates in whichever directory you launch it from, so we \`cd\` in so the \`.sisyphus/\` state lands inside the demo):
7146
7494
  \`\`\`
7147
- cd /tmp/sisyphus-tutorial-demo && sis start "Add three improvements to this todo app: (1) add a priority field (high/medium/low) to todos, (2) add a GET /todos/stats endpoint that returns counts of total/done/pending todos, (3) add tests for the new features. Explain your thinking at each step." -c "TUTORIAL DEMO: A user is watching this session to learn how sisyphus works. Be EXTRA VERBOSE \u2014 explain your reasoning, narrate what you're doing, and make your planning visible. When spawning agents, give each agent context that this is a tutorial demo and they should explain their work clearly. Keep scope small: 2-3 agents, 1-2 cycles."
7495
+ cd ${demoPath} && sis start "Add three improvements to this todo app: (1) add a priority field (high/medium/low) to todos, (2) add a GET /todos/stats endpoint that returns counts of total/done/pending todos, (3) add tests for the new features. Explain your thinking at each step." -c "TUTORIAL DEMO: A user is watching this session to learn how sisyphus works. Be EXTRA VERBOSE \u2014 explain your reasoning, narrate what you're doing, and make your planning visible. When spawning agents, give each agent context that this is a tutorial demo and they should explain their work clearly. Keep scope small: 2-3 agents, 1-2 cycles."
7148
7496
  \`\`\`
7149
7497
 
7150
7498
  After launching, tell them:
@@ -7187,8 +7535,8 @@ Between polls, encourage the user to explore:
7187
7535
 
7188
7536
  Once the session shows "completed":
7189
7537
 
7190
- - Show them what the agents built: \`cd /tmp/sisyphus-tutorial-demo && git log --oneline\`
7191
- - Run the tests to prove the work: \`cd /tmp/sisyphus-tutorial-demo && node --test test.js\`
7538
+ - Show them what the agents built: \`cd ${demoPath} && git log --oneline\`
7539
+ - Run the tests to prove the work: \`cd ${demoPath} && node --test test.js\`
7192
7540
  - Show the session artifacts: find the session dir in \`.sisyphus/sessions/\` and show \`roadmap.md\`
7193
7541
  - Explain: "Every session creates a roadmap, agent reports, and logs \u2014 all stored in .sisyphus/sessions/"
7194
7542
 
@@ -7202,20 +7550,26 @@ sis admin getting-started --tutorial 5
7202
7550
  `);
7203
7551
  }
7204
7552
  function printStep5() {
7553
+ const demoPath = join20(process.cwd(), "tmp", "sisyphus-tutorial-demo");
7554
+ const demoExists = existsSync21(demoPath);
7205
7555
  let recentCommits = "";
7206
7556
  let topLevelFiles = "";
7207
7557
  try {
7208
- recentCommits = execSync15("git log --oneline -15 2>/dev/null", { encoding: "utf-8" }).trim();
7558
+ recentCommits = execSync16("git log --oneline -15 2>/dev/null", { encoding: "utf-8" }).trim();
7209
7559
  } catch {
7210
7560
  }
7211
7561
  try {
7212
- topLevelFiles = execSync15("ls -1 2>/dev/null", { encoding: "utf-8" }).trim();
7562
+ topLevelFiles = execSync16("ls -1 2>/dev/null", { encoding: "utf-8" }).trim();
7213
7563
  } catch {
7214
7564
  }
7215
7565
  console.log(`
7216
7566
  <claude-instructions>
7217
7567
  # Sisyphus Getting Started \u2014 Step 5: What's Next
7218
7568
 
7569
+ ## Environment Data
7570
+ - demoPath: ${demoPath}
7571
+ - demoExists: ${demoExists}
7572
+
7219
7573
  ## Codebase Context
7220
7574
  <recent-commits>
7221
7575
  ${recentCommits || "(no git repo detected)"}
@@ -7278,14 +7632,38 @@ Or directly: \`sis start "your task" -c "any background context"\`
7278
7632
 
7279
7633
  Look at the recent commits and top-level files above. Based on what you can see of their project, suggest 2-3 concrete sisyphus-scale tasks they could try. Be specific to their codebase \u2014 reference actual directories, patterns, or areas you can see.
7280
7634
 
7281
- If there are no commits or files (e.g., they ran this from /tmp), skip this section.
7635
+ If there are no commits or files (e.g., they ran this from an empty scratch dir, or from the tutorial demo dir itself at tmp/sisyphus-tutorial-demo), skip this section.
7282
7636
 
7283
7637
  Format as:
7284
7638
  > Based on your codebase, here are some tasks sisyphus would be great for:
7285
7639
  > - "..."
7286
7640
  > - "..."
7287
7641
 
7288
- ### 5. There's more to learn
7642
+ ### 5. Clean up the demo dir
7643
+
7644
+ **Only if demoExists is true** (see Environment Data above). The tutorial dropped a
7645
+ demo todo app at \`${demoPath}\` with its own \`.sisyphus/\` session state. Ask the user
7646
+ before deleting \u2014 they may want to poke around the session files first.
7647
+
7648
+ Ask exactly:
7649
+
7650
+ > Want me to remove the tutorial demo at \`${demoPath}\`? It's safe to delete \u2014 it
7651
+ > only contains the demo todo app and the session sisyphus just ran on it.
7652
+
7653
+ If they say yes, run:
7654
+ \`\`\`
7655
+ rm -rf ${demoPath}
7656
+ \`\`\`
7657
+
7658
+ Then check whether \`${join20(process.cwd(), "tmp")}\` is now empty, and if so remove it too:
7659
+ \`\`\`
7660
+ rmdir ${join20(process.cwd(), "tmp")} 2>/dev/null || true
7661
+ \`\`\`
7662
+
7663
+ If they say no or want to explore first, leave it. They can clean up later with the same
7664
+ \`rm -rf\` command.
7665
+
7666
+ ### 6. There's more to learn
7289
7667
 
7290
7668
  Tell them:
7291
7669
 
@@ -7687,22 +8065,10 @@ function registerGettingStarted(program2) {
7687
8065
 
7688
8066
  // src/cli/commands/history.ts
7689
8067
  init_paths();
7690
- import { readdirSync as readdirSync6, readFileSync as readFileSync22, existsSync as existsSync20 } from "fs";
8068
+ import { readdirSync as readdirSync6, readFileSync as readFileSync23, existsSync as existsSync22 } from "fs";
7691
8069
  import { resolve as resolve6 } from "path";
7692
- var RESET3 = "\x1B[0m";
7693
- var BOLD3 = "\x1B[1m";
7694
- var DIM3 = "\x1B[2m";
7695
- var COLOR = {
7696
- green: "\x1B[32m",
7697
- yellow: "\x1B[33m",
7698
- cyan: "\x1B[36m",
7699
- red: "\x1B[31m",
7700
- gray: "\x1B[90m",
7701
- white: "\x1B[37m",
7702
- magenta: "\x1B[35m"
7703
- };
7704
8070
  function c(color, text) {
7705
- return `${COLOR[color] ?? ""}${text}${RESET3}`;
8071
+ return colorize(text, color);
7706
8072
  }
7707
8073
  var INTERACTIVE_AGENT_TYPES = /* @__PURE__ */ new Set([
7708
8074
  "sisyphus:requirements",
@@ -7727,13 +8093,13 @@ function splitAgentTime(agents) {
7727
8093
  }
7728
8094
  function loadAllSummaries() {
7729
8095
  const base = historyBaseDir();
7730
- if (!existsSync20(base)) return [];
8096
+ if (!existsSync22(base)) return [];
7731
8097
  const results = [];
7732
8098
  for (const name of readdirSync6(base)) {
7733
8099
  const summaryPath = historySessionSummaryPath(name);
7734
- if (existsSync20(summaryPath)) {
8100
+ if (existsSync22(summaryPath)) {
7735
8101
  try {
7736
- const raw = readFileSync22(summaryPath, "utf-8");
8102
+ const raw = readFileSync23(summaryPath, "utf-8");
7737
8103
  results.push({ id: name, summary: JSON.parse(raw) });
7738
8104
  continue;
7739
8105
  } catch {
@@ -7747,10 +8113,10 @@ function loadAllSummaries() {
7747
8113
  }
7748
8114
  function buildLiveSummary(sessionId) {
7749
8115
  const eventsPath = historyEventsPath(sessionId);
7750
- if (!existsSync20(eventsPath)) return null;
8116
+ if (!existsSync22(eventsPath)) return null;
7751
8117
  let cwd = null;
7752
8118
  try {
7753
- const lines = readFileSync22(eventsPath, "utf-8").split("\n");
8119
+ const lines = readFileSync23(eventsPath, "utf-8").split("\n");
7754
8120
  for (const line of lines) {
7755
8121
  if (!line.trim()) continue;
7756
8122
  try {
@@ -7768,10 +8134,10 @@ function buildLiveSummary(sessionId) {
7768
8134
  }
7769
8135
  if (!cwd) return null;
7770
8136
  const sPath = statePath(cwd, sessionId);
7771
- if (!existsSync20(sPath)) return null;
8137
+ if (!existsSync22(sPath)) return null;
7772
8138
  let session2;
7773
8139
  try {
7774
- session2 = JSON.parse(readFileSync22(sPath, "utf-8"));
8140
+ session2 = JSON.parse(readFileSync23(sPath, "utf-8"));
7775
8141
  } catch {
7776
8142
  return null;
7777
8143
  }
@@ -7832,8 +8198,8 @@ function buildLiveSummary(sessionId) {
7832
8198
  }
7833
8199
  function loadEvents(sessionId) {
7834
8200
  const eventsPath = historyEventsPath(sessionId);
7835
- if (!existsSync20(eventsPath)) return [];
7836
- const lines = readFileSync22(eventsPath, "utf-8").split("\n").filter((l) => l.trim());
8201
+ if (!existsSync22(eventsPath)) return [];
8202
+ const lines = readFileSync23(eventsPath, "utf-8").split("\n").filter((l) => l.trim());
7837
8203
  const events = [];
7838
8204
  for (const line of lines) {
7839
8205
  try {
@@ -7846,13 +8212,13 @@ function loadEvents(sessionId) {
7846
8212
  }
7847
8213
  function findSession(idOrName) {
7848
8214
  const summaryPath = historySessionSummaryPath(idOrName);
7849
- if (existsSync20(summaryPath)) {
8215
+ if (existsSync22(summaryPath)) {
7850
8216
  try {
7851
- return { id: idOrName, summary: JSON.parse(readFileSync22(summaryPath, "utf-8")) };
8217
+ return { id: idOrName, summary: JSON.parse(readFileSync23(summaryPath, "utf-8")) };
7852
8218
  } catch {
7853
8219
  }
7854
8220
  }
7855
- if (existsSync20(historySessionDir(idOrName))) {
8221
+ if (existsSync22(historySessionDir(idOrName))) {
7856
8222
  const live = buildLiveSummary(idOrName);
7857
8223
  if (live) return { id: idOrName, summary: live };
7858
8224
  }
@@ -7920,9 +8286,9 @@ function listSessions(opts) {
7920
8286
  const agents = s.agentCount > 0 ? `${s.agentCount} agents` : "";
7921
8287
  const cycles = s.cycleCount > 0 ? `${s.cycleCount} cycles` : "";
7922
8288
  const meta = [agents, cycles, dur].filter(Boolean).join(", ");
7923
- console.log(`${date} ${status} ${name} ${DIM3}${meta}${RESET3} ${proj}`);
8289
+ console.log(`${date} ${status} ${name} ${dim(meta)} ${proj}`);
7924
8290
  const taskPreview = s.task.length > 100 ? s.task.slice(0, 100) + "..." : s.task;
7925
- console.log(` ${DIM3}${taskPreview}${RESET3}`);
8291
+ console.log(` ${dim(taskPreview)}`);
7926
8292
  console.log("");
7927
8293
  }
7928
8294
  const total = loadAllSummaries().length;
@@ -7961,54 +8327,54 @@ function showSession(idOrName, opts) {
7961
8327
  }
7962
8328
  const inProgress = s.completedAt == null;
7963
8329
  const inProgressTag = inProgress ? ` ${c("yellow", "(in progress)")}` : "";
7964
- console.log(`${BOLD3}${s.name ?? s.sessionId.slice(0, 8)}${RESET3} ${fmtStatus(s.status)}${inProgressTag}`);
7965
- console.log(`${DIM3}ID:${RESET3} ${s.sessionId}`);
7966
- console.log(`${DIM3}Project:${RESET3} ${s.cwd}`);
7967
- console.log(`${DIM3}Model:${RESET3} ${s.model ?? "default"}`);
7968
- console.log(`${DIM3}Started:${RESET3} ${fmtDate(s.startedAt)}`);
7969
- console.log(`${DIM3}Ended:${RESET3} ${s.completedAt ? fmtDate(s.completedAt) : c("gray", "\u2014 still running")}`);
8330
+ console.log(`${bold(s.name ?? s.sessionId.slice(0, 8))} ${fmtStatus(s.status)}${inProgressTag}`);
8331
+ console.log(`${dim("ID:")} ${s.sessionId}`);
8332
+ console.log(`${dim("Project:")} ${s.cwd}`);
8333
+ console.log(`${dim("Model:")} ${s.model ?? "default"}`);
8334
+ console.log(`${dim("Started:")} ${fmtDate(s.startedAt)}`);
8335
+ console.log(`${dim("Ended:")} ${s.completedAt ? fmtDate(s.completedAt) : c("gray", "\u2014 still running")}`);
7970
8336
  const { computeMs, interactiveMs } = splitAgentTime(s.agents);
7971
8337
  const wallStr = s.wallClockMs ? formatDuration(s.wallClockMs) : "\u2014";
7972
- console.log(`${DIM3}Active:${RESET3} ${formatDuration(s.activeMs)} ${DIM3}Wall:${RESET3} ${wallStr}`);
8338
+ console.log(`${dim("Active:")} ${formatDuration(s.activeMs)} ${dim("Wall:")} ${wallStr}`);
7973
8339
  if (interactiveMs > 0) {
7974
- console.log(`${DIM3}Compute:${RESET3} ${formatDuration(computeMs)} ${DIM3}Interactive:${RESET3} ${formatDuration(interactiveMs)} ${DIM3}(TUI wait time, not compute)${RESET3}`);
8340
+ console.log(`${dim("Compute:")} ${formatDuration(computeMs)} ${dim("Interactive:")} ${formatDuration(interactiveMs)} ${dim("(TUI wait time, not compute)")}`);
7975
8341
  }
7976
8342
  if (s.userBlockedMs > 0) {
7977
- console.log(`${DIM3}Waiting on user:${RESET3} ${formatDuration(s.userBlockedMs)} ${DIM3}(blocked on sis ask, not compute)${RESET3}`);
8343
+ console.log(`${dim("Waiting on user:")} ${formatDuration(s.userBlockedMs)} ${dim("(blocked on sis ask, not compute)")}`);
7978
8344
  }
7979
8345
  console.log("");
7980
- console.log(`${BOLD3}Task${RESET3}`);
8346
+ console.log(bold("Task"));
7981
8347
  console.log(s.task);
7982
8348
  console.log("");
7983
8349
  if (s.context) {
7984
- console.log(`${BOLD3}Context${RESET3}`);
8350
+ console.log(bold("Context"));
7985
8351
  console.log(s.context.length > 500 ? s.context.slice(0, 500) + "..." : s.context);
7986
8352
  console.log("");
7987
8353
  }
7988
8354
  if (s.agents.length > 0) {
7989
- console.log(`${BOLD3}Agents${RESET3} (${s.agents.length})`);
8355
+ console.log(`${bold("Agents")} (${s.agents.length})`);
7990
8356
  for (const a of s.agents) {
7991
8357
  const name = a.nickname ? `${a.name} "${a.nickname}"` : a.name;
7992
8358
  const type = a.agentType ? c("gray", ` [${a.agentType}]`) : "";
7993
8359
  const interactive = isInteractiveAgent(a.agentType) ? c("yellow", " (interactive)") : "";
7994
8360
  const blocked = a.userBlockedMs ?? 0;
7995
- const waiting = blocked > 0 ? ` ${DIM3}\xB7 ${formatDuration(blocked)} waiting${RESET3}` : "";
7996
- console.log(` ${fmtStatus(a.status)} ${name}${type}${interactive} ${DIM3}${formatDuration(a.activeMs)}${RESET3}${waiting}`);
8361
+ const waiting = blocked > 0 ? ` ${dim(`\xB7 ${formatDuration(blocked)} waiting`)}` : "";
8362
+ console.log(` ${fmtStatus(a.status)} ${name}${type}${interactive} ${dim(formatDuration(a.activeMs))}${waiting}`);
7997
8363
  }
7998
8364
  console.log("");
7999
8365
  }
8000
8366
  if (s.cycles.length > 0) {
8001
- console.log(`${BOLD3}Cycles${RESET3} (${s.cycles.length})`);
8367
+ console.log(`${bold("Cycles")} (${s.cycles.length})`);
8002
8368
  for (const cy of s.cycles) {
8003
8369
  const mode = cy.mode ? c("magenta", cy.mode) : "";
8004
8370
  const blocked = cy.userBlockedMs ?? 0;
8005
- const waiting = blocked > 0 ? ` ${DIM3}\xB7 ${formatDuration(blocked)} waiting${RESET3}` : "";
8006
- console.log(` ${DIM3}#${cy.cycle}${RESET3} ${mode} ${cy.agentsSpawned} agents ${DIM3}${formatDuration(cy.activeMs)}${RESET3}${waiting}`);
8371
+ const waiting = blocked > 0 ? ` ${dim(`\xB7 ${formatDuration(blocked)} waiting`)}` : "";
8372
+ console.log(` ${dim(`#${cy.cycle}`)} ${mode} ${cy.agentsSpawned} agents ${dim(formatDuration(cy.activeMs))}${waiting}`);
8007
8373
  }
8008
8374
  console.log("");
8009
8375
  }
8010
8376
  if (s.messages.length > 0) {
8011
- console.log(`${BOLD3}Messages${RESET3} (${s.messages.length})`);
8377
+ console.log(`${bold("Messages")} (${s.messages.length})`);
8012
8378
  for (const m of s.messages) {
8013
8379
  const src = c("gray", m.source);
8014
8380
  const preview = m.content.length > 120 ? m.content.slice(0, 120) + "..." : m.content;
@@ -8017,12 +8383,12 @@ function showSession(idOrName, opts) {
8017
8383
  console.log("");
8018
8384
  }
8019
8385
  if (s.completionReport) {
8020
- console.log(`${BOLD3}Completion Report${RESET3}`);
8386
+ console.log(bold("Completion Report"));
8021
8387
  console.log(s.completionReport);
8022
8388
  console.log("");
8023
8389
  }
8024
8390
  if (s.achievements.length > 0) {
8025
- console.log(`${BOLD3}Achievements Unlocked${RESET3}`);
8391
+ console.log(bold("Achievements Unlocked"));
8026
8392
  console.log(` ${s.achievements.join(", ")}`);
8027
8393
  console.log("");
8028
8394
  }
@@ -8035,7 +8401,7 @@ function showSession(idOrName, opts) {
8035
8401
  `hour ${sig.hourOfDay}`,
8036
8402
  sig.idleDurationMs > 6e4 ? `idle ${formatDuration(sig.idleDurationMs)}` : null
8037
8403
  ].filter(Boolean);
8038
- console.log(`${DIM3}Final signals: ${parts.join(" \xB7 ")}${RESET3}`);
8404
+ console.log(dim(`Final signals: ${parts.join(" \xB7 ")}`));
8039
8405
  }
8040
8406
  }
8041
8407
  function formatEventData(e) {
@@ -8046,11 +8412,11 @@ function formatEventData(e) {
8046
8412
  case "session-named":
8047
8413
  return c("white", d.name);
8048
8414
  case "agent-spawned":
8049
- return `${c("white", d.agentId)} ${d.agentType ?? ""} ${DIM3}${d.instruction?.slice(0, 60) ?? ""}...${RESET3}`;
8415
+ return `${c("white", d.agentId)} ${d.agentType ?? ""} ${dim(`${d.instruction?.slice(0, 60) ?? ""}...`)}`;
8050
8416
  case "agent-nicknamed":
8051
8417
  return `${d.agentId} "${c("white", d.nickname)}"`;
8052
8418
  case "agent-completed":
8053
- return `${d.agentId} ${DIM3}${formatDuration(d.activeMs)}${RESET3} ${DIM3}${d.reportSummary?.slice(0, 60) ?? ""}${RESET3}`;
8419
+ return `${d.agentId} ${dim(formatDuration(d.activeMs))} ${dim(d.reportSummary?.slice(0, 60) ?? "")}`;
8054
8420
  case "agent-exited":
8055
8421
  return `${d.agentId} ${fmtStatus(d.status)} ${d.reason ?? ""}`;
8056
8422
  case "cycle-boundary":
@@ -8066,15 +8432,15 @@ function formatEventData(e) {
8066
8432
  return `${c("cyan", d.type)} ${c("gray", fileParts)}`;
8067
8433
  }
8068
8434
  case "agent-killed":
8069
- return `${d.agentId} ${fmtStatus(d.status)} ${DIM3}${formatDuration(d.activeMs)}${RESET3} ${d.reason ?? ""}`;
8435
+ return `${d.agentId} ${fmtStatus(d.status)} ${dim(formatDuration(d.activeMs))} ${d.reason ?? ""}`;
8070
8436
  case "agent-restarted":
8071
- return `${d.agentId} restart #${d.restartCount} ${DIM3}was ${d.previousStatus}${RESET3}`;
8437
+ return `${d.agentId} restart #${d.restartCount} ${dim(`was ${d.previousStatus}`)}`;
8072
8438
  case "rollback":
8073
8439
  return `cycle ${d.fromCycle} \u2192 ${d.toCycle} ${d.killedAgentCount} agents killed`;
8074
8440
  case "session-resumed":
8075
8441
  return `was ${fmtStatus(d.previousStatus)} ${d.lostAgentCount} agents lost`;
8076
8442
  case "session-continued":
8077
- return `${d.cycleCount} cycles ${DIM3}${formatDuration(d.activeMs)}${RESET3}`;
8443
+ return `${d.cycleCount} cycles ${dim(formatDuration(d.activeMs))}`;
8078
8444
  case "session-end":
8079
8445
  return `${fmtStatus(d.status)} ${formatDuration(d.activeMs)} ${d.agentCount} agents ${d.cycleCount} cycles`;
8080
8446
  default:
@@ -8172,29 +8538,29 @@ function showStats(opts) {
8172
8538
  }, null, 2));
8173
8539
  return;
8174
8540
  }
8175
- console.log(`${BOLD3}Session History Stats${RESET3}`);
8541
+ console.log(bold("Session History Stats"));
8176
8542
  console.log("");
8177
- console.log(` ${BOLD3}Sessions:${RESET3} ${sessions.length} total ${c("cyan", `${completed.length} completed`)} ${c("red", `${killed.length} killed`)}`);
8178
- const timeLine = ` ${BOLD3}Time:${RESET3} ${formatDuration(totalActiveMs)} total ${formatDuration(avgMs)} avg` + (p50Ms != null && p90Ms != null ? ` ${DIM3}p50=${formatDuration(p50Ms)} p90=${formatDuration(p90Ms)}${RESET3}` : "");
8543
+ console.log(` ${bold("Sessions:")} ${sessions.length} total ${c("cyan", `${completed.length} completed`)} ${c("red", `${killed.length} killed`)}`);
8544
+ const timeLine = ` ${bold("Time:")} ${formatDuration(totalActiveMs)} total ${formatDuration(avgMs)} avg` + (p50Ms != null && p90Ms != null ? ` ${dim(`p50=${formatDuration(p50Ms)} p90=${formatDuration(p90Ms)}`)}` : "");
8179
8545
  console.log(timeLine);
8180
8546
  if (avgEfficiency != null) {
8181
8547
  const effColor = avgEfficiency >= 0.7 ? "green" : avgEfficiency >= 0.4 ? "yellow" : "red";
8182
- console.log(` ${BOLD3}Efficiency:${RESET3} ${c(effColor, (avgEfficiency * 100).toFixed(1) + "%")} ${DIM3}(${efficiencyValues.length} sessions with data)${RESET3}`);
8548
+ console.log(` ${bold("Efficiency:")} ${c(effColor, (avgEfficiency * 100).toFixed(1) + "%")} ${dim(`(${efficiencyValues.length} sessions with data)`)}`);
8183
8549
  }
8184
- console.log(` ${BOLD3}Agents:${RESET3} ${totalAgents} spawned (${(totalAgents / sessions.length).toFixed(1)} avg/session)`);
8185
- console.log(` ${BOLD3}Cycles:${RESET3} ${totalCycles} total (${(totalCycles / sessions.length).toFixed(1)} avg/session)`);
8186
- console.log(` ${BOLD3}Messages:${RESET3} ${totalMessages} total`);
8550
+ console.log(` ${bold("Agents:")} ${totalAgents} spawned (${(totalAgents / sessions.length).toFixed(1)} avg/session)`);
8551
+ console.log(` ${bold("Cycles:")} ${totalCycles} total (${(totalCycles / sessions.length).toFixed(1)} avg/session)`);
8552
+ console.log(` ${bold("Messages:")} ${totalMessages} total`);
8187
8553
  console.log("");
8188
- console.log(`${BOLD3}By Project${RESET3}`);
8554
+ console.log(bold("By Project"));
8189
8555
  const sorted = [...byProject.entries()].sort((a, b) => b[1].count - a[1].count);
8190
8556
  for (const [proj, data] of sorted) {
8191
8557
  console.log(` ${c("gray", fmtProject(proj))} ${data.count} sessions ${formatDuration(data.activeMs)} ${data.agents} agents`);
8192
8558
  }
8193
8559
  if (agentTypeMap.size > 0) {
8194
8560
  console.log("");
8195
- console.log(`${BOLD3}By Agent Type${RESET3}`);
8561
+ console.log(bold("By Agent Type"));
8196
8562
  const typeHeader = ` ${"Type".padEnd(20)} ${"Count".padStart(6)} ${"Avg Time".padStart(10)} ${"Crash %".padStart(8)} ${"Done %".padStart(8)}`;
8197
- console.log(`${DIM3}${typeHeader}${RESET3}`);
8563
+ console.log(dim(typeHeader));
8198
8564
  const sortedTypes = [...agentTypeMap.entries()].sort((a, b) => b[1].count - a[1].count);
8199
8565
  for (const [type, data] of sortedTypes) {
8200
8566
  const avgTime = formatDuration(data.totalMs / data.count);
@@ -8206,12 +8572,12 @@ function showStats(opts) {
8206
8572
  }
8207
8573
  if (sessions.length >= 5) {
8208
8574
  console.log("");
8209
- console.log(`${BOLD3}Temporal Patterns${RESET3}`);
8575
+ console.log(bold("Temporal Patterns"));
8210
8576
  const topBlocks = [...hourBlocks.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
8211
- console.log(` ${DIM3}Busiest times:${RESET3} ${topBlocks.map(([label, count]) => `${label} (${count})`).join(" ")}`);
8577
+ console.log(` ${dim("Busiest times:")} ${topBlocks.map(([label, count]) => `${label} (${count})`).join(" ")}`);
8212
8578
  const dayOrder = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
8213
8579
  const dayParts = dayOrder.map((d) => `${d} ${dayCounts.get(d) ?? 0}`);
8214
- console.log(` ${DIM3}By day:${RESET3} ${dayParts.join(" ")}`);
8580
+ console.log(` ${dim("By day:")} ${dayParts.join(" ")}`);
8215
8581
  }
8216
8582
  }
8217
8583
  function registerHistory(program2) {
@@ -8241,7 +8607,7 @@ function registerHistory(program2) {
8241
8607
  init_paths();
8242
8608
  import { execFile as execFile2 } from "child_process";
8243
8609
  import { promisify } from "util";
8244
- import { existsSync as existsSync21, readFileSync as readFileSync23, mkdirSync as mkdirSync10, symlinkSync, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "fs";
8610
+ import { existsSync as existsSync23, readFileSync as readFileSync24, mkdirSync as mkdirSync10, symlinkSync, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "fs";
8245
8611
  import { homedir as homedir12 } from "os";
8246
8612
  import { join as join22 } from "path";
8247
8613
  function sanitizeName(name) {
@@ -8253,7 +8619,7 @@ function buildOutputPath(label, dir) {
8253
8619
  const base = `sisyphus-${label}-${date}`;
8254
8620
  let candidate = join22(dir, `${base}.zip`);
8255
8621
  let counter = 1;
8256
- while (existsSync21(candidate)) {
8622
+ while (existsSync23(candidate)) {
8257
8623
  counter++;
8258
8624
  candidate = join22(dir, `${base}-${counter}.zip`);
8259
8625
  }
@@ -8317,16 +8683,16 @@ async function exportSessionToZip(sessionId, cwd, options) {
8317
8683
  const reveal = options?.reveal ?? true;
8318
8684
  const sessDir = sessionDir(cwd, sessionId);
8319
8685
  const histDir = historySessionDir(sessionId);
8320
- const sessExists = existsSync21(sessDir);
8321
- const histExists = existsSync21(histDir);
8686
+ const sessExists = existsSync23(sessDir);
8687
+ const histExists = existsSync23(histDir);
8322
8688
  if (!sessExists && !histExists) {
8323
8689
  throw new Error(`No data found for session ${sessionId}`);
8324
8690
  }
8325
8691
  let label = sessionId.slice(0, 8);
8326
8692
  const stPath = statePath(cwd, sessionId);
8327
- if (existsSync21(stPath)) {
8693
+ if (existsSync23(stPath)) {
8328
8694
  try {
8329
- const state = JSON.parse(readFileSync23(stPath, "utf-8"));
8695
+ const state = JSON.parse(readFileSync24(stPath, "utf-8"));
8330
8696
  if (state.name) {
8331
8697
  label = sanitizeName(state.name);
8332
8698
  }
@@ -8465,12 +8831,12 @@ function buildManifest(args2) {
8465
8831
  }
8466
8832
 
8467
8833
  // src/shared/version.ts
8468
- import { readFileSync as readFileSync24 } from "fs";
8834
+ import { readFileSync as readFileSync25 } from "fs";
8469
8835
  import { resolve as resolve7 } from "path";
8470
8836
  function readSisyphusVersion() {
8471
8837
  for (const rel of ["../package.json", "../../package.json"]) {
8472
8838
  try {
8473
- const raw = readFileSync24(resolve7(import.meta.dirname, rel), "utf-8");
8839
+ const raw = readFileSync25(resolve7(import.meta.dirname, rel), "utf-8");
8474
8840
  const pkg = JSON.parse(raw);
8475
8841
  if (pkg.name === "sisyphi" && pkg.version) return pkg.version;
8476
8842
  } catch {
@@ -8565,13 +8931,13 @@ function registerUpload(program2) {
8565
8931
  }
8566
8932
 
8567
8933
  // src/cli/commands/scratch.ts
8568
- import { execSync as execSync16 } from "child_process";
8934
+ import { execSync as execSync17 } from "child_process";
8569
8935
  init_shell();
8570
8936
  function findHomeSession(cwd) {
8571
8937
  const normalizedCwd = cwd.replace(/\/+$/, "");
8572
8938
  let output;
8573
8939
  try {
8574
- output = execSync16('tmux list-sessions -F "#{session_id}|#{session_name}"', {
8940
+ output = execSync17('tmux list-sessions -F "#{session_id}|#{session_name}"', {
8575
8941
  encoding: "utf-8",
8576
8942
  stdio: ["pipe", "pipe", "pipe"]
8577
8943
  }).trim();
@@ -8585,7 +8951,7 @@ function findHomeSession(cwd) {
8585
8951
  const name = line.slice(pipeIdx + 1);
8586
8952
  if (name.startsWith("ssyph_")) continue;
8587
8953
  try {
8588
- const val = execSync16(
8954
+ const val = execSync17(
8589
8955
  `tmux show-options -t ${shellQuote(sessId)} -v @sisyphus_cwd`,
8590
8956
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
8591
8957
  ).trim();
@@ -8601,7 +8967,7 @@ function registerScratch(program2) {
8601
8967
  const cwd = opts.cwd ?? process.env["SISYPHUS_CWD"] ?? process.cwd();
8602
8968
  const homeSession = findHomeSession(cwd);
8603
8969
  if (!homeSession) {
8604
- const current = execSync16('tmux display-message -p "#{session_name}"', {
8970
+ const current = execSync17('tmux display-message -p "#{session_name}"', {
8605
8971
  encoding: "utf-8"
8606
8972
  }).trim();
8607
8973
  openScratchWindow(current, cwd, promptParts.join(" "));
@@ -8611,7 +8977,7 @@ function registerScratch(program2) {
8611
8977
  });
8612
8978
  }
8613
8979
  function openScratchWindow(tmuxSession, cwd, prompt) {
8614
- const windowId = execSync16(
8980
+ const windowId = execSync17(
8615
8981
  `tmux new-window -t ${shellQuote(tmuxSession + ":")} -n "scratch" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
8616
8982
  { encoding: "utf-8" }
8617
8983
  ).trim();
@@ -8619,7 +8985,7 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
8619
8985
  if (prompt) {
8620
8986
  cmd += ` -p ${shellQuote(prompt)}`;
8621
8987
  }
8622
- execSync16(
8988
+ execSync17(
8623
8989
  `tmux send-keys -t ${shellQuote(windowId)} ${shellQuote(cmd)} Enter`
8624
8990
  );
8625
8991
  console.log(`Scratch session opened in ${tmuxSession}`);
@@ -8628,7 +8994,7 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
8628
8994
  // src/cli/commands/review.ts
8629
8995
  init_paths();
8630
8996
  import { join as join23, resolve as resolve8, dirname as dirname8 } from "path";
8631
- import { existsSync as existsSync22, readFileSync as readFileSync25, writeFileSync as writeFileSync12, renameSync as renameSync3, readdirSync as readdirSync7 } from "fs";
8997
+ import { existsSync as existsSync24, readFileSync as readFileSync26, writeFileSync as writeFileSync12, renameSync as renameSync3, readdirSync as readdirSync7 } from "fs";
8632
8998
  var _statusCheck = ["draft", "question", "approved", "rejected", "deferred"];
8633
8999
  function resolveContextArtifact(file, opts, filename, notFoundMessage) {
8634
9000
  const cwd = opts.cwd || process.env.SISYPHUS_CWD || process.cwd();
@@ -8636,18 +9002,18 @@ function resolveContextArtifact(file, opts, filename, notFoundMessage) {
8636
9002
  const sessionId = opts.sessionId || process.env.SISYPHUS_SESSION_ID;
8637
9003
  if (sessionId) {
8638
9004
  const target = join23(contextDir(cwd, sessionId), filename);
8639
- if (!existsSync22(target)) {
9005
+ if (!existsSync24(target)) {
8640
9006
  console.error(`Error: File not found: ${target}`);
8641
9007
  process.exit(1);
8642
9008
  }
8643
9009
  return target;
8644
9010
  }
8645
9011
  const dir = sessionsDir(cwd);
8646
- if (existsSync22(dir)) {
9012
+ if (existsSync24(dir)) {
8647
9013
  const sessions = readdirSync7(dir);
8648
9014
  for (const session2 of sessions.reverse()) {
8649
9015
  const candidate = join23(dir, session2, "context", filename);
8650
- if (existsSync22(candidate)) return candidate;
9016
+ if (existsSync24(candidate)) return candidate;
8651
9017
  }
8652
9018
  }
8653
9019
  console.error(`Error: ${notFoundMessage}`);
@@ -8689,16 +9055,16 @@ Examples:
8689
9055
  "requirements.json",
8690
9056
  "No requirements.json found. Provide a path or use --session-id."
8691
9057
  );
8692
- if (!existsSync22(targetPath)) {
9058
+ if (!existsSync24(targetPath)) {
8693
9059
  console.error(`Error: File not found: ${targetPath}`);
8694
9060
  process.exit(1);
8695
9061
  }
8696
- const parsed = JSON.parse(readFileSync25(targetPath, "utf-8"));
9062
+ const parsed = JSON.parse(readFileSync26(targetPath, "utf-8"));
8697
9063
  const rendered = renderRequirementsMarkdown(parsed);
8698
9064
  const outPath = join23(dirname8(targetPath), "requirements.md");
8699
9065
  const tmpPath = outPath + ".tmp";
8700
- if (existsSync22(outPath)) {
8701
- const existing = readFileSync25(outPath, "utf-8");
9066
+ if (existsSync24(outPath)) {
9067
+ const existing = readFileSync26(outPath, "utf-8");
8702
9068
  if (existing !== rendered) {
8703
9069
  if (!opts.force) {
8704
9070
  process.stderr.write(
@@ -9079,7 +9445,7 @@ function renderRequirementsMarkdown(json) {
9079
9445
 
9080
9446
  // src/cli/commands/companion.ts
9081
9447
  import { basename as basename5, dirname as dirname11, join as join28 } from "path";
9082
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync30, writeFileSync as writeFileSync17 } from "fs";
9448
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync31, writeFileSync as writeFileSync17 } from "fs";
9083
9449
  init_paths();
9084
9450
 
9085
9451
  // src/shared/companion-render.ts
@@ -9270,7 +9636,7 @@ var MOOD_COLORS = {
9270
9636
  function getMoodTmuxColor(mood) {
9271
9637
  return MOOD_COLORS[mood].tmux;
9272
9638
  }
9273
- function colorize2(text, mood, tmux) {
9639
+ function colorize3(text, mood, tmux) {
9274
9640
  const { ansi, tmux: tmuxColor } = MOOD_COLORS[mood];
9275
9641
  if (tmux) {
9276
9642
  return `#[fg=${tmuxColor}]${text}#[fg=default]`;
@@ -9368,7 +9734,7 @@ function applyColor(result, fields, facePart, mood, opts) {
9368
9734
  const useColor = opts?.color === true || opts?.tmuxFormat === true;
9369
9735
  if (!useColor || facePart === null || !fields.includes("face")) return result;
9370
9736
  const tmux = opts?.tmuxFormat === true;
9371
- const coloredFace = colorize2(facePart, mood, tmux);
9737
+ const coloredFace = colorize3(facePart, mood, tmux);
9372
9738
  return result.replace(facePart, coloredFace);
9373
9739
  }
9374
9740
 
@@ -10181,7 +10547,7 @@ function stripAnsiForWidth(s) {
10181
10547
  return s.replace(/\x1b\[[0-9;]*m/g, "");
10182
10548
  }
10183
10549
  function renderBadgeCard(def, unlock, opts) {
10184
- const dim = opts?.dim === true || unlock === null;
10550
+ const dim2 = opts?.dim === true || unlock === null;
10185
10551
  const art = BADGE_ART[def.id] ?? [];
10186
10552
  const lines = [];
10187
10553
  const category = def.category.toUpperCase();
@@ -10195,7 +10561,7 @@ function renderBadgeCard(def, unlock, opts) {
10195
10561
  const artSlice = art.slice(0, artMaxLines);
10196
10562
  for (const artLine of artSlice) {
10197
10563
  const centered = centerLine(artLine, CARD_INNER);
10198
- lines.push(`\u2502${dim ? dimText(centered) : centered}\u2502`);
10564
+ lines.push(`\u2502${dim2 ? dimText(centered) : centered}\u2502`);
10199
10565
  }
10200
10566
  for (let i = artSlice.length; i < artMaxLines; i++) {
10201
10567
  lines.push(`\u2502${" ".repeat(CARD_INNER)}\u2502`);
@@ -10206,7 +10572,7 @@ function renderBadgeCard(def, unlock, opts) {
10206
10572
  const descLines = wrapText(def.description, CARD_INNER - 4);
10207
10573
  for (const dl of descLines.slice(0, 2)) {
10208
10574
  const centered = centerLine(dl, CARD_INNER);
10209
- lines.push(`\u2502${dim ? dimText(centered) : centered}\u2502`);
10575
+ lines.push(`\u2502${dim2 ? dimText(centered) : centered}\u2502`);
10210
10576
  }
10211
10577
  const usedContent = 1 + 1 + artMaxLines + 1 + 1 + Math.min(descLines.length, 2);
10212
10578
  const remaining = CARD_HEIGHT - 2 - usedContent;
@@ -10266,7 +10632,7 @@ function createBadgeGallery(unlockedAchievements, startIndex) {
10266
10632
 
10267
10633
  // src/daemon/companion-memory.ts
10268
10634
  init_paths();
10269
- import { existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync27, renameSync as renameSync5, writeFileSync as writeFileSync14 } from "fs";
10635
+ import { existsSync as existsSync26, mkdirSync as mkdirSync12, readFileSync as readFileSync28, renameSync as renameSync5, writeFileSync as writeFileSync14 } from "fs";
10270
10636
  import { dirname as dirname10, join as join25 } from "path";
10271
10637
  import { randomUUID as randomUUID4 } from "crypto";
10272
10638
  import { z as z2 } from "zod";
@@ -10278,7 +10644,7 @@ var COOLDOWN_MS = 5 * 60 * 1e3;
10278
10644
 
10279
10645
  // src/daemon/companion.ts
10280
10646
  init_paths();
10281
- import { existsSync as existsSync23, mkdirSync as mkdirSync11, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync13 } from "fs";
10647
+ import { existsSync as existsSync25, mkdirSync as mkdirSync11, readFileSync as readFileSync27, renameSync as renameSync4, writeFileSync as writeFileSync13 } from "fs";
10282
10648
  import { randomUUID as randomUUID3 } from "crypto";
10283
10649
  import { dirname as dirname9, join as join24 } from "path";
10284
10650
 
@@ -10329,12 +10695,12 @@ function normalizeCompanion(state) {
10329
10695
  // src/daemon/companion.ts
10330
10696
  function loadCompanion() {
10331
10697
  const path = companionPath();
10332
- if (!existsSync23(path)) {
10698
+ if (!existsSync25(path)) {
10333
10699
  const state2 = createDefaultCompanion();
10334
10700
  saveCompanion(state2);
10335
10701
  return state2;
10336
10702
  }
10337
- const raw = readFileSync26(path, "utf-8");
10703
+ const raw = readFileSync27(path, "utf-8");
10338
10704
  const state = JSON.parse(raw);
10339
10705
  return normalizeCompanion(state);
10340
10706
  }
@@ -10415,10 +10781,10 @@ function fillDefaults(state) {
10415
10781
  }
10416
10782
  function loadMemoryStrict() {
10417
10783
  const path = resolvedMemoryPath();
10418
- if (!existsSync24(path)) return defaultMemoryState();
10784
+ if (!existsSync26(path)) return defaultMemoryState();
10419
10785
  let raw;
10420
10786
  try {
10421
- raw = readFileSync27(path, "utf-8");
10787
+ raw = readFileSync28(path, "utf-8");
10422
10788
  } catch (err) {
10423
10789
  throw new MemoryStoreParseError(err);
10424
10790
  }
@@ -10461,7 +10827,7 @@ var ObservationZodSchema = z2.object({
10461
10827
  });
10462
10828
 
10463
10829
  // src/daemon/companion-popup.ts
10464
- import { writeFileSync as writeFileSync15, readFileSync as readFileSync28, unlinkSync as unlinkSync3, existsSync as existsSync25 } from "fs";
10830
+ import { writeFileSync as writeFileSync15, readFileSync as readFileSync29, unlinkSync as unlinkSync3, existsSync as existsSync27 } from "fs";
10465
10831
  import { tmpdir as tmpdir2 } from "os";
10466
10832
  import { join as join26, resolve as resolve9 } from "path";
10467
10833
  init_exec();
@@ -10515,7 +10881,7 @@ function showCommentaryPopupQueue(pages) {
10515
10881
  if (contentHeight > maxContentHeight) maxContentHeight = contentHeight;
10516
10882
  writeFileSync15(`${POPUP_TMP_PREFIX}-${i}.txt`, content);
10517
10883
  }
10518
- const whipAvailable = existsSync25(WHIP_ANIMATION_PATH);
10884
+ const whipAvailable = existsSync27(WHIP_ANIMATION_PATH);
10519
10885
  if (whipAvailable && maxContentHeight < WHIP_ANIMATION_ROWS + 2) {
10520
10886
  maxContentHeight = WHIP_ANIMATION_ROWS + 2;
10521
10887
  }
@@ -10589,7 +10955,7 @@ fi
10589
10955
  }
10590
10956
  let raw;
10591
10957
  try {
10592
- raw = readFileSync28(POPUP_RESULT_PREFIX, "utf8").trim();
10958
+ raw = readFileSync29(POPUP_RESULT_PREFIX, "utf8").trim();
10593
10959
  } catch {
10594
10960
  return null;
10595
10961
  } finally {
@@ -10766,7 +11132,7 @@ function registerCompanion(program2) {
10766
11132
  const cachePath = join28(globalDir(), "companion-context-cache", `${opts.sessionId}.json`);
10767
11133
  let prev = {};
10768
11134
  try {
10769
- prev = JSON.parse(readFileSync30(cachePath, "utf-8"));
11135
+ prev = JSON.parse(readFileSync31(cachePath, "utf-8"));
10770
11136
  } catch {
10771
11137
  prev = {};
10772
11138
  }
@@ -10809,8 +11175,8 @@ import { join as join29 } from "path";
10809
11175
  init_paths();
10810
11176
  init_exec();
10811
11177
  init_creds();
10812
- import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
10813
- import { copyFileSync as copyFileSync2, existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync33 } from "fs";
11178
+ import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
11179
+ import { copyFileSync as copyFileSync2, existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync34 } from "fs";
10814
11180
 
10815
11181
  // src/cli/deploy/pricing.ts
10816
11182
  var LAST_VERIFIED = "2026-05-06";
@@ -10845,12 +11211,12 @@ function formatCostLine(provider, instanceType) {
10845
11211
  // src/cli/deploy/runtime.ts
10846
11212
  init_atomic();
10847
11213
  init_paths();
10848
- import { existsSync as existsSync28, readFileSync as readFileSync32, unlinkSync as unlinkSync4 } from "fs";
11214
+ import { existsSync as existsSync30, readFileSync as readFileSync33, unlinkSync as unlinkSync4 } from "fs";
10849
11215
  function readRuntimeState(provider) {
10850
11216
  const path = deployRuntimePath(provider);
10851
- if (!existsSync28(path)) return null;
11217
+ if (!existsSync30(path)) return null;
10852
11218
  try {
10853
- return JSON.parse(readFileSync32(path, "utf-8"));
11219
+ return JSON.parse(readFileSync33(path, "utf-8"));
10854
11220
  } catch {
10855
11221
  return null;
10856
11222
  }
@@ -10860,7 +11226,7 @@ function writeRuntimeState(provider, state) {
10860
11226
  }
10861
11227
  function clearRuntimeState(provider) {
10862
11228
  const path = deployRuntimePath(provider);
10863
- if (existsSync28(path)) unlinkSync4(path);
11229
+ if (existsSync30(path)) unlinkSync4(path);
10864
11230
  }
10865
11231
 
10866
11232
  // src/cli/deploy/tailnet.ts
@@ -10910,15 +11276,15 @@ function isTailscaleAvailable() {
10910
11276
  }
10911
11277
 
10912
11278
  // src/cli/deploy/templates.ts
10913
- import { existsSync as existsSync29 } from "fs";
11279
+ import { existsSync as existsSync31 } from "fs";
10914
11280
  import { dirname as dirname12, resolve as resolve10 } from "path";
10915
11281
  import { fileURLToPath as fileURLToPath4 } from "url";
10916
11282
  function deployRoot() {
10917
11283
  const here = dirname12(fileURLToPath4(import.meta.url));
10918
11284
  const bundled = resolve10(here, "..", "deploy");
10919
- if (existsSync29(bundled)) return bundled;
11285
+ if (existsSync31(bundled)) return bundled;
10920
11286
  const sourceRoot = resolve10(here, "..", "..", "..", "deploy");
10921
- if (existsSync29(sourceRoot)) return sourceRoot;
11287
+ if (existsSync31(sourceRoot)) return sourceRoot;
10922
11288
  throw new Error(
10923
11289
  `Could not locate deploy/ templates. Looked at:
10924
11290
  ${bundled}
@@ -11083,7 +11449,7 @@ async function authTailscale() {
11083
11449
  function runTerraform(provider, args2, extraEnv) {
11084
11450
  ensureProviderStateDir(provider);
11085
11451
  ensureTerraformInstalled();
11086
- const result = spawnSync3("terraform", args2, {
11452
+ const result = spawnSync4("terraform", args2, {
11087
11453
  cwd: providerModuleDir(provider),
11088
11454
  stdio: "inherit",
11089
11455
  env: { ...EXEC_ENV, ...extraEnv }
@@ -11092,7 +11458,7 @@ function runTerraform(provider, args2, extraEnv) {
11092
11458
  return result.status === null ? 1 : result.status;
11093
11459
  }
11094
11460
  function ensureTerraformInstalled() {
11095
- const result = spawnSync3("terraform", ["version"], { stdio: "pipe", env: EXEC_ENV });
11461
+ const result = spawnSync4("terraform", ["version"], { stdio: "pipe", env: EXEC_ENV });
11096
11462
  if (result.error || result.status !== 0) {
11097
11463
  const platform = process.platform;
11098
11464
  const hint = platform === "darwin" ? "brew install terraform" : "See https://developer.hashicorp.com/terraform/install";
@@ -11102,14 +11468,14 @@ function ensureTerraformInstalled() {
11102
11468
  function ensureProviderStateDir(provider) {
11103
11469
  ensureDeployDir();
11104
11470
  const dir = deployProviderDir(provider);
11105
- if (!existsSync30(dir)) mkdirSync16(dir, { recursive: true, mode: 448 });
11471
+ if (!existsSync32(dir)) mkdirSync16(dir, { recursive: true, mode: 448 });
11106
11472
  }
11107
11473
  function backupState(provider) {
11108
11474
  const src = deployStatePath(provider);
11109
- if (existsSync30(src)) copyFileSync2(src, deployStateBackupPath(provider));
11475
+ if (existsSync32(src)) copyFileSync2(src, deployStateBackupPath(provider));
11110
11476
  }
11111
11477
  function readSshPubkey(path) {
11112
- if (!existsSync30(path)) {
11478
+ if (!existsSync32(path)) {
11113
11479
  const privateKeyPath = path.replace(/\.pub$/, "");
11114
11480
  throw new Error(
11115
11481
  `SSH pubkey not found at ${path}. Generate one with:
@@ -11117,10 +11483,10 @@ function readSshPubkey(path) {
11117
11483
  or pass --ssh-key <path>.`
11118
11484
  );
11119
11485
  }
11120
- return readFileSync33(path, "utf-8").trim();
11486
+ return readFileSync34(path, "utf-8").trim();
11121
11487
  }
11122
11488
  function readOutputs(provider) {
11123
- const result = spawnSync3("terraform", ["output", "-json", `-state=${deployStatePath(provider)}`], {
11489
+ const result = spawnSync4("terraform", ["output", "-json", `-state=${deployStatePath(provider)}`], {
11124
11490
  cwd: providerModuleDir(provider),
11125
11491
  encoding: "utf-8",
11126
11492
  env: EXEC_ENV
@@ -11144,7 +11510,7 @@ function readOutputs(provider) {
11144
11510
  }
11145
11511
  }
11146
11512
  function isProvisioned(provider) {
11147
- if (!existsSync30(deployStatePath(provider))) return false;
11513
+ if (!existsSync32(deployStatePath(provider))) return false;
11148
11514
  return readOutputs(provider) !== null;
11149
11515
  }
11150
11516
  async function deployUp(provider, opts) {
@@ -11230,7 +11596,7 @@ Applied \u2014 but could not parse outputs. Run \`sis deploy ${provider} status\
11230
11596
  console.log("");
11231
11597
  }
11232
11598
  async function deployDown(provider, opts) {
11233
- if (!existsSync30(deployStatePath(provider))) {
11599
+ if (!existsSync32(deployStatePath(provider))) {
11234
11600
  console.log(`No ${provider} state found at ${deployStatePath(provider)}. Nothing to destroy.`);
11235
11601
  return;
11236
11602
  }
@@ -11311,7 +11677,7 @@ function effectiveSshTarget(provider) {
11311
11677
  }
11312
11678
  function deploySsh(provider, remoteCmd) {
11313
11679
  const target = effectiveSshTarget(provider);
11314
- const moshAvailable = spawnSync3("mosh", ["--version"], { stdio: "pipe", env: EXEC_ENV }).status === 0;
11680
+ const moshAvailable = spawnSync4("mosh", ["--version"], { stdio: "pipe", env: EXEC_ENV }).status === 0;
11315
11681
  const bin = moshAvailable && remoteCmd.length === 0 ? "mosh" : "ssh";
11316
11682
  const args2 = remoteCmd.length > 0 ? [target, ...remoteCmd] : [target];
11317
11683
  const child = spawn2(bin, args2, { stdio: "inherit", env: EXEC_ENV });
@@ -11427,10 +11793,10 @@ import { hostname } from "os";
11427
11793
 
11428
11794
  // src/cli/deploy/ssh-exec.ts
11429
11795
  init_exec();
11430
- import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
11796
+ import { spawn as spawn3, spawnSync as spawnSync5 } from "child_process";
11431
11797
  function runOnBox(provider, cmd) {
11432
11798
  const target = effectiveSshTarget(provider);
11433
- const result = spawnSync4("ssh", [target, cmd], {
11799
+ const result = spawnSync5("ssh", [target, cmd], {
11434
11800
  encoding: "utf-8",
11435
11801
  env: EXEC_ENV
11436
11802
  });
@@ -11478,11 +11844,11 @@ function ensureGroveRegistered(provider, repo, instancePath) {
11478
11844
 
11479
11845
  // src/cli/cloud/repo.ts
11480
11846
  init_exec();
11481
- import { spawnSync as spawnSync5 } from "child_process";
11482
- import { existsSync as existsSync31 } from "fs";
11847
+ import { spawnSync as spawnSync6 } from "child_process";
11848
+ import { existsSync as existsSync33 } from "fs";
11483
11849
  import { basename as basename6, join as join30 } from "path";
11484
11850
  function captureGit(args2, cwd) {
11485
- const result = spawnSync5("git", args2, {
11851
+ const result = spawnSync6("git", args2, {
11486
11852
  encoding: "utf-8",
11487
11853
  env: EXEC_ENV,
11488
11854
  cwd: cwd ?? process.cwd()
@@ -11532,10 +11898,10 @@ function buildRsyncArgs(localDir, remoteTarget) {
11532
11898
  ];
11533
11899
  }
11534
11900
  function detectPackageManager(toplevel) {
11535
- if (existsSync31(join30(toplevel, "pnpm-lock.yaml"))) return "pnpm";
11536
- if (existsSync31(join30(toplevel, "bun.lockb"))) return "bun";
11537
- if (existsSync31(join30(toplevel, "yarn.lock"))) return "yarn";
11538
- if (existsSync31(join30(toplevel, "package-lock.json"))) return "npm";
11901
+ if (existsSync33(join30(toplevel, "pnpm-lock.yaml"))) return "pnpm";
11902
+ if (existsSync33(join30(toplevel, "bun.lockb"))) return "bun";
11903
+ if (existsSync33(join30(toplevel, "yarn.lock"))) return "yarn";
11904
+ if (existsSync33(join30(toplevel, "package-lock.json"))) return "npm";
11539
11905
  return null;
11540
11906
  }
11541
11907
  function packageManagerInstallCmd(pm) {
@@ -11744,7 +12110,7 @@ function cloudStatus(provider, repo) {
11744
12110
 
11745
12111
  // src/cli/cloud/handoff.ts
11746
12112
  import { spawn as spawn5 } from "child_process";
11747
- import { existsSync as existsSync32, readFileSync as readFileSync34, writeFileSync as writeFileSync18 } from "fs";
12113
+ import { existsSync as existsSync34, readFileSync as readFileSync35, writeFileSync as writeFileSync18 } from "fs";
11748
12114
  init_exec();
11749
12115
  init_paths();
11750
12116
  init_shell();
@@ -11819,10 +12185,10 @@ async function waitForSentOrError(cwd, sessionId) {
11819
12185
  const path = statePath(cwd, sessionId);
11820
12186
  while (Date.now() - start < MAX_WAIT_MS) {
11821
12187
  await sleep2(POLL_INTERVAL_MS);
11822
- if (!existsSync32(path)) continue;
12188
+ if (!existsSync34(path)) continue;
11823
12189
  let session2;
11824
12190
  try {
11825
- session2 = JSON.parse(readFileSync34(path, "utf-8"));
12191
+ session2 = JSON.parse(readFileSync35(path, "utf-8"));
11826
12192
  } catch (err) {
11827
12193
  void err;
11828
12194
  continue;
@@ -11886,7 +12252,7 @@ async function cloudReclaim(sessionId, opts) {
11886
12252
  await rsyncDown(target, remotePath, localPath, { withDelete: false });
11887
12253
  }
11888
12254
  const localStatePath = statePath(cwd, sessionId);
11889
- const merged = JSON.parse(readFileSync34(localStatePath, "utf-8"));
12255
+ const merged = JSON.parse(readFileSync35(localStatePath, "utf-8"));
11890
12256
  merged.cwd = cwd;
11891
12257
  merged.handoff = local.handoff;
11892
12258
  writeFileSync18(localStatePath, JSON.stringify(merged, null, 2));
@@ -11916,11 +12282,11 @@ async function cloudReclaim(sessionId, opts) {
11916
12282
  }
11917
12283
  function readLocalSession(cwd, sessionId) {
11918
12284
  const path = statePath(cwd, sessionId);
11919
- if (!existsSync32(path)) {
12285
+ if (!existsSync34(path)) {
11920
12286
  console.error(`No local state.json for ${sessionId} at ${path}.`);
11921
12287
  process.exit(1);
11922
12288
  }
11923
- return JSON.parse(readFileSync34(path, "utf-8"));
12289
+ return JSON.parse(readFileSync35(path, "utf-8"));
11924
12290
  }
11925
12291
  async function waitForBoxPaused(provider, remoteSessionDir) {
11926
12292
  const POLL_INTERVAL_MS = 2e3;
@@ -12030,8 +12396,8 @@ function attachNotify(diagnostic2) {
12030
12396
 
12031
12397
  // src/cli/commands/tmux-sessions.ts
12032
12398
  init_paths();
12033
- import { execSync as execSync18 } from "child_process";
12034
- import { readFileSync as readFileSync35, existsSync as existsSync33 } from "fs";
12399
+ import { execSync as execSync19 } from "child_process";
12400
+ import { readFileSync as readFileSync36, existsSync as existsSync35 } from "fs";
12035
12401
  var DOT_MAP = {
12036
12402
  "orchestrator:processing": { icon: "\u25CF", color: "#d4ad6a" },
12037
12403
  "orchestrator:idle": { icon: "\u25CF", color: "#d47766" },
@@ -12042,16 +12408,16 @@ var DOT_MAP = {
12042
12408
  };
12043
12409
  function readManifest() {
12044
12410
  const p = sessionsManifestPath();
12045
- if (!existsSync33(p)) return null;
12411
+ if (!existsSync35(p)) return null;
12046
12412
  try {
12047
- return JSON.parse(readFileSync35(p, "utf-8"));
12413
+ return JSON.parse(readFileSync36(p, "utf-8"));
12048
12414
  } catch {
12049
12415
  return null;
12050
12416
  }
12051
12417
  }
12052
12418
  function tmuxExec(cmd) {
12053
12419
  try {
12054
- return execSync18(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
12420
+ return execSync19(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
12055
12421
  } catch {
12056
12422
  return null;
12057
12423
  }
@@ -12087,10 +12453,23 @@ if (nodeVersion < 22) {
12087
12453
  console.error(`Sisyphus requires Node.js v22+ (current: v${process.versions.node})`);
12088
12454
  process.exit(1);
12089
12455
  }
12456
+ if (process.platform === "win32") {
12457
+ console.error(`Sisyphus does not run on native Windows (PowerShell / cmd.exe).
12458
+
12459
+ It depends on tmux, bash, and POSIX sockets \u2014 please run it inside WSL2:
12460
+
12461
+ 1. Install WSL2: https://learn.microsoft.com/windows/wsl/install
12462
+ 2. Open your WSL distro (Ubuntu is a safe default).
12463
+ 3. Install Node.js v22+ and Claude Code inside WSL.
12464
+ 4. Re-run \`sis\` from the WSL shell, not PowerShell.
12465
+
12466
+ Tip: enable systemd in /etc/wsl.conf for the recommended daemon setup.`);
12467
+ process.exit(1);
12468
+ }
12090
12469
  var program = new Command();
12091
12470
  program.name("sis").description("tmux-integrated orchestration daemon for Claude Code").version(
12092
12471
  JSON.parse(
12093
- readFileSync36(join32(dirname13(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
12472
+ readFileSync37(join32(dirname13(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
12094
12473
  ).version
12095
12474
  );
12096
12475
  program.configureHelp({
@@ -12165,7 +12544,7 @@ Run 'sis admin getting-started' for a complete usage guide.
12165
12544
  var args = process.argv.slice(2);
12166
12545
  var firstArg = args[0];
12167
12546
  var skipWelcome = ["admin", "help", "--help", "-h", "--version", "-V"];
12168
- if (!existsSync34(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
12547
+ if (!existsSync36(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
12169
12548
  mkdirSync17(globalDir(), { recursive: true });
12170
12549
  console.log("");
12171
12550
  console.log(" Welcome to Sisyphus. Run 'sis admin setup' to get started.");