sisyphi 1.0.4 → 1.0.6

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
@@ -1,8 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildCompanionContext,
4
- computeActiveTimeMs
5
- } from "./chunk-DBR33QHM.js";
4
+ computeActiveTimeMs,
5
+ formatDuration,
6
+ rawSend,
7
+ statusColor
8
+ } from "./chunk-T7ETTIQK.js";
9
+ import {
10
+ shellQuote
11
+ } from "./chunk-6G226ZK7.js";
6
12
  import {
7
13
  daemonLogPath,
8
14
  daemonPidPath,
@@ -10,17 +16,15 @@ import {
10
16
  globalDir,
11
17
  roadmapPath,
12
18
  socketPath
13
- } from "./chunk-YGBGKMTF.js";
19
+ } from "./chunk-JXKUI4P6.js";
14
20
 
15
21
  // src/cli/index.ts
16
22
  import { Command } from "commander";
23
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
17
24
 
18
25
  // src/cli/commands/start.ts
19
26
  import { execSync as execSync5 } from "child_process";
20
27
 
21
- // src/cli/client.ts
22
- import { connect as connect2 } from "net";
23
-
24
28
  // src/cli/install.ts
25
29
  import { execSync as execSync2 } from "child_process";
26
30
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
@@ -304,37 +308,8 @@ async function waitForDaemon(maxWaitMs = 6e3) {
304
308
  }
305
309
 
306
310
  // src/cli/client.ts
