sisyphi 1.1.7 → 1.1.9

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
@@ -10,6 +10,7 @@ import {
10
10
  shellQuote
11
11
  } from "./chunk-6G226ZK7.js";
12
12
  import {
13
+ cycleLogPath,
13
14
  daemonLogPath,
14
15
  daemonPidPath,
15
16
  daemonUpdatingPath,
@@ -20,9 +21,9 @@ import {
20
21
 
21
22
  // src/cli/index.ts
22
23
  import { Command } from "commander";
23
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
24
- import { dirname as dirname2, join as join6 } from "path";
25
- import { fileURLToPath as fileURLToPath2 } from "url";
24
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "fs";
25
+ import { dirname as dirname4, join as join8 } from "path";
26
+ import { fileURLToPath as fileURLToPath4 } from "url";
26
27
 
27
28
  // src/cli/commands/start.ts
28
29
  import { execSync as execSync5 } from "child_process";
@@ -91,8 +92,9 @@ while IFS= read -r name; do
91
92
  scwd=$(tmux show-option -t "$name" -v @sisyphus_cwd 2>/dev/null)
92
93
  if [ "$scwd" = "$cwd" ]; then
93
94
  tmux switch-client -t "$name"
94
- # Focus the dashboard window if it exists
95
- tmux select-window -t "$name:sisyphus-dashboard" 2>/dev/null
95
+ # Focus the dashboard window by stored ID (not name)
96
+ dwid=$(tmux show-option -t "$name" -v @sisyphus_dashboard 2>/dev/null)
97
+ [ -n "$dwid" ] && tmux select-window -t "$dwid" 2>/dev/null
96
98
  exit 0
97
99
  fi
98
100
  done < <(tmux list-sessions -F '#{session_name}')
@@ -112,7 +114,8 @@ if [ "$pane_count" -le 1 ]; then
112
114
  scwd=$(tmux show-option -t "$name" -v @sisyphus_cwd 2>/dev/null)
113
115
  if [ "$scwd" = "$cwd" ]; then
114
116
  tmux switch-client -t "$name"
115
- tmux select-window -t "$name:sisyphus-dashboard" 2>/dev/null
117
+ dwid=$(tmux show-option -t "$name" -v @sisyphus_dashboard 2>/dev/null)
118
+ [ -n "$dwid" ] && tmux select-window -t "$dwid" 2>/dev/null
116
119
  tmux kill-session -t "$session"
117
120
  exit 0
118
121
  fi
@@ -486,18 +489,22 @@ function getTmuxSession() {
486
489
  // src/cli/commands/dashboard.ts
487
490
  import { join as join3 } from "path";
488
491
  import { execSync as execSync4 } from "child_process";
489
- function ensureDashboard(tmuxSession, cwd) {
492
+ function openDashboardWindow(tmuxSession, cwd) {
490
493
  try {
491
- const windows = execSync4(
492
- `tmux list-windows -t ${shellQuote(tmuxSession)} -F "#{window_name}"`,
493
- { encoding: "utf-8" }
494
- );
495
- const isOpen = windows.split("\n").some((name) => name.trim() === "sisyphus-dashboard");
496
- if (isOpen) {
497
- execSync4(
498
- `tmux select-window -t ${shellQuote(tmuxSession)}:sisyphus-dashboard`
499
- );
500
- return false;
494
+ const storedId = execSync4(
495
+ `tmux show-option -t ${shellQuote(tmuxSession)} -v @sisyphus_dashboard`,
496
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
497
+ ).trim();
498
+ if (storedId) {
499
+ try {
500
+ execSync4(
501
+ `tmux display-message -t ${shellQuote(storedId)} -p "#{window_id}"`,
502
+ { stdio: "pipe" }
503
+ );
504
+ execSync4(`tmux select-window -t ${shellQuote(storedId)}`, { stdio: "pipe" });
505
+ return false;
506
+ } catch {
507
+ }
501
508
  }
502
509
  } catch {
503
510
  }
@@ -506,17 +513,23 @@ function ensureDashboard(tmuxSession, cwd) {
506
513
  `tmux new-window -n "sisyphus-dashboard" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
507
514
  { encoding: "utf-8" }
508
515
  ).trim();
509
- const cmd = `node ${shellQuote(tuiPath)} --cwd ${shellQuote(cwd)}`;
516
+ const cmd = `node ${shellQuote(tuiPath)} --cwd ${shellQuote(cwd)}; exit`;
510
517
  execSync4(
511
518
  `tmux send-keys -t ${shellQuote(windowId)} ${shellQuote(cmd)} Enter`
512
519
  );
520
+ execSync4(
521
+ `tmux set-option -t ${shellQuote(tmuxSession)} @sisyphus_dashboard ${shellQuote(windowId)}`,
522
+ { stdio: "pipe" }
523
+ );
513
524
  return true;
514
525
  }
515
526
  function registerDashboard(program2) {
516
527
  program2.command("dashboard").description("Launch the TUI dashboard for monitoring and managing sessions").action(async () => {
517
528
  assertTmux();
518
- const tmuxSession = getTmuxSession();
519
- ensureDashboard(tmuxSession, process.cwd());
529
+ const tuiPath = join3(import.meta.dirname, "tui.js");
530
+ execSync4(`node ${shellQuote(tuiPath)} --cwd ${shellQuote(process.cwd())}`, {
531
+ stdio: "inherit"
532
+ });
520
533
  });
521
534
  }
522
535
 
@@ -550,7 +563,7 @@ function registerStart(program2) {
550
563
  }
551
564
  try {
552
565
  const tmuxSession = getTmuxSession();
553
- if (ensureDashboard(tmuxSession, cwd)) {
566
+ if (openDashboardWindow(tmuxSession, cwd)) {
554
567
  console.log(`Dashboard opened in tmux window "sisyphus-dashboard"`);
555
568
  }
556
569
  } catch {
@@ -644,24 +657,6 @@ function registerSpawn(program2) {
644
657
  }
645
658
 
646
659
  // src/cli/commands/submit.ts
647
- import { execSync as execSync6 } from "child_process";
648
- function isInWorktree() {
649
- try {
650
- const gitDir = execSync6("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
651
- const commonDir = execSync6("git rev-parse --git-common-dir", { encoding: "utf-8" }).trim();
652
- return gitDir !== commonDir;
653
- } catch {
654
- return false;
655
- }
656
- }
657
- function getUncommittedChanges() {
658
- try {
659
- const status = execSync6("git status --porcelain", { encoding: "utf-8" }).trim();
660
- return status || null;
661
- } catch {
662
- return null;
663
- }
664
- }
665
660
  function registerSubmit(program2) {
666
661
  program2.command("submit").description("Submit work report and exit (agent only)").option("--report <report>", "Work report (or pipe via stdin)").option("--session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID env var)").action(async (opts) => {
667
662
  assertTmux();
@@ -671,17 +666,6 @@ function registerSubmit(program2) {
671
666
  console.error("Error: provide --session or set SISYPHUS_SESSION_ID (and SISYPHUS_AGENT_ID) environment variables");
672
667
  process.exit(1);
673
668
  }
674
- if (isInWorktree()) {
675
- const changes = getUncommittedChanges();
676
- if (changes) {
677
- console.error("Error: uncommitted changes in worktree. Commit your changes before submitting.");
678
- console.error('\nCommit first:\n git add -A && git commit -m "description of changes"\n');
679
- console.error("Or discard:\n git checkout -- .\n");
680
- console.error("Uncommitted changes:");
681
- console.error(changes);
682
- process.exit(1);
683
- }
684
- }
685
669
  const report = opts.report ?? await readStdin();
686
670
  if (!report) {
687
671
  console.error("Error: provide --report or pipe content via stdin");
@@ -766,7 +750,8 @@ function registerContinue(program2) {
766
750
  }
767
751
 
768
752
  // src/cli/commands/status.ts
769
- import { readFileSync as readFileSync3 } from "fs";
753
+ import { execSync as execSync6 } from "child_process";
754
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
770
755
  var COLOR_CODES = {
771
756
  green: "\x1B[32m",
772
757
  yellow: "\x1B[33m",
@@ -802,12 +787,17 @@ function inferOrchestratorPhase(session) {
802
787
  return "starting";
803
788
  }
804
789
  }
805
- function formatAgent(agent) {
790
+ function formatAgent(agent, verbose) {
806
791
  const status = colorize(agent.status, agent.status);
807
792
  const name = `${BOLD}${agent.name}${RESET}`;
808
793
  const type = `${DIM}(${agent.agentType})${RESET}`;
809
794
  const duration = formatDuration(agent.activeMs);
810
795
  let line = ` ${agent.id} ${name} ${type} \u2014 ${status} ${DIM}(${duration})${RESET}`;
796
+ if (verbose && agent.instruction) {
797
+ const truncated = agent.instruction.length > 200 ? agent.instruction.slice(0, 200) + "..." : agent.instruction;
798
+ line += `
799
+ ${DIM}Instruction: ${truncated}${RESET}`;
800
+ }
811
801
  if (agent.reports.length > 0) {
812
802
  for (const r of agent.reports) {
813
803
  const label = r.type === "final" ? "Final" : "Update";
@@ -857,7 +847,33 @@ function readRoadmapTodos(cwd, sessionId) {
857
847
  return [];
858
848
  }
859
849
  }
860
- function printSession(session) {
850
+ function readFullRoadmap(cwd, sessionId) {
851
+ try {
852
+ return readFileSync3(roadmapPath(cwd, sessionId), "utf8");
853
+ } catch {
854
+ return null;
855
+ }
856
+ }
857
+ function readCycleLog(cwd, sessionId, cycle) {
858
+ try {
859
+ const path = cycleLogPath(cwd, sessionId, cycle);
860
+ if (!existsSync4(path)) return null;
861
+ return readFileSync3(path, "utf8");
862
+ } catch {
863
+ return null;
864
+ }
865
+ }
866
+ function capturePaneOutput(paneId, lines = 50) {
867
+ try {
868
+ return execSync6(
869
+ `tmux capture-pane -t "${paneId}" -p -S -${lines}`,
870
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
871
+ ).trimEnd();
872
+ } catch {
873
+ return null;
874
+ }
875
+ }
876
+ function printSession(session, verbose) {
861
877
  const status = colorize(session.status, session.status);
862
878
  const sessionDuration = formatDuration(session.createdAt, session.completedAt);
863
879
  console.log(`
@@ -865,7 +881,7 @@ ${BOLD}Session: ${session.id}${RESET}`);
865
881
  console.log(` Status: ${status}`);
866
882
  console.log(` Task: ${session.task}`);
867
883
  if (session.context) {
868
- const truncated = session.context.length > 120 ? session.context.slice(0, 120) + "..." : session.context;
884
+ const truncated = !verbose && session.context.length > 120 ? session.context.slice(0, 120) + "..." : session.context;
869
885
  console.log(` Context: ${truncated}`);
870
886
  }
871
887
  console.log(` CWD: ${session.cwd}`);
@@ -886,14 +902,27 @@ ${BOLD}Active agents (${runningAgents.length}):${RESET}`);
886
902
  const type = `${DIM}(${agent.agentType})${RESET}`;
887
903
  const duration = formatDuration(agent.activeMs);
888
904
  console.log(` ${agent.id} ${name} ${type} running ${duration}`);
905
+ if (verbose && agent.instruction) {
906
+ const truncated = agent.instruction.length > 200 ? agent.instruction.slice(0, 200) + "..." : agent.instruction;
907
+ console.log(` ${DIM}Instruction: ${truncated}${RESET}`);
908
+ }
889
909
  }
890
910
  }
891
- const todos = readRoadmapTodos(session.cwd, session.id);
892
- if (todos.length > 0) {
893
- console.log(`
911
+ if (verbose) {
912
+ const roadmap = readFullRoadmap(session.cwd, session.id);
913
+ if (roadmap) {
914
+ console.log(`
915
+ ${BOLD}Roadmap:${RESET}`);
916
+ console.log(roadmap);
917
+ }
918
+ } else {
919
+ const todos = readRoadmapTodos(session.cwd, session.id);
920
+ if (todos.length > 0) {
921
+ console.log(`
894
922
  ${BOLD}Remaining (${todos.length} unchecked):${RESET}`);
895
- for (const todo of todos) {
896
- console.log(` - ${todo}`);
923
+ for (const todo of todos) {
924
+ console.log(` - ${todo}`);
925
+ }
897
926
  }
898
927
  }
899
928
  if (session.orchestratorCycles.length > 0) {
@@ -904,25 +933,68 @@ ${BOLD}Remaining (${todos.length} unchecked):${RESET}`);
904
933
  const isLast = i === cycles.length - 1;
905
934
  const phase = isLast && session.status === "active" ? inferOrchestratorPhase(session) : void 0;
906
935
  console.log(formatCycle(cycles[i], phase));
936
+ if (verbose) {
937
+ const log = readCycleLog(session.cwd, session.id, cycles[i].cycle);
938
+ if (log) {
939
+ const lines = log.split("\n");
940
+ const preview = lines.slice(0, 20).join("\n");
941
+ console.log(` ${DIM}--- cycle log ---${RESET}`);
942
+ for (const line of preview.split("\n")) {
943
+ console.log(` ${DIM}${line}${RESET}`);
944
+ }
945
+ if (lines.length > 20) {
946
+ console.log(` ${DIM}... (${lines.length - 20} more lines)${RESET}`);
947
+ }
948
+ }
949
+ }
907
950
  }
908
951
  }
909
952
  if (session.agents.length > 0) {
910
953
  console.log(`
911
954
  ${BOLD}Agents:${RESET}`);
912
955
  for (const agent of session.agents) {
913
- console.log(formatAgent(agent));
956
+ console.log(formatAgent(agent, verbose));
914
957
  }
915
958
  }
959
+ if (verbose) {
960
+ const lastCycle = session.orchestratorCycles[session.orchestratorCycles.length - 1];
961
+ if (lastCycle && !lastCycle.completedAt && lastCycle.paneId) {
962
+ const output = capturePaneOutput(lastCycle.paneId);
963
+ if (output) {
964
+ console.log(`
965
+ <orchestrator-pane-output lines="50">`);
966
+ console.log(output);
967
+ console.log(`</orchestrator-pane-output>`);
968
+ }
969
+ }
970
+ for (const agent of runningAgents) {
971
+ if (agent.paneId) {
972
+ const output = capturePaneOutput(agent.paneId, 30);
973
+ if (output) {
974
+ console.log(`
975
+ <agent-pane-output agent="${agent.id}" name="${agent.name}" lines="30">`);
976
+ console.log(output);
977
+ console.log(`</agent-pane-output>`);
978
+ }
979
+ }
980
+ }
981
+ }
982
+ if (verbose && session.completionReport) {
983
+ console.log(`
984
+ ${BOLD}Completion Report:${RESET}`);
985
+ console.log(session.completionReport);
986
+ }
916
987
  }
917
988
  function registerStatus(program2) {
918
- program2.command("status").description("Show session status").argument("[session-id]", "Session ID (defaults to SISYPHUS_SESSION_ID env)").action(async (sessionIdArg) => {
989
+ program2.command("status").description("Show session status").argument("[session-id]", "Session ID (defaults to SISYPHUS_SESSION_ID env)").option("-v, --verbose", "Show detailed output (roadmap, pane output, agent instructions)").action(async (sessionIdArg, opts) => {
919
990
  const sessionId = sessionIdArg ?? process.env.SISYPHUS_SESSION_ID;
991
+ const verbose = opts?.verbose ?? false;
920
992
  const request = { type: "status", sessionId };
921
993
  const response = await sendRequest(request);
922
994
  if (response.ok) {
923
995
  const session = response.data?.session;
924
996
  if (session) {
925
- printSession(session);
997
+ printSession(session, verbose);
926
998
  } else {
927
999
  console.log("No session found");
928
1000
  }
@@ -1193,8 +1265,245 @@ function registerSetupKeybind(program2) {
1193
1265
  }
1194
1266
 
1195
1267
  // src/cli/commands/doctor.ts
1268
+ import { execSync as execSync8 } from "child_process";
1269
+ import { existsSync as existsSync6, statSync } from "fs";
1270
+
1271
+ // src/cli/onboard.ts
1196
1272
  import { execSync as execSync7 } from "child_process";
1197
- import { existsSync as existsSync4, statSync } from "fs";
1273
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1274
+ import { homedir as homedir3 } from "os";
1275
+ import { dirname as dirname2, join as join5 } from "path";
1276
+ import { fileURLToPath as fileURLToPath2 } from "url";
1277
+ function detectTerminal() {
1278
+ const termProgram = process.env["TERM_PROGRAM"] || "";
1279
+ const isIterm = termProgram === "iTerm.app" || !!process.env["ITERM_SESSION_ID"];
1280
+ return { name: termProgram || "unknown", isIterm };
1281
+ }
1282
+ function isTmuxAvailable() {
1283
+ try {
1284
+ execSync7("which tmux", { stdio: "pipe" });
1285
+ return true;
1286
+ } catch {
1287
+ return false;
1288
+ }
1289
+ }
1290
+ function isBrewAvailable() {
1291
+ try {
1292
+ execSync7("which brew", { stdio: "pipe" });
1293
+ return true;
1294
+ } catch {
1295
+ return false;
1296
+ }
1297
+ }
1298
+ function tryAutoInstallTmux() {
1299
+ if (!isBrewAvailable()) return false;
1300
+ try {
1301
+ console.log(" Installing tmux via Homebrew...");
1302
+ execSync7("brew install tmux", { stdio: "inherit" });
1303
+ return isTmuxAvailable();
1304
+ } catch {
1305
+ return false;
1306
+ }
1307
+ }
1308
+ function checkItermOptionKey() {
1309
+ if (process.platform !== "darwin") {
1310
+ return { checked: false, allCorrect: true, incorrectProfiles: [] };
1311
+ }
1312
+ const plistPath2 = join5(homedir3(), "Library", "Preferences", "com.googlecode.iterm2.plist");
1313
+ if (!existsSync5(plistPath2)) {
1314
+ return { checked: false, allCorrect: false, incorrectProfiles: [] };
1315
+ }
1316
+ try {
1317
+ const json = execSync7(
1318
+ `plutil -extract "New Bookmarks" json -o - "${plistPath2}"`,
1319
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
1320
+ );
1321
+ const profiles = JSON.parse(json);
1322
+ const currentProfile = process.env["ITERM_PROFILE"];
1323
+ const incorrect = [];
1324
+ for (const profile of profiles) {
1325
+ const name = profile["Name"] || "Unnamed";
1326
+ if (currentProfile && name !== currentProfile) continue;
1327
+ if (profile["Right Option Key Sends"] !== 2) {
1328
+ incorrect.push(name);
1329
+ }
1330
+ }
1331
+ return { checked: true, allCorrect: incorrect.length === 0, incorrectProfiles: incorrect };
1332
+ } catch {
1333
+ return { checked: false, allCorrect: false, incorrectProfiles: [] };
1334
+ }
1335
+ }
1336
+ function hasExistingTmuxConf() {
1337
+ return existsSync5(join5(homedir3(), ".tmux.conf")) || existsSync5(join5(homedir3(), ".config", "tmux", "tmux.conf"));
1338
+ }
1339
+ var TMUX_DEFAULTS = `# Sensible tmux defaults (installed by sisyphus)
1340
+ # Customize freely \u2014 sisyphus won't overwrite this file.
1341
+
1342
+ # Enable mouse (click panes, scroll, resize)
1343
+ set -g mouse on
1344
+
1345
+ # Scrollback history
1346
+ set -g history-limit 50000
1347
+
1348
+ # 256 color + true color support
1349
+ set -g default-terminal "tmux-256color"
1350
+ set -as terminal-overrides ",*:Tc"
1351
+
1352
+ # Low escape delay (keeps Option/Meta keybindings responsive)
1353
+ set -sg escape-time 10
1354
+
1355
+ # Window numbering from 1
1356
+ set -g base-index 1
1357
+ setw -g pane-base-index 1
1358
+
1359
+ # Renumber windows when one closes
1360
+ set -g renumber-windows on
1361
+
1362
+ # Clipboard integration
1363
+ set -g set-clipboard on
1364
+
1365
+ # Focus events (for editors)
1366
+ set -g focus-events on
1367
+
1368
+ # --- Pane navigation (no prefix needed) ---
1369
+ bind -n C-h select-pane -L
1370
+ bind -n C-j select-pane -D
1371
+ bind -n C-k select-pane -U
1372
+ bind -n C-l select-pane -R
1373
+
1374
+ # --- Window navigation (no prefix needed) ---
1375
+ bind -n C-n next-window
1376
+ bind -n C-p previous-window
1377
+
1378
+ # --- New window / splits preserve cwd ---
1379
+ bind c new-window -c "#{pane_current_path}"
1380
+ bind '"' split-window -v -c "#{pane_current_path}"
1381
+ bind % split-window -h -c "#{pane_current_path}"
1382
+
1383
+ # --- Kill pane + rebalance ---
1384
+ bind x kill-pane \\; select-layout even-horizontal
1385
+
1386
+ # --- Auto-rebalance on pane close ---
1387
+ set-hook -g after-kill-pane "select-layout even-horizontal"
1388
+ set-hook -g pane-exited "select-layout even-horizontal"
1389
+
1390
+ # --- Manual re-tile ---
1391
+ bind = select-layout even-horizontal
1392
+
1393
+ # --- Scroll (no prefix needed) ---
1394
+ bind -n C-u copy-mode \\; send-keys -X halfpage-up
1395
+ bind -n C-d copy-mode \\; send-keys -X halfpage-down
1396
+
1397
+ # --- Vi copy mode ---
1398
+ setw -g mode-keys vi
1399
+ bind -T copy-mode-vi v send-keys -X begin-selection
1400
+ bind -T copy-mode-vi y send-keys -X copy-selection-and-cancel
1401
+ `;
1402
+ function writeTmuxDefaults() {
1403
+ const confPath = join5(homedir3(), ".tmux.conf");
1404
+ writeFileSync3(confPath, TMUX_DEFAULTS, "utf8");
1405
+ }
1406
+ function isNvimAvailable() {
1407
+ try {
1408
+ execSync7("which nvim", { stdio: "pipe" });
1409
+ return true;
1410
+ } catch {
1411
+ return false;
1412
+ }
1413
+ }
1414
+ function getNvimVersion() {
1415
+ try {
1416
+ return execSync7("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "") || "unknown";
1417
+ } catch {
1418
+ return "unknown";
1419
+ }
1420
+ }
1421
+ function hasLazyVimConfig() {
1422
+ return existsSync5(join5(homedir3(), ".config", "nvim", "lazy-lock.json"));
1423
+ }
1424
+ function tryAutoInstallNvim() {
1425
+ if (isNvimAvailable()) {
1426
+ return { installed: true, autoInstalled: false, version: getNvimVersion(), lazyVimInstalled: hasLazyVimConfig() };
1427
+ }
1428
+ if (!isBrewAvailable()) {
1429
+ return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false };
1430
+ }
1431
+ try {
1432
+ console.log(" Installing neovim via Homebrew...");
1433
+ execSync7("brew install neovim", { stdio: "inherit" });
1434
+ } catch {
1435
+ return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false };
1436
+ }
1437
+ if (!isNvimAvailable()) {
1438
+ return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false };
1439
+ }
1440
+ const nvimConfigDir = join5(homedir3(), ".config", "nvim");
1441
+ let lazyVimInstalled = false;
1442
+ if (!existsSync5(nvimConfigDir)) {
1443
+ try {
1444
+ console.log(" Cloning LazyVim starter config...");
1445
+ execSync7(`git clone https://github.com/LazyVim/starter ${nvimConfigDir}`, { stdio: "inherit" });
1446
+ const gitDir = join5(nvimConfigDir, ".git");
1447
+ if (existsSync5(gitDir)) {
1448
+ execSync7(`rm -rf "${gitDir}"`, { stdio: "pipe" });
1449
+ }
1450
+ lazyVimInstalled = true;
1451
+ } catch {
1452
+ }
1453
+ }
1454
+ return { installed: true, autoInstalled: true, version: getNvimVersion(), lazyVimInstalled };
1455
+ }
1456
+ function beginCommandPath() {
1457
+ return join5(homedir3(), ".claude", "commands", "sisyphus", "begin.md");
1458
+ }
1459
+ function bundledBeginCommandPath() {
1460
+ const distDir = dirname2(fileURLToPath2(import.meta.url));
1461
+ return join5(distDir, "templates", "begin.md");
1462
+ }
1463
+ function isBeginCommandInstalled() {
1464
+ return existsSync5(beginCommandPath());
1465
+ }
1466
+ function installBeginCommand() {
1467
+ const dest = beginCommandPath();
1468
+ if (existsSync5(dest)) {
1469
+ return { installed: true, autoInstalled: false, path: dest };
1470
+ }
1471
+ const src = bundledBeginCommandPath();
1472
+ if (!existsSync5(src)) {
1473
+ return { installed: false, autoInstalled: false, path: dest };
1474
+ }
1475
+ try {
1476
+ mkdirSync3(dirname2(dest), { recursive: true });
1477
+ writeFileSync3(dest, readFileSync4(src, "utf-8"), "utf8");
1478
+ return { installed: true, autoInstalled: true, path: dest };
1479
+ } catch {
1480
+ return { installed: false, autoInstalled: false, path: dest };
1481
+ }
1482
+ }
1483
+ function runOnboarding() {
1484
+ const terminal = detectTerminal();
1485
+ const tmuxAlreadyInstalled = isTmuxAvailable();
1486
+ let tmuxInstalled = tmuxAlreadyInstalled;
1487
+ let tmuxAutoInstalled = false;
1488
+ let tmuxDefaultsWritten = false;
1489
+ if (!tmuxAlreadyInstalled && process.platform === "darwin") {
1490
+ tmuxAutoInstalled = tryAutoInstallTmux();
1491
+ tmuxInstalled = tmuxAutoInstalled;
1492
+ if (tmuxAutoInstalled && !hasExistingTmuxConf()) {
1493
+ writeTmuxDefaults();
1494
+ tmuxDefaultsWritten = true;
1495
+ }
1496
+ }
1497
+ let itermOptionKey = { checked: false, allCorrect: true, incorrectProfiles: [] };
1498
+ if (terminal.isIterm) {
1499
+ itermOptionKey = checkItermOptionKey();
1500
+ }
1501
+ const nvim = tryAutoInstallNvim();
1502
+ const command = installBeginCommand();
1503
+ return { tmuxInstalled, tmuxAutoInstalled, terminal, itermOptionKey, tmuxDefaultsWritten, nvim, command };
1504
+ }
1505
+
1506
+ // src/cli/commands/doctor.ts
1198
1507
  function checkNodeVersion() {
1199
1508
  const major = parseInt(process.versions.node.split(".")[0], 10);
1200
1509
  if (major < 22) {
@@ -1204,7 +1513,7 @@ function checkNodeVersion() {
1204
1513
  }
1205
1514
  function checkClaudeCli() {
1206
1515
  try {
1207
- execSync7("which claude", { stdio: "pipe" });
1516
+ execSync8("which claude", { stdio: "pipe" });
1208
1517
  return { name: "Claude CLI", status: "ok", detail: "Found on PATH" };
1209
1518
  } catch {
1210
1519
  return {
@@ -1217,7 +1526,7 @@ function checkClaudeCli() {
1217
1526
  }
1218
1527
  function checkGit() {
1219
1528
  try {
1220
- const version = execSync7("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
1529
+ const version = execSync8("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
1221
1530
  return { name: "git", status: "ok", detail: version };
1222
1531
  } catch {
1223
1532
  return { name: "git", status: "fail", detail: "Not found on PATH", fix: "Install git: https://git-scm.com/downloads" };
@@ -1225,7 +1534,7 @@ function checkGit() {
1225
1534
  }
1226
1535
  function checkTmuxVersion() {
1227
1536
  try {
1228
- const version = execSync7("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
1537
+ const version = execSync8("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
1229
1538
  const match = version.match(/(\d+\.\d+)/);
1230
1539
  if (!match) return { name: "tmux version", status: "warn", detail: `Could not parse version: ${version}` };
1231
1540
  const ver = parseFloat(match[1]);
@@ -1251,7 +1560,7 @@ function checkDaemonInstalled() {
1251
1560
  };
1252
1561
  }
1253
1562
  const pid = daemonPidPath();
1254
- if (existsSync4(pid)) {
1563
+ if (existsSync6(pid)) {
1255
1564
  return { name: "Daemon setup", status: "ok", detail: `PID file found at ${pid}` };
1256
1565
  }
1257
1566
  return {
@@ -1263,7 +1572,7 @@ function checkDaemonInstalled() {
1263
1572
  }
1264
1573
  function checkDaemonRunning() {
1265
1574
  const pid = daemonPidPath();
1266
- if (!existsSync4(pid)) {
1575
+ if (!existsSync6(pid)) {
1267
1576
  const fix = process.platform === "darwin" ? "launchctl load -w ~/Library/LaunchAgents/com.sisyphus.daemon.plist" : "sisyphusd & \u2014 or check if the process is running";
1268
1577
  return {
1269
1578
  name: "Daemon process",
@@ -1274,7 +1583,7 @@ function checkDaemonRunning() {
1274
1583
  }
1275
1584
  try {
1276
1585
  const sock = socketPath();
1277
- execSync7(`test -S "${sock}"`, { stdio: "pipe" });
1586
+ execSync8(`test -S "${sock}"`, { stdio: "pipe" });
1278
1587
  return { name: "Daemon process", status: "ok", detail: `Socket at ${sock}` };
1279
1588
  } catch {
1280
1589
  return {
@@ -1287,13 +1596,13 @@ function checkDaemonRunning() {
1287
1596
  }
1288
1597
  function checkTmux() {
1289
1598
  try {
1290
- execSync7("which tmux", { stdio: "pipe" });
1599
+ execSync8("which tmux", { stdio: "pipe" });
1291
1600
  } catch {
1292
1601
  const installHint = process.platform === "darwin" ? "brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
1293
1602
  return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: installHint };
1294
1603
  }
1295
1604
  try {
1296
- execSync7("tmux list-sessions", { stdio: "pipe" });
1605
+ execSync8("tmux list-sessions", { stdio: "pipe" });
1297
1606
  return { name: "tmux", status: "ok", detail: "Running" };
1298
1607
  } catch {
1299
1608
  return { name: "tmux", status: "warn", detail: "Installed but no server running" };
@@ -1301,7 +1610,7 @@ function checkTmux() {
1301
1610
  }
1302
1611
  function checkCycleScript() {
1303
1612
  const path = cycleScriptPath();
1304
- if (!existsSync4(path)) {
1613
+ if (!existsSync6(path)) {
1305
1614
  return {
1306
1615
  name: "Cycle script",
1307
1616
  status: "fail",
@@ -1326,7 +1635,7 @@ function checkCycleScript() {
1326
1635
  function checkTmuxKeybind() {
1327
1636
  const existing = getExistingBinding(DEFAULT_KEY);
1328
1637
  if (existing === null) {
1329
- if (existsSync4(sisyphusTmuxConfPath())) {
1638
+ if (existsSync6(sisyphusTmuxConfPath())) {
1330
1639
  return {
1331
1640
  name: `Tmux keybind (${DEFAULT_KEY})`,
1332
1641
  status: "warn",
@@ -1352,25 +1661,85 @@ function checkTmuxKeybind() {
1352
1661
  }
1353
1662
  function checkGlobalDir() {
1354
1663
  const dir = globalDir();
1355
- if (existsSync4(dir)) {
1664
+ if (existsSync6(dir)) {
1356
1665
  return { name: "Data directory", status: "ok", detail: dir };
1357
1666
  }
1358
1667
  return { name: "Data directory", status: "warn", detail: `${dir} does not exist (created on first use)` };
1359
1668
  }
1669
+ function checkTerminal() {
1670
+ if (process.platform !== "darwin") {
1671
+ return { name: "Terminal", status: "ok", detail: "Non-macOS (skipped)" };
1672
+ }
1673
+ const terminal = detectTerminal();
1674
+ if (terminal.isIterm) {
1675
+ return { name: "Terminal", status: "ok", detail: terminal.name };
1676
+ }
1677
+ return {
1678
+ name: "Terminal",
1679
+ status: "warn",
1680
+ detail: terminal.name ? terminal.name : "unknown",
1681
+ fix: "iTerm2 recommended for best experience: https://iterm2.com"
1682
+ };
1683
+ }
1684
+ function checkItermRightOptionKey() {
1685
+ if (process.platform !== "darwin") return null;
1686
+ const terminal = detectTerminal();
1687
+ if (!terminal.isIterm) return null;
1688
+ const result = checkItermOptionKey();
1689
+ if (!result.checked) return null;
1690
+ if (result.allCorrect) {
1691
+ return { name: "Right Option Key", status: "ok", detail: "Esc+" };
1692
+ }
1693
+ const profiles = result.incorrectProfiles.map((p) => `"${p}"`).join(", ");
1694
+ return {
1695
+ name: "Right Option Key",
1696
+ status: "warn",
1697
+ detail: `Not Esc+ for ${profiles}`,
1698
+ fix: "iTerm2 \u2192 Settings \u2192 Profiles \u2192 Keys \u2192 Right Option Key \u2192 Esc+"
1699
+ };
1700
+ }
1701
+ function checkBeginCommand() {
1702
+ if (isBeginCommandInstalled()) {
1703
+ return { name: "/begin command", status: "ok", detail: "Installed" };
1704
+ }
1705
+ return {
1706
+ name: "/begin command",
1707
+ status: "warn",
1708
+ detail: "Not installed",
1709
+ fix: "sisyphus setup"
1710
+ };
1711
+ }
1712
+ function checkNvim() {
1713
+ if (!isNvimAvailable()) {
1714
+ const fix = process.platform === "darwin" ? "brew install neovim" : "Install neovim from https://neovim.io";
1715
+ return { name: "nvim", status: "warn", detail: "Not installed", fix };
1716
+ }
1717
+ try {
1718
+ const version = execSync8("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
1719
+ return { name: "nvim", status: "ok", detail: version ?? "installed" };
1720
+ } catch {
1721
+ return { name: "nvim", status: "ok", detail: "installed" };
1722
+ }
1723
+ }
1360
1724
  var SYMBOLS = { ok: "\u2713", warn: "!", fail: "\u2717" };
1361
1725
  function registerDoctor(program2) {
1362
1726
  program2.command("doctor").description("Check sisyphus installation health").action(async () => {
1727
+ const itermCheck = checkItermRightOptionKey();
1363
1728
  const checks = [
1364
1729
  checkNodeVersion(),
1365
1730
  checkClaudeCli(),
1366
1731
  checkGit(),
1367
1732
  checkTmux(),
1368
1733
  checkTmuxVersion(),
1734
+ checkTerminal(),
1735
+ ...itermCheck ? [itermCheck] : [],
1369
1736
  checkGlobalDir(),
1370
1737
  checkDaemonInstalled(),
1371
1738
  checkDaemonRunning(),
1372
1739
  checkCycleScript(),
1373
- checkTmuxKeybind()
1740
+ checkTmuxKeybind(),
1741
+ checkBeginCommand(),
1742
+ checkNvim()
1374
1743
  ];
1375
1744
  let hasIssues = false;
1376
1745
  for (const c of checks) {
@@ -1400,135 +1769,959 @@ function registerCompanionContext(program2) {
1400
1769
  }
1401
1770
 
1402
1771
  // src/cli/commands/getting-started.ts
1403
- function registerGettingStarted(program2) {
1404
- program2.command("getting-started").description("Show a complete guide to using sisyphus effectively").action(() => {
1405
- const hasTmux = isTmuxInstalled();
1406
- const inTmux = !!process.env["TMUX"];
1407
- const lines = [
1408
- "",
1409
- " \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
1410
- " \u2551 Getting Started with Sisyphus \u2551",
1411
- " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
1412
- "",
1413
- " Sisyphus is a multi-agent orchestration daemon for Claude Code.",
1414
- " It breaks large tasks into subtasks, spawns parallel Claude agents,",
1415
- " and coordinates their work across multiple cycles \u2014 autonomously.",
1416
- "",
1417
- " \u2500\u2500\u2500 Tmux \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1418
- ""
1419
- ];
1420
- if (!hasTmux) {
1421
- lines.push(
1422
- " \u26A0 tmux is not installed. Sisyphus requires tmux.",
1423
- " Install it: brew install tmux (macOS)",
1424
- " apt install tmux (Linux)",
1425
- ""
1426
- );
1427
- } else if (!inTmux) {
1428
- lines.push(
1429
- " \u26A0 You are not inside a tmux session.",
1430
- " Sisyphus spawns agent panes inside tmux, so you should",
1431
- " start a tmux session before running sisyphus:",
1432
- "",
1433
- " tmux new-session",
1434
- ""
1435
- );
1772
+ import { execSync as execSync9 } from "child_process";
1773
+ import { dirname as dirname3, join as join6 } from "path";
1774
+ import { fileURLToPath as fileURLToPath3 } from "url";
1775
+ function templatePath(name) {
1776
+ return join6(dirname3(fileURLToPath3(import.meta.url)), "templates", name);
1777
+ }
1778
+ function isClaudeCode() {
1779
+ return !!process.env["CLAUDECODE"];
1780
+ }
1781
+ function printNonClaudeMessage() {
1782
+ console.log(`
1783
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1784
+ \u2551 sisyphus getting-started \u2014 Interactive Tutorial \u2551
1785
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
1786
+
1787
+ This command provides an interactive tutorial best experienced
1788
+ inside Claude Code. Claude will walk you through setup step by step.
1789
+
1790
+ To start:
1791
+ 1. Open Claude Code: claude
1792
+ 2. Run: sisyphus getting-started
1793
+
1794
+ If you just want the quick reference, run:
1795
+ sisyphus --help
1796
+ sisyphus doctor
1797
+ `);
1798
+ }
1799
+ function printStep0() {
1800
+ const tmuxInstalled = isTmuxInstalled();
1801
+ const inTmux = !!process.env["TMUX"];
1802
+ let tmuxSession = null;
1803
+ if (process.env["TMUX"]) {
1804
+ const segments = process.env["TMUX"].split(",");
1805
+ const pathPart = segments[0];
1806
+ if (pathPart) {
1807
+ const parts = pathPart.split("/");
1808
+ tmuxSession = parts[parts.length - 1] || null;
1809
+ }
1810
+ }
1811
+ console.log(`
1812
+ <claude-instructions>
1813
+ # Sisyphus Getting Started \u2014 Step 0: Entry & Tmux Gate
1814
+
1815
+ ## Environment Data
1816
+ - tmuxInstalled: ${tmuxInstalled}
1817
+ - inTmux: ${inTmux}
1818
+ - tmuxSession: ${tmuxSession ?? "none"}
1819
+
1820
+ ## Tutorial Overview
1821
+
1822
+ This tutorial has 6 steps. Share this overview so the user knows what's coming and can skip ahead:
1823
+
1824
+ | Step | Topic | Command |
1825
+ |------|-------|---------|
1826
+ | 0 | Entry & tmux gate (you are here) | \`sisyphus getting-started\` |
1827
+ | 1 | Tmux basics \u2014 sessions, panes, navigation | \`--tutorial 1\` |
1828
+ | 2 | Nvim basics \u2014 open, save, quit (optional) | \`--tutorial 2\` |
1829
+ | 3 | Sisyphus concepts \u2014 session model & keybinds | \`--tutorial 3\` |
1830
+ | 4 | Live demo \u2014 launch and observe a real session | \`--tutorial 4\` |
1831
+ | 5 | What's next \u2014 real usage guidance & suggestions | \`--tutorial 5\` |
1832
+
1833
+ Tell the user they can skip to any step with \`sisyphus getting-started --tutorial <N>\`.
1834
+
1835
+ ## Instructions for Claude
1836
+
1837
+ You are guiding a user through the Sisyphus interactive tutorial.
1838
+
1839
+ ### First: Ask if they want the tutorial
1840
+
1841
+ Ask the user if they'd like the interactive walkthrough. If they decline, give this quick summary and stop:
1842
+
1843
+ > Sisyphus is a multi-agent orchestrator for Claude Code. Start a session with \`sisyphus start "task"\`,
1844
+ > monitor with \`sisyphus dashboard\`, and check health with \`sisyphus doctor\`.
1845
+
1846
+ ### If they want the tutorial:
1847
+
1848
+ **Case 1: tmux is NOT installed (tmuxInstalled: false)**
1849
+ - Explain what tmux is in 2-3 sentences: "tmux is a terminal multiplexer \u2014 it lets you split your terminal into multiple panes and keep sessions running in the background. Sisyphus uses it to give each agent its own workspace."
1850
+ - Show this ASCII diagram:
1851
+
1852
+ \`\`\`
1853
+ Regular terminal: With tmux:
1854
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1855
+ \u2502 \u2502 \u2502 Agent\u2502 Agent\u2502
1856
+ \u2502 one view \u2502 \u2502 001 \u2502 002 \u2502
1857
+ \u2502 \u2502 \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1858
+ \u2502 \u2502 \u2502 Agent\u2502 Orch \u2502
1859
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 003 \u2502 \u2502
1860
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1861
+ \`\`\`
1862
+
1863
+ - Tell them to install tmux:
1864
+ - macOS: \`brew install tmux\`
1865
+ - Linux: \`apt install tmux\` or their package manager
1866
+ - After install, re-run: \`sisyphus getting-started --tutorial 0\` to verify
1867
+
1868
+ **Case 2: tmux installed but NOT in a tmux session (inTmux: false)**
1869
+ - Tell the user they need to be inside a tmux session for the tutorial
1870
+ - Have them run: \`tmux new-session\`
1871
+ - Then resume the conversation with Claude in the new tmux session: \`claude\`
1872
+ - Then re-run: \`sisyphus getting-started --tutorial 0\` to verify
1873
+
1874
+ **Case 3: In tmux (inTmux: true)**
1875
+ - Tell the user they're all set \u2014 tmux is running
1876
+ - Proceed by running: \`sisyphus getting-started --tutorial 1\`
1877
+ </claude-instructions>
1878
+ `);
1879
+ }
1880
+ function printStep1() {
1881
+ console.log(`
1882
+ <claude-instructions>
1883
+ # Sisyphus Getting Started \u2014 Step 1: Tmux Basics
1884
+
1885
+ ## Instructions for Claude
1886
+
1887
+ Teach the user tmux fundamentals. Be conversational and encouraging.
1888
+
1889
+ ### 1. Explain the concepts with diagrams
1890
+
1891
+ **Sessions, Windows, and Panes:**
1892
+
1893
+ \`\`\`
1894
+ tmux session "work"
1895
+ \u251C\u2500\u2500 window 1: "code"
1896
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1897
+ \u2502 \u2502 pane 1 \u2502 pane 2 \u2502
1898
+ \u2502 \u2502 (editor)\u2502 (tests) \u2502
1899
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1900
+ \u2514\u2500\u2500 window 2: "servers"
1901
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1902
+ \u2502 pane 1 \u2502
1903
+ \u2502 (dev server) \u2502
1904
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1905
+ \`\`\`
1906
+
1907
+ - **Session**: A collection of windows. Persists even if you close the terminal.
1908
+ - **Window**: Like a tab. Each window fills the screen.
1909
+ - **Pane**: A split within a window. Sisyphus puts each agent in its own pane.
1910
+
1911
+ ### 2. Hands-on: Create a test split
1912
+
1913
+ Run this command for the user:
1914
+ \`\`\`
1915
+ tmux split-window -h
1916
+ \`\`\`
1917
+
1918
+ Tell them: "I just split your terminal. You should see two panes side by side."
1919
+
1920
+ Explain navigation:
1921
+ - \`Ctrl+l\`: move to the right pane
1922
+ - \`Ctrl+h\`: move to the left pane
1923
+ - \`Ctrl+j\`: move to the pane below
1924
+ - \`Ctrl+k\`: move to the pane above
1925
+ - No prefix key needed \u2014 just hold Ctrl and press the direction letter
1926
+ - For windows: \`Ctrl+n\` next window, \`Ctrl+p\` previous window
1927
+
1928
+ Ask them to try navigating between panes.
1929
+
1930
+ ### 3. Clean up the test pane
1931
+
1932
+ Once they confirm they can navigate, close the extra pane:
1933
+ \`\`\`
1934
+ tmux kill-pane -t {the other pane}
1935
+ \`\`\`
1936
+
1937
+ Or tell them they can type \`exit\` in the extra pane to close it.
1938
+
1939
+ ### 4. Teach essential commands
1940
+
1941
+ - **Detach**: \`Ctrl-b d\` \u2014 leaves tmux running in background, returns to normal terminal
1942
+ - **Reattach**: \`tmux attach\` (or \`tmux a\`) \u2014 reconnects to the running session
1943
+ - **Scroll up/down**: \`Ctrl+u\` / \`Ctrl+d\` \u2014 scroll half-page up/down (no prefix needed). Press \`q\` to exit scroll mode.
1944
+ - **New window**: \`Ctrl-b n\` \u2014 opens a new window in the current directory
1945
+ - **Kill pane**: \`Ctrl-b x\` \u2014 closes the current pane and rebalances layout
1946
+ - **Re-tile**: \`Ctrl-b =\` \u2014 rebalance all panes to equal widths
1947
+
1948
+ ### 5. Verification
1949
+
1950
+ Ask the user to confirm: "Can you navigate between panes with Ctrl+h and Ctrl+l?"
1951
+
1952
+ Once confirmed, proceed:
1953
+ \`\`\`
1954
+ sisyphus getting-started --tutorial 2
1955
+ \`\`\`
1956
+ </claude-instructions>
1957
+ `);
1958
+ }
1959
+ function printStep2() {
1960
+ const nvimInstalled = isNvimAvailable();
1961
+ console.log(`
1962
+ <claude-instructions>
1963
+ # Sisyphus Getting Started \u2014 Step 2: Nvim Basics
1964
+
1965
+ ## Environment Data
1966
+ - nvimInstalled: ${nvimInstalled}
1967
+
1968
+ ## Instructions for Claude
1969
+
1970
+ This step is OPTIONAL. Nvim is useful for reviewing and editing files when you jump into agent panes, but not required.
1971
+
1972
+ Note: The sisyphus dashboard has keys that auto-open files in nvim \u2014 users don't need to know how to open files from the command line. Focus on what they'll need once they're INSIDE nvim.
1973
+
1974
+ ### If nvim is NOT installed (nvimInstalled: false)
1975
+
1976
+ Ask the user: "Neovim is handy for reviewing and editing files in tmux panes. Want me to install it, or skip this step?"
1977
+
1978
+ - **Install**: Run \`brew install neovim\` (macOS) or suggest their package manager
1979
+ - **Skip**: That's fine \u2014 they can use \`cat\`, \`less\`, or any editor they prefer. Proceed to step 3.
1980
+
1981
+ ### If nvim IS installed (nvimInstalled: true)
1982
+
1983
+ Briefly explain the key concept \u2014 nvim has two modes:
1984
+
1985
+ - **Normal mode** (default): Keys are commands, not text. This is where you navigate.
1986
+ - **Insert mode**: Press \`i\` to enter. Now you type normally. \`Esc\` goes back to normal.
1987
+
1988
+ Then tell the user: "I'm going to open an interactive tutorial file in a pane to your right. It walks you through everything \u2014 navigation, editing, saving. Follow the instructions inside the file."
1989
+
1990
+ Open the bundled tutorial file in a split pane:
1991
+ \`\`\`
1992
+ cp ${templatePath("nvim-tutorial.txt")} /tmp/sisyphus-nvim-tutorial.txt
1993
+ tmux split-window -h "nvim /tmp/sisyphus-nvim-tutorial.txt"
1994
+ \`\`\`
1995
+
1996
+ Tell them to click on the right pane (or \`Ctrl+l\`) and follow the instructions in the file. When they \`:wq\` or \`ZZ\`, the pane closes and they're back in Claude.
1997
+
1998
+ Tell them: "When you jump into an agent's pane and the dashboard opens a file, you'll land in normal mode. Now you know how to look around, make edits, and get out."
1999
+
2000
+ ### Verification
2001
+
2002
+ Ask if they were able to edit and save the file (or if they skipped).
2003
+
2004
+ Proceed:
2005
+ \`\`\`
2006
+ sisyphus getting-started --tutorial 3
2007
+ \`\`\`
2008
+ </claude-instructions>
2009
+ `);
2010
+ }
2011
+ function printStep3() {
2012
+ let rightOptionKeyStatus = "unknown";
2013
+ const terminal = detectTerminal();
2014
+ if (!terminal.isIterm) {
2015
+ rightOptionKeyStatus = "not-iterm";
2016
+ } else {
2017
+ const result = checkItermOptionKey();
2018
+ if (!result.checked) {
2019
+ rightOptionKeyStatus = "could-not-check";
2020
+ } else if (result.allCorrect) {
2021
+ rightOptionKeyStatus = "ok";
1436
2022
  } else {
1437
- lines.push(
1438
- " \u2713 You are inside tmux. Good to go.",
1439
- ""
1440
- );
2023
+ rightOptionKeyStatus = `incorrect:${result.incorrectProfiles.join(",")}`;
1441
2024
  }
1442
- lines.push(
1443
- " \u2500\u2500\u2500 What Makes a Good Sisyphus Task \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1444
- "",
1445
- " Sisyphus is built for BIG tasks \u2014 the kind that would take",
1446
- " multiple cycles of orchestration and parallel agent work.",
1447
- " If you could do it with a single Claude Code session in plan",
1448
- " mode, it's too small for sisyphus.",
1449
- "",
1450
- " Good tasks:",
1451
- ' \u2022 "Implement this feature" @path/to/requirements.md',
1452
- ' \u2022 "Do a deep dive on all SEO/AEO optimizations and',
1453
- ' systematically apply them across the site"',
1454
- " \u2022 Large-scale refactors spanning dozens of files",
1455
- " \u2022 Full feature builds from written requirements",
1456
- "",
1457
- " Too small for sisyphus:",
1458
- ' \u2022 "Add these 3 UI components to the page"',
1459
- ' \u2022 "Fix this bug in auth.ts"',
1460
- " \u2022 Anything a single Claude session handles comfortably",
1461
- "",
1462
- " Tasks don't need to be hyper-specific \u2014 broad but meaningful",
1463
- " tasks work great because the orchestrator will plan the approach.",
1464
- " What matters is SCALE, not specificity.",
1465
- "",
1466
- " For best results, write requirements and reference them directly:",
1467
- "",
1468
- ' sisyphus start "Implement this @path/to/requirements.md"',
1469
- "",
1470
- " \u2500\u2500\u2500 How It Works \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1471
- "",
1472
- ' 1. You run: sisyphus start "task description"',
1473
- " 2. An orchestrator Claude reviews the task and creates a roadmap",
1474
- " 3. It spawns agent Claude instances in parallel tmux panes",
1475
- " 4. Agents work independently, then submit reports when done",
1476
- " 5. The orchestrator respawns with fresh context, reviews progress,",
1477
- " and kicks off the next cycle of work",
1478
- " 6. This repeats until the orchestrator marks the task complete",
1479
- "",
1480
- " The orchestrator is stateless \u2014 it gets killed after each cycle",
1481
- " and respawned fresh with the full session state. This means it",
1482
- " never runs out of context, no matter how many cycles a task takes.",
1483
- "",
1484
- " \u2500\u2500\u2500 Monitoring (Important!) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1485
- "",
1486
- " Sisyphus sessions should be actively monitored. Agents can get",
1487
- " stuck waiting for input, fail to submit reports, or take the",
1488
- " roadmap in a direction you don't want. The dashboard is your",
1489
- " primary tool for staying on top of things:",
1490
- "",
1491
- " sisyphus dashboard",
1492
- "",
1493
- " Key dashboard actions:",
1494
- " m \u2014 Message the orchestrator to steer direction",
1495
- " w \u2014 Jump directly into the sisyphus tmux session",
1496
- " to see exactly what agents are doing",
1497
- "",
1498
- " Use `m` to course-correct from the dashboard, and `w` when you",
1499
- " need the most granular view of agent activity.",
1500
- "",
1501
- " \u2500\u2500\u2500 Commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1502
- "",
1503
- " Start & monitor:",
1504
- ' sisyphus start "task" Start a session',
1505
- ' sisyphus start "task @requirements.md" Start referencing requirements',
1506
- " sisyphus status [id] Show session status",
1507
- " sisyphus list List all sessions",
1508
- " sisyphus dashboard Open TUI dashboard",
1509
- "",
1510
- " Control:",
1511
- ' sisyphus resume <id> "instructions" Resume with new direction',
1512
- " sisyphus kill <id> Stop a session",
1513
- "",
1514
- " Health:",
1515
- " sisyphus doctor Check installation health",
1516
- " tail -f ~/.sisyphus/daemon.log Watch daemon logs",
1517
- "",
1518
- " \u2500\u2500\u2500 Next Steps \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1519
- "",
1520
- " 1. Run `sisyphus doctor` to check your setup",
1521
- " 2. Start a tmux session: `tmux new-session`",
1522
- ' 3. Try it: `sisyphus start "your task description"`',
1523
- ""
1524
- );
1525
- console.log(lines.join("\n"));
2025
+ }
2026
+ console.log(`
2027
+ <claude-instructions>
2028
+ # Sisyphus Getting Started \u2014 Step 3: Sisyphus Concepts & Keybinds
2029
+
2030
+ ## Environment Data
2031
+ - rightOptionKeyStatus: ${rightOptionKeyStatus}
2032
+
2033
+ ## Instructions for Claude
2034
+
2035
+ ### 1. CRITICAL FIRST: Right Option Key Setup
2036
+
2037
+ **This must be done before anything else.** Sisyphus keybinds use the Option key as "Meta". By default, macOS terminals send special characters when you press Option (e.g., Option+s types \`\xDF\`). We need the RIGHT Option key to send escape sequences instead.
2038
+
2039
+ **Check the environment data above:**
2040
+
2041
+ - **rightOptionKeyStatus: ok** \u2014 They're all set, briefly confirm and move on.
2042
+
2043
+ - **rightOptionKeyStatus: incorrect:ProfileName** \u2014 Walk them through the fix:
2044
+
2045
+ > Your Right Option key isn't configured correctly yet. Here's how to fix it:
2046
+ >
2047
+ > 1. Open **iTerm2 Settings** (Cmd+,)
2048
+ > 2. Go to **Profiles** \u2192 select your profile (shown above)
2049
+ > 3. Click the **Keys** tab
2050
+ > 4. At the bottom, find **Right Option Key**
2051
+ > 5. Change it from **Normal** to **Esc+**
2052
+ >
2053
+ > \`\`\`
2054
+ > \u250C\u2500 iTerm2 Settings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2055
+ > \u2502 Profiles > Keys \u2502
2056
+ > \u2502 \u2502
2057
+ > \u2502 Right Option Key: \u2502
2058
+ > \u2502 \u25CB Normal (sends special chars like \xDF) \u2502
2059
+ > \u2502 \u25CF Esc+ (sends escape sequences) \u2190 \u2713 \u2502
2060
+ > \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2061
+ > \`\`\`
2062
+ >
2063
+ > **Why right and not left?** You'll still want the left Option key for
2064
+ > typing special characters (accents, symbols). The right Option key
2065
+ > becomes your "Meta" key for tmux/sisyphus keybinds.
2066
+
2067
+ After they change it, have them verify by re-running \`sisyphus doctor\` \u2014 look for "Right Option Key: Esc+".
2068
+
2069
+ - **rightOptionKeyStatus: not-iterm** \u2014 They're not using iTerm2. Explain:
2070
+ > Sisyphus keybinds use Option as Meta. In iTerm2 this is configured via
2071
+ > "Right Option Key \u2192 Esc+". For your terminal, look for a similar setting
2072
+ > like "Option sends Meta" or "Option sends Esc+". Without this, pressing
2073
+ > Option+s will type a special character instead of triggering the keybind.
2074
+
2075
+ - **rightOptionKeyStatus: could-not-check** or **unknown** \u2014 Ask them to manually check:
2076
+ > Press Option+s in your terminal. If you see \`\xDF\` (or another special character),
2077
+ > your Option key needs to be reconfigured. In iTerm2: Settings \u2192 Profiles \u2192 Keys \u2192
2078
+ > Right Option Key \u2192 Esc+.
2079
+
2080
+ ### 2. Explain the session model
2081
+
2082
+ This is the KEY concept. Use the diagram and be clear:
2083
+
2084
+ \`\`\`
2085
+ YOUR tmux session ("work") Sisyphus tmux session ("sisyphus-abc123")
2086
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2087
+ \u2502 \u2502 \u2502 Orch \u2502 Agent \u2502 Agent \u2502
2088
+ \u2502 Your normal work \u2502 \u2190\u2500\u2500\u2192 \u2502 (yellow)\u2502 (blue) \u2502 (green) \u2502
2089
+ \u2502 + dashboard \u2502 \u2502 \u2502 \u2502 \u2502
2090
+ \u2502 \u2502 \u2502 Plans & \u2502 Writes \u2502 Writes \u2502
2091
+ \u2502 \u2502 \u2502 assigns \u2502 code \u2502 tests \u2502
2092
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2093
+ \`\`\`
2094
+
2095
+ Key points:
2096
+ - Sisyphus creates its OWN tmux session \u2014 it doesn't clutter yours
2097
+ - The **orchestrator** (yellow pane) plans work and spawns agents
2098
+ - **Agents** (colored panes) work in parallel on subtasks
2099
+ - Your session stays clean \u2014 you get a **dashboard** for monitoring
2100
+ - You can jump between your session and the sisyphus session to observe
2101
+
2102
+ ### 3. Teach keybinds
2103
+
2104
+ Two keybinds to remember (both use the RIGHT Option key):
2105
+
2106
+ | Keybind | Action |
2107
+ |---------|--------|
2108
+ | Right Option + s | Cycle through sisyphus sessions |
2109
+ | Right Option + Shift + s | Jump back to dashboard |
2110
+
2111
+ ### 4. Verify keybinds are installed
2112
+
2113
+ Run \`sisyphus doctor\` and check the output. Look for:
2114
+ - "Cycle script" \u2014 should be \u2713
2115
+ - "Tmux keybind" \u2014 should be \u2713
2116
+ - "Right Option Key" \u2014 should be "Esc+"
2117
+
2118
+ If cycle script or keybind is missing, run: \`sisyphus setup-keybind\`
2119
+
2120
+ ### 5. Test the keybind
2121
+
2122
+ Have the user try pressing Right Option + s. Nothing should happen yet (no sisyphus session running) \u2014 and that's fine. The important thing is no special character appears.
2123
+
2124
+ If they see \`\xDF\` or similar, circle back to the Right Option Key setup above.
2125
+
2126
+ ### 6. Verification
2127
+
2128
+ Confirm:
2129
+ - They understand the two-session model (their session vs sisyphus session)
2130
+ - \`sisyphus doctor\` shows keybinds installed AND Right Option Key: Esc+
2131
+ - Right Option + s doesn't produce a special character
2132
+
2133
+ Proceed:
2134
+ \`\`\`
2135
+ sisyphus getting-started --tutorial 4
2136
+ \`\`\`
2137
+ </claude-instructions>
2138
+ `);
2139
+ }
2140
+ function printStep4() {
2141
+ console.log(`
2142
+ <claude-instructions>
2143
+ # Sisyphus Getting Started \u2014 Step 4: Demo Session
2144
+
2145
+ ## Instructions for Claude
2146
+
2147
+ This is the grand finale \u2014 a live demo session.
2148
+
2149
+ ### 1. Health check
2150
+
2151
+ Run \`sisyphus doctor\` first. If any checks are failing, help the user fix them before proceeding.
2152
+ All core checks (tmux, daemon, keybinds) should be \u2713.
2153
+
2154
+ ### 2. BEFORE launching: Teach navigation
2155
+
2156
+ **This is critical.** When \`sisyphus start\` runs, it auto-opens the dashboard in a new tmux window. The user will suddenly be looking at the dashboard and may feel "stuck". Teach them how to navigate BEFORE launching:
2157
+
2158
+ Explain clearly:
2159
+
2160
+ > Before we launch, you need to know how to move between tmux windows. Right now you're in a window with Claude. When sisyphus starts, it'll open a dashboard in a new window. Think of windows like tabs:
2161
+ >
2162
+ > \`\`\`
2163
+ > Window 1 (you are here) Window 2 (dashboard)
2164
+ > \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2165
+ > \u2502 Claude Code \u2502 \u2502 Sisyphus \u2502
2166
+ > \u2502 (this session) \u2502 \u2192 \u2502 Dashboard \u2502
2167
+ > \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2168
+ > Ctrl+n \u2192 \u2190 Ctrl+p
2169
+ > \`\`\`
2170
+ >
2171
+ > - **\`Ctrl+n\`** \u2014 next window (go to dashboard)
2172
+ > - **\`Ctrl+p\`** \u2014 previous window (come back here)
2173
+ >
2174
+ > And remember from step 3:
2175
+ > - **Right Option + s** \u2014 jump to the sisyphus agent session (where you can watch agents work live)
2176
+ > - **Right Option + Shift + s** \u2014 jump back to dashboard
2177
+
2178
+ Have the user confirm they understand these keybinds before proceeding.
2179
+
2180
+ ### 3. Set expectations, copy demo app, and launch
2181
+
2182
+ First, copy the demo todo app to a temp directory and init a git repo (sisyphus needs git):
2183
+ \`\`\`
2184
+ rm -rf /tmp/sisyphus-tutorial-demo
2185
+ cp -r ${templatePath("tutorial-demo")} /tmp/sisyphus-tutorial-demo
2186
+ git -C /tmp/sisyphus-tutorial-demo init
2187
+ git -C /tmp/sisyphus-tutorial-demo add -A
2188
+ git -C /tmp/sisyphus-tutorial-demo commit -m "Initial todo app"
2189
+ \`\`\`
2190
+
2191
+ Tell the user:
2192
+
2193
+ > I've set up a small todo app in /tmp/sisyphus-tutorial-demo \u2014 a Node.js API
2194
+ > with a few files. I'm going to launch sisyphus on it. Here's what will happen:
2195
+ > 1. The dashboard opens automatically (you'll be switched to it)
2196
+ > 2. Press **Ctrl+p** to come back here to Claude \u2014 I'll guide you through what to watch
2197
+ > 3. The session takes a few minutes. You can watch agents work live!
2198
+
2199
+ Then launch from the demo directory:
2200
+ \`\`\`
2201
+ cd /tmp/sisyphus-tutorial-demo && sisyphus 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."
2202
+ \`\`\`
2203
+
2204
+ After launching, tell them:
2205
+
2206
+ > The dashboard just opened. Press **Ctrl+p** to come back here \u2014 I'll provide live commentary as the session runs so you know what's happening.
2207
+
2208
+ Wait for them to confirm they're back, then start live commentary.
2209
+
2210
+ ### 4. Live commentary loop
2211
+
2212
+ **This is the most important part of the demo.** Don't just launch and wait \u2014 actively narrate.
2213
+
2214
+ Once the user is back, start a polling loop. Every ~45 seconds, run \`sisyphus status --verbose <session-id>\` and provide SHORT, contextual commentary about what's happening. The \`--verbose\` flag shows agent instructions, full roadmap, cycle logs, and live pane output from the orchestrator and running agents \u2014 use this rich data to narrate what's actually happening, not just phase names.
2215
+
2216
+ **How to narrate each phase:**
2217
+
2218
+ - **Cycle 1, no agents yet**: "The orchestrator is reading the codebase and planning. It's figuring out how to split the work. Check the dashboard (\`Ctrl+n\`) \u2014 you'll see the roadmap updating."
2219
+
2220
+ - **Agents spawning**: "Agents just spawned! You should see new panes appearing. Try \`Right Option + s\` to jump to the sisyphus session and watch them work. Each colored pane is an independent Claude instance."
2221
+
2222
+ - **Agents working**: "Agent-001 is working on [X], Agent-002 is on [Y]. They're working in parallel \u2014 this is the key advantage of sisyphus. Jump over and watch if you like (\`Right Option + s\`)."
2223
+
2224
+ - **Agents submitting**: "Agent-001 just submitted its report! [N] more to go. When all agents finish, the orchestrator will respawn to review."
2225
+
2226
+ - **Between cycles**: "All agents done. The orchestrator is respawning with fresh context to review the reports and decide what's next. This is the cycle boundary \u2014 the orchestrator never runs out of context because it starts fresh each time."
2227
+
2228
+ - **Completion**: "The session is complete! Let me show you the results."
2229
+
2230
+ **Important:**
2231
+ - Keep commentary to 1-3 sentences per check \u2014 don't wall-of-text
2232
+ - Remind them of navigation keys when relevant ("jump over with Right Option + s to see this live")
2233
+ - If agents are still working with no change, say so briefly ("Still working... Agent-001 is the furthest along")
2234
+ - Reference specific agent names and tasks from the status output
2235
+ - Stop polling when status shows "completed"
2236
+
2237
+ Between polls, encourage the user to explore:
2238
+ > "While we wait, try jumping around: \`Ctrl+n\` for dashboard, \`Right Option + s\` for the agent session, \`Right Option + Shift + s\` to jump back. I'll keep narrating here."
2239
+
2240
+ ### 5. After completion
2241
+
2242
+ Once the session shows "completed":
2243
+
2244
+ - Show them what the agents built: \`cd /tmp/sisyphus-tutorial-demo && git log --oneline\`
2245
+ - Run the tests to prove the work: \`cd /tmp/sisyphus-tutorial-demo && node --test test.js\`
2246
+ - Show the session artifacts: find the session dir in \`.sisyphus/sessions/\` and show \`roadmap.md\`
2247
+ - Explain: "Every session creates a roadmap, agent reports, and logs \u2014 all stored in .sisyphus/sessions/"
2248
+
2249
+ ### 6. Proceed to wrap-up
2250
+
2251
+ Tell the user the demo is done. Then run:
2252
+ \`\`\`
2253
+ sisyphus getting-started --tutorial 5
2254
+ \`\`\`
2255
+ </claude-instructions>
2256
+ `);
2257
+ }
2258
+ function printStep5() {
2259
+ let recentCommits = "";
2260
+ let topLevelFiles = "";
2261
+ try {
2262
+ recentCommits = execSync9("git log --oneline -15 2>/dev/null", { encoding: "utf-8" }).trim();
2263
+ } catch {
2264
+ }
2265
+ try {
2266
+ topLevelFiles = execSync9("ls -1 2>/dev/null", { encoding: "utf-8" }).trim();
2267
+ } catch {
2268
+ }
2269
+ console.log(`
2270
+ <claude-instructions>
2271
+ # Sisyphus Getting Started \u2014 Step 5: What's Next
2272
+
2273
+ ## Codebase Context
2274
+ <recent-commits>
2275
+ ${recentCommits || "(no git repo detected)"}
2276
+ </recent-commits>
2277
+
2278
+ <top-level-files>
2279
+ ${topLevelFiles || "(could not list)"}
2280
+ </top-level-files>
2281
+
2282
+ ## Instructions for Claude
2283
+
2284
+ ### 1. Congratulate them
2285
+
2286
+ Tell them they've completed the tutorial and recap what they learned:
2287
+ - tmux basics (sessions, panes, navigation)
2288
+ - nvim basics for reviewing files
2289
+ - The sisyphus session model (separate tmux session for orchestrator + agents)
2290
+ - Monitoring with dashboard and keybinds
2291
+ - A live session lifecycle
2292
+
2293
+ ### 2. Navigation cheat sheet
2294
+
2295
+ | Key | Action |
2296
+ |-----|--------|
2297
+ | \`Ctrl+n\` / \`Ctrl+p\` | Next/previous tmux window |
2298
+ | \`Ctrl+h/j/k/l\` | Navigate between panes |
2299
+ | \`Right Option + s\` | Jump to sisyphus agent session |
2300
+ | \`Right Option + Shift + s\` | Jump to dashboard |
2301
+
2302
+ ### 3. How to use sisyphus for REAL work
2303
+
2304
+ This is the most important part. Explain clearly:
2305
+
2306
+ > **Sisyphus is for big, end-to-end features \u2014 the kind that need exploration,
2307
+ > planning, and parallel implementation across multiple systems.**
2308
+ >
2309
+ > You don't need to define the task precisely. Broad is fine \u2014 the orchestrator
2310
+ > will explore the codebase, write specs, plan phases, and break it down itself.
2311
+
2312
+ **Real sisyphus sessions (from production use):**
2313
+ - "Design and implement a human-in-the-loop agent inbox system" \u2014 exploration, spec writing, DB schema, API endpoints, UI components, webhook integration, e2e validation
2314
+ - "Build multi-user organization features \u2014 invites, privilege gating, org switcher, workspace sharing, credit tracking" \u2014 touched auth, DB, API, UI, billing, permissions
2315
+ - "Rework all 5 worker onboarding templates to match production pipeline patterns" \u2014 mapped existing patterns, designed new architecture, implemented across templates, validated with e2e tests
2316
+ - "Autonomous failure detection system across 8 sequential phases" \u2014 monitoring, alerting, recovery, dashboard, with each phase building on the last
2317
+ - "Comprehensive code quality audit \u2014 find and fix dead code, null handling, useless fallbacks" \u2014 systematic codebase-wide analysis and cleanup
2318
+ - "Implement @requirements.md" \u2014 point it at a spec and let it go
2319
+
2320
+ **NOT good for sisyphus:**
2321
+ - Five unrelated small tasks bundled together ("fix the login bug, update the README, add a loading spinner") \u2014 these aren't one feature, they're a todo list
2322
+ - Something Claude Code in plan mode would handle \u2014 plan mode already handles substantial single-engineer work. If it fits in one Claude session, just do it directly.
2323
+ - Quick fixes, bug fixes, small refactors \u2014 use regular Claude Code
2324
+
2325
+ **How to start:**
2326
+ The easiest way is the \`/sisyphus:begin\` slash command inside Claude Code. Just tell Claude
2327
+ what you want to build and it'll hand it off to sisyphus with the right context.
2328
+
2329
+ Or directly: \`sisyphus start "your task" -c "any background context"\`
2330
+
2331
+ ### 4. Suggest real tasks for THEIR codebase
2332
+
2333
+ 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.
2334
+
2335
+ If there are no commits or files (e.g., they ran this from /tmp), skip this section.
2336
+
2337
+ Format as:
2338
+ > Based on your codebase, here are some tasks sisyphus would be great for:
2339
+ > - "..."
2340
+ > - "..."
2341
+
2342
+ ### 5. There's more to learn
2343
+
2344
+ Tell them:
2345
+
2346
+ > There's actually a lot of depth to how sisyphus works \u2014 the design is intentional
2347
+ > and there's real reasoning behind why it does things the way it does. If you want
2348
+ > to understand the philosophy, or you want a deeper rundown on the dashboard,
2349
+ > monitoring, configuration, or how to steer sessions \u2014 just ask and I'll explain.
2350
+
2351
+ If the user says yes or asks to learn more, run \`sisyphus getting-started --explain\`
2352
+ and use its output to explain the system to them conversationally. Don't dump the whole
2353
+ thing \u2014 answer what they're curious about, using the reference as your source material.
2354
+ </claude-instructions>
2355
+ `);
2356
+ }
2357
+ function printExplain() {
2358
+ console.log(`
2359
+ <claude-instructions>
2360
+ # Sisyphus \u2014 Comprehensive Reference
2361
+
2362
+ This is a detailed reference for how sisyphus works. The user asked to understand
2363
+ sisyphus more deeply. Use this to answer their questions conversationally \u2014 don't dump
2364
+ the whole thing. Read through it, then respond to what they're curious about.
2365
+
2366
+ ## Design Philosophy
2367
+
2368
+ Sisyphus is built on specific insights about how to get the best work out of LLM agents.
2369
+ These aren't arbitrary \u2014 each design decision solves a real failure mode.
2370
+
2371
+ ### 1. The Orchestrator as "Human-in-the-Loop"
2372
+
2373
+ When you use Claude Code effectively, YOU are the orchestrator \u2014 you review work,
2374
+ steer direction, break problems down, and assign the next piece. Sisyphus automates
2375
+ that human role. The orchestrator does what a skilled developer does when prompting
2376
+ Claude: explore the codebase, understand the problem, write specs, plan phases,
2377
+ assign focused work, review results, and iterate.
2378
+
2379
+ The strategy layer mirrors how developers actually work on end-to-end features:
2380
+ explore, understand, spec, plan, implement, review, validate. The orchestrator
2381
+ follows this same workflow, but runs it with parallel agents.
2382
+
2383
+ ### 2. Fresh Context Kills Shortcuts
2384
+
2385
+ The orchestrator is KILLED after every cycle and respawned fresh. This is the most
2386
+ important design decision.
2387
+
2388
+ When an LLM accumulates context over a long session, it starts taking shortcuts.
2389
+ It "knows" what it did earlier, so it skips re-reading, assumes things still hold,
2390
+ and builds on stale understanding. A fresh start forces honest reassessment every
2391
+ cycle \u2014 the orchestrator reads the actual state, not its memory of it.
2392
+
2393
+ This is inspired by adversarial training (think GANs) \u2014 better results come from
2394
+ adversarial pressure. Each fresh orchestrator effectively audits the previous cycle's
2395
+ work because it has no stake in defending prior decisions. It sees the roadmap, the
2396
+ reports, the code \u2014 and judges them with fresh eyes.
2397
+
2398
+ ### 3. Single-Focus Agents
2399
+
2400
+ Each agent gets ONE task with a fully self-contained instruction. No context switching,
2401
+ no juggling multiple concerns, no "also while you're there could you..."
2402
+
2403
+ LLMs perform dramatically better when focused. An agent implementing a priority field
2404
+ doesn't think about the stats endpoint. It reads the relevant context, does its one
2405
+ thing well, and reports back. The orchestrator handles decomposition \u2014 agents handle
2406
+ execution.
2407
+
2408
+ ### 4. Shared Context Directory (Saved Research)
2409
+
2410
+ Every session has a context/ directory where agents save research, specs, plans, and
2411
+ design docs. These files persist across ALL cycles and are visible to the orchestrator
2412
+ and subsequent agents.
2413
+
2414
+ This means research is never repeated. Cycle 1 agents explore and write findings to
2415
+ context/explore-auth-system.md. Cycle 3 agents read those findings and build on them.
2416
+ Knowledge accumulates even though the orchestrator itself is stateless.
2417
+
2418
+ ### 5. Two-Layer Planning (Strategy + Roadmap)
2419
+
2420
+ The system maintains two documents at different abstraction levels:
2421
+
2422
+ **strategy.md** \u2014 The high-level problem-solving map. What phases exist, what gates
2423
+ between them, what backtrack paths exist. Updated every few cycles when the shape of
2424
+ work changes. Helps the orchestrator see the forest.
2425
+
2426
+ **roadmap.md** \u2014 Working memory. Updated every cycle. Current Stage, Exit Criteria,
2427
+ Active Context, Next Steps. The orchestrator reads this first each cycle to understand
2428
+ where things stand. Helps the orchestrator see the trees.
2429
+
2430
+ This prevents the failure mode where a single document becomes either too abstract
2431
+ to act on or too detailed to show the big picture.
2432
+
2433
+ ### 6. Adversarial Review Is Built In
2434
+
2435
+ The orchestrator doesn't just implement \u2014 it runs mandatory critique cycles. After
2436
+ implementation, review agents attack different dimensions: code reuse, quality,
2437
+ efficiency, correctness. Fix agents address the findings. Re-review until only nits
2438
+ remain. Multiple agents auditing each other produces better results than any single
2439
+ agent reviewing its own work.
2440
+
2441
+ The rule: never let 2+ stages complete without critique. Small issues compound into
2442
+ architectural problems if unchecked.
2443
+
2444
+ ### 7. Evidence Over Assumptions
2445
+
2446
+ Validation requires PROOF \u2014 command output, test results, HTTP responses. "The code
2447
+ looks correct" is not evidence. "All 14 tests pass" is. This catches the gap between
2448
+ code that looks right and code that works.
2449
+
2450
+ ## Architecture Overview
2451
+
2452
+ \`\`\`
2453
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2454
+ \u2502 USER'S TMUX SESSION \u2502
2455
+ \u2502 \u2502
2456
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2457
+ \u2502 \u2502 Window 1: Claude Code \u2502 \u2502 Window 2: Dashboard (TUI) \u2502 \u2502
2458
+ \u2502 \u2502 \u2502 \u2502 \u2502 \u2502
2459
+ \u2502 \u2502 User's normal work \u2502 \u2502 Real-time session monitor \u2502 \u2502
2460
+ \u2502 \u2502 + this conversation \u2502 \u2502 Roadmap, agents, reports \u2502 \u2502
2461
+ \u2502 \u2502 \u2502 \u2502 Interactive controls \u2502 \u2502
2462
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2463
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2464
+ \u2502 Right Option+s / Right Option+Shift+s
2465
+ \u25BC
2466
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2467
+ \u2502 SISYPHUS TMUX SESSION \u2502
2468
+ \u2502 (created per sisyphus session) \u2502
2469
+ \u2502 \u2502
2470
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2471
+ \u2502 \u2502 Orch \u2502 Agent \u2502 Agent \u2502 Agent \u2502 \u2190 panes \u2502
2472
+ \u2502 \u2502 (yellow) \u2502 (blue) \u2502 (green) \u2502 (magenta)\u2502 \u2502
2473
+ \u2502 \u2502 \u2502 \u2502 \u2502 \u2502 \u2502
2474
+ \u2502 \u2502 Plans, \u2502 Impl \u2502 Tests \u2502 Docs \u2502 \u2190 each is a \u2502
2475
+ \u2502 \u2502 assigns, \u2502 feature \u2502 \u2502 \u2502 Claude Code \u2502
2476
+ \u2502 \u2502 reviews \u2502 \u2502 \u2502 \u2502 instance \u2502
2477
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2478
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2479
+ \u2502
2480
+ \u25BC
2481
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2482
+ \u2502 DAEMON (sisyphusd) \u2502
2483
+ \u2502 Background process via launchd \u2502
2484
+ \u2502 \u2502
2485
+ \u2502 Listens on ~/.sisyphus/daemon.sock \u2502
2486
+ \u2502 Manages session lifecycle, pane monitoring, state persistence \u2502
2487
+ \u2502 Polls panes to detect when agents/orchestrator finish \u2502
2488
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2489
+ \`\`\`
2490
+
2491
+ ## The Session Lifecycle (in detail)
2492
+
2493
+ \`\`\`
2494
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2495
+ \u2502 SESSION LIFECYCLE \u2502
2496
+ \u2502 \u2502
2497
+ \u2502 sisyphus start "task" \u2502
2498
+ \u2502 \u2502 \u2502
2499
+ \u2502 \u25BC \u2502
2500
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 spawn agents \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2501
+ \u2502 \u2502 Orch \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2192 \u2502 Agents work \u2502 \u2502
2502
+ \u2502 \u2502 plans \u2502 then yields \u2502 in parallel \u2502 \u2502
2503
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2504
+ \u2502 \u2502 \u2502 each calls \u2502
2505
+ \u2502 \u2502 orchestrator \u2502 sisyphus submit \u2502
2506
+ \u2502 \u2502 is KILLED \u2502 when done \u2502
2507
+ \u2502 \u2502 \u25BC \u2502
2508
+ \u2502 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2509
+ \u2502 \u2502 \u2502 All agents \u2502 \u2502
2510
+ \u2502 \u2502 \u2502 finished? \u2502 \u2502
2511
+ \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2512
+ \u2502 \u2502 \u2502 yes \u2502
2513
+ \u2502 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2514
+ \u2502 \u2502 \u25BC \u2502
2515
+ \u2502 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2516
+ \u2502 \u2514\u2500\u2500\u2500\u2500 \u2502 Respawn \u2502 Fresh orchestrator with full state \u2502
2517
+ \u2502 next cycle \u2502 Orch \u2502 Reviews reports, plans next cycle \u2502
2518
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2518 \u2502
2519
+ \u2502 \u2502 \u2502
2520
+ \u2502 \u25BC \u2502
2521
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2522
+ \u2502 \u2502 More work \u2502\u2500\u2500yes\u2500\u2500\u2192 \u2502 Spawn \u2502 \u2192 (loop) \u2502
2523
+ \u2502 \u2502 needed? \u2502 \u2502 agents \u2502 \u2502
2524
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2525
+ \u2502 \u2502 no \u2502
2526
+ \u2502 \u25BC \u2502
2527
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2528
+ \u2502 \u2502 sisyphus \u2502 \u2502
2529
+ \u2502 \u2502 complete \u2502 \u2502
2530
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2531
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2532
+ \`\`\`
2533
+
2534
+ **Key insight**: The orchestrator is STATELESS. It gets killed after each yield and
2535
+ respawned fresh with the complete session state (roadmap, agent reports, cycle history).
2536
+ This means it never runs out of context, no matter how many cycles a session takes.
2537
+
2538
+ ## The Dashboard
2539
+
2540
+ The dashboard is a real-time TUI that shows session state. Launch with \`sisyphus dashboard\`
2541
+ or it auto-opens when a session starts.
2542
+
2543
+ **Dashboard sections:**
2544
+ - **Header**: Session ID, status, task description
2545
+ - **Roadmap**: Current strategic plan with checked/unchecked items
2546
+ - **Agents**: List of all agents with status, duration, and report summaries
2547
+ - **Cycles**: Orchestrator cycle history
2548
+ - **Messages**: Recent session messages
2549
+
2550
+ **Dashboard keys:**
2551
+ | Key | Action |
2552
+ |-----|--------|
2553
+ | \`m\` | Message the orchestrator (steer direction mid-session) |
2554
+ | \`w\` | Jump to the sisyphus tmux session (watch agents work) |
2555
+ | \`k\` | Kill the session |
2556
+ | \`r\` | Resume a paused/completed session |
2557
+ | \`q\` | Quit the dashboard |
2558
+ | \`\u2191/\u2193\` | Scroll through content |
2559
+ | \`Tab\` | Cycle through sections |
2560
+
2561
+ **The \`m\` key is the most powerful feature.** You can message the orchestrator at any time
2562
+ to course-correct: "Focus on the API layer first", "Skip the tests for now",
2563
+ "The approach for auth is wrong, use JWT instead". The orchestrator reads these
2564
+ messages when it respawns each cycle.
2565
+
2566
+ ## Monitoring Strategy
2567
+
2568
+ Sisyphus sessions should be actively monitored. Here's what to watch for:
2569
+
2570
+ **Things that go wrong:**
2571
+ - Agents stuck waiting for user input (they're autonomous \u2014 they shouldn't need input)
2572
+ - Agents going down rabbit holes or working on the wrong thing
2573
+ - Merge conflicts between agents touching the same files
2574
+ - Orchestrator spawning too many agents or too few
2575
+ - Agents crashing or getting killed unexpectedly
2576
+
2577
+ **When to intervene:**
2578
+ - Use \`m\` in the dashboard to message the orchestrator with corrections
2579
+ - Use \`sisyphus kill <id>\` to stop a runaway session
2580
+ - Use \`sisyphus resume <id> "new instructions"\` to restart with different direction
2581
+
2582
+ **Useful monitoring commands:**
2583
+ \`\`\`
2584
+ sisyphus status <id> # Quick status check
2585
+ sisyphus status --verbose <id> # Full detail: roadmap, pane output, agent instructions
2586
+ sisyphus dashboard # Interactive TUI
2587
+ tail -f ~/.sisyphus/daemon.log # Daemon activity log
2588
+ \`\`\`
2589
+
2590
+ ## The .sisyphus/ Directory
2591
+
2592
+ Everything sisyphus does lives in a \`.sisyphus/\` directory at the root of your project.
2593
+ This is project-local \u2014 each project gets its own. It contains:
2594
+
2595
+ \`\`\`
2596
+ .sisyphus/
2597
+ \u251C\u2500\u2500 config.json # Project-specific config (model, poll interval, etc.)
2598
+ \u251C\u2500\u2500 orchestrator.md # Optional custom orchestrator prompt override
2599
+ \u2514\u2500\u2500 sessions/
2600
+ \u251C\u2500\u2500 <session-id-1>/ # Each session gets its own directory
2601
+ \u251C\u2500\u2500 <session-id-2>/
2602
+ \u2514\u2500\u2500 ...
2603
+ \`\`\`
2604
+
2605
+ There's also a global directory at \`~/.sisyphus/\` for the daemon socket, PID file,
2606
+ logs, keybind scripts, and global config. But the session state \u2014 the roadmaps,
2607
+ reports, context files, cycle logs \u2014 all lives in your project's \`.sisyphus/sessions/\`.
2608
+
2609
+ ## Session Files
2610
+
2611
+ Every session creates a directory at \`.sisyphus/sessions/<id>/\` with:
2612
+
2613
+ \`\`\`
2614
+ .sisyphus/sessions/<id>/
2615
+ \u251C\u2500\u2500 state.json # Session state (agents, cycles, status)
2616
+ \u251C\u2500\u2500 roadmap.md # Strategic plan (updated by orchestrator each cycle)
2617
+ \u251C\u2500\u2500 goal.md # Original task description
2618
+ \u251C\u2500\u2500 strategy.md # High-level strategy notes
2619
+ \u251C\u2500\u2500 logs/
2620
+ \u2502 \u251C\u2500\u2500 cycle-000.md # What the orchestrator did in cycle 0
2621
+ \u2502 \u251C\u2500\u2500 cycle-001.md # What it did in cycle 1, etc.
2622
+ \u2502 \u2514\u2500\u2500 ...
2623
+ \u251C\u2500\u2500 reports/
2624
+ \u2502 \u251C\u2500\u2500 agent-001-final.md # Agent's final report
2625
+ \u2502 \u251C\u2500\u2500 agent-002-update.md # Agent's progress update
2626
+ \u2502 \u2514\u2500\u2500 ...
2627
+ \u251C\u2500\u2500 prompts/ # System/user prompts sent to orchestrator and agents
2628
+ \u2514\u2500\u2500 context/ # Shared context files for agents
2629
+ \`\`\`
2630
+
2631
+ ## Configuration
2632
+
2633
+ **Global config**: \`~/.sisyphus/config.json\`
2634
+ **Project config**: \`.sisyphus/config.json\` (overrides global)
2635
+
2636
+ Options:
2637
+ - \`model\` \u2014 Claude model for orchestrator and agents
2638
+ - \`orchestratorPrompt\` \u2014 Path to custom orchestrator prompt
2639
+ - \`pollIntervalMs\` \u2014 How often daemon checks pane status (default: 2000)
2640
+
2641
+ ## Starting Sessions \u2014 Best Practices
2642
+
2643
+ **The /sisyphus:begin slash command** is the recommended way to start. Inside Claude Code:
2644
+ \`\`\`
2645
+ /sisyphus:begin
2646
+ \`\`\`
2647
+ Then describe your task. Claude will hand it off with the right context.
2648
+
2649
+ **Direct CLI:**
2650
+ \`\`\`
2651
+ sisyphus start "task description" -c "background context"
2652
+ sisyphus start "Implement @requirements.md" -n my-feature
2653
+ \`\`\`
2654
+
2655
+ **Reference files with @**: \`sisyphus start "Build @docs/spec.md"\` \u2014 the orchestrator
2656
+ will read the referenced file as part of its planning.
2657
+
2658
+ **The -c flag** adds background context the orchestrator sees but doesn't act on directly.
2659
+ Use it for constraints: \`-c "Don't modify the auth module, use the existing API"\`
2660
+
2661
+ **The -n flag** gives the session a human-readable name for easier tracking.
2662
+
2663
+ ## CLI Command Reference
2664
+
2665
+ | Command | Purpose |
2666
+ |---------|---------|
2667
+ | \`sisyphus start "task"\` | Start a new session |
2668
+ | \`sisyphus status [id]\` | Check session status |
2669
+ | \`sisyphus status -v [id]\` | Detailed status with pane output |
2670
+ | \`sisyphus list\` | List all sessions |
2671
+ | \`sisyphus dashboard\` | Open the TUI dashboard |
2672
+ | \`sisyphus resume <id> "msg"\` | Resume with new instructions |
2673
+ | \`sisyphus kill <id>\` | Stop a session |
2674
+ | \`sisyphus doctor\` | Check installation health |
2675
+ | \`sisyphus setup\` | Run setup/onboarding |
2676
+ | \`sisyphus setup-keybind\` | Install tmux keybinds |
2677
+
2678
+ ## Troubleshooting
2679
+
2680
+ **Daemon not running:**
2681
+ \`\`\`
2682
+ sisyphusd restart
2683
+ \`\`\`
2684
+
2685
+ **Keybinds not working (special characters appear):**
2686
+ iTerm2 \u2192 Settings \u2192 Profiles \u2192 Keys \u2192 Right Option Key \u2192 Esc+
2687
+
2688
+ **Agents stuck:** Check \`sisyphus status --verbose <id>\` to see pane output. If an
2689
+ agent is waiting for input, kill the session and restart with clearer instructions.
2690
+
2691
+ **Dashboard not opening:** Run \`sisyphus dashboard\` manually. Must be inside tmux.
2692
+
2693
+ **Session seems hung:** Check \`tail -20 ~/.sisyphus/daemon.log\` for errors.
2694
+ The daemon polls panes every 2s \u2014 if a pane dies unexpectedly, it'll be detected.
2695
+ </claude-instructions>
2696
+ `);
2697
+ }
2698
+ var STEPS = [printStep0, printStep1, printStep2, printStep3, printStep4, printStep5];
2699
+ function registerGettingStarted(program2) {
2700
+ program2.command("getting-started").description("Interactive tutorial (best with Claude Code)").option("--tutorial <step>", "Tutorial step (0-5)", parseInt).option("--explain", "Comprehensive reference for how sisyphus works").action((opts) => {
2701
+ if (opts.explain) {
2702
+ printExplain();
2703
+ return;
2704
+ }
2705
+ if (opts.tutorial !== void 0) {
2706
+ const step = opts.tutorial;
2707
+ if (step < 0 || step > 5 || Number.isNaN(step)) {
2708
+ console.error(`Invalid tutorial step: ${opts.tutorial}. Must be 0-5.`);
2709
+ process.exit(1);
2710
+ }
2711
+ STEPS[step]();
2712
+ return;
2713
+ }
2714
+ if (!isClaudeCode()) {
2715
+ printNonClaudeMessage();
2716
+ return;
2717
+ }
2718
+ printStep0();
1526
2719
  });
1527
2720
  }
1528
2721
 
1529
2722
  // src/cli/commands/init.ts
1530
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
1531
- import { join as join5 } from "path";
2723
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
2724
+ import { join as join7 } from "path";
1532
2725
  var DEFAULT_CONFIG = {};
1533
2726
  var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
1534
2727
 
@@ -1539,19 +2732,19 @@ var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
1539
2732
  function registerInit(program2) {
1540
2733
  program2.command("init").description("Initialize sisyphus configuration for this project").option("--orchestrator", "Also create a custom orchestrator prompt template").action((opts) => {
1541
2734
  const cwd = process.cwd();
1542
- const sisDir = join5(cwd, ".sisyphus");
1543
- const configPath = join5(sisDir, "config.json");
1544
- if (existsSync5(configPath)) {
2735
+ const sisDir = join7(cwd, ".sisyphus");
2736
+ const configPath = join7(sisDir, "config.json");
2737
+ if (existsSync7(configPath)) {
1545
2738
  console.log(`Already initialized: ${configPath}`);
1546
2739
  return;
1547
2740
  }
1548
- mkdirSync3(sisDir, { recursive: true });
1549
- writeFileSync3(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
2741
+ mkdirSync4(sisDir, { recursive: true });
2742
+ writeFileSync4(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
1550
2743
  console.log(`Created ${configPath}`);
1551
2744
  if (opts.orchestrator) {
1552
- const orchPath = join5(sisDir, "orchestrator.md");
1553
- if (!existsSync5(orchPath)) {
1554
- writeFileSync3(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
2745
+ const orchPath = join7(sisDir, "orchestrator.md");
2746
+ if (!existsSync7(orchPath)) {
2747
+ writeFileSync4(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
1555
2748
  console.log(`Created ${orchPath}`);
1556
2749
  }
1557
2750
  }
@@ -1564,6 +2757,94 @@ function registerInit(program2) {
1564
2757
  });
1565
2758
  }
1566
2759
 
2760
+ // src/cli/commands/setup.ts
2761
+ import { execSync as execSync10 } from "child_process";
2762
+ function getTmuxVersion() {
2763
+ try {
2764
+ return execSync10("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
2765
+ } catch {
2766
+ return "installed";
2767
+ }
2768
+ }
2769
+ function printResults(result, daemonOk, keybindMsg) {
2770
+ console.log("");
2771
+ console.log("Setting up Sisyphus...");
2772
+ console.log("");
2773
+ if (result.tmuxInstalled) {
2774
+ const detail = getTmuxVersion();
2775
+ console.log(` \u2713 tmux: ${detail}${result.tmuxAutoInstalled ? " (just installed)" : ""}`);
2776
+ } else {
2777
+ const hint = process.platform === "darwin" ? "Install Homebrew (https://brew.sh) then: brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
2778
+ console.log(` \u2717 tmux: Not installed \u2014 ${hint}`);
2779
+ }
2780
+ if (result.tmuxDefaultsWritten) {
2781
+ console.log(" \u2713 tmux config: Sensible defaults written to ~/.tmux.conf");
2782
+ }
2783
+ if (process.platform === "darwin") {
2784
+ if (result.terminal.isIterm) {
2785
+ console.log(` \u2713 Terminal: ${result.terminal.name}`);
2786
+ } else {
2787
+ const name = result.terminal.name ? result.terminal.name : "unknown";
2788
+ console.log(` \u26A0 Terminal: ${name} \u2014 iTerm2 recommended (https://iterm2.com)`);
2789
+ }
2790
+ }
2791
+ if (result.itermOptionKey.checked) {
2792
+ if (result.itermOptionKey.allCorrect) {
2793
+ console.log(" \u2713 Right Option Key: Esc+");
2794
+ } else {
2795
+ const profiles = result.itermOptionKey.incorrectProfiles.map((p) => `"${p}"`).join(", ");
2796
+ console.log(` \u26A0 Right Option Key: Not set to Esc+ for ${profiles}`);
2797
+ console.log(" Fix: iTerm2 \u2192 Settings \u2192 Profiles \u2192 Keys \u2192 Right Option Key \u2192 Esc+");
2798
+ }
2799
+ }
2800
+ if (daemonOk) {
2801
+ console.log(" \u2713 Daemon: Running");
2802
+ } else {
2803
+ console.log(" \u2717 Daemon: Failed to start");
2804
+ }
2805
+ console.log(` \u2713 Keybindings: ${keybindMsg}`);
2806
+ if (result.command.installed) {
2807
+ console.log(` \u2713 /begin command: ${result.command.path}${result.command.autoInstalled ? " (just installed)" : ""}`);
2808
+ } else {
2809
+ console.log(" \u2717 /begin command: Failed to install");
2810
+ }
2811
+ if (result.nvim.installed) {
2812
+ const extra = result.nvim.autoInstalled ? " (just installed)" : "";
2813
+ console.log(` \u2713 Editor: nvim ${result.nvim.version}${extra}`);
2814
+ if (result.nvim.lazyVimInstalled) {
2815
+ console.log(" \u2713 LazyVim: Starter config installed to ~/.config/nvim/");
2816
+ }
2817
+ } else {
2818
+ console.log(" \u26A0 Editor: nvim not installed");
2819
+ if (process.platform === "darwin") {
2820
+ console.log(" Install: brew install neovim");
2821
+ }
2822
+ }
2823
+ console.log("");
2824
+ console.log("Run 'sisyphus getting-started' for a usage guide.");
2825
+ console.log("");
2826
+ }
2827
+ function registerSetup(program2) {
2828
+ program2.command("setup").description("One-time setup: install dependencies, daemon, keybindings, and commands").action(async () => {
2829
+ const result = runOnboarding();
2830
+ let daemonOk = false;
2831
+ try {
2832
+ await ensureDaemonInstalled();
2833
+ daemonOk = true;
2834
+ } catch {
2835
+ daemonOk = isInstalled();
2836
+ }
2837
+ const keybindResult = setupTmuxKeybind();
2838
+ let keybindMsg;
2839
+ if (keybindResult.status === "installed" || keybindResult.status === "already-installed") {
2840
+ keybindMsg = `${DEFAULT_KEY} (cycle), ${DEFAULT_HOME_KEY} (dashboard)`;
2841
+ } else {
2842
+ keybindMsg = keybindResult.message;
2843
+ }
2844
+ printResults(result, daemonOk, keybindMsg);
2845
+ });
2846
+ }
2847
+
1567
2848
  // src/cli/index.ts
1568
2849
  var nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
1569
2850
  if (nodeVersion < 22) {
@@ -1573,7 +2854,7 @@ if (nodeVersion < 22) {
1573
2854
  var program = new Command();
1574
2855
  program.name("sisyphus").description("tmux-integrated orchestration daemon for Claude Code").version(
1575
2856
  JSON.parse(
1576
- readFileSync4(join6(dirname2(fileURLToPath2(import.meta.url)), "..", "package.json"), "utf-8")
2857
+ readFileSync5(join8(dirname4(fileURLToPath4(import.meta.url)), "..", "package.json"), "utf-8")
1577
2858
  ).version
1578
2859
  );
1579
2860
  program.configureHelp({
@@ -1602,6 +2883,7 @@ registerDoctor(program);
1602
2883
  registerCompanionContext(program);
1603
2884
  registerGettingStarted(program);
1604
2885
  registerInit(program);
2886
+ registerSetup(program);
1605
2887
  program.addHelpText("after", `
1606
2888
  Examples:
1607
2889
  $ sisyphus start "Implement auth system" Start a new session
@@ -1614,15 +2896,11 @@ Run 'sisyphus getting-started' for a complete usage guide.
1614
2896
  `);
1615
2897
  var args = process.argv.slice(2);
1616
2898
  var firstArg = args[0];
1617
- var skipWelcome = ["doctor", "getting-started", "help", "--help", "-h", "init", "uninstall", "--version", "-V"];
1618
- if (!existsSync6(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
1619
- mkdirSync4(globalDir(), { recursive: true });
1620
- console.log("");
1621
- console.log(" Welcome to Sisyphus \u2014 multi-agent orchestration for Claude Code.");
2899
+ var skipWelcome = ["doctor", "getting-started", "help", "--help", "-h", "init", "setup", "uninstall", "--version", "-V"];
2900
+ if (!existsSync8(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
2901
+ mkdirSync5(globalDir(), { recursive: true });
1622
2902
  console.log("");
1623
- console.log(" First time? Run these commands:");
1624
- console.log(" sisyphus doctor Check your setup");
1625
- console.log(" sisyphus getting-started Learn the basics");
2903
+ console.log(" Welcome to Sisyphus. Run 'sisyphus setup' to get started.");
1626
2904
  console.log("");
1627
2905
  }
1628
2906
  program.parseAsync(process.argv).catch((err) => {