sisyphi 0.1.21 → 0.1.23
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/chunk-KQBSC5KY.js +31 -0
- package/dist/chunk-KQBSC5KY.js.map +1 -0
- package/dist/{chunk-LTAW6OWS.js → chunk-YGBGKMTF.js} +31 -6
- package/dist/chunk-YGBGKMTF.js.map +1 -0
- package/dist/chunk-ZE2SKB4B.js +35 -0
- package/dist/chunk-ZE2SKB4B.js.map +1 -0
- package/dist/cli.js +638 -51
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +915 -289
- package/dist/daemon.js.map +1 -1
- package/dist/paths-FYYSBD27.js +58 -0
- package/dist/paths-FYYSBD27.js.map +1 -0
- package/dist/templates/CLAUDE.md +21 -20
- package/dist/templates/agent-plugin/agents/CLAUDE.md +2 -0
- package/dist/templates/agent-plugin/agents/debug.md +1 -0
- package/dist/templates/agent-plugin/agents/operator.md +1 -2
- package/dist/templates/agent-plugin/agents/plan.md +86 -55
- package/dist/templates/agent-plugin/agents/review-plan.md +1 -0
- package/dist/templates/agent-plugin/agents/spec-draft.md +1 -0
- package/dist/templates/agent-plugin/hooks/hooks.json +19 -1
- package/dist/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
- package/dist/templates/agent-plugin/hooks/require-submit.sh +24 -0
- package/dist/templates/agent-suffix.md +18 -0
- package/dist/templates/dashboard-claude.md +38 -0
- package/dist/templates/orchestrator-base.md +270 -0
- package/dist/templates/orchestrator-impl.md +116 -0
- package/dist/templates/orchestrator-planning.md +131 -0
- package/dist/templates/orchestrator-plugin/hooks/hooks.json +1 -15
- package/dist/templates/orchestrator-plugin/skills/git-management/SKILL.md +1 -1
- package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +4 -16
- package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +22 -23
- package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +11 -11
- package/dist/tui.js +3236 -0
- package/dist/tui.js.map +1 -0
- package/package.json +5 -1
- package/templates/CLAUDE.md +21 -20
- package/templates/agent-plugin/agents/CLAUDE.md +2 -0
- package/templates/agent-plugin/agents/debug.md +1 -0
- package/templates/agent-plugin/agents/operator.md +1 -2
- package/templates/agent-plugin/agents/plan.md +86 -55
- package/templates/agent-plugin/agents/review-plan.md +1 -0
- package/templates/agent-plugin/agents/spec-draft.md +1 -0
- package/templates/agent-plugin/hooks/hooks.json +19 -1
- package/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
- package/templates/agent-plugin/hooks/require-submit.sh +24 -0
- package/templates/agent-suffix.md +18 -0
- package/templates/dashboard-claude.md +38 -0
- package/templates/orchestrator-base.md +270 -0
- package/templates/orchestrator-impl.md +116 -0
- package/templates/orchestrator-planning.md +131 -0
- package/templates/orchestrator-plugin/hooks/hooks.json +1 -15
- package/templates/orchestrator-plugin/skills/git-management/SKILL.md +1 -1
- package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +4 -16
- package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +22 -23
- package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +11 -11
- package/dist/chunk-LTAW6OWS.js.map +0 -1
- package/dist/templates/orchestrator-plugin/scripts/block-task.sh +0 -11
- package/dist/templates/orchestrator.md +0 -173
- package/templates/orchestrator-plugin/scripts/block-task.sh +0 -11
- package/templates/orchestrator.md +0 -173
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,172 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
computeActiveTimeMs
|
|
4
|
+
} from "./chunk-ZE2SKB4B.js";
|
|
2
5
|
import {
|
|
3
6
|
daemonLogPath,
|
|
7
|
+
daemonPidPath,
|
|
4
8
|
daemonUpdatingPath,
|
|
5
9
|
globalDir,
|
|
10
|
+
roadmapPath,
|
|
6
11
|
socketPath
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YGBGKMTF.js";
|
|
8
13
|
|
|
9
14
|
// src/cli/index.ts
|
|
10
15
|
import { Command } from "commander";
|
|
11
16
|
|
|
17
|
+
// src/cli/commands/start.ts
|
|
18
|
+
import { execSync as execSync5 } from "child_process";
|
|
19
|
+
|
|
12
20
|
// src/cli/client.ts
|
|
13
21
|
import { connect as connect2 } from "net";
|
|
14
22
|
|
|
15
23
|
// src/cli/install.ts
|
|
16
|
-
import { execSync } from "child_process";
|
|
17
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "fs";
|
|
24
|
+
import { execSync as execSync2 } from "child_process";
|
|
25
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
18
26
|
import { connect } from "net";
|
|
19
|
-
import { homedir } from "os";
|
|
20
|
-
import { dirname, join, resolve } from "path";
|
|
27
|
+
import { homedir as homedir2 } from "os";
|
|
28
|
+
import { dirname, join as join2, resolve } from "path";
|
|
21
29
|
import { fileURLToPath } from "url";
|
|
30
|
+
|
|
31
|
+
// src/cli/tmux-setup.ts
|
|
32
|
+
import { execSync } from "child_process";
|
|
33
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, unlinkSync } from "fs";
|
|
34
|
+
import { homedir } from "os";
|
|
35
|
+
import { join } from "path";
|
|
36
|
+
var DEFAULT_KEY = "M-s";
|
|
37
|
+
var SISYPHUS_CONF_MARKER = "# sisyphus-managed \u2014 do not edit";
|
|
38
|
+
function cycleScriptPath() {
|
|
39
|
+
return join(globalDir(), "bin", "sisyphus-cycle");
|
|
40
|
+
}
|
|
41
|
+
function sisyphusTmuxConfPath() {
|
|
42
|
+
return join(globalDir(), "tmux.conf");
|
|
43
|
+
}
|
|
44
|
+
function userTmuxConfPath() {
|
|
45
|
+
const dotfile = join(homedir(), ".tmux.conf");
|
|
46
|
+
const xdg = join(homedir(), ".config", "tmux", "tmux.conf");
|
|
47
|
+
if (existsSync(xdg)) return xdg;
|
|
48
|
+
if (existsSync(dotfile)) return dotfile;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
var CYCLE_SCRIPT = `#!/bin/bash
|
|
52
|
+
cwd=$(tmux show-option -v @sisyphus_cwd 2>/dev/null)
|
|
53
|
+
[ -z "$cwd" ] && exit 0
|
|
54
|
+
current=$(tmux display-message -p '#{session_name}')
|
|
55
|
+
sessions=()
|
|
56
|
+
while IFS= read -r name; do
|
|
57
|
+
scwd=$(tmux show-option -t "$name" -v @sisyphus_cwd 2>/dev/null)
|
|
58
|
+
[ "$scwd" = "$cwd" ] && sessions+=("$name")
|
|
59
|
+
done < <(tmux list-sessions -F '#{session_name}')
|
|
60
|
+
(( \${#sessions[@]} <= 1 )) && exit 0
|
|
61
|
+
for (( i=0; i<\${#sessions[@]}; i++ )); do
|
|
62
|
+
if [ "\${sessions[$i]}" = "$current" ]; then
|
|
63
|
+
next=$(( (i + 1) % \${#sessions[@]} ))
|
|
64
|
+
tmux switch-client -t "\${sessions[$next]}"
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
done
|
|
68
|
+
tmux switch-client -t "\${sessions[0]}"
|
|
69
|
+
`;
|
|
70
|
+
function installCycleScript() {
|
|
71
|
+
const scriptPath = cycleScriptPath();
|
|
72
|
+
mkdirSync(join(globalDir(), "bin"), { recursive: true });
|
|
73
|
+
writeFileSync(scriptPath, CYCLE_SCRIPT, "utf8");
|
|
74
|
+
chmodSync(scriptPath, 493);
|
|
75
|
+
}
|
|
76
|
+
function getExistingBinding(key) {
|
|
77
|
+
try {
|
|
78
|
+
const output = execSync("tmux list-keys", { stdio: ["pipe", "pipe", "pipe"] }).toString();
|
|
79
|
+
for (const line of output.split("\n")) {
|
|
80
|
+
if (line.includes(key)) {
|
|
81
|
+
const parts = line.trim().split(/\s+/);
|
|
82
|
+
const keyIdx = parts.indexOf(key);
|
|
83
|
+
if (keyIdx !== -1) {
|
|
84
|
+
return line.trim();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function isSisyphusBinding(binding) {
|
|
94
|
+
return binding.includes("sisyphus");
|
|
95
|
+
}
|
|
96
|
+
function setupTmuxKeybind(key = DEFAULT_KEY) {
|
|
97
|
+
installCycleScript();
|
|
98
|
+
const existing = getExistingBinding(key);
|
|
99
|
+
if (existing !== null && !isSisyphusBinding(existing)) {
|
|
100
|
+
return {
|
|
101
|
+
status: "conflict",
|
|
102
|
+
message: `Tmux key ${key} is already bound to something else. Run "sisyphus setup-keybind <key>" to use a different key.`,
|
|
103
|
+
existingBinding: existing
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const confPath = sisyphusTmuxConfPath();
|
|
107
|
+
const bindingLine = `bind-key -T root ${key} run-shell ${cycleScriptPath()}`;
|
|
108
|
+
writeFileSync(confPath, `${SISYPHUS_CONF_MARKER}
|
|
109
|
+
${bindingLine}
|
|
110
|
+
`, "utf8");
|
|
111
|
+
const userConf = userTmuxConfPath();
|
|
112
|
+
const sourceLine = `source-file ${confPath}`;
|
|
113
|
+
const markedSourceLine = `${sourceLine} ${SISYPHUS_CONF_MARKER}`;
|
|
114
|
+
let persistedToConf = false;
|
|
115
|
+
if (userConf !== null) {
|
|
116
|
+
const contents = readFileSync(userConf, "utf8");
|
|
117
|
+
if (!contents.includes(confPath)) {
|
|
118
|
+
const separator = contents.endsWith("\n") ? "" : "\n";
|
|
119
|
+
writeFileSync(userConf, `${contents}${separator}${markedSourceLine}
|
|
120
|
+
`, "utf8");
|
|
121
|
+
}
|
|
122
|
+
persistedToConf = true;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
execSync(`tmux bind-key -T root ${key} run-shell ${cycleScriptPath()}`, { stdio: "pipe" });
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
if (existing !== null && isSisyphusBinding(existing)) {
|
|
129
|
+
return {
|
|
130
|
+
status: "already-installed",
|
|
131
|
+
message: `Tmux keybinding ${key} already configured for sisyphus.`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const persistNote = persistedToConf ? "" : `
|
|
135
|
+
Note: No tmux.conf found. Add this to your tmux config for persistence:
|
|
136
|
+
source-file ${confPath}`;
|
|
137
|
+
return {
|
|
138
|
+
status: "installed",
|
|
139
|
+
message: `Tmux keybinding set: ${key} cycles sisyphus sessions${persistNote}`
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function removeTmuxKeybind() {
|
|
143
|
+
const confPath = sisyphusTmuxConfPath();
|
|
144
|
+
for (const candidate of [join(homedir(), ".tmux.conf"), join(homedir(), ".config", "tmux", "tmux.conf")]) {
|
|
145
|
+
if (existsSync(candidate)) {
|
|
146
|
+
const contents = readFileSync(candidate, "utf8");
|
|
147
|
+
const filtered = contents.split("\n").filter((line) => !line.includes(confPath)).join("\n");
|
|
148
|
+
if (filtered !== contents) {
|
|
149
|
+
writeFileSync(candidate, filtered, "utf8");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (existsSync(confPath)) {
|
|
154
|
+
unlinkSync(confPath);
|
|
155
|
+
}
|
|
156
|
+
const scriptPath = cycleScriptPath();
|
|
157
|
+
if (existsSync(scriptPath)) {
|
|
158
|
+
unlinkSync(scriptPath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/cli/install.ts
|
|
22
163
|
var PLIST_LABEL = "com.sisyphus.daemon";
|
|
23
164
|
var PLIST_FILENAME = `${PLIST_LABEL}.plist`;
|
|
24
165
|
function launchAgentDir() {
|
|
25
|
-
return
|
|
166
|
+
return join2(homedir2(), "Library", "LaunchAgents");
|
|
26
167
|
}
|
|
27
168
|
function plistPath() {
|
|
28
|
-
return
|
|
169
|
+
return join2(launchAgentDir(), PLIST_FILENAME);
|
|
29
170
|
}
|
|
30
171
|
function daemonBinPath() {
|
|
31
172
|
const installDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -56,7 +197,7 @@ function generatePlist(nodePath, daemonPath, logPath) {
|
|
|
56
197
|
`;
|
|
57
198
|
}
|
|
58
199
|
function isInstalled() {
|
|
59
|
-
return
|
|
200
|
+
return existsSync2(plistPath());
|
|
60
201
|
}
|
|
61
202
|
async function ensureDaemonInstalled() {
|
|
62
203
|
if (process.platform !== "darwin") return;
|
|
@@ -64,11 +205,13 @@ async function ensureDaemonInstalled() {
|
|
|
64
205
|
const nodePath = process.execPath;
|
|
65
206
|
const daemonPath = daemonBinPath();
|
|
66
207
|
const logPath = daemonLogPath();
|
|
67
|
-
|
|
68
|
-
|
|
208
|
+
mkdirSync2(globalDir(), { recursive: true });
|
|
209
|
+
mkdirSync2(launchAgentDir(), { recursive: true });
|
|
69
210
|
const plist = generatePlist(nodePath, daemonPath, logPath);
|
|
70
|
-
|
|
71
|
-
|
|
211
|
+
writeFileSync2(plistPath(), plist, "utf8");
|
|
212
|
+
execSync2(`launchctl load -w ${plistPath()}`);
|
|
213
|
+
const keybindResult = setupTmuxKeybind();
|
|
214
|
+
printGettingStarted(keybindResult);
|
|
72
215
|
}
|
|
73
216
|
await waitForDaemon();
|
|
74
217
|
}
|
|
@@ -78,24 +221,60 @@ async function uninstallDaemon(purge) {
|
|
|
78
221
|
return;
|
|
79
222
|
}
|
|
80
223
|
const plist = plistPath();
|
|
81
|
-
if (
|
|
224
|
+
if (existsSync2(plist)) {
|
|
82
225
|
try {
|
|
83
|
-
|
|
226
|
+
execSync2(`launchctl unload -w ${plist}`, { stdio: "pipe" });
|
|
84
227
|
} catch {
|
|
85
228
|
}
|
|
86
|
-
|
|
229
|
+
unlinkSync2(plist);
|
|
87
230
|
console.log("Daemon unloaded and plist removed.");
|
|
88
231
|
} else {
|
|
89
232
|
console.log("Daemon is not installed (plist not found).");
|
|
90
233
|
}
|
|
234
|
+
removeTmuxKeybind();
|
|
91
235
|
if (purge) {
|
|
92
236
|
const dir = globalDir();
|
|
93
|
-
if (
|
|
237
|
+
if (existsSync2(dir)) {
|
|
94
238
|
rmSync(dir, { recursive: true, force: true });
|
|
95
239
|
console.log(`Removed ${dir}`);
|
|
96
240
|
}
|
|
97
241
|
}
|
|
98
242
|
}
|
|
243
|
+
function printGettingStarted(keybindResult) {
|
|
244
|
+
const lines = [
|
|
245
|
+
"",
|
|
246
|
+
"Sisyphus installed \u2014 daemon running via launchd.",
|
|
247
|
+
"",
|
|
248
|
+
"Sisyphus is a tmux-integrated orchestration daemon for Claude Code multi-agent workflows.",
|
|
249
|
+
"A background daemon manages sessions where an orchestrator Claude breaks tasks into",
|
|
250
|
+
"subtasks, spawns agent Claude instances in tmux panes, and coordinates their lifecycle.",
|
|
251
|
+
"",
|
|
252
|
+
"Quick start:",
|
|
253
|
+
' sisyphus start "task description" Start a session (must be inside tmux)',
|
|
254
|
+
" sisyphus list List sessions",
|
|
255
|
+
" sisyphus status Show current session status",
|
|
256
|
+
" sisyphus kill <id> Kill a session",
|
|
257
|
+
"",
|
|
258
|
+
"Monitoring:",
|
|
259
|
+
" sisyphus dashboard Open TUI dashboard",
|
|
260
|
+
" tail -f ~/.sisyphus/daemon.log Watch daemon logs",
|
|
261
|
+
""
|
|
262
|
+
];
|
|
263
|
+
if (keybindResult.status === "installed") {
|
|
264
|
+
lines.push(`Tmux keybind: ${keybindResult.message}`);
|
|
265
|
+
} else if (keybindResult.status === "conflict") {
|
|
266
|
+
lines.push(`Keybind: ${keybindResult.message}`);
|
|
267
|
+
}
|
|
268
|
+
lines.push(
|
|
269
|
+
"",
|
|
270
|
+
"Troubleshooting:",
|
|
271
|
+
" sisyphus doctor Check installation health",
|
|
272
|
+
" sisyphus setup-keybind [key] Configure tmux session-cycling keybind",
|
|
273
|
+
" sisyphus uninstall [--purge] Remove daemon and optionally all data",
|
|
274
|
+
""
|
|
275
|
+
);
|
|
276
|
+
console.log(lines.join("\n"));
|
|
277
|
+
}
|
|
99
278
|
function testConnection() {
|
|
100
279
|
return new Promise((resolve2, reject) => {
|
|
101
280
|
const sock = connect(socketPath());
|
|
@@ -117,10 +296,10 @@ async function waitForDaemon(maxWaitMs = 6e3) {
|
|
|
117
296
|
let updatingLogged = false;
|
|
118
297
|
while (Date.now() - start < maxWaitMs) {
|
|
119
298
|
const updatingPath = daemonUpdatingPath();
|
|
120
|
-
if (
|
|
299
|
+
if (existsSync2(updatingPath)) {
|
|
121
300
|
if (!updatingLogged) {
|
|
122
301
|
try {
|
|
123
|
-
const version =
|
|
302
|
+
const version = readFileSync2(updatingPath, "utf-8").trim();
|
|
124
303
|
console.log(`Updating sisyphus to ${version}...`);
|
|
125
304
|
} catch {
|
|
126
305
|
console.log("Updating sisyphus...");
|
|
@@ -129,7 +308,7 @@ async function waitForDaemon(maxWaitMs = 6e3) {
|
|
|
129
308
|
}
|
|
130
309
|
maxWaitMs = Math.max(maxWaitMs, 3e4);
|
|
131
310
|
}
|
|
132
|
-
if (
|
|
311
|
+
if (existsSync2(socketPath())) {
|
|
133
312
|
try {
|
|
134
313
|
await testConnection();
|
|
135
314
|
return;
|
|
@@ -212,7 +391,7 @@ async function sendRequest(request) {
|
|
|
212
391
|
}
|
|
213
392
|
|
|
214
393
|
// src/cli/tmux.ts
|
|
215
|
-
import { execSync as
|
|
394
|
+
import { execSync as execSync3 } from "child_process";
|
|
216
395
|
function assertTmux() {
|
|
217
396
|
if (!process.env.TMUX) {
|
|
218
397
|
throw new Error("Not running inside a tmux pane. Sisyphus requires tmux.");
|
|
@@ -220,24 +399,79 @@ function assertTmux() {
|
|
|
220
399
|
}
|
|
221
400
|
function getTmuxSession() {
|
|
222
401
|
assertTmux();
|
|
223
|
-
return
|
|
402
|
+
return execSync3('tmux display-message -p "#{session_name}"', { encoding: "utf8" }).trim();
|
|
224
403
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
404
|
+
|
|
405
|
+
// src/cli/commands/dashboard.ts
|
|
406
|
+
import { join as join3 } from "path";
|
|
407
|
+
import { execSync as execSync4 } from "child_process";
|
|
408
|
+
function shellQuote(s) {
|
|
409
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
410
|
+
}
|
|
411
|
+
function isDashboardOpen(tmuxSession) {
|
|
412
|
+
try {
|
|
413
|
+
const windows = execSync4(
|
|
414
|
+
`tmux list-windows -t ${shellQuote(tmuxSession)} -F "#{window_name}"`,
|
|
415
|
+
{ encoding: "utf-8" }
|
|
416
|
+
);
|
|
417
|
+
return windows.split("\n").some((name) => name.trim() === "sisyphus-dashboard");
|
|
418
|
+
} catch {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function launchDashboard(tmuxSession, cwd) {
|
|
423
|
+
const tuiPath = join3(import.meta.dirname, "tui.js");
|
|
424
|
+
const windowId = execSync4(
|
|
425
|
+
`tmux new-window -n "sisyphus-dashboard" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
|
|
426
|
+
{ encoding: "utf-8" }
|
|
427
|
+
).trim();
|
|
428
|
+
const cmd = `node ${shellQuote(tuiPath)} --cwd ${shellQuote(cwd)}`;
|
|
429
|
+
execSync4(
|
|
430
|
+
`tmux send-keys -t ${shellQuote(windowId)} ${shellQuote(cmd)} Enter`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
function registerDashboard(program2) {
|
|
434
|
+
program2.command("dashboard").description("Launch the TUI dashboard for monitoring and managing sessions").action(async () => {
|
|
435
|
+
assertTmux();
|
|
436
|
+
const tmuxSession = getTmuxSession();
|
|
437
|
+
const cwd = process.cwd();
|
|
438
|
+
launchDashboard(tmuxSession, cwd);
|
|
439
|
+
});
|
|
228
440
|
}
|
|
229
441
|
|
|
230
442
|
// src/cli/commands/start.ts
|
|
443
|
+
function shellQuote2(s) {
|
|
444
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
445
|
+
}
|
|
231
446
|
function registerStart(program2) {
|
|
232
|
-
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").action(async (task, opts) => {
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
const request = { type: "start", task, context: opts.context, cwd: process.cwd(), tmuxSession, tmuxWindow };
|
|
447
|
+
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) => {
|
|
448
|
+
const cwd = process.env["SISYPHUS_CWD"] ?? process.cwd();
|
|
449
|
+
const request = { type: "start", task, context: opts.context, cwd, name: opts.name };
|
|
236
450
|
const response = await sendRequest(request);
|
|
237
451
|
if (response.ok) {
|
|
238
452
|
const sessionId = response.data?.sessionId;
|
|
453
|
+
const tmuxSessionName = response.data?.tmuxSessionName;
|
|
454
|
+
if (process.env["TMUX"]) {
|
|
455
|
+
try {
|
|
456
|
+
execSync5(`tmux set-option @sisyphus_cwd ${shellQuote2(cwd)}`, { stdio: "ignore" });
|
|
457
|
+
} catch {
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const tmuxSession = getTmuxSession();
|
|
461
|
+
if (!isDashboardOpen(tmuxSession)) {
|
|
462
|
+
launchDashboard(tmuxSession, cwd);
|
|
463
|
+
console.log(`Dashboard opened in tmux window "sisyphus-dashboard"`);
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
}
|
|
239
468
|
console.log(`Task handed off to sisyphus orchestrator (session ${sessionId})`);
|
|
240
469
|
console.log(`The orchestrator and its agents will handle this task autonomously \u2014 no further action needed from you.`);
|
|
470
|
+
if (tmuxSessionName) {
|
|
471
|
+
console.log(`
|
|
472
|
+
Tmux session: ${tmuxSessionName}`);
|
|
473
|
+
console.log(` tmux attach -t ${tmuxSessionName}`);
|
|
474
|
+
}
|
|
241
475
|
console.log(`
|
|
242
476
|
Monitor:`);
|
|
243
477
|
console.log(` sisyphus status ${sessionId} # agents, cycles, reports`);
|
|
@@ -303,11 +537,11 @@ function registerSpawn(program2) {
|
|
|
303
537
|
}
|
|
304
538
|
|
|
305
539
|
// src/cli/commands/submit.ts
|
|
306
|
-
import { execSync as
|
|
540
|
+
import { execSync as execSync6 } from "child_process";
|
|
307
541
|
function isInWorktree() {
|
|
308
542
|
try {
|
|
309
|
-
const gitDir =
|
|
310
|
-
const commonDir =
|
|
543
|
+
const gitDir = execSync6("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
544
|
+
const commonDir = execSync6("git rev-parse --git-common-dir", { encoding: "utf-8" }).trim();
|
|
311
545
|
return gitDir !== commonDir;
|
|
312
546
|
} catch {
|
|
313
547
|
return false;
|
|
@@ -315,7 +549,7 @@ function isInWorktree() {
|
|
|
315
549
|
}
|
|
316
550
|
function getUncommittedChanges() {
|
|
317
551
|
try {
|
|
318
|
-
const status =
|
|
552
|
+
const status = execSync6("git status --porcelain", { encoding: "utf-8" }).trim();
|
|
319
553
|
return status || null;
|
|
320
554
|
} catch {
|
|
321
555
|
return null;
|
|
@@ -360,7 +594,7 @@ function registerSubmit(program2) {
|
|
|
360
594
|
|
|
361
595
|
// src/cli/commands/yield.ts
|
|
362
596
|
function registerYield(program2) {
|
|
363
|
-
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) => {
|
|
597
|
+
program2.command("yield").description("Yield control back to daemon (orchestrator only)").option("--prompt <text>", "Instructions for the next orchestrator cycle (or pipe via stdin)").option("--mode <mode>", "System prompt mode for next cycle (planning, implementation)").action(async (opts) => {
|
|
364
598
|
assertTmux();
|
|
365
599
|
const sessionId = process.env.SISYPHUS_SESSION_ID;
|
|
366
600
|
if (!sessionId) {
|
|
@@ -368,7 +602,7 @@ function registerYield(program2) {
|
|
|
368
602
|
process.exit(1);
|
|
369
603
|
}
|
|
370
604
|
const nextPrompt = opts.prompt ?? await readStdin() ?? void 0;
|
|
371
|
-
const request = { type: "yield", sessionId, agentId: "orchestrator", nextPrompt };
|
|
605
|
+
const request = { type: "yield", sessionId, agentId: "orchestrator", nextPrompt, mode: opts.mode };
|
|
372
606
|
const response = await sendRequest(request);
|
|
373
607
|
if (response.ok) {
|
|
374
608
|
console.log("Yielded. Waiting for agents to complete.");
|
|
@@ -394,7 +628,28 @@ function registerComplete(program2) {
|
|
|
394
628
|
console.log("Session completed.");
|
|
395
629
|
console.log(`
|
|
396
630
|
Follow up:`);
|
|
397
|
-
console.log(` sisyphus
|
|
631
|
+
console.log(` sisyphus continue # reactivate and keep working`);
|
|
632
|
+
console.log(` sisyphus resume ${sessionId} "new instructions" # respawn orchestrator externally`);
|
|
633
|
+
} else {
|
|
634
|
+
console.error(`Error: ${response.error}`);
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/cli/commands/continue.ts
|
|
641
|
+
function registerContinue(program2) {
|
|
642
|
+
program2.command("continue").description("Reactivate a completed session (orchestrator only)").action(async () => {
|
|
643
|
+
assertTmux();
|
|
644
|
+
const sessionId = process.env.SISYPHUS_SESSION_ID;
|
|
645
|
+
if (!sessionId) {
|
|
646
|
+
console.error("Error: SISYPHUS_SESSION_ID environment variable not set");
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
const request = { type: "continue", sessionId };
|
|
650
|
+
const response = await sendRequest(request);
|
|
651
|
+
if (response.ok) {
|
|
652
|
+
console.log("Session reactivated. Plan cleared.");
|
|
398
653
|
} else {
|
|
399
654
|
console.error(`Error: ${response.error}`);
|
|
400
655
|
process.exit(1);
|
|
@@ -403,6 +658,7 @@ Follow up:`);
|
|
|
403
658
|
}
|
|
404
659
|
|
|
405
660
|
// src/cli/commands/status.ts
|
|
661
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
406
662
|
var STATUS_COLORS = {
|
|
407
663
|
active: "\x1B[32m",
|
|
408
664
|
// green
|
|
@@ -423,13 +679,12 @@ var RESET = "\x1B[0m";
|
|
|
423
679
|
var BOLD = "\x1B[1m";
|
|
424
680
|
var DIM = "\x1B[2m";
|
|
425
681
|
function colorize(text, status) {
|
|
426
|
-
const color = STATUS_COLORS[status]
|
|
682
|
+
const color = STATUS_COLORS[status];
|
|
683
|
+
if (!color) return `${text}${RESET}`;
|
|
427
684
|
return `${color}${text}${RESET}`;
|
|
428
685
|
}
|
|
429
|
-
function
|
|
430
|
-
const
|
|
431
|
-
const end = endIso ? new Date(endIso).getTime() : Date.now();
|
|
432
|
-
const totalSeconds = Math.floor((end - start) / 1e3);
|
|
686
|
+
function formatMs(ms) {
|
|
687
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
433
688
|
if (totalSeconds < 0) return "0s";
|
|
434
689
|
const hours = Math.floor(totalSeconds / 3600);
|
|
435
690
|
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
@@ -440,6 +695,30 @@ function formatDuration(startIso, endIso) {
|
|
|
440
695
|
parts.push(`${seconds}s`);
|
|
441
696
|
return parts.join(" ");
|
|
442
697
|
}
|
|
698
|
+
function formatDuration(startOrMs, endIso) {
|
|
699
|
+
if (typeof startOrMs === "number") return formatMs(startOrMs);
|
|
700
|
+
const start = new Date(startOrMs).getTime();
|
|
701
|
+
const end = endIso ? new Date(endIso).getTime() : Date.now();
|
|
702
|
+
return formatMs(end - start);
|
|
703
|
+
}
|
|
704
|
+
function inferOrchestratorPhase(session) {
|
|
705
|
+
const cycles = session.orchestratorCycles;
|
|
706
|
+
if (cycles.length === 0) return "planning";
|
|
707
|
+
const lastCycle = cycles[cycles.length - 1];
|
|
708
|
+
if (!lastCycle.completedAt) {
|
|
709
|
+
const elapsed = Date.now() - new Date(lastCycle.timestamp).getTime();
|
|
710
|
+
if (elapsed < 5e3 || lastCycle.agentsSpawned.length === 0) return "planning";
|
|
711
|
+
return "spawning";
|
|
712
|
+
} else {
|
|
713
|
+
const runningAgents = session.agents.filter(
|
|
714
|
+
(a) => lastCycle.agentsSpawned.includes(a.id) && a.status === "running"
|
|
715
|
+
);
|
|
716
|
+
if (runningAgents.length > 0) {
|
|
717
|
+
return `waiting on ${runningAgents.map((a) => a.id).join(", ")}`;
|
|
718
|
+
}
|
|
719
|
+
return "starting";
|
|
720
|
+
}
|
|
721
|
+
}
|
|
443
722
|
function formatAgent(agent) {
|
|
444
723
|
const status = colorize(agent.status, agent.status);
|
|
445
724
|
const name = `${BOLD}${agent.name}${RESET}`;
|
|
@@ -459,10 +738,41 @@ function formatAgent(agent) {
|
|
|
459
738
|
}
|
|
460
739
|
return line;
|
|
461
740
|
}
|
|
462
|
-
function formatCycle(cycle) {
|
|
463
|
-
|
|
741
|
+
function formatCycle(cycle, phase) {
|
|
742
|
+
let duration;
|
|
743
|
+
if (cycle.completedAt) {
|
|
744
|
+
duration = ` ${DIM}(${formatDuration(cycle.timestamp, cycle.completedAt)})${RESET}`;
|
|
745
|
+
} else {
|
|
746
|
+
const elapsed = formatDuration(cycle.timestamp, null);
|
|
747
|
+
duration = ` ${DIM}(running, ${elapsed})${RESET}`;
|
|
748
|
+
}
|
|
464
749
|
const agents = cycle.agentsSpawned.length > 0 ? ` \u2014 agents: ${cycle.agentsSpawned.join(", ")}` : "";
|
|
465
|
-
|
|
750
|
+
const phaseStr = phase ? ` \u2014 orchestrator: ${phase}` : "";
|
|
751
|
+
return ` Cycle ${cycle.cycle}${duration}${agents}${phaseStr}`;
|
|
752
|
+
}
|
|
753
|
+
function computeLastActivity(session) {
|
|
754
|
+
const timestamps = [];
|
|
755
|
+
for (const cycle of session.orchestratorCycles) {
|
|
756
|
+
timestamps.push(new Date(cycle.timestamp).getTime());
|
|
757
|
+
if (cycle.completedAt) timestamps.push(new Date(cycle.completedAt).getTime());
|
|
758
|
+
}
|
|
759
|
+
for (const agent of session.agents) {
|
|
760
|
+
timestamps.push(new Date(agent.spawnedAt).getTime());
|
|
761
|
+
if (agent.completedAt) timestamps.push(new Date(agent.completedAt).getTime());
|
|
762
|
+
for (const r of agent.reports) {
|
|
763
|
+
timestamps.push(new Date(r.timestamp).getTime());
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (timestamps.length === 0) return null;
|
|
767
|
+
return new Date(Math.max(...timestamps));
|
|
768
|
+
}
|
|
769
|
+
function readRoadmapTodos(cwd, sessionId) {
|
|
770
|
+
try {
|
|
771
|
+
const content = readFileSync3(roadmapPath(cwd, sessionId), "utf8");
|
|
772
|
+
return content.split("\n").filter((line) => /^\s*- \[ \]/.test(line)).map((line) => line.replace(/^\s*- \[ \]\s*/, "")).slice(0, 5);
|
|
773
|
+
} catch {
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
466
776
|
}
|
|
467
777
|
function printSession(session) {
|
|
468
778
|
const status = colorize(session.status, session.status);
|
|
@@ -477,13 +787,40 @@ ${BOLD}Session: ${session.id}${RESET}`);
|
|
|
477
787
|
}
|
|
478
788
|
console.log(` CWD: ${session.cwd}`);
|
|
479
789
|
console.log(` Created: ${session.createdAt}`);
|
|
480
|
-
|
|
790
|
+
const activeTime = formatDuration(computeActiveTimeMs(session));
|
|
791
|
+
console.log(` Duration: ${sessionDuration}${session.completedAt ? "" : " (ongoing)"} (${activeTime} active)`);
|
|
792
|
+
const lastActivity = computeLastActivity(session);
|
|
793
|
+
if (lastActivity) {
|
|
794
|
+
console.log(` Last activity: ${formatMs(Date.now() - lastActivity.getTime())} ago`);
|
|
795
|
+
}
|
|
481
796
|
console.log(` Orchestrator cycles: ${session.orchestratorCycles.length}`);
|
|
797
|
+
const runningAgents = session.agents.filter((a) => a.status === "running");
|
|
798
|
+
if (runningAgents.length > 0) {
|
|
799
|
+
console.log(`
|
|
800
|
+
${BOLD}Active agents (${runningAgents.length}):${RESET}`);
|
|
801
|
+
for (const agent of runningAgents) {
|
|
802
|
+
const name = `${BOLD}${agent.name}${RESET}`;
|
|
803
|
+
const type = `${DIM}(${agent.agentType})${RESET}`;
|
|
804
|
+
const duration = formatDuration(agent.spawnedAt, null);
|
|
805
|
+
console.log(` ${agent.id} ${name} ${type} running ${duration}`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const todos = readRoadmapTodos(session.cwd, session.id);
|
|
809
|
+
if (todos.length > 0) {
|
|
810
|
+
console.log(`
|
|
811
|
+
${BOLD}Remaining (${todos.length} unchecked):${RESET}`);
|
|
812
|
+
for (const todo of todos) {
|
|
813
|
+
console.log(` - ${todo}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
482
816
|
if (session.orchestratorCycles.length > 0) {
|
|
483
817
|
console.log(`
|
|
484
818
|
${BOLD}Cycles:${RESET}`);
|
|
485
|
-
|
|
486
|
-
|
|
819
|
+
const cycles = session.orchestratorCycles;
|
|
820
|
+
for (let i = 0; i < cycles.length; i++) {
|
|
821
|
+
const isLast = i === cycles.length - 1;
|
|
822
|
+
const phase = isLast && session.status === "active" ? inferOrchestratorPhase(session) : void 0;
|
|
823
|
+
console.log(formatCycle(cycles[i], phase));
|
|
487
824
|
}
|
|
488
825
|
}
|
|
489
826
|
if (session.agents.length > 0) {
|
|
@@ -594,15 +931,15 @@ function registerReport(program2) {
|
|
|
594
931
|
// src/cli/commands/resume.ts
|
|
595
932
|
function registerResume(program2) {
|
|
596
933
|
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) => {
|
|
597
|
-
const tmuxSession = getTmuxSession();
|
|
598
|
-
const tmuxWindow = getTmuxWindow();
|
|
599
934
|
const cwd = process.cwd();
|
|
600
|
-
const request = { type: "resume", sessionId, cwd,
|
|
935
|
+
const request = { type: "resume", sessionId, cwd, message };
|
|
601
936
|
const response = await sendRequest(request);
|
|
602
937
|
if (response.ok) {
|
|
938
|
+
const tmuxSessionName = response.data?.tmuxSessionName;
|
|
603
939
|
console.log(`Session ${sessionId} resumed`);
|
|
604
|
-
if (
|
|
605
|
-
console.log(`
|
|
940
|
+
if (tmuxSessionName) {
|
|
941
|
+
console.log(`Tmux session: ${tmuxSessionName}`);
|
|
942
|
+
console.log(` tmux attach -t ${tmuxSessionName}`);
|
|
606
943
|
}
|
|
607
944
|
} else {
|
|
608
945
|
console.error(`Error: ${response.error}`);
|
|
@@ -666,6 +1003,248 @@ function registerNotify(program2) {
|
|
|
666
1003
|
});
|
|
667
1004
|
}
|
|
668
1005
|
|
|
1006
|
+
// src/cli/commands/message.ts
|
|
1007
|
+
function registerMessage(program2) {
|
|
1008
|
+
program2.command("message <content>").description("Queue a message for the orchestrator to see on next cycle").option("--session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID env var)").action(async (content, opts) => {
|
|
1009
|
+
const sessionId = opts.session ?? process.env.SISYPHUS_SESSION_ID;
|
|
1010
|
+
if (!sessionId) {
|
|
1011
|
+
console.error("Error: provide --session or set SISYPHUS_SESSION_ID environment variable");
|
|
1012
|
+
process.exit(1);
|
|
1013
|
+
}
|
|
1014
|
+
const source = process.env.SISYPHUS_AGENT_ID ? { type: "agent", agentId: process.env.SISYPHUS_AGENT_ID } : void 0;
|
|
1015
|
+
const request = { type: "message", sessionId, content, source };
|
|
1016
|
+
const response = await sendRequest(request);
|
|
1017
|
+
if (response.ok) {
|
|
1018
|
+
console.log("Message queued");
|
|
1019
|
+
} else {
|
|
1020
|
+
console.error(`Error: ${response.error}`);
|
|
1021
|
+
process.exit(1);
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// src/cli/commands/update-task.ts
|
|
1027
|
+
function registerUpdateTask(program2) {
|
|
1028
|
+
program2.command("update-task <task>").description("Update the session task/goal").option("--session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID env var)").action(async (task, opts) => {
|
|
1029
|
+
const sessionId = opts.session ?? process.env.SISYPHUS_SESSION_ID;
|
|
1030
|
+
if (!sessionId) {
|
|
1031
|
+
console.error("Error: provide --session or set SISYPHUS_SESSION_ID environment variable");
|
|
1032
|
+
process.exit(1);
|
|
1033
|
+
}
|
|
1034
|
+
const request = { type: "update-task", sessionId, task };
|
|
1035
|
+
const response = await sendRequest(request);
|
|
1036
|
+
if (response.ok) {
|
|
1037
|
+
console.log("Task updated");
|
|
1038
|
+
} else {
|
|
1039
|
+
console.error(`Error: ${response.error}`);
|
|
1040
|
+
process.exit(1);
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/cli/commands/rollback.ts
|
|
1046
|
+
function registerRollback(program2) {
|
|
1047
|
+
program2.command("rollback <sessionId> <cycle>").description("Roll back a session to a previous cycle boundary").action(async (sessionId, cycleStr) => {
|
|
1048
|
+
const toCycle = parseInt(cycleStr, 10);
|
|
1049
|
+
if (isNaN(toCycle) || toCycle < 1) {
|
|
1050
|
+
console.error("Error: cycle must be a positive integer");
|
|
1051
|
+
process.exit(1);
|
|
1052
|
+
}
|
|
1053
|
+
const request = { type: "rollback", sessionId, cwd: process.cwd(), toCycle };
|
|
1054
|
+
const response = await sendRequest(request);
|
|
1055
|
+
if (response.ok) {
|
|
1056
|
+
const data = response.data;
|
|
1057
|
+
console.log(`Session ${sessionId} rolled back to cycle ${data.restoredToCycle}.`);
|
|
1058
|
+
console.log(`Session is now paused. Use 'sisyphus resume ${sessionId}' to respawn the orchestrator.`);
|
|
1059
|
+
} else {
|
|
1060
|
+
console.error(`Error: ${response.error}`);
|
|
1061
|
+
process.exit(1);
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// src/cli/commands/restart-agent.ts
|
|
1067
|
+
function registerRestartAgent(program2) {
|
|
1068
|
+
program2.command("restart-agent <agentId>").description("Restart a failed/killed/lost agent in a new tmux pane").option("-s, --session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID)").action(async (agentId, opts) => {
|
|
1069
|
+
const sessionId = opts.session ?? process.env.SISYPHUS_SESSION_ID;
|
|
1070
|
+
if (!sessionId) {
|
|
1071
|
+
console.error("Error: No session ID. Use --session or set SISYPHUS_SESSION_ID.");
|
|
1072
|
+
process.exit(1);
|
|
1073
|
+
}
|
|
1074
|
+
const request = { type: "restart-agent", sessionId, agentId };
|
|
1075
|
+
const response = await sendRequest(request);
|
|
1076
|
+
if (response.ok) {
|
|
1077
|
+
console.log(`Agent ${agentId} restarted.`);
|
|
1078
|
+
} else {
|
|
1079
|
+
console.error(`Error: ${response.error}`);
|
|
1080
|
+
process.exit(1);
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/cli/commands/setup-keybind.ts
|
|
1086
|
+
function registerSetupKeybind(program2) {
|
|
1087
|
+
program2.command("setup-keybind [key]").description("Install the sisyphus-cycle tmux keybinding (default: M-s)").action(async (key) => {
|
|
1088
|
+
const resolvedKey = key ?? DEFAULT_KEY;
|
|
1089
|
+
const result = setupTmuxKeybind(resolvedKey);
|
|
1090
|
+
switch (result.status) {
|
|
1091
|
+
case "installed":
|
|
1092
|
+
console.log(result.message);
|
|
1093
|
+
break;
|
|
1094
|
+
case "already-installed":
|
|
1095
|
+
console.log(result.message);
|
|
1096
|
+
break;
|
|
1097
|
+
case "conflict":
|
|
1098
|
+
console.log(`Key ${resolvedKey} is already bound:`);
|
|
1099
|
+
console.log(` ${result.existingBinding}`);
|
|
1100
|
+
console.log("");
|
|
1101
|
+
console.log("Use a different key, e.g.:");
|
|
1102
|
+
console.log(" sisyphus setup-keybind M-S");
|
|
1103
|
+
console.log(" sisyphus setup-keybind M-w");
|
|
1104
|
+
console.log(" sisyphus setup-keybind M-j");
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// src/cli/commands/doctor.ts
|
|
1111
|
+
import { execSync as execSync7 } from "child_process";
|
|
1112
|
+
import { existsSync as existsSync3, statSync } from "fs";
|
|
1113
|
+
function checkDaemonInstalled() {
|
|
1114
|
+
if (isInstalled()) {
|
|
1115
|
+
return { name: "Daemon plist", status: "ok", detail: "Installed in LaunchAgents" };
|
|
1116
|
+
}
|
|
1117
|
+
return {
|
|
1118
|
+
name: "Daemon plist",
|
|
1119
|
+
status: "fail",
|
|
1120
|
+
detail: "Not installed",
|
|
1121
|
+
fix: 'Run any sisyphus command to auto-install, or: sisyphus start "test"'
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function checkDaemonRunning() {
|
|
1125
|
+
const pid = daemonPidPath();
|
|
1126
|
+
if (!existsSync3(pid)) {
|
|
1127
|
+
return {
|
|
1128
|
+
name: "Daemon process",
|
|
1129
|
+
status: "fail",
|
|
1130
|
+
detail: "No PID file found",
|
|
1131
|
+
fix: "launchctl load -w ~/Library/LaunchAgents/com.sisyphus.daemon.plist"
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
try {
|
|
1135
|
+
const sock = socketPath();
|
|
1136
|
+
execSync7(`test -S "${sock}"`, { stdio: "pipe" });
|
|
1137
|
+
return { name: "Daemon process", status: "ok", detail: `Socket at ${sock}` };
|
|
1138
|
+
} catch {
|
|
1139
|
+
return {
|
|
1140
|
+
name: "Daemon process",
|
|
1141
|
+
status: "warn",
|
|
1142
|
+
detail: "PID file exists but socket not found",
|
|
1143
|
+
fix: `Check logs: tail -20 ${daemonLogPath()}`
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function checkTmux() {
|
|
1148
|
+
try {
|
|
1149
|
+
execSync7("which tmux", { stdio: "pipe" });
|
|
1150
|
+
} catch {
|
|
1151
|
+
return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: "brew install tmux" };
|
|
1152
|
+
}
|
|
1153
|
+
try {
|
|
1154
|
+
execSync7("tmux list-sessions", { stdio: "pipe" });
|
|
1155
|
+
return { name: "tmux", status: "ok", detail: "Running" };
|
|
1156
|
+
} catch {
|
|
1157
|
+
return { name: "tmux", status: "warn", detail: "Installed but no server running" };
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function checkCycleScript() {
|
|
1161
|
+
const path = cycleScriptPath();
|
|
1162
|
+
if (!existsSync3(path)) {
|
|
1163
|
+
return {
|
|
1164
|
+
name: "Cycle script",
|
|
1165
|
+
status: "fail",
|
|
1166
|
+
detail: `Not found at ${path}`,
|
|
1167
|
+
fix: "sisyphus setup-keybind"
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
try {
|
|
1171
|
+
const mode = statSync(path).mode;
|
|
1172
|
+
if ((mode & 73) === 0) {
|
|
1173
|
+
return {
|
|
1174
|
+
name: "Cycle script",
|
|
1175
|
+
status: "fail",
|
|
1176
|
+
detail: "Not executable",
|
|
1177
|
+
fix: `chmod +x ${path}`
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1182
|
+
return { name: "Cycle script", status: "ok", detail: path };
|
|
1183
|
+
}
|
|
1184
|
+
function checkTmuxKeybind() {
|
|
1185
|
+
const existing = getExistingBinding(DEFAULT_KEY);
|
|
1186
|
+
if (existing === null) {
|
|
1187
|
+
if (existsSync3(sisyphusTmuxConfPath())) {
|
|
1188
|
+
return {
|
|
1189
|
+
name: `Tmux keybind (${DEFAULT_KEY})`,
|
|
1190
|
+
status: "warn",
|
|
1191
|
+
detail: "Configured in sisyphus tmux.conf but not active (tmux may not be running)"
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
return {
|
|
1195
|
+
name: `Tmux keybind (${DEFAULT_KEY})`,
|
|
1196
|
+
status: "fail",
|
|
1197
|
+
detail: "Not bound",
|
|
1198
|
+
fix: "sisyphus setup-keybind"
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
if (isSisyphusBinding(existing)) {
|
|
1202
|
+
return { name: `Tmux keybind (${DEFAULT_KEY})`, status: "ok", detail: "Bound to sisyphus-cycle" };
|
|
1203
|
+
}
|
|
1204
|
+
return {
|
|
1205
|
+
name: `Tmux keybind (${DEFAULT_KEY})`,
|
|
1206
|
+
status: "warn",
|
|
1207
|
+
detail: `Bound to something else: ${existing}`,
|
|
1208
|
+
fix: "sisyphus setup-keybind M-S (or another free key)"
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
function checkGlobalDir() {
|
|
1212
|
+
const dir = globalDir();
|
|
1213
|
+
if (existsSync3(dir)) {
|
|
1214
|
+
return { name: "Data directory", status: "ok", detail: dir };
|
|
1215
|
+
}
|
|
1216
|
+
return { name: "Data directory", status: "warn", detail: `${dir} does not exist (created on first use)` };
|
|
1217
|
+
}
|
|
1218
|
+
var SYMBOLS = { ok: "\u2713", warn: "!", fail: "\u2717" };
|
|
1219
|
+
function registerDoctor(program2) {
|
|
1220
|
+
program2.command("doctor").description("Check sisyphus installation health").action(async () => {
|
|
1221
|
+
const checks = [
|
|
1222
|
+
checkGlobalDir(),
|
|
1223
|
+
checkDaemonInstalled(),
|
|
1224
|
+
checkDaemonRunning(),
|
|
1225
|
+
checkTmux(),
|
|
1226
|
+
checkCycleScript(),
|
|
1227
|
+
checkTmuxKeybind()
|
|
1228
|
+
];
|
|
1229
|
+
let hasIssues = false;
|
|
1230
|
+
for (const c of checks) {
|
|
1231
|
+
const sym = SYMBOLS[c.status];
|
|
1232
|
+
console.log(` ${sym} ${c.name}: ${c.detail}`);
|
|
1233
|
+
if (c.status !== "ok") hasIssues = true;
|
|
1234
|
+
}
|
|
1235
|
+
const fixable = checks.filter((c) => c.fix);
|
|
1236
|
+
if (fixable.length > 0) {
|
|
1237
|
+
console.log("\nFixes:");
|
|
1238
|
+
for (const c of fixable) {
|
|
1239
|
+
console.log(` ${c.name}: ${c.fix}`);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (!hasIssues) {
|
|
1243
|
+
console.log("\nAll checks passed.");
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
669
1248
|
// src/cli/index.ts
|
|
670
1249
|
var program = new Command();
|
|
671
1250
|
program.name("sisyphus").description("tmux-integrated orchestration daemon for Claude Code").version("0.1.0");
|
|
@@ -675,12 +1254,20 @@ registerSubmit(program);
|
|
|
675
1254
|
registerReport(program);
|
|
676
1255
|
registerYield(program);
|
|
677
1256
|
registerComplete(program);
|
|
1257
|
+
registerContinue(program);
|
|
678
1258
|
registerStatus(program);
|
|
679
1259
|
registerList(program);
|
|
680
1260
|
registerResume(program);
|
|
681
1261
|
registerKill(program);
|
|
682
1262
|
registerUninstall(program);
|
|
683
1263
|
registerNotify(program);
|
|
1264
|
+
registerMessage(program);
|
|
1265
|
+
registerUpdateTask(program);
|
|
1266
|
+
registerDashboard(program);
|
|
1267
|
+
registerRollback(program);
|
|
1268
|
+
registerRestartAgent(program);
|
|
1269
|
+
registerSetupKeybind(program);
|
|
1270
|
+
registerDoctor(program);
|
|
684
1271
|
program.parseAsync(process.argv).catch((err) => {
|
|
685
1272
|
console.error(err.message);
|
|
686
1273
|
process.exit(1);
|