sisyphi 1.1.34 → 1.1.36
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 +1480 -431
- 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 +0 -1
- 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/companion-plugin/hooks/user-prompt-context.sh +5 -2
- 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 +0 -1
- 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/companion-plugin/hooks/user-prompt-context.sh +5 -2
- 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 mkdirSync15, readFileSync as readFileSync31 } 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)) mkdirSync15(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(readFileSync31(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
|
|
671
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync17, readFileSync as readFileSync35 } from "fs";
|
|
672
|
+
import { dirname as dirname13, join as join31 } 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";
|
|
@@ -4163,16 +4559,55 @@ function readFileSafe(filePath) {
|
|
|
4163
4559
|
function escapeXml(s) {
|
|
4164
4560
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4165
4561
|
}
|
|
4166
|
-
function
|
|
4562
|
+
function buildSessionBlock(cwd, session2) {
|
|
4563
|
+
const lines = [];
|
|
4564
|
+
const nameAttr = session2.name ? ` name="${escapeXml(session2.name)}"` : "";
|
|
4565
|
+
lines.push(` <session id="${escapeXml(session2.id)}"${nameAttr} status="${escapeXml(session2.status)}">`);
|
|
4566
|
+
lines.push(` <task>${escapeXml(session2.task)}</task>`);
|
|
4567
|
+
lines.push(` <created>${escapeXml(session2.createdAt)}</created>`);
|
|
4568
|
+
lines.push(` <cycles>${session2.orchestratorCycles.length}</cycles>`);
|
|
4569
|
+
if (session2.status === "completed") {
|
|
4570
|
+
if (session2.completionReport) {
|
|
4571
|
+
const snippet = session2.completionReport.slice(0, 300).replace(/\n+/g, " ").trim();
|
|
4572
|
+
lines.push(` <completion-report>${escapeXml(snippet)}${session2.completionReport.length > 300 ? "\u2026" : ""}</completion-report>`);
|
|
4573
|
+
}
|
|
4574
|
+
} else {
|
|
4575
|
+
if (session2.agents.length > 0) {
|
|
4576
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4577
|
+
for (const agent2 of session2.agents) {
|
|
4578
|
+
counts.set(agent2.status, (counts.get(agent2.status) ?? 0) + 1);
|
|
4579
|
+
}
|
|
4580
|
+
const summary = [...counts.entries()].map(([status, n]) => `${n} ${status}`).join(", ");
|
|
4581
|
+
lines.push(` <agents>${escapeXml(summary)}</agents>`);
|
|
4582
|
+
}
|
|
4583
|
+
const goalContent = readFileSafe(goalPath(cwd, session2.id));
|
|
4584
|
+
if (goalContent) {
|
|
4585
|
+
const firstLine = goalContent.split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
|
|
4586
|
+
if (firstLine) lines.push(` <goal>${escapeXml(firstLine)}</goal>`);
|
|
4587
|
+
}
|
|
4588
|
+
const roadmapContent = readFileSafe(roadmapPath(cwd, session2.id));
|
|
4589
|
+
if (roadmapContent) {
|
|
4590
|
+
const todos = roadmapContent.split("\n").filter((l) => l.includes("- [ ]")).slice(0, 5).map((l) => l.trim());
|
|
4591
|
+
if (todos.length > 0) {
|
|
4592
|
+
lines.push(" <todos>");
|
|
4593
|
+
for (const todo of todos) lines.push(` ${escapeXml(todo)}`);
|
|
4594
|
+
lines.push(" </todos>");
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
lines.push(" </session>");
|
|
4599
|
+
return lines.join("\n");
|
|
4600
|
+
}
|
|
4601
|
+
function buildCompanionContextBlocks(cwd) {
|
|
4167
4602
|
let sessionDirs;
|
|
4168
4603
|
try {
|
|
4169
4604
|
sessionDirs = readdirSync4(sessionsDir(cwd));
|
|
4170
4605
|
} catch {
|
|
4171
|
-
return
|
|
4606
|
+
return {};
|
|
4172
4607
|
}
|
|
4173
4608
|
const now = Date.now();
|
|
4174
4609
|
const sevenDaysMs = 7 * 24 * 60 * 60 * 1e3;
|
|
4175
|
-
const
|
|
4610
|
+
const blocks = {};
|
|
4176
4611
|
for (const sessionId of sessionDirs) {
|
|
4177
4612
|
const stateRaw = readFileSafe(statePath(cwd, sessionId));
|
|
4178
4613
|
if (!stateRaw) continue;
|
|
@@ -4185,48 +4620,31 @@ function buildCompanionContext(cwd) {
|
|
|
4185
4620
|
if (session2.status === "completed" && session2.completedAt) {
|
|
4186
4621
|
if (now - new Date(session2.completedAt).getTime() > sevenDaysMs) continue;
|
|
4187
4622
|
}
|
|
4188
|
-
|
|
4189
|
-
const nameAttr = session2.name ? ` name="${escapeXml(session2.name)}"` : "";
|
|
4190
|
-
lines.push(` <session id="${escapeXml(session2.id)}"${nameAttr} status="${escapeXml(session2.status)}">`);
|
|
4191
|
-
lines.push(` <task>${escapeXml(session2.task)}</task>`);
|
|
4192
|
-
lines.push(` <created>${escapeXml(session2.createdAt)}</created>`);
|
|
4193
|
-
lines.push(` <cycles>${session2.orchestratorCycles.length}</cycles>`);
|
|
4194
|
-
if (session2.status === "completed") {
|
|
4195
|
-
if (session2.completionReport) {
|
|
4196
|
-
const snippet = session2.completionReport.slice(0, 300).replace(/\n+/g, " ").trim();
|
|
4197
|
-
lines.push(` <completion-report>${escapeXml(snippet)}${session2.completionReport.length > 300 ? "\u2026" : ""}</completion-report>`);
|
|
4198
|
-
}
|
|
4199
|
-
} else {
|
|
4200
|
-
if (session2.agents.length > 0) {
|
|
4201
|
-
const counts = /* @__PURE__ */ new Map();
|
|
4202
|
-
for (const agent2 of session2.agents) {
|
|
4203
|
-
counts.set(agent2.status, (counts.get(agent2.status) ?? 0) + 1);
|
|
4204
|
-
}
|
|
4205
|
-
const summary = [...counts.entries()].map(([status, n]) => `${n} ${status}`).join(", ");
|
|
4206
|
-
lines.push(` <agents>${escapeXml(summary)}</agents>`);
|
|
4207
|
-
}
|
|
4208
|
-
const goalContent = readFileSafe(goalPath(cwd, session2.id));
|
|
4209
|
-
if (goalContent) {
|
|
4210
|
-
const firstLine = goalContent.split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
|
|
4211
|
-
if (firstLine) lines.push(` <goal>${escapeXml(firstLine)}</goal>`);
|
|
4212
|
-
}
|
|
4213
|
-
const roadmapContent = readFileSafe(roadmapPath(cwd, session2.id));
|
|
4214
|
-
if (roadmapContent) {
|
|
4215
|
-
const todos = roadmapContent.split("\n").filter((l) => l.includes("- [ ]")).slice(0, 5).map((l) => l.trim());
|
|
4216
|
-
if (todos.length > 0) {
|
|
4217
|
-
lines.push(" <todos>");
|
|
4218
|
-
for (const todo of todos) lines.push(` ${escapeXml(todo)}`);
|
|
4219
|
-
lines.push(" </todos>");
|
|
4220
|
-
}
|
|
4221
|
-
}
|
|
4222
|
-
}
|
|
4223
|
-
lines.push(" </session>");
|
|
4224
|
-
sessionBlocks.push(lines.join("\n"));
|
|
4623
|
+
blocks[session2.id] = buildSessionBlock(cwd, session2);
|
|
4225
4624
|
}
|
|
4226
|
-
|
|
4227
|
-
|
|
4625
|
+
return blocks;
|
|
4626
|
+
}
|
|
4627
|
+
function renderFullContext(blocks) {
|
|
4628
|
+
const entries = Object.values(blocks);
|
|
4629
|
+
if (entries.length === 0) return "<sessions>No sessions found.</sessions>";
|
|
4630
|
+
return ["<sessions>", ...entries, "</sessions>"].join("\n");
|
|
4631
|
+
}
|
|
4632
|
+
function renderContextDelta(prev, next) {
|
|
4633
|
+
const ids = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
|
|
4634
|
+
const entries = [];
|
|
4635
|
+
for (const id of ids) {
|
|
4636
|
+
const p = prev[id];
|
|
4637
|
+
const n = next[id];
|
|
4638
|
+
if (p === void 0 && n !== void 0) {
|
|
4639
|
+
entries.push(n.replace(/^(\s*)<session /, `$1<session change="added" `));
|
|
4640
|
+
} else if (p !== void 0 && n === void 0) {
|
|
4641
|
+
entries.push(` <session id="${escapeXml(id)}" change="removed" />`);
|
|
4642
|
+
} else if (p !== n && n !== void 0) {
|
|
4643
|
+
entries.push(n.replace(/^(\s*)<session /, `$1<session change="updated" `));
|
|
4644
|
+
}
|
|
4228
4645
|
}
|
|
4229
|
-
|
|
4646
|
+
if (entries.length === 0) return null;
|
|
4647
|
+
return ["<sessions-changed-since-last-prompt>", ...entries, "</sessions-changed-since-last-prompt>"].join("\n");
|
|
4230
4648
|
}
|
|
4231
4649
|
function buildSessionContext(session2, cwd) {
|
|
4232
4650
|
const goal = readFileSafe(goalPath(cwd, session2.id));
|
|
@@ -4293,14 +4711,14 @@ function registerSessionContext(program2) {
|
|
|
4293
4711
|
}
|
|
4294
4712
|
|
|
4295
4713
|
// src/cli/commands/spawn.ts
|
|
4296
|
-
import { existsSync as
|
|
4297
|
-
import { join as
|
|
4714
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4715
|
+
import { join as join15, resolve as resolve5 } from "path";
|
|
4298
4716
|
|
|
4299
4717
|
// src/daemon/frontmatter.ts
|
|
4300
4718
|
init_paths();
|
|
4301
|
-
import { readFileSync as readFileSync16, existsSync as
|
|
4302
|
-
import { homedir as
|
|
4303
|
-
import { join as
|
|
4719
|
+
import { readFileSync as readFileSync16, existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
|
|
4720
|
+
import { homedir as homedir8 } from "os";
|
|
4721
|
+
import { join as join14, basename as basename4 } from "path";
|
|
4304
4722
|
function parseAgentFrontmatter(content) {
|
|
4305
4723
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
4306
4724
|
if (!match) return {};
|
|
@@ -4350,7 +4768,7 @@ function discoverAgentTypes(pluginDir, cwd) {
|
|
|
4350
4768
|
if (seen.has(qualifiedName)) continue;
|
|
4351
4769
|
seen.add(qualifiedName);
|
|
4352
4770
|
try {
|
|
4353
|
-
const content = readFileSync16(
|
|
4771
|
+
const content = readFileSync16(join14(dir, file), "utf-8");
|
|
4354
4772
|
const fm = parseAgentFrontmatter(content);
|
|
4355
4773
|
results.push({ qualifiedName, source, description: fm.description, model: fm.model });
|
|
4356
4774
|
} catch {
|
|
@@ -4358,13 +4776,13 @@ function discoverAgentTypes(pluginDir, cwd) {
|
|
|
4358
4776
|
}
|
|
4359
4777
|
}
|
|
4360
4778
|
}
|
|
4361
|
-
scanDir(
|
|
4362
|
-
scanDir(
|
|
4363
|
-
scanDir(
|
|
4364
|
-
scanDir(
|
|
4365
|
-
scanDir(
|
|
4779
|
+
scanDir(join14(projectAgentPluginDir(cwd), "agents"), null, "project-sis");
|
|
4780
|
+
scanDir(join14(userAgentPluginDir(), "agents"), null, "user-sis");
|
|
4781
|
+
scanDir(join14(cwd, ".claude", "agents"), null, "project");
|
|
4782
|
+
scanDir(join14(homedir8(), ".claude", "agents"), null, "user");
|
|
4783
|
+
scanDir(join14(pluginDir, "agents"), "sisyphus", "bundled");
|
|
4366
4784
|
try {
|
|
4367
|
-
const registryPath =
|
|
4785
|
+
const registryPath = join14(homedir8(), ".claude", "plugins", "installed_plugins.json");
|
|
4368
4786
|
const registry = JSON.parse(readFileSync16(registryPath, "utf-8"));
|
|
4369
4787
|
const pluginEntries = registry.plugins ?? registry;
|
|
4370
4788
|
for (const key of Object.keys(pluginEntries)) {
|
|
@@ -4374,7 +4792,7 @@ function discoverAgentTypes(pluginDir, cwd) {
|
|
|
4374
4792
|
const entry = pluginEntries[key];
|
|
4375
4793
|
const installPath = Array.isArray(entry) ? entry[0]?.installPath : entry?.installPath;
|
|
4376
4794
|
if (installPath) {
|
|
4377
|
-
scanDir(
|
|
4795
|
+
scanDir(join14(installPath, "agents"), namespace, "plugin");
|
|
4378
4796
|
}
|
|
4379
4797
|
}
|
|
4380
4798
|
} catch {
|
|
@@ -4444,8 +4862,8 @@ function registerSpawn(program2) {
|
|
|
4444
4862
|
process.exit(1);
|
|
4445
4863
|
}
|
|
4446
4864
|
if (opts.repo && opts.repo !== ".") {
|
|
4447
|
-
const repoPath =
|
|
4448
|
-
if (!
|
|
4865
|
+
const repoPath = join15(sisyphusCwd, opts.repo);
|
|
4866
|
+
if (!existsSync13(repoPath)) {
|
|
4449
4867
|
console.error(`Error: repo directory does not exist: ${repoPath}`);
|
|
4450
4868
|
process.exit(1);
|
|
4451
4869
|
}
|
|
@@ -4552,7 +4970,7 @@ function registerReport(program2) {
|
|
|
4552
4970
|
}
|
|
4553
4971
|
|
|
4554
4972
|
// src/cli/commands/await.ts
|
|
4555
|
-
import { existsSync as
|
|
4973
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
|
|
4556
4974
|
var AWAIT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
|
|
4557
4975
|
function registerAwait(program2) {
|
|
4558
4976
|
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 +4994,7 @@ function registerAwait(program2) {
|
|
|
4576
4994
|
const shortType = agentType && agentType !== "worker" ? agentType.replace(/^sisyphus:/, "") : "";
|
|
4577
4995
|
const label = shortType ? `${shortType}-${agentName}` : agentName;
|
|
4578
4996
|
console.log(`[${status}] ${agentId} (${label})`);
|
|
4579
|
-
if (reportPath &&
|
|
4997
|
+
if (reportPath && existsSync14(reportPath)) {
|
|
4580
4998
|
try {
|
|
4581
4999
|
const body = readFileSync17(reportPath, "utf-8");
|
|
4582
5000
|
if (body.length > 0) {
|
|
@@ -4706,9 +5124,9 @@ import { execSync as execSync10 } from "child_process";
|
|
|
4706
5124
|
|
|
4707
5125
|
// src/cli/onboard.ts
|
|
4708
5126
|
import { execSync as execSync9 } from "child_process";
|
|
4709
|
-
import { existsSync as
|
|
4710
|
-
import { homedir as
|
|
4711
|
-
import { dirname as dirname5, join as
|
|
5127
|
+
import { existsSync as existsSync15, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
|
|
5128
|
+
import { homedir as homedir9 } from "os";
|
|
5129
|
+
import { dirname as dirname5, join as join16 } from "path";
|
|
4712
5130
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4713
5131
|
function detectTerminal() {
|
|
4714
5132
|
const termProgram = process.env["TERM_PROGRAM"] || "";
|
|
@@ -4745,8 +5163,8 @@ function checkItermOptionKey() {
|
|
|
4745
5163
|
if (process.platform !== "darwin") {
|
|
4746
5164
|
return { checked: false, allCorrect: true, incorrectProfiles: [] };
|
|
4747
5165
|
}
|
|
4748
|
-
const plistPath2 =
|
|
4749
|
-
if (!
|
|
5166
|
+
const plistPath2 = join16(homedir9(), "Library", "Preferences", "com.googlecode.iterm2.plist");
|
|
5167
|
+
if (!existsSync15(plistPath2)) {
|
|
4750
5168
|
return { checked: false, allCorrect: false, incorrectProfiles: [] };
|
|
4751
5169
|
}
|
|
4752
5170
|
try {
|
|
@@ -4770,7 +5188,7 @@ function checkItermOptionKey() {
|
|
|
4770
5188
|
}
|
|
4771
5189
|
}
|
|
4772
5190
|
function hasExistingTmuxConf() {
|
|
4773
|
-
return
|
|
5191
|
+
return existsSync15(join16(homedir9(), ".tmux.conf")) || existsSync15(join16(homedir9(), ".config", "tmux", "tmux.conf"));
|
|
4774
5192
|
}
|
|
4775
5193
|
var SISYPHUS_DEFAULTS_MARKER = "# sisyphus-managed \u2014 do not edit";
|
|
4776
5194
|
function buildTmuxDefaults() {
|
|
@@ -4882,7 +5300,7 @@ source-file -q ${sisyphusConf} ${SISYPHUS_DEFAULTS_MARKER}
|
|
|
4882
5300
|
`;
|
|
4883
5301
|
}
|
|
4884
5302
|
function writeTmuxDefaults() {
|
|
4885
|
-
const confPath =
|
|
5303
|
+
const confPath = join16(homedir9(), ".tmux.conf");
|
|
4886
5304
|
writeFileSync8(confPath, buildTmuxDefaults(), "utf8");
|
|
4887
5305
|
}
|
|
4888
5306
|
function isNvimAvailable() {
|
|
@@ -4901,19 +5319,19 @@ function getNvimVersion() {
|
|
|
4901
5319
|
}
|
|
4902
5320
|
}
|
|
4903
5321
|
function hasLazyVimConfig() {
|
|
4904
|
-
return
|
|
5322
|
+
return existsSync15(join16(homedir9(), ".config", "nvim", "lazy-lock.json"));
|
|
4905
5323
|
}
|
|
4906
5324
|
function bundledBaleiaPluginPath() {
|
|
4907
5325
|
const distDir = dirname5(fileURLToPath2(import.meta.url));
|
|
4908
|
-
return
|
|
5326
|
+
return join16(distDir, "templates", "baleia.lua");
|
|
4909
5327
|
}
|
|
4910
5328
|
function installBaleiaPlugin() {
|
|
4911
|
-
const pluginsDir =
|
|
4912
|
-
if (!
|
|
4913
|
-
const dest =
|
|
4914
|
-
if (
|
|
5329
|
+
const pluginsDir = join16(homedir9(), ".config", "nvim", "lua", "plugins");
|
|
5330
|
+
if (!existsSync15(pluginsDir)) return false;
|
|
5331
|
+
const dest = join16(pluginsDir, "sisyphus-baleia.lua");
|
|
5332
|
+
if (existsSync15(dest)) return true;
|
|
4915
5333
|
const src = bundledBaleiaPluginPath();
|
|
4916
|
-
if (!
|
|
5334
|
+
if (!existsSync15(src)) return false;
|
|
4917
5335
|
try {
|
|
4918
5336
|
writeFileSync8(dest, readFileSync18(src, "utf-8"), "utf8");
|
|
4919
5337
|
return true;
|
|
@@ -4938,9 +5356,9 @@ function tryAutoInstallNvim() {
|
|
|
4938
5356
|
if (!isNvimAvailable()) {
|
|
4939
5357
|
return { installed: false, autoInstalled: false, version: "", lazyVimInstalled: false, baleiaInstalled: false };
|
|
4940
5358
|
}
|
|
4941
|
-
const nvimConfigDir =
|
|
5359
|
+
const nvimConfigDir = join16(homedir9(), ".config", "nvim");
|
|
4942
5360
|
let lazyVimInstalled = false;
|
|
4943
|
-
if (!
|
|
5361
|
+
if (!existsSync15(nvimConfigDir)) {
|
|
4944
5362
|
const cloneCmd = [
|
|
4945
5363
|
"git",
|
|
4946
5364
|
"-c core.hooksPath=/dev/null",
|
|
@@ -4956,8 +5374,8 @@ function tryAutoInstallNvim() {
|
|
|
4956
5374
|
stdio: "inherit",
|
|
4957
5375
|
env: { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" }
|
|
4958
5376
|
});
|
|
4959
|
-
const gitDir =
|
|
4960
|
-
if (
|
|
5377
|
+
const gitDir = join16(nvimConfigDir, ".git");
|
|
5378
|
+
if (existsSync15(gitDir)) {
|
|
4961
5379
|
execSync9(`rm -rf "${gitDir}"`, { stdio: "pipe" });
|
|
4962
5380
|
}
|
|
4963
5381
|
lazyVimInstalled = true;
|
|
@@ -5128,7 +5546,7 @@ function printResults(result, daemonOk, keybindMsg) {
|
|
|
5128
5546
|
console.log("");
|
|
5129
5547
|
}
|
|
5130
5548
|
function registerSetup(program2) {
|
|
5131
|
-
program2.command("setup").description("One-time setup: install dependencies, daemon, keybindings, and commands").option("-y, --yes", "Skip
|
|
5549
|
+
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
5550
|
const result = runOnboarding();
|
|
5133
5551
|
let daemonOk = false;
|
|
5134
5552
|
try {
|
|
@@ -5140,11 +5558,15 @@ function registerSetup(program2) {
|
|
|
5140
5558
|
const keybindResult = await setupTmuxKeybind(
|
|
5141
5559
|
DEFAULT_CYCLE_KEY,
|
|
5142
5560
|
DEFAULT_PREFIX_KEY,
|
|
5143
|
-
{ assumeYes: opts.yes }
|
|
5561
|
+
{ assumeYes: opts.yes, force: opts.force }
|
|
5144
5562
|
);
|
|
5145
5563
|
let keybindMsg;
|
|
5146
5564
|
if (keybindResult.status === "installed" || keybindResult.status === "already-installed") {
|
|
5147
5565
|
keybindMsg = `${DEFAULT_CYCLE_KEY} cycle, ${DEFAULT_PREFIX_KEY} prefix (h=dashboard, x=kill)`;
|
|
5566
|
+
} else if (keybindResult.status === "requires-force" || keybindResult.status === "conflict") {
|
|
5567
|
+
keybindMsg = `${keybindResult.message}
|
|
5568
|
+
Run "sis admin check-keybinds" for the full decision tree, then re-run "sis admin setup --force" to proceed.`;
|
|
5569
|
+
process.exitCode = 1;
|
|
5148
5570
|
} else {
|
|
5149
5571
|
keybindMsg = keybindResult.message;
|
|
5150
5572
|
}
|
|
@@ -5154,9 +5576,12 @@ function registerSetup(program2) {
|
|
|
5154
5576
|
|
|
5155
5577
|
// src/cli/commands/setup-keybind.ts
|
|
5156
5578
|
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, {
|
|
5579
|
+
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) => {
|
|
5580
|
+
const resolvedKey = key === void 0 ? DEFAULT_CYCLE_KEY : key;
|
|
5581
|
+
const result = await setupTmuxKeybind(resolvedKey, void 0, {
|
|
5582
|
+
assumeYes: opts.yes,
|
|
5583
|
+
force: opts.force
|
|
5584
|
+
});
|
|
5160
5585
|
switch (result.status) {
|
|
5161
5586
|
case "installed":
|
|
5162
5587
|
console.log(result.message);
|
|
@@ -5166,28 +5591,602 @@ function registerSetupKeybind(program2) {
|
|
|
5166
5591
|
console.log(result.message);
|
|
5167
5592
|
break;
|
|
5168
5593
|
case "conflict":
|
|
5169
|
-
console.log(`Key ${
|
|
5594
|
+
console.log(`Key ${result.conflictKey} is already bound:`);
|
|
5170
5595
|
console.log(` ${result.existingBinding}`);
|
|
5171
5596
|
console.log("");
|
|
5172
|
-
console.log("
|
|
5173
|
-
console.log(" sis admin setup-keybind M-
|
|
5174
|
-
console.log("
|
|
5175
|
-
console.log(" sis admin setup-keybind
|
|
5597
|
+
console.log("Options:");
|
|
5598
|
+
console.log(" - Pick a different cycle key: sis admin setup-keybind M-w");
|
|
5599
|
+
console.log(' - Run "sis admin check-keybinds" for a full breakdown');
|
|
5600
|
+
console.log(" - Override and overwrite: sis admin setup-keybind --force");
|
|
5601
|
+
process.exitCode = 1;
|
|
5176
5602
|
break;
|
|
5177
5603
|
case "unsupported-tmux":
|
|
5178
5604
|
console.log(result.message);
|
|
5605
|
+
process.exitCode = 1;
|
|
5606
|
+
break;
|
|
5607
|
+
case "requires-force":
|
|
5608
|
+
console.log(result.message);
|
|
5609
|
+
console.log("");
|
|
5610
|
+
console.log('Run "sis admin check-keybinds" first if you want the full decision tree before deciding.');
|
|
5611
|
+
process.exitCode = 1;
|
|
5179
5612
|
break;
|
|
5180
5613
|
case "conf-modification-declined":
|
|
5181
5614
|
console.log(result.message);
|
|
5182
5615
|
console.log("");
|
|
5183
|
-
console.log("Re-run with --
|
|
5616
|
+
console.log("Re-run with --force to append automatically.");
|
|
5184
5617
|
break;
|
|
5185
5618
|
}
|
|
5186
5619
|
});
|
|
5187
5620
|
}
|
|
5188
5621
|
|
|
5189
|
-
// src/cli/commands/
|
|
5622
|
+
// src/cli/commands/check-keybinds.ts
|
|
5190
5623
|
import { execSync as execSync11 } from "child_process";
|
|
5624
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
5625
|
+
function isTmuxInstalled2() {
|
|
5626
|
+
try {
|
|
5627
|
+
execSync11("which tmux", { stdio: "pipe" });
|
|
5628
|
+
return true;
|
|
5629
|
+
} catch {
|
|
5630
|
+
return false;
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
function getTmuxVersion2() {
|
|
5634
|
+
try {
|
|
5635
|
+
return execSync11("tmux -V", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
5636
|
+
} catch {
|
|
5637
|
+
return null;
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
function isTmuxServerRunning() {
|
|
5641
|
+
try {
|
|
5642
|
+
execSync11("tmux list-sessions", { stdio: ["pipe", "pipe", "pipe"] });
|
|
5643
|
+
return true;
|
|
5644
|
+
} catch {
|
|
5645
|
+
return false;
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
function getTmuxPrefix() {
|
|
5649
|
+
try {
|
|
5650
|
+
return execSync11("tmux show-options -gv prefix", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim() || null;
|
|
5651
|
+
} catch {
|
|
5652
|
+
return null;
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
function classifyKey(key, serverRunning) {
|
|
5656
|
+
if (!serverRunning) return { kind: "unbound" };
|
|
5657
|
+
const binding = getExistingBinding(key);
|
|
5658
|
+
if (binding === null) return { kind: "unbound" };
|
|
5659
|
+
if (isSisyphusBinding(binding)) return { kind: "sisyphus", binding };
|
|
5660
|
+
return { kind: "conflict", binding };
|
|
5661
|
+
}
|
|
5662
|
+
function runCheck() {
|
|
5663
|
+
const tmuxInstalled = isTmuxInstalled2();
|
|
5664
|
+
const tmuxVersion = tmuxInstalled ? getTmuxVersion2() : null;
|
|
5665
|
+
const tmuxVersionOk = tmuxInstalled ? tmuxVersionAtLeast(3, 2) : false;
|
|
5666
|
+
const tmuxServerRunning = tmuxInstalled ? isTmuxServerRunning() : false;
|
|
5667
|
+
const inTmux = !!process.env["TMUX"];
|
|
5668
|
+
const cycleKey = { key: DEFAULT_CYCLE_KEY, ...classifyKey(DEFAULT_CYCLE_KEY, tmuxServerRunning) };
|
|
5669
|
+
const prefixKey = { key: DEFAULT_PREFIX_KEY, ...classifyKey(DEFAULT_PREFIX_KEY, tmuxServerRunning) };
|
|
5670
|
+
const tmuxPrefix = tmuxServerRunning ? getTmuxPrefix() : null;
|
|
5671
|
+
const prefixCollision = tmuxPrefix !== null && tmuxPrefix === DEFAULT_PREFIX_KEY;
|
|
5672
|
+
const sisyphusConfPath = sisyphusTmuxConfPath();
|
|
5673
|
+
const userConfPath = userTmuxConfPath();
|
|
5674
|
+
let userConfAlreadySources = false;
|
|
5675
|
+
if (userConfPath !== null) {
|
|
5676
|
+
try {
|
|
5677
|
+
userConfAlreadySources = readFileSync19(userConfPath, "utf-8").includes(sisyphusConfPath);
|
|
5678
|
+
} catch {
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
return {
|
|
5682
|
+
tmuxInstalled,
|
|
5683
|
+
tmuxServerRunning,
|
|
5684
|
+
tmuxVersion,
|
|
5685
|
+
tmuxVersionOk,
|
|
5686
|
+
inTmux,
|
|
5687
|
+
cycleKey,
|
|
5688
|
+
prefixKey,
|
|
5689
|
+
tmuxPrefix,
|
|
5690
|
+
prefixCollision,
|
|
5691
|
+
userConfPath,
|
|
5692
|
+
userConfAlreadySources,
|
|
5693
|
+
sisyphusConfPath
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5696
|
+
function fmtKeyState(k) {
|
|
5697
|
+
switch (k.kind) {
|
|
5698
|
+
case "sisyphus":
|
|
5699
|
+
return `${k.key}: sisyphus (${k.binding})`;
|
|
5700
|
+
case "conflict":
|
|
5701
|
+
return `${k.key}: CONFLICT \u2014 already bound: ${k.binding}`;
|
|
5702
|
+
case "unbound":
|
|
5703
|
+
return `${k.key}: unbound`;
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
function printInstructions(r) {
|
|
5707
|
+
const bothSisyphus = r.cycleKey.kind === "sisyphus" && r.prefixKey.kind === "sisyphus";
|
|
5708
|
+
const anyConflict = r.cycleKey.kind === "conflict" || r.prefixKey.kind === "conflict";
|
|
5709
|
+
const wouldModifyUserConf = r.userConfPath !== null && !r.userConfAlreadySources;
|
|
5710
|
+
const safeAutoInstall = !anyConflict && !r.prefixCollision && !wouldModifyUserConf;
|
|
5711
|
+
const tmuxVersionLabel = r.tmuxVersion === null ? "(none)" : r.tmuxVersion;
|
|
5712
|
+
const tmuxPrefixLabel = r.tmuxPrefix === null ? "(default C-b)" : r.tmuxPrefix;
|
|
5713
|
+
const userConfLabel = r.userConfPath === null ? "(none found)" : r.userConfPath;
|
|
5714
|
+
const userConfForCopy = r.userConfPath === null ? "~/.tmux.conf" : r.userConfPath;
|
|
5715
|
+
console.log(`
|
|
5716
|
+
<claude-instructions>
|
|
5717
|
+
# Sisyphus Keybind Precheck
|
|
5718
|
+
|
|
5719
|
+
Run this BEFORE asking the user to try any sisyphus keybind (e.g. \`Right Option+s\`,
|
|
5720
|
+
\`C-s h\`, \`C-s ?\`). If the keybinds aren't actually wired up, the user will press
|
|
5721
|
+
the key and nothing will happen \u2014 confusing, looks broken.
|
|
5722
|
+
|
|
5723
|
+
## Environment Data
|
|
5724
|
+
- tmuxInstalled: ${r.tmuxInstalled}
|
|
5725
|
+
- tmuxVersion: ${tmuxVersionLabel}
|
|
5726
|
+
- tmuxVersionOk: ${r.tmuxVersionOk} (sisyphus needs 3.2+ for display-menu)
|
|
5727
|
+
- tmuxServerRunning: ${r.tmuxServerRunning}
|
|
5728
|
+
- inTmux ($TMUX set): ${r.inTmux}
|
|
5729
|
+
- ${fmtKeyState(r.cycleKey)}
|
|
5730
|
+
- ${fmtKeyState(r.prefixKey)}
|
|
5731
|
+
- tmuxPrefix (\`set -g prefix\`): ${tmuxPrefixLabel}
|
|
5732
|
+
- prefixCollision: ${r.prefixCollision} (true = user's tmux prefix is ${DEFAULT_PREFIX_KEY}; binding it in root would shadow it)
|
|
5733
|
+
- userConfPath: ${userConfLabel}
|
|
5734
|
+
- userConfAlreadySources: ${r.userConfAlreadySources}
|
|
5735
|
+
- sisyphusConfPath: ${r.sisyphusConfPath}
|
|
5736
|
+
|
|
5737
|
+
## Decision
|
|
5738
|
+
|
|
5739
|
+
Pick exactly one path. Do NOT proceed past this check until both keys read "sisyphus".
|
|
5740
|
+
|
|
5741
|
+
### Path A \u2014 All wired, proceed
|
|
5742
|
+
**Trigger:** cycleKey is "sisyphus" AND prefixKey is "sisyphus".
|
|
5743
|
+
**Action:** ${bothSisyphus ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} Confirm briefly and continue with the onboarding step that needs keybinds.
|
|
5744
|
+
|
|
5745
|
+
### Path B \u2014 Safe to auto-install
|
|
5746
|
+
**Trigger:** No conflicts, no prefix collision, AND (no user tmux.conf OR it already sources sisyphus).
|
|
5747
|
+
**Action:** ${safeAutoInstall && !bothSisyphus ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} Run:
|
|
5748
|
+
\`\`\`
|
|
5749
|
+
sis admin setup-keybind --yes
|
|
5750
|
+
\`\`\`
|
|
5751
|
+
This installs the helper scripts in ~/.sisyphus/bin/ and applies the bindings to the live
|
|
5752
|
+
tmux server. No user files are clobbered (none to clobber, or already wired).
|
|
5753
|
+
|
|
5754
|
+
### Path C \u2014 Would append a line to the user's tmux.conf
|
|
5755
|
+
**Trigger:** No conflicts, no prefix collision, but ${userConfForCopy} exists and doesn't yet source ${r.sisyphusConfPath}.
|
|
5756
|
+
**Action:** ${!safeAutoInstall && !anyConflict && !r.prefixCollision && wouldModifyUserConf && !bothSisyphus ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} Ask the user to choose:
|
|
5757
|
+
|
|
5758
|
+
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."
|
|
5759
|
+
Run: \`sis admin setup-keybind --yes\`
|
|
5760
|
+
|
|
5761
|
+
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."
|
|
5762
|
+
Run: \`sis admin setup-keybind\` (no --yes; non-TTY auto-declines the conf prompt while still applying live bindings + installing helper scripts)
|
|
5763
|
+
|
|
5764
|
+
### Path D \u2014 Conflict on ${DEFAULT_CYCLE_KEY} or ${DEFAULT_PREFIX_KEY}
|
|
5765
|
+
**Trigger:** cycleKey or prefixKey is "CONFLICT".
|
|
5766
|
+
**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:
|
|
5767
|
+
|
|
5768
|
+
1. **Pick alternate keys.** Re-run with a different cycle key \u2014 e.g. \`M-S\`, \`M-w\`, \`M-j\`, \`M-\\\`\`:
|
|
5769
|
+
\`\`\`
|
|
5770
|
+
sis admin setup-keybind M-w
|
|
5771
|
+
\`\`\`
|
|
5772
|
+
This still uses C-s for the prefix; if the prefix also conflicts, you'll need to wire
|
|
5773
|
+
directly (option 3 below), since setup-keybind only takes a custom cycle key.
|
|
5774
|
+
|
|
5775
|
+
2. **Skip keybinds entirely.** The user can drive sisyphus from the CLI: \`sis dashboard\`,
|
|
5776
|
+
\`sis status\`, \`sis start\`, \`sis session resume\`. Lose tmux quick-actions, keep
|
|
5777
|
+
existing bindings.
|
|
5778
|
+
|
|
5779
|
+
3. **Wire commands directly (advanced).** Bypass setup-keybind and bind individual
|
|
5780
|
+
sisyphus actions to keys the user picks. Helper scripts must already exist \u2014 if
|
|
5781
|
+
~/.sisyphus/bin/sisyphus-cycle is missing, you cannot use this path until setup-keybind
|
|
5782
|
+
has run at least once successfully (try option 1 first).
|
|
5783
|
+
\`\`\`
|
|
5784
|
+
# cycle sessions on a key the user chooses (replace M-w):
|
|
5785
|
+
tmux bind-key -T root M-w run-shell "$HOME/.sisyphus/bin/sisyphus-cycle"
|
|
5786
|
+
|
|
5787
|
+
# open the dashboard directly on a key (replace M-h):
|
|
5788
|
+
tmux bind-key -T root M-h run-shell "$HOME/.sisyphus/bin/sisyphus-home"
|
|
5789
|
+
\`\`\`
|
|
5790
|
+
These apply only to the running tmux server. To persist, append the same lines to
|
|
5791
|
+
${userConfForCopy}.
|
|
5792
|
+
|
|
5793
|
+
### Path E \u2014 Hidden prefix collision
|
|
5794
|
+
**Trigger:** prefixCollision is true (the user's \`set -g prefix\` is already ${DEFAULT_PREFIX_KEY}).
|
|
5795
|
+
**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:
|
|
5796
|
+
|
|
5797
|
+
> "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."
|
|
5798
|
+
|
|
5799
|
+
For (b), wire just the cycle key directly:
|
|
5800
|
+
\`\`\`
|
|
5801
|
+
sis admin setup-keybind ${DEFAULT_CYCLE_KEY}
|
|
5802
|
+
# then unbind C-s if setup-keybind ended up taking it:
|
|
5803
|
+
tmux unbind-key -T root ${DEFAULT_PREFIX_KEY}
|
|
5804
|
+
\`\`\`
|
|
5805
|
+
|
|
5806
|
+
### Path F \u2014 tmux not ready
|
|
5807
|
+
**Trigger:** any of: tmuxInstalled=false, tmuxVersionOk=false, tmuxServerRunning=false.
|
|
5808
|
+
**Action:** ${!r.tmuxInstalled || !r.tmuxVersionOk || !r.tmuxServerRunning ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"} Don't install keybinds yet. Fix the precondition:
|
|
5809
|
+
- tmuxInstalled=false \u2192 \`brew install tmux\` (macOS) or your package manager
|
|
5810
|
+
- tmuxVersionOk=false \u2192 upgrade tmux to 3.2+
|
|
5811
|
+
- tmuxServerRunning=false \u2192 user needs to run \`tmux\` (or attach to an existing session) before live bindings can be installed or tested
|
|
5812
|
+
|
|
5813
|
+
After fixing, re-run \`sis admin check-keybinds\`.
|
|
5814
|
+
|
|
5815
|
+
## After acting
|
|
5816
|
+
Re-run \`sis admin check-keybinds\` to confirm both keys read "sisyphus", THEN ask the
|
|
5817
|
+
user to try the keybind. Don't skip the verification \u2014 \`setup-keybind\` can fail silently
|
|
5818
|
+
if the tmux server dies between commands.
|
|
5819
|
+
</claude-instructions>
|
|
5820
|
+
`);
|
|
5821
|
+
}
|
|
5822
|
+
function registerCheckKeybinds(program2) {
|
|
5823
|
+
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) => {
|
|
5824
|
+
const result = runCheck();
|
|
5825
|
+
if (opts.json) {
|
|
5826
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5827
|
+
return;
|
|
5828
|
+
}
|
|
5829
|
+
printInstructions(result);
|
|
5830
|
+
});
|
|
5831
|
+
}
|
|
5832
|
+
|
|
5833
|
+
// src/cli/commands/check-statusbar.ts
|
|
5834
|
+
init_paths();
|
|
5835
|
+
import { execSync as execSync12 } from "child_process";
|
|
5836
|
+
import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
|
|
5837
|
+
import { homedir as homedir10 } from "os";
|
|
5838
|
+
import { join as join17 } from "path";
|
|
5839
|
+
var SISYPHUS_LEFT_TOKEN = "@sisyphus_left";
|
|
5840
|
+
var SISYPHUS_RIGHT_TOKEN = "@sisyphus_right";
|
|
5841
|
+
var TMUX_DEFAULT_STATUS_LEFT = "[#S] ";
|
|
5842
|
+
var TMUX_DEFAULT_STATUS_RIGHT_PREFIX = '"#{=21:pane_title}"';
|
|
5843
|
+
function isTmuxInstalled3() {
|
|
5844
|
+
try {
|
|
5845
|
+
execSync12("which tmux", { stdio: "pipe" });
|
|
5846
|
+
return true;
|
|
5847
|
+
} catch {
|
|
5848
|
+
return false;
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
function isTmuxServerRunning2() {
|
|
5852
|
+
try {
|
|
5853
|
+
execSync12("tmux list-sessions", { stdio: ["pipe", "pipe", "pipe"] });
|
|
5854
|
+
return true;
|
|
5855
|
+
} catch {
|
|
5856
|
+
return false;
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
function isDaemonRunning() {
|
|
5860
|
+
const pidFile = daemonPidPath();
|
|
5861
|
+
if (!existsSync16(pidFile)) return false;
|
|
5862
|
+
try {
|
|
5863
|
+
const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
|
|
5864
|
+
if (Number.isNaN(pid) || pid <= 0) return false;
|
|
5865
|
+
process.kill(pid, 0);
|
|
5866
|
+
return true;
|
|
5867
|
+
} catch {
|
|
5868
|
+
return false;
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
function showOption(name) {
|
|
5872
|
+
try {
|
|
5873
|
+
const out = execSync12(`tmux show-options -g ${name}`, { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
5874
|
+
if (out.length === 0) return null;
|
|
5875
|
+
const prefix = `${name} `;
|
|
5876
|
+
const stripped = out.startsWith(prefix) ? out.slice(prefix.length) : out;
|
|
5877
|
+
if (stripped.startsWith('"') && stripped.endsWith('"') && stripped.length >= 2) {
|
|
5878
|
+
return stripped.slice(1, -1);
|
|
5879
|
+
}
|
|
5880
|
+
return stripped;
|
|
5881
|
+
} catch {
|
|
5882
|
+
return null;
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
function probeTmuxOptions(serverRunning) {
|
|
5886
|
+
if (!serverRunning) {
|
|
5887
|
+
return {
|
|
5888
|
+
status: null,
|
|
5889
|
+
statusLeft: null,
|
|
5890
|
+
statusRight: null,
|
|
5891
|
+
statusPosition: null,
|
|
5892
|
+
statusStyle: null,
|
|
5893
|
+
statusInterval: null
|
|
5894
|
+
};
|
|
5895
|
+
}
|
|
5896
|
+
return {
|
|
5897
|
+
status: showOption("status"),
|
|
5898
|
+
statusLeft: showOption("status-left"),
|
|
5899
|
+
statusRight: showOption("status-right"),
|
|
5900
|
+
statusPosition: showOption("status-position"),
|
|
5901
|
+
statusStyle: showOption("status-style"),
|
|
5902
|
+
statusInterval: showOption("status-interval")
|
|
5903
|
+
};
|
|
5904
|
+
}
|
|
5905
|
+
function findUserTmuxConf() {
|
|
5906
|
+
const xdg = join17(homedir10(), ".config", "tmux", "tmux.conf");
|
|
5907
|
+
const dotfile = join17(homedir10(), ".tmux.conf");
|
|
5908
|
+
if (existsSync16(xdg)) return xdg;
|
|
5909
|
+
if (existsSync16(dotfile)) return dotfile;
|
|
5910
|
+
return null;
|
|
5911
|
+
}
|
|
5912
|
+
function probeUserConf() {
|
|
5913
|
+
const path = findUserTmuxConf();
|
|
5914
|
+
if (path === null) {
|
|
5915
|
+
return { path: null, setsStatusLeft: false, setsStatusRight: false, sourcesSisyphusManaged: false };
|
|
5916
|
+
}
|
|
5917
|
+
let contents = "";
|
|
5918
|
+
try {
|
|
5919
|
+
contents = readFileSync20(path, "utf-8");
|
|
5920
|
+
} catch {
|
|
5921
|
+
return { path, setsStatusLeft: false, setsStatusRight: false, sourcesSisyphusManaged: false };
|
|
5922
|
+
}
|
|
5923
|
+
const lines = contents.split("\n").filter((line) => !line.trim().startsWith("#"));
|
|
5924
|
+
const setsStatusLeft = lines.some((line) => /^\s*(set|set-option)\s+-g(?:\s+-\w+)*\s+status-left\b/.test(line));
|
|
5925
|
+
const setsStatusRight = lines.some((line) => /^\s*(set|set-option)\s+-g(?:\s+-\w+)*\s+status-right\b/.test(line));
|
|
5926
|
+
const sourcesSisyphusManaged = contents.includes(join17(homedir10(), ".sisyphus", "tmux.conf"));
|
|
5927
|
+
return { path, setsStatusLeft, setsStatusRight, sourcesSisyphusManaged };
|
|
5928
|
+
}
|
|
5929
|
+
function loadGlobalSisyphusConfig() {
|
|
5930
|
+
const path = globalConfigPath();
|
|
5931
|
+
if (!existsSync16(path)) return null;
|
|
5932
|
+
try {
|
|
5933
|
+
const parsed = JSON.parse(readFileSync20(path, "utf-8"));
|
|
5934
|
+
return parsed.statusBar === void 0 ? null : parsed.statusBar;
|
|
5935
|
+
} catch {
|
|
5936
|
+
return null;
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5939
|
+
function classifyState(opts, serverRunning) {
|
|
5940
|
+
if (!serverRunning) {
|
|
5941
|
+
return { state: "tmux-not-ready", referencesSisyphusLeft: false, referencesSisyphusRight: false };
|
|
5942
|
+
}
|
|
5943
|
+
if (opts.status === "off") {
|
|
5944
|
+
return { state: "disabled", referencesSisyphusLeft: false, referencesSisyphusRight: false };
|
|
5945
|
+
}
|
|
5946
|
+
const referencesSisyphusLeft = opts.statusLeft !== null && opts.statusLeft.includes(SISYPHUS_LEFT_TOKEN);
|
|
5947
|
+
const referencesSisyphusRight = opts.statusRight !== null && opts.statusRight.includes(SISYPHUS_RIGHT_TOKEN);
|
|
5948
|
+
if (referencesSisyphusLeft && referencesSisyphusRight) {
|
|
5949
|
+
return { state: "wired", referencesSisyphusLeft, referencesSisyphusRight };
|
|
5950
|
+
}
|
|
5951
|
+
if (referencesSisyphusLeft) {
|
|
5952
|
+
return { state: "partial-left-only", referencesSisyphusLeft, referencesSisyphusRight };
|
|
5953
|
+
}
|
|
5954
|
+
if (referencesSisyphusRight) {
|
|
5955
|
+
return { state: "partial-right-only", referencesSisyphusLeft, referencesSisyphusRight };
|
|
5956
|
+
}
|
|
5957
|
+
const isStock = (opts.statusLeft === TMUX_DEFAULT_STATUS_LEFT || opts.statusLeft === null) && (opts.statusRight === null || opts.statusRight.includes(TMUX_DEFAULT_STATUS_RIGHT_PREFIX));
|
|
5958
|
+
return {
|
|
5959
|
+
state: isStock ? "tmux-default" : "custom-no-sisyphus",
|
|
5960
|
+
referencesSisyphusLeft,
|
|
5961
|
+
referencesSisyphusRight
|
|
5962
|
+
};
|
|
5963
|
+
}
|
|
5964
|
+
function runCheck2() {
|
|
5965
|
+
const tmuxInstalled = isTmuxInstalled3();
|
|
5966
|
+
const tmuxServerRunning = tmuxInstalled ? isTmuxServerRunning2() : false;
|
|
5967
|
+
const tmuxOptions = probeTmuxOptions(tmuxServerRunning);
|
|
5968
|
+
const userConf = probeUserConf();
|
|
5969
|
+
const globalConfig = loadGlobalSisyphusConfig();
|
|
5970
|
+
const daemonRunning = isDaemonRunning();
|
|
5971
|
+
const { state, referencesSisyphusLeft, referencesSisyphusRight } = classifyState(tmuxOptions, tmuxServerRunning);
|
|
5972
|
+
return {
|
|
5973
|
+
tmuxInstalled,
|
|
5974
|
+
tmuxServerRunning,
|
|
5975
|
+
daemonRunning,
|
|
5976
|
+
tmuxOptions,
|
|
5977
|
+
userConf,
|
|
5978
|
+
globalConfig,
|
|
5979
|
+
state,
|
|
5980
|
+
referencesSisyphusLeft,
|
|
5981
|
+
referencesSisyphusRight
|
|
5982
|
+
};
|
|
5983
|
+
}
|
|
5984
|
+
function fmtOption(value) {
|
|
5985
|
+
if (value === null) return "(unset)";
|
|
5986
|
+
return JSON.stringify(value);
|
|
5987
|
+
}
|
|
5988
|
+
function renderConfigSummary(cfg) {
|
|
5989
|
+
if (cfg === null) return "(no statusBar block in ~/.sisyphus/config.json \u2014 defaults apply)";
|
|
5990
|
+
const parts = [];
|
|
5991
|
+
if (cfg.enabled === false) parts.push("enabled: false (DISABLED)");
|
|
5992
|
+
if (cfg.left !== void 0) parts.push(`left: [${cfg.left.join(", ")}]`);
|
|
5993
|
+
if (cfg.right !== void 0) parts.push(`right: [${cfg.right.join(", ")}]`);
|
|
5994
|
+
if (cfg.colors !== void 0) parts.push(`colors: ${JSON.stringify(cfg.colors)}`);
|
|
5995
|
+
if (cfg.segments !== void 0) {
|
|
5996
|
+
const segNames = Object.keys(cfg.segments);
|
|
5997
|
+
if (segNames.length > 0) parts.push(`per-segment overrides: ${segNames.join(", ")}`);
|
|
5998
|
+
}
|
|
5999
|
+
if (parts.length === 0) return "(statusBar block exists but is empty \u2014 defaults apply)";
|
|
6000
|
+
return parts.join("\n ");
|
|
6001
|
+
}
|
|
6002
|
+
function printInstructions2(r) {
|
|
6003
|
+
const userConfPath = r.userConf.path === null ? "~/.tmux.conf (none found)" : r.userConf.path;
|
|
6004
|
+
const userConfForCopy = r.userConf.path === null ? "~/.tmux.conf" : r.userConf.path;
|
|
6005
|
+
console.log(`
|
|
6006
|
+
<claude-instructions>
|
|
6007
|
+
# Sisyphus Statusbar Helper
|
|
6008
|
+
|
|
6009
|
+
This is a READ-ONLY check. Use it to figure out how to help the user improve their
|
|
6010
|
+
tmux statusbar with sisyphus segments WITHOUT clobbering their existing setup. The
|
|
6011
|
+
user's tmux config is theirs \u2014 never overwrite it. Append, suggest snippets, or
|
|
6012
|
+
nudge them to edit ~/.sisyphus/config.json.
|
|
6013
|
+
|
|
6014
|
+
## Environment Data
|
|
6015
|
+
- tmuxInstalled: ${r.tmuxInstalled}
|
|
6016
|
+
- tmuxServerRunning: ${r.tmuxServerRunning}
|
|
6017
|
+
- daemonRunning: ${r.daemonRunning} (false = @sisyphus_left/@sisyphus_right won't get populated, statusbar will look broken until daemon is up)
|
|
6018
|
+
|
|
6019
|
+
### Live tmux options
|
|
6020
|
+
- status: ${fmtOption(r.tmuxOptions.status)}
|
|
6021
|
+
- status-left: ${fmtOption(r.tmuxOptions.statusLeft)}
|
|
6022
|
+
- status-right: ${fmtOption(r.tmuxOptions.statusRight)}
|
|
6023
|
+
- status-position: ${fmtOption(r.tmuxOptions.statusPosition)}
|
|
6024
|
+
- status-style: ${fmtOption(r.tmuxOptions.statusStyle)}
|
|
6025
|
+
- status-interval: ${fmtOption(r.tmuxOptions.statusInterval)}
|
|
6026
|
+
- referencesSisyphusLeft: ${r.referencesSisyphusLeft}
|
|
6027
|
+
- referencesSisyphusRight: ${r.referencesSisyphusRight}
|
|
6028
|
+
|
|
6029
|
+
### User tmux config
|
|
6030
|
+
- path: ${userConfPath}
|
|
6031
|
+
- setsStatusLeft: ${r.userConf.setsStatusLeft}
|
|
6032
|
+
- setsStatusRight: ${r.userConf.setsStatusRight}
|
|
6033
|
+
- sourcesSisyphusManaged: ${r.userConf.sourcesSisyphusManaged} (sources ~/.sisyphus/tmux.conf \u2014 keybinds, NOT statusbar; included for context)
|
|
6034
|
+
|
|
6035
|
+
### Sisyphus statusbar config (~/.sisyphus/config.json \u2192 statusBar)
|
|
6036
|
+
${renderConfigSummary(r.globalConfig)}
|
|
6037
|
+
|
|
6038
|
+
### Available segments
|
|
6039
|
+
- session-name (left default) \u2014 current tmux session name
|
|
6040
|
+
- windows (left default) \u2014 tmux window list with active highlight
|
|
6041
|
+
- sessions (right default) \u2014 all tmux sessions with claude-state colors
|
|
6042
|
+
- sisyphus-sessions (right default) \u2014 sisyphus-managed sessions with phase indicators
|
|
6043
|
+
- companion (right default) \u2014 companion mood/state pill
|
|
6044
|
+
- clock \u2014 separate %H:%M (NOT a sisyphus segment; tmux renders it inline; the default
|
|
6045
|
+
~/.tmux.conf appends \`#[fg=...]#[bg=...] %H:%M \` after #{E:@sisyphus_right})
|
|
6046
|
+
|
|
6047
|
+
## Detected state: **${r.state}**
|
|
6048
|
+
|
|
6049
|
+
Pick exactly one path. Each path tells you what to ask the user and what to do.
|
|
6050
|
+
|
|
6051
|
+
### Path A \u2014 Already wired (state: wired)
|
|
6052
|
+
**Trigger:** status-left and status-right both reference @sisyphus_left/@sisyphus_right.
|
|
6053
|
+
**Action:** ${r.state === "wired" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6054
|
+
|
|
6055
|
+
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:
|
|
6056
|
+
|
|
6057
|
+
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:
|
|
6058
|
+
\`\`\`json
|
|
6059
|
+
{
|
|
6060
|
+
"statusBar": {
|
|
6061
|
+
"left": ["session-name"],
|
|
6062
|
+
"right": ["sisyphus-sessions", "companion"]
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6065
|
+
\`\`\`
|
|
6066
|
+
2. **Recolor.** \`statusBar.colors\` overrides processing/stopped/idle/activeBg/activeText/inactiveText. \`statusBar.segments.<id>.bg\` overrides per-segment band background.
|
|
6067
|
+
3. **Disable a single segment.** Remove its id from the left/right arrays.
|
|
6068
|
+
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).
|
|
6069
|
+
5. **Restart the daemon** after any config change: \`sisyphusd restart\`. Changes don't auto-apply.
|
|
6070
|
+
|
|
6071
|
+
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\`.
|
|
6072
|
+
|
|
6073
|
+
### Path B \u2014 Partial wiring (state: partial-left-only or partial-right-only)
|
|
6074
|
+
**Trigger:** Only one side references sisyphus.
|
|
6075
|
+
**Action:** ${r.state === "partial-left-only" || r.state === "partial-right-only" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6076
|
+
|
|
6077
|
+
Their config is half-wired. Show them the missing side:
|
|
6078
|
+
- Missing left \u2192 suggest adding \`set -g status-left "#{E:@sisyphus_left}"\` to ${userConfForCopy}
|
|
6079
|
+
- Missing right \u2192 suggest adding \`set -g status-right "#{E:@sisyphus_right}#[fg=#2d2f33]#[bg=#2d2f33,fg=#b0a898] %H:%M "\`
|
|
6080
|
+
|
|
6081
|
+
Don't auto-edit their config \u2014 present the snippet and ask if they want you to append it.
|
|
6082
|
+
|
|
6083
|
+
### Path C \u2014 Stock tmux statusbar (state: tmux-default)
|
|
6084
|
+
**Trigger:** status-left/right are tmux defaults; user has no custom statusbar.
|
|
6085
|
+
**Action:** ${r.state === "tmux-default" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6086
|
+
|
|
6087
|
+
Easiest case. Two options to offer:
|
|
6088
|
+
|
|
6089
|
+
1. **Full sisyphus statusbar.** Append these lines to ${userConfForCopy} (NOT a clobber \u2014 pure additive):
|
|
6090
|
+
\`\`\`tmux
|
|
6091
|
+
# --- Sisyphus statusbar ---
|
|
6092
|
+
set -g status on
|
|
6093
|
+
set -g status-style "bg=#1d1e21,fg=#d4cbb8"
|
|
6094
|
+
set -g status-position bottom
|
|
6095
|
+
set -g status-left "#{E:@sisyphus_left}"
|
|
6096
|
+
set -g status-left-length 250
|
|
6097
|
+
set -g status-right "#{E:@sisyphus_right}#[fg=#2d2f33]#[bg=#2d2f33,fg=#b0a898] %H:%M "
|
|
6098
|
+
set -g status-right-length 250
|
|
6099
|
+
set -g status-interval 2
|
|
6100
|
+
set -g window-status-format ""
|
|
6101
|
+
set -g window-status-current-format ""
|
|
6102
|
+
set -g window-status-separator ""
|
|
6103
|
+
\`\`\`
|
|
6104
|
+
${r.userConf.path === null ? `Note: no tmux config exists yet. Create ${userConfForCopy} with these lines.` : ""}
|
|
6105
|
+
|
|
6106
|
+
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:
|
|
6107
|
+
\`\`\`tmux
|
|
6108
|
+
set -g status-right "#{E:@sisyphus_right}#[default] %H:%M "
|
|
6109
|
+
set -g status-right-length 200
|
|
6110
|
+
\`\`\`
|
|
6111
|
+
|
|
6112
|
+
After appending, run \`tmux source-file ${userConfForCopy}\` so the change takes effect immediately.
|
|
6113
|
+
|
|
6114
|
+
### Path D \u2014 User has a custom statusbar (state: custom-no-sisyphus)
|
|
6115
|
+
**Trigger:** status-left/right are user-customized but don't reference sisyphus tokens.
|
|
6116
|
+
**Action:** ${r.state === "custom-no-sisyphus" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6117
|
+
|
|
6118
|
+
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:
|
|
6119
|
+
|
|
6120
|
+
Their current right side is currently:
|
|
6121
|
+
\`\`\`
|
|
6122
|
+
${r.tmuxOptions.statusRight === null ? "(unset)" : r.tmuxOptions.statusRight}
|
|
6123
|
+
\`\`\`
|
|
6124
|
+
And left side:
|
|
6125
|
+
\`\`\`
|
|
6126
|
+
${r.tmuxOptions.statusLeft === null ? "(unset)" : r.tmuxOptions.statusLeft}
|
|
6127
|
+
\`\`\`
|
|
6128
|
+
|
|
6129
|
+
Offer three integration patterns and let the user pick:
|
|
6130
|
+
|
|
6131
|
+
1. **Append sisyphus to the right side** (most common \u2014 leaves their left alone):
|
|
6132
|
+
\`\`\`tmux
|
|
6133
|
+
set -g status-right "${r.tmuxOptions.statusRight === null ? "" : r.tmuxOptions.statusRight}#{E:@sisyphus_right}"
|
|
6134
|
+
\`\`\`
|
|
6135
|
+
Or prepend it (sisyphus content appears first/leftmost):
|
|
6136
|
+
\`\`\`tmux
|
|
6137
|
+
set -g status-right "#{E:@sisyphus_right}${r.tmuxOptions.statusRight === null ? "" : r.tmuxOptions.statusRight}"
|
|
6138
|
+
\`\`\`
|
|
6139
|
+
|
|
6140
|
+
2. **Append to the left side** (if they want session/windows pills at the start):
|
|
6141
|
+
\`\`\`tmux
|
|
6142
|
+
set -g status-left "${r.tmuxOptions.statusLeft === null ? "" : r.tmuxOptions.statusLeft}#{E:@sisyphus_left}"
|
|
6143
|
+
\`\`\`
|
|
6144
|
+
|
|
6145
|
+
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.
|
|
6146
|
+
|
|
6147
|
+
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.
|
|
6148
|
+
|
|
6149
|
+
### Path E \u2014 Statusbar is disabled (state: disabled)
|
|
6150
|
+
**Trigger:** \`set -g status off\`.
|
|
6151
|
+
**Action:** ${r.state === "disabled" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6152
|
+
|
|
6153
|
+
The user explicitly turned off the statusbar. Don't enable it without asking. Suggest:
|
|
6154
|
+
- "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?"
|
|
6155
|
+
|
|
6156
|
+
If they say yes \u2192 fall through to Path C, option 2 (minimal pill).
|
|
6157
|
+
|
|
6158
|
+
### Path F \u2014 tmux not ready (state: tmux-not-ready)
|
|
6159
|
+
**Trigger:** tmux isn't installed or no server is running.
|
|
6160
|
+
**Action:** ${r.state === "tmux-not-ready" ? "\u2713 THIS IS YOUR PATH." : "(not applicable)"}
|
|
6161
|
+
|
|
6162
|
+
Don't propose statusbar changes yet:
|
|
6163
|
+
- tmuxInstalled=false \u2192 install tmux first (\`brew install tmux\` on macOS)
|
|
6164
|
+
- tmuxServerRunning=false \u2192 user needs to run \`tmux\` (or attach to a session)
|
|
6165
|
+
|
|
6166
|
+
Re-run \`sis admin check-statusbar\` once tmux is up.
|
|
6167
|
+
|
|
6168
|
+
## Daemon reminder
|
|
6169
|
+
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).
|
|
6170
|
+
|
|
6171
|
+
## After acting
|
|
6172
|
+
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.
|
|
6173
|
+
</claude-instructions>
|
|
6174
|
+
`);
|
|
6175
|
+
}
|
|
6176
|
+
function registerCheckStatusbar(program2) {
|
|
6177
|
+
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) => {
|
|
6178
|
+
const result = runCheck2();
|
|
6179
|
+
if (opts.json) {
|
|
6180
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
printInstructions2(result);
|
|
6184
|
+
});
|
|
6185
|
+
}
|
|
6186
|
+
|
|
6187
|
+
// src/cli/commands/home-init.ts
|
|
6188
|
+
init_shell();
|
|
6189
|
+
import { execSync as execSync13 } from "child_process";
|
|
5191
6190
|
function registerHomeInit(parent) {
|
|
5192
6191
|
parent.command("home-init <name> <cwd>").description("Bootstrap a tmux home session with the sisyphus dashboard.").action((name, cwd) => {
|
|
5193
6192
|
ensureSession(name, cwd);
|
|
@@ -5197,7 +6196,7 @@ function registerHomeInit(parent) {
|
|
|
5197
6196
|
}
|
|
5198
6197
|
function sessionExists(name) {
|
|
5199
6198
|
try {
|
|
5200
|
-
|
|
6199
|
+
execSync13(`tmux has-session -t ${shellQuote(name)}`, { stdio: "pipe" });
|
|
5201
6200
|
return true;
|
|
5202
6201
|
} catch {
|
|
5203
6202
|
return false;
|
|
@@ -5205,13 +6204,13 @@ function sessionExists(name) {
|
|
|
5205
6204
|
}
|
|
5206
6205
|
function ensureSession(name, cwd) {
|
|
5207
6206
|
if (sessionExists(name)) return;
|
|
5208
|
-
|
|
6207
|
+
execSync13(
|
|
5209
6208
|
`tmux new-session -d -s ${shellQuote(name)} -c ${shellQuote(cwd)}`,
|
|
5210
6209
|
{ stdio: "pipe" }
|
|
5211
6210
|
);
|
|
5212
6211
|
}
|
|
5213
6212
|
function setSessionCwd(name, cwd) {
|
|
5214
|
-
|
|
6213
|
+
execSync13(
|
|
5215
6214
|
`tmux set-option -t ${shellQuote(name)} @sisyphus_cwd ${shellQuote(cwd.replace(/\/+$/, ""))}`,
|
|
5216
6215
|
{ stdio: "pipe" }
|
|
5217
6216
|
);
|
|
@@ -5219,10 +6218,10 @@ function setSessionCwd(name, cwd) {
|
|
|
5219
6218
|
|
|
5220
6219
|
// src/cli/commands/doctor.ts
|
|
5221
6220
|
init_paths();
|
|
5222
|
-
import { execSync as
|
|
5223
|
-
import { existsSync as
|
|
5224
|
-
import { homedir as
|
|
5225
|
-
import { join as
|
|
6221
|
+
import { execSync as execSync14 } from "child_process";
|
|
6222
|
+
import { existsSync as existsSync17, statSync as statSync3 } from "fs";
|
|
6223
|
+
import { homedir as homedir11 } from "os";
|
|
6224
|
+
import { join as join18 } from "path";
|
|
5226
6225
|
function checkNodeVersion() {
|
|
5227
6226
|
const major = parseInt(process.versions.node.split(".")[0], 10);
|
|
5228
6227
|
if (major < 22) {
|
|
@@ -5232,7 +6231,7 @@ function checkNodeVersion() {
|
|
|
5232
6231
|
}
|
|
5233
6232
|
function checkClaudeCli() {
|
|
5234
6233
|
try {
|
|
5235
|
-
|
|
6234
|
+
execSync14("which claude", { stdio: "pipe" });
|
|
5236
6235
|
return { name: "Claude CLI", status: "ok", detail: "Found on PATH" };
|
|
5237
6236
|
} catch {
|
|
5238
6237
|
return {
|
|
@@ -5245,7 +6244,7 @@ function checkClaudeCli() {
|
|
|
5245
6244
|
}
|
|
5246
6245
|
function checkGit() {
|
|
5247
6246
|
try {
|
|
5248
|
-
const version =
|
|
6247
|
+
const version = execSync14("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
5249
6248
|
return { name: "git", status: "ok", detail: version };
|
|
5250
6249
|
} catch {
|
|
5251
6250
|
return { name: "git", status: "fail", detail: "Not found on PATH", fix: "Install git: https://git-scm.com/downloads" };
|
|
@@ -5253,7 +6252,7 @@ function checkGit() {
|
|
|
5253
6252
|
}
|
|
5254
6253
|
function checkTmuxVersion() {
|
|
5255
6254
|
try {
|
|
5256
|
-
const version =
|
|
6255
|
+
const version = execSync14("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
5257
6256
|
const match = version.match(/(\d+\.\d+)/);
|
|
5258
6257
|
if (!match) return { name: "tmux version", status: "warn", detail: `Could not parse version: ${version}` };
|
|
5259
6258
|
const ver = parseFloat(match[1]);
|
|
@@ -5279,7 +6278,7 @@ function checkDaemonInstalled() {
|
|
|
5279
6278
|
};
|
|
5280
6279
|
}
|
|
5281
6280
|
const pid = daemonPidPath();
|
|
5282
|
-
if (
|
|
6281
|
+
if (existsSync17(pid)) {
|
|
5283
6282
|
return { name: "Daemon setup", status: "ok", detail: `PID file found at ${pid}` };
|
|
5284
6283
|
}
|
|
5285
6284
|
return {
|
|
@@ -5291,7 +6290,7 @@ function checkDaemonInstalled() {
|
|
|
5291
6290
|
}
|
|
5292
6291
|
function checkDaemonRunning() {
|
|
5293
6292
|
const pid = daemonPidPath();
|
|
5294
|
-
if (!
|
|
6293
|
+
if (!existsSync17(pid)) {
|
|
5295
6294
|
const fix = process.platform === "darwin" ? "launchctl load -w ~/Library/LaunchAgents/com.sisyphus.daemon.plist" : "sisyphusd & \u2014 or check if the process is running";
|
|
5296
6295
|
return {
|
|
5297
6296
|
name: "Daemon process",
|
|
@@ -5302,7 +6301,7 @@ function checkDaemonRunning() {
|
|
|
5302
6301
|
}
|
|
5303
6302
|
try {
|
|
5304
6303
|
const sock = socketPath();
|
|
5305
|
-
|
|
6304
|
+
execSync14(`test -S "${sock}"`, { stdio: "pipe" });
|
|
5306
6305
|
return { name: "Daemon process", status: "ok", detail: `Socket at ${sock}` };
|
|
5307
6306
|
} catch {
|
|
5308
6307
|
return {
|
|
@@ -5315,13 +6314,13 @@ function checkDaemonRunning() {
|
|
|
5315
6314
|
}
|
|
5316
6315
|
function checkTmux() {
|
|
5317
6316
|
try {
|
|
5318
|
-
|
|
6317
|
+
execSync14("which tmux", { stdio: "pipe" });
|
|
5319
6318
|
} catch {
|
|
5320
6319
|
const installHint = process.platform === "darwin" ? "brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
|
|
5321
6320
|
return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: installHint };
|
|
5322
6321
|
}
|
|
5323
6322
|
try {
|
|
5324
|
-
|
|
6323
|
+
execSync14("tmux list-sessions", { stdio: "pipe" });
|
|
5325
6324
|
return { name: "tmux", status: "ok", detail: "Running" };
|
|
5326
6325
|
} catch {
|
|
5327
6326
|
return { name: "tmux", status: "warn", detail: "Installed but no server running" };
|
|
@@ -5329,7 +6328,7 @@ function checkTmux() {
|
|
|
5329
6328
|
}
|
|
5330
6329
|
function checkCycleScript() {
|
|
5331
6330
|
const path = cycleScriptPath();
|
|
5332
|
-
if (!
|
|
6331
|
+
if (!existsSync17(path)) {
|
|
5333
6332
|
return {
|
|
5334
6333
|
name: "Cycle script",
|
|
5335
6334
|
status: "fail",
|
|
@@ -5354,7 +6353,7 @@ function checkCycleScript() {
|
|
|
5354
6353
|
function checkTmuxKeybind() {
|
|
5355
6354
|
const existing = getExistingBinding(DEFAULT_CYCLE_KEY);
|
|
5356
6355
|
if (existing === null) {
|
|
5357
|
-
if (
|
|
6356
|
+
if (existsSync17(sisyphusTmuxConfPath())) {
|
|
5358
6357
|
return {
|
|
5359
6358
|
name: `Tmux keybind (${DEFAULT_CYCLE_KEY})`,
|
|
5360
6359
|
status: "warn",
|
|
@@ -5380,7 +6379,7 @@ function checkTmuxKeybind() {
|
|
|
5380
6379
|
}
|
|
5381
6380
|
function checkGlobalDir() {
|
|
5382
6381
|
const dir = globalDir();
|
|
5383
|
-
if (
|
|
6382
|
+
if (existsSync17(dir)) {
|
|
5384
6383
|
return { name: "Data directory", status: "ok", detail: dir };
|
|
5385
6384
|
}
|
|
5386
6385
|
return { name: "Data directory", status: "warn", detail: `${dir} does not exist (created on first use)` };
|
|
@@ -5432,7 +6431,7 @@ function checkSisyphusPlugin() {
|
|
|
5432
6431
|
function checkTermrender() {
|
|
5433
6432
|
if (isTermrenderAvailable()) {
|
|
5434
6433
|
try {
|
|
5435
|
-
const version =
|
|
6434
|
+
const version = execSync14("termrender --version", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
5436
6435
|
return { name: "termrender", status: "ok", detail: version };
|
|
5437
6436
|
} catch {
|
|
5438
6437
|
return { name: "termrender", status: "ok", detail: "installed" };
|
|
@@ -5451,7 +6450,7 @@ function checkNvim() {
|
|
|
5451
6450
|
return { name: "nvim", status: "warn", detail: "Not installed", fix };
|
|
5452
6451
|
}
|
|
5453
6452
|
try {
|
|
5454
|
-
const version =
|
|
6453
|
+
const version = execSync14("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
|
|
5455
6454
|
return { name: "nvim", status: "ok", detail: version ?? "installed" };
|
|
5456
6455
|
} catch {
|
|
5457
6456
|
return { name: "nvim", status: "ok", detail: "installed" };
|
|
@@ -5459,8 +6458,8 @@ function checkNvim() {
|
|
|
5459
6458
|
}
|
|
5460
6459
|
function checkNotifyBinary() {
|
|
5461
6460
|
if (process.platform !== "darwin") return null;
|
|
5462
|
-
const binary =
|
|
5463
|
-
if (
|
|
6461
|
+
const binary = join18(homedir11(), ".sisyphus", "SisyphusNotify.app", "Contents", "MacOS", "sisyphus-notify");
|
|
6462
|
+
if (existsSync17(binary)) {
|
|
5464
6463
|
return { name: "Notifications", status: "ok", detail: "SisyphusNotify.app built" };
|
|
5465
6464
|
}
|
|
5466
6465
|
return {
|
|
@@ -5513,8 +6512,8 @@ function registerDoctor(program2) {
|
|
|
5513
6512
|
}
|
|
5514
6513
|
|
|
5515
6514
|
// src/cli/commands/init.ts
|
|
5516
|
-
import { existsSync as
|
|
5517
|
-
import { join as
|
|
6515
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
6516
|
+
import { join as join19 } from "path";
|
|
5518
6517
|
var DEFAULT_CONFIG2 = {};
|
|
5519
6518
|
var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
|
|
5520
6519
|
|
|
@@ -5525,18 +6524,18 @@ var ORCHESTRATOR_TEMPLATE = `# Custom Orchestrator Prompt
|
|
|
5525
6524
|
function registerInit(program2) {
|
|
5526
6525
|
program2.command("init").description("Initialize sisyphus configuration for this project").option("--orchestrator", "Also create a custom orchestrator prompt template").action((opts) => {
|
|
5527
6526
|
const cwd = process.cwd();
|
|
5528
|
-
const sisDir =
|
|
5529
|
-
const configPath =
|
|
5530
|
-
if (
|
|
6527
|
+
const sisDir = join19(cwd, ".sisyphus");
|
|
6528
|
+
const configPath = join19(sisDir, "config.json");
|
|
6529
|
+
if (existsSync18(configPath)) {
|
|
5531
6530
|
console.log(`Already initialized: ${configPath}`);
|
|
5532
6531
|
return;
|
|
5533
6532
|
}
|
|
5534
|
-
|
|
6533
|
+
mkdirSync8(sisDir, { recursive: true });
|
|
5535
6534
|
writeFileSync9(configPath, JSON.stringify(DEFAULT_CONFIG2, null, 2) + "\n", "utf-8");
|
|
5536
6535
|
console.log(`Created ${configPath}`);
|
|
5537
6536
|
if (opts.orchestrator) {
|
|
5538
|
-
const orchPath =
|
|
5539
|
-
if (!
|
|
6537
|
+
const orchPath = join19(sisDir, "orchestrator.md");
|
|
6538
|
+
if (!existsSync18(orchPath)) {
|
|
5540
6539
|
writeFileSync9(orchPath, ORCHESTRATOR_TEMPLATE, "utf-8");
|
|
5541
6540
|
console.log(`Created ${orchPath}`);
|
|
5542
6541
|
}
|
|
@@ -5579,7 +6578,7 @@ function registerUninstall(program2) {
|
|
|
5579
6578
|
|
|
5580
6579
|
// src/cli/commands/configure-upload.ts
|
|
5581
6580
|
init_paths();
|
|
5582
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
6581
|
+
import { chmodSync as chmodSync2, existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
|
|
5583
6582
|
import { createInterface as createInterface3 } from "readline";
|
|
5584
6583
|
import { dirname as dirname6 } from "path";
|
|
5585
6584
|
async function readUrlFromInput(interactive) {
|
|
@@ -5637,16 +6636,16 @@ function registerConfigureUpload(program2) {
|
|
|
5637
6636
|
const url = parsed.origin + (strippedPath.length > 0 ? strippedPath : "");
|
|
5638
6637
|
const configPath = globalConfigPath();
|
|
5639
6638
|
let existing = {};
|
|
5640
|
-
if (
|
|
6639
|
+
if (existsSync19(configPath)) {
|
|
5641
6640
|
try {
|
|
5642
|
-
existing = JSON.parse(
|
|
6641
|
+
existing = JSON.parse(readFileSync21(configPath, "utf-8"));
|
|
5643
6642
|
} catch {
|
|
5644
6643
|
console.error(`Error: ${configPath} could not be parsed \u2014 fix or delete it first`);
|
|
5645
6644
|
process.exit(1);
|
|
5646
6645
|
}
|
|
5647
6646
|
}
|
|
5648
6647
|
const merged = { ...existing, upload: { url, token } };
|
|
5649
|
-
|
|
6648
|
+
mkdirSync9(dirname6(configPath), { recursive: true });
|
|
5650
6649
|
writeFileSync10(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
5651
6650
|
chmodSync2(configPath, 384);
|
|
5652
6651
|
console.log(`\u2713 upload configured (${configPath})`);
|
|
@@ -5654,11 +6653,11 @@ function registerConfigureUpload(program2) {
|
|
|
5654
6653
|
}
|
|
5655
6654
|
|
|
5656
6655
|
// src/cli/commands/getting-started.ts
|
|
5657
|
-
import { execSync as
|
|
5658
|
-
import { dirname as dirname7, join as
|
|
6656
|
+
import { execSync as execSync15 } from "child_process";
|
|
6657
|
+
import { dirname as dirname7, join as join20 } from "path";
|
|
5659
6658
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5660
6659
|
function templatePath(name) {
|
|
5661
|
-
return
|
|
6660
|
+
return join20(dirname7(fileURLToPath3(import.meta.url)), "templates", name);
|
|
5662
6661
|
}
|
|
5663
6662
|
function isClaudeCode() {
|
|
5664
6663
|
return !!process.env["CLAUDECODE"];
|
|
@@ -5983,18 +6982,30 @@ Two keybinds to remember (both use the RIGHT Option key):
|
|
|
5983
6982
|
| Right Option + s | Cycle through sisyphus sessions |
|
|
5984
6983
|
| Right Option + Shift + s | Jump back to dashboard |
|
|
5985
6984
|
|
|
5986
|
-
### 4. Verify keybinds are installed
|
|
6985
|
+
### 4. Verify keybinds are installed (REQUIRED before step 5)
|
|
5987
6986
|
|
|
5988
|
-
Run \`sis admin
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
-
|
|
6987
|
+
Run \`sis admin check-keybinds\` and follow the decision tree it emits. The output is
|
|
6988
|
+
structured for you \u2014 it tells you the current state and which path to take (Path A
|
|
6989
|
+
through Path F). Do NOT skip this and do NOT ask the user to test a keybind until
|
|
6990
|
+
both \`M-s\` and \`C-s\` read "sisyphus" in that command's output.
|
|
5992
6991
|
|
|
5993
|
-
|
|
6992
|
+
Common paths:
|
|
6993
|
+
- **Path A (already wired):** confirm and move on.
|
|
6994
|
+
- **Path B (safe auto-install):** run \`sis admin setup-keybind --yes\`.
|
|
6995
|
+
- **Path C (would touch user's tmux.conf):** ask the user \u2014 persistent (\`--force\`) or
|
|
6996
|
+
live-only (no flag, non-TTY auto-declines the conf write).
|
|
6997
|
+
- **Path D (binding conflict):** pick alternate keys or wire directly.
|
|
6998
|
+
- **Path E (hidden prefix collision):** their tmux prefix is C-s; explain and offer
|
|
6999
|
+
alternatives.
|
|
7000
|
+
- **Path F (tmux not ready):** fix the precondition first.
|
|
7001
|
+
|
|
7002
|
+
After acting, re-run \`sis admin check-keybinds\` to confirm success.
|
|
5994
7003
|
|
|
5995
7004
|
### 5. Test the keybind
|
|
5996
7005
|
|
|
5997
|
-
|
|
7006
|
+
Once check-keybinds reports both keys as "sisyphus", have the user try pressing
|
|
7007
|
+
Right Option + s. Nothing should happen yet (no sisyphus session running) \u2014 and that's
|
|
7008
|
+
fine. The important thing is no special character appears.
|
|
5998
7009
|
|
|
5999
7010
|
If they see \`\xDF\` or similar, circle back to the Right Option Key setup above.
|
|
6000
7011
|
|
|
@@ -6134,11 +7145,11 @@ function printStep5() {
|
|
|
6134
7145
|
let recentCommits = "";
|
|
6135
7146
|
let topLevelFiles = "";
|
|
6136
7147
|
try {
|
|
6137
|
-
recentCommits =
|
|
7148
|
+
recentCommits = execSync15("git log --oneline -15 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
6138
7149
|
} catch {
|
|
6139
7150
|
}
|
|
6140
7151
|
try {
|
|
6141
|
-
topLevelFiles =
|
|
7152
|
+
topLevelFiles = execSync15("ls -1 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
6142
7153
|
} catch {
|
|
6143
7154
|
}
|
|
6144
7155
|
console.log(`
|
|
@@ -6616,7 +7627,7 @@ function registerGettingStarted(program2) {
|
|
|
6616
7627
|
|
|
6617
7628
|
// src/cli/commands/history.ts
|
|
6618
7629
|
init_paths();
|
|
6619
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
7630
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync22, existsSync as existsSync20 } from "fs";
|
|
6620
7631
|
import { resolve as resolve6 } from "path";
|
|
6621
7632
|
var RESET3 = "\x1B[0m";
|
|
6622
7633
|
var BOLD3 = "\x1B[1m";
|
|
@@ -6656,13 +7667,13 @@ function splitAgentTime(agents) {
|
|
|
6656
7667
|
}
|
|
6657
7668
|
function loadAllSummaries() {
|
|
6658
7669
|
const base = historyBaseDir();
|
|
6659
|
-
if (!
|
|
7670
|
+
if (!existsSync20(base)) return [];
|
|
6660
7671
|
const results = [];
|
|
6661
7672
|
for (const name of readdirSync6(base)) {
|
|
6662
7673
|
const summaryPath = historySessionSummaryPath(name);
|
|
6663
|
-
if (
|
|
7674
|
+
if (existsSync20(summaryPath)) {
|
|
6664
7675
|
try {
|
|
6665
|
-
const raw =
|
|
7676
|
+
const raw = readFileSync22(summaryPath, "utf-8");
|
|
6666
7677
|
results.push({ id: name, summary: JSON.parse(raw) });
|
|
6667
7678
|
continue;
|
|
6668
7679
|
} catch {
|
|
@@ -6676,10 +7687,10 @@ function loadAllSummaries() {
|
|
|
6676
7687
|
}
|
|
6677
7688
|
function buildLiveSummary(sessionId) {
|
|
6678
7689
|
const eventsPath = historyEventsPath(sessionId);
|
|
6679
|
-
if (!
|
|
7690
|
+
if (!existsSync20(eventsPath)) return null;
|
|
6680
7691
|
let cwd = null;
|
|
6681
7692
|
try {
|
|
6682
|
-
const lines =
|
|
7693
|
+
const lines = readFileSync22(eventsPath, "utf-8").split("\n");
|
|
6683
7694
|
for (const line of lines) {
|
|
6684
7695
|
if (!line.trim()) continue;
|
|
6685
7696
|
try {
|
|
@@ -6697,10 +7708,10 @@ function buildLiveSummary(sessionId) {
|
|
|
6697
7708
|
}
|
|
6698
7709
|
if (!cwd) return null;
|
|
6699
7710
|
const sPath = statePath(cwd, sessionId);
|
|
6700
|
-
if (!
|
|
7711
|
+
if (!existsSync20(sPath)) return null;
|
|
6701
7712
|
let session2;
|
|
6702
7713
|
try {
|
|
6703
|
-
session2 = JSON.parse(
|
|
7714
|
+
session2 = JSON.parse(readFileSync22(sPath, "utf-8"));
|
|
6704
7715
|
} catch {
|
|
6705
7716
|
return null;
|
|
6706
7717
|
}
|
|
@@ -6761,8 +7772,8 @@ function buildLiveSummary(sessionId) {
|
|
|
6761
7772
|
}
|
|
6762
7773
|
function loadEvents(sessionId) {
|
|
6763
7774
|
const eventsPath = historyEventsPath(sessionId);
|
|
6764
|
-
if (!
|
|
6765
|
-
const lines =
|
|
7775
|
+
if (!existsSync20(eventsPath)) return [];
|
|
7776
|
+
const lines = readFileSync22(eventsPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
6766
7777
|
const events = [];
|
|
6767
7778
|
for (const line of lines) {
|
|
6768
7779
|
try {
|
|
@@ -6775,13 +7786,13 @@ function loadEvents(sessionId) {
|
|
|
6775
7786
|
}
|
|
6776
7787
|
function findSession(idOrName) {
|
|
6777
7788
|
const summaryPath = historySessionSummaryPath(idOrName);
|
|
6778
|
-
if (
|
|
7789
|
+
if (existsSync20(summaryPath)) {
|
|
6779
7790
|
try {
|
|
6780
|
-
return { id: idOrName, summary: JSON.parse(
|
|
7791
|
+
return { id: idOrName, summary: JSON.parse(readFileSync22(summaryPath, "utf-8")) };
|
|
6781
7792
|
} catch {
|
|
6782
7793
|
}
|
|
6783
7794
|
}
|
|
6784
|
-
if (
|
|
7795
|
+
if (existsSync20(historySessionDir(idOrName))) {
|
|
6785
7796
|
const live = buildLiveSummary(idOrName);
|
|
6786
7797
|
if (live) return { id: idOrName, summary: live };
|
|
6787
7798
|
}
|
|
@@ -7170,21 +8181,21 @@ function registerHistory(program2) {
|
|
|
7170
8181
|
init_paths();
|
|
7171
8182
|
import { execFile as execFile2 } from "child_process";
|
|
7172
8183
|
import { promisify } from "util";
|
|
7173
|
-
import { existsSync as
|
|
7174
|
-
import { homedir as
|
|
7175
|
-
import { join as
|
|
8184
|
+
import { existsSync as existsSync21, readFileSync as readFileSync23, mkdirSync as mkdirSync10, symlinkSync, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "fs";
|
|
8185
|
+
import { homedir as homedir12 } from "os";
|
|
8186
|
+
import { join as join22 } from "path";
|
|
7176
8187
|
function sanitizeName(name) {
|
|
7177
8188
|
return name.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
7178
8189
|
}
|
|
7179
8190
|
function buildOutputPath(label, dir) {
|
|
7180
8191
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7181
|
-
|
|
8192
|
+
mkdirSync10(dir, { recursive: true });
|
|
7182
8193
|
const base = `sisyphus-${label}-${date}`;
|
|
7183
|
-
let candidate =
|
|
8194
|
+
let candidate = join22(dir, `${base}.zip`);
|
|
7184
8195
|
let counter = 1;
|
|
7185
|
-
while (
|
|
8196
|
+
while (existsSync21(candidate)) {
|
|
7186
8197
|
counter++;
|
|
7187
|
-
candidate =
|
|
8198
|
+
candidate = join22(dir, `${base}-${counter}.zip`);
|
|
7188
8199
|
}
|
|
7189
8200
|
return candidate;
|
|
7190
8201
|
}
|
|
@@ -7246,33 +8257,33 @@ async function exportSessionToZip(sessionId, cwd, options) {
|
|
|
7246
8257
|
const reveal = options?.reveal ?? true;
|
|
7247
8258
|
const sessDir = sessionDir(cwd, sessionId);
|
|
7248
8259
|
const histDir = historySessionDir(sessionId);
|
|
7249
|
-
const sessExists =
|
|
7250
|
-
const histExists =
|
|
8260
|
+
const sessExists = existsSync21(sessDir);
|
|
8261
|
+
const histExists = existsSync21(histDir);
|
|
7251
8262
|
if (!sessExists && !histExists) {
|
|
7252
8263
|
throw new Error(`No data found for session ${sessionId}`);
|
|
7253
8264
|
}
|
|
7254
8265
|
let label = sessionId.slice(0, 8);
|
|
7255
8266
|
const stPath = statePath(cwd, sessionId);
|
|
7256
|
-
if (
|
|
8267
|
+
if (existsSync21(stPath)) {
|
|
7257
8268
|
try {
|
|
7258
|
-
const state = JSON.parse(
|
|
8269
|
+
const state = JSON.parse(readFileSync23(stPath, "utf-8"));
|
|
7259
8270
|
if (state.name) {
|
|
7260
8271
|
label = sanitizeName(state.name);
|
|
7261
8272
|
}
|
|
7262
8273
|
} catch {
|
|
7263
8274
|
}
|
|
7264
8275
|
}
|
|
7265
|
-
const dir = options?.outputDir ??
|
|
8276
|
+
const dir = options?.outputDir ?? join22(homedir12(), "Downloads");
|
|
7266
8277
|
const outputPath = buildOutputPath(label, dir);
|
|
7267
8278
|
const tmpDir = `/tmp/sisyphus-export-${sessionId.slice(0, 8)}-${Date.now()}`;
|
|
7268
8279
|
try {
|
|
7269
|
-
|
|
7270
|
-
writeFileSync11(
|
|
8280
|
+
mkdirSync10(tmpDir, { recursive: true });
|
|
8281
|
+
writeFileSync11(join22(tmpDir, "CLAUDE.md"), generateGuide(), "utf-8");
|
|
7271
8282
|
if (sessExists) {
|
|
7272
|
-
symlinkSync(sessDir,
|
|
8283
|
+
symlinkSync(sessDir, join22(tmpDir, "session"));
|
|
7273
8284
|
}
|
|
7274
8285
|
if (histExists) {
|
|
7275
|
-
symlinkSync(histDir,
|
|
8286
|
+
symlinkSync(histDir, join22(tmpDir, "history"));
|
|
7276
8287
|
}
|
|
7277
8288
|
const parts = ["CLAUDE.md", sessExists ? "session/" : "", histExists ? "history/" : ""].filter(Boolean);
|
|
7278
8289
|
await execFileAsync("zip", ["-rq", outputPath, ...parts], { cwd: tmpDir });
|
|
@@ -7394,12 +8405,12 @@ function buildManifest(args2) {
|
|
|
7394
8405
|
}
|
|
7395
8406
|
|
|
7396
8407
|
// src/shared/version.ts
|
|
7397
|
-
import { readFileSync as
|
|
8408
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
7398
8409
|
import { resolve as resolve7 } from "path";
|
|
7399
8410
|
function readSisyphusVersion() {
|
|
7400
8411
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
7401
8412
|
try {
|
|
7402
|
-
const raw =
|
|
8413
|
+
const raw = readFileSync24(resolve7(import.meta.dirname, rel), "utf-8");
|
|
7403
8414
|
const pkg = JSON.parse(raw);
|
|
7404
8415
|
if (pkg.name === "sisyphi" && pkg.version) return pkg.version;
|
|
7405
8416
|
} catch {
|
|
@@ -7494,12 +8505,13 @@ function registerUpload(program2) {
|
|
|
7494
8505
|
}
|
|
7495
8506
|
|
|
7496
8507
|
// src/cli/commands/scratch.ts
|
|
7497
|
-
import { execSync as
|
|
8508
|
+
import { execSync as execSync16 } from "child_process";
|
|
8509
|
+
init_shell();
|
|
7498
8510
|
function findHomeSession(cwd) {
|
|
7499
8511
|
const normalizedCwd = cwd.replace(/\/+$/, "");
|
|
7500
8512
|
let output;
|
|
7501
8513
|
try {
|
|
7502
|
-
output =
|
|
8514
|
+
output = execSync16('tmux list-sessions -F "#{session_id}|#{session_name}"', {
|
|
7503
8515
|
encoding: "utf-8",
|
|
7504
8516
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7505
8517
|
}).trim();
|
|
@@ -7513,7 +8525,7 @@ function findHomeSession(cwd) {
|
|
|
7513
8525
|
const name = line.slice(pipeIdx + 1);
|
|
7514
8526
|
if (name.startsWith("ssyph_")) continue;
|
|
7515
8527
|
try {
|
|
7516
|
-
const val =
|
|
8528
|
+
const val = execSync16(
|
|
7517
8529
|
`tmux show-options -t ${shellQuote(sessId)} -v @sisyphus_cwd`,
|
|
7518
8530
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
7519
8531
|
).trim();
|
|
@@ -7529,7 +8541,7 @@ function registerScratch(program2) {
|
|
|
7529
8541
|
const cwd = opts.cwd ?? process.env["SISYPHUS_CWD"] ?? process.cwd();
|
|
7530
8542
|
const homeSession = findHomeSession(cwd);
|
|
7531
8543
|
if (!homeSession) {
|
|
7532
|
-
const current =
|
|
8544
|
+
const current = execSync16('tmux display-message -p "#{session_name}"', {
|
|
7533
8545
|
encoding: "utf-8"
|
|
7534
8546
|
}).trim();
|
|
7535
8547
|
openScratchWindow(current, cwd, promptParts.join(" "));
|
|
@@ -7539,7 +8551,7 @@ function registerScratch(program2) {
|
|
|
7539
8551
|
});
|
|
7540
8552
|
}
|
|
7541
8553
|
function openScratchWindow(tmuxSession, cwd, prompt) {
|
|
7542
|
-
const windowId =
|
|
8554
|
+
const windowId = execSync16(
|
|
7543
8555
|
`tmux new-window -t ${shellQuote(tmuxSession + ":")} -n "scratch" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
|
|
7544
8556
|
{ encoding: "utf-8" }
|
|
7545
8557
|
).trim();
|
|
@@ -7547,7 +8559,7 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
|
|
|
7547
8559
|
if (prompt) {
|
|
7548
8560
|
cmd += ` -p ${shellQuote(prompt)}`;
|
|
7549
8561
|
}
|
|
7550
|
-
|
|
8562
|
+
execSync16(
|
|
7551
8563
|
`tmux send-keys -t ${shellQuote(windowId)} ${shellQuote(cmd)} Enter`
|
|
7552
8564
|
);
|
|
7553
8565
|
console.log(`Scratch session opened in ${tmuxSession}`);
|
|
@@ -7555,27 +8567,27 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
|
|
|
7555
8567
|
|
|
7556
8568
|
// src/cli/commands/review.ts
|
|
7557
8569
|
init_paths();
|
|
7558
|
-
import { join as
|
|
7559
|
-
import { existsSync as
|
|
8570
|
+
import { join as join23, resolve as resolve8, dirname as dirname8 } from "path";
|
|
8571
|
+
import { existsSync as existsSync22, readFileSync as readFileSync25, writeFileSync as writeFileSync12, renameSync as renameSync3, readdirSync as readdirSync7 } from "fs";
|
|
7560
8572
|
var _statusCheck = ["draft", "question", "approved", "rejected", "deferred"];
|
|
7561
8573
|
function resolveContextArtifact(file, opts, filename, notFoundMessage) {
|
|
7562
8574
|
const cwd = opts.cwd || process.env.SISYPHUS_CWD || process.cwd();
|
|
7563
8575
|
if (file) return resolve8(file);
|
|
7564
8576
|
const sessionId = opts.sessionId || process.env.SISYPHUS_SESSION_ID;
|
|
7565
8577
|
if (sessionId) {
|
|
7566
|
-
const target =
|
|
7567
|
-
if (!
|
|
8578
|
+
const target = join23(contextDir(cwd, sessionId), filename);
|
|
8579
|
+
if (!existsSync22(target)) {
|
|
7568
8580
|
console.error(`Error: File not found: ${target}`);
|
|
7569
8581
|
process.exit(1);
|
|
7570
8582
|
}
|
|
7571
8583
|
return target;
|
|
7572
8584
|
}
|
|
7573
8585
|
const dir = sessionsDir(cwd);
|
|
7574
|
-
if (
|
|
8586
|
+
if (existsSync22(dir)) {
|
|
7575
8587
|
const sessions = readdirSync7(dir);
|
|
7576
8588
|
for (const session2 of sessions.reverse()) {
|
|
7577
|
-
const candidate =
|
|
7578
|
-
if (
|
|
8589
|
+
const candidate = join23(dir, session2, "context", filename);
|
|
8590
|
+
if (existsSync22(candidate)) return candidate;
|
|
7579
8591
|
}
|
|
7580
8592
|
}
|
|
7581
8593
|
console.error(`Error: ${notFoundMessage}`);
|
|
@@ -7617,16 +8629,16 @@ Examples:
|
|
|
7617
8629
|
"requirements.json",
|
|
7618
8630
|
"No requirements.json found. Provide a path or use --session-id."
|
|
7619
8631
|
);
|
|
7620
|
-
if (!
|
|
8632
|
+
if (!existsSync22(targetPath)) {
|
|
7621
8633
|
console.error(`Error: File not found: ${targetPath}`);
|
|
7622
8634
|
process.exit(1);
|
|
7623
8635
|
}
|
|
7624
|
-
const parsed = JSON.parse(
|
|
8636
|
+
const parsed = JSON.parse(readFileSync25(targetPath, "utf-8"));
|
|
7625
8637
|
const rendered = renderRequirementsMarkdown(parsed);
|
|
7626
|
-
const outPath =
|
|
8638
|
+
const outPath = join23(dirname8(targetPath), "requirements.md");
|
|
7627
8639
|
const tmpPath = outPath + ".tmp";
|
|
7628
|
-
if (
|
|
7629
|
-
const existing =
|
|
8640
|
+
if (existsSync22(outPath)) {
|
|
8641
|
+
const existing = readFileSync25(outPath, "utf-8");
|
|
7630
8642
|
if (existing !== rendered) {
|
|
7631
8643
|
if (!opts.force) {
|
|
7632
8644
|
process.stderr.write(
|
|
@@ -8006,7 +9018,9 @@ function renderRequirementsMarkdown(json) {
|
|
|
8006
9018
|
}
|
|
8007
9019
|
|
|
8008
9020
|
// src/cli/commands/companion.ts
|
|
8009
|
-
import { basename as basename5 } from "path";
|
|
9021
|
+
import { basename as basename5, dirname as dirname11, join as join28 } from "path";
|
|
9022
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync30, writeFileSync as writeFileSync17 } from "fs";
|
|
9023
|
+
init_paths();
|
|
8010
9024
|
|
|
8011
9025
|
// src/shared/companion-render.ts
|
|
8012
9026
|
import stringWidth from "string-width";
|
|
@@ -9192,20 +10206,21 @@ function createBadgeGallery(unlockedAchievements, startIndex) {
|
|
|
9192
10206
|
|
|
9193
10207
|
// src/daemon/companion-memory.ts
|
|
9194
10208
|
init_paths();
|
|
9195
|
-
import { existsSync as
|
|
9196
|
-
import { dirname as dirname10, join as
|
|
10209
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync27, renameSync as renameSync5, writeFileSync as writeFileSync14 } from "fs";
|
|
10210
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
9197
10211
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
9198
10212
|
import { z as z2 } from "zod";
|
|
9199
10213
|
|
|
9200
10214
|
// src/daemon/haiku.ts
|
|
10215
|
+
init_env();
|
|
9201
10216
|
import { query, createSdkMcpServer } from "@r-cli/sdk";
|
|
9202
10217
|
var COOLDOWN_MS = 5 * 60 * 1e3;
|
|
9203
10218
|
|
|
9204
10219
|
// src/daemon/companion.ts
|
|
9205
10220
|
init_paths();
|
|
9206
|
-
import { existsSync as
|
|
10221
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync11, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync13 } from "fs";
|
|
9207
10222
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
9208
|
-
import { dirname as dirname9, join as
|
|
10223
|
+
import { dirname as dirname9, join as join24 } from "path";
|
|
9209
10224
|
|
|
9210
10225
|
// src/shared/companion-normalize.ts
|
|
9211
10226
|
function emptyStats() {
|
|
@@ -9254,20 +10269,20 @@ function normalizeCompanion(state) {
|
|
|
9254
10269
|
// src/daemon/companion.ts
|
|
9255
10270
|
function loadCompanion() {
|
|
9256
10271
|
const path = companionPath();
|
|
9257
|
-
if (!
|
|
10272
|
+
if (!existsSync23(path)) {
|
|
9258
10273
|
const state2 = createDefaultCompanion();
|
|
9259
10274
|
saveCompanion(state2);
|
|
9260
10275
|
return state2;
|
|
9261
10276
|
}
|
|
9262
|
-
const raw =
|
|
10277
|
+
const raw = readFileSync26(path, "utf-8");
|
|
9263
10278
|
const state = JSON.parse(raw);
|
|
9264
10279
|
return normalizeCompanion(state);
|
|
9265
10280
|
}
|
|
9266
10281
|
function saveCompanion(state) {
|
|
9267
10282
|
const path = companionPath();
|
|
9268
10283
|
const dir = dirname9(path);
|
|
9269
|
-
|
|
9270
|
-
const tmp =
|
|
10284
|
+
mkdirSync11(dir, { recursive: true });
|
|
10285
|
+
const tmp = join24(dir, `.companion.${randomUUID3()}.tmp`);
|
|
9271
10286
|
writeFileSync13(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
9272
10287
|
renameSync4(tmp, path);
|
|
9273
10288
|
}
|
|
@@ -9340,10 +10355,10 @@ function fillDefaults(state) {
|
|
|
9340
10355
|
}
|
|
9341
10356
|
function loadMemoryStrict() {
|
|
9342
10357
|
const path = resolvedMemoryPath();
|
|
9343
|
-
if (!
|
|
10358
|
+
if (!existsSync24(path)) return defaultMemoryState();
|
|
9344
10359
|
let raw;
|
|
9345
10360
|
try {
|
|
9346
|
-
raw =
|
|
10361
|
+
raw = readFileSync27(path, "utf-8");
|
|
9347
10362
|
} catch (err) {
|
|
9348
10363
|
throw new MemoryStoreParseError(err);
|
|
9349
10364
|
}
|
|
@@ -9386,15 +10401,17 @@ var ObservationZodSchema = z2.object({
|
|
|
9386
10401
|
});
|
|
9387
10402
|
|
|
9388
10403
|
// src/daemon/companion-popup.ts
|
|
9389
|
-
import { writeFileSync as writeFileSync15, readFileSync as
|
|
10404
|
+
import { writeFileSync as writeFileSync15, readFileSync as readFileSync28, unlinkSync as unlinkSync3, existsSync as existsSync25 } from "fs";
|
|
9390
10405
|
import { tmpdir as tmpdir2 } from "os";
|
|
9391
|
-
import { join as
|
|
10406
|
+
import { join as join26, resolve as resolve9 } from "path";
|
|
10407
|
+
init_exec();
|
|
10408
|
+
init_shell();
|
|
9392
10409
|
var POPUP_WIDTH = 38;
|
|
9393
10410
|
var INNER_WIDTH = POPUP_WIDTH - 6;
|
|
9394
10411
|
var POPUP_DURATION = 15;
|
|
9395
|
-
var POPUP_TMP_PREFIX =
|
|
9396
|
-
var POPUP_SCRIPT =
|
|
9397
|
-
var POPUP_RESULT_PREFIX =
|
|
10412
|
+
var POPUP_TMP_PREFIX = join26(tmpdir2(), "sisyphus-popup");
|
|
10413
|
+
var POPUP_SCRIPT = join26(tmpdir2(), "sisyphus-popup.sh");
|
|
10414
|
+
var POPUP_RESULT_PREFIX = join26(tmpdir2(), "sisyphus-popup-result");
|
|
9398
10415
|
var WHIP_ANIMATION_PATH = resolve9(import.meta.dirname, "../templates/whip-animation.sh");
|
|
9399
10416
|
var WHIP_ANIMATION_ROWS = 12;
|
|
9400
10417
|
function wrapText2(text, width) {
|
|
@@ -9438,7 +10455,7 @@ function showCommentaryPopupQueue(pages) {
|
|
|
9438
10455
|
if (contentHeight > maxContentHeight) maxContentHeight = contentHeight;
|
|
9439
10456
|
writeFileSync15(`${POPUP_TMP_PREFIX}-${i}.txt`, content);
|
|
9440
10457
|
}
|
|
9441
|
-
const whipAvailable =
|
|
10458
|
+
const whipAvailable = existsSync25(WHIP_ANIMATION_PATH);
|
|
9442
10459
|
if (whipAvailable && maxContentHeight < WHIP_ANIMATION_ROWS + 2) {
|
|
9443
10460
|
maxContentHeight = WHIP_ANIMATION_ROWS + 2;
|
|
9444
10461
|
}
|
|
@@ -9512,7 +10529,7 @@ fi
|
|
|
9512
10529
|
}
|
|
9513
10530
|
let raw;
|
|
9514
10531
|
try {
|
|
9515
|
-
raw =
|
|
10532
|
+
raw = readFileSync28(POPUP_RESULT_PREFIX, "utf8").trim();
|
|
9516
10533
|
} catch {
|
|
9517
10534
|
return null;
|
|
9518
10535
|
} finally {
|
|
@@ -9685,9 +10702,29 @@ function registerCompanion(program2) {
|
|
|
9685
10702
|
companion.command("memory").description("Show accumulated companion observations grouped by category").option("--repo <path>", "Filter observations by repo path").action(async (opts) => {
|
|
9686
10703
|
await runCompanionMemory(opts);
|
|
9687
10704
|
});
|
|
9688
|
-
companion.command("context").description("
|
|
9689
|
-
const
|
|
9690
|
-
|
|
10705
|
+
companion.command("context").description("Emit per-prompt context for the companion plugin hook. Caches the last emission per claude session and writes only the delta on subsequent calls (or nothing, when unchanged).").requiredOption("--cwd <path>", "Project directory whose sessions to summarise").requiredOption("--session-id <id>", "Claude session id (from the UserPromptSubmit stdin payload) \u2014 keys the per-session cache").action((opts) => {
|
|
10706
|
+
const cachePath = join28(globalDir(), "companion-context-cache", `${opts.sessionId}.json`);
|
|
10707
|
+
let prev = {};
|
|
10708
|
+
try {
|
|
10709
|
+
prev = JSON.parse(readFileSync30(cachePath, "utf-8"));
|
|
10710
|
+
} catch {
|
|
10711
|
+
prev = {};
|
|
10712
|
+
}
|
|
10713
|
+
const next = buildCompanionContextBlocks(opts.cwd);
|
|
10714
|
+
const hadPrev = Object.keys(prev).length > 0;
|
|
10715
|
+
if (hadPrev) {
|
|
10716
|
+
const delta = renderContextDelta(prev, next);
|
|
10717
|
+
if (delta === null) return;
|
|
10718
|
+
process.stdout.write(delta);
|
|
10719
|
+
} else {
|
|
10720
|
+
process.stdout.write(renderFullContext(next));
|
|
10721
|
+
}
|
|
10722
|
+
mkdirSync14(dirname11(cachePath), { recursive: true });
|
|
10723
|
+
writeFileSync17(cachePath, JSON.stringify(next), "utf-8");
|
|
10724
|
+
});
|
|
10725
|
+
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) => {
|
|
10726
|
+
const { openCompanionPane: openCompanionPane2 } = await Promise.resolve().then(() => (init_tmux(), tmux_exports));
|
|
10727
|
+
openCompanionPane2(opts.cwd);
|
|
9691
10728
|
});
|
|
9692
10729
|
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
10730
|
const feedback = showCommentaryPopup(opts.text);
|
|
@@ -9705,14 +10742,15 @@ function registerCompanion(program2) {
|
|
|
9705
10742
|
}
|
|
9706
10743
|
|
|
9707
10744
|
// src/cli/commands/deploy.ts
|
|
9708
|
-
import { homedir as
|
|
9709
|
-
import { join as
|
|
10745
|
+
import { homedir as homedir13 } from "os";
|
|
10746
|
+
import { join as join29 } from "path";
|
|
9710
10747
|
|
|
9711
10748
|
// src/cli/deploy/runner.ts
|
|
9712
10749
|
init_paths();
|
|
9713
|
-
|
|
9714
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync13, readFileSync as readFileSync29 } from "fs";
|
|
10750
|
+
init_exec();
|
|
9715
10751
|
init_creds();
|
|
10752
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
10753
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync33 } from "fs";
|
|
9716
10754
|
|
|
9717
10755
|
// src/cli/deploy/pricing.ts
|
|
9718
10756
|
var LAST_VERIFIED = "2026-05-06";
|
|
@@ -9747,12 +10785,12 @@ function formatCostLine(provider, instanceType) {
|
|
|
9747
10785
|
// src/cli/deploy/runtime.ts
|
|
9748
10786
|
init_atomic();
|
|
9749
10787
|
init_paths();
|
|
9750
|
-
import { existsSync as
|
|
10788
|
+
import { existsSync as existsSync28, readFileSync as readFileSync32, unlinkSync as unlinkSync4 } from "fs";
|
|
9751
10789
|
function readRuntimeState(provider) {
|
|
9752
10790
|
const path = deployRuntimePath(provider);
|
|
9753
|
-
if (!
|
|
10791
|
+
if (!existsSync28(path)) return null;
|
|
9754
10792
|
try {
|
|
9755
|
-
return JSON.parse(
|
|
10793
|
+
return JSON.parse(readFileSync32(path, "utf-8"));
|
|
9756
10794
|
} catch {
|
|
9757
10795
|
return null;
|
|
9758
10796
|
}
|
|
@@ -9762,10 +10800,11 @@ function writeRuntimeState(provider, state) {
|
|
|
9762
10800
|
}
|
|
9763
10801
|
function clearRuntimeState(provider) {
|
|
9764
10802
|
const path = deployRuntimePath(provider);
|
|
9765
|
-
if (
|
|
10803
|
+
if (existsSync28(path)) unlinkSync4(path);
|
|
9766
10804
|
}
|
|
9767
10805
|
|
|
9768
10806
|
// src/cli/deploy/tailnet.ts
|
|
10807
|
+
init_exec();
|
|
9769
10808
|
function discoverNode(requestedName) {
|
|
9770
10809
|
const json = execSafe("tailscale status --json");
|
|
9771
10810
|
if (!json) return null;
|
|
@@ -9811,15 +10850,15 @@ function isTailscaleAvailable() {
|
|
|
9811
10850
|
}
|
|
9812
10851
|
|
|
9813
10852
|
// src/cli/deploy/templates.ts
|
|
9814
|
-
import { existsSync as
|
|
9815
|
-
import { dirname as
|
|
10853
|
+
import { existsSync as existsSync29 } from "fs";
|
|
10854
|
+
import { dirname as dirname12, resolve as resolve10 } from "path";
|
|
9816
10855
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
9817
10856
|
function deployRoot() {
|
|
9818
|
-
const here =
|
|
10857
|
+
const here = dirname12(fileURLToPath4(import.meta.url));
|
|
9819
10858
|
const bundled = resolve10(here, "..", "deploy");
|
|
9820
|
-
if (
|
|
10859
|
+
if (existsSync29(bundled)) return bundled;
|
|
9821
10860
|
const sourceRoot = resolve10(here, "..", "..", "..", "deploy");
|
|
9822
|
-
if (
|
|
10861
|
+
if (existsSync29(sourceRoot)) return sourceRoot;
|
|
9823
10862
|
throw new Error(
|
|
9824
10863
|
`Could not locate deploy/ templates. Looked at:
|
|
9825
10864
|
${bundled}
|
|
@@ -10003,14 +11042,14 @@ function ensureTerraformInstalled() {
|
|
|
10003
11042
|
function ensureProviderStateDir(provider) {
|
|
10004
11043
|
ensureDeployDir();
|
|
10005
11044
|
const dir = deployProviderDir(provider);
|
|
10006
|
-
if (!
|
|
11045
|
+
if (!existsSync30(dir)) mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
10007
11046
|
}
|
|
10008
11047
|
function backupState(provider) {
|
|
10009
11048
|
const src = deployStatePath(provider);
|
|
10010
|
-
if (
|
|
11049
|
+
if (existsSync30(src)) copyFileSync2(src, deployStateBackupPath(provider));
|
|
10011
11050
|
}
|
|
10012
11051
|
function readSshPubkey(path) {
|
|
10013
|
-
if (!
|
|
11052
|
+
if (!existsSync30(path)) {
|
|
10014
11053
|
const privateKeyPath = path.replace(/\.pub$/, "");
|
|
10015
11054
|
throw new Error(
|
|
10016
11055
|
`SSH pubkey not found at ${path}. Generate one with:
|
|
@@ -10018,7 +11057,7 @@ function readSshPubkey(path) {
|
|
|
10018
11057
|
or pass --ssh-key <path>.`
|
|
10019
11058
|
);
|
|
10020
11059
|
}
|
|
10021
|
-
return
|
|
11060
|
+
return readFileSync33(path, "utf-8").trim();
|
|
10022
11061
|
}
|
|
10023
11062
|
function readOutputs(provider) {
|
|
10024
11063
|
const result = spawnSync3("terraform", ["output", "-json", `-state=${deployStatePath(provider)}`], {
|
|
@@ -10045,7 +11084,7 @@ function readOutputs(provider) {
|
|
|
10045
11084
|
}
|
|
10046
11085
|
}
|
|
10047
11086
|
function isProvisioned(provider) {
|
|
10048
|
-
if (!
|
|
11087
|
+
if (!existsSync30(deployStatePath(provider))) return false;
|
|
10049
11088
|
return readOutputs(provider) !== null;
|
|
10050
11089
|
}
|
|
10051
11090
|
async function deployUp(provider, opts) {
|
|
@@ -10131,7 +11170,7 @@ Applied \u2014 but could not parse outputs. Run \`sis deploy ${provider} status\
|
|
|
10131
11170
|
console.log("");
|
|
10132
11171
|
}
|
|
10133
11172
|
async function deployDown(provider, opts) {
|
|
10134
|
-
if (!
|
|
11173
|
+
if (!existsSync30(deployStatePath(provider))) {
|
|
10135
11174
|
console.log(`No ${provider} state found at ${deployStatePath(provider)}. Nothing to destroy.`);
|
|
10136
11175
|
return;
|
|
10137
11176
|
}
|
|
@@ -10296,7 +11335,7 @@ function registerDeploy(program2) {
|
|
|
10296
11335
|
});
|
|
10297
11336
|
for (const provider of PROVIDERS) {
|
|
10298
11337
|
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.",
|
|
11338
|
+
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.", join29(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
11339
|
const opts = resolveUpOptions(provider, raw);
|
|
10301
11340
|
await deployUp(provider, opts);
|
|
10302
11341
|
});
|
|
@@ -10320,11 +11359,14 @@ function registerDeploy(program2) {
|
|
|
10320
11359
|
|
|
10321
11360
|
// src/cli/cloud/runner.ts
|
|
10322
11361
|
init_paths();
|
|
11362
|
+
init_shell();
|
|
11363
|
+
init_exec();
|
|
11364
|
+
init_creds();
|
|
10323
11365
|
import { spawn as spawn4 } from "child_process";
|
|
10324
11366
|
import { hostname } from "os";
|
|
10325
|
-
init_creds();
|
|
10326
11367
|
|
|
10327
11368
|
// src/cli/deploy/ssh-exec.ts
|
|
11369
|
+
init_exec();
|
|
10328
11370
|
import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
|
|
10329
11371
|
function runOnBox(provider, cmd) {
|
|
10330
11372
|
const target = effectiveSshTarget(provider);
|
|
@@ -10355,6 +11397,7 @@ function runOnBoxStreaming(provider, cmd) {
|
|
|
10355
11397
|
}
|
|
10356
11398
|
|
|
10357
11399
|
// src/cli/cloud/grove.ts
|
|
11400
|
+
init_shell();
|
|
10358
11401
|
var GROVE_VERSION = "0.2.13";
|
|
10359
11402
|
function ensureGroveInstalled(provider) {
|
|
10360
11403
|
const probe = runOnBox(provider, "command -v grove >/dev/null 2>&1");
|
|
@@ -10374,9 +11417,10 @@ function ensureGroveRegistered(provider, repo, instancePath) {
|
|
|
10374
11417
|
}
|
|
10375
11418
|
|
|
10376
11419
|
// src/cli/cloud/repo.ts
|
|
11420
|
+
init_exec();
|
|
10377
11421
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
10378
|
-
import { existsSync as
|
|
10379
|
-
import { basename as basename6, join as
|
|
11422
|
+
import { existsSync as existsSync31 } from "fs";
|
|
11423
|
+
import { basename as basename6, join as join30 } from "path";
|
|
10380
11424
|
function captureGit(args2) {
|
|
10381
11425
|
const result = spawnSync5("git", args2, {
|
|
10382
11426
|
encoding: "utf-8",
|
|
@@ -10427,10 +11471,10 @@ function buildRsyncArgs(localDir, remoteTarget) {
|
|
|
10427
11471
|
];
|
|
10428
11472
|
}
|
|
10429
11473
|
function detectPackageManager(toplevel) {
|
|
10430
|
-
if (
|
|
10431
|
-
if (
|
|
10432
|
-
if (
|
|
10433
|
-
if (
|
|
11474
|
+
if (existsSync31(join30(toplevel, "pnpm-lock.yaml"))) return "pnpm";
|
|
11475
|
+
if (existsSync31(join30(toplevel, "bun.lockb"))) return "bun";
|
|
11476
|
+
if (existsSync31(join30(toplevel, "yarn.lock"))) return "yarn";
|
|
11477
|
+
if (existsSync31(join30(toplevel, "package-lock.json"))) return "npm";
|
|
10434
11478
|
return null;
|
|
10435
11479
|
}
|
|
10436
11480
|
function packageManagerInstallCmd(pm) {
|
|
@@ -10450,6 +11494,7 @@ function packageManagerInstallCmd(pm) {
|
|
|
10450
11494
|
|
|
10451
11495
|
// src/cli/cloud/sidecar.ts
|
|
10452
11496
|
init_paths();
|
|
11497
|
+
init_shell();
|
|
10453
11498
|
function readSidecar(provider, repo) {
|
|
10454
11499
|
const path = boxCloudSidecarPath(repo);
|
|
10455
11500
|
const result = runOnBox(provider, `cat ${shellQuoteHomePath(path)} 2>/dev/null`);
|
|
@@ -10658,6 +11703,7 @@ function pickProvider(explicit) {
|
|
|
10658
11703
|
}
|
|
10659
11704
|
|
|
10660
11705
|
// src/cli/commands/cloud.ts
|
|
11706
|
+
init_shell();
|
|
10661
11707
|
function resolve11(raw) {
|
|
10662
11708
|
const provider = pickProvider(raw.provider);
|
|
10663
11709
|
const repo = raw.name ? raw.name : inferRepoName();
|
|
@@ -10712,8 +11758,8 @@ function attachNotify(diagnostic2) {
|
|
|
10712
11758
|
|
|
10713
11759
|
// src/cli/commands/tmux-sessions.ts
|
|
10714
11760
|
init_paths();
|
|
10715
|
-
import { execSync as
|
|
10716
|
-
import { readFileSync as
|
|
11761
|
+
import { execSync as execSync18 } from "child_process";
|
|
11762
|
+
import { readFileSync as readFileSync34, existsSync as existsSync32 } from "fs";
|
|
10717
11763
|
var DOT_MAP = {
|
|
10718
11764
|
"orchestrator:processing": { icon: "\u25CF", color: "#d4ad6a" },
|
|
10719
11765
|
"orchestrator:idle": { icon: "\u25CF", color: "#d47766" },
|
|
@@ -10724,16 +11770,16 @@ var DOT_MAP = {
|
|
|
10724
11770
|
};
|
|
10725
11771
|
function readManifest() {
|
|
10726
11772
|
const p = sessionsManifestPath();
|
|
10727
|
-
if (!
|
|
11773
|
+
if (!existsSync32(p)) return null;
|
|
10728
11774
|
try {
|
|
10729
|
-
return JSON.parse(
|
|
11775
|
+
return JSON.parse(readFileSync34(p, "utf-8"));
|
|
10730
11776
|
} catch {
|
|
10731
11777
|
return null;
|
|
10732
11778
|
}
|
|
10733
11779
|
}
|
|
10734
11780
|
function tmuxExec(cmd) {
|
|
10735
11781
|
try {
|
|
10736
|
-
return
|
|
11782
|
+
return execSync18(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10737
11783
|
} catch {
|
|
10738
11784
|
return null;
|
|
10739
11785
|
}
|
|
@@ -10772,7 +11818,7 @@ if (nodeVersion < 22) {
|
|
|
10772
11818
|
var program = new Command();
|
|
10773
11819
|
program.name("sis").description("tmux-integrated orchestration daemon for Claude Code").version(
|
|
10774
11820
|
JSON.parse(
|
|
10775
|
-
|
|
11821
|
+
readFileSync35(join31(dirname13(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
|
|
10776
11822
|
).version
|
|
10777
11823
|
);
|
|
10778
11824
|
program.configureHelp({
|
|
@@ -10797,6 +11843,7 @@ registerReconnect(session);
|
|
|
10797
11843
|
registerClone(session);
|
|
10798
11844
|
registerSessionTask(session);
|
|
10799
11845
|
registerSessionEffort(session);
|
|
11846
|
+
registerSessionDangerous(session);
|
|
10800
11847
|
registerSessionContext(session);
|
|
10801
11848
|
var agent = program.command("agent").description("Manage agents");
|
|
10802
11849
|
registerSpawn(agent);
|
|
@@ -10813,6 +11860,8 @@ registerSegmentUnregister(segment);
|
|
|
10813
11860
|
var admin = program.command("admin").description("Admin / setup commands");
|
|
10814
11861
|
registerSetup(admin);
|
|
10815
11862
|
registerSetupKeybind(admin);
|
|
11863
|
+
registerCheckKeybinds(admin);
|
|
11864
|
+
registerCheckStatusbar(admin);
|
|
10816
11865
|
registerHomeInit(admin);
|
|
10817
11866
|
registerDoctor(admin);
|
|
10818
11867
|
registerInit(admin);
|
|
@@ -10843,8 +11892,8 @@ Run 'sis admin getting-started' for a complete usage guide.
|
|
|
10843
11892
|
var args = process.argv.slice(2);
|
|
10844
11893
|
var firstArg = args[0];
|
|
10845
11894
|
var skipWelcome = ["admin", "help", "--help", "-h", "--version", "-V"];
|
|
10846
|
-
if (!
|
|
10847
|
-
|
|
11895
|
+
if (!existsSync33(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
|
|
11896
|
+
mkdirSync17(globalDir(), { recursive: true });
|
|
10848
11897
|
console.log("");
|
|
10849
11898
|
console.log(" Welcome to Sisyphus. Run 'sis admin setup' to get started.");
|
|
10850
11899
|
console.log("");
|