sisyphi 1.1.0 → 1.1.8

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
@@ -20,7 +20,9 @@ import {
20
20
 
21
21
  // src/cli/index.ts
22
22
  import { Command } from "commander";
23
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
23
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "fs";
24
+ import { dirname as dirname3, join as join7 } from "path";
25
+ import { fileURLToPath as fileURLToPath3 } from "url";
24
26
 
25
27
  // src/cli/commands/start.ts
26
28
  import { execSync as execSync5 } from "child_process";
@@ -642,24 +644,6 @@ function registerSpawn(program2) {
642
644
  }
643
645
 
644
646
  // src/cli/commands/submit.ts
645
- import { execSync as execSync6 } from "child_process";
646
- function isInWorktree() {
647
- try {
648
- const gitDir = execSync6("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
649
- const commonDir = execSync6("git rev-parse --git-common-dir", { encoding: "utf-8" }).trim();
650
- return gitDir !== commonDir;
651
- } catch {
652
- return false;
653
- }
654
- }
655
- function getUncommittedChanges() {
656
- try {
657
- const status = execSync6("git status --porcelain", { encoding: "utf-8" }).trim();
658
- return status || null;
659
- } catch {
660
- return null;
661
- }
662
- }
663
647
  function registerSubmit(program2) {
664
648
  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) => {
665
649
  assertTmux();
@@ -669,17 +653,6 @@ function registerSubmit(program2) {
669
653
  console.error("Error: provide --session or set SISYPHUS_SESSION_ID (and SISYPHUS_AGENT_ID) environment variables");
670
654
  process.exit(1);
671
655
  }
672
- if (isInWorktree()) {
673
- const changes = getUncommittedChanges();
674
- if (changes) {
675
- console.error("Error: uncommitted changes in worktree. Commit your changes before submitting.");
676
- console.error('\nCommit first:\n git add -A && git commit -m "description of changes"\n');
677
- console.error("Or discard:\n git checkout -- .\n");
678
- console.error("Uncommitted changes:");
679
- console.error(changes);
680
- process.exit(1);
681
- }
682
- }
683
656
  const report = opts.report ?? await readStdin();
684
657
  if (!report) {
685
658
  console.error("Error: provide --report or pipe content via stdin");
@@ -1192,7 +1165,210 @@ function registerSetupKeybind(program2) {
1192
1165
 
1193
1166
  // src/cli/commands/doctor.ts
1194
1167
  import { execSync as execSync7 } from "child_process";
1195
- import { existsSync as existsSync4, statSync } from "fs";
1168
+ import { existsSync as existsSync5, statSync } from "fs";
1169
+
1170
+ // src/cli/onboard.ts
1171
+ import { execSync as execSync6 } from "child_process";
1172
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1173
+ import { homedir as homedir3 } from "os";
1174
+ import { dirname as dirname2, join as join5 } from "path";
1175
+ import { fileURLToPath as fileURLToPath2 } from "url";
1176
+ function detectTerminal() {
1177
+ const termProgram = process.env["TERM_PROGRAM"] || "";
1178
+ const isIterm = termProgram === "iTerm.app" || !!process.env["ITERM_SESSION_ID"];
1179
+ return { name: termProgram || "unknown", isIterm };
1180
+ }
1181
+ function isTmuxAvailable() {
1182
+ try {
1183
+ execSync6("which tmux", { stdio: "pipe" });
1184
+ return true;
1185
+ } catch {
1186
+ return false;
1187
+ }
1188
+ }
1189
+ function isBrewAvailable() {
1190
+ try {
1191
+ execSync6("which brew", { stdio: "pipe" });
1192
+ return true;
1193
+ } catch {
1194
+ return false;
1195
+ }
1196
+ }
1197
+ function tryAutoInstallTmux() {
1198
+ if (!isBrewAvailable()) return false;
1199
+ try {
1200
+ console.log(" Installing tmux via Homebrew...");
1201
+ execSync6("brew install tmux", { stdio: "inherit" });
1202
+ return isTmuxAvailable();
1203
+ } catch {
1204
+ return false;
1205
+ }
1206
+ }
1207
+ function checkItermOptionKey() {
1208
+ if (process.platform !== "darwin") {
1209
+ return { checked: false, allCorrect: true, incorrectProfiles: [] };
1210
+ }
1211
+ const plistPath2 = join5(homedir3(), "Library", "Preferences", "com.googlecode.iterm2.plist");
1212
+ if (!existsSync4(plistPath2)) {
1213
+ return { checked: false, allCorrect: false, incorrectProfiles: [] };
1214
+ }
1215
+ try {
1216
+ const json = execSync6(
1217
+ `plutil -extract "New Bookmarks" json -o - "${plistPath2}"`,
1218
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
1219
+ );
1220
+ const profiles = JSON.parse(json);
1221
+ const currentProfile = process.env["ITERM_PROFILE"];
1222
+ const incorrect = [];
1223
+ for (const profile of profiles) {
1224
+ const name = profile["Name"] || "Unnamed";
1225
+ if (currentProfile && name !== currentProfile) continue;
1226
+ if (profile["Right Option Key Sends"] !== 2) {
1227
+ incorrect.push(name);
1228
+ }
1229
+ }
1230
+ return { checked: true, allCorrect: incorrect.length === 0, incorrectProfiles: incorrect };
1231
+ } catch {
1232
+ return { checked: false, allCorrect: false, incorrectProfiles: [] };
1233
+ }
1234
+ }
1235
+ function hasExistingTmuxConf() {
1236
+ return existsSync4(join5(homedir3(), ".tmux.conf")) || existsSync4(join5(homedir3(), ".config", "tmux", "tmux.conf"));
1237
+ }
1238
+ var TMUX_DEFAULTS = `# Sensible tmux defaults (installed by sisyphus)
1239
+ # Customize freely \u2014 sisyphus won't overwrite this file.
1240
+
1241
+ # Enable mouse (click panes, scroll, resize)
1242
+ set -g mouse on
1243
+
1244
+ # Scrollback history
1245
+ set -g history-limit 50000
1246
+
1247
+ # 256 color + true color support
1248
+ set -g default-terminal "tmux-256color"
1249
+ set -as terminal-overrides ",*:Tc"
1250
+
1251
+ # Low escape delay (keeps Option/Meta keybindings responsive)
1252
+ set -sg escape-time 10
1253
+
1254
+ # Window numbering from 1
1255
+ set -g base-index 1
1256
+ setw -g pane-base-index 1
1257
+
1258
+ # Renumber windows when one closes
1259
+ set -g renumber-windows on
1260
+
1261
+ # Clipboard integration
1262
+ set -g set-clipboard on
1263
+
1264
+ # Focus events (for editors)
1265
+ set -g focus-events on
1266
+ `;
1267
+ function writeTmuxDefaults() {
1268
+ const confPath = join5(homedir3(), ".tmux.conf");
1269
+ writeFileSync3(confPath, TMUX_DEFAULTS, "utf8");
1270
+ }
1271
+ function isNvimAvailable() {
1272
+ try {
1273
+ execSync6("which nvim", { stdio: "pipe" });
1274
+ return true;
1275
+ } catch {
1276
+ return false;
1277
+ }
1278
+ }
1279
+ function getNvimVersion() {
1280
+ try {
1281
+ return execSync6("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "") || "unknown";
1282
+ } catch {
1283
+ return "unknown";
1284
+ }
1285
+ }
1286
+ function hasLazyVimConfig() {
1287
+ return existsSync4(join5(homedir3(), ".config", "nvim", "lazy-lock.json"));
1288
+ }
1289
+ function tryAutoInstallNvim() {
1290
+ if (isNvimAvailable()) {
1291
+ return { installed: true, autoInstalled: false, version: getNvimVersion(), lazyVimInstalled: hasLazyVimConfig() };
1292
+ }
1293
+ if (!isBrewAvailable()) {
1294
+ return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false };
1295
+ }
1296
+ try {
1297
+ console.log(" Installing neovim via Homebrew...");
1298
+ execSync6("brew install neovim", { stdio: "inherit" });
1299
+ } catch {
1300
+ return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false };
1301
+ }
1302
+ if (!isNvimAvailable()) {
1303
+ return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false };
1304
+ }
1305
+ const nvimConfigDir = join5(homedir3(), ".config", "nvim");
1306
+ let lazyVimInstalled = false;
1307
+ if (!existsSync4(nvimConfigDir)) {
1308
+ try {
1309
+ console.log(" Cloning LazyVim starter config...");
1310
+ execSync6(`git clone https://github.com/LazyVim/starter ${nvimConfigDir}`, { stdio: "inherit" });
1311
+ const gitDir = join5(nvimConfigDir, ".git");
1312
+ if (existsSync4(gitDir)) {
1313
+ execSync6(`rm -rf "${gitDir}"`, { stdio: "pipe" });
1314
+ }
1315
+ lazyVimInstalled = true;
1316
+ } catch {
1317
+ }
1318
+ }
1319
+ return { installed: true, autoInstalled: true, version: getNvimVersion(), lazyVimInstalled };
1320
+ }
1321
+ function beginCommandPath() {
1322
+ return join5(homedir3(), ".claude", "commands", "sisyphus", "begin.md");
1323
+ }
1324
+ function bundledBeginCommandPath() {
1325
+ const distDir = dirname2(fileURLToPath2(import.meta.url));
1326
+ return join5(distDir, "templates", "begin.md");
1327
+ }
1328
+ function isBeginCommandInstalled() {
1329
+ return existsSync4(beginCommandPath());
1330
+ }
1331
+ function installBeginCommand() {
1332
+ const dest = beginCommandPath();
1333
+ if (existsSync4(dest)) {
1334
+ return { installed: true, autoInstalled: false, path: dest };
1335
+ }
1336
+ const src = bundledBeginCommandPath();
1337
+ if (!existsSync4(src)) {
1338
+ return { installed: false, autoInstalled: false, path: dest };
1339
+ }
1340
+ try {
1341
+ mkdirSync3(dirname2(dest), { recursive: true });
1342
+ writeFileSync3(dest, readFileSync4(src, "utf-8"), "utf8");
1343
+ return { installed: true, autoInstalled: true, path: dest };
1344
+ } catch {
1345
+ return { installed: false, autoInstalled: false, path: dest };
1346
+ }
1347
+ }
1348
+ function runOnboarding() {
1349
+ const terminal = detectTerminal();
1350
+ const tmuxAlreadyInstalled = isTmuxAvailable();
1351
+ let tmuxInstalled = tmuxAlreadyInstalled;
1352
+ let tmuxAutoInstalled = false;
1353
+ let tmuxDefaultsWritten = false;
1354
+ if (!tmuxAlreadyInstalled && process.platform === "darwin") {
1355
+ tmuxAutoInstalled = tryAutoInstallTmux();
1356
+ tmuxInstalled = tmuxAutoInstalled;
1357
+ if (tmuxAutoInstalled && !hasExistingTmuxConf()) {
1358
+ writeTmuxDefaults();
1359
+ tmuxDefaultsWritten = true;
1360
+ }
1361
+ }
1362
+ let itermOptionKey = { checked: false, allCorrect: true, incorrectProfiles: [] };
1363
+ if (terminal.isIterm) {
1364
+ itermOptionKey = checkItermOptionKey();
1365
+ }
1366
+ const nvim = tryAutoInstallNvim();
1367
+ const command = installBeginCommand();
1368
+ return { tmuxInstalled, tmuxAutoInstalled, terminal, itermOptionKey, tmuxDefaultsWritten, nvim, command };
1369
+ }
1370
+
1371
+ // src/cli/commands/doctor.ts
1196
1372
  function checkNodeVersion() {
1197
1373
  const major = parseInt(process.versions.node.split(".")[0], 10);
1198
1374
  if (major < 22) {
@@ -1249,7 +1425,7 @@ function checkDaemonInstalled() {
1249
1425
  };
1250
1426
  }
1251
1427
  const pid = daemonPidPath();
1252
- if (existsSync4(pid)) {
1428
+ if (existsSync5(pid)) {
1253
1429
  return { name: "Daemon setup", status: "ok", detail: `PID file found at ${pid}` };
1254
1430
  }
1255
1431
  return {
@@ -1261,7 +1437,7 @@ function checkDaemonInstalled() {
1261
1437
  }
1262
1438
  function checkDaemonRunning() {
1263
1439
  const pid = daemonPidPath();
1264
- if (!existsSync4(pid)) {
1440
+ if (!existsSync5(pid)) {
1265
1441
  const fix = process.platform === "darwin" ? "launchctl load -w ~/Library/LaunchAgents/com.sisyphus.daemon.plist" : "sisyphusd & \u2014 or check if the process is running";
1266
1442
  return {
1267
1443
  name: "Daemon process",
@@ -1299,7 +1475,7 @@ function checkTmux() {
1299
1475
  }
1300
1476
  function checkCycleScript() {
1301
1477
  const path = cycleScriptPath();
1302
- if (!existsSync4(path)) {
1478
+ if (!existsSync5(path)) {
1303
1479
  return {
1304
1480
  name: "Cycle script",
1305
1481
  status: "fail",
@@ -1324,7 +1500,7 @@ function checkCycleScript() {
1324
1500
  function checkTmuxKeybind() {
1325
1501
  const existing = getExistingBinding(DEFAULT_KEY);
1326
1502
  if (existing === null) {
1327
- if (existsSync4(sisyphusTmuxConfPath())) {
1503
+ if (existsSync5(sisyphusTmuxConfPath())) {
1328
1504
  return {
1329
1505
  name: `Tmux keybind (${DEFAULT_KEY})`,
1330
1506
  status: "warn",
@@ -1350,25 +1526,85 @@ function checkTmuxKeybind() {
1350
1526
  }
1351
1527
  function checkGlobalDir() {
1352
1528
  const dir = globalDir();
1353
- if (existsSync4(dir)) {
1529
+ if (existsSync5(dir)) {
1354
1530
  return { name: "Data directory", status: "ok", detail: dir };
1355
1531
  }
1356
1532
  return { name: "Data directory", status: "warn", detail: `${dir} does not exist (created on first use)` };
1357
1533
  }
1534
+ function checkTerminal() {
1535
+ if (process.platform !== "darwin") {
1536
+ return { name: "Terminal", status: "ok", detail: "Non-macOS (skipped)" };
1537
+ }
1538
+ const terminal = detectTerminal();
1539
+ if (terminal.isIterm) {
1540
+ return { name: "Terminal", status: "ok", detail: terminal.name };
1541
+ }
1542
+ return {
1543
+ name: "Terminal",
1544
+ status: "warn",
1545
+ detail: terminal.name ? terminal.name : "unknown",
1546
+ fix: "iTerm2 recommended for best experience: https://iterm2.com"
1547
+ };
1548
+ }
1549
+ function checkItermRightOptionKey() {
1550
+ if (process.platform !== "darwin") return null;
1551
+ const terminal = detectTerminal();
1552
+ if (!terminal.isIterm) return null;
1553
+ const result = checkItermOptionKey();
1554
+ if (!result.checked) return null;
1555
+ if (result.allCorrect) {
1556
+ return { name: "Right Option Key", status: "ok", detail: "Esc+" };
1557
+ }
1558
+ const profiles = result.incorrectProfiles.map((p) => `"${p}"`).join(", ");
1559
+ return {
1560
+ name: "Right Option Key",
1561
+ status: "warn",
1562
+ detail: `Not Esc+ for ${profiles}`,
1563
+ fix: "iTerm2 \u2192 Settings \u2192 Profiles \u2192 Keys \u2192 Right Option Key \u2192 Esc+"
1564
+ };
1565
+ }
1566
+ function checkBeginCommand() {
1567
+ if (isBeginCommandInstalled()) {
1568
+ return { name: "/begin command", status: "ok", detail: "Installed" };
1569
+ }
1570
+ return {
1571
+ name: "/begin command",
1572
+ status: "warn",
1573
+ detail: "Not installed",
1574
+ fix: "sisyphus setup"
1575
+ };
1576
+ }
1577
+ function checkNvim() {
1578
+ if (!isNvimAvailable()) {
1579
+ const fix = process.platform === "darwin" ? "brew install neovim" : "Install neovim from https://neovim.io";
1580
+ return { name: "nvim", status: "warn", detail: "Not installed", fix };
1581
+ }
1582
+ try {
1583
+ const version = execSync7("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
1584
+ return { name: "nvim", status: "ok", detail: version ?? "installed" };
1585
+ } catch {
1586
+ return { name: "nvim", status: "ok", detail: "installed" };
1587
+ }
1588
+ }
1358
1589
  var SYMBOLS = { ok: "\u2713", warn: "!", fail: "\u2717" };
1359
1590
  function registerDoctor(program2) {
1360
1591
  program2.command("doctor").description("Check sisyphus installation health").action(async () => {
1592
+ const itermCheck = checkItermRightOptionKey();
1361
1593
  const checks = [
1362
1594
  checkNodeVersion(),
1363
1595
  checkClaudeCli(),
1364
1596
  checkGit(),
1365
1597
  checkTmux(),
1366
1598
  checkTmuxVersion(),
1599
+ checkTerminal(),
1600
+ ...itermCheck ? [itermCheck] : [],
1367
1601
  checkGlobalDir(),
1368
1602
  checkDaemonInstalled(),
1369
1603
  checkDaemonRunning(),
1370
1604
  checkCycleScript(),
1371
- checkTmuxKeybind()
1605
+ checkTmuxKeybind(),
1606
+ checkBeginCommand(),
1607
+ checkNvim()
1372
1608
  ];
1373
1609
  let hasIssues = false;
1374
1610
  for (const c of checks) {
@@ -1398,135 +1634,400 @@ function registerCompanionContext(program2) {
1398
1634
  }
1399
1635
 
1400
1636
  // src/cli/commands/getting-started.ts
1637
+ function isClaudeCode() {
1638
+ return !!process.env["CLAUDECODE"];
1639
+ }
1640
+ function printNonClaudeMessage() {
1641
+ console.log(`
1642
+ \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
1643
+ \u2551 sisyphus getting-started \u2014 Interactive Tutorial \u2551
1644
+ \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
1645
+
1646
+ This command provides an interactive tutorial best experienced
1647
+ inside Claude Code. Claude will walk you through setup step by step.
1648
+
1649
+ To start:
1650
+ 1. Open Claude Code: claude
1651
+ 2. Run: sisyphus getting-started
1652
+
1653
+ If you just want the quick reference, run:
1654
+ sisyphus --help
1655
+ sisyphus doctor
1656
+ `);
1657
+ }
1658
+ function printStep0() {
1659
+ const tmuxInstalled = isTmuxInstalled();
1660
+ const inTmux = !!process.env["TMUX"];
1661
+ let tmuxSession = null;
1662
+ if (process.env["TMUX"]) {
1663
+ const segments = process.env["TMUX"].split(",");
1664
+ const pathPart = segments[0];
1665
+ if (pathPart) {
1666
+ const parts = pathPart.split("/");
1667
+ tmuxSession = parts[parts.length - 1] || null;
1668
+ }
1669
+ }
1670
+ console.log(`
1671
+ <claude-instructions>
1672
+ # Sisyphus Getting Started \u2014 Step 0: Entry & Tmux Gate
1673
+
1674
+ ## Environment Data
1675
+ - tmuxInstalled: ${tmuxInstalled}
1676
+ - inTmux: ${inTmux}
1677
+ - tmuxSession: ${tmuxSession ?? "none"}
1678
+
1679
+ ## Tutorial Overview
1680
+
1681
+ This tutorial has 5 steps. Share this overview so the user knows what's coming and can skip ahead:
1682
+
1683
+ | Step | Topic | Command |
1684
+ |------|-------|---------|
1685
+ | 0 | Entry & tmux gate (you are here) | \`sisyphus getting-started\` |
1686
+ | 1 | Tmux basics \u2014 sessions, panes, navigation | \`--tutorial 1\` |
1687
+ | 2 | Nvim basics \u2014 open, save, quit (optional) | \`--tutorial 2\` |
1688
+ | 3 | Sisyphus concepts \u2014 session model & keybinds | \`--tutorial 3\` |
1689
+ | 4 | Live demo \u2014 launch and observe a real session | \`--tutorial 4\` |
1690
+
1691
+ Tell the user they can skip to any step with \`sisyphus getting-started --tutorial <N>\`.
1692
+
1693
+ ## Instructions for Claude
1694
+
1695
+ You are guiding a user through the Sisyphus interactive tutorial.
1696
+
1697
+ ### First: Ask if they want the tutorial
1698
+
1699
+ Ask the user if they'd like the interactive walkthrough. If they decline, give this quick summary and stop:
1700
+
1701
+ > Sisyphus is a multi-agent orchestrator for Claude Code. Start a session with \`sisyphus start "task"\`,
1702
+ > monitor with \`sisyphus dashboard\`, and check health with \`sisyphus doctor\`.
1703
+
1704
+ ### If they want the tutorial:
1705
+
1706
+ **Case 1: tmux is NOT installed (tmuxInstalled: false)**
1707
+ - 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."
1708
+ - Show this ASCII diagram:
1709
+
1710
+ \`\`\`
1711
+ Regular terminal: With tmux:
1712
+ \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
1713
+ \u2502 \u2502 \u2502 Agent\u2502 Agent\u2502
1714
+ \u2502 one view \u2502 \u2502 001 \u2502 002 \u2502
1715
+ \u2502 \u2502 \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1716
+ \u2502 \u2502 \u2502 Agent\u2502 Orch \u2502
1717
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 003 \u2502 \u2502
1718
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1719
+ \`\`\`
1720
+
1721
+ - Tell them to install tmux:
1722
+ - macOS: \`brew install tmux\`
1723
+ - Linux: \`apt install tmux\` or their package manager
1724
+ - After install, re-run: \`sisyphus getting-started --tutorial 0\` to verify
1725
+
1726
+ **Case 2: tmux installed but NOT in a tmux session (inTmux: false)**
1727
+ - Tell the user they need to be inside a tmux session for the tutorial
1728
+ - Have them run: \`tmux new-session\`
1729
+ - Then resume the conversation with Claude in the new tmux session: \`claude\`
1730
+ - Then re-run: \`sisyphus getting-started --tutorial 0\` to verify
1731
+
1732
+ **Case 3: In tmux (inTmux: true)**
1733
+ - Tell the user they're all set \u2014 tmux is running
1734
+ - Proceed by running: \`sisyphus getting-started --tutorial 1\`
1735
+ </claude-instructions>
1736
+ `);
1737
+ }
1738
+ function printStep1() {
1739
+ console.log(`
1740
+ <claude-instructions>
1741
+ # Sisyphus Getting Started \u2014 Step 1: Tmux Basics
1742
+
1743
+ ## Instructions for Claude
1744
+
1745
+ Teach the user tmux fundamentals. Be conversational and encouraging.
1746
+
1747
+ ### 1. Explain the concepts with diagrams
1748
+
1749
+ **Sessions, Windows, and Panes:**
1750
+
1751
+ \`\`\`
1752
+ tmux session "work"
1753
+ \u251C\u2500\u2500 window 1: "code"
1754
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1755
+ \u2502 \u2502 pane 1 \u2502 pane 2 \u2502
1756
+ \u2502 \u2502 (editor)\u2502 (tests) \u2502
1757
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1758
+ \u2514\u2500\u2500 window 2: "servers"
1759
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1760
+ \u2502 pane 1 \u2502
1761
+ \u2502 (dev server) \u2502
1762
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1763
+ \`\`\`
1764
+
1765
+ - **Session**: A collection of windows. Persists even if you close the terminal.
1766
+ - **Window**: Like a tab. Each window fills the screen.
1767
+ - **Pane**: A split within a window. Sisyphus puts each agent in its own pane.
1768
+
1769
+ ### 2. Hands-on: Create a test split
1770
+
1771
+ Run this command for the user:
1772
+ \`\`\`
1773
+ tmux split-window -h
1774
+ \`\`\`
1775
+
1776
+ Tell them: "I just split your terminal. You should see two panes side by side."
1777
+
1778
+ Explain navigation:
1779
+ - \`Ctrl-b\` then \`\u2192\` (right arrow): move to the right pane
1780
+ - \`Ctrl-b\` then \`\u2190\` (left arrow): move to the left pane
1781
+ - The prefix \`Ctrl-b\` is always pressed first, then released, then the arrow key
1782
+
1783
+ Ask them to try navigating between panes.
1784
+
1785
+ ### 3. Clean up the test pane
1786
+
1787
+ Once they confirm they can navigate, close the extra pane:
1788
+ \`\`\`
1789
+ tmux kill-pane -t {the other pane}
1790
+ \`\`\`
1791
+
1792
+ Or tell them they can type \`exit\` in the extra pane to close it.
1793
+
1794
+ ### 4. Teach essential commands
1795
+
1796
+ - **Detach**: \`Ctrl-b d\` \u2014 leaves tmux running in background, returns to normal terminal
1797
+ - **Reattach**: \`tmux attach\` (or \`tmux a\`) \u2014 reconnects to the running session
1798
+ - **Scroll mode**: \`Ctrl-b [\` \u2014 lets you scroll up through output. Press \`q\` to exit scroll mode.
1799
+
1800
+ ### 5. Verification
1801
+
1802
+ Ask the user to confirm: "Can you navigate between panes with Ctrl-b + arrow keys?"
1803
+
1804
+ Once confirmed, proceed:
1805
+ \`\`\`
1806
+ sisyphus getting-started --tutorial 2
1807
+ \`\`\`
1808
+ </claude-instructions>
1809
+ `);
1810
+ }
1811
+ function printStep2() {
1812
+ const nvimInstalled = isNvimAvailable();
1813
+ console.log(`
1814
+ <claude-instructions>
1815
+ # Sisyphus Getting Started \u2014 Step 2: Nvim Basics
1816
+
1817
+ ## Environment Data
1818
+ - nvimInstalled: ${nvimInstalled}
1819
+
1820
+ ## Instructions for Claude
1821
+
1822
+ This step is OPTIONAL. Nvim is useful for reviewing agent work in tmux panes but not required.
1823
+
1824
+ ### If nvim is NOT installed (nvimInstalled: false)
1825
+
1826
+ Ask the user: "Neovim is handy for reviewing files in tmux panes. Want me to install it, or skip this step?"
1827
+
1828
+ - **Install**: Run \`brew install neovim\` (macOS) or suggest their package manager
1829
+ - **Skip**: That's fine \u2014 they can use \`cat\`, \`less\`, or any editor they prefer. Proceed to step 3.
1830
+
1831
+ ### If nvim IS installed (nvimInstalled: true)
1832
+
1833
+ Teach exactly 3 things \u2014 no more:
1834
+
1835
+ 1. **Open a file**: \`nvim filename.txt\`
1836
+ 2. **Save and close**: \`ZZ\` (capital Z twice \u2014 hold Shift, press Z twice)
1837
+ 3. **Quit without saving**: \`:q!\` (colon, q, exclamation mark, Enter)
1838
+
1839
+ ### Hands-on exercise
1840
+
1841
+ Create a temporary file for practice:
1842
+ \`\`\`
1843
+ echo "Hello from the sisyphus tutorial!" > /tmp/sisyphus-tutorial-test.txt
1844
+ \`\`\`
1845
+
1846
+ Walk them through:
1847
+ 1. Open it: \`nvim /tmp/sisyphus-tutorial-test.txt\`
1848
+ 2. Look around (they're in normal mode \u2014 arrow keys work for navigation)
1849
+ 3. Close it: type \`ZZ\`
1850
+
1851
+ Tell them: "That's all you need. When you jump into a sisyphus agent's pane, you can use \`nvim\` to inspect files the agent is working on."
1852
+
1853
+ ### Verification
1854
+
1855
+ Ask if they were able to open and close the file (or if they skipped).
1856
+
1857
+ Proceed:
1858
+ \`\`\`
1859
+ sisyphus getting-started --tutorial 3
1860
+ \`\`\`
1861
+ </claude-instructions>
1862
+ `);
1863
+ }
1864
+ function printStep3() {
1865
+ console.log(`
1866
+ <claude-instructions>
1867
+ # Sisyphus Getting Started \u2014 Step 3: Sisyphus Concepts & Keybinds
1868
+
1869
+ ## Instructions for Claude
1870
+
1871
+ ### 1. Explain the session model
1872
+
1873
+ This is the KEY concept. Use the diagram and be clear:
1874
+
1875
+ \`\`\`
1876
+ YOUR tmux session ("work") Sisyphus tmux session ("sisyphus-abc123")
1877
+ \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
1878
+ \u2502 \u2502 \u2502 Orch \u2502 Agent \u2502 Agent \u2502
1879
+ \u2502 Your normal work \u2502 \u2190\u2500\u2500\u2192 \u2502 (yellow)\u2502 (blue) \u2502 (green) \u2502
1880
+ \u2502 + dashboard \u2502 \u2502 \u2502 \u2502 \u2502
1881
+ \u2502 \u2502 \u2502 Plans & \u2502 Writes \u2502 Writes \u2502
1882
+ \u2502 \u2502 \u2502 assigns \u2502 code \u2502 tests \u2502
1883
+ \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
1884
+ \`\`\`
1885
+
1886
+ Key points:
1887
+ - Sisyphus creates its OWN tmux session \u2014 it doesn't clutter yours
1888
+ - The **orchestrator** (yellow pane) plans work and spawns agents
1889
+ - **Agents** (colored panes) work in parallel on subtasks
1890
+ - Your session stays clean \u2014 you get a **dashboard** for monitoring
1891
+ - You can jump between your session and the sisyphus session to observe
1892
+
1893
+ ### 2. Teach keybinds
1894
+
1895
+ Two keybinds to remember:
1896
+
1897
+ | Keybind | Action |
1898
+ |---------|--------|
1899
+ | \`M-s\` (Option+s) | Cycle through sisyphus sessions |
1900
+ | \`M-S\` (Option+Shift+s) | Jump back to dashboard |
1901
+
1902
+ "M" means "Meta" which is the Option key on macOS. So \`M-s\` = hold Option, press s.
1903
+
1904
+ ### 3. Verify keybinds are installed
1905
+
1906
+ Run \`sisyphus doctor\` and check the output. Look for:
1907
+ - "Cycle script" \u2014 should be \u2713
1908
+ - "Tmux keybind" \u2014 should be \u2713
1909
+
1910
+ If either is missing, run: \`sisyphus setup-keybind\`
1911
+
1912
+ ### 4. Test the keybind
1913
+
1914
+ Have the user try pressing \`Option+s\`. Nothing should happen yet (no sisyphus session running) \u2014 and that's fine. The important thing is it doesn't error.
1915
+
1916
+ If they get a special character (like \xDF) instead of the keybind firing, explain:
1917
+ - Their terminal needs to send Option as Esc+ (Meta)
1918
+ - iTerm2: Settings \u2192 Profiles \u2192 Keys \u2192 Right Option Key \u2192 Esc+
1919
+ - Other terminals: look for "Meta key" or "Option sends" in preferences
1920
+
1921
+ ### 5. Verification
1922
+
1923
+ Confirm:
1924
+ - They understand the two-session model (their session vs sisyphus session)
1925
+ - \`sisyphus doctor\` shows keybinds installed
1926
+ - Option+s doesn't produce a special character
1927
+
1928
+ Proceed:
1929
+ \`\`\`
1930
+ sisyphus getting-started --tutorial 4
1931
+ \`\`\`
1932
+ </claude-instructions>
1933
+ `);
1934
+ }
1935
+ function printStep4() {
1936
+ console.log(`
1937
+ <claude-instructions>
1938
+ # Sisyphus Getting Started \u2014 Step 4: Demo Session
1939
+
1940
+ ## Instructions for Claude
1941
+
1942
+ This is the grand finale \u2014 a live demo session.
1943
+
1944
+ ### 1. Health check
1945
+
1946
+ Run \`sisyphus doctor\` first. If any checks are failing, help the user fix them before proceeding.
1947
+ All core checks (tmux, daemon, keybinds) should be \u2713.
1948
+
1949
+ ### 2. Launch the demo
1950
+
1951
+ Run this command:
1952
+ \`\`\`
1953
+ sisyphus start "Tutorial demo: explore this repository's structure, identify the main entry points, and write a brief summary of what each top-level directory contains" -c "This is a tutorial demo session. Be extra verbose in your planning and reports so the user watching can understand what's happening. Keep the scope small \u2014 2-3 agents max, 1-2 cycles."
1954
+ \`\`\`
1955
+
1956
+ ### 3. Walk through what's happening
1957
+
1958
+ Guide the user through observing the session in real-time:
1959
+
1960
+ **Step A: Dashboard**
1961
+ - The dashboard should auto-open. If not, run \`sisyphus dashboard\`
1962
+ - Point out: session status, cycle number, agent list
1963
+ - Tell them: "Watch the roadmap section \u2014 it updates as the orchestrator plans"
1964
+
1965
+ **Step B: Jump to sisyphus session**
1966
+ - Have them press \`M-s\` (Option+s) to cycle to the sisyphus tmux session
1967
+ - They should see the orchestrator (yellow) working \u2014 reading files, planning
1968
+ - Point out: "Each pane is a separate Claude instance working independently"
1969
+
1970
+ **Step C: Jump back**
1971
+ - Press \`M-S\` (Option+Shift+s) to jump back to the dashboard
1972
+ - Or \`M-s\` to cycle through
1973
+
1974
+ **Step D: Watch the lifecycle**
1975
+ - As agents spawn, they'll appear in both the dashboard and the sisyphus session
1976
+ - Agents submit reports when done
1977
+ - The orchestrator respawns each cycle with fresh context
1978
+ - Eventually the session completes
1979
+
1980
+ ### 4. After completion
1981
+
1982
+ Once the session shows "completed":
1983
+
1984
+ - Run \`sisyphus status\` to see the final state
1985
+ - Show them the session directory: \`ls .sisyphus/sessions/\` \u2192 find the session \u2192 show \`roadmap.md\`
1986
+ - Explain: "Every session creates a roadmap, agent reports, and logs \u2014 all in .sisyphus/sessions/"
1987
+
1988
+ ### 5. Congratulations!
1989
+
1990
+ Wrap up with:
1991
+
1992
+ > You've completed the Sisyphus tutorial! Here's what you learned:
1993
+ > - tmux basics (sessions, panes, navigation)
1994
+ > - How sisyphus creates separate sessions for orchestrator + agents
1995
+ > - How to monitor with the dashboard and keybinds
1996
+ > - What a real session lifecycle looks like
1997
+ >
1998
+ > **Ready for real work?**
1999
+ > \`sisyphus start "your actual task here"\`
2000
+ >
2001
+ > **Tips:**
2002
+ > - Write requirements in a file and reference them: \`sisyphus start "Implement @requirements.md"\`
2003
+ > - Monitor actively \u2014 agents can get stuck. Use the dashboard's \`m\` key to message the orchestrator.
2004
+ > - Check \`sisyphus --help\` for all commands.
2005
+ </claude-instructions>
2006
+ `);
2007
+ }
2008
+ var STEPS = [printStep0, printStep1, printStep2, printStep3, printStep4];
1401
2009
  function registerGettingStarted(program2) {
1402
- program2.command("getting-started").description("Show a complete guide to using sisyphus effectively").action(() => {
1403
- const hasTmux = isTmuxInstalled();
1404
- const inTmux = !!process.env["TMUX"];
1405
- const lines = [
1406
- "",
1407
- " \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",
1408
- " \u2551 Getting Started with Sisyphus \u2551",
1409
- " \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",
1410
- "",
1411
- " Sisyphus is a multi-agent orchestration daemon for Claude Code.",
1412
- " It breaks large tasks into subtasks, spawns parallel Claude agents,",
1413
- " and coordinates their work across multiple cycles \u2014 autonomously.",
1414
- "",
1415
- " \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",
1416
- ""
1417
- ];
1418
- if (!hasTmux) {
1419
- lines.push(
1420
- " \u26A0 tmux is not installed. Sisyphus requires tmux.",
1421
- " Install it: brew install tmux (macOS)",
1422
- " apt install tmux (Linux)",
1423
- ""
1424
- );
1425
- } else if (!inTmux) {
1426
- lines.push(
1427
- " \u26A0 You are not inside a tmux session.",
1428
- " Sisyphus spawns agent panes inside tmux, so you should",
1429
- " start a tmux session before running sisyphus:",
1430
- "",
1431
- " tmux new-session",
1432
- ""
1433
- );
1434
- } else {
1435
- lines.push(
1436
- " \u2713 You are inside tmux. Good to go.",
1437
- ""
1438
- );
2010
+ program2.command("getting-started").description("Interactive tutorial (best with Claude Code)").option("--tutorial <step>", "Tutorial step (0-4)", parseInt).action((opts) => {
2011
+ if (opts.tutorial !== void 0) {
2012
+ const step = opts.tutorial;
2013
+ if (step < 0 || step > 4 || Number.isNaN(step)) {
2014
+ console.error(`Invalid tutorial step: ${opts.tutorial}. Must be 0-4.`);
2015
+ process.exit(1);
2016
+ }
2017
+ STEPS[step]();
2018
+ return;
1439
2019
  }
1440
- lines.push(
1441
- " \u2500\u2500\u2500 What Makes a Good Sisyphus Task \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1442
- "",
1443
- " Sisyphus is built for BIG tasks \u2014 the kind that would take",
1444
- " multiple cycles of orchestration and parallel agent work.",
1445
- " If you could do it with a single Claude Code session in plan",
1446
- " mode, it's too small for sisyphus.",
1447
- "",
1448
- " Good tasks:",
1449
- ' \u2022 "Implement this feature" @path/to/requirements.md',
1450
- ' \u2022 "Do a deep dive on all SEO/AEO optimizations and',
1451
- ' systematically apply them across the site"',
1452
- " \u2022 Large-scale refactors spanning dozens of files",
1453
- " \u2022 Full feature builds from written requirements",
1454
- "",
1455
- " Too small for sisyphus:",
1456
- ' \u2022 "Add these 3 UI components to the page"',
1457
- ' \u2022 "Fix this bug in auth.ts"',
1458
- " \u2022 Anything a single Claude session handles comfortably",
1459
- "",
1460
- " Tasks don't need to be hyper-specific \u2014 broad but meaningful",
1461
- " tasks work great because the orchestrator will plan the approach.",
1462
- " What matters is SCALE, not specificity.",
1463
- "",
1464
- " For best results, write requirements and reference them directly:",
1465
- "",
1466
- ' sisyphus start "Implement this @path/to/requirements.md"',
1467
- "",
1468
- " \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",
1469
- "",
1470
- ' 1. You run: sisyphus start "task description"',
1471
- " 2. An orchestrator Claude reviews the task and creates a roadmap",
1472
- " 3. It spawns agent Claude instances in parallel tmux panes",
1473
- " 4. Agents work independently, then submit reports when done",
1474
- " 5. The orchestrator respawns with fresh context, reviews progress,",
1475
- " and kicks off the next cycle of work",
1476
- " 6. This repeats until the orchestrator marks the task complete",
1477
- "",
1478
- " The orchestrator is stateless \u2014 it gets killed after each cycle",
1479
- " and respawned fresh with the full session state. This means it",
1480
- " never runs out of context, no matter how many cycles a task takes.",
1481
- "",
1482
- " \u2500\u2500\u2500 Monitoring (Important!) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1483
- "",
1484
- " Sisyphus sessions should be actively monitored. Agents can get",
1485
- " stuck waiting for input, fail to submit reports, or take the",
1486
- " roadmap in a direction you don't want. The dashboard is your",
1487
- " primary tool for staying on top of things:",
1488
- "",
1489
- " sisyphus dashboard",
1490
- "",
1491
- " Key dashboard actions:",
1492
- " m \u2014 Message the orchestrator to steer direction",
1493
- " w \u2014 Jump directly into the sisyphus tmux session",
1494
- " to see exactly what agents are doing",
1495
- "",
1496
- " Use `m` to course-correct from the dashboard, and `w` when you",
1497
- " need the most granular view of agent activity.",
1498
- "",
1499
- " \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",
1500
- "",
1501
- " Start & monitor:",
1502
- ' sisyphus start "task" Start a session',
1503
- ' sisyphus start "task @requirements.md" Start referencing requirements',
1504
- " sisyphus status [id] Show session status",
1505
- " sisyphus list List all sessions",
1506
- " sisyphus dashboard Open TUI dashboard",
1507
- "",
1508
- " Control:",
1509
- ' sisyphus resume <id> "instructions" Resume with new direction',
1510
- " sisyphus kill <id> Stop a session",
1511
- "",
1512
- " Health:",
1513
- " sisyphus doctor Check installation health",
1514
- " tail -f ~/.sisyphus/daemon.log Watch daemon logs",
1515
- "",
1516
- " \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",
1517
- "",
1518
- " 1. Run `sisyphus doctor` to check your setup",
1519
- " 2. Start a tmux session: `tmux new-session`",
1520
- ' 3. Try it: `sisyphus start "your task description"`',
1521
- ""
1522
- );
1523
- console.log(lines.join("\n"));
2020
+ if (!isClaudeCode()) {
2021
+ printNonClaudeMessage();
2022
+ return;
2023
+ }
2024
+ printStep0();
1524
2025
  });
1525
2026
  }
1526
2027
 
1527
2028
  // src/cli/commands/init.ts
1528
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
1529
- import { join as join5 } from "path";
2029
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
2030
+ import { join as join6 } from "path";
1530
2031
  var DEFAULT_CONFIG = {};
1531
2032
  var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
1532
2033
 
@@ -1537,19 +2038,19 @@ var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
1537
2038
  function registerInit(program2) {
1538
2039
  program2.command("init").description("Initialize sisyphus configuration for this project").option("--orchestrator", "Also create a custom orchestrator prompt template").action((opts) => {
1539
2040
  const cwd = process.cwd();
1540
- const sisDir = join5(cwd, ".sisyphus");
1541
- const configPath = join5(sisDir, "config.json");
1542
- if (existsSync5(configPath)) {
2041
+ const sisDir = join6(cwd, ".sisyphus");
2042
+ const configPath = join6(sisDir, "config.json");
2043
+ if (existsSync6(configPath)) {
1543
2044
  console.log(`Already initialized: ${configPath}`);
1544
2045
  return;
1545
2046
  }
1546
- mkdirSync3(sisDir, { recursive: true });
1547
- writeFileSync3(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
2047
+ mkdirSync4(sisDir, { recursive: true });
2048
+ writeFileSync4(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
1548
2049
  console.log(`Created ${configPath}`);
1549
2050
  if (opts.orchestrator) {
1550
- const orchPath = join5(sisDir, "orchestrator.md");
1551
- if (!existsSync5(orchPath)) {
1552
- writeFileSync3(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
2051
+ const orchPath = join6(sisDir, "orchestrator.md");
2052
+ if (!existsSync6(orchPath)) {
2053
+ writeFileSync4(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
1553
2054
  console.log(`Created ${orchPath}`);
1554
2055
  }
1555
2056
  }
@@ -1562,6 +2063,94 @@ function registerInit(program2) {
1562
2063
  });
1563
2064
  }
1564
2065
 
2066
+ // src/cli/commands/setup.ts
2067
+ import { execSync as execSync8 } from "child_process";
2068
+ function getTmuxVersion() {
2069
+ try {
2070
+ return execSync8("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
2071
+ } catch {
2072
+ return "installed";
2073
+ }
2074
+ }
2075
+ function printResults(result, daemonOk, keybindMsg) {
2076
+ console.log("");
2077
+ console.log("Setting up Sisyphus...");
2078
+ console.log("");
2079
+ if (result.tmuxInstalled) {
2080
+ const detail = getTmuxVersion();
2081
+ console.log(` \u2713 tmux: ${detail}${result.tmuxAutoInstalled ? " (just installed)" : ""}`);
2082
+ } else {
2083
+ const hint = process.platform === "darwin" ? "Install Homebrew (https://brew.sh) then: brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
2084
+ console.log(` \u2717 tmux: Not installed \u2014 ${hint}`);
2085
+ }
2086
+ if (result.tmuxDefaultsWritten) {
2087
+ console.log(" \u2713 tmux config: Sensible defaults written to ~/.tmux.conf");
2088
+ }
2089
+ if (process.platform === "darwin") {
2090
+ if (result.terminal.isIterm) {
2091
+ console.log(` \u2713 Terminal: ${result.terminal.name}`);
2092
+ } else {
2093
+ const name = result.terminal.name ? result.terminal.name : "unknown";
2094
+ console.log(` \u26A0 Terminal: ${name} \u2014 iTerm2 recommended (https://iterm2.com)`);
2095
+ }
2096
+ }
2097
+ if (result.itermOptionKey.checked) {
2098
+ if (result.itermOptionKey.allCorrect) {
2099
+ console.log(" \u2713 Right Option Key: Esc+");
2100
+ } else {
2101
+ const profiles = result.itermOptionKey.incorrectProfiles.map((p) => `"${p}"`).join(", ");
2102
+ console.log(` \u26A0 Right Option Key: Not set to Esc+ for ${profiles}`);
2103
+ console.log(" Fix: iTerm2 \u2192 Settings \u2192 Profiles \u2192 Keys \u2192 Right Option Key \u2192 Esc+");
2104
+ }
2105
+ }
2106
+ if (daemonOk) {
2107
+ console.log(" \u2713 Daemon: Running");
2108
+ } else {
2109
+ console.log(" \u2717 Daemon: Failed to start");
2110
+ }
2111
+ console.log(` \u2713 Keybindings: ${keybindMsg}`);
2112
+ if (result.command.installed) {
2113
+ console.log(` \u2713 /begin command: ${result.command.path}${result.command.autoInstalled ? " (just installed)" : ""}`);
2114
+ } else {
2115
+ console.log(" \u2717 /begin command: Failed to install");
2116
+ }
2117
+ if (result.nvim.installed) {
2118
+ const extra = result.nvim.autoInstalled ? " (just installed)" : "";
2119
+ console.log(` \u2713 Editor: nvim ${result.nvim.version}${extra}`);
2120
+ if (result.nvim.lazyVimInstalled) {
2121
+ console.log(" \u2713 LazyVim: Starter config installed to ~/.config/nvim/");
2122
+ }
2123
+ } else {
2124
+ console.log(" \u26A0 Editor: nvim not installed");
2125
+ if (process.platform === "darwin") {
2126
+ console.log(" Install: brew install neovim");
2127
+ }
2128
+ }
2129
+ console.log("");
2130
+ console.log("Run 'sisyphus getting-started' for a usage guide.");
2131
+ console.log("");
2132
+ }
2133
+ function registerSetup(program2) {
2134
+ program2.command("setup").description("One-time setup: install dependencies, daemon, keybindings, and commands").action(async () => {
2135
+ const result = runOnboarding();
2136
+ let daemonOk = false;
2137
+ try {
2138
+ await ensureDaemonInstalled();
2139
+ daemonOk = true;
2140
+ } catch {
2141
+ daemonOk = isInstalled();
2142
+ }
2143
+ const keybindResult = setupTmuxKeybind();
2144
+ let keybindMsg;
2145
+ if (keybindResult.status === "installed" || keybindResult.status === "already-installed") {
2146
+ keybindMsg = `${DEFAULT_KEY} (cycle), ${DEFAULT_HOME_KEY} (dashboard)`;
2147
+ } else {
2148
+ keybindMsg = keybindResult.message;
2149
+ }
2150
+ printResults(result, daemonOk, keybindMsg);
2151
+ });
2152
+ }
2153
+
1565
2154
  // src/cli/index.ts
1566
2155
  var nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
1567
2156
  if (nodeVersion < 22) {
@@ -1569,7 +2158,11 @@ if (nodeVersion < 22) {
1569
2158
  process.exit(1);
1570
2159
  }
1571
2160
  var program = new Command();
1572
- program.name("sisyphus").description("tmux-integrated orchestration daemon for Claude Code").version("0.1.0");
2161
+ program.name("sisyphus").description("tmux-integrated orchestration daemon for Claude Code").version(
2162
+ JSON.parse(
2163
+ readFileSync5(join7(dirname3(fileURLToPath3(import.meta.url)), "..", "package.json"), "utf-8")
2164
+ ).version
2165
+ );
1573
2166
  program.configureHelp({
1574
2167
  sortSubcommands: false
1575
2168
  });
@@ -1596,6 +2189,7 @@ registerDoctor(program);
1596
2189
  registerCompanionContext(program);
1597
2190
  registerGettingStarted(program);
1598
2191
  registerInit(program);
2192
+ registerSetup(program);
1599
2193
  program.addHelpText("after", `
1600
2194
  Examples:
1601
2195
  $ sisyphus start "Implement auth system" Start a new session
@@ -1608,15 +2202,11 @@ Run 'sisyphus getting-started' for a complete usage guide.
1608
2202
  `);
1609
2203
  var args = process.argv.slice(2);
1610
2204
  var firstArg = args[0];
1611
- var skipWelcome = ["doctor", "getting-started", "help", "--help", "-h", "init", "uninstall", "--version", "-V"];
1612
- if (!existsSync6(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
1613
- mkdirSync4(globalDir(), { recursive: true });
1614
- console.log("");
1615
- console.log(" Welcome to Sisyphus \u2014 multi-agent orchestration for Claude Code.");
2205
+ var skipWelcome = ["doctor", "getting-started", "help", "--help", "-h", "init", "setup", "uninstall", "--version", "-V"];
2206
+ if (!existsSync7(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
2207
+ mkdirSync5(globalDir(), { recursive: true });
1616
2208
  console.log("");
1617
- console.log(" First time? Run these commands:");
1618
- console.log(" sisyphus doctor Check your setup");
1619
- console.log(" sisyphus getting-started Learn the basics");
2209
+ console.log(" Welcome to Sisyphus. Run 'sisyphus setup' to get started.");
1620
2210
  console.log("");
1621
2211
  }
1622
2212
  program.parseAsync(process.argv).catch((err) => {