sisyphi 0.1.1 → 0.1.3

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.
Files changed (74) hide show
  1. package/README.md +9 -0
  2. package/dist/{chunk-T6Z5F4SP.js → chunk-N2BPQOO2.js} +27 -3
  3. package/dist/chunk-N2BPQOO2.js.map +1 -0
  4. package/dist/cli.js +241 -161
  5. package/dist/cli.js.map +1 -1
  6. package/dist/daemon.js +608 -187
  7. package/dist/daemon.js.map +1 -1
  8. package/dist/templates/CLAUDE.md +50 -0
  9. package/dist/templates/agent-plugin/.claude/agents/debug.md +39 -0
  10. package/dist/templates/agent-plugin/.claude/agents/plan.md +101 -0
  11. package/dist/templates/agent-plugin/.claude/agents/review-plan.md +81 -0
  12. package/dist/templates/agent-plugin/.claude/agents/review.md +56 -0
  13. package/dist/templates/agent-plugin/.claude/agents/spec-draft.md +73 -0
  14. package/dist/templates/agent-plugin/.claude/agents/test-spec.md +56 -0
  15. package/dist/templates/agent-plugin/.claude-plugin/plugin.json +5 -0
  16. package/dist/templates/agent-plugin/agents/CLAUDE.md +52 -0
  17. package/dist/templates/agent-plugin/agents/debug.md +39 -0
  18. package/dist/templates/agent-plugin/agents/operator.md +56 -0
  19. package/dist/templates/agent-plugin/agents/plan.md +101 -0
  20. package/dist/templates/agent-plugin/agents/review-plan.md +81 -0
  21. package/dist/templates/agent-plugin/agents/review.md +56 -0
  22. package/dist/templates/agent-plugin/agents/spec-draft.md +73 -0
  23. package/dist/templates/agent-plugin/agents/test-spec.md +56 -0
  24. package/dist/templates/agent-suffix.md +3 -1
  25. package/dist/templates/banner.txt +25 -0
  26. package/dist/templates/orchestrator-plugin/.claude/commands/begin.md +62 -0
  27. package/dist/templates/orchestrator-plugin/.claude/skills/orchestration/SKILL.md +40 -0
  28. package/dist/templates/orchestrator-plugin/.claude/skills/orchestration/task-patterns.md +222 -0
  29. package/dist/templates/orchestrator-plugin/.claude/skills/orchestration/workflow-examples.md +208 -0
  30. package/dist/templates/orchestrator-plugin/.claude-plugin/plugin.json +5 -0
  31. package/dist/templates/orchestrator-plugin/hooks/hooks.json +25 -0
  32. package/dist/templates/orchestrator-plugin/scripts/block-task.sh +4 -0
  33. package/dist/templates/orchestrator-plugin/scripts/stop-suggest.sh +4 -0
  34. package/dist/templates/orchestrator-plugin/skills/git-management/SKILL.md +111 -0
  35. package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +40 -0
  36. package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +248 -0
  37. package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +237 -0
  38. package/dist/templates/orchestrator-settings.json +2 -0
  39. package/dist/templates/orchestrator.md +56 -49
  40. package/dist/templates/resources/.claude/agents/debug.md +39 -0
  41. package/dist/templates/resources/.claude/agents/plan.md +101 -0
  42. package/dist/templates/resources/.claude/agents/review-plan.md +81 -0
  43. package/dist/templates/resources/.claude/agents/review.md +56 -0
  44. package/dist/templates/resources/.claude/agents/spec-draft.md +73 -0
  45. package/dist/templates/resources/.claude/agents/test-spec.md +56 -0
  46. package/dist/templates/resources/.claude/commands/begin.md +62 -0
  47. package/dist/templates/resources/.claude/skills/orchestration/SKILL.md +40 -0
  48. package/dist/templates/resources/.claude/skills/orchestration/task-patterns.md +222 -0
  49. package/dist/templates/resources/.claude/skills/orchestration/workflow-examples.md +208 -0
  50. package/dist/templates/resources/.claude-plugin/plugin.json +8 -0
  51. package/package.json +2 -2
  52. package/templates/CLAUDE.md +50 -0
  53. package/templates/agent-plugin/.claude-plugin/plugin.json +5 -0
  54. package/templates/agent-plugin/agents/CLAUDE.md +52 -0
  55. package/templates/agent-plugin/agents/debug.md +39 -0
  56. package/templates/agent-plugin/agents/operator.md +56 -0
  57. package/templates/agent-plugin/agents/plan.md +101 -0
  58. package/templates/agent-plugin/agents/review-plan.md +81 -0
  59. package/templates/agent-plugin/agents/review.md +56 -0
  60. package/templates/agent-plugin/agents/spec-draft.md +73 -0
  61. package/templates/agent-plugin/agents/test-spec.md +56 -0
  62. package/templates/agent-suffix.md +3 -1
  63. package/templates/banner.txt +25 -0
  64. package/templates/orchestrator-plugin/.claude-plugin/plugin.json +5 -0
  65. package/templates/orchestrator-plugin/hooks/hooks.json +25 -0
  66. package/templates/orchestrator-plugin/scripts/block-task.sh +4 -0
  67. package/templates/orchestrator-plugin/scripts/stop-suggest.sh +4 -0
  68. package/templates/orchestrator-plugin/skills/git-management/SKILL.md +111 -0
  69. package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +40 -0
  70. package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +248 -0
  71. package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +237 -0
  72. package/templates/orchestrator-settings.json +2 -0
  73. package/templates/orchestrator.md +56 -49
  74. package/dist/chunk-T6Z5F4SP.js.map +0 -1
