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.
- package/README.md +9 -0
- package/dist/{chunk-T6Z5F4SP.js → chunk-N2BPQOO2.js} +27 -3
- package/dist/chunk-N2BPQOO2.js.map +1 -0
- package/dist/cli.js +241 -161
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +608 -187
- package/dist/daemon.js.map +1 -1
- package/dist/templates/CLAUDE.md +50 -0
- package/dist/templates/agent-plugin/.claude/agents/debug.md +39 -0
- package/dist/templates/agent-plugin/.claude/agents/plan.md +101 -0
- package/dist/templates/agent-plugin/.claude/agents/review-plan.md +81 -0
- package/dist/templates/agent-plugin/.claude/agents/review.md +56 -0
- package/dist/templates/agent-plugin/.claude/agents/spec-draft.md +73 -0
- package/dist/templates/agent-plugin/.claude/agents/test-spec.md +56 -0
- package/dist/templates/agent-plugin/.claude-plugin/plugin.json +5 -0
- package/dist/templates/agent-plugin/agents/CLAUDE.md +52 -0
- package/dist/templates/agent-plugin/agents/debug.md +39 -0
- package/dist/templates/agent-plugin/agents/operator.md +56 -0
- package/dist/templates/agent-plugin/agents/plan.md +101 -0
- package/dist/templates/agent-plugin/agents/review-plan.md +81 -0
- package/dist/templates/agent-plugin/agents/review.md +56 -0
- package/dist/templates/agent-plugin/agents/spec-draft.md +73 -0
- package/dist/templates/agent-plugin/agents/test-spec.md +56 -0
- package/dist/templates/agent-suffix.md +3 -1
- package/dist/templates/banner.txt +25 -0
- package/dist/templates/orchestrator-plugin/.claude/commands/begin.md +62 -0
- package/dist/templates/orchestrator-plugin/.claude/skills/orchestration/SKILL.md +40 -0
- package/dist/templates/orchestrator-plugin/.claude/skills/orchestration/task-patterns.md +222 -0
- package/dist/templates/orchestrator-plugin/.claude/skills/orchestration/workflow-examples.md +208 -0
- package/dist/templates/orchestrator-plugin/.claude-plugin/plugin.json +5 -0
- package/dist/templates/orchestrator-plugin/hooks/hooks.json +25 -0
- package/dist/templates/orchestrator-plugin/scripts/block-task.sh +4 -0
- package/dist/templates/orchestrator-plugin/scripts/stop-suggest.sh +4 -0
- package/dist/templates/orchestrator-plugin/skills/git-management/SKILL.md +111 -0
- package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +40 -0
- package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +248 -0
- package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +237 -0
- package/dist/templates/orchestrator-settings.json +2 -0
- package/dist/templates/orchestrator.md +56 -49
- package/dist/templates/resources/.claude/agents/debug.md +39 -0
- package/dist/templates/resources/.claude/agents/plan.md +101 -0
- package/dist/templates/resources/.claude/agents/review-plan.md +81 -0
- package/dist/templates/resources/.claude/agents/review.md +56 -0
- package/dist/templates/resources/.claude/agents/spec-draft.md +73 -0
- package/dist/templates/resources/.claude/agents/test-spec.md +56 -0
- package/dist/templates/resources/.claude/commands/begin.md +62 -0
- package/dist/templates/resources/.claude/skills/orchestration/SKILL.md +40 -0
- package/dist/templates/resources/.claude/skills/orchestration/task-patterns.md +222 -0
- package/dist/templates/resources/.claude/skills/orchestration/workflow-examples.md +208 -0
- package/dist/templates/resources/.claude-plugin/plugin.json +8 -0
- package/package.json +2 -2
- package/templates/CLAUDE.md +50 -0
- package/templates/agent-plugin/.claude-plugin/plugin.json +5 -0
- package/templates/agent-plugin/agents/CLAUDE.md +52 -0
- package/templates/agent-plugin/agents/debug.md +39 -0
- package/templates/agent-plugin/agents/operator.md +56 -0
- package/templates/agent-plugin/agents/plan.md +101 -0
- package/templates/agent-plugin/agents/review-plan.md +81 -0
- package/templates/agent-plugin/agents/review.md +56 -0
- package/templates/agent-plugin/agents/spec-draft.md +73 -0
- package/templates/agent-plugin/agents/test-spec.md +56 -0
- package/templates/agent-suffix.md +3 -1
- package/templates/banner.txt +25 -0
- package/templates/orchestrator-plugin/.claude-plugin/plugin.json +5 -0
- package/templates/orchestrator-plugin/hooks/hooks.json +25 -0
- package/templates/orchestrator-plugin/scripts/block-task.sh +4 -0
- package/templates/orchestrator-plugin/scripts/stop-suggest.sh +4 -0
- package/templates/orchestrator-plugin/skills/git-management/SKILL.md +111 -0
- package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +40 -0
- package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +248 -0
- package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +237 -0
- package/templates/orchestrator-settings.json +2 -0
- package/templates/orchestrator.md +56 -49
- 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
|
-
|
|
77
|
+
promptsDir,
|
|
78
|
+
contextDir,
|
|
79
|
+
planPath,
|
|
80
|
+
logsPath,
|
|
81
|
+
worktreeConfigPath,
|
|
82
|
+
worktreeBaseDir
|
|
59
83
|
};
|
|
60
|
-
//# sourceMappingURL=chunk-
|
|
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-
|
|
6
|
+
} from "./chunk-N2BPQOO2.js";
|
|
5
7
|
|
|
6
8
|
// src/cli/index.ts
|
|
7
9
|
import { Command } from "commander";
|
|
8
10
|
|
|
9
|
-
// src/cli/
|
|
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
|
-
|
|
14
|
-
async function sendRequest(request) {
|
|
130
|
+
function rawSend(request) {
|
|
15
131
|
const sock = socketPath();
|
|
16
|
-
return new Promise((
|
|
17
|
-
const socket =
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
426
|
-
const status = `${color}${s.status}${
|
|
427
|
-
const agents = `${DIM2}${s.agentCount} agent(s)${
|
|
428
|
-
|
|
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 =
|
|
481
|
-
const tmuxWindow =
|
|
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);
|