sisyphi 1.1.33 → 1.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1391 -382
- package/dist/cli.js.map +1 -1
- package/dist/templates/agent-plugin/agents/operator.md +10 -0
- package/dist/templates/agent-plugin/hooks/CLAUDE.md +3 -3
- package/dist/templates/agent-plugin/hooks/operator-user-prompt.sh +33 -1
- package/dist/templates/agent-plugin/skills/operator/SKILL.md +38 -0
- package/dist/templates/agent-plugin/skills/operator-memory/SKILL.md +64 -0
- package/dist/templates/orchestrator-planning.md +7 -1
- package/dist/tui.js +45 -20
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/templates/agent-plugin/agents/operator.md +10 -0
- package/templates/agent-plugin/hooks/CLAUDE.md +3 -3
- package/templates/agent-plugin/hooks/operator-user-prompt.sh +33 -1
- package/templates/agent-plugin/skills/operator/SKILL.md +38 -0
- package/templates/agent-plugin/skills/operator-memory/SKILL.md +64 -0
- package/templates/orchestrator-planning.md +7 -1
package/dist/cli.js
CHANGED
|
@@ -84,6 +84,9 @@ function askOutputPath(cwd, sessionId, askId) {
|
|
|
84
84
|
function askVisualsDir(cwd, sessionId, askId) {
|
|
85
85
|
return join(askEntryDir(cwd, sessionId, askId), "visuals");
|
|
86
86
|
}
|
|
87
|
+
function tmuxSessionName(cwd, sessionLabel) {
|
|
88
|
+
return `ssyph_${basename(cwd)}_${sessionLabel}`;
|
|
89
|
+
}
|
|
87
90
|
function sessionsManifestPath() {
|
|
88
91
|
return join(globalDir(), "sessions-manifest.json");
|
|
89
92
|
}
|
|
@@ -141,13 +144,34 @@ var init_paths = __esm({
|
|
|
141
144
|
}
|
|
142
145
|
});
|
|
143
146
|
|
|
147
|
+
// src/shared/shell.ts
|
|
148
|
+
function shellQuote(s) {
|
|
149
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
150
|
+
}
|
|
151
|
+
function shellQuoteHomePath(path) {
|
|
152
|
+
if (path === "~") return "~";
|
|
153
|
+
if (path.startsWith("~/")) return `~/${shellQuote(path.slice(2))}`;
|
|
154
|
+
return shellQuote(path);
|
|
155
|
+
}
|
|
156
|
+
function validateRepoName(repo) {
|
|
157
|
+
return !repo.includes("/") && !repo.includes("\\") && !repo.includes("..");
|
|
158
|
+
}
|
|
159
|
+
function escapeAppleScript(s) {
|
|
160
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
161
|
+
}
|
|
162
|
+
var init_shell = __esm({
|
|
163
|
+
"src/shared/shell.ts"() {
|
|
164
|
+
"use strict";
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
144
168
|
// src/daemon/lib/atomic.ts
|
|
145
169
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
146
|
-
import { dirname as dirname4, join as
|
|
170
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
147
171
|
import { renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
148
172
|
function atomicWrite(filePath, data) {
|
|
149
173
|
const dir = dirname4(filePath);
|
|
150
|
-
const tmpPath =
|
|
174
|
+
const tmpPath = join9(dir, `.atomic.${randomUUID2()}.tmp`);
|
|
151
175
|
writeFileSync4(tmpPath, data, "utf-8");
|
|
152
176
|
renameSync2(tmpPath, filePath);
|
|
153
177
|
}
|
|
@@ -176,6 +200,333 @@ var init_atomic = __esm({
|
|
|
176
200
|
}
|
|
177
201
|
});
|
|
178
202
|
|
|
203
|
+
// src/shared/env.ts
|
|
204
|
+
import { resolve as resolve3 } from "path";
|
|
205
|
+
function augmentedPath() {
|
|
206
|
+
const rawPath = process.env["PATH"];
|
|
207
|
+
const basePath = rawPath !== void 0 && rawPath.length > 0 ? rawPath : "/usr/bin:/bin";
|
|
208
|
+
const home = process.env["HOME"];
|
|
209
|
+
const candidates = [
|
|
210
|
+
...home ? [`${home}/.local/bin`] : [],
|
|
211
|
+
// Claude CLI, pipx, user-local installs
|
|
212
|
+
resolve3(process.execPath, ".."),
|
|
213
|
+
// Node.js bin dir (ensures node/npm available)
|
|
214
|
+
"/opt/homebrew/bin",
|
|
215
|
+
// Homebrew (Apple Silicon macOS)
|
|
216
|
+
"/opt/homebrew/sbin",
|
|
217
|
+
// Homebrew sbin
|
|
218
|
+
"/usr/local/bin",
|
|
219
|
+
// Homebrew (Intel macOS), manual installs
|
|
220
|
+
"/usr/local/sbin",
|
|
221
|
+
// Manual installs
|
|
222
|
+
"/opt/local/bin",
|
|
223
|
+
// MacPorts
|
|
224
|
+
"/opt/local/sbin",
|
|
225
|
+
// MacPorts
|
|
226
|
+
"/home/linuxbrew/.linuxbrew/bin"
|
|
227
|
+
// Linuxbrew
|
|
228
|
+
];
|
|
229
|
+
const nixProfile = process.env["NIX_PROFILES"];
|
|
230
|
+
if (nixProfile) {
|
|
231
|
+
for (const p of nixProfile.split(" ").reverse()) {
|
|
232
|
+
candidates.push(`${p}/bin`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const existing = new Set(basePath.split(":"));
|
|
236
|
+
const prepend = candidates.filter((dir) => !existing.has(dir));
|
|
237
|
+
return prepend.length > 0 ? `${prepend.join(":")}:${basePath}` : basePath;
|
|
238
|
+
}
|
|
239
|
+
function execEnv() {
|
|
240
|
+
return {
|
|
241
|
+
...process.env,
|
|
242
|
+
PATH: augmentedPath()
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
var init_env = __esm({
|
|
246
|
+
"src/shared/env.ts"() {
|
|
247
|
+
"use strict";
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// src/shared/exec.ts
|
|
252
|
+
import { execSync as execSync8 } from "child_process";
|
|
253
|
+
function exec2(cmd, cwd, timeoutMs = 3e4) {
|
|
254
|
+
return execSync8(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, timeout: timeoutMs }).trim();
|
|
255
|
+
}
|
|
256
|
+
function execSafe(cmd, cwd, timeoutMs) {
|
|
257
|
+
try {
|
|
258
|
+
return execSync8(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, stdio: ["pipe", "pipe", "pipe"], timeout: timeoutMs }).trim();
|
|
259
|
+
} catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
var EXEC_ENV;
|
|
264
|
+
var init_exec = __esm({
|
|
265
|
+
"src/shared/exec.ts"() {
|
|
266
|
+
"use strict";
|
|
267
|
+
init_env();
|
|
268
|
+
EXEC_ENV = execEnv();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// src/tui/lib/tmux.ts
|
|
273
|
+
var tmux_exports = {};
|
|
274
|
+
__export(tmux_exports, {
|
|
275
|
+
editInPopup: () => editInPopup,
|
|
276
|
+
getWindowId: () => getWindowId,
|
|
277
|
+
listAllWindowIds: () => listAllWindowIds,
|
|
278
|
+
openClaudeResumePopup: () => openClaudeResumePopup,
|
|
279
|
+
openClaudeResumeSession: () => openClaudeResumeSession,
|
|
280
|
+
openCompanionPane: () => openCompanionPane,
|
|
281
|
+
openEditorPopup: () => openEditorPopup,
|
|
282
|
+
openInFileManager: () => openInFileManager,
|
|
283
|
+
openLogPopup: () => openLogPopup,
|
|
284
|
+
openShellPopup: () => openShellPopup,
|
|
285
|
+
paneExists: () => paneExists,
|
|
286
|
+
promptInPopup: () => promptInPopup,
|
|
287
|
+
registerDashboardWindow: () => registerDashboardWindow,
|
|
288
|
+
selectPane: () => selectPane,
|
|
289
|
+
selectWindow: () => selectWindow,
|
|
290
|
+
switchToSession: () => switchToSession,
|
|
291
|
+
windowExists: () => windowExists
|
|
292
|
+
});
|
|
293
|
+
import { execSync as execSync17 } from "child_process";
|
|
294
|
+
import { join as join27 } from "path";
|
|
295
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync16, mkdtempSync, rmSync as rmSync6, cpSync as cpSync3, existsSync as existsSync26, mkdirSync as mkdirSync13 } from "fs";
|
|
296
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
297
|
+
function getWindowId() {
|
|
298
|
+
const pane = process.env["TMUX_PANE"];
|
|
299
|
+
if (pane) {
|
|
300
|
+
return exec2(`tmux display-message -t ${shellQuote(pane)} -p "#{window_id}"`);
|
|
301
|
+
}
|
|
302
|
+
return exec2('tmux display-message -p "#{window_id}"');
|
|
303
|
+
}
|
|
304
|
+
function selectWindow(windowId) {
|
|
305
|
+
execSafe(`tmux select-window -t ${shellQuote(windowId)}`);
|
|
306
|
+
}
|
|
307
|
+
function selectPane(paneId) {
|
|
308
|
+
execSafe(`tmux select-pane -t ${shellQuote(paneId)}`);
|
|
309
|
+
}
|
|
310
|
+
function windowExists(windowId) {
|
|
311
|
+
return execSafe(`tmux display-message -t ${shellQuote(windowId)} -p "#{window_id}"`) !== null;
|
|
312
|
+
}
|
|
313
|
+
function listAllWindowIds() {
|
|
314
|
+
try {
|
|
315
|
+
const output = execSync17('tmux list-windows -a -F "#{window_id}"', { encoding: "utf-8", env: EXEC_ENV });
|
|
316
|
+
return new Set(output.trim().split("\n").filter(Boolean));
|
|
317
|
+
} catch {
|
|
318
|
+
return /* @__PURE__ */ new Set();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function registerDashboardWindow(cwd) {
|
|
322
|
+
const wid = getWindowId();
|
|
323
|
+
const pane = process.env["TMUX_PANE"];
|
|
324
|
+
let sessionTarget = null;
|
|
325
|
+
if (pane) {
|
|
326
|
+
sessionTarget = execSafe(`tmux display-message -t ${shellQuote(pane)} -p "#{session_id}"`)?.trim() || null;
|
|
327
|
+
}
|
|
328
|
+
if (sessionTarget) {
|
|
329
|
+
execSafe(`tmux set-option -t ${shellQuote(sessionTarget)} @sisyphus_dashboard ${wid}`);
|
|
330
|
+
} else {
|
|
331
|
+
execSafe(`tmux set-option @sisyphus_dashboard ${wid}`);
|
|
332
|
+
}
|
|
333
|
+
if (cwd) {
|
|
334
|
+
const normalizedCwd = cwd.replace(/\/+$/, "");
|
|
335
|
+
const target = sessionTarget;
|
|
336
|
+
const existing = target ? execSafe(`tmux show-options -t ${shellQuote(target)} -v @sisyphus_cwd`)?.trim() : execSafe(`tmux show-options -v @sisyphus_cwd`)?.trim();
|
|
337
|
+
if (!existing) {
|
|
338
|
+
if (target) {
|
|
339
|
+
execSafe(`tmux set-option -t ${shellQuote(target)} @sisyphus_cwd ${shellQuote(normalizedCwd)}`);
|
|
340
|
+
} else {
|
|
341
|
+
execSafe(`tmux set-option @sisyphus_cwd ${shellQuote(normalizedCwd)}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function setupCompanionPlugin() {
|
|
347
|
+
const srcDir = join27(import.meta.dirname, "templates", "companion-plugin");
|
|
348
|
+
const destDir = join27(globalDir(), "companion-plugin");
|
|
349
|
+
if (!existsSync26(destDir)) mkdirSync13(destDir, { recursive: true });
|
|
350
|
+
cpSync3(srcDir, destDir, { recursive: true });
|
|
351
|
+
return destDir;
|
|
352
|
+
}
|
|
353
|
+
function paneExists(paneId) {
|
|
354
|
+
return execSafe(`tmux display-message -t ${shellQuote(paneId)} -p "#{pane_id}"`) !== null;
|
|
355
|
+
}
|
|
356
|
+
function findCompanionPaneForCwd(normalizedCwd) {
|
|
357
|
+
const out = execSafe(`tmux list-panes -aF "#{pane_id} #{@sisyphus_companion}"`);
|
|
358
|
+
if (!out) return null;
|
|
359
|
+
for (const line of out.split("\n")) {
|
|
360
|
+
const tabIdx = line.indexOf(" ");
|
|
361
|
+
if (tabIdx < 0) continue;
|
|
362
|
+
const paneId = line.slice(0, tabIdx);
|
|
363
|
+
const marker = line.slice(tabIdx + 1);
|
|
364
|
+
if (paneId && marker === normalizedCwd) return paneId;
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function openCompanionPane(cwd) {
|
|
369
|
+
const normalizedCwd = cwd.replace(/\/+$/, "");
|
|
370
|
+
const existing = findCompanionPaneForCwd(normalizedCwd);
|
|
371
|
+
if (existing) {
|
|
372
|
+
execSafe(`tmux select-pane -t ${shellQuote(existing)}`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const pluginDir = setupCompanionPlugin();
|
|
376
|
+
const templatePath2 = join27(import.meta.dirname, "templates", "dashboard-claude.md");
|
|
377
|
+
let template;
|
|
378
|
+
try {
|
|
379
|
+
template = readFileSync29(templatePath2, "utf-8");
|
|
380
|
+
} catch {
|
|
381
|
+
template = `You are a Sisyphus dashboard companion. Help the user manage multi-agent sessions.
|
|
382
|
+
Project: ${cwd}
|
|
383
|
+
Run \`sis list\` and \`sis status\` to see current state.`;
|
|
384
|
+
}
|
|
385
|
+
const rendered = template.replace(/\{\{CWD\}\}/g, cwd);
|
|
386
|
+
const promptPath = join27(globalDir(), "dashboard-companion-prompt.md");
|
|
387
|
+
writeFileSync16(promptPath, rendered, "utf-8");
|
|
388
|
+
const pathEnv = augmentedPath();
|
|
389
|
+
const claudeCmd = `SISYPHUS_COMPANION_CWD=${shellQuote(cwd)} PATH=${shellQuote(pathEnv)} claude --dangerously-skip-permissions --plugin-dir ${shellQuote(pluginDir)} --append-system-prompt "$(cat ${shellQuote(promptPath)})"`;
|
|
390
|
+
const result = exec2(
|
|
391
|
+
`tmux split-window -h -l 33% -P -F "#{pane_id}" -c ${shellQuote(cwd)} ${shellQuote(claudeCmd)}`
|
|
392
|
+
);
|
|
393
|
+
const newPaneId = result.trim();
|
|
394
|
+
if (newPaneId) {
|
|
395
|
+
execSafe(`tmux set-option -p -t ${shellQuote(newPaneId)} @sisyphus_companion ${shellQuote(normalizedCwd)}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function switchToSession(sessionName) {
|
|
399
|
+
execSafe(`tmux switch-client -t ${shellQuote(sessionName)}`);
|
|
400
|
+
}
|
|
401
|
+
function editInPopup(cwd, editor, opts) {
|
|
402
|
+
const tmpDir = mkdtempSync(join27(tmpdir3(), "sisyphus-"));
|
|
403
|
+
const filePath = join27(tmpDir, "input.md");
|
|
404
|
+
try {
|
|
405
|
+
writeFileSync16(filePath, opts?.content ? opts.content : "", "utf-8");
|
|
406
|
+
openEditorPopup(cwd, editor, filePath, opts?.size);
|
|
407
|
+
const result = readFileSync29(filePath, "utf-8").trim();
|
|
408
|
+
return result || null;
|
|
409
|
+
} finally {
|
|
410
|
+
rmSync6(tmpDir, { recursive: true, force: true });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function promptInPopup(prompt, opts) {
|
|
414
|
+
const { w = "50%", h = "3" } = opts ?? {};
|
|
415
|
+
const tmpDir = mkdtempSync(join27(tmpdir3(), "sisyphus-"));
|
|
416
|
+
const outFile = join27(tmpDir, "result");
|
|
417
|
+
try {
|
|
418
|
+
const script = `printf ${shellQuote(prompt + " ")} && read -r line && printf '%s' "$line" > ${shellQuote(outFile)}`;
|
|
419
|
+
execSync17(
|
|
420
|
+
`tmux display-popup -E -w ${w} -h ${h} ${shellQuote(`bash -c ${shellQuote(script)}`)}`,
|
|
421
|
+
{ stdio: "inherit", env: EXEC_ENV }
|
|
422
|
+
);
|
|
423
|
+
if (!existsSync26(outFile)) return null;
|
|
424
|
+
const result = readFileSync29(outFile, "utf-8").trim();
|
|
425
|
+
return result || null;
|
|
426
|
+
} finally {
|
|
427
|
+
rmSync6(tmpDir, { recursive: true, force: true });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function openLogPopup() {
|
|
431
|
+
execSync17(
|
|
432
|
+
`tmux display-popup -E -w 90% -h 80% ${shellQuote("tail -f ~/.sisyphus/daemon.log")}`,
|
|
433
|
+
{ stdio: "inherit", env: EXEC_ENV }
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
function openShellPopup(cwd, command) {
|
|
437
|
+
execSync17(
|
|
438
|
+
`tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd)} ${shellQuote(`sh -c '${command.replace(/'/g, "'\\''")}; echo; echo "Press enter to close"; read'`)}`,
|
|
439
|
+
{ stdio: "inherit", env: EXEC_ENV }
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
function openInFileManager(path) {
|
|
443
|
+
execSync17(`open ${shellQuote(path)}`, { stdio: "inherit", env: EXEC_ENV });
|
|
444
|
+
}
|
|
445
|
+
function openClaudeResumePopup(cwd, claudeSessionId, resumeEnv, resumeArgs) {
|
|
446
|
+
const pathEnv = augmentedPath();
|
|
447
|
+
const envPrefix = resumeEnv ? `${resumeEnv} && ` : "";
|
|
448
|
+
const args2 = resumeArgs ? `${resumeArgs} --resume ${shellQuote(claudeSessionId)}` : `--resume ${shellQuote(claudeSessionId)}`;
|
|
449
|
+
const cmd = `${envPrefix}PATH=${shellQuote(pathEnv)} claude ${args2}`;
|
|
450
|
+
execSync17(
|
|
451
|
+
`tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd)} ${shellQuote(cmd)}`,
|
|
452
|
+
{ stdio: "inherit", env: EXEC_ENV }
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
function openClaudeResumeSession(cwd, sessionId, claudeSessionId, sessionLabel, resumeEnv, resumeArgs, cycleNum, mode) {
|
|
456
|
+
const sessionName = tmuxSessionName(cwd, sessionLabel);
|
|
457
|
+
const cycleLabel = cycleNum != null ? `c${cycleNum}` : "";
|
|
458
|
+
const paneTitle = cycleLabel ? `ssph:orch ${sessionLabel} ${cycleLabel}` : `ssph:orch ${sessionLabel}`;
|
|
459
|
+
const existing = execSafe('tmux list-sessions -F "#{session_id}|#{session_name}"');
|
|
460
|
+
const existingLine = existing?.split("\n").find((line) => line.slice(line.indexOf("|") + 1) === sessionName);
|
|
461
|
+
if (existingLine) {
|
|
462
|
+
const existingSessId = existingLine.slice(0, existingLine.indexOf("|"));
|
|
463
|
+
execSafe(`tmux set-option -t ${shellQuote(existingSessId)} @sisyphus_cwd ${shellQuote(cwd.replace(/\/+$/, ""))}`);
|
|
464
|
+
execSafe(`tmux set-option -t ${shellQuote(existingSessId)} @sisyphus_session_id ${shellQuote(sessionId)}`);
|
|
465
|
+
const firstPaneId2 = execSafe(`tmux list-panes -t ${shellQuote(existingSessId)} -F '#{pane_id}'`)?.split("\n")[0];
|
|
466
|
+
if (firstPaneId2) applyOrchestratorPaneStyle(firstPaneId2, paneTitle, sessionLabel, cycleLabel, mode);
|
|
467
|
+
return sessionName;
|
|
468
|
+
}
|
|
469
|
+
const pathEnv = augmentedPath();
|
|
470
|
+
const envPrefix = resumeEnv ? `${resumeEnv} && ` : "";
|
|
471
|
+
const args2 = resumeArgs ? `${resumeArgs} --resume ${shellQuote(claudeSessionId)}` : `--resume ${shellQuote(claudeSessionId)}`;
|
|
472
|
+
const cmd = `${envPrefix}PATH=${shellQuote(pathEnv)} claude ${args2}`;
|
|
473
|
+
const createOut = exec2(`tmux new-session -d -s ${shellQuote(sessionName)} -n main -c ${shellQuote(cwd)} -P -F '#{session_id}|#{pane_id}' ${shellQuote(cmd)}`).trim();
|
|
474
|
+
const pipeIdx = createOut.indexOf("|");
|
|
475
|
+
const newSessId = createOut.slice(0, pipeIdx);
|
|
476
|
+
const firstPaneId = createOut.slice(pipeIdx + 1);
|
|
477
|
+
execSafe(`tmux set-option -t ${shellQuote(newSessId)} @sisyphus_cwd ${shellQuote(cwd.replace(/\/+$/, ""))}`);
|
|
478
|
+
execSafe(`tmux set-option -t ${shellQuote(newSessId)} @sisyphus_session_id ${shellQuote(sessionId)}`);
|
|
479
|
+
execSafe(`tmux set -w -t ${shellQuote(newSessId + ":")} pane-border-status top`);
|
|
480
|
+
execSafe(`tmux set -w -t ${shellQuote(newSessId + ":")} allow-rename off`);
|
|
481
|
+
execSafe(`tmux set -w -t ${shellQuote(newSessId + ":")} automatic-rename off`);
|
|
482
|
+
if (firstPaneId) applyOrchestratorPaneStyle(firstPaneId, paneTitle, sessionLabel, cycleLabel, mode);
|
|
483
|
+
return sessionName;
|
|
484
|
+
}
|
|
485
|
+
function applyOrchestratorPaneStyle(paneId, title, sessionLabel, cycleLabel, mode) {
|
|
486
|
+
const color = "yellow";
|
|
487
|
+
execSafe(`tmux select-pane -t ${shellQuote(paneId)} -T ${shellQuote(title)}`);
|
|
488
|
+
execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_role ${shellQuote("orch")}`);
|
|
489
|
+
execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_session ${shellQuote(sessionLabel)}`);
|
|
490
|
+
if (cycleLabel) execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_cycle ${shellQuote(cycleLabel)}`);
|
|
491
|
+
if (mode) execSafe(`tmux set -p -t ${shellQuote(paneId)} @pane_mode ${shellQuote(mode)}`);
|
|
492
|
+
const gitBranch = `#(cd #{pane_current_path} && git branch --show-current 2>/dev/null)`;
|
|
493
|
+
const branchSuffix = `#(cd #{pane_current_path} && git branch --show-current 2>/dev/null | grep -q . && echo ' |') ${gitBranch}`;
|
|
494
|
+
const homePath = `#(echo '#{pane_current_path}' | sed "s|^$HOME|~|")`;
|
|
495
|
+
const modeSegment = `#{?#{@pane_mode}, #[fg=${color}\\,italics]#{@pane_mode}#[default],}`;
|
|
496
|
+
const fmt = [
|
|
497
|
+
`#[bg=${color},fg=black,bold] #{@pane_role} #[default]`,
|
|
498
|
+
` #[fg=${color},bold]#{@pane_session}`,
|
|
499
|
+
modeSegment,
|
|
500
|
+
` #[default,dim]#{@pane_cycle}`,
|
|
501
|
+
` ${homePath}${branchSuffix}`,
|
|
502
|
+
`#[default]`
|
|
503
|
+
].join("");
|
|
504
|
+
execSafe(`tmux set -p -t ${shellQuote(paneId)} pane-border-format ${shellQuote(fmt)}`);
|
|
505
|
+
}
|
|
506
|
+
function openEditorPopup(cwd, editor, filePath, size) {
|
|
507
|
+
const { w = "90%", h = "90%" } = size ?? {};
|
|
508
|
+
const editorBin = editor.split(/\s+/)[0].split("/").pop();
|
|
509
|
+
if (TERMINAL_EDITORS.has(editorBin)) {
|
|
510
|
+
execSync17(
|
|
511
|
+
`tmux display-popup -E -w ${w} -h ${h} -d ${shellQuote(cwd)} ${shellQuote(`${editor} ${shellQuote(filePath)}`)}`,
|
|
512
|
+
{ stdio: "inherit", env: EXEC_ENV }
|
|
513
|
+
);
|
|
514
|
+
} else {
|
|
515
|
+
execSync17(`${editor} ${shellQuote(filePath)}`, { stdio: "inherit", cwd, env: EXEC_ENV });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
var TERMINAL_EDITORS;
|
|
519
|
+
var init_tmux = __esm({
|
|
520
|
+
"src/tui/lib/tmux.ts"() {
|
|
521
|
+
"use strict";
|
|
522
|
+
init_paths();
|
|
523
|
+
init_env();
|
|
524
|
+
init_shell();
|
|
525
|
+
init_exec();
|
|
526
|
+
TERMINAL_EDITORS = /* @__PURE__ */ new Set(["nvim", "vim", "vi", "nano", "emacs", "micro", "helix", "hx", "joe", "ne", "kak"]);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
179
530
|
// src/cli/deploy/creds.ts
|
|
180
531
|
var creds_exports = {};
|
|
181
532
|
__export(creds_exports, {
|
|
@@ -188,14 +539,14 @@ __export(creds_exports, {
|
|
|
188
539
|
readTailscaleEnv: () => readTailscaleEnv,
|
|
189
540
|
writeTailscaleEnv: () => writeTailscaleEnv
|
|
190
541
|
});
|
|
191
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
542
|
+
import { chmodSync as chmodSync3, existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync30 } from "fs";
|
|
192
543
|
import { createInterface as createInterface4 } from "readline";
|
|
193
544
|
function isValidProvider(value) {
|
|
194
545
|
return PROVIDERS.includes(value);
|
|
195
546
|
}
|
|
196
547
|
function ensureDeployDir() {
|
|
197
548
|
const dir = deployDir();
|
|
198
|
-
if (!
|
|
549
|
+
if (!existsSync27(dir)) mkdirSync14(dir, { recursive: true, mode: 448 });
|
|
199
550
|
}
|
|
200
551
|
function parseEnvFile(text) {
|
|
201
552
|
const out = {};
|
|
@@ -221,8 +572,8 @@ function serializeEnvFile(values) {
|
|
|
221
572
|
return lines.join("\n") + "\n";
|
|
222
573
|
}
|
|
223
574
|
function readEnvFile(path) {
|
|
224
|
-
if (!
|
|
225
|
-
return parseEnvFile(
|
|
575
|
+
if (!existsSync27(path)) return null;
|
|
576
|
+
return parseEnvFile(readFileSync30(path, "utf-8"));
|
|
226
577
|
}
|
|
227
578
|
function writeEnvFile(path, values) {
|
|
228
579
|
ensureDeployDir();
|
|
@@ -317,8 +668,8 @@ var init_creds = __esm({
|
|
|
317
668
|
|
|
318
669
|
// src/cli/index.ts
|
|
319
670
|
import { Command } from "commander";
|
|
320
|
-
import { existsSync as
|
|
321
|
-
import { dirname as dirname12, join as
|
|
671
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync16, readFileSync as readFileSync34 } from "fs";
|
|
672
|
+
import { dirname as dirname12, join as join30 } from "path";
|
|
322
673
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
323
674
|
|
|
324
675
|
// src/cli/commands/start.ts
|
|
@@ -366,18 +717,18 @@ function rawSend(request, timeoutMs = 1e4) {
|
|
|
366
717
|
// src/cli/install.ts
|
|
367
718
|
init_paths();
|
|
368
719
|
import { execSync as execSync3 } from "child_process";
|
|
369
|
-
import { existsSync as
|
|
720
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
370
721
|
import { connect as connect2 } from "net";
|
|
371
|
-
import { homedir as
|
|
372
|
-
import { dirname, join as
|
|
722
|
+
import { homedir as homedir5 } from "os";
|
|
723
|
+
import { dirname, join as join5, resolve } from "path";
|
|
373
724
|
import { fileURLToPath } from "url";
|
|
374
725
|
|
|
375
726
|
// src/cli/tmux-setup.ts
|
|
376
727
|
init_paths();
|
|
377
728
|
import { execSync } from "child_process";
|
|
378
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, unlinkSync } from "fs";
|
|
379
|
-
import { homedir as
|
|
380
|
-
import { join as
|
|
729
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync, chmodSync, unlinkSync } from "fs";
|
|
730
|
+
import { homedir as homedir3 } from "os";
|
|
731
|
+
import { join as join3 } from "path";
|
|
381
732
|
import { createInterface } from "readline";
|
|
382
733
|
|
|
383
734
|
// src/shared/keymap.ts
|
|
@@ -438,7 +789,8 @@ var KEYMAP = {
|
|
|
438
789
|
{ key: "/", label: " Search / filter", action: { type: "script", name: "sisyphus-search-reports" }, tuiAction: "search" },
|
|
439
790
|
{ key: " ", label: " Open popup explicitly", action: { type: "tui", action: "show-leader" } },
|
|
440
791
|
{ key: "y", label: " Yank \u203A", action: { type: "submenu", ref: "copy" } },
|
|
441
|
-
{ key: "c", label: "
|
|
792
|
+
{ key: "c", label: " Side claude pane", action: { type: "script", name: "sisyphus-companion-pane" }, tuiAction: "companion-pane" },
|
|
793
|
+
{ key: "C", label: " Companion (gamification) \u203A", action: { type: "submenu", ref: "companion" } },
|
|
442
794
|
{ key: "o", label: " Open \u203A", action: { type: "submenu", ref: "open" } },
|
|
443
795
|
{ key: "a", label: " Agent \u203A", action: { type: "submenu", ref: "agent" } },
|
|
444
796
|
{ key: "S", label: " Session \u203A", action: { type: "submenu", ref: "session" } },
|
|
@@ -450,8 +802,7 @@ var KEYMAP = {
|
|
|
450
802
|
title: " Companion ",
|
|
451
803
|
items: [
|
|
452
804
|
{ key: "p", label: " profile (overlay)", action: { type: "tui", action: "companion-overlay" } },
|
|
453
|
-
{ key: "d", label: " debug (mood signals)", action: { type: "tui", action: "companion-debug" } }
|
|
454
|
-
{ key: "t", label: " open in tmux pane", action: { type: "tui", action: "companion-pane" } }
|
|
805
|
+
{ key: "d", label: " debug (mood signals)", action: { type: "tui", action: "companion-debug" } }
|
|
455
806
|
]
|
|
456
807
|
},
|
|
457
808
|
copy: {
|
|
@@ -521,22 +872,41 @@ var KEYMAP = {
|
|
|
521
872
|
}
|
|
522
873
|
};
|
|
523
874
|
|
|
875
|
+
// src/shared/sisyphus-init-lua.ts
|
|
876
|
+
import { mkdirSync, existsSync, cpSync } from "fs";
|
|
877
|
+
import { join as join2 } from "path";
|
|
878
|
+
import { homedir as homedir2 } from "os";
|
|
879
|
+
var initLuaEnsured = false;
|
|
880
|
+
function ensureSisyphusInitLua() {
|
|
881
|
+
if (initLuaEnsured) return;
|
|
882
|
+
initLuaEnsured = true;
|
|
883
|
+
try {
|
|
884
|
+
const destDir = join2(homedir2(), ".config", "sisyphus");
|
|
885
|
+
const destPath = join2(destDir, "init.lua");
|
|
886
|
+
if (existsSync(destPath)) return;
|
|
887
|
+
mkdirSync(destDir, { recursive: true });
|
|
888
|
+
const srcPath = join2(import.meta.dirname, "templates", "sisyphus-init.lua");
|
|
889
|
+
cpSync(srcPath, destPath);
|
|
890
|
+
} catch {
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
524
894
|
// src/cli/tmux-setup.ts
|
|
525
895
|
var DEFAULT_CYCLE_KEY = "M-s";
|
|
526
896
|
var DEFAULT_PREFIX_KEY = "C-s";
|
|
527
897
|
var KEY_TABLE = "sisyphus";
|
|
528
898
|
var SISYPHUS_CONF_MARKER = "# sisyphus-managed \u2014 do not edit";
|
|
529
899
|
function scriptPath(name) {
|
|
530
|
-
return
|
|
900
|
+
return join3(globalDir(), "bin", name);
|
|
531
901
|
}
|
|
532
902
|
function cycleScriptPath() {
|
|
533
903
|
return scriptPath("sisyphus-cycle");
|
|
534
904
|
}
|
|
535
905
|
function keymapJsonPath() {
|
|
536
|
-
return
|
|
906
|
+
return join3(globalDir(), "keymap.json");
|
|
537
907
|
}
|
|
538
908
|
function writeKeymapJson() {
|
|
539
|
-
|
|
909
|
+
mkdirSync2(globalDir(), { recursive: true });
|
|
540
910
|
writeFileSync(keymapJsonPath(), JSON.stringify(KEYMAP, null, 2), "utf8");
|
|
541
911
|
}
|
|
542
912
|
function tmuxVersionAtLeast(major, minor) {
|
|
@@ -553,7 +923,7 @@ function tmuxVersionAtLeast(major, minor) {
|
|
|
553
923
|
function menuItemCommand(action, scriptsDir) {
|
|
554
924
|
switch (action.type) {
|
|
555
925
|
case "script":
|
|
556
|
-
return `run-shell ${
|
|
926
|
+
return `run-shell ${join3(scriptsDir, action.name)}`;
|
|
557
927
|
case "popup": {
|
|
558
928
|
const { w, h, borderStyle, title, cwd } = action.popup;
|
|
559
929
|
let args2 = "-E";
|
|
@@ -562,10 +932,10 @@ function menuItemCommand(action, scriptsDir) {
|
|
|
562
932
|
if (borderStyle) args2 += ` -S '${borderStyle}'`;
|
|
563
933
|
if (title) args2 += ` -T '${title}'`;
|
|
564
934
|
if (cwd === "current") args2 += ` -d '#{pane_current_path}'`;
|
|
565
|
-
return `display-popup ${args2} ${
|
|
935
|
+
return `display-popup ${args2} ${join3(scriptsDir, action.name)}`;
|
|
566
936
|
}
|
|
567
937
|
case "submenu":
|
|
568
|
-
return `run-shell ${
|
|
938
|
+
return `run-shell ${join3(scriptsDir, `sisyphus-menu-${action.ref}`)}`;
|
|
569
939
|
case "tmux":
|
|
570
940
|
return action.cmd;
|
|
571
941
|
case "tui":
|
|
@@ -591,13 +961,13 @@ function generateTopLevelBinding(prefixKey, def, scriptsDir) {
|
|
|
591
961
|
return `bind-key -T root ${prefixKey} display-menu -T '${def.title}' -x R -y S ${args2}`;
|
|
592
962
|
}
|
|
593
963
|
function sisyphusTmuxConfPath() {
|
|
594
|
-
return
|
|
964
|
+
return join3(globalDir(), "tmux.conf");
|
|
595
965
|
}
|
|
596
966
|
function userTmuxConfPath() {
|
|
597
|
-
const dotfile =
|
|
598
|
-
const xdg =
|
|
599
|
-
if (
|
|
600
|
-
if (
|
|
967
|
+
const dotfile = join3(homedir3(), ".tmux.conf");
|
|
968
|
+
const xdg = join3(homedir3(), ".config", "tmux", "tmux.conf");
|
|
969
|
+
if (existsSync2(xdg)) return xdg;
|
|
970
|
+
if (existsSync2(dotfile)) return dotfile;
|
|
601
971
|
return null;
|
|
602
972
|
}
|
|
603
973
|
var CYCLE_SCRIPT = `#!/bin/bash
|
|
@@ -663,7 +1033,7 @@ resolve_home() {
|
|
|
663
1033
|
return 1
|
|
664
1034
|
}`.trim();
|
|
665
1035
|
function homeScript() {
|
|
666
|
-
const tuiPath =
|
|
1036
|
+
const tuiPath = join3(import.meta.dirname, "tui.js");
|
|
667
1037
|
return `#!/bin/bash
|
|
668
1038
|
# Jump to the dashboard window for the home session matching this cwd.
|
|
669
1039
|
${RESOLVE_HOME}
|
|
@@ -716,9 +1086,9 @@ fi
|
|
|
716
1086
|
`;
|
|
717
1087
|
var NEW_PROMPT_SCRIPT = `#!/bin/bash
|
|
718
1088
|
# Open nvim to compose a new sisyphus task, then start a session
|
|
719
|
-
tmpfile=$(mktemp /tmp/sisyphus-new
|
|
1089
|
+
tmpfile=$(mktemp /tmp/sisyphus-new.XXXXXX)
|
|
720
1090
|
trap 'rm -f "$tmpfile"' EXIT
|
|
721
|
-
nvim "$tmpfile"
|
|
1091
|
+
NVIM_APPNAME=sisyphus nvim "$tmpfile"
|
|
722
1092
|
grep -q '[^[:space:]]' "$tmpfile" || exit 0
|
|
723
1093
|
exec sis start "$(cat "$tmpfile")"
|
|
724
1094
|
`;
|
|
@@ -747,9 +1117,9 @@ fi
|
|
|
747
1117
|
|
|
748
1118
|
[ -z "$session_id" ] && { echo "No active sisyphus session found"; sleep 1; exit 1; }
|
|
749
1119
|
|
|
750
|
-
tmpfile=$(mktemp /tmp/sisyphus-msg
|
|
1120
|
+
tmpfile=$(mktemp /tmp/sisyphus-msg.XXXXXX)
|
|
751
1121
|
trap 'rm -f "$tmpfile"' EXIT
|
|
752
|
-
nvim "$tmpfile"
|
|
1122
|
+
NVIM_APPNAME=sisyphus nvim "$tmpfile"
|
|
753
1123
|
grep -q '[^[:space:]]' "$tmpfile" || exit 0
|
|
754
1124
|
exec sis message --session "$session_id" "$(cat "$tmpfile")"
|
|
755
1125
|
`;
|
|
@@ -806,6 +1176,12 @@ ${formatHelpForKeymap(KEYMAP)}
|
|
|
806
1176
|
EOF_HELP
|
|
807
1177
|
read -n 1 -s -r -p " Press any key to close"
|
|
808
1178
|
`;
|
|
1179
|
+
var COMPANION_PANE_SCRIPT = `#!/bin/bash
|
|
1180
|
+
tmux_sid=$(tmux display-message -p '#{session_id}')
|
|
1181
|
+
cwd=$(tmux show-options -t "$tmux_sid" -v @sisyphus_cwd 2>/dev/null)
|
|
1182
|
+
[ -z "$cwd" ] && cwd=$(tmux display-message -p '#{pane_current_path}')
|
|
1183
|
+
exec sis companion pane --cwd "$cwd"
|
|
1184
|
+
`;
|
|
809
1185
|
var STATUS_POPUP_SCRIPT = `#!/bin/bash
|
|
810
1186
|
# Show session status \u2014 if no sisyphus session here, list all.
|
|
811
1187
|
# -t targeting uses $N session id \u2014 -t <name> can substring-match under sparse env.
|
|
@@ -1005,10 +1381,10 @@ ${SESSION_RESOLVE}
|
|
|
1005
1381
|
short_id="\${session_id:0:8}"
|
|
1006
1382
|
|
|
1007
1383
|
# Optional message \u2014 leave empty to resume with no extra instructions.
|
|
1008
|
-
tmpfile=$(mktemp /tmp/sisyphus-resume
|
|
1384
|
+
tmpfile=$(mktemp /tmp/sisyphus-resume.XXXXXX)
|
|
1009
1385
|
trap 'rm -f "$tmpfile"' EXIT
|
|
1010
1386
|
printf "# Resume session %s\\n# (Optional) Add follow-up instructions for the orchestrator below.\\n# Save & quit empty to resume with no message.\\n\\n" "$short_id" > "$tmpfile"
|
|
1011
|
-
nvim "$tmpfile"
|
|
1387
|
+
NVIM_APPNAME=sisyphus nvim "$tmpfile"
|
|
1012
1388
|
|
|
1013
1389
|
# Strip comment + blank lines to detect empty submission
|
|
1014
1390
|
body=$(grep -v '^[[:space:]]*#' "$tmpfile" | sed '/^[[:space:]]*$/d')
|
|
@@ -1099,10 +1475,10 @@ var SPAWN_AGENT_SCRIPT = `#!/bin/bash
|
|
|
1099
1475
|
# Run from a sisyphus session pane, not the home dashboard.
|
|
1100
1476
|
${SESSION_RESOLVE}
|
|
1101
1477
|
|
|
1102
|
-
tmpfile=$(mktemp /tmp/sisyphus-spawn
|
|
1478
|
+
tmpfile=$(mktemp /tmp/sisyphus-spawn.XXXXXX)
|
|
1103
1479
|
trap 'rm -f "$tmpfile"' EXIT
|
|
1104
1480
|
printf "# Spawn agent in session %s\\n# Write the agent's instruction below. Empty = abort.\\n\\n" "\${session_id:0:8}" > "$tmpfile"
|
|
1105
|
-
nvim "$tmpfile"
|
|
1481
|
+
NVIM_APPNAME=sisyphus nvim "$tmpfile"
|
|
1106
1482
|
|
|
1107
1483
|
body=$(grep -v '^[[:space:]]*#' "$tmpfile" | sed '/^[[:space:]]*$/d')
|
|
1108
1484
|
[ -z "$body" ] && exit 0
|
|
@@ -1226,9 +1602,9 @@ else
|
|
|
1226
1602
|
(( idx >= 0 && idx < \${#entries[@]} )) || exit 0
|
|
1227
1603
|
fi
|
|
1228
1604
|
|
|
1229
|
-
tmpfile=$(mktemp /tmp/sisyphus-msg-agent
|
|
1605
|
+
tmpfile=$(mktemp /tmp/sisyphus-msg-agent.XXXXXX)
|
|
1230
1606
|
trap 'rm -f "$tmpfile"' EXIT
|
|
1231
|
-
nvim "$tmpfile"
|
|
1607
|
+
NVIM_APPNAME=sisyphus nvim "$tmpfile"
|
|
1232
1608
|
grep -q '[^[:space:]]' "$tmpfile" || exit 0
|
|
1233
1609
|
exec sis message --session "$session_id" --agent "\${ids[$idx]}" "$(cat "$tmpfile")"
|
|
1234
1610
|
`;
|
|
@@ -1668,12 +2044,13 @@ var OPEN_SCRATCH_SCRIPT = `#!/bin/bash
|
|
|
1668
2044
|
exec sis admin scratch
|
|
1669
2045
|
`;
|
|
1670
2046
|
function installScript(name, content) {
|
|
1671
|
-
|
|
2047
|
+
mkdirSync2(join3(globalDir(), "bin"), { recursive: true });
|
|
1672
2048
|
const path = scriptPath(name);
|
|
1673
2049
|
writeFileSync(path, content, "utf8");
|
|
1674
2050
|
chmodSync(path, 493);
|
|
1675
2051
|
}
|
|
1676
2052
|
function installAllScripts() {
|
|
2053
|
+
ensureSisyphusInitLua();
|
|
1677
2054
|
installScript("sisyphus-cycle", CYCLE_SCRIPT);
|
|
1678
2055
|
installScript("sisyphus-home", homeScript());
|
|
1679
2056
|
installScript("sisyphus-kill-pane", KILL_PANE_SCRIPT);
|
|
@@ -1688,7 +2065,7 @@ function installAllScripts() {
|
|
|
1688
2065
|
installScript("sisyphus-open-roadmap", OPEN_ROADMAP_SCRIPT);
|
|
1689
2066
|
installScript("sisyphus-open-strategy", OPEN_STRATEGY_SCRIPT);
|
|
1690
2067
|
installScript("sisyphus-export-session", EXPORT_SESSION_SCRIPT);
|
|
1691
|
-
const scriptsDir =
|
|
2068
|
+
const scriptsDir = join3(globalDir(), "bin");
|
|
1692
2069
|
for (const [id, def] of Object.entries(KEYMAP.submenus)) {
|
|
1693
2070
|
installScript(`sisyphus-menu-${id}`, generateSubmenuScript(id, def, scriptsDir));
|
|
1694
2071
|
}
|
|
@@ -1721,6 +2098,7 @@ function installAllScripts() {
|
|
|
1721
2098
|
installScript("sisyphus-reconnect", RECONNECT_SCRIPT);
|
|
1722
2099
|
installScript("sisyphus-open-scratch", OPEN_SCRATCH_SCRIPT);
|
|
1723
2100
|
installScript("sisyphus-help", HELP_SCRIPT);
|
|
2101
|
+
installScript("sisyphus-companion-pane", COMPANION_PANE_SCRIPT);
|
|
1724
2102
|
}
|
|
1725
2103
|
function getExistingBinding(key, table = "root") {
|
|
1726
2104
|
try {
|
|
@@ -1770,18 +2148,43 @@ async function setupTmuxKeybind(cycleKey = DEFAULT_CYCLE_KEY, prefixKey = DEFAUL
|
|
|
1770
2148
|
message: `tmux 3.2+ required for sisyphus keybindings; got ${version}`
|
|
1771
2149
|
};
|
|
1772
2150
|
}
|
|
1773
|
-
|
|
1774
|
-
const
|
|
1775
|
-
|
|
2151
|
+
if (!opts.force) {
|
|
2152
|
+
for (const [label, key] of [["cycle", cycleKey], ["prefix", prefixKey]]) {
|
|
2153
|
+
const existing = getExistingBinding(key);
|
|
2154
|
+
if (existing !== null && !isSisyphusBinding(existing)) {
|
|
2155
|
+
return {
|
|
2156
|
+
status: "conflict",
|
|
2157
|
+
message: `Tmux key ${key} (${label}) is already bound to something else. Re-run with --force to overwrite, or pass an alternate cycle key (e.g. "sis admin setup-keybind M-w").`,
|
|
2158
|
+
existingBinding: existing,
|
|
2159
|
+
conflictKey: key
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
const userConfPreview = userTmuxConfPath();
|
|
2165
|
+
const sisyphusConfPathPreview = sisyphusTmuxConfPath();
|
|
2166
|
+
const markedSourceLinePreview = `source-file ${sisyphusConfPathPreview} ${SISYPHUS_CONF_MARKER}`;
|
|
2167
|
+
if (userConfPreview !== null && !opts.force && !opts.assumeYes) {
|
|
2168
|
+
let alreadySources = false;
|
|
2169
|
+
try {
|
|
2170
|
+
alreadySources = readFileSync(userConfPreview, "utf8").includes(sisyphusConfPathPreview);
|
|
2171
|
+
} catch {
|
|
2172
|
+
}
|
|
2173
|
+
if (!alreadySources) {
|
|
1776
2174
|
return {
|
|
1777
|
-
status: "
|
|
1778
|
-
|
|
1779
|
-
|
|
2175
|
+
status: "requires-force",
|
|
2176
|
+
reason: "would-modify-user-conf",
|
|
2177
|
+
userConf: userConfPreview,
|
|
2178
|
+
manualLine: markedSourceLinePreview,
|
|
2179
|
+
message: `Refusing to modify ${userConfPreview} without explicit consent.
|
|
2180
|
+
Re-run with --force (persist) or --yes (same effect, conf-append only) to append:
|
|
2181
|
+
${markedSourceLinePreview}
|
|
2182
|
+
Or run "sis admin check-keybinds" for the full decision tree (live-only install is also an option).`
|
|
1780
2183
|
};
|
|
1781
2184
|
}
|
|
1782
2185
|
}
|
|
1783
2186
|
writeKeymapJson();
|
|
1784
|
-
const scriptsDir =
|
|
2187
|
+
const scriptsDir = join3(globalDir(), "bin");
|
|
1785
2188
|
const bindings = [
|
|
1786
2189
|
// C-s → display-menu top-level (descriptor-driven)
|
|
1787
2190
|
generateTopLevelBinding(prefixKey, KEYMAP.topLevel, scriptsDir),
|
|
@@ -1803,7 +2206,7 @@ ${bindings.join("\n")}
|
|
|
1803
2206
|
if (contents.includes(confPath)) {
|
|
1804
2207
|
persistedToConf = true;
|
|
1805
2208
|
} else {
|
|
1806
|
-
const shouldAppend = opts.assumeYes ? true : await confirmConfAppend(userConf, markedSourceLine);
|
|
2209
|
+
const shouldAppend = opts.assumeYes || opts.force ? true : await confirmConfAppend(userConf, markedSourceLine);
|
|
1807
2210
|
if (shouldAppend) {
|
|
1808
2211
|
const separator = contents.endsWith("\n") ? "" : "\n";
|
|
1809
2212
|
writeFileSync(userConf, `${contents}${separator}${markedSourceLine}
|
|
@@ -1846,8 +2249,8 @@ Note: No tmux.conf found. Add this to your tmux config for persistence:
|
|
|
1846
2249
|
}
|
|
1847
2250
|
function removeTmuxKeybind() {
|
|
1848
2251
|
const confPath = sisyphusTmuxConfPath();
|
|
1849
|
-
for (const candidate of [
|
|
1850
|
-
if (
|
|
2252
|
+
for (const candidate of [join3(homedir3(), ".tmux.conf"), join3(homedir3(), ".config", "tmux", "tmux.conf")]) {
|
|
2253
|
+
if (existsSync2(candidate)) {
|
|
1851
2254
|
const contents = readFileSync(candidate, "utf8");
|
|
1852
2255
|
const filtered = contents.split("\n").filter((line) => !line.includes(confPath)).join("\n");
|
|
1853
2256
|
if (filtered !== contents) {
|
|
@@ -1855,7 +2258,7 @@ function removeTmuxKeybind() {
|
|
|
1855
2258
|
}
|
|
1856
2259
|
}
|
|
1857
2260
|
}
|
|
1858
|
-
if (
|
|
2261
|
+
if (existsSync2(confPath)) {
|
|
1859
2262
|
unlinkSync(confPath);
|
|
1860
2263
|
}
|
|
1861
2264
|
try {
|
|
@@ -1871,7 +2274,7 @@ function removeTmuxKeybind() {
|
|
|
1871
2274
|
} catch {
|
|
1872
2275
|
}
|
|
1873
2276
|
const kmPath = keymapJsonPath();
|
|
1874
|
-
if (
|
|
2277
|
+
if (existsSync2(kmPath)) unlinkSync(kmPath);
|
|
1875
2278
|
const scripts = [
|
|
1876
2279
|
"sisyphus-cycle",
|
|
1877
2280
|
"sisyphus-home",
|
|
@@ -1922,7 +2325,7 @@ function removeTmuxKeybind() {
|
|
|
1922
2325
|
];
|
|
1923
2326
|
for (const name of scripts) {
|
|
1924
2327
|
const path = scriptPath(name);
|
|
1925
|
-
if (
|
|
2328
|
+
if (existsSync2(path)) unlinkSync(path);
|
|
1926
2329
|
}
|
|
1927
2330
|
}
|
|
1928
2331
|
|
|
@@ -1987,10 +2390,10 @@ function loadConfig(cwd) {
|
|
|
1987
2390
|
// src/daemon/plugins.ts
|
|
1988
2391
|
import { readFileSync as readFileSync3 } from "fs";
|
|
1989
2392
|
import { execFileSync } from "child_process";
|
|
1990
|
-
import { homedir as
|
|
1991
|
-
import { join as
|
|
2393
|
+
import { homedir as homedir4 } from "os";
|
|
2394
|
+
import { join as join4 } from "path";
|
|
1992
2395
|
function installedPluginsPath() {
|
|
1993
|
-
return
|
|
2396
|
+
return join4(homedir4(), ".claude", "plugins", "installed_plugins.json");
|
|
1994
2397
|
}
|
|
1995
2398
|
function resolveInstalledPlugin(name) {
|
|
1996
2399
|
let data;
|
|
@@ -2071,10 +2474,10 @@ async function ensureRequiredPlugins(cwd) {
|
|
|
2071
2474
|
var PLIST_LABEL = "com.sisyphus.daemon";
|
|
2072
2475
|
var PLIST_FILENAME = `${PLIST_LABEL}.plist`;
|
|
2073
2476
|
function launchAgentDir() {
|
|
2074
|
-
return
|
|
2477
|
+
return join5(homedir5(), "Library", "LaunchAgents");
|
|
2075
2478
|
}
|
|
2076
2479
|
function plistPath() {
|
|
2077
|
-
return
|
|
2480
|
+
return join5(launchAgentDir(), PLIST_FILENAME);
|
|
2078
2481
|
}
|
|
2079
2482
|
function daemonBinPath() {
|
|
2080
2483
|
const installDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -2105,7 +2508,7 @@ function generatePlist(nodePath, daemonPath, logPath) {
|
|
|
2105
2508
|
`;
|
|
2106
2509
|
}
|
|
2107
2510
|
function isInstalled() {
|
|
2108
|
-
return
|
|
2511
|
+
return existsSync3(plistPath());
|
|
2109
2512
|
}
|
|
2110
2513
|
async function ensureDaemonInstalled() {
|
|
2111
2514
|
if (process.platform !== "darwin") return;
|
|
@@ -2114,8 +2517,8 @@ async function ensureDaemonInstalled() {
|
|
|
2114
2517
|
const nodePath = process.execPath;
|
|
2115
2518
|
const daemonPath = daemonBinPath();
|
|
2116
2519
|
const logPath = daemonLogPath();
|
|
2117
|
-
|
|
2118
|
-
|
|
2520
|
+
mkdirSync3(globalDir(), { recursive: true });
|
|
2521
|
+
mkdirSync3(launchAgentDir(), { recursive: true });
|
|
2119
2522
|
const plist = generatePlist(nodePath, daemonPath, logPath);
|
|
2120
2523
|
writeFileSync2(plistPath(), plist, "utf8");
|
|
2121
2524
|
execSync3(`launchctl load -w ${plistPath()}`);
|
|
@@ -2131,7 +2534,7 @@ async function uninstallDaemon(purge) {
|
|
|
2131
2534
|
return;
|
|
2132
2535
|
}
|
|
2133
2536
|
const plist = plistPath();
|
|
2134
|
-
if (
|
|
2537
|
+
if (existsSync3(plist)) {
|
|
2135
2538
|
try {
|
|
2136
2539
|
execSync3(`launchctl unload -w ${plist}`, { stdio: "pipe" });
|
|
2137
2540
|
} catch {
|
|
@@ -2144,7 +2547,7 @@ async function uninstallDaemon(purge) {
|
|
|
2144
2547
|
removeTmuxKeybind();
|
|
2145
2548
|
if (purge) {
|
|
2146
2549
|
const dir = globalDir();
|
|
2147
|
-
if (
|
|
2550
|
+
if (existsSync3(dir)) {
|
|
2148
2551
|
rmSync(dir, { recursive: true, force: true });
|
|
2149
2552
|
console.log(`Removed ${dir}`);
|
|
2150
2553
|
}
|
|
@@ -2158,8 +2561,9 @@ function printGettingStarted(keybindResult, sisyphusPlugin) {
|
|
|
2158
2561
|
];
|
|
2159
2562
|
if (keybindResult.status === "installed") {
|
|
2160
2563
|
lines.push(`Tmux keybind: ${keybindResult.message}`, "");
|
|
2161
|
-
} else if (keybindResult.status === "conflict") {
|
|
2564
|
+
} else if (keybindResult.status === "conflict" || keybindResult.status === "requires-force") {
|
|
2162
2565
|
lines.push(`Keybind: ${keybindResult.message}`, "");
|
|
2566
|
+
lines.push("Run `sis admin check-keybinds` to see options, then `sis admin setup --force`.", "");
|
|
2163
2567
|
} else if (keybindResult.status === "conf-modification-declined") {
|
|
2164
2568
|
lines.push(keybindResult.message, "");
|
|
2165
2569
|
}
|
|
@@ -2195,7 +2599,7 @@ async function waitForDaemon(maxWaitMs = 6e3) {
|
|
|
2195
2599
|
let updatingLogged = false;
|
|
2196
2600
|
while (Date.now() - start < maxWaitMs) {
|
|
2197
2601
|
const updatingPath = daemonUpdatingPath();
|
|
2198
|
-
if (
|
|
2602
|
+
if (existsSync3(updatingPath)) {
|
|
2199
2603
|
if (!updatingLogged) {
|
|
2200
2604
|
try {
|
|
2201
2605
|
const version = readFileSync4(updatingPath, "utf-8").trim();
|
|
@@ -2207,7 +2611,7 @@ async function waitForDaemon(maxWaitMs = 6e3) {
|
|
|
2207
2611
|
}
|
|
2208
2612
|
maxWaitMs = Math.max(maxWaitMs, 3e4);
|
|
2209
2613
|
}
|
|
2210
|
-
if (
|
|
2614
|
+
if (existsSync3(socketPath())) {
|
|
2211
2615
|
try {
|
|
2212
2616
|
await testConnection();
|
|
2213
2617
|
return;
|
|
@@ -2322,25 +2726,13 @@ function getTmuxSessionInfo() {
|
|
|
2322
2726
|
return { id: out.slice(0, pipeIdx), name: out.slice(pipeIdx + 1) };
|
|
2323
2727
|
}
|
|
2324
2728
|
|
|
2325
|
-
// src/
|
|
2326
|
-
|
|
2327
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
2328
|
-
}
|
|
2329
|
-
function shellQuoteHomePath(path) {
|
|
2330
|
-
if (path === "~") return "~";
|
|
2331
|
-
if (path.startsWith("~/")) return `~/${shellQuote(path.slice(2))}`;
|
|
2332
|
-
return shellQuote(path);
|
|
2333
|
-
}
|
|
2334
|
-
function validateRepoName(repo) {
|
|
2335
|
-
return !repo.includes("/") && !repo.includes("\\") && !repo.includes("..");
|
|
2336
|
-
}
|
|
2337
|
-
function escapeAppleScript(s) {
|
|
2338
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2339
|
-
}
|
|
2729
|
+
// src/cli/commands/start.ts
|
|
2730
|
+
init_shell();
|
|
2340
2731
|
|
|
2341
2732
|
// src/cli/commands/dashboard.ts
|
|
2342
|
-
import { join as
|
|
2733
|
+
import { join as join6 } from "path";
|
|
2343
2734
|
import { execSync as execSync5 } from "child_process";
|
|
2735
|
+
init_shell();
|
|
2344
2736
|
function openDashboardWindow(tmuxSession, cwd) {
|
|
2345
2737
|
try {
|
|
2346
2738
|
const storedId = execSync5(
|
|
@@ -2360,7 +2752,7 @@ function openDashboardWindow(tmuxSession, cwd) {
|
|
|
2360
2752
|
}
|
|
2361
2753
|
} catch {
|
|
2362
2754
|
}
|
|
2363
|
-
const tuiPath =
|
|
2755
|
+
const tuiPath = join6(import.meta.dirname, "tui.js");
|
|
2364
2756
|
const windowId = execSync5(
|
|
2365
2757
|
`tmux new-window -t ${shellQuote(tmuxSession + ":")} -n "sisyphus-dashboard" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
|
|
2366
2758
|
{ encoding: "utf-8" }
|
|
@@ -2378,7 +2770,7 @@ function openDashboardWindow(tmuxSession, cwd) {
|
|
|
2378
2770
|
function registerDashboard(program2) {
|
|
2379
2771
|
program2.command("dashboard").description("Launch the TUI dashboard for monitoring and managing sessions").action(async () => {
|
|
2380
2772
|
assertTmux();
|
|
2381
|
-
const tuiPath =
|
|
2773
|
+
const tuiPath = join6(import.meta.dirname, "tui.js");
|
|
2382
2774
|
execSync5(`node ${shellQuote(tuiPath)} --cwd ${shellQuote(process.cwd())}`, {
|
|
2383
2775
|
stdio: "inherit"
|
|
2384
2776
|
});
|
|
@@ -2471,10 +2863,10 @@ function registerStart(program2) {
|
|
|
2471
2863
|
const sessionId = response.data?.sessionId;
|
|
2472
2864
|
console.log(`Task handed off to sisyphus orchestrator (session ${sessionId})`);
|
|
2473
2865
|
if (opts.tmuxCheck === false) {
|
|
2474
|
-
const
|
|
2475
|
-
if (
|
|
2476
|
-
console.log(`Tmux session: ${
|
|
2477
|
-
console.log(` tmux attach -t ${
|
|
2866
|
+
const tmuxSessionName2 = response.data?.tmuxSessionName;
|
|
2867
|
+
if (tmuxSessionName2) {
|
|
2868
|
+
console.log(`Tmux session: ${tmuxSessionName2}`);
|
|
2869
|
+
console.log(` tmux attach -t ${tmuxSessionName2}`);
|
|
2478
2870
|
}
|
|
2479
2871
|
console.log(`Monitor: sis status ${sessionId}`);
|
|
2480
2872
|
return;
|
|
@@ -2526,7 +2918,7 @@ function registerStart(program2) {
|
|
|
2526
2918
|
|
|
2527
2919
|
// src/cli/commands/status.ts
|
|
2528
2920
|
import { execSync as execSync7 } from "child_process";
|
|
2529
|
-
import { existsSync as
|
|
2921
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
2530
2922
|
|
|
2531
2923
|
// src/shared/utils.ts
|
|
2532
2924
|
function computeActiveTimeMs(session2) {
|
|
@@ -2672,7 +3064,7 @@ function readRoadmap(cwd, sessionId) {
|
|
|
2672
3064
|
function readCycleLog(cwd, sessionId, cycle) {
|
|
2673
3065
|
try {
|
|
2674
3066
|
const path = cycleLogPath(cwd, sessionId, cycle);
|
|
2675
|
-
if (!
|
|
3067
|
+
if (!existsSync4(path)) return null;
|
|
2676
3068
|
return readFileSync5(path, "utf8");
|
|
2677
3069
|
} catch {
|
|
2678
3070
|
return null;
|
|
@@ -2932,16 +3324,16 @@ function registerTell(program2) {
|
|
|
2932
3324
|
}
|
|
2933
3325
|
|
|
2934
3326
|
// src/cli/commands/read.ts
|
|
2935
|
-
import { existsSync as
|
|
2936
|
-
import { homedir as
|
|
2937
|
-
import { join as
|
|
3327
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
3328
|
+
import { homedir as homedir6 } from "os";
|
|
3329
|
+
import { join as join7 } from "path";
|
|
2938
3330
|
var ORCH_ALIASES2 = /* @__PURE__ */ new Set(["orchestrator", "orch", "o"]);
|
|
2939
3331
|
var TURN_TYPES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
2940
3332
|
function projectDirFromCwd(cwd) {
|
|
2941
3333
|
return cwd.replace(/\//g, "-");
|
|
2942
3334
|
}
|
|
2943
3335
|
function transcriptPath(cwd, claudeSessionId) {
|
|
2944
|
-
return
|
|
3336
|
+
return join7(homedir6(), ".claude", "projects", projectDirFromCwd(cwd), `${claudeSessionId}.jsonl`);
|
|
2945
3337
|
}
|
|
2946
3338
|
function truncate(s, max) {
|
|
2947
3339
|
if (s.length <= max) return s;
|
|
@@ -3057,7 +3449,7 @@ function registerRead(program2) {
|
|
|
3057
3449
|
process.exit(1);
|
|
3058
3450
|
}
|
|
3059
3451
|
const path = transcriptPath(session2.cwd, claudeSessionId);
|
|
3060
|
-
if (!
|
|
3452
|
+
if (!existsSync5(path)) {
|
|
3061
3453
|
console.error(`Error: transcript not found at ${path}`);
|
|
3062
3454
|
process.exit(1);
|
|
3063
3455
|
}
|
|
@@ -3145,13 +3537,13 @@ function registerMessage(program2) {
|
|
|
3145
3537
|
}
|
|
3146
3538
|
|
|
3147
3539
|
// src/cli/commands/ask.ts
|
|
3148
|
-
import { existsSync as
|
|
3149
|
-
import { join as
|
|
3540
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13, watchFile, unwatchFile } from "fs";
|
|
3541
|
+
import { join as join13, resolve as resolve4 } from "path";
|
|
3150
3542
|
import { ulid } from "ulid";
|
|
3151
3543
|
|
|
3152
3544
|
// src/shared/ask-schema.ts
|
|
3153
3545
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3154
|
-
import { existsSync as
|
|
3546
|
+
import { existsSync as existsSync6, lstatSync, readFileSync as readFileSync8, realpathSync } from "fs";
|
|
3155
3547
|
import { dirname as dirname2, resolve as resolve2, sep } from "path";
|
|
3156
3548
|
import { z } from "zod";
|
|
3157
3549
|
var interactionOptionSchema = z.object({
|
|
@@ -3219,7 +3611,7 @@ function runTermrenderCheck(content) {
|
|
|
3219
3611
|
function inlineBodyPath(deckPath, bodyPath) {
|
|
3220
3612
|
const deckDir = dirname2(deckPath);
|
|
3221
3613
|
const joined = resolve2(deckDir, bodyPath);
|
|
3222
|
-
if (!
|
|
3614
|
+
if (!existsSync6(joined)) {
|
|
3223
3615
|
throw new Error(
|
|
3224
3616
|
`bodyPath does not exist: '${bodyPath}' (resolved against deck dir '${deckDir}'). bodyPath is interpreted relative to the deck JSON's directory; place the body file there and use a relative path (e.g. "completion-summary.md").`
|
|
3225
3617
|
);
|
|
@@ -3266,17 +3658,17 @@ function parseDeck(deckPath) {
|
|
|
3266
3658
|
|
|
3267
3659
|
// src/daemon/ask-store.ts
|
|
3268
3660
|
init_paths();
|
|
3269
|
-
import { existsSync as
|
|
3661
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync12, readdirSync as readdirSync3 } from "fs";
|
|
3270
3662
|
|
|
3271
3663
|
// src/daemon/history.ts
|
|
3272
3664
|
init_paths();
|
|
3273
|
-
import { appendFileSync, mkdirSync as
|
|
3665
|
+
import { appendFileSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, renameSync, readdirSync, readFileSync as readFileSync9, rmSync as rmSync2, statSync } from "fs";
|
|
3274
3666
|
import { randomUUID } from "crypto";
|
|
3275
|
-
import { dirname as dirname3, join as
|
|
3667
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
3276
3668
|
var knownDirs = /* @__PURE__ */ new Set();
|
|
3277
3669
|
function ensureDir(sessionId) {
|
|
3278
3670
|
if (knownDirs.has(sessionId)) return;
|
|
3279
|
-
|
|
3671
|
+
mkdirSync4(historySessionDir(sessionId), { recursive: true });
|
|
3280
3672
|
knownDirs.add(sessionId);
|
|
3281
3673
|
}
|
|
3282
3674
|
function emitHistoryEvent(sessionId, event, data) {
|
|
@@ -3291,12 +3683,12 @@ function emitHistoryEvent(sessionId, event, data) {
|
|
|
3291
3683
|
// src/daemon/state.ts
|
|
3292
3684
|
init_atomic();
|
|
3293
3685
|
init_paths();
|
|
3294
|
-
import { copyFileSync, cpSync, existsSync as
|
|
3295
|
-
import { join as
|
|
3686
|
+
import { copyFileSync, cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, readdirSync as readdirSync2, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
3687
|
+
import { join as join11 } from "path";
|
|
3296
3688
|
|
|
3297
3689
|
// src/shared/gitignore.ts
|
|
3298
|
-
import { existsSync as
|
|
3299
|
-
import { join as
|
|
3690
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
3691
|
+
import { join as join10 } from "path";
|
|
3300
3692
|
|
|
3301
3693
|
// src/shared/types.ts
|
|
3302
3694
|
var ORCHESTRATOR_ASKED_BY = "orchestrator";
|
|
@@ -3358,10 +3750,11 @@ async function incrementUserBlockedMs(cwd, sessionId, deltaMs, askedAt, askedBy)
|
|
|
3358
3750
|
init_atomic();
|
|
3359
3751
|
|
|
3360
3752
|
// src/daemon/notify.ts
|
|
3753
|
+
init_shell();
|
|
3361
3754
|
import { spawn, execFile } from "child_process";
|
|
3362
|
-
import { writeFileSync as writeFileSync7, mkdirSync as
|
|
3363
|
-
import { join as
|
|
3364
|
-
import { homedir as
|
|
3755
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync9 } from "fs";
|
|
3756
|
+
import { join as join12 } from "path";
|
|
3757
|
+
import { homedir as homedir7 } from "os";
|
|
3365
3758
|
var TMUX_SOCKET = `/tmp/tmux-${process.getuid?.() ?? 0}/default`;
|
|
3366
3759
|
var SWITCH_SCRIPT = [
|
|
3367
3760
|
"#!/bin/bash",
|
|
@@ -3403,24 +3796,24 @@ var SWITCH_SCRIPT = [
|
|
|
3403
3796
|
""
|
|
3404
3797
|
].join("\n");
|
|
3405
3798
|
function ensureSwitchScript() {
|
|
3406
|
-
const dir =
|
|
3407
|
-
const scriptPath2 =
|
|
3799
|
+
const dir = join12(homedir7(), ".sisyphus");
|
|
3800
|
+
const scriptPath2 = join12(dir, "notify-switch.sh");
|
|
3408
3801
|
try {
|
|
3409
|
-
|
|
3802
|
+
mkdirSync6(dir, { recursive: true });
|
|
3410
3803
|
writeFileSync7(scriptPath2, SWITCH_SCRIPT, { mode: 493 });
|
|
3411
3804
|
} catch {
|
|
3412
3805
|
}
|
|
3413
3806
|
}
|
|
3414
3807
|
var notifyProcess = null;
|
|
3415
3808
|
function getNotifyBinary() {
|
|
3416
|
-
return
|
|
3809
|
+
return join12(homedir7(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
|
|
3417
3810
|
}
|
|
3418
3811
|
function ensureNotifyProcess() {
|
|
3419
3812
|
if (notifyProcess && !notifyProcess.killed && notifyProcess.stdin?.writable) {
|
|
3420
3813
|
return notifyProcess;
|
|
3421
3814
|
}
|
|
3422
3815
|
const binary = getNotifyBinary();
|
|
3423
|
-
if (!
|
|
3816
|
+
if (!existsSync9(binary)) {
|
|
3424
3817
|
return null;
|
|
3425
3818
|
}
|
|
3426
3819
|
notifyProcess = spawn(binary, [], {
|
|
@@ -3500,7 +3893,7 @@ function maybeNotifyOnAskCreated(cwd, sessionId, meta) {
|
|
|
3500
3893
|
}
|
|
3501
3894
|
}
|
|
3502
3895
|
function createAsk(cwd, sessionId, params) {
|
|
3503
|
-
|
|
3896
|
+
mkdirSync7(askVisualsDir(cwd, sessionId, params.askId), { recursive: true });
|
|
3504
3897
|
const askedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3505
3898
|
const meta = {
|
|
3506
3899
|
askId: params.askId,
|
|
@@ -3547,7 +3940,7 @@ function writeOutput(cwd, sessionId, askId, responses, completedAt) {
|
|
|
3547
3940
|
}
|
|
3548
3941
|
function readMeta(cwd, sessionId, askId) {
|
|
3549
3942
|
const p = askMetaPath(cwd, sessionId, askId);
|
|
3550
|
-
if (!
|
|
3943
|
+
if (!existsSync10(p)) {
|
|
3551
3944
|
return null;
|
|
3552
3945
|
}
|
|
3553
3946
|
return JSON.parse(readFileSync12(p, "utf-8"));
|
|
@@ -3574,7 +3967,7 @@ function buildAutoResponses(deck) {
|
|
|
3574
3967
|
}
|
|
3575
3968
|
async function autoResolveAsk(cwd, sessionId, askId, deck) {
|
|
3576
3969
|
try {
|
|
3577
|
-
if (
|
|
3970
|
+
if (existsSync10(askOutputPath(cwd, sessionId, askId))) return false;
|
|
3578
3971
|
const d = deck ?? readDecisions(cwd, sessionId, askId);
|
|
3579
3972
|
if (!d) return false;
|
|
3580
3973
|
const responses = buildAutoResponses(d);
|
|
@@ -3600,64 +3993,8 @@ async function maybeAutoResolveAsk(cwd, sessionId, askId, deck) {
|
|
|
3600
3993
|
|
|
3601
3994
|
// src/cli/commands/ask.ts
|
|
3602
3995
|
init_paths();
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
import { execSync as execSync8 } from "child_process";
|
|
3606
|
-
|
|
3607
|
-
// src/shared/env.ts
|
|
3608
|
-
import { resolve as resolve3 } from "path";
|
|
3609
|
-
function augmentedPath() {
|
|
3610
|
-
const rawPath = process.env["PATH"];
|
|
3611
|
-
const basePath = rawPath !== void 0 && rawPath.length > 0 ? rawPath : "/usr/bin:/bin";
|
|
3612
|
-
const home = process.env["HOME"];
|
|
3613
|
-
const candidates = [
|
|
3614
|
-
...home ? [`${home}/.local/bin`] : [],
|
|
3615
|
-
// Claude CLI, pipx, user-local installs
|
|
3616
|
-
resolve3(process.execPath, ".."),
|
|
3617
|
-
// Node.js bin dir (ensures node/npm available)
|
|
3618
|
-
"/opt/homebrew/bin",
|
|
3619
|
-
// Homebrew (Apple Silicon macOS)
|
|
3620
|
-
"/opt/homebrew/sbin",
|
|
3621
|
-
// Homebrew sbin
|
|
3622
|
-
"/usr/local/bin",
|
|
3623
|
-
// Homebrew (Intel macOS), manual installs
|
|
3624
|
-
"/usr/local/sbin",
|
|
3625
|
-
// Manual installs
|
|
3626
|
-
"/opt/local/bin",
|
|
3627
|
-
// MacPorts
|
|
3628
|
-
"/opt/local/sbin",
|
|
3629
|
-
// MacPorts
|
|
3630
|
-
"/home/linuxbrew/.linuxbrew/bin"
|
|
3631
|
-
// Linuxbrew
|
|
3632
|
-
];
|
|
3633
|
-
const nixProfile = process.env["NIX_PROFILES"];
|
|
3634
|
-
if (nixProfile) {
|
|
3635
|
-
for (const p of nixProfile.split(" ").reverse()) {
|
|
3636
|
-
candidates.push(`${p}/bin`);
|
|
3637
|
-
}
|
|
3638
|
-
}
|
|
3639
|
-
const existing = new Set(basePath.split(":"));
|
|
3640
|
-
const prepend = candidates.filter((dir) => !existing.has(dir));
|
|
3641
|
-
return prepend.length > 0 ? `${prepend.join(":")}:${basePath}` : basePath;
|
|
3642
|
-
}
|
|
3643
|
-
function execEnv() {
|
|
3644
|
-
return {
|
|
3645
|
-
...process.env,
|
|
3646
|
-
PATH: augmentedPath()
|
|
3647
|
-
};
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
// src/shared/exec.ts
|
|
3651
|
-
var EXEC_ENV = execEnv();
|
|
3652
|
-
function execSafe(cmd, cwd, timeoutMs) {
|
|
3653
|
-
try {
|
|
3654
|
-
return execSync8(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, stdio: ["pipe", "pipe", "pipe"], timeout: timeoutMs }).trim();
|
|
3655
|
-
} catch {
|
|
3656
|
-
return null;
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
|
|
3660
|
-
// src/cli/commands/ask.ts
|
|
3996
|
+
init_exec();
|
|
3997
|
+
init_shell();
|
|
3661
3998
|
var ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
3662
3999
|
function validateAskId(askId) {
|
|
3663
4000
|
if (!ULID_RE.test(askId)) {
|
|
@@ -3726,7 +4063,7 @@ function mintAskId() {
|
|
|
3726
4063
|
return ulid();
|
|
3727
4064
|
}
|
|
3728
4065
|
function resolveClaudeSessionId(cwd, sessionId, askedBy) {
|
|
3729
|
-
if (!
|
|
4066
|
+
if (!existsSync11(statePath(cwd, sessionId))) return void 0;
|
|
3730
4067
|
const session2 = getSession(cwd, sessionId);
|
|
3731
4068
|
if (askedBy === ORCHESTRATOR_ASKED_BY) {
|
|
3732
4069
|
const last = session2.orchestratorCycles[session2.orchestratorCycles.length - 1];
|
|
@@ -3763,7 +4100,7 @@ async function markAnswered(cwd, sessionId, askId) {
|
|
|
3763
4100
|
});
|
|
3764
4101
|
if (meta.blocking && durationMs > 0) {
|
|
3765
4102
|
try {
|
|
3766
|
-
if (
|
|
4103
|
+
if (existsSync11(statePath(cwd, sessionId))) {
|
|
3767
4104
|
await incrementUserBlockedMs(cwd, sessionId, durationMs, meta.askedAt, meta.askedBy);
|
|
3768
4105
|
}
|
|
3769
4106
|
} catch {
|
|
@@ -3772,7 +4109,7 @@ async function markAnswered(cwd, sessionId, askId) {
|
|
|
3772
4109
|
}
|
|
3773
4110
|
function waitForOutput(cwd, sessionId, askId, initialPpid) {
|
|
3774
4111
|
const outputPath = askOutputPath(cwd, sessionId, askId);
|
|
3775
|
-
if (
|
|
4112
|
+
if (existsSync11(outputPath)) {
|
|
3776
4113
|
return Promise.resolve(JSON.parse(readFileSync13(outputPath, "utf-8")));
|
|
3777
4114
|
}
|
|
3778
4115
|
return new Promise((res, _rej) => {
|
|
@@ -3783,7 +4120,7 @@ function waitForOutput(cwd, sessionId, askId, initialPpid) {
|
|
|
3783
4120
|
process.removeListener("SIGINT", onSigint);
|
|
3784
4121
|
};
|
|
3785
4122
|
const onChange = () => {
|
|
3786
|
-
if (!
|
|
4123
|
+
if (!existsSync11(outputPath)) return;
|
|
3787
4124
|
try {
|
|
3788
4125
|
const out = JSON.parse(readFileSync13(outputPath, "utf-8"));
|
|
3789
4126
|
cleanup();
|
|
@@ -3816,7 +4153,7 @@ function maybeSpawnAskPane(cwd, sessionId, askId) {
|
|
|
3816
4153
|
const callerPane = process.env.TMUX_PANE;
|
|
3817
4154
|
if (!callerPane) return;
|
|
3818
4155
|
if (process.env.SISYPHUS_DISABLE_ASK_PANE === "1") return;
|
|
3819
|
-
const tuiPath =
|
|
4156
|
+
const tuiPath = join13(import.meta.dirname, "tui.js");
|
|
3820
4157
|
const cmd = `node ${shellQuote(tuiPath)} --cwd ${shellQuote(cwd)} --session-id ${shellQuote(sessionId)} --ask ${shellQuote(askId)}`;
|
|
3821
4158
|
execSafe(`tmux split-window -d -h -t ${shellQuote(callerPane)} -c ${shellQuote(cwd)} ${shellQuote(cmd)}`);
|
|
3822
4159
|
}
|
|
@@ -3824,7 +4161,7 @@ async function submit(file, opts) {
|
|
|
3824
4161
|
const { cwd, sessionId } = resolveSessionEnv(opts);
|
|
3825
4162
|
const askedBy = process.env.SISYPHUS_AGENT_ID ?? ORCHESTRATOR_ASKED_BY;
|
|
3826
4163
|
const deckPath = resolve4(file);
|
|
3827
|
-
if (!
|
|
4164
|
+
if (!existsSync11(deckPath)) {
|
|
3828
4165
|
console.error(`Error: deck file not found: ${deckPath}`);
|
|
3829
4166
|
process.exit(1);
|
|
3830
4167
|
}
|
|
@@ -3883,7 +4220,7 @@ async function peek(askId, opts) {
|
|
|
3883
4220
|
};
|
|
3884
4221
|
if (meta.completedAt) result.completedAt = meta.completedAt;
|
|
3885
4222
|
try {
|
|
3886
|
-
if (
|
|
4223
|
+
if (existsSync11(outputPath)) {
|
|
3887
4224
|
result.output = JSON.parse(readFileSync13(outputPath, "utf-8"));
|
|
3888
4225
|
}
|
|
3889
4226
|
} catch (err) {
|
|
@@ -3951,11 +4288,11 @@ function registerResume(program2) {
|
|
|
3951
4288
|
const request = { type: "resume", sessionId, cwd, message };
|
|
3952
4289
|
const response = await sendRequest(request);
|
|
3953
4290
|
if (response.ok) {
|
|
3954
|
-
const
|
|
4291
|
+
const tmuxSessionName2 = response.data?.tmuxSessionName;
|
|
3955
4292
|
console.log(`Session ${sessionId} resumed`);
|
|
3956
|
-
if (
|
|
3957
|
-
console.log(`Tmux session: ${
|
|
3958
|
-
console.log(` tmux attach -t ${
|
|
4293
|
+
if (tmuxSessionName2) {
|
|
4294
|
+
console.log(`Tmux session: ${tmuxSessionName2}`);
|
|
4295
|
+
console.log(` tmux attach -t ${tmuxSessionName2}`);
|
|
3959
4296
|
}
|
|
3960
4297
|
} else {
|
|
3961
4298
|
console.error(`Error: ${response.error}`);
|
|
@@ -4038,9 +4375,9 @@ function registerReconnect(program2) {
|
|
|
4038
4375
|
const request = { type: "reconnect", sessionId, cwd };
|
|
4039
4376
|
const response = await sendRequest(request);
|
|
4040
4377
|
if (response.ok) {
|
|
4041
|
-
const
|
|
4378
|
+
const tmuxSessionName2 = response.data?.tmuxSessionName;
|
|
4042
4379
|
const tmuxWindowId = response.data?.tmuxWindowId;
|
|
4043
|
-
console.log(`Reconnected to ${
|
|
4380
|
+
console.log(`Reconnected to ${tmuxSessionName2} (window ${tmuxWindowId})`);
|
|
4044
4381
|
} else {
|
|
4045
4382
|
console.error(`Error: ${response.error}`);
|
|
4046
4383
|
process.exit(1);
|
|
@@ -4130,6 +4467,65 @@ function registerSessionEffort(program2) {
|
|
|
4130
4467
|
});
|
|
4131
4468
|
}
|
|
4132
4469
|
|
|
4470
|
+
// src/cli/commands/set-dangerous.ts
|
|
4471
|
+
var VALID_STATES = ["on", "off", "toggle"];
|
|
4472
|
+
function registerSessionDangerous(program2) {
|
|
4473
|
+
program2.command("dangerous [sessionId] [state]").description("Toggle dangerous mode (auto-accept first option for every ask). state: on|off|toggle (default: toggle)").action(async (sessionIdArg, stateArg) => {
|
|
4474
|
+
let sessionId;
|
|
4475
|
+
if (sessionIdArg) {
|
|
4476
|
+
sessionId = sessionIdArg;
|
|
4477
|
+
} else if (process.env.SISYPHUS_SESSION_ID) {
|
|
4478
|
+
sessionId = process.env.SISYPHUS_SESSION_ID;
|
|
4479
|
+
} else {
|
|
4480
|
+
console.error("Error: provide <sessionId> or set SISYPHUS_SESSION_ID environment variable");
|
|
4481
|
+
process.exit(1);
|
|
4482
|
+
}
|
|
4483
|
+
let state;
|
|
4484
|
+
if (!stateArg) {
|
|
4485
|
+
state = "toggle";
|
|
4486
|
+
} else {
|
|
4487
|
+
const stateInput = stateArg.toLowerCase();
|
|
4488
|
+
if (!VALID_STATES.includes(stateInput)) {
|
|
4489
|
+
console.error(`Error: state must be one of: ${VALID_STATES.join(", ")}`);
|
|
4490
|
+
process.exit(1);
|
|
4491
|
+
}
|
|
4492
|
+
state = stateInput;
|
|
4493
|
+
}
|
|
4494
|
+
let enabled;
|
|
4495
|
+
if (state === "toggle") {
|
|
4496
|
+
const cwd = process.env["SISYPHUS_CWD"] ? process.env["SISYPHUS_CWD"] : process.cwd();
|
|
4497
|
+
const statusResp = await sendRequest({ type: "status", sessionId, cwd });
|
|
4498
|
+
if (!statusResp.ok) {
|
|
4499
|
+
console.error(`Error: ${statusResp.error}`);
|
|
4500
|
+
process.exit(1);
|
|
4501
|
+
}
|
|
4502
|
+
const session2 = statusResp.data?.session;
|
|
4503
|
+
if (!session2) {
|
|
4504
|
+
console.error(`Error: session ${sessionId} not found`);
|
|
4505
|
+
process.exit(1);
|
|
4506
|
+
}
|
|
4507
|
+
enabled = !session2.dangerousMode;
|
|
4508
|
+
} else {
|
|
4509
|
+
enabled = state === "on";
|
|
4510
|
+
}
|
|
4511
|
+
const request = { type: "set-dangerous-mode", sessionId, enabled };
|
|
4512
|
+
const response = await sendRequest(request);
|
|
4513
|
+
if (response.ok) {
|
|
4514
|
+
const flushedRaw = response.data?.flushed;
|
|
4515
|
+
const flushed = typeof flushedRaw === "number" ? flushedRaw : 0;
|
|
4516
|
+
const label = enabled ? "ON" : "OFF";
|
|
4517
|
+
let msg = `DANGEROUS mode ${label} for session ${sessionId}`;
|
|
4518
|
+
if (enabled && flushed > 0) {
|
|
4519
|
+
msg += ` \u2014 ${flushed} pending ask${flushed === 1 ? "" : "s"} auto-resolved`;
|
|
4520
|
+
}
|
|
4521
|
+
console.log(msg);
|
|
4522
|
+
} else {
|
|
4523
|
+
console.error(`Error: ${response.error}`);
|
|
4524
|
+
process.exit(1);
|
|
4525
|
+
}
|
|
4526
|
+
});
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4133
4529
|
// src/tui/lib/context.ts
|
|
4134
4530
|
init_paths();
|
|
4135
4531
|
import { readFileSync as readFileSync15, readdirSync as readdirSync4 } from "fs";
|
|
@@ -4293,14 +4689,14 @@ function registerSessionContext(program2) {
|
|
|
4293
4689
|
}
|
|
4294
4690
|
|
|
4295
4691
|
// src/cli/commands/spawn.ts
|
|
4296
|
-
import { existsSync as
|
|
4297
|
-
import { join as
|
|
4692
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4693
|
+
import { join as join15, resolve as resolve5 } from "path";
|
|
4298
4694
|
|
|
4299
4695
|
// src/daemon/frontmatter.ts
|
|
4300
4696
|
init_paths();
|
|
4301
|
-
import { readFileSync as readFileSync16, existsSync as
|
|
4302
|
-
import { homedir as
|
|
4303
|
-
import { join as
|
|
4697
|
+
import { readFileSync as readFileSync16, existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
|
|
4698
|
+
import { homedir as homedir8 } from "os";
|
|
4699
|
+
import { join as join14, basename as basename4 } from "path";
|
|
4304
4700
|
function parseAgentFrontmatter(content) {
|
|
4305
4701
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4306
4702
|
if (!match) return {};
|
|
@@ -4350,7 +4746,7 @@ function discoverAgentTypes(pluginDir, cwd) {
|
|
|
4350
4746
|
if (seen.has(qualifiedName)) continue;
|
|
4351
4747
|
seen.add(qualifiedName);
|
|
4352
4748
|
try {
|
|
4353
|
-
const content = readFileSync16(
|
|
4749
|
+
const content = readFileSync16(join14(dir, file), "utf-8");
|
|
4354
4750
|
const fm = parseAgentFrontmatter(content);
|
|
4355
4751
|
results.push({ qualifiedName, source, description: fm.description, model: fm.model });
|
|
4356
4752
|
} catch {
|
|
@@ -4358,13 +4754,13 @@ function discoverAgentTypes(pluginDir, cwd) {
|
|
|
4358
4754
|
}
|
|
4359
4755
|
}
|
|
4360
4756
|
}
|
|
4361
|
-
scanDir(
|
|
4362
|
-
scanDir(
|
|
4363
|
-
scanDir(
|
|
4364
|
-
scanDir(
|
|
4365
|
-
scanDir(
|
|
4757
|
+
scanDir(join14(projectAgentPluginDir(cwd), "agents"), null, "project-sis");
|
|
4758
|
+
scanDir(join14(userAgentPluginDir(), "agents"), null, "user-sis");
|
|
4759
|
+
scanDir(join14(cwd, ".claude", "agents"), null, "project");
|
|
4760
|
+
scanDir(join14(homedir8(), ".claude", "agents"), null, "user");
|
|
4761
|
+
scanDir(join14(pluginDir, "agents"), "sisyphus", "bundled");
|
|
4366
4762
|
try {
|
|
4367
|
-
const registryPath =
|
|
4763
|
+
const registryPath = join14(homedir8(), ".claude", "plugins", "installed_plugins.json");
|
|
4368
4764
|
const registry = JSON.parse(readFileSync16(registryPath, "utf-8"));
|
|
4369
4765
|
const pluginEntries = registry.plugins ?? registry;
|
|
4370
4766
|
for (const key of Object.keys(pluginEntries)) {
|
|
@@ -4374,7 +4770,7 @@ function discoverAgentTypes(pluginDir, cwd) {
|
|
|
4374
4770
|
const entry = pluginEntries[key];
|
|
4375
4771
|
const installPath = Array.isArray(entry) ? entry[0]?.installPath : entry?.installPath;
|
|
4376
4772
|
if (installPath) {
|
|
4377
|
-
scanDir(
|
|
4773
|
+
scanDir(join14(installPath, "agents"), namespace, "plugin");
|
|
4378
4774
|
}
|
|
4379
4775
|
}
|
|
4380
4776
|
} catch {
|
|
@@ -4444,8 +4840,8 @@ function registerSpawn(program2) {
|
|
|
4444
4840
|
process.exit(1);
|
|
4445
4841
|
}
|
|
4446
4842
|
if (opts.repo && opts.repo !== ".") {
|
|
4447
|
-
const repoPath =
|
|
4448
|
-
if (!
|
|
4843
|
+
const repoPath = join15(sisyphusCwd, opts.repo);
|
|
4844
|
+
if (!existsSync13(repoPath)) {
|
|
4449
4845
|
console.error(`Error: repo directory does not exist: ${repoPath}`);
|
|
4450
4846
|
process.exit(1);
|
|
4451
4847
|
}
|
|
@@ -4552,7 +4948,7 @@ function registerReport(program2) {
|
|
|
4552
4948
|
}
|
|
4553
4949
|
|
|
4554
4950
|
// src/cli/commands/await.ts
|
|
4555
|
-
import { existsSync as
|
|
4951
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
|
|
4556
4952
|
var AWAIT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
|
|
4557
4953
|
function registerAwait(program2) {
|
|
4558
4954
|
program2.command("await").description("Block until an agent reaches a terminal status, then print its final report inline. Marks the agent as consumed-inline so its report is suppressed from the next cycle.").argument("<agentId>", "Agent ID to await").option("--session <sessionId>", "Session ID (defaults to SISYPHUS_SESSION_ID env var)").action(async (agentId, opts) => {
|
|
@@ -4576,7 +4972,7 @@ function registerAwait(program2) {
|
|
|
4576
4972
|
const shortType = agentType && agentType !== "worker" ? agentType.replace(/^sisyphus:/, "") : "";
|
|
4577
4973
|
const label = shortType ? `${shortType}-${agentName}` : agentName;
|
|
4578
4974
|
console.log(`[${status}] ${agentId} (${label})`);
|
|
4579
|
-
if (reportPath &&
|
|
4975
|
+
if (reportPath && existsSync14(reportPath)) {
|
|
4580
4976
|
try {
|
|
4581
4977
|
const body = readFileSync17(reportPath, "utf-8");
|
|
4582
4978
|
if (body.length > 0) {
|
|
@@ -4706,9 +5102,9 @@ import { execSync as execSync10 } from "child_process";
|
|
|
4706
5102
|
|
|
4707
5103
|
// src/cli/onboard.ts
|
|
4708
5104
|
import { execSync as execSync9 } from "child_process";
|
|
4709
|
-
import { existsSync as
|
|
4710
|
-
import { homedir as
|
|
4711
|
-
import { dirname as dirname5, join as
|
|
5105
|
+
import { existsSync as existsSync15, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
|
|
5106
|
+
import { homedir as homedir9 } from "os";
|
|
5107
|
+
import { dirname as dirname5, join as join16 } from "path";
|
|
4712
5108
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4713
5109
|
function detectTerminal() {
|
|
4714
5110
|
const termProgram = process.env["TERM_PROGRAM"] || "";
|
|
@@ -4745,8 +5141,8 @@ function checkItermOptionKey() {
|
|
|
4745
5141
|
if (process.platform !== "darwin") {
|
|
4746
5142
|
return { checked: false, allCorrect: true, incorrectProfiles: [] };
|
|
4747
5143
|
}
|
|
4748
|
-
const plistPath2 =
|
|
4749
|
-
if (!
|
|
5144
|
+
const plistPath2 = join16(homedir9(), "Library", "Preferences", "com.googlecode.iterm2.plist");
|
|
5145
|
+
if (!existsSync15(plistPath2)) {
|
|
4750
5146
|
return { checked: false, allCorrect: false, incorrectProfiles: [] };
|
|
4751
5147
|
}
|
|
4752
5148
|
try {
|
|
@@ -4770,7 +5166,7 @@ function checkItermOptionKey() {
|
|
|
4770
5166
|
}
|
|
4771
5167
|
}
|
|
4772
5168
|
function hasExistingTmuxConf() {
|
|
4773
|
-
return
|
|
5169
|
+
return existsSync15(join16(homedir9(), ".tmux.conf")) || existsSync15(join16(homedir9(), ".config", "tmux", "tmux.conf"));
|
|
4774
5170
|
}
|
|
4775
5171
|
var SISYPHUS_DEFAULTS_MARKER = "# sisyphus-managed \u2014 do not edit";
|
|
4776
5172
|
function buildTmuxDefaults() {
|
|
@@ -4882,7 +5278,7 @@ source-file -q ${sisyphusConf} ${SISYPHUS_DEFAULTS_MARKER}
|
|
|
4882
5278
|
`;
|
|
4883
5279
|
}
|
|
4884
5280
|
function writeTmuxDefaults() {
|
|
4885
|
-
const confPath =
|
|
5281
|
+
const confPath = join16(homedir9(), ".tmux.conf");
|
|
4886
5282
|
writeFileSync8(confPath, buildTmuxDefaults(), "utf8");
|
|
4887
5283
|
}
|
|
4888
5284
|
function isNvimAvailable() {
|
|
@@ -4901,19 +5297,19 @@ function getNvimVersion() {
|
|
|
4901
5297
|
}
|
|
4902
5298
|
}
|
|
4903
5299
|
function hasLazyVimConfig() {
|
|
4904
|
-
return
|
|
5300
|
+
return existsSync15(join16(homedir9(), ".config", "nvim", "lazy-lock.json"));
|
|
4905
5301
|
}
|
|
4906
5302
|
function bundledBaleiaPluginPath() {
|
|
4907
5303
|
const distDir = dirname5(fileURLToPath2(import.meta.url));
|
|
4908
|
-
return
|
|
5304
|
+
return join16(distDir, "templates", "baleia.lua");
|
|
4909
5305
|
}
|
|
4910
5306
|
function installBaleiaPlugin() {
|
|
4911
|
-
const pluginsDir =
|
|
4912
|
-
if (!
|
|
4913
|
-
const dest =
|
|
4914
|
-
if (
|
|
5307
|
+
const pluginsDir = join16(homedir9(), ".config", "nvim", "lua", "plugins");
|
|
5308
|
+
if (!existsSync15(pluginsDir)) return false;
|
|
5309
|
+
const dest = join16(pluginsDir, "sisyphus-baleia.lua");
|
|
5310
|
+
if (existsSync15(dest)) return true;
|
|
4915
5311
|
const src = bundledBaleiaPluginPath();
|
|
4916
|
-
if (!
|
|
5312
|
+
if (!existsSync15(src)) return false;
|
|
4917
5313
|
try {
|
|
4918
5314
|
writeFileSync8(dest, readFileSync18(src, "utf-8"), "utf8");
|
|
4919
5315
|
return true;
|
|
@@ -4938,9 +5334,9 @@ function tryAutoInstallNvim() {
|
|
|
4938
5334
|
if (!isNvimAvailable()) {
|
|
4939
5335
|
return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false, baleiaInstalled: false };
|
|
4940
5336
|
}
|
|
4941
|
-
const nvimConfigDir =
|
|
5337
|
+
const nvimConfigDir = join16(homedir9(), ".config", "nvim");
|
|
4942
5338
|
let lazyVimInstalled = false;
|
|
4943
|
-
if (!
|
|
5339
|
+
if (!existsSync15(nvimConfigDir)) {
|
|
4944
5340
|
const cloneCmd = [
|
|
4945
5341
|
"git",
|
|
4946
5342
|
"-c core.hooksPath=/dev/null",
|
|
@@ -4956,8 +5352,8 @@ function tryAutoInstallNvim() {
|
|
|
4956
5352
|
stdio: "inherit",
|
|
4957
5353
|
env: { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" }
|
|
4958
5354
|
});
|
|
4959
|
-
const gitDir =
|
|
4960
|
-
if (
|
|
5355
|
+
const gitDir = join16(nvimConfigDir, ".git");
|
|
5356
|
+
if (existsSync15(gitDir)) {
|
|
4961
5357
|
execSync9(`rm -rf "${gitDir}"`, { stdio: "pipe" });
|
|
4962
5358
|
}
|
|
4963
5359
|
lazyVimInstalled = true;
|
|
@@ -5128,7 +5524,7 @@ function printResults(result, daemonOk, keybindMsg) {
|
|
|
5128
5524
|
console.log("");
|
|
5129
5525
|
}
|
|
5130
5526
|
function registerSetup(program2) {
|
|
5131
|
-
program2.command("setup").description("One-time setup: install dependencies, daemon, keybindings, and commands").option("-y, --yes", "Skip
|
|
5527
|
+
program2.command("setup").description("One-time setup: install dependencies, daemon, keybindings, and commands").option("-y, --yes", "Skip the y/N prompt before modifying ~/.tmux.conf").option("-f, --force", "Override safety refusals: overwrite existing key bindings AND auto-append source-file to ~/.tmux.conf").action(async (opts) => {
|
|
5132
5528
|
const result = runOnboarding();
|
|
5133
5529
|
let daemonOk = false;
|
|
5134
5530
|
try {
|
|
@@ -5140,11 +5536,15 @@ function registerSetup(program2) {
|
|
|
5140
5536
|
const keybindResult = await setupTmuxKeybind(
|
|
5141
5537
|
DEFAULT_CYCLE_KEY,
|
|
5142
5538
|
DEFAULT_PREFIX_KEY,
|
|
5143
|
-
{ assumeYes: opts.yes }
|
|
5539
|
+
{ assumeYes: opts.yes, force: opts.force }
|
|
5144
5540
|
);
|
|
5145
5541
|
let keybindMsg;
|
|
5146
5542
|
if (keybindResult.status === "installed" || keybindResult.status === "already-installed") {
|
|
5147
5543
|
keybindMsg = `${DEFAULT_CYCLE_KEY} cycle, ${DEFAULT_PREFIX_KEY} prefix (h=dashboard, x=kill)`;
|
|
5544
|
+
} else if (keybindResult.status === "requires-force" || keybindResult.status === "conflict") {
|
|
5545
|
+
keybindMsg = `${keybindResult.message}
|
|
5546
|
+
Run "sis admin check-keybinds" for the full decision tree, then re-run "sis admin setup --force" to proceed.`;
|
|
5547
|
+
process.exitCode = 1;
|
|
5148
5548
|
} else {
|
|
5149
5549
|
keybindMsg = keybindResult.message;
|
|
5150
5550
|
}
|
|
@@ -5154,9 +5554,12 @@ function registerSetup(program2) {
|
|
|
5154
5554
|
|
|
5155
5555
|
// src/cli/commands/setup-keybind.ts
|
|
5156
5556
|
function registerSetupKeybind(program2) {
|
|
5157
|
-
program2.command("setup-keybind [cycle-key]").description("Install sisyphus tmux keybindings (default: M-s cycle, C-s prefix)").option("-y, --yes", "
|
|
5158
|
-
const resolvedKey = key
|
|
5159
|
-
const result = await setupTmuxKeybind(resolvedKey, void 0, {
|
|
5557
|
+
program2.command("setup-keybind [cycle-key]").description("Install sisyphus tmux keybindings (default: M-s cycle, C-s prefix)").option("-y, --yes", "Auto-accept the y/N prompt before appending source-file to ~/.tmux.conf").option("-f, --force", "Override safety refusals: overwrite existing key bindings AND auto-append the source-file line").action(async (key, opts) => {
|
|
5558
|
+
const resolvedKey = key === void 0 ? DEFAULT_CYCLE_KEY : key;
|
|
5559
|
+
const result = await setupTmuxKeybind(resolvedKey, void 0, {
|
|
5560
|
+
assumeYes: opts.yes,
|
|
5561
|
+
force: opts.force
|
|
5562
|
+
});
|
|
5160
5563
|
switch (result.status) {
|
|
5161
5564
|
case "installed":
|
|
5162
5565
|
console.log(result.message);
|
|
@@ -5166,28 +5569,602 @@ function registerSetupKeybind(program2) {
|
|
|
5166
5569
|
console.log(result.message);
|
|
5167
5570
|
break;
|
|
5168
5571
|
case "conflict":
|
|
5169
|
-
console.log(`Key ${
|
|
5572
|
+
console.log(`Key ${result.conflictKey} is already bound:`);
|
|
5170
5573
|
console.log(` ${result.existingBinding}`);
|
|
5171
5574
|
console.log("");
|
|
5172
|
-
console.log("
|
|
5173
|
-
console.log(" sis admin setup-keybind M-
|
|
5174
|
-
console.log("
|
|
5175
|
-
console.log(" sis admin setup-keybind
|
|
5575
|
+
console.log("Options:");
|
|
5576
|
+
console.log(" - Pick a different cycle key: sis admin setup-keybind M-w");
|
|
5577
|
+
console.log(' - Run "sis admin check-keybinds" for a full breakdown');
|
|
5578
|
+
console.log(" - Override and overwrite: sis admin setup-keybind --force");
|
|
5579
|
+
process.exitCode = 1;
|
|
5176
5580
|
break;
|
|
5177
5581
|
case "unsupported-tmux":
|
|
5178
5582
|
console.log(result.message);
|
|
5583
|
+
process.exitCode = 1;
|
|
5584
|
+
break;
|
|
5585
|
+
case "requires-force":
|
|
5586
|
+
console.log(result.message);
|
|
5587
|
+
console.log("");
|
|
5588
|
+
console.log('Run "sis admin check-keybinds" first if you want the full decision tree before deciding.');
|
|
5589
|
+
process.exitCode = 1;
|
|
5179
5590
|
break;
|
|
5180
5591
|
case "conf-modification-declined":
|
|
5181
5592
|
console.log(result.message);
|
|
5182
5593
|
console.log("");
|
|
5183
|
-
console.log("Re-run with --
|
|
5594
|
+
console.log("Re-run with --force to append automatically.");
|
|
5184
5595
|
break;
|
|
5185
5596
|
}
|
|
5186
5597
|
});
|
|
5187
5598
|
}
|
|
5188
5599
|
|
|
5189
|
-
// src/cli/commands/
|
|
5600
|
+
// src/cli/commands/check-keybinds.ts
|
|
5190
5601
|
import { execSync as execSync11 } from "child_process";
|
|
5602
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
5603
|
+
function isTmuxInstalled2() {
|
|
5604
|
+
try {
|
|
5605
|
+
execSync11("which tmux", { stdio: "pipe" });
|
|
5606
|
+
return true;
|
|
5607
|
+
} catch {
|
|
5608
|
+
return false;
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
function getTmuxVersion2() {
|
|
5612
|
+
try {
|
|
5613
|
+
return execSync11("tmux -V", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
5614
|
+
} catch {
|
|
5615
|
+
return null;
|
|
5616
|
+
}
|
|
5617
|
+
}
|
|
5618
|
+
function isTmuxServerRunning() {
|
|
5619
|
+
try {
|
|
5620
|
+
execSync11("tmux list-sessions", { stdio: ["pipe", "pipe", "pipe"] });
|
|
5621
|
+
return true;
|
|
5622
|
+
} catch {
|
|
5623
|
+
return false;
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
function getTmuxPrefix() {
|
|
5627
|
+
try {
|
|
5628
|
+
return execSync11("tmux show-options -gv prefix", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim() || null;
|
|
5629
|
+
} catch {
|
|
5630
|
+
return null;
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
function classifyKey(key, serverRunning) {
|
|
5634
|
+
if (!serverRunning) return { kind: "unbound" };
|
|
5635
|
+
const binding = getExistingBinding(key);
|
|
5636
|
+
if (binding === null) return { kind: "unbound" };
|
|
5637
|
+
if (isSisyphusBinding(binding)) return { kind: "sisyphus", binding };
|
|
5638
|
+
return { kind: "conflict", binding };
|
|
5639
|
+
}
|
|
5640
|
+
function runCheck() {
|
|
5641
|
+
const tmuxInstalled = isTmuxInstalled2();
|
|
5642
|
+
const tmuxVersion = tmuxInstalled ? getTmuxVersion2() : null;
|
|
5643
|
+
const tmuxVersionOk = tmuxInstalled ? tmuxVersionAtLeast(3, 2) : false;
|
|
5644
|
+
const tmuxServerRunning = tmuxInstalled ? isTmuxServerRunning() : false;
|
|
5645
|
+
const inTmux = !!process.env["TMUX"];
|
|
5646
|
+
const cycleKey = { key: DEFAULT_CYCLE_KEY, ...classifyKey(DEFAULT_CYCLE_KEY, tmuxServerRunning) };
|
|
5647
|
+
const prefixKey = { key: DEFAULT_PREFIX_KEY, ...classifyKey(DEFAULT_PREFIX_KEY, tmuxServerRunning) };
|
|
5648
|
+
const tmuxPrefix = tmuxServerRunning ? getTmuxPrefix() : null;
|
|
5649
|
+
const prefixCollision = tmuxPrefix !== null && tmuxPrefix === DEFAULT_PREFIX_KEY;
|
|
5650
|
+
const sisyphusConfPath = sisyphusTmuxConfPath();
|
|
5651
|
+
const userConfPath = userTmuxConfPath();
|
|
5652
|
+
let userConfAlreadySources = false;
|
|
5653
|
+
if (userConfPath !== null) {
|
|
5654
|
+
try {
|
|
5655
|
+
userConfAlreadySources = readFileSync19(userConfPath, "utf-8").includes(sisyphusConfPath);
|
|
5656
|
+
} catch {
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
5659
|
+
return {
|
|
5660
|
+
tmuxInstalled,
|
|
5661
|
+
tmuxServerRunning,
|
|
5662
|
+
tmuxVersion,
|
|
5663
|
+
tmuxVersionOk,
|
|
5664
|
+
inTmux,
|
|
5665
|
+
cycleKey,
|
|
5666
|
+
prefixKey,
|
|
5667
|
+
tmuxPrefix,
|
|
5668
|
+
prefixCollision,
|
|
5669
|
+
userConfPath,
|
|
5670
|
+
userConfAlreadySources,
|
|
5671
|
+
sisyphusConfPath
|
|
5672
|
+
};
|
|
5673
|
+
}
|
|
5674
|
+
function fmtKeyState(k) {
|
|
5675
|
+
switch (k.kind) {
|
|
5676
|
+
case "sisyphus":
|
|
5677
|
+
return `${k.key}: sisyphus (${k.binding})`;
|
|
5678
|
+
case "conflict":
|
|
5679
|
+
return `${k.key}: CONFLICT \u2014 already bound: ${k.binding}`;
|
|
5680
|
+
case "unbound":
|
|
5681
|
+
return `${k.key}: unbound`;
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
function printInstructions(r) {
|
|
5685
|
+
const bothSisyphus = r.cycleKey.kind === "sisyphus" && r.prefixKey.kind === "sisyphus";
|
|
5686
|
+
const anyConflict = r.cycleKey.kind === "conflict" || r.prefixKey.kind === "conflict";
|
|
5687
|
+
const wouldModifyUserConf = r.userConfPath !== null && !r.userConfAlreadySources;
|
|
5688
|
+
const safeAutoInstall = !anyConflict && !r.prefixCollision && !wouldModifyUserConf;
|
|
5689
|
+
const tmuxVersionLabel = r.tmuxVersion === null ? "(none)" : r.tmuxVersion;
|
|
5690
|
+
const tmuxPrefixLabel = r.tmuxPrefix === null ? "(default C-b)" : r.tmuxPrefix;
|
|
5691
|
+
const userConfLabel = r.userConfPath === null ? "(none found)" : r.userConfPath;
|
|
5692
|
+
const userConfForCopy = r.userConfPath === null ? "~/.tmux.conf" : r.userConfPath;
|
|
5693
|
+
console.log(`
|
|
5694
|
+
<claude-instructions>
|
|
5695
|
+
# Sisyphus Keybind Precheck
|
|
5696
|
+
|
|
5697
|
+
Run this BEFORE asking the user to try any sisyphus keybind (e.g. \`Right Option+s\`,
|
|
5698
|
+
\`C-s h\`, \`C-s ?\`). If the keybinds aren't actually wired up, the user will press
|
|
5699
|
+
the key and nothing will happen \u2014 confusing, looks broken.
|
|
5700
|
+
|
|
5701
|
+
## Environment Data
|
|
5702
|
+
- tmuxInstalled: ${r.tmuxInstalled}
|
|
5703
|
+
- tmuxVersion: ${tmuxVersionLabel}
|
|
5704
|
+
- tmuxVersionOk: ${r.tmuxVersionOk} (sisyphus needs 3.2+ for display-menu)
|
|
5705
|
+
- tmuxServerRunning: ${r.tmuxServerRunning}
|
|
5706
|
+
- inTmux ($TMUX set): ${r.inTmux}
|
|
5707
|
+
- ${fmtKeyState(r.cycleKey)}
|
|
5708
|
+
- ${fmtKeyState(r.prefixKey)}
|
|
5709
|
+
- tmuxPrefix (\`set -g prefix\`): ${tmuxPrefixLabel}
|
|
5710
|
+
- prefixCollision: ${r.prefixCollision} (true = user's tmux prefix is ${DEFAULT_PREFIX_KEY}; binding it in root would shadow it)
|
|
5711
|
+
- userConfPath: ${userConfLabel}
|
|
5712
|
+
- userConfAlreadySources: ${r.userConfAlreadySources}
|
|
5713
|
+
- sisyphusConfPath: ${r.sisyphusConfPath}
|
|
5714
|
+
|
|
5715
|
+
## Decision
|
|
5716
|
+
|
|
5717
|
+
Pick exactly one path. Do NOT proceed past this check until both keys read "sisyphus".
|
|
5718
|
+
|
|
5719
|
+
### Path A \u2014 All wired, proceed
|
|
5720
|
+
**Trigger:** cycleKey is "sisyphus" AND prefixKey is "sisyphus".
|
|
5721
|
+
**Action:** ${bothSisyphus ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} Confirm briefly and continue with the onboarding step that needs keybinds.
|
|
5722
|
+
|
|
5723
|
+
### Path B \u2014 Safe to auto-install
|
|
5724
|
+
**Trigger:** No conflicts, no prefix collision, AND (no user tmux.conf OR it already sources sisyphus).
|
|
5725
|
+
**Action:** ${safeAutoInstall && !bothSisyphus ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} Run:
|
|
5726
|
+
\`\`\`
|
|
5727
|
+
sis admin setup-keybind --yes
|
|
5728
|
+
\`\`\`
|
|
5729
|
+
This installs the helper scripts in ~/.sisyphus/bin/ and applies the bindings to the live
|
|
5730
|
+
tmux server. No user files are clobbered (none to clobber, or already wired).
|
|
5731
|
+
|
|
5732
|
+
### Path C \u2014 Would append a line to the user's tmux.conf
|
|
5733
|
+
**Trigger:** No conflicts, no prefix collision, but ${userConfForCopy} exists and doesn't yet source ${r.sisyphusConfPath}.
|
|
5734
|
+
**Action:** ${!safeAutoInstall && !anyConflict && !r.prefixCollision && wouldModifyUserConf && !bothSisyphus ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} Ask the user to choose:
|
|
5735
|
+
|
|
5736
|
+
1. **Persistent (recommended).** "I'll add one line to ${userConfForCopy} so the bindings survive tmux restarts. The line is marked \`# sisyphus-managed \u2014 do not edit\` and is grep-removable later."
|
|
5737
|
+
Run: \`sis admin setup-keybind --yes\`
|
|
5738
|
+
|
|
5739
|
+
2. **Live only, no file changes.** "I'll wire the bindings into your current tmux server without touching any config file. They'll vanish when you restart tmux, and you can re-run \`sis admin setup-keybind\` anytime to make them stick."
|
|
5740
|
+
Run: \`sis admin setup-keybind\` (no --yes; non-TTY auto-declines the conf prompt while still applying live bindings + installing helper scripts)
|
|
5741
|
+
|
|
5742
|
+
### Path D \u2014 Conflict on ${DEFAULT_CYCLE_KEY} or ${DEFAULT_PREFIX_KEY}
|
|
5743
|
+
**Trigger:** cycleKey or prefixKey is "CONFLICT".
|
|
5744
|
+
**Action:** ${anyConflict ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} The user already has these keys bound to something else. Show them the conflicting bindings (above) and offer:
|
|
5745
|
+
|
|
5746
|
+
1. **Pick alternate keys.** Re-run with a different cycle key \u2014 e.g. \`M-S\`, \`M-w\`, \`M-j\`, \`M-\\\`\`:
|
|
5747
|
+
\`\`\`
|
|
5748
|
+
sis admin setup-keybind M-w
|
|
5749
|
+
\`\`\`
|
|
5750
|
+
This still uses C-s for the prefix; if the prefix also conflicts, you'll need to wire
|
|
5751
|
+
directly (option 3 below), since setup-keybind only takes a custom cycle key.
|
|
5752
|
+
|
|
5753
|
+
2. **Skip keybinds entirely.** The user can drive sisyphus from the CLI: \`sis dashboard\`,
|
|
5754
|
+
\`sis status\`, \`sis start\`, \`sis session resume\`. Lose tmux quick-actions, keep
|
|
5755
|
+
existing bindings.
|
|
5756
|
+
|
|
5757
|
+
3. **Wire commands directly (advanced).** Bypass setup-keybind and bind individual
|
|
5758
|
+
sisyphus actions to keys the user picks. Helper scripts must already exist \u2014 if
|
|
5759
|
+
~/.sisyphus/bin/sisyphus-cycle is missing, you cannot use this path until setup-keybind
|
|
5760
|
+
has run at least once successfully (try option 1 first).
|
|
5761
|
+
\`\`\`
|
|
5762
|
+
# cycle sessions on a key the user chooses (replace M-w):
|
|
5763
|
+
tmux bind-key -T root M-w run-shell "$HOME/.sisyphus/bin/sisyphus-cycle"
|
|
5764
|
+
|
|
5765
|
+
# open the dashboard directly on a key (replace M-h):
|
|
5766
|
+
tmux bind-key -T root M-h run-shell "$HOME/.sisyphus/bin/sisyphus-home"
|
|
5767
|
+
\`\`\`
|
|
5768
|
+
These apply only to the running tmux server. To persist, append the same lines to
|
|
5769
|
+
${userConfForCopy}.
|
|
5770
|
+
|
|
5771
|
+
### Path E \u2014 Hidden prefix collision
|
|
5772
|
+
**Trigger:** prefixCollision is true (the user's \`set -g prefix\` is already ${DEFAULT_PREFIX_KEY}).
|
|
5773
|
+
**Action:** ${r.prefixCollision ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} \`tmux list-keys\` won't surface this as a binding conflict, but installing the C-s root-table menu would shadow the user's prefix. Tell the user:
|
|
5774
|
+
|
|
5775
|
+
> "Your tmux prefix is set to ${DEFAULT_PREFIX_KEY}. Sisyphus wants to bind ${DEFAULT_PREFIX_KEY} in the root table for its menu, which would shadow your prefix. Options: (a) move your prefix (e.g. \`set -g prefix C-a\`) and let sisyphus take ${DEFAULT_PREFIX_KEY}, or (b) skip the menu binding \u2014 only \`${DEFAULT_CYCLE_KEY}\` cycle gets installed."
|
|
5776
|
+
|
|
5777
|
+
For (b), wire just the cycle key directly:
|
|
5778
|
+
\`\`\`
|
|
5779
|
+
sis admin setup-keybind ${DEFAULT_CYCLE_KEY}
|
|
5780
|
+
# then unbind C-s if setup-keybind ended up taking it:
|
|
5781
|
+
tmux unbind-key -T root ${DEFAULT_PREFIX_KEY}
|
|
5782
|
+
\`\`\`
|
|
5783
|
+
|
|
5784
|
+
### Path F \u2014 tmux not ready
|
|
5785
|
+
**Trigger:** any of: tmuxInstalled=false, tmuxVersionOk=false, tmuxServerRunning=false.
|
|
5786
|
+
**Action:** ${!r.tmuxInstalled || !r.tmuxVersionOk || !r.tmuxServerRunning ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} Don't install keybinds yet. Fix the precondition:
|
|
5787
|
+
- tmuxInstalled=false \u2192 \`brew install tmux\` (macOS) or your package manager
|
|
5788
|
+
- tmuxVersionOk=false \u2192 upgrade tmux to 3.2+
|
|
5789
|
+
- tmuxServerRunning=false \u2192 user needs to run \`tmux\` (or attach to an existing session) before live bindings can be installed or tested
|
|
5790
|
+
|
|
5791
|
+
After fixing, re-run \`sis admin check-keybinds\`.
|
|
5792
|
+
|
|
5793
|
+
## After acting
|
|
5794
|
+
Re-run \`sis admin check-keybinds\` to confirm both keys read "sisyphus", THEN ask the
|
|
5795
|
+
user to try the keybind. Don't skip the verification \u2014 \`setup-keybind\` can fail silently
|
|
5796
|
+
if the tmux server dies between commands.
|
|
5797
|
+
</claude-instructions>
|
|
5798
|
+
`);
|
|
5799
|
+
}
|
|
5800
|
+
function registerCheckKeybinds(program2) {
|
|
5801
|
+
program2.command("check-keybinds").description("Verify tmux keybind state before asking the user to try a sisyphus keybind").option("--json", "Print raw JSON state instead of Claude instructions").action((opts) => {
|
|
5802
|
+
const result = runCheck();
|
|
5803
|
+
if (opts.json) {
|
|
5804
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5805
|
+
return;
|
|
5806
|
+
}
|
|
5807
|
+
printInstructions(result);
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5811
|
+
// src/cli/commands/check-statusbar.ts
|
|
5812
|
+
init_paths();
|
|
5813
|
+
import { execSync as execSync12 } from "child_process";
|
|
5814
|
+
import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
|
|
5815
|
+
import { homedir as homedir10 } from "os";
|
|
5816
|
+
import { join as join17 } from "path";
|
|
5817
|
+
var SISYPHUS_LEFT_TOKEN = "@sisyphus_left";
|
|
5818
|
+
var SISYPHUS_RIGHT_TOKEN = "@sisyphus_right";
|
|
5819
|
+
var TMUX_DEFAULT_STATUS_LEFT = "[#S] ";
|
|
5820
|
+
var TMUX_DEFAULT_STATUS_RIGHT_PREFIX = '"#{=21:pane_title}"';
|
|
5821
|
+
function isTmuxInstalled3() {
|
|
5822
|
+
try {
|
|
5823
|
+
execSync12("which tmux", { stdio: "pipe" });
|
|
5824
|
+
return true;
|
|
5825
|
+
} catch {
|
|
5826
|
+
return false;
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
function isTmuxServerRunning2() {
|
|
5830
|
+
try {
|
|
5831
|
+
execSync12("tmux list-sessions", { stdio: ["pipe", "pipe", "pipe"] });
|
|
5832
|
+
return true;
|
|
5833
|
+
} catch {
|
|
5834
|
+
return false;
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
function isDaemonRunning() {
|
|
5838
|
+
const pidFile = daemonPidPath();
|
|
5839
|
+
if (!existsSync16(pidFile)) return false;
|
|
5840
|
+
try {
|
|
5841
|
+
const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
|
|
5842
|
+
if (Number.isNaN(pid) || pid <= 0) return false;
|
|
5843
|
+
process.kill(pid, 0);
|
|
5844
|
+
return true;
|
|
5845
|
+
} catch {
|
|
5846
|
+
return false;
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
function showOption(name) {
|
|
5850
|
+
try {
|
|
5851
|
+
const out = execSync12(`tmux show-options -g ${name}`, { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
5852
|
+
if (out.length === 0) return null;
|
|
5853
|
+
const prefix = `${name} `;
|
|
5854
|
+
const stripped = out.startsWith(prefix) ? out.slice(prefix.length) : out;
|
|
5855
|
+
if (stripped.startsWith('"') && stripped.endsWith('"') && stripped.length >= 2) {
|
|
5856
|
+
return stripped.slice(1, -1);
|
|
5857
|
+
}
|
|
5858
|
+
return stripped;
|
|
5859
|
+
} catch {
|
|
5860
|
+
return null;
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
function probeTmuxOptions(serverRunning) {
|
|
5864
|
+
if (!serverRunning) {
|
|
5865
|
+
return {
|
|
5866
|
+
status: null,
|
|
5867
|
+
statusLeft: null,
|
|
5868
|
+
statusRight: null,
|
|
5869
|
+
statusPosition: null,
|
|
5870
|
+
statusStyle: null,
|
|
5871
|
+
statusInterval: null
|
|
5872
|
+
};
|
|
5873
|
+
}
|
|
5874
|
+
return {
|
|
5875
|
+
status: showOption("status"),
|
|
5876
|
+
statusLeft: showOption("status-left"),
|
|
5877
|
+
statusRight: showOption("status-right"),
|
|
5878
|
+
statusPosition: showOption("status-position"),
|
|
5879
|
+
statusStyle: showOption("status-style"),
|
|
5880
|
+
statusInterval: showOption("status-interval")
|
|
5881
|
+
};
|
|
5882
|
+
}
|
|
5883
|
+
function findUserTmuxConf() {
|
|
5884
|
+
const xdg = join17(homedir10(), ".config", "tmux", "tmux.conf");
|
|
5885
|
+
const dotfile = join17(homedir10(), ".tmux.conf");
|
|
5886
|
+
if (existsSync16(xdg)) return xdg;
|
|
5887
|
+
if (existsSync16(dotfile)) return dotfile;
|
|
5888
|
+
return null;
|
|
5889
|
+
}
|
|
5890
|
+
function probeUserConf() {
|
|
5891
|
+
const path = findUserTmuxConf();
|
|
5892
|
+
if (path === null) {
|
|
5893
|
+
return { path: null, setsStatusLeft: false, setsStatusRight: false, sourcesSisyphusManaged: false };
|
|
5894
|
+
}
|
|
5895
|
+
let contents = "";
|
|
5896
|
+
try {
|
|
5897
|
+
contents = readFileSync20(path, "utf-8");
|
|
5898
|
+
} catch {
|
|
5899
|
+
return { path, setsStatusLeft: false, setsStatusRight: false, sourcesSisyphusManaged: false };
|
|
5900
|
+
}
|
|
5901
|
+
const lines = contents.split("\n").filter((line) => !line.trim().startsWith("#"));
|
|
5902
|
+
const setsStatusLeft = lines.some((line) => /^\s*(set|set-option)\s+-g(?:\s+-\w+)*\s+status-left\b/.test(line));
|
|
5903
|
+
const setsStatusRight = lines.some((line) => /^\s*(set|set-option)\s+-g(?:\s+-\w+)*\s+status-right\b/.test(line));
|
|
5904
|
+
const sourcesSisyphusManaged = contents.includes(join17(homedir10(), ".sisyphus", "tmux.conf"));
|
|
5905
|
+
return { path, setsStatusLeft, setsStatusRight, sourcesSisyphusManaged };
|
|
5906
|
+
}
|
|
5907
|
+
function loadGlobalSisyphusConfig() {
|
|
5908
|
+
const path = globalConfigPath();
|
|
5909
|
+
if (!existsSync16(path)) return null;
|
|
5910
|
+
try {
|
|
5911
|
+
const parsed = JSON.parse(readFileSync20(path, "utf-8"));
|
|
5912
|
+
return parsed.statusBar === void 0 ? null : parsed.statusBar;
|
|
5913
|
+
} catch {
|
|
5914
|
+
return null;
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5917
|
+
function classifyState(opts, serverRunning) {
|
|
5918
|
+
if (!serverRunning) {
|
|
5919
|
+
return { state: "tmux-not-ready", referencesSisyphusLeft: false, referencesSisyphusRight: false };
|
|
5920
|
+
}
|
|
5921
|
+
if (opts.status === "off") {
|
|
5922
|
+
return { state: "disabled", referencesSisyphusLeft: false, referencesSisyphusRight: false };
|
|
5923
|
+
}
|
|
5924
|
+
const referencesSisyphusLeft = opts.statusLeft !== null && opts.statusLeft.includes(SISYPHUS_LEFT_TOKEN);
|
|
5925
|
+
const referencesSisyphusRight = opts.statusRight !== null && opts.statusRight.includes(SISYPHUS_RIGHT_TOKEN);
|
|
5926
|
+
if (referencesSisyphusLeft && referencesSisyphusRight) {
|
|
5927
|
+
return { state: "wired", referencesSisyphusLeft, referencesSisyphusRight };
|
|
5928
|
+
}
|
|
5929
|
+
if (referencesSisyphusLeft) {
|
|
5930
|
+
return { state: "partial-left-only", referencesSisyphusLeft, referencesSisyphusRight };
|
|
5931
|
+
}
|
|
5932
|
+
if (referencesSisyphusRight) {
|
|
5933
|
+
return { state: "partial-right-only", referencesSisyphusLeft, referencesSisyphusRight };
|
|
5934
|
+
}
|
|
5935
|
+
const isStock = (opts.statusLeft === TMUX_DEFAULT_STATUS_LEFT || opts.statusLeft === null) && (opts.statusRight === null || opts.statusRight.includes(TMUX_DEFAULT_STATUS_RIGHT_PREFIX));
|
|
5936
|
+
return {
|
|
5937
|
+
state: isStock ? "tmux-default" : "custom-no-sisyphus",
|
|
5938
|
+
referencesSisyphusLeft,
|
|
5939
|
+
referencesSisyphusRight
|
|
5940
|
+
};
|
|
5941
|
+
}
|
|
5942
|
+
function runCheck2() {
|
|
5943
|
+
const tmuxInstalled = isTmuxInstalled3();
|
|
5944
|
+
const tmuxServerRunning = tmuxInstalled ? isTmuxServerRunning2() : false;
|
|
5945
|
+
const tmuxOptions = probeTmuxOptions(tmuxServerRunning);
|
|
5946
|
+
const userConf = probeUserConf();
|
|
5947
|
+
const globalConfig = loadGlobalSisyphusConfig();
|
|
5948
|
+
const daemonRunning = isDaemonRunning();
|
|
5949
|
+
const { state, referencesSisyphusLeft, referencesSisyphusRight } = classifyState(tmuxOptions, tmuxServerRunning);
|
|
5950
|
+
return {
|
|
5951
|
+
tmuxInstalled,
|
|
5952
|
+
tmuxServerRunning,
|
|
5953
|
+
daemonRunning,
|
|
5954
|
+
tmuxOptions,
|
|
5955
|
+
userConf,
|
|
5956
|
+
globalConfig,
|
|
5957
|
+
state,
|
|
5958
|
+
referencesSisyphusLeft,
|
|
5959
|
+
referencesSisyphusRight
|
|
5960
|
+
};
|
|
5961
|
+
}
|
|
5962
|
+
function fmtOption(value) {
|
|
5963
|
+
if (value === null) return "(unset)";
|
|
5964
|
+
return JSON.stringify(value);
|
|
5965
|
+
}
|
|
5966
|
+
function renderConfigSummary(cfg) {
|
|
5967
|
+
if (cfg === null) return "(no statusBar block in ~/.sisyphus/config.json \u2014 defaults apply)";
|
|
5968
|
+
const parts = [];
|
|
5969
|
+
if (cfg.enabled === false) parts.push("enabled: false (DISABLED)");
|
|
5970
|
+
if (cfg.left !== void 0) parts.push(`left: [${cfg.left.join(", ")}]`);
|
|
5971
|
+
if (cfg.right !== void 0) parts.push(`right: [${cfg.right.join(", ")}]`);
|
|
5972
|
+
if (cfg.colors !== void 0) parts.push(`colors: ${JSON.stringify(cfg.colors)}`);
|
|
5973
|
+
if (cfg.segments !== void 0) {
|
|
5974
|
+
const segNames = Object.keys(cfg.segments);
|
|
5975
|
+
if (segNames.length > 0) parts.push(`per-segment overrides: ${segNames.join(", ")}`);
|
|
5976
|
+
}
|
|
5977
|
+
if (parts.length === 0) return "(statusBar block exists but is empty \u2014 defaults apply)";
|
|
5978
|
+
return parts.join("\n ");
|
|
5979
|
+
}
|
|
5980
|
+
function printInstructions2(r) {
|
|
5981
|
+
const userConfPath = r.userConf.path === null ? "~/.tmux.conf (none found)" : r.userConf.path;
|
|
5982
|
+
const userConfForCopy = r.userConf.path === null ? "~/.tmux.conf" : r.userConf.path;
|
|
5983
|
+
console.log(`
|
|
5984
|
+
<claude-instructions>
|
|
5985
|
+
# Sisyphus Statusbar Helper
|
|
5986
|
+
|
|
5987
|
+
This is a READ-ONLY check. Use it to figure out how to help the user improve their
|
|
5988
|
+
tmux statusbar with sisyphus segments WITHOUT clobbering their existing setup. The
|
|
5989
|
+
user's tmux config is theirs \u2014 never overwrite it. Append, suggest snippets, or
|
|
5990
|
+
nudge them to edit ~/.sisyphus/config.json.
|
|
5991
|
+
|
|
5992
|
+
## Environment Data
|
|
5993
|
+
- tmuxInstalled: ${r.tmuxInstalled}
|
|
5994
|
+
- tmuxServerRunning: ${r.tmuxServerRunning}
|
|
5995
|
+
- daemonRunning: ${r.daemonRunning} (false = @sisyphus_left/@sisyphus_right won't get populated, statusbar will look broken until daemon is up)
|
|
5996
|
+
|
|
5997
|
+
### Live tmux options
|
|
5998
|
+
- status: ${fmtOption(r.tmuxOptions.status)}
|
|
5999
|
+
- status-left: ${fmtOption(r.tmuxOptions.statusLeft)}
|
|
6000
|
+
- status-right: ${fmtOption(r.tmuxOptions.statusRight)}
|
|
6001
|
+
- status-position: ${fmtOption(r.tmuxOptions.statusPosition)}
|
|
6002
|
+
- status-style: ${fmtOption(r.tmuxOptions.statusStyle)}
|
|
6003
|
+
- status-interval: ${fmtOption(r.tmuxOptions.statusInterval)}
|
|
6004
|
+
- referencesSisyphusLeft: ${r.referencesSisyphusLeft}
|
|
6005
|
+
- referencesSisyphusRight: ${r.referencesSisyphusRight}
|
|
6006
|
+
|
|
6007
|
+
### User tmux config
|
|
6008
|
+
- path: ${userConfPath}
|
|
6009
|
+
- setsStatusLeft: ${r.userConf.setsStatusLeft}
|
|
6010
|
+
- setsStatusRight: ${r.userConf.setsStatusRight}
|
|
6011
|
+
- sourcesSisyphusManaged: ${r.userConf.sourcesSisyphusManaged} (sources ~/.sisyphus/tmux.conf \u2014 keybinds, NOT statusbar; included for context)
|
|
6012
|
+
|
|
6013
|
+
### Sisyphus statusbar config (~/.sisyphus/config.json \u2192 statusBar)
|
|
6014
|
+
${renderConfigSummary(r.globalConfig)}
|
|
6015
|
+
|
|
6016
|
+
### Available segments
|
|
6017
|
+
- session-name (left default) \u2014 current tmux session name
|
|
6018
|
+
- windows (left default) \u2014 tmux window list with active highlight
|
|
6019
|
+
- sessions (right default) \u2014 all tmux sessions with claude-state colors
|
|
6020
|
+
- sisyphus-sessions (right default) \u2014 sisyphus-managed sessions with phase indicators
|
|
6021
|
+
- companion (right default) \u2014 companion mood/state pill
|
|
6022
|
+
- clock \u2014 separate %H:%M (NOT a sisyphus segment; tmux renders it inline; the default
|
|
6023
|
+
~/.tmux.conf appends \`#[fg=...]#[bg=...] %H:%M \` after #{E:@sisyphus_right})
|
|
6024
|
+
|
|
6025
|
+
## Detected state: **${r.state}**
|
|
6026
|
+
|
|
6027
|
+
Pick exactly one path. Each path tells you what to ask the user and what to do.
|
|
6028
|
+
|
|
6029
|
+
### Path A \u2014 Already wired (state: wired)
|
|
6030
|
+
**Trigger:** status-left and status-right both reference @sisyphus_left/@sisyphus_right.
|
|
6031
|
+
**Action:** ${r.state === "wired" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6032
|
+
|
|
6033
|
+
The plumbing is correct. ${r.daemonRunning ? "" : "BUT daemon is not running \u2014 run `sisyphusd start` (or restart) so the @sisyphus_* options get populated. "}Now offer to **customize** what's shown:
|
|
6034
|
+
|
|
6035
|
+
1. **Reorder or hide segments.** Edit \`~/.sisyphus/config.json\`, set \`statusBar.left\` / \`statusBar.right\` to the array of segment ids you want (in order). Example to hide windows and reorder:
|
|
6036
|
+
\`\`\`json
|
|
6037
|
+
{
|
|
6038
|
+
"statusBar": {
|
|
6039
|
+
"left": ["session-name"],
|
|
6040
|
+
"right": ["sisyphus-sessions", "companion"]
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
\`\`\`
|
|
6044
|
+
2. **Recolor.** \`statusBar.colors\` overrides processing/stopped/idle/activeBg/activeText/inactiveText. \`statusBar.segments.<id>.bg\` overrides per-segment band background.
|
|
6045
|
+
3. **Disable a single segment.** Remove its id from the left/right arrays.
|
|
6046
|
+
4. **Disable the whole bar.** \`statusBar.enabled: false\` \u2014 daemon stops writing the options; the user's status-left/right will then render as literal \`#{E:@sisyphus_left}\` (which tmux silently treats as empty).
|
|
6047
|
+
5. **Restart the daemon** after any config change: \`sisyphusd restart\`. Changes don't auto-apply.
|
|
6048
|
+
|
|
6049
|
+
Ask the user what they want to change. Edit \`~/.sisyphus/config.json\` for them \u2014 that file is sisyphus-managed config, safe to edit. Don't touch their \`~/.tmux.conf\`.
|
|
6050
|
+
|
|
6051
|
+
### Path B \u2014 Partial wiring (state: partial-left-only or partial-right-only)
|
|
6052
|
+
**Trigger:** Only one side references sisyphus.
|
|
6053
|
+
**Action:** ${r.state === "partial-left-only" || r.state === "partial-right-only" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6054
|
+
|
|
6055
|
+
Their config is half-wired. Show them the missing side:
|
|
6056
|
+
- Missing left \u2192 suggest adding \`set -g status-left "#{E:@sisyphus_left}"\` to ${userConfForCopy}
|
|
6057
|
+
- Missing right \u2192 suggest adding \`set -g status-right "#{E:@sisyphus_right}#[fg=#2d2f33]#[bg=#2d2f33,fg=#b0a898] %H:%M "\`
|
|
6058
|
+
|
|
6059
|
+
Don't auto-edit their config \u2014 present the snippet and ask if they want you to append it.
|
|
6060
|
+
|
|
6061
|
+
### Path C \u2014 Stock tmux statusbar (state: tmux-default)
|
|
6062
|
+
**Trigger:** status-left/right are tmux defaults; user has no custom statusbar.
|
|
6063
|
+
**Action:** ${r.state === "tmux-default" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6064
|
+
|
|
6065
|
+
Easiest case. Two options to offer:
|
|
6066
|
+
|
|
6067
|
+
1. **Full sisyphus statusbar.** Append these lines to ${userConfForCopy} (NOT a clobber \u2014 pure additive):
|
|
6068
|
+
\`\`\`tmux
|
|
6069
|
+
# --- Sisyphus statusbar ---
|
|
6070
|
+
set -g status on
|
|
6071
|
+
set -g status-style "bg=#1d1e21,fg=#d4cbb8"
|
|
6072
|
+
set -g status-position bottom
|
|
6073
|
+
set -g status-left "#{E:@sisyphus_left}"
|
|
6074
|
+
set -g status-left-length 250
|
|
6075
|
+
set -g status-right "#{E:@sisyphus_right}#[fg=#2d2f33]#[bg=#2d2f33,fg=#b0a898] %H:%M "
|
|
6076
|
+
set -g status-right-length 250
|
|
6077
|
+
set -g status-interval 2
|
|
6078
|
+
set -g window-status-format ""
|
|
6079
|
+
set -g window-status-current-format ""
|
|
6080
|
+
set -g window-status-separator ""
|
|
6081
|
+
\`\`\`
|
|
6082
|
+
${r.userConf.path === null ? `Note: no tmux config exists yet. Create ${userConfForCopy} with these lines.` : ""}
|
|
6083
|
+
|
|
6084
|
+
2. **Minimal sisyphus pill on the right.** If they want to keep the stock left side and just add a single sisyphus pill on the right:
|
|
6085
|
+
\`\`\`tmux
|
|
6086
|
+
set -g status-right "#{E:@sisyphus_right}#[default] %H:%M "
|
|
6087
|
+
set -g status-right-length 200
|
|
6088
|
+
\`\`\`
|
|
6089
|
+
|
|
6090
|
+
After appending, run \`tmux source-file ${userConfForCopy}\` so the change takes effect immediately.
|
|
6091
|
+
|
|
6092
|
+
### Path D \u2014 User has a custom statusbar (state: custom-no-sisyphus)
|
|
6093
|
+
**Trigger:** status-left/right are user-customized but don't reference sisyphus tokens.
|
|
6094
|
+
**Action:** ${r.state === "custom-no-sisyphus" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6095
|
+
|
|
6096
|
+
This is the most delicate case \u2014 they care enough to have customized their bar. **Do not overwrite.** Show them the current contents and offer additive integration:
|
|
6097
|
+
|
|
6098
|
+
Their current right side is currently:
|
|
6099
|
+
\`\`\`
|
|
6100
|
+
${r.tmuxOptions.statusRight === null ? "(unset)" : r.tmuxOptions.statusRight}
|
|
6101
|
+
\`\`\`
|
|
6102
|
+
And left side:
|
|
6103
|
+
\`\`\`
|
|
6104
|
+
${r.tmuxOptions.statusLeft === null ? "(unset)" : r.tmuxOptions.statusLeft}
|
|
6105
|
+
\`\`\`
|
|
6106
|
+
|
|
6107
|
+
Offer three integration patterns and let the user pick:
|
|
6108
|
+
|
|
6109
|
+
1. **Append sisyphus to the right side** (most common \u2014 leaves their left alone):
|
|
6110
|
+
\`\`\`tmux
|
|
6111
|
+
set -g status-right "${r.tmuxOptions.statusRight === null ? "" : r.tmuxOptions.statusRight}#{E:@sisyphus_right}"
|
|
6112
|
+
\`\`\`
|
|
6113
|
+
Or prepend it (sisyphus content appears first/leftmost):
|
|
6114
|
+
\`\`\`tmux
|
|
6115
|
+
set -g status-right "#{E:@sisyphus_right}${r.tmuxOptions.statusRight === null ? "" : r.tmuxOptions.statusRight}"
|
|
6116
|
+
\`\`\`
|
|
6117
|
+
|
|
6118
|
+
2. **Append to the left side** (if they want session/windows pills at the start):
|
|
6119
|
+
\`\`\`tmux
|
|
6120
|
+
set -g status-left "${r.tmuxOptions.statusLeft === null ? "" : r.tmuxOptions.statusLeft}#{E:@sisyphus_left}"
|
|
6121
|
+
\`\`\`
|
|
6122
|
+
|
|
6123
|
+
3. **Slim sisyphus only.** If they only want one specific signal (e.g., just sisyphus-sessions), they can set \`statusBar.right: ["sisyphus-sessions"]\` in \`~/.sisyphus/config.json\` and then append \`#{E:@sisyphus_right}\` to their existing right side. The composed string will only contain that one segment.
|
|
6124
|
+
|
|
6125
|
+
Important: the user's existing string may include format conditionals or special characters; use the exact value above when proposing the snippet (do not retype). Confirm with the user before editing ${userConfForCopy} \u2014 present the diff first.
|
|
6126
|
+
|
|
6127
|
+
### Path E \u2014 Statusbar is disabled (state: disabled)
|
|
6128
|
+
**Trigger:** \`set -g status off\`.
|
|
6129
|
+
**Action:** ${r.state === "disabled" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6130
|
+
|
|
6131
|
+
The user explicitly turned off the statusbar. Don't enable it without asking. Suggest:
|
|
6132
|
+
- "I noticed you have the statusbar disabled (\`set -g status off\` in your config). Sisyphus needs it on to show session state. Want me to enable it and add a minimal sisyphus pill, or leave it as-is?"
|
|
6133
|
+
|
|
6134
|
+
If they say yes \u2192 fall through to Path C, option 2 (minimal pill).
|
|
6135
|
+
|
|
6136
|
+
### Path F \u2014 tmux not ready (state: tmux-not-ready)
|
|
6137
|
+
**Trigger:** tmux isn't installed or no server is running.
|
|
6138
|
+
**Action:** ${r.state === "tmux-not-ready" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6139
|
+
|
|
6140
|
+
Don't propose statusbar changes yet:
|
|
6141
|
+
- tmuxInstalled=false \u2192 install tmux first (\`brew install tmux\` on macOS)
|
|
6142
|
+
- tmuxServerRunning=false \u2192 user needs to run \`tmux\` (or attach to a session)
|
|
6143
|
+
|
|
6144
|
+
Re-run \`sis admin check-statusbar\` once tmux is up.
|
|
6145
|
+
|
|
6146
|
+
## Daemon reminder
|
|
6147
|
+
Even with status-left/right perfectly wired, the bar will show literal \`#{E:@sisyphus_left}\` (rendered as empty) until the daemon is running and has populated the option at least once. Always end this flow by checking \`daemonRunning: true\` above; if false, run \`sisyphusd start\` (or \`sisyphusd restart\` if it's stuck).
|
|
6148
|
+
|
|
6149
|
+
## After acting
|
|
6150
|
+
Re-run \`sis admin check-statusbar\` to verify the new state. If anything looks wrong, the JSON form (\`sis admin check-statusbar --json\`) is easier to diff against expected values.
|
|
6151
|
+
</claude-instructions>
|
|
6152
|
+
`);
|
|
6153
|
+
}
|
|
6154
|
+
function registerCheckStatusbar(program2) {
|
|
6155
|
+
program2.command("check-statusbar").description("Inspect tmux statusbar state and emit a Claude decision tree for non-clobbering integration").option("--json", "Print raw JSON state instead of Claude instructions").action((opts) => {
|
|
6156
|
+
const result = runCheck2();
|
|
6157
|
+
if (opts.json) {
|
|
6158
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
printInstructions2(result);
|
|
6162
|
+
});
|
|
6163
|
+
}
|
|
6164
|
+
|
|
6165
|
+
// src/cli/commands/home-init.ts
|
|
6166
|
+
init_shell();
|
|
6167
|
+
import { execSync as execSync13 } from "child_process";
|
|
5191
6168
|
function registerHomeInit(parent) {
|
|
5192
6169
|
parent.command("home-init <name> <cwd>").description("Bootstrap a tmux home session with the sisyphus dashboard.").action((name, cwd) => {
|
|
5193
6170
|
ensureSession(name, cwd);
|
|
@@ -5197,7 +6174,7 @@ function registerHomeInit(parent) {
|
|
|
5197
6174
|
}
|
|
5198
6175
|
function sessionExists(name) {
|
|
5199
6176
|
try {
|
|
5200
|
-
|
|
6177
|
+
execSync13(`tmux has-session -t ${shellQuote(name)}`, { stdio: "pipe" });
|
|
5201
6178
|
return true;
|
|
5202
6179
|
} catch {
|
|
5203
6180
|
return false;
|
|
@@ -5205,13 +6182,13 @@ function sessionExists(name) {
|
|
|
5205
6182
|
}
|
|
5206
6183
|
function ensureSession(name, cwd) {
|
|
5207
6184
|
if (sessionExists(name)) return;
|
|
5208
|
-
|
|
6185
|
+
execSync13(
|
|
5209
6186
|
`tmux new-session -d -s ${shellQuote(name)} -c ${shellQuote(cwd)}`,
|
|
5210
6187
|
{ stdio: "pipe" }
|
|
5211
6188
|
);
|
|
5212
6189
|
}
|
|
5213
6190
|
function setSessionCwd(name, cwd) {
|
|
5214
|
-
|
|
6191
|
+
execSync13(
|
|
5215
6192
|
`tmux set-option -t ${shellQuote(name)} @sisyphus_cwd ${shellQuote(cwd.replace(/\/+$/, ""))}`,
|
|
5216
6193
|
{ stdio: "pipe" }
|
|
5217
6194
|
);
|
|
@@ -5219,10 +6196,10 @@ function setSessionCwd(name, cwd) {
|
|
|
5219
6196
|
|
|
5220
6197
|
// src/cli/commands/doctor.ts
|
|
5221
6198
|
init_paths();
|
|
5222
|
-
import { execSync as
|
|
5223
|
-
import { existsSync as
|
|
5224
|
-
import { homedir as
|
|
5225
|
-
import { join as
|
|
6199
|
+
import { execSync as execSync14 } from "child_process";
|
|
6200
|
+
import { existsSync as existsSync17, statSync as statSync3 } from "fs";
|
|
6201
|
+
import { homedir as homedir11 } from "os";
|
|
6202
|
+
import { join as join18 } from "path";
|
|
5226
6203
|
function checkNodeVersion() {
|
|
5227
6204
|
const major = parseInt(process.versions.node.split(".")[0], 10);
|
|
5228
6205
|
if (major < 22) {
|
|
@@ -5232,7 +6209,7 @@ function checkNodeVersion() {
|
|
|
5232
6209
|
}
|
|
5233
6210
|
function checkClaudeCli() {
|
|
5234
6211
|
try {
|
|
5235
|
-
|
|
6212
|
+
execSync14("which claude", { stdio: "pipe" });
|
|
5236
6213
|
return { name: "Claude CLI", status: "ok", detail: "Found on PATH" };
|
|
5237
6214
|
} catch {
|
|
5238
6215
|
return {
|
|
@@ -5245,7 +6222,7 @@ function checkClaudeCli() {
|
|
|
5245
6222
|
}
|
|
5246
6223
|
function checkGit() {
|
|
5247
6224
|
try {
|
|
5248
|
-
const version =
|
|
6225
|
+
const version = execSync14("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
5249
6226
|
return { name: "git", status: "ok", detail: version };
|
|
5250
6227
|
} catch {
|
|
5251
6228
|
return { name: "git", status: "fail", detail: "Not found on PATH", fix: "Install git: https://git-scm.com/downloads" };
|
|
@@ -5253,7 +6230,7 @@ function checkGit() {
|
|
|
5253
6230
|
}
|
|
5254
6231
|
function checkTmuxVersion() {
|
|
5255
6232
|
try {
|
|
5256
|
-
const version =
|
|
6233
|
+
const version = execSync14("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
5257
6234
|
const match = version.match(/(\d+\.\d+)/);
|
|
5258
6235
|
if (!match) return { name: "tmux version", status: "warn", detail: `Could not parse version: ${version}` };
|
|
5259
6236
|
const ver = parseFloat(match[1]);
|
|
@@ -5279,7 +6256,7 @@ function checkDaemonInstalled() {
|
|
|
5279
6256
|
};
|
|
5280
6257
|
}
|
|
5281
6258
|
const pid = daemonPidPath();
|
|
5282
|
-
if (
|
|
6259
|
+
if (existsSync17(pid)) {
|
|
5283
6260
|
return { name: "Daemon setup", status: "ok", detail: `PID file found at ${pid}` };
|
|
5284
6261
|
}
|
|
5285
6262
|
return {
|
|
@@ -5291,7 +6268,7 @@ function checkDaemonInstalled() {
|
|
|
5291
6268
|
}
|
|
5292
6269
|
function checkDaemonRunning() {
|
|
5293
6270
|
const pid = daemonPidPath();
|
|
5294
|
-
if (!
|
|
6271
|
+
if (!existsSync17(pid)) {
|
|
5295
6272
|
const fix = process.platform === "darwin" ? "launchctl load -w ~/Library/LaunchAgents/com.sisyphus.daemon.plist" : "sisyphusd & \u2014 or check if the process is running";
|
|
5296
6273
|
return {
|
|
5297
6274
|
name: "Daemon process",
|
|
@@ -5302,7 +6279,7 @@ function checkDaemonRunning() {
|
|
|
5302
6279
|
}
|
|
5303
6280
|
try {
|
|
5304
6281
|
const sock = socketPath();
|
|
5305
|
-
|
|
6282
|
+
execSync14(`test -S "${sock}"`, { stdio: "pipe" });
|
|
5306
6283
|
return { name: "Daemon process", status: "ok", detail: `Socket at ${sock}` };
|
|
5307
6284
|
} catch {
|
|
5308
6285
|
return {
|
|
@@ -5315,13 +6292,13 @@ function checkDaemonRunning() {
|
|
|
5315
6292
|
}
|
|
5316
6293
|
function checkTmux() {
|
|
5317
6294
|
try {
|
|
5318
|
-
|
|
6295
|
+
execSync14("which tmux", { stdio: "pipe" });
|
|
5319
6296
|
} catch {
|
|
5320
6297
|
const installHint = process.platform === "darwin" ? "brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
|
|
5321
6298
|
return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: installHint };
|
|
5322
6299
|
}
|
|
5323
6300
|
try {
|
|
5324
|
-
|
|
6301
|
+
execSync14("tmux list-sessions", { stdio: "pipe" });
|
|
5325
6302
|
return { name: "tmux", status: "ok", detail: "Running" };
|
|
5326
6303
|
} catch {
|
|
5327
6304
|
return { name: "tmux", status: "warn", detail: "Installed but no server running" };
|
|
@@ -5329,7 +6306,7 @@ function checkTmux() {
|
|
|
5329
6306
|
}
|
|
5330
6307
|
function checkCycleScript() {
|
|
5331
6308
|
const path = cycleScriptPath();
|
|
5332
|
-
if (!
|
|
6309
|
+
if (!existsSync17(path)) {
|
|
5333
6310
|
return {
|
|
5334
6311
|
name: "Cycle script",
|
|
5335
6312
|
status: "fail",
|
|
@@ -5354,7 +6331,7 @@ function checkCycleScript() {
|
|
|
5354
6331
|
function checkTmuxKeybind() {
|
|
5355
6332
|
const existing = getExistingBinding(DEFAULT_CYCLE_KEY);
|
|
5356
6333
|
if (existing === null) {
|
|
5357
|
-
if (
|
|
6334
|
+
if (existsSync17(sisyphusTmuxConfPath())) {
|
|
5358
6335
|
return {
|
|
5359
6336
|
name: `Tmux keybind (${DEFAULT_CYCLE_KEY})`,
|
|
5360
6337
|
status: "warn",
|
|
@@ -5380,7 +6357,7 @@ function checkTmuxKeybind() {
|
|
|
5380
6357
|
}
|
|
5381
6358
|
function checkGlobalDir() {
|
|
5382
6359
|
const dir = globalDir();
|
|
5383
|
-
if (
|
|
6360
|
+
if (existsSync17(dir)) {
|
|
5384
6361
|
return { name: "Data directory", status: "ok", detail: dir };
|
|
5385
6362
|
}
|
|
5386
6363
|
return { name: "Data directory", status: "warn", detail: `${dir} does not exist (created on first use)` };
|
|
@@ -5432,7 +6409,7 @@ function checkSisyphusPlugin() {
|
|
|
5432
6409
|
function checkTermrender() {
|
|
5433
6410
|
if (isTermrenderAvailable()) {
|
|
5434
6411
|
try {
|
|
5435
|
-
const version =
|
|
6412
|
+
const version = execSync14("termrender --version", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
5436
6413
|
return { name: "termrender", status: "ok", detail: version };
|
|
5437
6414
|
} catch {
|
|
5438
6415
|
return { name: "termrender", status: "ok", detail: "installed" };
|
|
@@ -5451,7 +6428,7 @@ function checkNvim() {
|
|
|
5451
6428
|
return { name: "nvim", status: "warn", detail: "Not installed", fix };
|
|
5452
6429
|
}
|
|
5453
6430
|
try {
|
|
5454
|
-
const version =
|
|
6431
|
+
const version = execSync14("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
|
|
5455
6432
|
return { name: "nvim", status: "ok", detail: version ?? "installed" };
|
|
5456
6433
|
} catch {
|
|
5457
6434
|
return { name: "nvim", status: "ok", detail: "installed" };
|
|
@@ -5459,8 +6436,8 @@ function checkNvim() {
|
|
|
5459
6436
|
}
|
|
5460
6437
|
function checkNotifyBinary() {
|
|
5461
6438
|
if (process.platform !== "darwin") return null;
|
|
5462
|
-
const binary =
|
|
5463
|
-
if (
|
|
6439
|
+
const binary = join18(homedir11(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
|
|
6440
|
+
if (existsSync17(binary)) {
|
|
5464
6441
|
return { name: "Notifications", status: "ok", detail: "SisyphusNotify.app built" };
|
|
5465
6442
|
}
|
|
5466
6443
|
return {
|
|
@@ -5513,8 +6490,8 @@ function registerDoctor(program2) {
|
|
|
5513
6490
|
}
|
|
5514
6491
|
|
|
5515
6492
|
// src/cli/commands/init.ts
|
|
5516
|
-
import { existsSync as
|
|
5517
|
-
import { join as
|
|
6493
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
6494
|
+
import { join as join19 } from "path";
|
|
5518
6495
|
var DEFAULT_CONFIG2 = {};
|
|
5519
6496
|
var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
|
|
5520
6497
|
|
|
@@ -5525,18 +6502,18 @@ var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
|
|
|
5525
6502
|
function registerInit(program2) {
|
|
5526
6503
|
program2.command("init").description("Initialize sisyphus configuration for this project").option("--orchestrator", "Also create a custom orchestrator prompt template").action((opts) => {
|
|
5527
6504
|
const cwd = process.cwd();
|
|
5528
|
-
const sisDir =
|
|
5529
|
-
const configPath =
|
|
5530
|
-
if (
|
|
6505
|
+
const sisDir = join19(cwd, ".sisyphus");
|
|
6506
|
+
const configPath = join19(sisDir, "config.json");
|
|
6507
|
+
if (existsSync18(configPath)) {
|
|
5531
6508
|
console.log(`Already initialized: ${configPath}`);
|
|
5532
6509
|
return;
|
|
5533
6510
|
}
|
|
5534
|
-
|
|
6511
|
+
mkdirSync8(sisDir, { recursive: true });
|
|
5535
6512
|
writeFileSync9(configPath, JSON.stringify(DEFAULT_CONFIG2, null, 2) + "\n", "utf-8");
|
|
5536
6513
|
console.log(`Created ${configPath}`);
|
|
5537
6514
|
if (opts.orchestrator) {
|
|
5538
|
-
const orchPath =
|
|
5539
|
-
if (!
|
|
6515
|
+
const orchPath = join19(sisDir, "orchestrator.md");
|
|
6516
|
+
if (!existsSync18(orchPath)) {
|
|
5540
6517
|
writeFileSync9(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
|
|
5541
6518
|
console.log(`Created ${orchPath}`);
|
|
5542
6519
|
}
|
|
@@ -5579,7 +6556,7 @@ function registerUninstall(program2) {
|
|
|
5579
6556
|
|
|
5580
6557
|
// src/cli/commands/configure-upload.ts
|
|
5581
6558
|
init_paths();
|
|
5582
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
6559
|
+
import { chmodSync as chmodSync2, existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
|
|
5583
6560
|
import { createInterface as createInterface3 } from "readline";
|
|
5584
6561
|
import { dirname as dirname6 } from "path";
|
|
5585
6562
|
async function readUrlFromInput(interactive) {
|
|
@@ -5637,16 +6614,16 @@ function registerConfigureUpload(program2) {
|
|
|
5637
6614
|
const url = parsed.origin + (strippedPath.length > 0 ? strippedPath : "");
|
|
5638
6615
|
const configPath = globalConfigPath();
|
|
5639
6616
|
let existing = {};
|
|
5640
|
-
if (
|
|
6617
|
+
if (existsSync19(configPath)) {
|
|
5641
6618
|
try {
|
|
5642
|
-
existing = JSON.parse(
|
|
6619
|
+
existing = JSON.parse(readFileSync21(configPath, "utf-8"));
|
|
5643
6620
|
} catch {
|
|
5644
6621
|
console.error(`Error: ${configPath} could not be parsed \u2014 fix or delete it first`);
|
|
5645
6622
|
process.exit(1);
|
|
5646
6623
|
}
|
|
5647
6624
|
}
|
|
5648
6625
|
const merged = { ...existing, upload: { url, token } };
|
|
5649
|
-
|
|
6626
|
+
mkdirSync9(dirname6(configPath), { recursive: true });
|
|
5650
6627
|
writeFileSync10(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
5651
6628
|
chmodSync2(configPath, 384);
|
|
5652
6629
|
console.log(`\u2713 upload configured (${configPath})`);
|
|
@@ -5654,11 +6631,11 @@ function registerConfigureUpload(program2) {
|
|
|
5654
6631
|
}
|
|
5655
6632
|
|
|
5656
6633
|
// src/cli/commands/getting-started.ts
|
|
5657
|
-
import { execSync as
|
|
5658
|
-
import { dirname as dirname7, join as
|
|
6634
|
+
import { execSync as execSync15 } from "child_process";
|
|
6635
|
+
import { dirname as dirname7, join as join20 } from "path";
|
|
5659
6636
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5660
6637
|
function templatePath(name) {
|
|
5661
|
-
return
|
|
6638
|
+
return join20(dirname7(fileURLToPath3(import.meta.url)), "templates", name);
|
|
5662
6639
|
}
|
|
5663
6640
|
function isClaudeCode() {
|
|
5664
6641
|
return !!process.env["CLAUDECODE"];
|
|
@@ -5983,18 +6960,30 @@ Two keybinds to remember (both use the RIGHT Option key):
|
|
|
5983
6960
|
| Right Option + s | Cycle through sisyphus sessions |
|
|
5984
6961
|
| Right Option + Shift + s | Jump back to dashboard |
|
|
5985
6962
|
|
|
5986
|
-
### 4. Verify keybinds are installed
|
|
6963
|
+
### 4. Verify keybinds are installed (REQUIRED before step 5)
|
|
6964
|
+
|
|
6965
|
+
Run \`sis admin check-keybinds\` and follow the decision tree it emits. The output is
|
|
6966
|
+
structured for you \u2014 it tells you the current state and which path to take (Path A
|
|
6967
|
+
through Path F). Do NOT skip this and do NOT ask the user to test a keybind until
|
|
6968
|
+
both \`M-s\` and \`C-s\` read "sisyphus" in that command's output.
|
|
5987
6969
|
|
|
5988
|
-
|
|
5989
|
-
-
|
|
5990
|
-
-
|
|
5991
|
-
-
|
|
6970
|
+
Common paths:
|
|
6971
|
+
- **Path A (already wired):** confirm and move on.
|
|
6972
|
+
- **Path B (safe auto-install):** run \`sis admin setup-keybind --yes\`.
|
|
6973
|
+
- **Path C (would touch user's tmux.conf):** ask the user \u2014 persistent (\`--force\`) or
|
|
6974
|
+
live-only (no flag, non-TTY auto-declines the conf write).
|
|
6975
|
+
- **Path D (binding conflict):** pick alternate keys or wire directly.
|
|
6976
|
+
- **Path E (hidden prefix collision):** their tmux prefix is C-s; explain and offer
|
|
6977
|
+
alternatives.
|
|
6978
|
+
- **Path F (tmux not ready):** fix the precondition first.
|
|
5992
6979
|
|
|
5993
|
-
|
|
6980
|
+
After acting, re-run \`sis admin check-keybinds\` to confirm success.
|
|
5994
6981
|
|
|
5995
6982
|
### 5. Test the keybind
|
|
5996
6983
|
|
|
5997
|
-
|
|
6984
|
+
Once check-keybinds reports both keys as "sisyphus", have the user try pressing
|
|
6985
|
+
Right Option + s. Nothing should happen yet (no sisyphus session running) \u2014 and that's
|
|
6986
|
+
fine. The important thing is no special character appears.
|
|
5998
6987
|
|
|
5999
6988
|
If they see \`\xDF\` or similar, circle back to the Right Option Key setup above.
|
|
6000
6989
|
|
|
@@ -6134,11 +7123,11 @@ function printStep5() {
|
|
|
6134
7123
|
let recentCommits = "";
|
|
6135
7124
|
let topLevelFiles = "";
|
|
6136
7125
|
try {
|
|
6137
|
-
recentCommits =
|
|
7126
|
+
recentCommits = execSync15("git log --oneline -15 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
6138
7127
|
} catch {
|
|
6139
7128
|
}
|
|
6140
7129
|
try {
|
|
6141
|
-
topLevelFiles =
|
|
7130
|
+
topLevelFiles = execSync15("ls -1 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
6142
7131
|
} catch {
|
|
6143
7132
|
}
|
|
6144
7133
|
console.log(`
|
|
@@ -6616,7 +7605,7 @@ function registerGettingStarted(program2) {
|
|
|
6616
7605
|
|
|
6617
7606
|
// src/cli/commands/history.ts
|
|
6618
7607
|
init_paths();
|
|
6619
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
7608
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync22, existsSync as existsSync20 } from "fs";
|
|
6620
7609
|
import { resolve as resolve6 } from "path";
|
|
6621
7610
|
var RESET3 = "\x1B[0m";
|
|
6622
7611
|
var BOLD3 = "\x1B[1m";
|
|
@@ -6656,13 +7645,13 @@ function splitAgentTime(agents) {
|
|
|
6656
7645
|
}
|
|
6657
7646
|
function loadAllSummaries() {
|
|
6658
7647
|
const base = historyBaseDir();
|
|
6659
|
-
if (!
|
|
7648
|
+
if (!existsSync20(base)) return [];
|
|
6660
7649
|
const results = [];
|
|
6661
7650
|
for (const name of readdirSync6(base)) {
|
|
6662
7651
|
const summaryPath = historySessionSummaryPath(name);
|
|
6663
|
-
if (
|
|
7652
|
+
if (existsSync20(summaryPath)) {
|
|
6664
7653
|
try {
|
|
6665
|
-
const raw =
|
|
7654
|
+
const raw = readFileSync22(summaryPath, "utf-8");
|
|
6666
7655
|
results.push({ id: name, summary: JSON.parse(raw) });
|
|
6667
7656
|
continue;
|
|
6668
7657
|
} catch {
|
|
@@ -6676,10 +7665,10 @@ function loadAllSummaries() {
|
|
|
6676
7665
|
}
|
|
6677
7666
|
function buildLiveSummary(sessionId) {
|
|
6678
7667
|
const eventsPath = historyEventsPath(sessionId);
|
|
6679
|
-
if (!
|
|
7668
|
+
if (!existsSync20(eventsPath)) return null;
|
|
6680
7669
|
let cwd = null;
|
|
6681
7670
|
try {
|
|
6682
|
-
const lines =
|
|
7671
|
+
const lines = readFileSync22(eventsPath, "utf-8").split("\n");
|
|
6683
7672
|
for (const line of lines) {
|
|
6684
7673
|
if (!line.trim()) continue;
|
|
6685
7674
|
try {
|
|
@@ -6697,10 +7686,10 @@ function buildLiveSummary(sessionId) {
|
|
|
6697
7686
|
}
|
|
6698
7687
|
if (!cwd) return null;
|
|
6699
7688
|
const sPath = statePath(cwd, sessionId);
|
|
6700
|
-
if (!
|
|
7689
|
+
if (!existsSync20(sPath)) return null;
|
|
6701
7690
|
let session2;
|
|
6702
7691
|
try {
|
|
6703
|
-
session2 = JSON.parse(
|
|
7692
|
+
session2 = JSON.parse(readFileSync22(sPath, "utf-8"));
|
|
6704
7693
|
} catch {
|
|
6705
7694
|
return null;
|
|
6706
7695
|
}
|
|
@@ -6761,8 +7750,8 @@ function buildLiveSummary(sessionId) {
|
|
|
6761
7750
|
}
|
|
6762
7751
|
function loadEvents(sessionId) {
|
|
6763
7752
|
const eventsPath = historyEventsPath(sessionId);
|
|
6764
|
-
if (!
|
|
6765
|
-
const lines =
|
|
7753
|
+
if (!existsSync20(eventsPath)) return [];
|
|
7754
|
+
const lines = readFileSync22(eventsPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
6766
7755
|
const events = [];
|
|
6767
7756
|
for (const line of lines) {
|
|
6768
7757
|
try {
|
|
@@ -6775,13 +7764,13 @@ function loadEvents(sessionId) {
|
|
|
6775
7764
|
}
|
|
6776
7765
|
function findSession(idOrName) {
|
|
6777
7766
|
const summaryPath = historySessionSummaryPath(idOrName);
|
|
6778
|
-
if (
|
|
7767
|
+
if (existsSync20(summaryPath)) {
|
|
6779
7768
|
try {
|
|
6780
|
-
return { id: idOrName, summary: JSON.parse(
|
|
7769
|
+
return { id: idOrName, summary: JSON.parse(readFileSync22(summaryPath, "utf-8")) };
|
|
6781
7770
|
} catch {
|
|
6782
7771
|
}
|
|
6783
7772
|
}
|
|
6784
|
-
if (
|
|
7773
|
+
if (existsSync20(historySessionDir(idOrName))) {
|
|
6785
7774
|
const live = buildLiveSummary(idOrName);
|
|
6786
7775
|
if (live) return { id: idOrName, summary: live };
|
|
6787
7776
|
}
|
|
@@ -7170,21 +8159,21 @@ function registerHistory(program2) {
|
|
|
7170
8159
|
init_paths();
|
|
7171
8160
|
import { execFile as execFile2 } from "child_process";
|
|
7172
8161
|
import { promisify } from "util";
|
|
7173
|
-
import { existsSync as
|
|
7174
|
-
import { homedir as
|
|
7175
|
-
import { join as
|
|
8162
|
+
import { existsSync as existsSync21, readFileSync as readFileSync23, mkdirSync as mkdirSync10, symlinkSync, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "fs";
|
|
8163
|
+
import { homedir as homedir12 } from "os";
|
|
8164
|
+
import { join as join22 } from "path";
|
|
7176
8165
|
function sanitizeName(name) {
|
|
7177
8166
|
return name.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
7178
8167
|
}
|
|
7179
8168
|
function buildOutputPath(label, dir) {
|
|
7180
8169
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7181
|
-
|
|
8170
|
+
mkdirSync10(dir, { recursive: true });
|
|
7182
8171
|
const base = `sisyphus-${label}-${date}`;
|
|
7183
|
-
let candidate =
|
|
8172
|
+
let candidate = join22(dir, `${base}.zip`);
|
|
7184
8173
|
let counter = 1;
|
|
7185
|
-
while (
|
|
8174
|
+
while (existsSync21(candidate)) {
|
|
7186
8175
|
counter++;
|
|
7187
|
-
candidate =
|
|
8176
|
+
candidate = join22(dir, `${base}-${counter}.zip`);
|
|
7188
8177
|
}
|
|
7189
8178
|
return candidate;
|
|
7190
8179
|
}
|
|
@@ -7246,33 +8235,33 @@ async function exportSessionToZip(sessionId, cwd, options) {
|
|
|
7246
8235
|
const reveal = options?.reveal ?? true;
|
|
7247
8236
|
const sessDir = sessionDir(cwd, sessionId);
|
|
7248
8237
|
const histDir = historySessionDir(sessionId);
|
|
7249
|
-
const sessExists =
|
|
7250
|
-
const histExists =
|
|
8238
|
+
const sessExists = existsSync21(sessDir);
|
|
8239
|
+
const histExists = existsSync21(histDir);
|
|
7251
8240
|
if (!sessExists && !histExists) {
|
|
7252
8241
|
throw new Error(`No data found for session ${sessionId}`);
|
|
7253
8242
|
}
|
|
7254
8243
|
let label = sessionId.slice(0, 8);
|
|
7255
8244
|
const stPath = statePath(cwd, sessionId);
|
|
7256
|
-
if (
|
|
8245
|
+
if (existsSync21(stPath)) {
|
|
7257
8246
|
try {
|
|
7258
|
-
const state = JSON.parse(
|
|
8247
|
+
const state = JSON.parse(readFileSync23(stPath, "utf-8"));
|
|
7259
8248
|
if (state.name) {
|
|
7260
8249
|
label = sanitizeName(state.name);
|
|
7261
8250
|
}
|
|
7262
8251
|
} catch {
|
|
7263
8252
|
}
|
|
7264
8253
|
}
|
|
7265
|
-
const dir = options?.outputDir ??
|
|
8254
|
+
const dir = options?.outputDir ?? join22(homedir12(), "Downloads");
|
|
7266
8255
|
const outputPath = buildOutputPath(label, dir);
|
|
7267
8256
|
const tmpDir = `/tmp/sisyphus-export-${sessionId.slice(0, 8)}-${Date.now()}`;
|
|
7268
8257
|
try {
|
|
7269
|
-
|
|
7270
|
-
writeFileSync11(
|
|
8258
|
+
mkdirSync10(tmpDir, { recursive: true });
|
|
8259
|
+
writeFileSync11(join22(tmpDir, "CLAUDE.md"), generateGuide(), "utf-8");
|
|
7271
8260
|
if (sessExists) {
|
|
7272
|
-
symlinkSync(sessDir,
|
|
8261
|
+
symlinkSync(sessDir, join22(tmpDir, "session"));
|
|
7273
8262
|
}
|
|
7274
8263
|
if (histExists) {
|
|
7275
|
-
symlinkSync(histDir,
|
|
8264
|
+
symlinkSync(histDir, join22(tmpDir, "history"));
|
|
7276
8265
|
}
|
|
7277
8266
|
const parts = ["CLAUDE.md", sessExists ? "session/" : "", histExists ? "history/" : ""].filter(Boolean);
|
|
7278
8267
|
await execFileAsync("zip", ["-rq", outputPath, ...parts], { cwd: tmpDir });
|
|
@@ -7394,12 +8383,12 @@ function buildManifest(args2) {
|
|
|
7394
8383
|
}
|
|
7395
8384
|
|
|
7396
8385
|
// src/shared/version.ts
|
|
7397
|
-
import { readFileSync as
|
|
8386
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
7398
8387
|
import { resolve as resolve7 } from "path";
|
|
7399
8388
|
function readSisyphusVersion() {
|
|
7400
8389
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
7401
8390
|
try {
|
|
7402
|
-
const raw =
|
|
8391
|
+
const raw = readFileSync24(resolve7(import.meta.dirname, rel), "utf-8");
|
|
7403
8392
|
const pkg = JSON.parse(raw);
|
|
7404
8393
|
if (pkg.name === "sisyphi" && pkg.version) return pkg.version;
|
|
7405
8394
|
} catch {
|
|
@@ -7494,12 +8483,13 @@ function registerUpload(program2) {
|
|
|
7494
8483
|
}
|
|
7495
8484
|
|
|
7496
8485
|
// src/cli/commands/scratch.ts
|
|
7497
|
-
import { execSync as
|
|
8486
|
+
import { execSync as execSync16 } from "child_process";
|
|
8487
|
+
init_shell();
|
|
7498
8488
|
function findHomeSession(cwd) {
|
|
7499
8489
|
const normalizedCwd = cwd.replace(/\/+$/, "");
|
|
7500
8490
|
let output;
|
|
7501
8491
|
try {
|
|
7502
|
-
output =
|
|
8492
|
+
output = execSync16('tmux list-sessions -F "#{session_id}|#{session_name}"', {
|
|
7503
8493
|
encoding: "utf-8",
|
|
7504
8494
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7505
8495
|
}).trim();
|
|
@@ -7513,7 +8503,7 @@ function findHomeSession(cwd) {
|
|
|
7513
8503
|
const name = line.slice(pipeIdx + 1);
|
|
7514
8504
|
if (name.startsWith("ssyph_")) continue;
|
|
7515
8505
|
try {
|
|
7516
|
-
const val =
|
|
8506
|
+
const val = execSync16(
|
|
7517
8507
|
`tmux show-options -t ${shellQuote(sessId)} -v @sisyphus_cwd`,
|
|
7518
8508
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
7519
8509
|
).trim();
|
|
@@ -7529,7 +8519,7 @@ function registerScratch(program2) {
|
|
|
7529
8519
|
const cwd = opts.cwd ?? process.env["SISYPHUS_CWD"] ?? process.cwd();
|
|
7530
8520
|
const homeSession = findHomeSession(cwd);
|
|
7531
8521
|
if (!homeSession) {
|
|
7532
|
-
const current =
|
|
8522
|
+
const current = execSync16('tmux display-message -p "#{session_name}"', {
|
|
7533
8523
|
encoding: "utf-8"
|
|
7534
8524
|
}).trim();
|
|
7535
8525
|
openScratchWindow(current, cwd, promptParts.join(" "));
|
|
@@ -7539,7 +8529,7 @@ function registerScratch(program2) {
|
|
|
7539
8529
|
});
|
|
7540
8530
|
}
|
|
7541
8531
|
function openScratchWindow(tmuxSession, cwd, prompt) {
|
|
7542
|
-
const windowId =
|
|
8532
|
+
const windowId = execSync16(
|
|
7543
8533
|
`tmux new-window -t ${shellQuote(tmuxSession + ":")} -n "scratch" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
|
|
7544
8534
|
{ encoding: "utf-8" }
|
|
7545
8535
|
).trim();
|
|
@@ -7547,7 +8537,7 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
|
|
|
7547
8537
|
if (prompt) {
|
|
7548
8538
|
cmd += ` -p ${shellQuote(prompt)}`;
|
|
7549
8539
|
}
|
|
7550
|
-
|
|
8540
|
+
execSync16(
|
|
7551
8541
|
`tmux send-keys -t ${shellQuote(windowId)} ${shellQuote(cmd)} Enter`
|
|
7552
8542
|
);
|
|
7553
8543
|
console.log(`Scratch session opened in ${tmuxSession}`);
|
|
@@ -7555,27 +8545,27 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
|
|
|
7555
8545
|
|
|
7556
8546
|
// src/cli/commands/review.ts
|
|
7557
8547
|
init_paths();
|
|
7558
|
-
import { join as
|
|
7559
|
-
import { existsSync as
|
|
8548
|
+
import { join as join23, resolve as resolve8, dirname as dirname8 } from "path";
|
|
8549
|
+
import { existsSync as existsSync22, readFileSync as readFileSync25, writeFileSync as writeFileSync12, renameSync as renameSync3, readdirSync as readdirSync7 } from "fs";
|
|
7560
8550
|
var _statusCheck = ["draft", "question", "approved", "rejected", "deferred"];
|
|
7561
8551
|
function resolveContextArtifact(file, opts, filename, notFoundMessage) {
|
|
7562
8552
|
const cwd = opts.cwd || process.env.SISYPHUS_CWD || process.cwd();
|
|
7563
8553
|
if (file) return resolve8(file);
|
|
7564
8554
|
const sessionId = opts.sessionId || process.env.SISYPHUS_SESSION_ID;
|
|
7565
8555
|
if (sessionId) {
|
|
7566
|
-
const target =
|
|
7567
|
-
if (!
|
|
8556
|
+
const target = join23(contextDir(cwd, sessionId), filename);
|
|
8557
|
+
if (!existsSync22(target)) {
|
|
7568
8558
|
console.error(`Error: File not found: ${target}`);
|
|
7569
8559
|
process.exit(1);
|
|
7570
8560
|
}
|
|
7571
8561
|
return target;
|
|
7572
8562
|
}
|
|
7573
8563
|
const dir = sessionsDir(cwd);
|
|
7574
|
-
if (
|
|
8564
|
+
if (existsSync22(dir)) {
|
|
7575
8565
|
const sessions = readdirSync7(dir);
|
|
7576
8566
|
for (const session2 of sessions.reverse()) {
|
|
7577
|
-
const candidate =
|
|
7578
|
-
if (
|
|
8567
|
+
const candidate = join23(dir, session2, "context", filename);
|
|
8568
|
+
if (existsSync22(candidate)) return candidate;
|
|
7579
8569
|
}
|
|
7580
8570
|
}
|
|
7581
8571
|
console.error(`Error: ${notFoundMessage}`);
|
|
@@ -7617,16 +8607,16 @@ Examples:
|
|
|
7617
8607
|
"requirements.json",
|
|
7618
8608
|
"No requirements.json found. Provide a path or use --session-id."
|
|
7619
8609
|
);
|
|
7620
|
-
if (!
|
|
8610
|
+
if (!existsSync22(targetPath)) {
|
|
7621
8611
|
console.error(`Error: File not found: ${targetPath}`);
|
|
7622
8612
|
process.exit(1);
|
|
7623
8613
|
}
|
|
7624
|
-
const parsed = JSON.parse(
|
|
8614
|
+
const parsed = JSON.parse(readFileSync25(targetPath, "utf-8"));
|
|
7625
8615
|
const rendered = renderRequirementsMarkdown(parsed);
|
|
7626
|
-
const outPath =
|
|
8616
|
+
const outPath = join23(dirname8(targetPath), "requirements.md");
|
|
7627
8617
|
const tmpPath = outPath + ".tmp";
|
|
7628
|
-
if (
|
|
7629
|
-
const existing =
|
|
8618
|
+
if (existsSync22(outPath)) {
|
|
8619
|
+
const existing = readFileSync25(outPath, "utf-8");
|
|
7630
8620
|
if (existing !== rendered) {
|
|
7631
8621
|
if (!opts.force) {
|
|
7632
8622
|
process.stderr.write(
|
|
@@ -9192,20 +10182,21 @@ function createBadgeGallery(unlockedAchievements, startIndex) {
|
|
|
9192
10182
|
|
|
9193
10183
|
// src/daemon/companion-memory.ts
|
|
9194
10184
|
init_paths();
|
|
9195
|
-
import { existsSync as
|
|
9196
|
-
import { dirname as dirname10, join as
|
|
10185
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync27, renameSync as renameSync5, writeFileSync as writeFileSync14 } from "fs";
|
|
10186
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
9197
10187
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
9198
10188
|
import { z as z2 } from "zod";
|
|
9199
10189
|
|
|
9200
10190
|
// src/daemon/haiku.ts
|
|
10191
|
+
init_env();
|
|
9201
10192
|
import { query, createSdkMcpServer } from "@r-cli/sdk";
|
|
9202
10193
|
var COOLDOWN_MS = 5 * 60 * 1e3;
|
|
9203
10194
|
|
|
9204
10195
|
// src/daemon/companion.ts
|
|
9205
10196
|
init_paths();
|
|
9206
|
-
import { existsSync as
|
|
10197
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync11, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync13 } from "fs";
|
|
9207
10198
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
9208
|
-
import { dirname as dirname9, join as
|
|
10199
|
+
import { dirname as dirname9, join as join24 } from "path";
|
|
9209
10200
|
|
|
9210
10201
|
// src/shared/companion-normalize.ts
|
|
9211
10202
|
function emptyStats() {
|
|
@@ -9254,20 +10245,20 @@ function normalizeCompanion(state) {
|
|
|
9254
10245
|
// src/daemon/companion.ts
|
|
9255
10246
|
function loadCompanion() {
|
|
9256
10247
|
const path = companionPath();
|
|
9257
|
-
if (!
|
|
10248
|
+
if (!existsSync23(path)) {
|
|
9258
10249
|
const state2 = createDefaultCompanion();
|
|
9259
10250
|
saveCompanion(state2);
|
|
9260
10251
|
return state2;
|
|
9261
10252
|
}
|
|
9262
|
-
const raw =
|
|
10253
|
+
const raw = readFileSync26(path, "utf-8");
|
|
9263
10254
|
const state = JSON.parse(raw);
|
|
9264
10255
|
return normalizeCompanion(state);
|
|
9265
10256
|
}
|
|
9266
10257
|
function saveCompanion(state) {
|
|
9267
10258
|
const path = companionPath();
|
|
9268
10259
|
const dir = dirname9(path);
|
|
9269
|
-
|
|
9270
|
-
const tmp =
|
|
10260
|
+
mkdirSync11(dir, { recursive: true });
|
|
10261
|
+
const tmp = join24(dir, `.companion.${randomUUID3()}.tmp`);
|
|
9271
10262
|
writeFileSync13(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
9272
10263
|
renameSync4(tmp, path);
|
|
9273
10264
|
}
|
|
@@ -9340,10 +10331,10 @@ function fillDefaults(state) {
|
|
|
9340
10331
|
}
|
|
9341
10332
|
function loadMemoryStrict() {
|
|
9342
10333
|
const path = resolvedMemoryPath();
|
|
9343
|
-
if (!
|
|
10334
|
+
if (!existsSync24(path)) return defaultMemoryState();
|
|
9344
10335
|
let raw;
|
|
9345
10336
|
try {
|
|
9346
|
-
raw =
|
|
10337
|
+
raw = readFileSync27(path, "utf-8");
|
|
9347
10338
|
} catch (err) {
|
|
9348
10339
|
throw new MemoryStoreParseError(err);
|
|
9349
10340
|
}
|
|
@@ -9386,15 +10377,17 @@ var ObservationZodSchema = z2.object({
|
|
|
9386
10377
|
});
|
|
9387
10378
|
|
|
9388
10379
|
// src/daemon/companion-popup.ts
|
|
9389
|
-
import { writeFileSync as writeFileSync15, readFileSync as
|
|
10380
|
+
import { writeFileSync as writeFileSync15, readFileSync as readFileSync28, unlinkSync as unlinkSync3, existsSync as existsSync25 } from "fs";
|
|
9390
10381
|
import { tmpdir as tmpdir2 } from "os";
|
|
9391
|
-
import { join as
|
|
10382
|
+
import { join as join26, resolve as resolve9 } from "path";
|
|
10383
|
+
init_exec();
|
|
10384
|
+
init_shell();
|
|
9392
10385
|
var POPUP_WIDTH = 38;
|
|
9393
10386
|
var INNER_WIDTH = POPUP_WIDTH - 6;
|
|
9394
10387
|
var POPUP_DURATION = 15;
|
|
9395
|
-
var POPUP_TMP_PREFIX =
|
|
9396
|
-
var POPUP_SCRIPT =
|
|
9397
|
-
var POPUP_RESULT_PREFIX =
|
|
10388
|
+
var POPUP_TMP_PREFIX = join26(tmpdir2(), "sisyphus-popup");
|
|
10389
|
+
var POPUP_SCRIPT = join26(tmpdir2(), "sisyphus-popup.sh");
|
|
10390
|
+
var POPUP_RESULT_PREFIX = join26(tmpdir2(), "sisyphus-popup-result");
|
|
9398
10391
|
var WHIP_ANIMATION_PATH = resolve9(import.meta.dirname, "../templates/whip-animation.sh");
|
|
9399
10392
|
var WHIP_ANIMATION_ROWS = 12;
|
|
9400
10393
|
function wrapText2(text, width) {
|
|
@@ -9438,7 +10431,7 @@ function showCommentaryPopupQueue(pages) {
|
|
|
9438
10431
|
if (contentHeight > maxContentHeight) maxContentHeight = contentHeight;
|
|
9439
10432
|
writeFileSync15(`${POPUP_TMP_PREFIX}-${i}.txt`, content);
|
|
9440
10433
|
}
|
|
9441
|
-
const whipAvailable =
|
|
10434
|
+
const whipAvailable = existsSync25(WHIP_ANIMATION_PATH);
|
|
9442
10435
|
if (whipAvailable && maxContentHeight < WHIP_ANIMATION_ROWS + 2) {
|
|
9443
10436
|
maxContentHeight = WHIP_ANIMATION_ROWS + 2;
|
|
9444
10437
|
}
|
|
@@ -9512,7 +10505,7 @@ fi
|
|
|
9512
10505
|
}
|
|
9513
10506
|
let raw;
|
|
9514
10507
|
try {
|
|
9515
|
-
raw =
|
|
10508
|
+
raw = readFileSync28(POPUP_RESULT_PREFIX, "utf8").trim();
|
|
9516
10509
|
} catch {
|
|
9517
10510
|
return null;
|
|
9518
10511
|
} finally {
|
|
@@ -9689,6 +10682,10 @@ function registerCompanion(program2) {
|
|
|
9689
10682
|
const context = buildCompanionContext(opts.cwd);
|
|
9690
10683
|
process.stdout.write(JSON.stringify({ additionalContext: context }));
|
|
9691
10684
|
});
|
|
10685
|
+
companion.command("pane").description("Open (or focus) a side claude pane next to the dashboard").option("--cwd <path>", "Project directory", process.cwd()).action(async (opts) => {
|
|
10686
|
+
const { openCompanionPane: openCompanionPane2 } = await Promise.resolve().then(() => (init_tmux(), tmux_exports));
|
|
10687
|
+
openCompanionPane2(opts.cwd);
|
|
10688
|
+
});
|
|
9692
10689
|
companion.command("popup-test").description("Show a test commentary popup to validate feedback key handling").option("--text <text>", "Custom popup text", "Cycle complete. Everything went exactly as planned. Nothing suspicious here.").action((opts) => {
|
|
9693
10690
|
const feedback = showCommentaryPopup(opts.text);
|
|
9694
10691
|
if (feedback === null) {
|
|
@@ -9705,14 +10702,15 @@ function registerCompanion(program2) {
|
|
|
9705
10702
|
}
|
|
9706
10703
|
|
|
9707
10704
|
// src/cli/commands/deploy.ts
|
|
9708
|
-
import { homedir as
|
|
9709
|
-
import { join as
|
|
10705
|
+
import { homedir as homedir13 } from "os";
|
|
10706
|
+
import { join as join28 } from "path";
|
|
9710
10707
|
|
|
9711
10708
|
// src/cli/deploy/runner.ts
|
|
9712
10709
|
init_paths();
|
|
9713
|
-
|
|
9714
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync13, readFileSync as readFileSync29 } from "fs";
|
|
10710
|
+
init_exec();
|
|
9715
10711
|
init_creds();
|
|
10712
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
10713
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync30, mkdirSync as mkdirSync15, readFileSync as readFileSync32 } from "fs";
|
|
9716
10714
|
|
|
9717
10715
|
// src/cli/deploy/pricing.ts
|
|
9718
10716
|
var LAST_VERIFIED = "2026-05-06";
|
|
@@ -9747,12 +10745,12 @@ function formatCostLine(provider, instanceType) {
|
|
|
9747
10745
|
// src/cli/deploy/runtime.ts
|
|
9748
10746
|
init_atomic();
|
|
9749
10747
|
init_paths();
|
|
9750
|
-
import { existsSync as
|
|
10748
|
+
import { existsSync as existsSync28, readFileSync as readFileSync31, unlinkSync as unlinkSync4 } from "fs";
|
|
9751
10749
|
function readRuntimeState(provider) {
|
|
9752
10750
|
const path = deployRuntimePath(provider);
|
|
9753
|
-
if (!
|
|
10751
|
+
if (!existsSync28(path)) return null;
|
|
9754
10752
|
try {
|
|
9755
|
-
return JSON.parse(
|
|
10753
|
+
return JSON.parse(readFileSync31(path, "utf-8"));
|
|
9756
10754
|
} catch {
|
|
9757
10755
|
return null;
|
|
9758
10756
|
}
|
|
@@ -9762,10 +10760,11 @@ function writeRuntimeState(provider, state) {
|
|
|
9762
10760
|
}
|
|
9763
10761
|
function clearRuntimeState(provider) {
|
|
9764
10762
|
const path = deployRuntimePath(provider);
|
|
9765
|
-
if (
|
|
10763
|
+
if (existsSync28(path)) unlinkSync4(path);
|
|
9766
10764
|
}
|
|
9767
10765
|
|
|
9768
10766
|
// src/cli/deploy/tailnet.ts
|
|
10767
|
+
init_exec();
|
|
9769
10768
|
function discoverNode(requestedName) {
|
|
9770
10769
|
const json = execSafe("tailscale status --json");
|
|
9771
10770
|
if (!json) return null;
|
|
@@ -9811,15 +10810,15 @@ function isTailscaleAvailable() {
|
|
|
9811
10810
|
}
|
|
9812
10811
|
|
|
9813
10812
|
// src/cli/deploy/templates.ts
|
|
9814
|
-
import { existsSync as
|
|
10813
|
+
import { existsSync as existsSync29 } from "fs";
|
|
9815
10814
|
import { dirname as dirname11, resolve as resolve10 } from "path";
|
|
9816
10815
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
9817
10816
|
function deployRoot() {
|
|
9818
10817
|
const here = dirname11(fileURLToPath4(import.meta.url));
|
|
9819
10818
|
const bundled = resolve10(here, "..", "deploy");
|
|
9820
|
-
if (
|
|
10819
|
+
if (existsSync29(bundled)) return bundled;
|
|
9821
10820
|
const sourceRoot = resolve10(here, "..", "..", "..", "deploy");
|
|
9822
|
-
if (
|
|
10821
|
+
if (existsSync29(sourceRoot)) return sourceRoot;
|
|
9823
10822
|
throw new Error(
|
|
9824
10823
|
`Could not locate deploy/ templates. Looked at:
|
|
9825
10824
|
${bundled}
|
|
@@ -10003,14 +11002,14 @@ function ensureTerraformInstalled() {
|
|
|
10003
11002
|
function ensureProviderStateDir(provider) {
|
|
10004
11003
|
ensureDeployDir();
|
|
10005
11004
|
const dir = deployProviderDir(provider);
|
|
10006
|
-
if (!
|
|
11005
|
+
if (!existsSync30(dir)) mkdirSync15(dir, { recursive: true, mode: 448 });
|
|
10007
11006
|
}
|
|
10008
11007
|
function backupState(provider) {
|
|
10009
11008
|
const src = deployStatePath(provider);
|
|
10010
|
-
if (
|
|
11009
|
+
if (existsSync30(src)) copyFileSync2(src, deployStateBackupPath(provider));
|
|
10011
11010
|
}
|
|
10012
11011
|
function readSshPubkey(path) {
|
|
10013
|
-
if (!
|
|
11012
|
+
if (!existsSync30(path)) {
|
|
10014
11013
|
const privateKeyPath = path.replace(/\.pub$/, "");
|
|
10015
11014
|
throw new Error(
|
|
10016
11015
|
`SSH pubkey not found at ${path}. Generate one with:
|
|
@@ -10018,7 +11017,7 @@ function readSshPubkey(path) {
|
|
|
10018
11017
|
or pass --ssh-key <path>.`
|
|
10019
11018
|
);
|
|
10020
11019
|
}
|
|
10021
|
-
return
|
|
11020
|
+
return readFileSync32(path, "utf-8").trim();
|
|
10022
11021
|
}
|
|
10023
11022
|
function readOutputs(provider) {
|
|
10024
11023
|
const result = spawnSync3("terraform", ["output", "-json", `-state=${deployStatePath(provider)}`], {
|
|
@@ -10045,7 +11044,7 @@ function readOutputs(provider) {
|
|
|
10045
11044
|
}
|
|
10046
11045
|
}
|
|
10047
11046
|
function isProvisioned(provider) {
|
|
10048
|
-
if (!
|
|
11047
|
+
if (!existsSync30(deployStatePath(provider))) return false;
|
|
10049
11048
|
return readOutputs(provider) !== null;
|
|
10050
11049
|
}
|
|
10051
11050
|
async function deployUp(provider, opts) {
|
|
@@ -10131,7 +11130,7 @@ Applied \u2014 but could not parse outputs. Run \`sis deploy ${provider} status\
|
|
|
10131
11130
|
console.log("");
|
|
10132
11131
|
}
|
|
10133
11132
|
async function deployDown(provider, opts) {
|
|
10134
|
-
if (!
|
|
11133
|
+
if (!existsSync30(deployStatePath(provider))) {
|
|
10135
11134
|
console.log(`No ${provider} state found at ${deployStatePath(provider)}. Nothing to destroy.`);
|
|
10136
11135
|
return;
|
|
10137
11136
|
}
|
|
@@ -10296,7 +11295,7 @@ function registerDeploy(program2) {
|
|
|
10296
11295
|
});
|
|
10297
11296
|
for (const provider of PROVIDERS) {
|
|
10298
11297
|
const sub = deploy.command(provider).description(`${provider} commands.`);
|
|
10299
|
-
sub.command("up").description(`Provision the ${provider} box (terraform init \u2192 plan \u2192 apply).`).option("--region <region>", `Provider region (defaults: hetzner=nbg1, aws=us-east-1).`).option("--arch <arch>", "'arm' (default) or 'x86'. Picks the default --size and image.", "arm").option("--size <size>", "Instance type override (defaults follow --arch).").option("--ssh-key <path>", "Path to SSH public key.",
|
|
11298
|
+
sub.command("up").description(`Provision the ${provider} box (terraform init \u2192 plan \u2192 apply).`).option("--region <region>", `Provider region (defaults: hetzner=nbg1, aws=us-east-1).`).option("--arch <arch>", "'arm' (default) or 'x86'. Picks the default --size and image.", "arm").option("--size <size>", "Instance type override (defaults follow --arch).").option("--ssh-key <path>", "Path to SSH public key.", join28(homedir13(), ".ssh", "id_ed25519.pub")).option("--no-chromium", "Skip headless Chromium install.").option("--no-auto-update", "Skip the daily auto-update systemd timer.").option("--name <name>", "Box hostname / Tailscale node name.", "sisyphus").option("-y, --yes", "Skip the re-provision confirmation prompt when state already exists.").action(async (raw) => {
|
|
10300
11299
|
const opts = resolveUpOptions(provider, raw);
|
|
10301
11300
|
await deployUp(provider, opts);
|
|
10302
11301
|
});
|
|
@@ -10320,11 +11319,14 @@ function registerDeploy(program2) {
|
|
|
10320
11319
|
|
|
10321
11320
|
// src/cli/cloud/runner.ts
|
|
10322
11321
|
init_paths();
|
|
11322
|
+
init_shell();
|
|
11323
|
+
init_exec();
|
|
11324
|
+
init_creds();
|
|
10323
11325
|
import { spawn as spawn4 } from "child_process";
|
|
10324
11326
|
import { hostname } from "os";
|
|
10325
|
-
init_creds();
|
|
10326
11327
|
|
|
10327
11328
|
// src/cli/deploy/ssh-exec.ts
|
|
11329
|
+
init_exec();
|
|
10328
11330
|
import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
|
|
10329
11331
|
function runOnBox(provider, cmd) {
|
|
10330
11332
|
const target = effectiveSshTarget(provider);
|
|
@@ -10355,6 +11357,7 @@ function runOnBoxStreaming(provider, cmd) {
|
|
|
10355
11357
|
}
|
|
10356
11358
|
|
|
10357
11359
|
// src/cli/cloud/grove.ts
|
|
11360
|
+
init_shell();
|
|
10358
11361
|
var GROVE_VERSION = "0.2.13";
|
|
10359
11362
|
function ensureGroveInstalled(provider) {
|
|
10360
11363
|
const probe = runOnBox(provider, "command -v grove >/dev/null 2>&1");
|
|
@@ -10374,9 +11377,10 @@ function ensureGroveRegistered(provider, repo, instancePath) {
|
|
|
10374
11377
|
}
|
|
10375
11378
|
|
|
10376
11379
|
// src/cli/cloud/repo.ts
|
|
11380
|
+
init_exec();
|
|
10377
11381
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
10378
|
-
import { existsSync as
|
|
10379
|
-
import { basename as basename6, join as
|
|
11382
|
+
import { existsSync as existsSync31 } from "fs";
|
|
11383
|
+
import { basename as basename6, join as join29 } from "path";
|
|
10380
11384
|
function captureGit(args2) {
|
|
10381
11385
|
const result = spawnSync5("git", args2, {
|
|
10382
11386
|
encoding: "utf-8",
|
|
@@ -10427,10 +11431,10 @@ function buildRsyncArgs(localDir, remoteTarget) {
|
|
|
10427
11431
|
];
|
|
10428
11432
|
}
|
|
10429
11433
|
function detectPackageManager(toplevel) {
|
|
10430
|
-
if (
|
|
10431
|
-
if (
|
|
10432
|
-
if (
|
|
10433
|
-
if (
|
|
11434
|
+
if (existsSync31(join29(toplevel, "pnpm-lock.yaml"))) return "pnpm";
|
|
11435
|
+
if (existsSync31(join29(toplevel, "bun.lockb"))) return "bun";
|
|
11436
|
+
if (existsSync31(join29(toplevel, "yarn.lock"))) return "yarn";
|
|
11437
|
+
if (existsSync31(join29(toplevel, "package-lock.json"))) return "npm";
|
|
10434
11438
|
return null;
|
|
10435
11439
|
}
|
|
10436
11440
|
function packageManagerInstallCmd(pm) {
|
|
@@ -10450,6 +11454,7 @@ function packageManagerInstallCmd(pm) {
|
|
|
10450
11454
|
|
|
10451
11455
|
// src/cli/cloud/sidecar.ts
|
|
10452
11456
|
init_paths();
|
|
11457
|
+
init_shell();
|
|
10453
11458
|
function readSidecar(provider, repo) {
|
|
10454
11459
|
const path = boxCloudSidecarPath(repo);
|
|
10455
11460
|
const result = runOnBox(provider, `cat ${shellQuoteHomePath(path)} 2>/dev/null`);
|
|
@@ -10658,6 +11663,7 @@ function pickProvider(explicit) {
|
|
|
10658
11663
|
}
|
|
10659
11664
|
|
|
10660
11665
|
// src/cli/commands/cloud.ts
|
|
11666
|
+
init_shell();
|
|
10661
11667
|
function resolve11(raw) {
|
|
10662
11668
|
const provider = pickProvider(raw.provider);
|
|
10663
11669
|
const repo = raw.name ? raw.name : inferRepoName();
|
|
@@ -10712,8 +11718,8 @@ function attachNotify(diagnostic2) {
|
|
|
10712
11718
|
|
|
10713
11719
|
// src/cli/commands/tmux-sessions.ts
|
|
10714
11720
|
init_paths();
|
|
10715
|
-
import { execSync as
|
|
10716
|
-
import { readFileSync as
|
|
11721
|
+
import { execSync as execSync18 } from "child_process";
|
|
11722
|
+
import { readFileSync as readFileSync33, existsSync as existsSync32 } from "fs";
|
|
10717
11723
|
var DOT_MAP = {
|
|
10718
11724
|
"orchestrator:processing": { icon: "\u25CF", color: "#d4ad6a" },
|
|
10719
11725
|
"orchestrator:idle": { icon: "\u25CF", color: "#d47766" },
|
|
@@ -10724,16 +11730,16 @@ var DOT_MAP = {
|
|
|
10724
11730
|
};
|
|
10725
11731
|
function readManifest() {
|
|
10726
11732
|
const p = sessionsManifestPath();
|
|
10727
|
-
if (!
|
|
11733
|
+
if (!existsSync32(p)) return null;
|
|
10728
11734
|
try {
|
|
10729
|
-
return JSON.parse(
|
|
11735
|
+
return JSON.parse(readFileSync33(p, "utf-8"));
|
|
10730
11736
|
} catch {
|
|
10731
11737
|
return null;
|
|
10732
11738
|
}
|
|
10733
11739
|
}
|
|
10734
11740
|
function tmuxExec(cmd) {
|
|
10735
11741
|
try {
|
|
10736
|
-
return
|
|
11742
|
+
return execSync18(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10737
11743
|
} catch {
|
|
10738
11744
|
return null;
|
|
10739
11745
|
}
|
|
@@ -10772,7 +11778,7 @@ if (nodeVersion < 22) {
|
|
|
10772
11778
|
var program = new Command();
|
|
10773
11779
|
program.name("sis").description("tmux-integrated orchestration daemon for Claude Code").version(
|
|
10774
11780
|
JSON.parse(
|
|
10775
|
-
|
|
11781
|
+
readFileSync34(join30(dirname12(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
|
|
10776
11782
|
).version
|
|
10777
11783
|
);
|
|
10778
11784
|
program.configureHelp({
|
|
@@ -10797,6 +11803,7 @@ registerReconnect(session);
|
|
|
10797
11803
|
registerClone(session);
|
|
10798
11804
|
registerSessionTask(session);
|
|
10799
11805
|
registerSessionEffort(session);
|
|
11806
|
+
registerSessionDangerous(session);
|
|
10800
11807
|
registerSessionContext(session);
|
|
10801
11808
|
var agent = program.command("agent").description("Manage agents");
|
|
10802
11809
|
registerSpawn(agent);
|
|
@@ -10813,6 +11820,8 @@ registerSegmentUnregister(segment);
|
|
|
10813
11820
|
var admin = program.command("admin").description("Admin / setup commands");
|
|
10814
11821
|
registerSetup(admin);
|
|
10815
11822
|
registerSetupKeybind(admin);
|
|
11823
|
+
registerCheckKeybinds(admin);
|
|
11824
|
+
registerCheckStatusbar(admin);
|
|
10816
11825
|
registerHomeInit(admin);
|
|
10817
11826
|
registerDoctor(admin);
|
|
10818
11827
|
registerInit(admin);
|
|
@@ -10843,8 +11852,8 @@ Run 'sis admin getting-started' for a complete usage guide.
|
|
|
10843
11852
|
var args = process.argv.slice(2);
|
|
10844
11853
|
var firstArg = args[0];
|
|
10845
11854
|
var skipWelcome = ["admin", "help", "--help", "-h", "--version", "-V"];
|
|
10846
|
-
if (!
|
|
10847
|
-
|
|
11855
|
+
if (!existsSync33(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
|
|
11856
|
+
mkdirSync16(globalDir(), { recursive: true });
|
|
10848
11857
|
console.log("");
|
|
10849
11858
|
console.log(" Welcome to Sisyphus. Run 'sis admin setup' to get started.");
|
|
10850
11859
|
console.log("");
|