package/README.md CHANGED
@@ -1,3 +1,12 @@
1
+ ```
2
+ _____ _____ _______ _______ _ _ _ _ _____
3
+ / ___|_ _/ ___\ \ / / ___ \ | | | | | / ___|
4
+ \ `--. | | \ `--. \ V /| |_/ / |_| | | | \ `--.
5
+ `--. \ | | `--. \ \ / | __/| _ | | | |`--. \
6
+ /\__/ /_| |_/\__/ / | | | | | | | | |_| /\__/ /
7
+ \____/ \___/\____/ \_/ \_| \_| |_/\___/\____/
8
+ ```
9
+
1
10
  # sisyphi
2
11
 
3
12
  A tmux-integrated orchestration daemon for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) multi-agent workflows.
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/shared/paths.ts
4
4
  import { homedir } from "os";
5
- import { join } from "path";
5
+ import { basename, join } from "path";
6
6
  function globalDir() {
7
7
  return join(homedir(), ".sisyphus");
8
8
  }
@@ -12,6 +12,9 @@ function socketPath() {
12
12
  function globalConfigPath() {
13
13
  return join(globalDir(), "config.json");
14
14
  }
15
+ function daemonLogPath() {
16
+ return join(globalDir(), "daemon.log");
17
+ }
15
18
  function daemonPidPath() {
16
19
  return join(globalDir(), "daemon.pid");
17
20
  }
@@ -39,14 +42,30 @@ function reportsDir(cwd, sessionId) {
39
42
  function reportFilePath(cwd, sessionId, agentId, suffix) {
40
43
  return join(reportsDir(cwd, sessionId), `${agentId}-${suffix}.md`);
41
44
  }
45
+ function promptsDir(cwd, sessionId) {
46
+ return join(sessionDir(cwd, sessionId), "prompts");
47
+ }
42
48
  function contextDir(cwd, sessionId) {
43
49
  return join(sessionDir(cwd, sessionId), "context");
44
50
  }
51
+ function planPath(cwd, sessionId) {
52
+ return join(sessionDir(cwd, sessionId), "plan.md");
53
+ }
54
+ function logsPath(cwd, sessionId) {
55
+ return join(sessionDir(cwd, sessionId), "logs.md");
56
+ }
57
+ function worktreeConfigPath(cwd) {
58
+ return join(projectDir(cwd), "worktree.json");
59
+ }
60
+ function worktreeBaseDir(cwd) {
61
+ return join(cwd, "..", `${basename(cwd)}-sisyphus-wt`);
62
+ }
45
63
 
46
64
  export {
47
65
  globalDir,
48
66
  socketPath,
49
67
  globalConfigPath,
68
+ daemonLogPath,
50
69
  daemonPidPath,
51
70
  projectConfigPath,
52
71
  projectOrchestratorPromptPath,
@@ -55,6 +74,11 @@ export {
55
74
  statePath,
56
75
  reportsDir,
57
76
  reportFilePath,
58
- contextDir
77
+ promptsDir,
78
+ contextDir,
79
+ planPath,
80
+ logsPath,
81
+ worktreeConfigPath,
82
+ worktreeBaseDir
59
83
  };
60
- //# sourceMappingURL=chunk-T6Z5F4SP.js.map
84
+ //# sourceMappingURL=chunk-N2BPQOO2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/paths.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { basename, join } from 'node:path';\n\nexport function globalDir(): string {\n return join(homedir(), '.sisyphus');\n}\n\nexport function socketPath(): string {\n return join(globalDir(), 'daemon.sock');\n}\n\nexport function globalConfigPath(): string {\n return join(globalDir(), 'config.json');\n}\n\nexport function daemonLogPath(): string {\n return join(globalDir(), 'daemon.log');\n}\n\nexport function daemonPidPath(): string {\n return join(globalDir(), 'daemon.pid');\n}\n\nexport function projectDir(cwd: string): string {\n return join(cwd, '.sisyphus');\n}\n\nexport function projectConfigPath(cwd: string): string {\n return join(projectDir(cwd), 'config.json');\n}\n\nexport function projectOrchestratorPromptPath(cwd: string): string {\n return join(projectDir(cwd), 'orchestrator.md');\n}\n\nexport function sessionsDir(cwd: string): string {\n return join(projectDir(cwd), 'sessions');\n}\n\nexport function sessionDir(cwd: string, sessionId: string): string {\n return join(sessionsDir(cwd), sessionId);\n}\n\nexport function statePath(cwd: string, sessionId: string): string {\n return join(sessionDir(cwd, sessionId), 'state.json');\n}\n\nexport function reportsDir(cwd: string, sessionId: string): string {\n return join(sessionDir(cwd, sessionId), 'reports');\n}\n\nexport function reportFilePath(cwd: string, sessionId: string, agentId: string, suffix: string): string {\n return join(reportsDir(cwd, sessionId), `${agentId}-${suffix}.md`);\n}\n\nexport function promptsDir(cwd: string, sessionId: string): string {\n return join(sessionDir(cwd, sessionId), 'prompts');\n}\n\nexport function contextDir(cwd: string, sessionId: string): string {\n return join(sessionDir(cwd, sessionId), 'context');\n}\n\nexport function planPath(cwd: string, sessionId: string): string {\n return join(sessionDir(cwd, sessionId), 'plan.md');\n}\n\nexport function logsPath(cwd: string, sessionId: string): string {\n return join(sessionDir(cwd, sessionId), 'logs.md');\n}\n\nexport function worktreeConfigPath(cwd: string): string {\n return join(projectDir(cwd), 'worktree.json');\n}\n\nexport function worktreeBaseDir(cwd: string): string {\n return join(cwd, '..', `${basename(cwd)}-sisyphus-wt`);\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;AACxB,SAAS,UAAU,YAAY;AAExB,SAAS,YAAoB;AAClC,SAAO,KAAK,QAAQ,GAAG,WAAW;AACpC;AAEO,SAAS,aAAqB;AACnC,SAAO,KAAK,UAAU,GAAG,aAAa;AACxC;AAEO,SAAS,mBAA2B;AACzC,SAAO,KAAK,UAAU,GAAG,aAAa;AACxC;AAEO,SAAS,gBAAwB;AACtC,SAAO,KAAK,UAAU,GAAG,YAAY;AACvC;AAEO,SAAS,gBAAwB;AACtC,SAAO,KAAK,UAAU,GAAG,YAAY;AACvC;AAEO,SAAS,WAAW,KAAqB;AAC9C,SAAO,KAAK,KAAK,WAAW;AAC9B;AAEO,SAAS,kBAAkB,KAAqB;AACrD,SAAO,KAAK,WAAW,GAAG,GAAG,aAAa;AAC5C;AAEO,SAAS,8BAA8B,KAAqB;AACjE,SAAO,KAAK,WAAW,GAAG,GAAG,iBAAiB;AAChD;AAEO,SAAS,YAAY,KAAqB;AAC/C,SAAO,KAAK,WAAW,GAAG,GAAG,UAAU;AACzC;AAEO,SAAS,WAAW,KAAa,WAA2B;AACjE,SAAO,KAAK,YAAY,GAAG,GAAG,SAAS;AACzC;AAEO,SAAS,UAAU,KAAa,WAA2B;AAChE,SAAO,KAAK,WAAW,KAAK,SAAS,GAAG,YAAY;AACtD;AAEO,SAAS,WAAW,KAAa,WAA2B;AACjE,SAAO,KAAK,WAAW,KAAK,SAAS,GAAG,SAAS;AACnD;AAEO,SAAS,eAAe,KAAa,WAAmB,SAAiB,QAAwB;AACtG,SAAO,KAAK,WAAW,KAAK,SAAS,GAAG,GAAG,OAAO,IAAI,MAAM,KAAK;AACnE;AAEO,SAAS,WAAW,KAAa,WAA2B;AACjE,SAAO,KAAK,WAAW,KAAK,SAAS,GAAG,SAAS;AACnD;AAEO,SAAS,WAAW,KAAa,WAA2B;AACjE,SAAO,KAAK,WAAW,KAAK,SAAS,GAAG,SAAS;AACnD;AAEO,SAAS,SAAS,KAAa,WAA2B;AAC/D,SAAO,KAAK,WAAW,KAAK,SAAS,GAAG,SAAS;AACnD;AAEO,SAAS,SAAS,KAAa,WAA2B;AAC/D,SAAO,KAAK,WAAW,KAAK,SAAS,GAAG,SAAS;AACnD;AAEO,SAAS,mBAAmB,KAAqB;AACtD,SAAO,KAAK,WAAW,GAAG,GAAG,eAAe;AAC9C;AAEO,SAAS,gBAAgB,KAAqB;AACnD,SAAO,KAAK,KAAK,MAAM,GAAG,SAAS,GAAG,CAAC,cAAc;AACvD;","names":[]}
package/dist/cli.js CHANGED
@@ -1,20 +1,136 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ daemonLogPath,
4
+ globalDir,
3
5
  socketPath
4
- } from "./chunk-T6Z5F4SP.js";
6
+ } from "./chunk-N2BPQOO2.js";
5
7
 
6
8
  // src/cli/index.ts
7
9
  import { Command } from "commander";
8
10
 
9
- // src/cli/commands/start.ts
11
+ // src/cli/client.ts
12
+ import { connect as connect2 } from "net";
13
+
14
+ // src/cli/install.ts
10
15
  import { execSync } from "child_process";
16
+ import { existsSync, mkdirSync, rmSync, unlinkSync, writeFileSync } from "fs";
17
+ import { connect } from "net";
18
+ import { homedir } from "os";
19
+ import { dirname, join, resolve } from "path";
20
+ import { fileURLToPath } from "url";
21
+ var PLIST_LABEL = "com.sisyphus.daemon";
22
+ var PLIST_FILENAME = `${PLIST_LABEL}.plist`;
23
+ function launchAgentDir() {
24
+ return join(homedir(), "Library", "LaunchAgents");
25
+ }
26
+ function plistPath() {
27
+ return join(launchAgentDir(), PLIST_FILENAME);
28
+ }
29
+ function daemonBinPath() {
30
+ const installDir = dirname(fileURLToPath(import.meta.url));
31
+ return resolve(installDir, "..", "daemon.js");
32
+ }
33
+ function generatePlist(nodePath, daemonPath, logPath) {
34
+ return `<?xml version="1.0" encoding="UTF-8"?>
35
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
36
+ <plist version="1.0">
37
+ <dict>
38
+ <key>Label</key>
39
+ <string>${PLIST_LABEL}</string>
40
+ <key>ProgramArguments</key>
41
+ <array>
42
+ <string>${nodePath}</string>
43
+ <string>${daemonPath}</string>
44
+ </array>
45
+ <key>RunAtLoad</key>
46
+ <true/>
47
+ <key>KeepAlive</key>
48
+ <true/>
49
+ <key>StandardOutPath</key>
50
+ <string>${logPath}</string>
51
+ <key>StandardErrorPath</key>
52
+ <string>${logPath}</string>
53
+ </dict>
54
+ </plist>
55
+ `;
56
+ }
57
+ function isInstalled() {
58
+ return existsSync(plistPath());
59
+ }
60
+ async function ensureDaemonInstalled() {
61
+ if (process.platform !== "darwin") return;
62
+ if (!isInstalled()) {
63
+ const nodePath = process.execPath;
64
+ const daemonPath = daemonBinPath();
65
+ const logPath = daemonLogPath();
66
+ mkdirSync(globalDir(), { recursive: true });
67
+ mkdirSync(launchAgentDir(), { recursive: true });
68
+ const plist = generatePlist(nodePath, daemonPath, logPath);
69
+ writeFileSync(plistPath(), plist, "utf8");
70
+ execSync(`launchctl load -w ${plistPath()}`);
71
+ }
72
+ await waitForDaemon();
73
+ }
74
+ async function uninstallDaemon(purge) {
75
+ if (process.platform !== "darwin") {
76
+ console.log("Auto-install is only supported on macOS.");
77
+ return;
78
+ }
79
+ const plist = plistPath();
80
+ if (existsSync(plist)) {
81
+ try {
82
+ execSync(`launchctl unload -w ${plist}`, { stdio: "pipe" });
83
+ } catch {
84
+ }
85
+ unlinkSync(plist);
86
+ console.log("Daemon unloaded and plist removed.");
87
+ } else {
88
+ console.log("Daemon is not installed (plist not found).");
89
+ }
90
+ if (purge) {
91
+ const dir = globalDir();
92
+ if (existsSync(dir)) {
93
+ rmSync(dir, { recursive: true, force: true });
94
+ console.log(`Removed ${dir}`);
95
+ }
96
+ }
97
+ }
98
+ function testConnection() {
99
+ return new Promise((resolve2, reject) => {
100
+ const sock = connect(socketPath());
101
+ sock.on("connect", () => {
102
+ sock.destroy();
103
+ resolve2();
104
+ });
105
+ sock.on("error", (err) => {
106
+ sock.destroy();
107
+ reject(err);
108
+ });
109
+ });
110
+ }
111
+ function sleep(ms) {
112
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
113
+ }
114
+ async function waitForDaemon(maxWaitMs = 6e3) {
115
+ const start = Date.now();
116
+ while (Date.now() - start < maxWaitMs) {
117
+ if (existsSync(socketPath())) {
118
+ try {
119
+ await testConnection();
120
+ return;
121
+ } catch {
122
+ }
123
+ }
124
+ await sleep(300);
125
+ }
126
+ throw new Error(`Daemon did not start within ${maxWaitMs}ms. Check ${daemonLogPath()}`);
127
+ }
11
128
 
12
129
  // src/cli/client.ts
13
- import { connect } from "net";
14
- async function sendRequest(request) {
130
+ function rawSend(request) {
15
131
  const sock = socketPath();
16
- return new Promise((resolve, reject) => {
17
- const socket = connect(sock);
132
+ return new Promise((resolve2, reject) => {
133
+ const socket = connect2(sock);
18
134
  let data = "";
19
135
  const timeout = setTimeout(() => {
20
136
  socket.destroy();
@@ -31,7 +147,7 @@ async function sendRequest(request) {
31
147
  const line = data.slice(0, newlineIdx);
32
148
  socket.destroy();
33
149
  try {
34
- resolve(JSON.parse(line));
150
+ resolve2(JSON.parse(line));
35
151
  } catch {
36
152
  reject(new Error(`Invalid JSON response from daemon: ${line}`));
37
153
  }
@@ -39,34 +155,64 @@ async function sendRequest(request) {
39
155
  });
40
156
  socket.on("error", (err) => {
41
157
  clearTimeout(timeout);
42
- if (err.code === "ENOENT" || err.code === "ECONNREFUSED") {
43
- reject(new Error(
44
- `Sisyphus daemon is not running.
45
- Start it with: launchctl load ~/Library/LaunchAgents/com.sisyphus.daemon.plist
46
- Or check logs at: ~/.sisyphus/daemon.log`
47
- ));
48
- } else {
49
- reject(err);
50
- }
158
+ reject(err);
51
159
  });
52
160
  });
53
161
  }
162
+ async function sendRequest(request) {
163
+ const sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
164
+ const MAX_ATTEMPTS = 5;
165
+ const RETRY_DELAY_MS = 2e3;
166
+ let installedDaemon = false;
167
+ let lastErr;
168
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
169
+ try {
170
+ return await rawSend(request);
171
+ } catch (err) {
172
+ lastErr = err;
173
+ const code = err.code;
174
+ if (code !== "ENOENT" && code !== "ECONNREFUSED") {
175
+ throw err;
176
+ }
177
+ if (attempt === MAX_ATTEMPTS) break;
178
+ if (process.platform === "darwin" && !installedDaemon) {
179
+ installedDaemon = true;
180
+ await ensureDaemonInstalled();
181
+ await waitForDaemon(5e3);
182
+ } else {
183
+ process.stderr.write(`Daemon not ready, retrying (${attempt}/${MAX_ATTEMPTS - 1})...
184
+ `);
185
+ await sleep2(RETRY_DELAY_MS);
186
+ }
187
+ }
188
+ }
189
+ if (process.platform !== "darwin") {
190
+ throw new Error(
191
+ `Sisyphus daemon is not running.
192
+ Start it manually: sisyphusd &
193
+ Or check logs at: ~/.sisyphus/daemon.log`
194
+ );
195
+ }
196
+ throw lastErr;
197
+ }
54
198
 
55
- // src/cli/commands/start.ts
56
- function getTmuxSession() {
57
- try {
58
- return execSync('tmux display-message -p "#{session_name}"', { encoding: "utf8" }).trim();
59
- } catch {
60
- throw new Error("Not running inside tmux");
199
+ // src/cli/tmux.ts
200
+ import { execSync as execSync2 } from "child_process";
201
+ function assertTmux() {
202
+ if (!process.env.TMUX) {
203
+ throw new Error("Not running inside a tmux pane. Sisyphus requires tmux.");
61
204
  }
62
205
  }
206
+ function getTmuxSession() {
207
+ assertTmux();
208
+ return execSync2('tmux display-message -p "#{session_name}"', { encoding: "utf8" }).trim();
209
+ }
63
210
  function getTmuxWindow() {
64
- try {
65
- return execSync('tmux display-message -p "#{window_id}"', { encoding: "utf8" }).trim();
66
- } catch {
67
- throw new Error("Not running inside tmux");
68
- }
211
+ assertTmux();
212
+ return execSync2('tmux display-message -p "#{window_id}"', { encoding: "utf8" }).trim();
69
213
  }
214
+
215
+ // src/cli/commands/start.ts
70
216
  function registerStart(program2) {
71
217
  program2.command("start").description("Start a new sisyphus session").argument("<task>", "Task description for the orchestrator").action(async (task) => {
72
218
  const tmuxSession = getTmuxSession();
@@ -88,7 +234,8 @@ function registerStart(program2) {
88
234
 
89
235
  // src/cli/commands/spawn.ts
90
236
  function registerSpawn(program2) {
91
- program2.command("spawn").description("Spawn a new agent (orchestrator only)").option("--agent-type <type>", "Agent role label (default: worker)", "worker").requiredOption("--name <name>", "Agent name").requiredOption("--instruction <instruction>", "Task instruction for the agent").action(async (opts) => {
237
+ program2.command("spawn").description("Spawn a new agent (orchestrator only)").option("--agent-type <type>", "Agent role label (default: worker)", "worker").requiredOption("--name <name>", "Agent name").requiredOption("--instruction <instruction>", "Task instruction for the agent").option("--worktree", "Spawn agent in an isolated git worktree").action(async (opts) => {
238
+ assertTmux();
92
239
  const sessionId = process.env.SISYPHUS_SESSION_ID;
93
240
  if (!sessionId) {
94
241
  console.error("Error: SISYPHUS_SESSION_ID environment variable not set");
@@ -99,7 +246,8 @@ function registerSpawn(program2) {
99
246
  sessionId,
100
247
  agentType: opts.agentType,
101
248
  name: opts.name,
102
- instruction: opts.instruction
249
+ instruction: opts.instruction,
250
+ ...opts.worktree ? { worktree: true } : {}
103
251
  };
104
252
  const response = await sendRequest(request);
105
253
  if (response.ok) {
@@ -117,12 +265,12 @@ function registerSpawn(program2) {
117
265
  // src/cli/stdin.ts
118
266
  function readStdin() {
119
267
  if (process.stdin.isTTY) return Promise.resolve(null);
120
- return new Promise((resolve, reject) => {
268
+ return new Promise((resolve2, reject) => {
121
269
  const chunks = [];
122
270
  process.stdin.on("data", (chunk) => chunks.push(chunk));
123
271
  process.stdin.on("end", () => {
124
272
  const text = Buffer.concat(chunks).toString("utf-8").trim();
125
- resolve(text || null);
273
+ resolve2(text || null);
126
274
  });
127
275
  process.stdin.on("error", reject);
128
276
  });
@@ -131,6 +279,7 @@ function readStdin() {
131
279
  // src/cli/commands/submit.ts
132
280
  function registerSubmit(program2) {
133
281
  program2.command("submit").description("Submit work report and exit (agent only)").option("--report <report>", "Work report (or pipe via stdin)").action(async (opts) => {
282
+ assertTmux();
134
283
  const sessionId = process.env.SISYPHUS_SESSION_ID;
135
284
  const agentId = process.env.SISYPHUS_AGENT_ID;
136
285
  if (!sessionId || !agentId) {
@@ -157,6 +306,7 @@ function registerSubmit(program2) {
157
306
  // src/cli/commands/yield.ts
158
307
  function registerYield(program2) {
159
308
  program2.command("yield").description("Yield control back to daemon (orchestrator only)").option("--prompt <text>", "Instructions for the next orchestrator cycle (or pipe via stdin)").action(async (opts) => {
309
+ assertTmux();
160
310
  const sessionId = process.env.SISYPHUS_SESSION_ID;
161
311
  if (!sessionId) {
162
312
  console.error("Error: SISYPHUS_SESSION_ID environment variable not set");
@@ -177,6 +327,7 @@ function registerYield(program2) {
177
327
  // src/cli/commands/complete.ts
178
328
  function registerComplete(program2) {
179
329
  program2.command("complete").description("Mark session as completed (orchestrator only)").requiredOption("--report <report>", "Final completion report").action(async (opts) => {
330
+ assertTmux();
180
331
  const sessionId = process.env.SISYPHUS_SESSION_ID;
181
332
  if (!sessionId) {
182
333
  console.error("Error: SISYPHUS_SESSION_ID environment variable not set");
@@ -186,7 +337,6 @@ function registerComplete(program2) {
186
337
  const response = await sendRequest(request);
187
338
  if (response.ok) {
188
339
  console.log("Session completed.");
189
- console.log("All panes will close.");
190
340
  } else {
191
341
  console.error(`Error: ${response.error}`);
192
342
  process.exit(1);
@@ -208,16 +358,8 @@ var STATUS_COLORS = {
208
358
  // red
209
359
  crashed: "\x1B[31m",
210
360
  // red
211
- lost: "\x1B[90m",
361
+ lost: "\x1B[90m"
212
362
  // gray
213
- draft: "\x1B[2m",
214
- // dim
215
- pending: "\x1B[90m",
216
- // gray
217
- in_progress: "\x1B[33m",
218
- // yellow
219
- done: "\x1B[32m"
220
- // green
221
363
  };
222
364
  var RESET = "\x1B[0m";
223
365
  var BOLD = "\x1B[1m";
@@ -259,10 +401,6 @@ function formatAgent(agent) {
259
401
  }
260
402
  return line;
261
403
  }
262
- function formatTask(task) {
263
- const status = colorize(task.status, task.status);
264
- return ` ${task.id}: ${task.description} [${status}]`;
265
- }
266
404
  function formatCycle(cycle) {
267
405
  const duration = cycle.completedAt ? ` ${DIM}(${formatDuration(cycle.timestamp, cycle.completedAt)})${RESET}` : ` ${DIM}(running)${RESET}`;
268
406
  const agents = cycle.agentsSpawned.length > 0 ? ` \u2014 agents: ${cycle.agentsSpawned.join(", ")}` : "";
@@ -286,13 +424,6 @@ ${BOLD}Session: ${session.id}${RESET}`);
286
424
  console.log(formatCycle(cycle));
287
425
  }
288
426
  }
289
- if (session.tasks.length > 0) {
290
- console.log(`
291
- ${BOLD}Tasks:${RESET}`);
292
- for (const task of session.tasks) {
293
- console.log(formatTask(task));
294
- }
295
- }
296
427
  if (session.agents.length > 0) {
297
428
  console.log(`
298
429
  ${BOLD}Agents:${RESET}`);
@@ -320,112 +451,50 @@ function registerStatus(program2) {
320
451
  });
321
452
  }
322
453
 
323
- // src/cli/commands/tasks.ts
324
- function getSessionId() {
325
- const sessionId = process.env.SISYPHUS_SESSION_ID;
326
- if (!sessionId) {
327
- console.error("Error: SISYPHUS_SESSION_ID environment variable not set");
328
- process.exit(1);
329
- }
330
- return sessionId;
331
- }
332
- var STATUS_COLORS2 = {
333
- draft: "\x1B[2m",
334
- // dim
335
- pending: "\x1B[90m",
336
- // gray
337
- in_progress: "\x1B[33m",
338
- // yellow
339
- done: "\x1B[32m"
340
- // green
341
- };
342
- var RESET2 = "\x1B[0m";
343
- function registerTasks(program2) {
344
- const tasks = program2.command("tasks").description("Manage session tasks");
345
- tasks.command("add").description("Add a new task").argument("[description]", "Task description (or pipe via stdin)").option("--status <status>", "Initial status (draft|pending)", "pending").action(async (descriptionArg, opts) => {
346
- const description = descriptionArg ?? await readStdin();
347
- if (!description) {
348
- console.error("Error: provide a description argument or pipe via stdin");
349
- process.exit(1);
350
- }
351
- const sessionId = getSessionId();
352
- const request = { type: "tasks_add", sessionId, description, status: opts.status !== "pending" ? opts.status : void 0 };
353
- const response = await sendRequest(request);
354
- if (response.ok) {
355
- const taskId = response.data?.taskId;
356
- console.log(`Task added: ${taskId} [${opts.status}]`);
357
- } else {
358
- console.error(`Error: ${response.error}`);
359
- if (response.error?.includes("Unknown session")) console.error("Hint: run `sisyphus list` to see active sessions.");
360
- process.exit(1);
361
- }
362
- });
363
- tasks.command("update").description("Update a task").argument("<task-id>", "Task ID (e.g. t1)").option("--status <status>", "New status (draft|pending|in_progress|done)").option("--description <description>", "New description").action(async (taskId, opts) => {
364
- if (!opts.status && !opts.description) {
365
- console.error("Error: provide --status and/or --description");
366
- process.exit(1);
367
- }
368
- const sessionId = getSessionId();
369
- const request = { type: "tasks_update", sessionId, taskId, status: opts.status, description: opts.description };
370
- const response = await sendRequest(request);
371
- if (response.ok) {
372
- const parts = [];
373
- if (opts.status) parts.push(`status \u2192 ${opts.status}`);
374
- if (opts.description) parts.push(`description updated`);
375
- console.log(`Task ${taskId}: ${parts.join(", ")}`);
376
- } else {
377
- console.error(`Error: ${response.error}`);
378
- if (response.error?.includes("not found")) console.error("Hint: run `sisyphus tasks list` to see current tasks.");
379
- if (response.error?.includes("Unknown session")) console.error("Hint: run `sisyphus list` to see active sessions.");
380
- process.exit(1);
381
- }
382
- });
383
- tasks.command("list").description("List all tasks").action(async () => {
384
- const sessionId = getSessionId();
385
- const request = { type: "tasks_list", sessionId };
386
- const response = await sendRequest(request);
387
- if (response.ok) {
388
- const taskList = response.data?.tasks ?? [];
389
- if (taskList.length === 0) {
390
- console.log("No tasks");
391
- return;
392
- }
393
- for (const task of taskList) {
394
- const color = STATUS_COLORS2[task.status] ?? "";
395
- console.log(` ${task.id}: ${task.description} [${color}${task.status}${RESET2}]`);
396
- }
397
- } else {
398
- console.error(`Error: ${response.error}`);
399
- if (response.error?.includes("Unknown session")) console.error("Hint: run `sisyphus list` to see active sessions.");
400
- process.exit(1);
401
- }
402
- });
403
- }
404
-
405
454
  // src/cli/commands/list.ts
406
- var STATUS_COLORS3 = {
455
+ import { basename } from "path";
456
+ var STATUS_COLORS2 = {
407
457
  active: "\x1B[32m",
408
458
  paused: "\x1B[33m",
409
459
  completed: "\x1B[36m"
410
460
  };
411
- var RESET3 = "\x1B[0m";
461
+ var RESET2 = "\x1B[0m";
412
462
  var BOLD2 = "\x1B[1m";
413
463
  var DIM2 = "\x1B[2m";
464
+ function truncateTask(task, max) {
465
+ if (task.length <= max) return task;
466
+ return task.slice(0, max - 1) + "\u2026";
467
+ }
414
468
  function registerList(program2) {
415
- program2.command("list").description("List all sessions").action(async () => {
416
- const request = { type: "list" };
469
+ program2.command("list").description("List sessions (defaults to current project)").option("-a, --all", "Show sessions from all projects").action(async (opts) => {
470
+ const cwd = process.cwd();
471
+ const request = { type: "list", cwd, all: opts.all };
417
472
  const response = await sendRequest(request);
418
473
  if (response.ok) {
419
474
  const sessions = response.data?.sessions ?? [];
475
+ const totalCount = response.data?.totalCount;
476
+ const filtered = response.data?.filtered;
420
477
  if (sessions.length === 0) {
421
- console.log("No sessions");
478
+ if (filtered && totalCount && totalCount > 0) {
479
+ console.log(`No sessions in this project. ${totalCount} session(s) in other projects.`);
480
+ console.log(`${DIM2}Run ${RESET2}sisyphus list --all${DIM2} to show all.${RESET2}`);
481
+ } else {
482
+ console.log("No sessions");
483
+ }
422
484
  return;
423
485
  }
424
486
  for (const s of sessions) {
425
- const color = STATUS_COLORS3[s.status] ?? "";
426
- const status = `${color}${s.status}${RESET3}`;
427
- const agents = `${DIM2}${s.agentCount} agent(s)${RESET3}`;
428
- console.log(` ${BOLD2}${s.id}${RESET3} ${status} ${agents} ${s.task}`);
487
+ const color = STATUS_COLORS2[s.status] ?? "";
488
+ const status = `${color}${s.status}${RESET2}`;
489
+ const agents = `${DIM2}${s.agentCount} agent(s)${RESET2}`;
490
+ const task = truncateTask(s.task, 60);
491
+ const cwdLabel = opts.all && s.cwd ? ` ${DIM2}${basename(s.cwd)}${RESET2}` : "";
492
+ console.log(` ${BOLD2}${s.id}${RESET2} ${status} ${agents} ${task}${cwdLabel}`);
493
+ }
494
+ if (filtered && totalCount && totalCount > sessions.length) {
495
+ const otherCount = totalCount - sessions.length;
496
+ console.log(`
497
+ ${DIM2}${otherCount} more session(s) in other projects. Run ${RESET2}sisyphus list --all${DIM2} to show all.${RESET2}`);
429
498
  }
430
499
  } else {
431
500
  console.error(`Error: ${response.error}`);
@@ -437,6 +506,7 @@ function registerList(program2) {
437
506
  // src/cli/commands/report.ts
438
507
  function registerReport(program2) {
439
508
  program2.command("report").description("Send a progress report without exiting (agent only)").option("--message <message>", "Progress report content").action(async (opts) => {
509
+ assertTmux();
440
510
  const sessionId = process.env.SISYPHUS_SESSION_ID;
441
511
  const agentId = process.env.SISYPHUS_AGENT_ID;
442
512
  if (!sessionId || !agentId) {
@@ -460,25 +530,10 @@ function registerReport(program2) {
460
530
  }
461
531
 
462
532
  // src/cli/commands/resume.ts
463
- import { execSync as execSync2 } from "child_process";
464
- function getTmuxSession2() {
465
- try {
466
- return execSync2('tmux display-message -p "#{session_name}"', { encoding: "utf8" }).trim();
467
- } catch {
468
- throw new Error("Not running inside tmux");
469
- }
470
- }
471
- function getTmuxWindow2() {
472
- try {
473
- return execSync2('tmux display-message -p "#{window_id}"', { encoding: "utf8" }).trim();
474
- } catch {
475
- throw new Error("Not running inside tmux");
476
- }
477
- }
478
533
  function registerResume(program2) {
479
534
  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) => {
480
- const tmuxSession = getTmuxSession2();
481
- const tmuxWindow = getTmuxWindow2();
535
+ const tmuxSession = getTmuxSession();
536
+ const tmuxWindow = getTmuxWindow();
482
537
  const cwd = process.cwd();
483
538
  const request = { type: "resume", sessionId, cwd, tmuxSession, tmuxWindow, message };
484
539
  const response = await sendRequest(request);
@@ -512,6 +567,31 @@ function registerKill(program2) {
512
567
  });
513
568
  }
514
569
 
570
+ // src/cli/commands/uninstall.ts
571
+ import { createInterface } from "readline";
572
+ async function confirm(question) {
573
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
574
+ return new Promise((resolve2) => {
575
+ rl.question(question, (answer) => {
576
+ rl.close();
577
+ resolve2(answer.trim().toLowerCase() === "y");
578
+ });
579
+ });
580
+ }
581
+ function registerUninstall(program2) {
582
+ program2.command("uninstall").description("Unload the sisyphus daemon from launchd and remove the plist").option("--purge", "Also remove all session data in ~/.sisyphus").option("-y, --yes", "Skip confirmation prompt for --purge").action(async (opts) => {
583
+ const purge = opts.purge ?? false;
584
+ if (purge && !opts.yes) {
585
+ const ok = await confirm("This will delete all session data in ~/.sisyphus. Continue? (y/N) ");
586
+ if (!ok) {
587
+ console.log("Aborted.");
588
+ return;
589
+ }
590
+ }
591
+ await uninstallDaemon(purge);
592
+ });
593
+ }
594
+
515
595
  // src/cli/index.ts
516
596
  var program = new Command();
517
597
  program.name("sisyphus").description("tmux-integrated orchestration daemon for Claude Code").version("0.1.0");
@@ -522,10 +602,10 @@ registerReport(program);
522
602
  registerYield(program);
523
603
  registerComplete(program);
524
604
  registerStatus(program);
525
- registerTasks(program);
526
605
  registerList(program);
527
606
  registerResume(program);
528
607
  registerKill(program);
608
+ registerUninstall(program);
529
609
  program.parseAsync(process.argv).catch((err) => {
530
610
  console.error(err.message);
531
611
  process.exit(1);