307
- function rawSend(request) {
308
- const sock = socketPath();
309
- return new Promise((resolve2, reject) => {
310
- const socket = connect2(sock);
311
- let data = "";
312
- const timeout = setTimeout(() => {
313
- socket.destroy();
314
- reject(new Error("Request timed out after 10s"));
315
- }, 1e4);
316
- socket.on("connect", () => {
317
- socket.write(JSON.stringify(request) + "\n");
318
- });
319
- socket.on("data", (chunk) => {
320
- data += chunk.toString();
321
- const newlineIdx = data.indexOf("\n");
322
- if (newlineIdx !== -1) {
323
- clearTimeout(timeout);
324
- const line = data.slice(0, newlineIdx);
325
- socket.destroy();
326
- try {
327
- resolve2(JSON.parse(line));
328
- } catch {
329
- reject(new Error(`Invalid JSON response from daemon: ${line}`));
330
- }
331
- }
332
- });
333
- socket.on("error", (err) => {
334
- clearTimeout(timeout);
335
- reject(err);
336
- });
337
- });
311
+ function rawSend2(request) {
312
+ return rawSend(request, 1e4);
338
313
  }
339
314
  async function sendRequest(request) {
340
315
  const sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -344,7 +319,7 @@ async function sendRequest(request) {
344
319
  let lastErr;
345
320
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
346
321
  try {
347
- return await rawSend(request);
322
+ return await rawSend2(request);
348
323
  } catch (err) {
349
324
  lastErr = err;
350
325
  const code = err.code;
@@ -364,17 +339,51 @@ async function sendRequest(request) {
364
339
  }
365
340
  }
366
341
  if (process.platform !== "darwin") {
367
- throw new Error(
368
- `Sisyphus daemon is not running.
369
- Start it manually: sisyphusd &
370
- Or check logs at: ~/.sisyphus/daemon.log`
342
+ const lines = [`Sisyphus daemon is not running.`];
343
+ if (process.platform === "linux") {
344
+ lines.push(
345
+ "",
346
+ " Start options:",
347
+ " sisyphusd & # Run in background",
348
+ " nohup sisyphusd > ~/.sisyphus/daemon.log 2>&1 & # Persist after logout",
349
+ "",
350
+ " For systemd (recommended):",
351
+ " # Create ~/.config/systemd/user/sisyphus.service with:",
352
+ " # [Unit]",
353
+ " # Description=Sisyphus Daemon",
354
+ " # [Service]",
355
+ " # ExecStart=/usr/bin/env node <path-to-sisyphusd>",
356
+ " # Restart=always",
357
+ " # [Install]",
358
+ " # WantedBy=default.target",
359
+ " systemctl --user enable --now sisyphus"
360
+ );
361
+ } else {
362
+ lines.push(
363
+ "",
364
+ " Start it manually: sisyphusd &"
365
+ );
366
+ }
367
+ lines.push(
368
+ "",
369
+ " Diagnose: sisyphus doctor",
370
+ " Logs: tail -f ~/.sisyphus/daemon.log"
371
371
  );
372
+ throw new Error(lines.join("\n"));
372
373
  }
373
374
  throw lastErr;
374
375
  }
375
376
 
376
377
  // src/cli/tmux.ts
377
378
  import { execSync as execSync3 } from "child_process";
379
+ function isTmuxInstalled() {
380
+ try {
381
+ execSync3("which tmux", { stdio: "pipe" });
382
+ return true;
383
+ } catch {
384
+ return false;
385
+ }
386
+ }
378
387
  function assertTmux() {
379
388
  if (!process.env.TMUX) {
380
389
  throw new Error("Not running inside a tmux pane. Sisyphus requires tmux.");
@@ -388,21 +397,21 @@ function getTmuxSession() {
388
397
  // src/cli/commands/dashboard.ts
389
398
  import { join as join3 } from "path";
390
399
  import { execSync as execSync4 } from "child_process";
391
- function shellQuote(s) {
392
- return `'${s.replace(/'/g, "'\\''")}'`;
393
- }
394
- function isDashboardOpen(tmuxSession) {
400
+ function ensureDashboard(tmuxSession, cwd) {
395
401
  try {
396
402
  const windows = execSync4(
397
403
  `tmux list-windows -t ${shellQuote(tmuxSession)} -F "#{window_name}"`,
398
404
  { encoding: "utf-8" }
399
405
  );
400
- return windows.split("\n").some((name) => name.trim() === "sisyphus-dashboard");
406
+ const isOpen = windows.split("\n").some((name) => name.trim() === "sisyphus-dashboard");
407
+ if (isOpen) {
408
+ execSync4(
409
+ `tmux select-window -t ${shellQuote(tmuxSession)}:sisyphus-dashboard`
410
+ );
411
+ return false;
412
+ }
401
413
  } catch {
402
- return false;
403
414
  }
404
- }
405
- function launchDashboard(tmuxSession, cwd) {
406
415
  const tuiPath = join3(import.meta.dirname, "tui.js");
407
416
  const windowId = execSync4(
408
417
  `tmux new-window -n "sisyphus-dashboard" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
@@ -412,36 +421,33 @@ function launchDashboard(tmuxSession, cwd) {
412
421
  execSync4(
413
422
  `tmux send-keys -t ${shellQuote(windowId)} ${shellQuote(cmd)} Enter`
414
423
  );
424
+ return true;
415
425
  }
416
426
  function registerDashboard(program2) {
417
427
  program2.command("dashboard").description("Launch the TUI dashboard for monitoring and managing sessions").action(async () => {
418
428
  assertTmux();
419
429
  const tmuxSession = getTmuxSession();
420
- const cwd = process.cwd();
421
- launchDashboard(tmuxSession, cwd);
430
+ ensureDashboard(tmuxSession, process.cwd());
422
431
  });
423
432
  }
424
433
 
425
434
  // src/cli/commands/start.ts
426
- function shellQuote2(s) {
427
- return `'${s.replace(/'/g, "'\\''")}'`;
428
- }
429
- function isTmuxInstalled() {
430
- try {
431
- execSync5("which tmux", { stdio: "pipe" });
432
- return true;
433
- } catch {
434
- return false;
435
- }
436
- }
437
435
  function registerStart(program2) {
438
- program2.command("start").description("Start a new sisyphus session").argument("<task>", "Task description for the orchestrator").option("-c, --context <context>", "Background context for the orchestrator").option("-n, --name <name>", "Human-readable name for the session").action(async (task, opts) => {
436
+ program2.command("start").description("Start a new sisyphus session").argument("<task>", "Task description for the orchestrator").option("-c, --context <context>", "Background context for the orchestrator").option("-n, --name <name>", "Human-readable name for the session").option("--no-tmux-check", "Skip the tmux session check").action(async (task, opts) => {
439
437
  const cwd = process.env["SISYPHUS_CWD"] ?? process.cwd();
440
- if (!process.env["TMUX"] && isTmuxInstalled()) {
441
- console.log("Note: Sisyphus uses tmux to manage agent panes.");
442
- console.log("It is highly recommended to run sisyphus from inside a tmux session.");
443
- console.log(" tmux new-session");
444
- console.log("");
438
+ if (!process.env["TMUX"] && opts.tmuxCheck !== false) {
439
+ if (!isTmuxInstalled()) {
440
+ console.error("Error: tmux is not installed. Sisyphus requires tmux for agent panes.");
441
+ console.error(" Install: brew install tmux (macOS) or apt install tmux (Linux)");
442
+ console.error(" Then: tmux new-session");
443
+ process.exit(1);
444
+ }
445
+ console.error("Error: Not running inside a tmux session.");
446
+ console.error(" Sisyphus uses tmux to manage agent panes.");
447
+ console.error(" Start a tmux session first: tmux new-session");
448
+ console.error("");
449
+ console.error(' To skip this check: sisyphus start --no-tmux-check "task"');
450
+ process.exit(1);
445
451
  }
446
452
  const request = { type: "start", task, context: opts.context, cwd, name: opts.name };
447
453
  const response = await sendRequest(request);
@@ -450,13 +456,12 @@ function registerStart(program2) {
450
456
  const tmuxSessionName = response.data?.tmuxSessionName;
451
457
  if (process.env["TMUX"]) {
452
458
  try {
453
- execSync5(`tmux set-option @sisyphus_cwd ${shellQuote2(cwd)}`, { stdio: "ignore" });
459
+ execSync5(`tmux set-option @sisyphus_cwd ${shellQuote(cwd)}`, { stdio: "ignore" });
454
460
  } catch {
455
461
  }
456
462
  try {
457
463
  const tmuxSession = getTmuxSession();
458
- if (!isDashboardOpen(tmuxSession)) {
459
- launchDashboard(tmuxSession, cwd);
464
+ if (ensureDashboard(tmuxSession, cwd)) {
460
465
  console.log(`Dashboard opened in tmux window "sisyphus-dashboard"`);
461
466
  }
462
467
  } catch {
@@ -636,7 +641,7 @@ Follow up:`);
636
641
 
637
642
  // src/cli/commands/continue.ts
638
643
  function registerContinue(program2) {
639
- program2.command("continue").description("Reactivate a completed session (orchestrator only)").action(async () => {
644
+ program2.command("continue").description("Clear roadmap and continue working on a completed session (stays in current cycle)").addHelpText("after", "\n Use `continue` when a session completed but you want to add more work.\n Use `resume` when you want to restart with specific new instructions.\n").action(async () => {
640
645
  assertTmux();
641
646
  const sessionId = process.env.SISYPHUS_SESSION_ID;
642
647
  if (!sessionId) {
@@ -656,47 +661,22 @@ function registerContinue(program2) {
656
661
 
657
662
  // src/cli/commands/status.ts
658
663
  import { readFileSync as readFileSync3 } from "fs";
659
- var STATUS_COLORS = {
660
- active: "\x1B[32m",
661
- // green
662
- paused: "\x1B[33m",
663
- // yellow
664
- completed: "\x1B[36m",
665
- // cyan
666
- running: "\x1B[32m",
667
- // green
668
- killed: "\x1B[31m",
669
- // red
670
- crashed: "\x1B[31m",
671
- // red
672
- lost: "\x1B[90m"
673
- // gray
664
+ var COLOR_CODES = {
665
+ green: "\x1B[32m",
666
+ yellow: "\x1B[33m",
667
+ cyan: "\x1B[36m",
668
+ red: "\x1B[31m",
669
+ gray: "\x1B[90m",
670
+ white: "\x1B[37m"
674
671
  };
675
672
  var RESET = "\x1B[0m";
676
673
  var BOLD = "\x1B[1m";
677
674
  var DIM = "\x1B[2m";
678
675
  function colorize(text, status) {
679
- const color = STATUS_COLORS[status];
680
- if (!color) return `${text}${RESET}`;
681
- return `${color}${text}${RESET}`;
682
- }
683
- function formatMs(ms) {
684
- const totalSeconds = Math.floor(ms / 1e3);
685
- if (totalSeconds < 0) return "0s";
686
- const hours = Math.floor(totalSeconds / 3600);
687
- const minutes = Math.floor(totalSeconds % 3600 / 60);
688
- const seconds = totalSeconds % 60;
689
- const parts = [];
690
- if (hours > 0) parts.push(`${hours}h`);
691
- if (minutes > 0) parts.push(`${minutes}m`);
692
- parts.push(`${seconds}s`);
693
- return parts.join(" ");
694
- }
695
- function formatDuration(startOrMs, endIso) {
696
- if (typeof startOrMs === "number") return formatMs(startOrMs);
697
- const start = new Date(startOrMs).getTime();
698
- const end = endIso ? new Date(endIso).getTime() : Date.now();
699
- return formatMs(end - start);
676
+ const colorName = statusColor(status);
677
+ const code = COLOR_CODES[colorName];
678
+ if (!code) return `${text}\x1B[0m`;
679
+ return `${code}${text}\x1B[0m`;
700
680
  }
701
681
  function inferOrchestratorPhase(session) {
702
682
  const cycles = session.orchestratorCycles;
@@ -788,7 +768,7 @@ ${BOLD}Session: ${session.id}${RESET}`);
788
768
  console.log(` Duration: ${sessionDuration}${session.completedAt ? "" : " (ongoing)"} (${activeTime} active)`);
789
769
  const lastActivity = computeLastActivity(session);
790
770
  if (lastActivity) {
791
- console.log(` Last activity: ${formatMs(Date.now() - lastActivity.getTime())} ago`);
771
+ console.log(` Last activity: ${formatDuration(Date.now() - lastActivity.getTime())} ago`);
792
772
  }
793
773
  console.log(` Orchestrator cycles: ${session.orchestratorCycles.length}`);
794
774
  const runningAgents = session.agents.filter((a) => a.status === "running");
@@ -849,7 +829,7 @@ function registerStatus(program2) {
849
829
 
850
830
  // src/cli/commands/list.ts
851
831
  import { basename } from "path";
852
- var STATUS_COLORS2 = {
832
+ var STATUS_COLORS = {
853
833
  active: "\x1B[32m",
854
834
  paused: "\x1B[33m",
855
835
  completed: "\x1B[36m"
@@ -880,7 +860,7 @@ function registerList(program2) {
880
860
  return;
881
861
  }
882
862
  for (const s of sessions) {
883
- const color = STATUS_COLORS2[s.status] ?? "";
863
+ const color = STATUS_COLORS[s.status] ?? "";
884
864
  const status = `${color}${s.status}${RESET2}`;
885
865
  const agents = `${DIM2}${s.agentCount} agent(s)${RESET2}`;
886
866
  const task = truncateTask(s.task, 60);
@@ -927,7 +907,7 @@ function registerReport(program2) {
927
907
 
928
908
  // src/cli/commands/resume.ts
929
909
  function registerResume(program2) {
930
- program2.command("resume").description("Resume a paused session").argument("<session-id>", "Session ID to resume").argument("[message]", "Additional instructions for the orchestrator").action(async (sessionId, message) => {
910
+ program2.command("resume").description("Respawn orchestrator with new instructions (for paused/completed sessions)").addHelpText("after", "\n Use `resume` to restart a paused or completed session with new instructions.\n Use `continue` to keep working on a completed session without new instructions.\n").argument("<session-id>", "Session ID to resume").argument("[message]", "Additional instructions for the orchestrator").action(async (sessionId, message) => {
931
911
  const cwd = process.cwd();
932
912
  const request = { type: "resume", sessionId, cwd, message };
933
913
  const response = await sendRequest(request);
@@ -994,7 +974,7 @@ function registerNotify(program2) {
994
974
  notify.command("pane-exited").description("Notify daemon that a tmux pane exited").requiredOption("--pane-id <paneId>", "Pane ID that exited").action(async (opts) => {
995
975
  try {
996
976
  const request = { type: "pane-exited", paneId: opts.paneId };
997
- await rawSend(request);
977
+ await rawSend2(request);
998
978
  } catch {
999
979
  }
1000
980
  });
@@ -1107,25 +1087,81 @@ function registerSetupKeybind(program2) {
1107
1087
  // src/cli/commands/doctor.ts
1108
1088
  import { execSync as execSync7 } from "child_process";
1109
1089
  import { existsSync as existsSync3, statSync } from "fs";
1090
+ function checkNodeVersion() {
1091
+ const major = parseInt(process.versions.node.split(".")[0], 10);
1092
+ if (major < 22) {
1093
+ return { name: "Node.js", status: "fail", detail: `v${process.versions.node} (v22+ required)`, fix: "Install Node.js 22+: https://nodejs.org" };
1094
+ }
1095
+ return { name: "Node.js", status: "ok", detail: `v${process.versions.node}` };
1096
+ }
1097
+ function checkClaudeCli() {
1098
+ try {
1099
+ execSync7("which claude", { stdio: "pipe" });
1100
+ return { name: "Claude CLI", status: "ok", detail: "Found on PATH" };
1101
+ } catch {
1102
+ return {
1103
+ name: "Claude CLI",
1104
+ status: "fail",
1105
+ detail: "Not found on PATH",
1106
+ fix: "Install Claude Code: https://docs.anthropic.com/en/docs/claude-code/overview"
1107
+ };
1108
+ }
1109
+ }
1110
+ function checkGit() {
1111
+ try {
1112
+ const version = execSync7("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
1113
+ return { name: "git", status: "ok", detail: version };
1114
+ } catch {
1115
+ return { name: "git", status: "fail", detail: "Not found on PATH", fix: "Install git: https://git-scm.com/downloads" };
1116
+ }
1117
+ }
1118
+ function checkTmuxVersion() {
1119
+ try {
1120
+ const version = execSync7("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
1121
+ const match = version.match(/(\d+\.\d+)/);
1122
+ if (!match) return { name: "tmux version", status: "warn", detail: `Could not parse version: ${version}` };
1123
+ const ver = parseFloat(match[1]);
1124
+ if (ver < 3.2) {
1125
+ const upgradeHint = process.platform === "darwin" ? "brew install tmux (or upgrade)" : "apt install tmux (Debian/Ubuntu) or your package manager";
1126
+ return { name: "tmux version", status: "warn", detail: `${version} (3.2+ recommended for popup support)`, fix: upgradeHint };
1127
+ }
1128
+ return { name: "tmux version", status: "ok", detail: version };
1129
+ } catch {
1130
+ return { name: "tmux version", status: "warn", detail: "Could not determine version" };
1131
+ }
1132
+ }
1110
1133
  function checkDaemonInstalled() {
1111
- if (isInstalled()) {
1112
- return { name: "Daemon plist", status: "ok", detail: "Installed in LaunchAgents" };
1134
+ if (process.platform === "darwin") {
1135
+ if (isInstalled()) {
1136
+ return { name: "Daemon plist", status: "ok", detail: "Installed in LaunchAgents" };
1137
+ }
1138
+ return {
1139
+ name: "Daemon plist",
1140
+ status: "fail",
1141
+ detail: "Not installed",
1142
+ fix: 'Run any sisyphus command to auto-install, or: sisyphus start "test"'
1143
+ };
1144
+ }
1145
+ const pid = daemonPidPath();
1146
+ if (existsSync3(pid)) {
1147
+ return { name: "Daemon setup", status: "ok", detail: `PID file found at ${pid}` };
1113
1148
  }
1114
1149
  return {
1115
- name: "Daemon plist",
1150
+ name: "Daemon setup",
1116
1151
  status: "fail",
1117
- detail: "Not installed",
1118
- fix: 'Run any sisyphus command to auto-install, or: sisyphus start "test"'
1152
+ detail: "Daemon not running (no PID file)",
1153
+ fix: "Start manually: sisyphusd & \u2014 or configure via systemd"
1119
1154
  };
1120
1155
  }
1121
1156
  function checkDaemonRunning() {
1122
1157
  const pid = daemonPidPath();
1123
1158
  if (!existsSync3(pid)) {
1159
+ const fix = process.platform === "darwin" ? "launchctl load -w ~/Library/LaunchAgents/com.sisyphus.daemon.plist" : "sisyphusd & \u2014 or check if the process is running";
1124
1160
  return {
1125
1161
  name: "Daemon process",
1126
1162
  status: "fail",
1127
1163
  detail: "No PID file found",
1128
- fix: "launchctl load -w ~/Library/LaunchAgents/com.sisyphus.daemon.plist"
1164
+ fix
1129
1165
  };
1130
1166
  }
1131
1167
  try {
@@ -1145,7 +1181,8 @@ function checkTmux() {
1145
1181
  try {
1146
1182
  execSync7("which tmux", { stdio: "pipe" });
1147
1183
  } catch {
1148
- return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: "brew install tmux" };
1184
+ const installHint = process.platform === "darwin" ? "brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
1185
+ return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: installHint };
1149
1186
  }
1150
1187
  try {
1151
1188
  execSync7("tmux list-sessions", { stdio: "pipe" });
@@ -1216,10 +1253,14 @@ var SYMBOLS = { ok: "\u2713", warn: "!", fail: "\u2717" };
1216
1253
  function registerDoctor(program2) {
1217
1254
  program2.command("doctor").description("Check sisyphus installation health").action(async () => {
1218
1255
  const checks = [
1256
+ checkNodeVersion(),
1257
+ checkClaudeCli(),
1258
+ checkGit(),
1259
+ checkTmux(),
1260
+ checkTmuxVersion(),
1219
1261
  checkGlobalDir(),
1220
1262
  checkDaemonInstalled(),
1221
1263
  checkDaemonRunning(),
1222
- checkTmux(),
1223
1264
  checkCycleScript(),
1224
1265
  checkTmuxKeybind()
1225
1266
  ];
@@ -1229,7 +1270,7 @@ function registerDoctor(program2) {
1229
1270
  console.log(` ${sym} ${c.name}: ${c.detail}`);
1230
1271
  if (c.status !== "ok") hasIssues = true;
1231
1272
  }
1232
- const fixable = checks.filter((c) => c.fix);
1273
+ const fixable = checks.filter((c) => c.fix && c.status !== "ok");
1233
1274
  if (fixable.length > 0) {
1234
1275
  console.log("\nFixes:");
1235
1276
  for (const c of fixable) {
@@ -1251,18 +1292,9 @@ function registerCompanionContext(program2) {
1251
1292
  }
1252
1293
 
1253
1294
  // src/cli/commands/getting-started.ts
1254
- import { execSync as execSync8 } from "child_process";
1255
- function isTmuxInstalled2() {
1256
- try {
1257
- execSync8("which tmux", { stdio: "pipe" });
1258
- return true;
1259
- } catch {
1260
- return false;
1261
- }
1262
- }
1263
1295
  function registerGettingStarted(program2) {
1264
1296
  program2.command("getting-started").description("Show a complete guide to using sisyphus effectively").action(() => {
1265
- const hasTmux = isTmuxInstalled2();
1297
+ const hasTmux = isTmuxInstalled();
1266
1298
  const inTmux = !!process.env["TMUX"];
1267
1299
  const lines = [
1268
1300
  "",
@@ -1374,15 +1406,67 @@ function registerGettingStarted(program2) {
1374
1406
  " Health:",
1375
1407
  " sisyphus doctor Check installation health",
1376
1408
  " tail -f ~/.sisyphus/daemon.log Watch daemon logs",
1409
+ "",
1410
+ " \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",
1411
+ "",
1412
+ " 1. Run `sisyphus doctor` to check your setup",
1413
+ " 2. Start a tmux session: `tmux new-session`",
1414
+ ' 3. Try it: `sisyphus start "your task description"`',
1377
1415
  ""
1378
1416
  );
1379
1417
  console.log(lines.join("\n"));
1380
1418
  });
1381
1419
  }
1382
1420
 
1421
+ // src/cli/commands/init.ts
1422
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
1423
+ import { join as join4 } from "path";
1424
+ var DEFAULT_CONFIG = {};
1425
+ var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
1426
+
1427
+ <!-- This file overrides the default orchestrator system prompt. -->
1428
+ <!-- Delete this file to use the built-in prompt. -->
1429
+ <!-- See: https://github.com/silasrhyneer/sisyphi for details. -->
1430
+ `;
1431
+ function registerInit(program2) {
1432
+ program2.command("init").description("Initialize sisyphus configuration for this project").option("--orchestrator", "Also create a custom orchestrator prompt template").action((opts) => {
1433
+ const cwd = process.cwd();
1434
+ const sisDir = join4(cwd, ".sisyphus");
1435
+ const configPath = join4(sisDir, "config.json");
1436
+ if (existsSync4(configPath)) {
1437
+ console.log(`Already initialized: ${configPath}`);
1438
+ return;
1439
+ }
1440
+ mkdirSync3(sisDir, { recursive: true });
1441
+ writeFileSync3(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
1442
+ console.log(`Created ${configPath}`);
1443
+ if (opts.orchestrator) {
1444
+ const orchPath = join4(sisDir, "orchestrator.md");
1445
+ if (!existsSync4(orchPath)) {
1446
+ writeFileSync3(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
1447
+ console.log(`Created ${orchPath}`);
1448
+ }
1449
+ }
1450
+ console.log("");
1451
+ console.log("Configuration options (add to .sisyphus/config.json):");
1452
+ console.log(' orchestratorEffort \u2014 "low" | "medium" | "high" | "max" (default: "high")');
1453
+ console.log(' agentEffort \u2014 "low" | "medium" | "high" | "max" (default: "medium")');
1454
+ console.log(" pollIntervalMs \u2014 Daemon poll interval in ms (default: 5000)");
1455
+ console.log(" autoUpdate \u2014 Auto-update daemon on restart (default: true)");
1456
+ });
1457
+ }
1458
+
1383
1459
  // src/cli/index.ts
1460
+ var nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
1461
+ if (nodeVersion < 22) {
1462
+ console.error(`Sisyphus requires Node.js v22+ (current: v${process.versions.node})`);
1463
+ process.exit(1);
1464
+ }
1384
1465
  var program = new Command();
1385
1466
  program.name("sisyphus").description("tmux-integrated orchestration daemon for Claude Code").version("0.1.0");
1467
+ program.configureHelp({
1468
+ sortSubcommands: false
1469
+ });
1386
1470
  registerStart(program);
1387
1471
  registerSpawn(program);
1388
1472
  registerSubmit(program);
@@ -1405,6 +1489,30 @@ registerSetupKeybind(program);
1405
1489
  registerDoctor(program);
1406
1490
  registerCompanionContext(program);
1407
1491
  registerGettingStarted(program);
1492
+ registerInit(program);
1493
+ program.addHelpText("after", `
1494
+ Examples:
1495
+ $ sisyphus start "Implement auth system" Start a new session
1496
+ $ sisyphus start "Build @spec.md" -n auth Start with a name and spec reference
1497
+ $ sisyphus status Check current sessions
1498
+ $ sisyphus dashboard Open the TUI
1499
+ $ sisyphus doctor Verify installation
1500
+
1501
+ Run 'sisyphus getting-started' for a complete usage guide.
1502
+ `);
1503
+ var args = process.argv.slice(2);
1504
+ var firstArg = args[0];
1505
+ var skipWelcome = ["doctor", "getting-started", "help", "--help", "-h", "init", "uninstall", "--version", "-V"];
1506
+ if (!existsSync5(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
1507
+ mkdirSync4(globalDir(), { recursive: true });
1508
+ console.log("");
1509
+ console.log(" Welcome to Sisyphus \u2014 multi-agent orchestration for Claude Code.");
1510
+ console.log("");
1511
+ console.log(" First time? Run these commands:");
1512
+ console.log(" sisyphus doctor Check your setup");
1513
+ console.log(" sisyphus getting-started Learn the basics");
1514
+ console.log("");
1515
+ }
1408
1516
  program.parseAsync(process.argv).catch((err) => {
1409
1517
  console.error(err.message);
1410
1518
  process.exit(1);