sisyphi 1.1.25 → 1.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -35
- package/deploy/aws/main.tf +1 -1
- package/deploy/aws/variables.tf +1 -1
- package/deploy/aws/versions.tf +1 -1
- package/deploy/hetzner/variables.tf +1 -1
- package/deploy/hetzner/versions.tf +1 -1
- package/deploy/shared/cloud-init.yaml.tpl +1 -1
- package/dist/cli.js +619 -200
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +23 -11
- package/dist/daemon.js.map +1 -1
- package/dist/deploy/aws/main.tf +1 -1
- package/dist/deploy/aws/variables.tf +1 -1
- package/dist/deploy/aws/versions.tf +1 -1
- package/dist/deploy/hetzner/variables.tf +1 -1
- package/dist/deploy/hetzner/versions.tf +1 -1
- package/dist/deploy/shared/cloud-init.yaml.tpl +1 -1
- package/dist/templates/agent-plugin/agents/explore.md +2 -2
- package/dist/templates/agent-plugin/agents/implementor.md +2 -2
- package/dist/templates/agent-plugin/agents/operator.md +3 -3
- package/dist/templates/agent-plugin/agents/plan.md +2 -2
- package/dist/templates/agent-plugin/agents/problem.md +8 -8
- package/dist/templates/agent-plugin/agents/review-plan/CLAUDE.md +1 -1
- package/dist/templates/agent-plugin/agents/spec/requirements-writer.md +1 -1
- package/dist/templates/agent-plugin/agents/spec.md +19 -19
- package/dist/templates/agent-plugin/skills/humanloop/SKILL.md +7 -7
- package/dist/templates/agent-plugin/skills/perspective-fanout/SKILL.md +2 -2
- package/dist/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +2 -2
- package/dist/templates/agent-suffix.md +3 -3
- package/dist/templates/dashboard-claude.md +13 -13
- package/dist/templates/orchestrator-base.md +13 -13
- package/dist/templates/orchestrator-completion.md +11 -11
- package/dist/templates/orchestrator-discovery.md +5 -5
- package/dist/templates/orchestrator-impl.md +8 -8
- package/dist/templates/orchestrator-planning.md +6 -6
- package/dist/templates/orchestrator-plugin/commands/sisyphus/scratch.md +1 -1
- package/dist/templates/orchestrator-plugin/commands/sisyphus/strategize.md +2 -2
- package/dist/templates/orchestrator-plugin/skills/humanloop/SKILL.md +9 -9
- package/dist/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +1 -1
- package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +1 -1
- package/dist/templates/orchestrator-plugin/skills/orchestration/strategy.md +4 -4
- package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +2 -2
- package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +1 -1
- package/dist/templates/orchestrator-validation.md +5 -5
- package/dist/templates/termrender-haiku-system.md +1 -1
- package/dist/tui.js +8 -8
- package/dist/tui.js.map +1 -1
- package/package.json +2 -1
- package/templates/agent-plugin/agents/explore.md +2 -2
- package/templates/agent-plugin/agents/implementor.md +2 -2
- package/templates/agent-plugin/agents/operator.md +3 -3
- package/templates/agent-plugin/agents/plan.md +2 -2
- package/templates/agent-plugin/agents/problem.md +8 -8
- package/templates/agent-plugin/agents/review-plan/CLAUDE.md +1 -1
- package/templates/agent-plugin/agents/spec/requirements-writer.md +1 -1
- package/templates/agent-plugin/agents/spec.md +19 -19
- package/templates/agent-plugin/skills/humanloop/SKILL.md +7 -7
- package/templates/agent-plugin/skills/perspective-fanout/SKILL.md +2 -2
- package/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +2 -2
- package/templates/agent-suffix.md +3 -3
- package/templates/dashboard-claude.md +13 -13
- package/templates/orchestrator-base.md +13 -13
- package/templates/orchestrator-completion.md +11 -11
- package/templates/orchestrator-discovery.md +5 -5
- package/templates/orchestrator-impl.md +8 -8
- package/templates/orchestrator-planning.md +6 -6
- package/templates/orchestrator-plugin/commands/sisyphus/scratch.md +1 -1
- package/templates/orchestrator-plugin/commands/sisyphus/strategize.md +2 -2
- package/templates/orchestrator-plugin/skills/humanloop/SKILL.md +9 -9
- package/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +1 -1
- package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +1 -1
- package/templates/orchestrator-plugin/skills/orchestration/strategy.md +4 -4
- package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +2 -2
- package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +1 -1
- package/templates/orchestrator-validation.md +5 -5
- package/templates/termrender-haiku-system.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -120,6 +120,15 @@ function deployCredsPath(provider) {
|
|
|
120
120
|
function deployTailscaleEnvPath() {
|
|
121
121
|
return join(deployDir(), "tailscale.env");
|
|
122
122
|
}
|
|
123
|
+
function boxRepoPath(repo) {
|
|
124
|
+
return `~/projects/${repo}`;
|
|
125
|
+
}
|
|
126
|
+
function boxCloudSidecarPath(repo) {
|
|
127
|
+
return `~/.sisyphus/cloud/${repo}.json`;
|
|
128
|
+
}
|
|
129
|
+
function boxCloudSidecarDir() {
|
|
130
|
+
return `~/.sisyphus/cloud`;
|
|
131
|
+
}
|
|
123
132
|
var init_paths = __esm({
|
|
124
133
|
"src/shared/paths.ts"() {
|
|
125
134
|
"use strict";
|
|
@@ -138,16 +147,16 @@ function atomicWrite(filePath, data) {
|
|
|
138
147
|
}
|
|
139
148
|
async function withLock(key, fn) {
|
|
140
149
|
const prev = locks.get(key) ?? Promise.resolve();
|
|
141
|
-
let
|
|
150
|
+
let resolve12;
|
|
142
151
|
const next = new Promise((r) => {
|
|
143
|
-
|
|
152
|
+
resolve12 = r;
|
|
144
153
|
});
|
|
145
154
|
locks.set(key, next);
|
|
146
155
|
await prev;
|
|
147
156
|
try {
|
|
148
157
|
return fn();
|
|
149
158
|
} finally {
|
|
150
|
-
|
|
159
|
+
resolve12();
|
|
151
160
|
if (locks.get(key) === next) {
|
|
152
161
|
locks.delete(key);
|
|
153
162
|
}
|
|
@@ -164,7 +173,9 @@ var init_atomic = __esm({
|
|
|
164
173
|
// src/cli/deploy/creds.ts
|
|
165
174
|
var creds_exports = {};
|
|
166
175
|
__export(creds_exports, {
|
|
176
|
+
PROVIDERS: () => PROVIDERS,
|
|
167
177
|
ensureDeployDir: () => ensureDeployDir,
|
|
178
|
+
isValidProvider: () => isValidProvider,
|
|
168
179
|
loadProviderCreds: () => loadProviderCreds,
|
|
169
180
|
maskValue: () => maskValue,
|
|
170
181
|
promptLine: () => promptLine,
|
|
@@ -173,6 +184,9 @@ __export(creds_exports, {
|
|
|
173
184
|
});
|
|
174
185
|
import { chmodSync as chmodSync3, existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync27 } from "fs";
|
|
175
186
|
import { createInterface as createInterface4 } from "readline";
|
|
187
|
+
function isValidProvider(value) {
|
|
188
|
+
return PROVIDERS.includes(value);
|
|
189
|
+
}
|
|
176
190
|
function ensureDeployDir() {
|
|
177
191
|
const dir = deployDir();
|
|
178
192
|
if (!existsSync24(dir)) mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
@@ -194,7 +208,7 @@ function parseEnvFile(text) {
|
|
|
194
208
|
return out;
|
|
195
209
|
}
|
|
196
210
|
function serializeEnvFile(values) {
|
|
197
|
-
const lines = ["# Managed by `
|
|
211
|
+
const lines = ["# Managed by `sis deploy`. Do not edit unless you know what you are doing."];
|
|
198
212
|
for (const [k, v] of Object.entries(values)) {
|
|
199
213
|
lines.push(`${k}=${v}`);
|
|
200
214
|
}
|
|
@@ -222,7 +236,7 @@ async function promptLine(question, hidden) {
|
|
|
222
236
|
}
|
|
223
237
|
};
|
|
224
238
|
}
|
|
225
|
-
const answer = await new Promise((
|
|
239
|
+
const answer = await new Promise((resolve12) => rl.question(question, resolve12));
|
|
226
240
|
rl.close();
|
|
227
241
|
if (hidden) process.stdout.write("\n");
|
|
228
242
|
return answer.trim();
|
|
@@ -272,12 +286,13 @@ function writeTailscaleEnv(env) {
|
|
|
272
286
|
if (env.tag) values.TS_TAG = env.tag;
|
|
273
287
|
writeEnvFile(deployTailscaleEnvPath(), values);
|
|
274
288
|
}
|
|
275
|
-
var SPECS;
|
|
289
|
+
var PROVIDERS, SPECS;
|
|
276
290
|
var init_creds = __esm({
|
|
277
291
|
"src/cli/deploy/creds.ts"() {
|
|
278
292
|
"use strict";
|
|
279
293
|
init_atomic();
|
|
280
294
|
init_paths();
|
|
295
|
+
PROVIDERS = ["hetzner", "aws"];
|
|
281
296
|
SPECS = {
|
|
282
297
|
hetzner: {
|
|
283
298
|
provider: "hetzner",
|
|
@@ -296,8 +311,8 @@ var init_creds = __esm({
|
|
|
296
311
|
|
|
297
312
|
// src/cli/index.ts
|
|
298
313
|
import { Command } from "commander";
|
|
299
|
-
import { existsSync as
|
|
300
|
-
import { dirname as dirname12, join as
|
|
314
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync14, readFileSync as readFileSync31 } from "fs";
|
|
315
|
+
import { dirname as dirname12, join as join27 } from "path";
|
|
301
316
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
302
317
|
|
|
303
318
|
// src/cli/commands/start.ts
|
|
@@ -309,13 +324,13 @@ init_paths();
|
|
|
309
324
|
import { connect } from "net";
|
|
310
325
|
function rawSend(request, timeoutMs = 1e4) {
|
|
311
326
|
const sock = socketPath();
|
|
312
|
-
return new Promise((
|
|
327
|
+
return new Promise((resolve12, reject) => {
|
|
313
328
|
const socket = connect(sock);
|
|
314
329
|
let data = "";
|
|
315
330
|
const timeout = setTimeout(() => {
|
|
316
331
|
socket.destroy();
|
|
317
332
|
reject(new Error(`Request timed out after ${(timeoutMs / 1e3).toFixed(0)}s. The daemon may be overloaded.
|
|
318
|
-
Check:
|
|
333
|
+
Check: sis admin doctor
|
|
319
334
|
Logs: tail -20 ~/.sisyphus/daemon.log`));
|
|
320
335
|
}, timeoutMs);
|
|
321
336
|
socket.on("connect", () => {
|
|
@@ -329,7 +344,7 @@ function rawSend(request, timeoutMs = 1e4) {
|
|
|
329
344
|
const line = data.slice(0, newlineIdx);
|
|
330
345
|
socket.destroy();
|
|
331
346
|
try {
|
|
332
|
-
|
|
347
|
+
resolve12(JSON.parse(line));
|
|
333
348
|
} catch {
|
|
334
349
|
reject(new Error(`Invalid JSON response from daemon: ${line}`));
|
|
335
350
|
}
|
|
@@ -594,7 +609,7 @@ while IFS=$'\\t' read -r type name scwd phase sid; do
|
|
|
594
609
|
[ "$sid" = "$current_id" ] && { cwd="$scwd"; break; }
|
|
595
610
|
done < "$MANIFEST"
|
|
596
611
|
if [ -z "$cwd" ]; then
|
|
597
|
-
tmux display-message "sisyphus: '$current_name' has no @sisyphus_cwd \u2014 run '
|
|
612
|
+
tmux display-message "sisyphus: '$current_name' has no @sisyphus_cwd \u2014 run 'sis start' here to register"
|
|
598
613
|
exit 0
|
|
599
614
|
fi
|
|
600
615
|
session_ids=()
|
|
@@ -699,7 +714,7 @@ tmpfile=$(mktemp /tmp/sisyphus-new-XXXXXX.md)
|
|
|
699
714
|
trap 'rm -f "$tmpfile"' EXIT
|
|
700
715
|
nvim "$tmpfile"
|
|
701
716
|
grep -q '[^[:space:]]' "$tmpfile" || exit 0
|
|
702
|
-
exec
|
|
717
|
+
exec sis start "$(cat "$tmpfile")"
|
|
703
718
|
`;
|
|
704
719
|
var MESSAGE_SCRIPT = `#!/bin/bash
|
|
705
720
|
# Open nvim to compose a message for the current session's orchestrator
|
|
@@ -730,7 +745,7 @@ tmpfile=$(mktemp /tmp/sisyphus-msg-XXXXXX.md)
|
|
|
730
745
|
trap 'rm -f "$tmpfile"' EXIT
|
|
731
746
|
nvim "$tmpfile"
|
|
732
747
|
grep -q '[^[:space:]]' "$tmpfile" || exit 0
|
|
733
|
-
exec
|
|
748
|
+
exec sis message --session "$session_id" "$(cat "$tmpfile")"
|
|
734
749
|
`;
|
|
735
750
|
var SESSION_RESOLVE = `
|
|
736
751
|
tmux_sid=$(tmux display-message -p '#{session_id}')
|
|
@@ -766,7 +781,7 @@ var KILL_SESSION_SCRIPT = `#!/bin/bash
|
|
|
766
781
|
# Kill the sisyphus session associated with the current tmux session
|
|
767
782
|
${SESSION_RESOLVE}
|
|
768
783
|
|
|
769
|
-
|
|
784
|
+
sis session kill "$session_id" >/dev/null 2>&1
|
|
770
785
|
${GO_HOME_AFTER}
|
|
771
786
|
`;
|
|
772
787
|
var DELETE_SESSION_SCRIPT = `#!/bin/bash
|
|
@@ -776,7 +791,7 @@ ${SESSION_RESOLVE}
|
|
|
776
791
|
printf "\\033[31mType 'yes' to confirm:\\033[0m "
|
|
777
792
|
read -r answer
|
|
778
793
|
[ "$answer" = "yes" ] || exit 0
|
|
779
|
-
|
|
794
|
+
sis session delete "$session_id" --cwd "$cwd" >/dev/null 2>&1
|
|
780
795
|
${GO_HOME_AFTER}
|
|
781
796
|
`;
|
|
782
797
|
var HELP_SCRIPT = `#!/bin/bash
|
|
@@ -810,9 +825,9 @@ if [ -z "$session_id" ]; then
|
|
|
810
825
|
fi
|
|
811
826
|
|
|
812
827
|
if [ -n "$session_id" ]; then
|
|
813
|
-
|
|
828
|
+
sis status "$session_id" 2>&1 | less -R
|
|
814
829
|
else
|
|
815
|
-
|
|
830
|
+
sis list 2>&1 | less -R
|
|
816
831
|
fi
|
|
817
832
|
`;
|
|
818
833
|
var PICK_SESSION_SCRIPT = `#!/bin/bash
|
|
@@ -871,7 +886,7 @@ short_id="\${session_id:0:8}"
|
|
|
871
886
|
printf "\\033[33mContinue session %s...?\\033[0m (y/n) " "$short_id"
|
|
872
887
|
read -r answer
|
|
873
888
|
[ "$answer" = "y" ] || [ "$answer" = "yes" ] || exit 0
|
|
874
|
-
|
|
889
|
+
sis session continue --session "$session_id"
|
|
875
890
|
sleep 1
|
|
876
891
|
`;
|
|
877
892
|
var OPEN_ROADMAP_SCRIPT = `#!/bin/bash
|
|
@@ -895,18 +910,18 @@ var EXPORT_SESSION_SCRIPT = `#!/bin/bash
|
|
|
895
910
|
${SESSION_RESOLVE}
|
|
896
911
|
|
|
897
912
|
echo "Exporting session \${session_id:0:8}..."
|
|
898
|
-
|
|
913
|
+
sis admin export "$session_id" --cwd "$cwd"
|
|
899
914
|
echo ""
|
|
900
915
|
read -n 1 -s -r -p "Press a key to close."
|
|
901
916
|
`;
|
|
902
917
|
var RESTART_AGENT_SCRIPT = `#!/bin/bash
|
|
903
918
|
# Pick a sisyphus agent and restart it (fzf picker with confirm for running agents).
|
|
904
|
-
# Assumes macOS (fzf optional). Requires \`
|
|
919
|
+
# Assumes macOS (fzf optional). Requires \`sis status --json\`.
|
|
905
920
|
${SESSION_RESOLVE}
|
|
906
921
|
|
|
907
922
|
command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
908
923
|
|
|
909
|
-
agents_json=$(
|
|
924
|
+
agents_json=$(sis status "$session_id" --json 2>/dev/null)
|
|
910
925
|
if [ -z "$agents_json" ]; then
|
|
911
926
|
echo "Failed to read session status"; sleep 1; exit 1
|
|
912
927
|
fi
|
|
@@ -943,7 +958,7 @@ if [ "\${statuses[$idx]}" = "running" ]; then
|
|
|
943
958
|
[ "$answer" = "yes" ] || exit 0
|
|
944
959
|
fi
|
|
945
960
|
|
|
946
|
-
|
|
961
|
+
sis agent restart "\${ids[$idx]}" --session "$session_id"
|
|
947
962
|
echo ""
|
|
948
963
|
read -n 1 -s -r -p "Press a key to close."
|
|
949
964
|
`;
|
|
@@ -993,9 +1008,9 @@ nvim "$tmpfile"
|
|
|
993
1008
|
body=$(grep -v '^[[:space:]]*#' "$tmpfile" | sed '/^[[:space:]]*$/d')
|
|
994
1009
|
|
|
995
1010
|
if [ -z "$body" ]; then
|
|
996
|
-
exec
|
|
1011
|
+
exec sis session resume "$session_id"
|
|
997
1012
|
else
|
|
998
|
-
exec
|
|
1013
|
+
exec sis session resume "$session_id" "$body"
|
|
999
1014
|
fi
|
|
1000
1015
|
`;
|
|
1001
1016
|
var ROLLBACK_SESSION_SCRIPT = `#!/bin/bash
|
|
@@ -1022,7 +1037,7 @@ if [ "$cycle_input" -lt 1 ]; then
|
|
|
1022
1037
|
exit 0
|
|
1023
1038
|
fi
|
|
1024
1039
|
|
|
1025
|
-
|
|
1040
|
+
sis session rollback "$session_id" "$cycle_input"
|
|
1026
1041
|
echo ""
|
|
1027
1042
|
echo "Rolled back to cycle $cycle_input \u2014 use [C-s S r] to resume."
|
|
1028
1043
|
read -n 1 -s -r -p "Press a key to close."
|
|
@@ -1059,12 +1074,12 @@ fi
|
|
|
1059
1074
|
|
|
1060
1075
|
# Fallback: orchestrator window is gone. Open last claude session in a popup.
|
|
1061
1076
|
state="$cwd/.sisyphus/sessions/$session_id/state.json"
|
|
1062
|
-
[ ! -f "$state" ] && { tmux display-message "Window dead and no state.json \u2014 try
|
|
1077
|
+
[ ! -f "$state" ] && { tmux display-message "Window dead and no state.json \u2014 try sis session resume"; exit 0; }
|
|
1063
1078
|
|
|
1064
1079
|
claude_sid=$(jq -r '[.orchestratorCycles[].claudeSessionId] | last // empty' "$state")
|
|
1065
1080
|
|
|
1066
1081
|
if [ -z "$claude_sid" ]; then
|
|
1067
|
-
tmux display-message "No orchestrator claude session id found \u2014 try
|
|
1082
|
+
tmux display-message "No orchestrator claude session id found \u2014 try sis session resume"
|
|
1068
1083
|
exit 0
|
|
1069
1084
|
fi
|
|
1070
1085
|
|
|
@@ -1086,7 +1101,7 @@ nvim "$tmpfile"
|
|
|
1086
1101
|
body=$(grep -v '^[[:space:]]*#' "$tmpfile" | sed '/^[[:space:]]*$/d')
|
|
1087
1102
|
[ -z "$body" ] && exit 0
|
|
1088
1103
|
|
|
1089
|
-
exec
|
|
1104
|
+
exec sis agent spawn --session "$session_id" --name "agent" --instruction "$body"
|
|
1090
1105
|
`;
|
|
1091
1106
|
var SEARCH_REPORTS_SCRIPT = `#!/bin/bash
|
|
1092
1107
|
# fzf over reports/*.md across all sessions for the current cwd.
|
|
@@ -1125,12 +1140,12 @@ fi
|
|
|
1125
1140
|
`;
|
|
1126
1141
|
var JUMP_TO_PANE_SCRIPT = `#!/bin/bash
|
|
1127
1142
|
# Pick a sisyphus agent and jump to its tmux pane.
|
|
1128
|
-
# Assumes macOS (pbcopy, fzf optional). Requires \`
|
|
1143
|
+
# Assumes macOS (pbcopy, fzf optional). Requires \`sis status --json\`.
|
|
1129
1144
|
${SESSION_RESOLVE}
|
|
1130
1145
|
|
|
1131
1146
|
command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
1132
1147
|
|
|
1133
|
-
agents_json=$(
|
|
1148
|
+
agents_json=$(sis status "$session_id" --json 2>/dev/null)
|
|
1134
1149
|
if [ -z "$agents_json" ]; then
|
|
1135
1150
|
echo "Failed to read session status"; sleep 1; exit 1
|
|
1136
1151
|
fi
|
|
@@ -1171,12 +1186,12 @@ tmux select-pane -t "$target_pane"
|
|
|
1171
1186
|
`;
|
|
1172
1187
|
var MSG_AGENT_SCRIPT = `#!/bin/bash
|
|
1173
1188
|
# Pick a sisyphus agent and send it a message via nvim.
|
|
1174
|
-
# Assumes macOS (fzf optional). Requires \`
|
|
1189
|
+
# Assumes macOS (fzf optional). Requires \`sis status --json\` and \`--agent\` on message.
|
|
1175
1190
|
${SESSION_RESOLVE}
|
|
1176
1191
|
|
|
1177
1192
|
command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
1178
1193
|
|
|
1179
|
-
agents_json=$(
|
|
1194
|
+
agents_json=$(sis status "$session_id" --json 2>/dev/null)
|
|
1180
1195
|
if [ -z "$agents_json" ]; then
|
|
1181
1196
|
echo "Failed to read session status"; sleep 1; exit 1
|
|
1182
1197
|
fi
|
|
@@ -1209,16 +1224,16 @@ tmpfile=$(mktemp /tmp/sisyphus-msg-agent-XXXX.md)
|
|
|
1209
1224
|
trap 'rm -f "$tmpfile"' EXIT
|
|
1210
1225
|
nvim "$tmpfile"
|
|
1211
1226
|
grep -q '[^[:space:]]' "$tmpfile" || exit 0
|
|
1212
|
-
exec
|
|
1227
|
+
exec sis message --session "$session_id" --agent "\${ids[$idx]}" "$(cat "$tmpfile")"
|
|
1213
1228
|
`;
|
|
1214
1229
|
var RERUN_AGENT_SCRIPT = `#!/bin/bash
|
|
1215
1230
|
# Pick a sisyphus agent and spawn a retry with its original instruction.
|
|
1216
|
-
# Assumes macOS (fzf optional). Requires \`
|
|
1231
|
+
# Assumes macOS (fzf optional). Requires \`sis status --json\`.
|
|
1217
1232
|
${SESSION_RESOLVE}
|
|
1218
1233
|
|
|
1219
1234
|
command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
1220
1235
|
|
|
1221
|
-
agents_json=$(
|
|
1236
|
+
agents_json=$(sis status "$session_id" --json 2>/dev/null)
|
|
1222
1237
|
if [ -z "$agents_json" ]; then
|
|
1223
1238
|
echo "Failed to read session status"; sleep 1; exit 1
|
|
1224
1239
|
fi
|
|
@@ -1260,11 +1275,11 @@ if [ "\${#instr}" -lt 20 ]; then
|
|
|
1260
1275
|
exit 1
|
|
1261
1276
|
fi
|
|
1262
1277
|
|
|
1263
|
-
exec
|
|
1278
|
+
exec sis agent spawn --session "$session_id" --agent-type "\${atypes[$idx]}" --name "\${anames[$idx]}-retry-$(date +%s)" --instruction "$instr"
|
|
1264
1279
|
`;
|
|
1265
1280
|
var OPEN_CLAUDE_AGENT_SCRIPT = `#!/bin/bash
|
|
1266
1281
|
# Pick a sisyphus agent or orchestrator cycle and resume its Claude session.
|
|
1267
|
-
# Assumes macOS (fzf optional). Requires \`
|
|
1282
|
+
# Assumes macOS (fzf optional). Requires \`sis status --json\`.
|
|
1268
1283
|
${SESSION_RESOLVE}
|
|
1269
1284
|
|
|
1270
1285
|
command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
@@ -1272,7 +1287,7 @@ command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
|
1272
1287
|
state="$cwd/.sisyphus/sessions/$session_id/state.json"
|
|
1273
1288
|
[ ! -f "$state" ] && { echo "No state.json for this session"; sleep 1; exit 1; }
|
|
1274
1289
|
|
|
1275
|
-
agents_json=$(
|
|
1290
|
+
agents_json=$(sis status "$session_id" --json 2>/dev/null)
|
|
1276
1291
|
if [ -z "$agents_json" ]; then
|
|
1277
1292
|
echo "Failed to read session status"; sleep 1; exit 1
|
|
1278
1293
|
fi
|
|
@@ -1312,12 +1327,12 @@ cd "$cwd" && exec claude --resume "$cid"
|
|
|
1312
1327
|
var TAIL_AGENT_LOGS_SCRIPT = `#!/bin/bash
|
|
1313
1328
|
# Pick a sisyphus agent and view its tmux pane scrollback (last 2000 lines) in less.
|
|
1314
1329
|
# Uses tmux capture-pane \u2014 no tail -f, no pipe-pane side effects.
|
|
1315
|
-
# Assumes macOS (fzf optional). Requires \`
|
|
1330
|
+
# Assumes macOS (fzf optional). Requires \`sis status --json\`.
|
|
1316
1331
|
${SESSION_RESOLVE}
|
|
1317
1332
|
|
|
1318
1333
|
command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
1319
1334
|
|
|
1320
|
-
agents_json=$(
|
|
1335
|
+
agents_json=$(sis status "$session_id" --json 2>/dev/null)
|
|
1321
1336
|
if [ -z "$agents_json" ]; then
|
|
1322
1337
|
echo "Failed to read session status"; sleep 1; exit 1
|
|
1323
1338
|
fi
|
|
@@ -1354,12 +1369,12 @@ tmux capture-pane -t "$target_pane" -p -S -2000 | less +G
|
|
|
1354
1369
|
`;
|
|
1355
1370
|
var KILL_AGENT_SCRIPT = `#!/bin/bash
|
|
1356
1371
|
# Pick a sisyphus agent and kill it (with red confirmation prompt).
|
|
1357
|
-
# Assumes macOS (fzf optional). Requires \`
|
|
1372
|
+
# Assumes macOS (fzf optional). Requires \`sis status --json\` and \`sis agent kill\`.
|
|
1358
1373
|
${SESSION_RESOLVE}
|
|
1359
1374
|
|
|
1360
1375
|
command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
1361
1376
|
|
|
1362
|
-
agents_json=$(
|
|
1377
|
+
agents_json=$(sis status "$session_id" --json 2>/dev/null)
|
|
1363
1378
|
if [ -z "$agents_json" ]; then
|
|
1364
1379
|
echo "Failed to read session status"; sleep 1; exit 1
|
|
1365
1380
|
fi
|
|
@@ -1391,18 +1406,18 @@ fi
|
|
|
1391
1406
|
printf '\\033[31mKill %s? (yes/no): \\033[0m' "\${ids[$idx]}"
|
|
1392
1407
|
read -r answer
|
|
1393
1408
|
[ "$answer" = "yes" ] || exit 0
|
|
1394
|
-
|
|
1409
|
+
sis agent kill "\${ids[$idx]}" --session "$session_id"
|
|
1395
1410
|
echo ""
|
|
1396
1411
|
read -n 1 -s -r -p "Press a key to close."
|
|
1397
1412
|
`;
|
|
1398
1413
|
var COPY_AGENT_ID_SCRIPT = `#!/bin/bash
|
|
1399
1414
|
# Pick a sisyphus agent and copy its ID to clipboard.
|
|
1400
|
-
# Assumes macOS (pbcopy, fzf optional). Requires \`
|
|
1415
|
+
# Assumes macOS (pbcopy, fzf optional). Requires \`sis status --json\`.
|
|
1401
1416
|
${SESSION_RESOLVE}
|
|
1402
1417
|
|
|
1403
1418
|
command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
|
|
1404
1419
|
|
|
1405
|
-
agents_json=$(
|
|
1420
|
+
agents_json=$(sis status "$session_id" --json 2>/dev/null)
|
|
1406
1421
|
if [ -z "$agents_json" ]; then
|
|
1407
1422
|
echo "Failed to read session status"; sleep 1; exit 1
|
|
1408
1423
|
fi
|
|
@@ -1477,10 +1492,10 @@ tmux display-message "Copied session ID"
|
|
|
1477
1492
|
`;
|
|
1478
1493
|
var COPY_CONTEXT_SCRIPT = `#!/bin/bash
|
|
1479
1494
|
# Copy the session context XML to clipboard.
|
|
1480
|
-
# Assumes macOS (pbcopy). Requires \`
|
|
1495
|
+
# Assumes macOS (pbcopy). Requires \`sis session context\`.
|
|
1481
1496
|
${SESSION_RESOLVE}
|
|
1482
1497
|
|
|
1483
|
-
|
|
1498
|
+
sis session context "$session_id" --cwd "$cwd" | pbcopy
|
|
1484
1499
|
tmux display-message "Copied session context (XML)"
|
|
1485
1500
|
`;
|
|
1486
1501
|
var EDIT_CONTEXT_FILE_SCRIPT = `#!/bin/bash
|
|
@@ -1541,7 +1556,7 @@ if [ "\${#instruction}" -lt 20 ]; then
|
|
|
1541
1556
|
fi
|
|
1542
1557
|
|
|
1543
1558
|
name="explore-$(date +%s)"
|
|
1544
|
-
|
|
1559
|
+
sis agent spawn \\
|
|
1545
1560
|
--agent-type sisyphus:explore \\
|
|
1546
1561
|
--name "$name" \\
|
|
1547
1562
|
--session "$session_id" \\
|
|
@@ -1575,7 +1590,7 @@ if [ "\${#instruction}" -lt 20 ]; then
|
|
|
1575
1590
|
fi
|
|
1576
1591
|
|
|
1577
1592
|
name="debug-$(date +%s)"
|
|
1578
|
-
|
|
1593
|
+
sis agent spawn \\
|
|
1579
1594
|
--agent-type sisyphus:debug \\
|
|
1580
1595
|
--name "$name" \\
|
|
1581
1596
|
--session "$session_id" \\
|
|
@@ -1623,7 +1638,7 @@ args=()
|
|
|
1623
1638
|
[ -n "$clone_name" ] && args+=(--name "$clone_name")
|
|
1624
1639
|
[ "$copy_strategy" = "y" ] || [ "$copy_strategy" = "Y" ] && args+=(--strategy)
|
|
1625
1640
|
|
|
1626
|
-
|
|
1641
|
+
sis session clone "\${args[@]}" "$goal"
|
|
1627
1642
|
exit_code=$?
|
|
1628
1643
|
read -n 1 -s -r -p "Press a key to close."
|
|
1629
1644
|
exit $exit_code
|
|
@@ -1631,12 +1646,12 @@ exit $exit_code
|
|
|
1631
1646
|
var HISTORY_SCRIPT = `#!/bin/bash
|
|
1632
1647
|
# Show rich session detail (history command's per-session view) in a popup.
|
|
1633
1648
|
${SESSION_RESOLVE}
|
|
1634
|
-
|
|
1649
|
+
sis admin history "$session_id" 2>&1 | less -R
|
|
1635
1650
|
`;
|
|
1636
1651
|
var RECONNECT_SCRIPT = `#!/bin/bash
|
|
1637
1652
|
# Reconnect daemon to an orphaned tmux session for the current cwd.
|
|
1638
1653
|
${SESSION_RESOLVE}
|
|
1639
|
-
|
|
1654
|
+
sis session reconnect "$session_id"
|
|
1640
1655
|
exit_code=$?
|
|
1641
1656
|
read -n 1 -s -r -p "Press a key to close."
|
|
1642
1657
|
exit $exit_code
|
|
@@ -1644,7 +1659,7 @@ exit $exit_code
|
|
|
1644
1659
|
var OPEN_SCRATCH_SCRIPT = `#!/bin/bash
|
|
1645
1660
|
# Open a standalone Claude scratch window in the home tmux session for this cwd.
|
|
1646
1661
|
# scratch resolves the home session itself via @sisyphus_cwd; no session_id needed.
|
|
1647
|
-
exec
|
|
1662
|
+
exec sis admin scratch
|
|
1648
1663
|
`;
|
|
1649
1664
|
function installScript(name, content) {
|
|
1650
1665
|
mkdirSync(join2(globalDir(), "bin"), { recursive: true });
|
|
@@ -1724,7 +1739,7 @@ function isSisyphusBinding(binding) {
|
|
|
1724
1739
|
async function confirmConfAppend(userConf, line) {
|
|
1725
1740
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
1726
1741
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1727
|
-
return new Promise((
|
|
1742
|
+
return new Promise((resolve12) => {
|
|
1728
1743
|
const question = `
|
|
1729
1744
|
Sisyphus needs to append one line to ${userConf} so its tmux keybindings persist:
|
|
1730
1745
|
${line}
|
|
@@ -1732,7 +1747,7 @@ Sisyphus needs to append one line to ${userConf} so its tmux keybindings persist
|
|
|
1732
1747
|
Append it now? (y/N) `;
|
|
1733
1748
|
rl.question(question, (answer) => {
|
|
1734
1749
|
rl.close();
|
|
1735
|
-
|
|
1750
|
+
resolve12(answer.trim().toLowerCase() === "y");
|
|
1736
1751
|
});
|
|
1737
1752
|
});
|
|
1738
1753
|
}
|
|
@@ -1754,7 +1769,7 @@ async function setupTmuxKeybind(cycleKey = DEFAULT_CYCLE_KEY, prefixKey = DEFAUL
|
|
|
1754
1769
|
if (existing !== null && !isSisyphusBinding(existing)) {
|
|
1755
1770
|
return {
|
|
1756
1771
|
status: "conflict",
|
|
1757
|
-
message: `Tmux key ${key} (${label}) is already bound to something else. Run "
|
|
1772
|
+
message: `Tmux key ${key} (${label}) is already bound to something else. Run "sis admin setup-keybind <key>" to use a different key.`,
|
|
1758
1773
|
existingBinding: existing
|
|
1759
1774
|
};
|
|
1760
1775
|
}
|
|
@@ -2145,20 +2160,20 @@ function printGettingStarted(keybindResult, sisyphusPlugin) {
|
|
|
2145
2160
|
if (sisyphusPlugin.installed && sisyphusPlugin.autoInstalled) {
|
|
2146
2161
|
lines.push(`Sisyphus plugin installed: sisyphus@sisyphus \u2192 ${sisyphusPlugin.installPath}`, "");
|
|
2147
2162
|
} else if (!sisyphusPlugin.installed) {
|
|
2148
|
-
lines.push("Sisyphus plugin: failed to install (run `
|
|
2163
|
+
lines.push("Sisyphus plugin: failed to install (run `sis admin setup` to retry; needs `claude` CLI)", "");
|
|
2149
2164
|
}
|
|
2150
2165
|
lines.push(
|
|
2151
|
-
"Run `
|
|
2166
|
+
"Run `sis admin getting-started` for a complete usage guide.",
|
|
2152
2167
|
""
|
|
2153
2168
|
);
|
|
2154
2169
|
console.log(lines.join("\n"));
|
|
2155
2170
|
}
|
|
2156
2171
|
function testConnection() {
|
|
2157
|
-
return new Promise((
|
|
2172
|
+
return new Promise((resolve12, reject) => {
|
|
2158
2173
|
const sock = connect2(socketPath());
|
|
2159
2174
|
sock.on("connect", () => {
|
|
2160
2175
|
sock.destroy();
|
|
2161
|
-
|
|
2176
|
+
resolve12();
|
|
2162
2177
|
});
|
|
2163
2178
|
sock.on("error", (err) => {
|
|
2164
2179
|
sock.destroy();
|
|
@@ -2167,7 +2182,7 @@ function testConnection() {
|
|
|
2167
2182
|
});
|
|
2168
2183
|
}
|
|
2169
2184
|
function sleep(ms) {
|
|
2170
|
-
return new Promise((
|
|
2185
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
2171
2186
|
}
|
|
2172
2187
|
async function waitForDaemon(maxWaitMs = 6e3) {
|
|
2173
2188
|
const start = Date.now();
|
|
@@ -2203,7 +2218,7 @@ function rawSend2(request, timeoutMs = 1e4) {
|
|
|
2203
2218
|
return rawSend(request, timeoutMs);
|
|
2204
2219
|
}
|
|
2205
2220
|
async function sendRequest(request, timeoutMs) {
|
|
2206
|
-
const sleep2 = (ms) => new Promise((
|
|
2221
|
+
const sleep2 = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
2207
2222
|
const MAX_ATTEMPTS = 5;
|
|
2208
2223
|
const RETRY_DELAY_MS = 2e3;
|
|
2209
2224
|
let installedDaemon = false;
|
|
@@ -2257,7 +2272,7 @@ async function sendRequest(request, timeoutMs) {
|
|
|
2257
2272
|
}
|
|
2258
2273
|
lines.push(
|
|
2259
2274
|
"",
|
|
2260
|
-
" Diagnose:
|
|
2275
|
+
" Diagnose: sis admin doctor",
|
|
2261
2276
|
" Logs: tail -f ~/.sisyphus/daemon.log"
|
|
2262
2277
|
);
|
|
2263
2278
|
throw new Error(lines.join("\n"));
|
|
@@ -2291,6 +2306,9 @@ function getTmuxSessionInfo() {
|
|
|
2291
2306
|
function shellQuote(s) {
|
|
2292
2307
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
2293
2308
|
}
|
|
2309
|
+
function validateRepoName(repo) {
|
|
2310
|
+
return !repo.includes("/") && !repo.includes("\\") && !repo.includes("..");
|
|
2311
|
+
}
|
|
2294
2312
|
function escapeAppleScript(s) {
|
|
2295
2313
|
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2296
2314
|
}
|
|
@@ -2392,7 +2410,7 @@ function registerStart(program2) {
|
|
|
2392
2410
|
console.log(`Tmux session: ${tmuxSessionName}`);
|
|
2393
2411
|
console.log(` tmux attach -t ${tmuxSessionName}`);
|
|
2394
2412
|
}
|
|
2395
|
-
console.log(`Monitor:
|
|
2413
|
+
console.log(`Monitor: sis status ${sessionId}`);
|
|
2396
2414
|
return;
|
|
2397
2415
|
}
|
|
2398
2416
|
let tmuxSession;
|
|
@@ -2436,7 +2454,7 @@ function registerStart(program2) {
|
|
|
2436
2454
|
if (!process.env["TMUX"]) {
|
|
2437
2455
|
attachToTmuxSession(tmuxSession);
|
|
2438
2456
|
}
|
|
2439
|
-
console.log(`Monitor:
|
|
2457
|
+
console.log(`Monitor: sis status ${sessionId}`);
|
|
2440
2458
|
});
|
|
2441
2459
|
}
|
|
2442
2460
|
|
|
@@ -2759,7 +2777,7 @@ function registerList(program2) {
|
|
|
2759
2777
|
if (sessions.length === 0) {
|
|
2760
2778
|
if (filtered && totalCount && totalCount > 0) {
|
|
2761
2779
|
console.log(`No sessions in this project. ${totalCount} session(s) in other projects.`);
|
|
2762
|
-
console.log(`${DIM2}Run ${RESET2}
|
|
2780
|
+
console.log(`${DIM2}Run ${RESET2}sis list --all${DIM2} to show all.${RESET2}`);
|
|
2763
2781
|
} else {
|
|
2764
2782
|
console.log("No sessions");
|
|
2765
2783
|
}
|
|
@@ -2777,7 +2795,7 @@ function registerList(program2) {
|
|
|
2777
2795
|
if (filtered && totalCount && totalCount > sessions.length) {
|
|
2778
2796
|
const otherCount = totalCount - sessions.length;
|
|
2779
2797
|
console.log(`
|
|
2780
|
-
${DIM2}${otherCount} more session(s) in other projects. Run ${RESET2}
|
|
2798
|
+
${DIM2}${otherCount} more session(s) in other projects. Run ${RESET2}sis list --all${DIM2} to show all.${RESET2}`);
|
|
2781
2799
|
}
|
|
2782
2800
|
} else {
|
|
2783
2801
|
console.error(`Error: ${response.error}`);
|
|
@@ -3507,7 +3525,7 @@ Posts a deck of questions to the user's dashboard inbox. They walk through it an
|
|
|
3507
3525
|
|
|
3508
3526
|
The CLI always blocks until the user answers (which can take 10+ minutes).
|
|
3509
3527
|
|
|
3510
|
-
- **Orchestrator:** invoke synchronously so the orchestrator's pane stays alive while the bash blocks. Daemon refuses \`
|
|
3528
|
+
- **Orchestrator:** invoke synchronously so the orchestrator's pane stays alive while the bash blocks. Daemon refuses \`sis orch yield\` while orchestrator owns a pending deck; foreground is the supported pattern.
|
|
3511
3529
|
- **Agents / one-off Claude Code sessions:** invoke through the Bash tool with \`run_in_background: true\` and end your turn \u2014 the bash completion notification wakes you with stdout ready to parse.
|
|
3512
3530
|
|
|
3513
3531
|
For guidance on when to use a deck, how to design options the user can actually choose between, and how to bundle related questions into one deck, read the \`humanloop\` skill before authoring.
|
|
@@ -3817,7 +3835,7 @@ function registerComplete(program2) {
|
|
|
3817
3835
|
console.log("Session completed.");
|
|
3818
3836
|
console.log(`
|
|
3819
3837
|
To keep working in this session:`);
|
|
3820
|
-
console.log(`
|
|
3838
|
+
console.log(` sis session continue # reactivate session and clear roadmap for new work`);
|
|
3821
3839
|
} else {
|
|
3822
3840
|
console.error(`Error: ${response.error}`);
|
|
3823
3841
|
process.exit(1);
|
|
@@ -3839,7 +3857,7 @@ function registerRollback(program2) {
|
|
|
3839
3857
|
if (response.ok) {
|
|
3840
3858
|
const data = response.data;
|
|
3841
3859
|
console.log(`Session ${sessionId} rolled back to cycle ${data.restoredToCycle}.`);
|
|
3842
|
-
console.log(`Session is now paused. Use '
|
|
3860
|
+
console.log(`Session is now paused. Use 'sis session resume ${sessionId}' to respawn the orchestrator.`);
|
|
3843
3861
|
} else {
|
|
3844
3862
|
console.error(`Error: ${response.error}`);
|
|
3845
3863
|
process.exit(1);
|
|
@@ -3875,7 +3893,7 @@ function registerClone(program2) {
|
|
|
3875
3893
|
}
|
|
3876
3894
|
const agentId = process.env.SISYPHUS_AGENT_ID;
|
|
3877
3895
|
if (agentId !== "orchestrator") {
|
|
3878
|
-
console.error("Error: clone can only be called by the orchestrator. Use
|
|
3896
|
+
console.error("Error: clone can only be called by the orchestrator. Use sis message to ask the orchestrator to clone.");
|
|
3879
3897
|
process.exit(1);
|
|
3880
3898
|
}
|
|
3881
3899
|
const request = {
|
|
@@ -4115,12 +4133,12 @@ import { join as join14, resolve as resolve5 } from "path";
|
|
|
4115
4133
|
// src/cli/stdin.ts
|
|
4116
4134
|
function readStdin2() {
|
|
4117
4135
|
if (process.stdin.isTTY) return Promise.resolve(null);
|
|
4118
|
-
return new Promise((
|
|
4136
|
+
return new Promise((resolve12, reject) => {
|
|
4119
4137
|
const chunks = [];
|
|
4120
4138
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
4121
4139
|
process.stdin.on("end", () => {
|
|
4122
4140
|
const text = Buffer.concat(chunks).toString("utf-8").trim();
|
|
4123
|
-
|
|
4141
|
+
resolve12(text || null);
|
|
4124
4142
|
});
|
|
4125
4143
|
process.stdin.on("error", reject);
|
|
4126
4144
|
});
|
|
@@ -4276,11 +4294,11 @@ function registerSpawn(program2) {
|
|
|
4276
4294
|
if (response.ok) {
|
|
4277
4295
|
const agentId = response.data?.agentId;
|
|
4278
4296
|
console.log(`Agent spawned: ${agentId}`);
|
|
4279
|
-
console.log(`Tip: \`
|
|
4280
|
-
console.log("Run `
|
|
4297
|
+
console.log(`Tip: \`sis agent await ${agentId}\` blocks for the report and consumes it inline (won't appear in next cycle).`);
|
|
4298
|
+
console.log("Run `sis orch yield` when done spawning agents.");
|
|
4281
4299
|
} else {
|
|
4282
4300
|
console.error(`Error: ${response.error}`);
|
|
4283
|
-
if (response.error?.includes("Unknown session")) console.error("Hint: run `
|
|
4301
|
+
if (response.error?.includes("Unknown session")) console.error("Hint: run `sis list` to see active sessions.");
|
|
4284
4302
|
process.exit(1);
|
|
4285
4303
|
}
|
|
4286
4304
|
});
|
|
@@ -4897,7 +4915,7 @@ function printResults(result, daemonOk, keybindMsg) {
|
|
|
4897
4915
|
}
|
|
4898
4916
|
}
|
|
4899
4917
|
console.log("");
|
|
4900
|
-
console.log("Run '
|
|
4918
|
+
console.log("Run 'sis admin getting-started' for a usage guide.");
|
|
4901
4919
|
console.log("");
|
|
4902
4920
|
}
|
|
4903
4921
|
function registerSetup(program2) {
|
|
@@ -4943,9 +4961,9 @@ function registerSetupKeybind(program2) {
|
|
|
4943
4961
|
console.log(` ${result.existingBinding}`);
|
|
4944
4962
|
console.log("");
|
|
4945
4963
|
console.log("Use a different key, e.g.:");
|
|
4946
|
-
console.log("
|
|
4947
|
-
console.log("
|
|
4948
|
-
console.log("
|
|
4964
|
+
console.log(" sis admin setup-keybind M-S");
|
|
4965
|
+
console.log(" sis admin setup-keybind M-w");
|
|
4966
|
+
console.log(" sis admin setup-keybind M-j");
|
|
4949
4967
|
break;
|
|
4950
4968
|
case "unsupported-tmux":
|
|
4951
4969
|
console.log(result.message);
|
|
@@ -4959,9 +4977,40 @@ function registerSetupKeybind(program2) {
|
|
|
4959
4977
|
});
|
|
4960
4978
|
}
|
|
4961
4979
|
|
|
4980
|
+
// src/cli/commands/home-init.ts
|
|
4981
|
+
import { execSync as execSync11 } from "child_process";
|
|
4982
|
+
function registerHomeInit(parent) {
|
|
4983
|
+
parent.command("home-init <name> <cwd>").description("Bootstrap a tmux home session with the sisyphus dashboard.").action((name, cwd) => {
|
|
4984
|
+
ensureSession(name, cwd);
|
|
4985
|
+
setSessionCwd(name, cwd);
|
|
4986
|
+
openDashboardWindow(name, cwd);
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
function sessionExists(name) {
|
|
4990
|
+
try {
|
|
4991
|
+
execSync11(`tmux has-session -t ${shellQuote(name)}`, { stdio: "pipe" });
|
|
4992
|
+
return true;
|
|
4993
|
+
} catch {
|
|
4994
|
+
return false;
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
function ensureSession(name, cwd) {
|
|
4998
|
+
if (sessionExists(name)) return;
|
|
4999
|
+
execSync11(
|
|
5000
|
+
`tmux new-session -d -s ${shellQuote(name)} -c ${shellQuote(cwd)}`,
|
|
5001
|
+
{ stdio: "pipe" }
|
|
5002
|
+
);
|
|
5003
|
+
}
|
|
5004
|
+
function setSessionCwd(name, cwd) {
|
|
5005
|
+
execSync11(
|
|
5006
|
+
`tmux set-option -t ${shellQuote(name)} @sisyphus_cwd ${shellQuote(cwd.replace(/\/+$/, ""))}`,
|
|
5007
|
+
{ stdio: "pipe" }
|
|
5008
|
+
);
|
|
5009
|
+
}
|
|
5010
|
+
|
|
4962
5011
|
// src/cli/commands/doctor.ts
|
|
4963
5012
|
init_paths();
|
|
4964
|
-
import { execSync as
|
|
5013
|
+
import { execSync as execSync12 } from "child_process";
|
|
4965
5014
|
import { existsSync as existsSync15, statSync as statSync3 } from "fs";
|
|
4966
5015
|
import { homedir as homedir9 } from "os";
|
|
4967
5016
|
import { join as join16 } from "path";
|
|
@@ -4974,7 +5023,7 @@ function checkNodeVersion() {
|
|
|
4974
5023
|
}
|
|
4975
5024
|
function checkClaudeCli() {
|
|
4976
5025
|
try {
|
|
4977
|
-
|
|
5026
|
+
execSync12("which claude", { stdio: "pipe" });
|
|
4978
5027
|
return { name: "Claude CLI", status: "ok", detail: "Found on PATH" };
|
|
4979
5028
|
} catch {
|
|
4980
5029
|
return {
|
|
@@ -4987,7 +5036,7 @@ function checkClaudeCli() {
|
|
|
4987
5036
|
}
|
|
4988
5037
|
function checkGit() {
|
|
4989
5038
|
try {
|
|
4990
|
-
const version =
|
|
5039
|
+
const version = execSync12("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
4991
5040
|
return { name: "git", status: "ok", detail: version };
|
|
4992
5041
|
} catch {
|
|
4993
5042
|
return { name: "git", status: "fail", detail: "Not found on PATH", fix: "Install git: https://git-scm.com/downloads" };
|
|
@@ -4995,7 +5044,7 @@ function checkGit() {
|
|
|
4995
5044
|
}
|
|
4996
5045
|
function checkTmuxVersion() {
|
|
4997
5046
|
try {
|
|
4998
|
-
const version =
|
|
5047
|
+
const version = execSync12("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
4999
5048
|
const match = version.match(/(\d+\.\d+)/);
|
|
5000
5049
|
if (!match) return { name: "tmux version", status: "warn", detail: `Could not parse version: ${version}` };
|
|
5001
5050
|
const ver = parseFloat(match[1]);
|
|
@@ -5017,7 +5066,7 @@ function checkDaemonInstalled() {
|
|
|
5017
5066
|
name: "Daemon plist",
|
|
5018
5067
|
status: "fail",
|
|
5019
5068
|
detail: "Not installed",
|
|
5020
|
-
fix: 'Run any
|
|
5069
|
+
fix: 'Run any sis command to auto-install, or: sis start "test"'
|
|
5021
5070
|
};
|
|
5022
5071
|
}
|
|
5023
5072
|
const pid = daemonPidPath();
|
|
@@ -5044,7 +5093,7 @@ function checkDaemonRunning() {
|
|
|
5044
5093
|
}
|
|
5045
5094
|
try {
|
|
5046
5095
|
const sock = socketPath();
|
|
5047
|
-
|
|
5096
|
+
execSync12(`test -S "${sock}"`, { stdio: "pipe" });
|
|
5048
5097
|
return { name: "Daemon process", status: "ok", detail: `Socket at ${sock}` };
|
|
5049
5098
|
} catch {
|
|
5050
5099
|
return {
|
|
@@ -5057,13 +5106,13 @@ function checkDaemonRunning() {
|
|
|
5057
5106
|
}
|
|
5058
5107
|
function checkTmux() {
|
|
5059
5108
|
try {
|
|
5060
|
-
|
|
5109
|
+
execSync12("which tmux", { stdio: "pipe" });
|
|
5061
5110
|
} catch {
|
|
5062
5111
|
const installHint = process.platform === "darwin" ? "brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
|
|
5063
5112
|
return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: installHint };
|
|
5064
5113
|
}
|
|
5065
5114
|
try {
|
|
5066
|
-
|
|
5115
|
+
execSync12("tmux list-sessions", { stdio: "pipe" });
|
|
5067
5116
|
return { name: "tmux", status: "ok", detail: "Running" };
|
|
5068
5117
|
} catch {
|
|
5069
5118
|
return { name: "tmux", status: "warn", detail: "Installed but no server running" };
|
|
@@ -5076,7 +5125,7 @@ function checkCycleScript() {
|
|
|
5076
5125
|
name: "Cycle script",
|
|
5077
5126
|
status: "fail",
|
|
5078
5127
|
detail: `Not found at ${path}`,
|
|
5079
|
-
fix: "
|
|
5128
|
+
fix: "sis admin setup-keybind"
|
|
5080
5129
|
};
|
|
5081
5130
|
}
|
|
5082
5131
|
try {
|
|
@@ -5107,7 +5156,7 @@ function checkTmuxKeybind() {
|
|
|
5107
5156
|
name: `Tmux keybind (${DEFAULT_CYCLE_KEY})`,
|
|
5108
5157
|
status: "fail",
|
|
5109
5158
|
detail: "Not bound",
|
|
5110
|
-
fix: "
|
|
5159
|
+
fix: "sis admin setup-keybind"
|
|
5111
5160
|
};
|
|
5112
5161
|
}
|
|
5113
5162
|
if (isSisyphusBinding(existing)) {
|
|
@@ -5117,7 +5166,7 @@ function checkTmuxKeybind() {
|
|
|
5117
5166
|
name: `Tmux keybind (${DEFAULT_CYCLE_KEY})`,
|
|
5118
5167
|
status: "warn",
|
|
5119
5168
|
detail: `Bound to something else: ${existing}`,
|
|
5120
|
-
fix: "
|
|
5169
|
+
fix: "sis admin setup-keybind M-S (or another free key)"
|
|
5121
5170
|
};
|
|
5122
5171
|
}
|
|
5123
5172
|
function checkGlobalDir() {
|
|
@@ -5168,13 +5217,13 @@ function checkSisyphusPlugin() {
|
|
|
5168
5217
|
name: "sisyphus@sisyphus plugin",
|
|
5169
5218
|
status: "warn",
|
|
5170
5219
|
detail: "Not installed (slash commands /sisyphus:begin, /sisyphus:autopsy, /sisyphus:configure-upload unavailable)",
|
|
5171
|
-
fix: "
|
|
5220
|
+
fix: "sis admin setup"
|
|
5172
5221
|
};
|
|
5173
5222
|
}
|
|
5174
5223
|
function checkTermrender() {
|
|
5175
5224
|
if (isTermrenderAvailable()) {
|
|
5176
5225
|
try {
|
|
5177
|
-
const version =
|
|
5226
|
+
const version = execSync12("termrender --version", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
5178
5227
|
return { name: "termrender", status: "ok", detail: version };
|
|
5179
5228
|
} catch {
|
|
5180
5229
|
return { name: "termrender", status: "ok", detail: "installed" };
|
|
@@ -5193,7 +5242,7 @@ function checkNvim() {
|
|
|
5193
5242
|
return { name: "nvim", status: "warn", detail: "Not installed", fix };
|
|
5194
5243
|
}
|
|
5195
5244
|
try {
|
|
5196
|
-
const version =
|
|
5245
|
+
const version = execSync12("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
|
|
5197
5246
|
return { name: "nvim", status: "ok", detail: version ?? "installed" };
|
|
5198
5247
|
} catch {
|
|
5199
5248
|
return { name: "nvim", status: "ok", detail: "installed" };
|
|
@@ -5298,10 +5347,10 @@ function registerInit(program2) {
|
|
|
5298
5347
|
import { createInterface as createInterface2 } from "readline";
|
|
5299
5348
|
async function confirm(question) {
|
|
5300
5349
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
5301
|
-
return new Promise((
|
|
5350
|
+
return new Promise((resolve12) => {
|
|
5302
5351
|
rl.question(question, (answer) => {
|
|
5303
5352
|
rl.close();
|
|
5304
|
-
|
|
5353
|
+
resolve12(answer.trim().toLowerCase() === "y");
|
|
5305
5354
|
});
|
|
5306
5355
|
});
|
|
5307
5356
|
}
|
|
@@ -5326,27 +5375,27 @@ import { createInterface as createInterface3 } from "readline";
|
|
|
5326
5375
|
import { dirname as dirname6 } from "path";
|
|
5327
5376
|
async function readUrlFromInput(interactive) {
|
|
5328
5377
|
if (interactive) {
|
|
5329
|
-
return new Promise((
|
|
5378
|
+
return new Promise((resolve12) => {
|
|
5330
5379
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
5331
5380
|
rl.question("Paste the upload URL (with embedded ?token=): ", (answer) => {
|
|
5332
5381
|
rl.close();
|
|
5333
|
-
|
|
5382
|
+
resolve12(answer.trim());
|
|
5334
5383
|
});
|
|
5335
5384
|
});
|
|
5336
5385
|
}
|
|
5337
|
-
return new Promise((
|
|
5386
|
+
return new Promise((resolve12) => {
|
|
5338
5387
|
const chunks = [];
|
|
5339
5388
|
process.stdin.setEncoding("utf-8");
|
|
5340
5389
|
process.stdin.on("data", (chunk) => {
|
|
5341
5390
|
chunks.push(chunk);
|
|
5342
5391
|
});
|
|
5343
5392
|
process.stdin.on("end", () => {
|
|
5344
|
-
|
|
5393
|
+
resolve12(chunks.join("").trim());
|
|
5345
5394
|
});
|
|
5346
5395
|
});
|
|
5347
5396
|
}
|
|
5348
5397
|
function registerConfigureUpload(program2) {
|
|
5349
|
-
program2.command("configure-upload").description("Configure the upload proxy from a token-bearing URL (writes ~/.sisyphus/config.json)").argument("[url]", "Worker URL with embedded ?token= query (https://worker/upload?token=sisyphus_pat_...); omit to read from stdin").option("--stdin", "Read URL from stdin (pipe-friendly: pbpaste |
|
|
5398
|
+
program2.command("configure-upload").description("Configure the upload proxy from a token-bearing URL (writes ~/.sisyphus/config.json)").argument("[url]", "Worker URL with embedded ?token= query (https://worker/upload?token=sisyphus_pat_...); omit to read from stdin").option("--stdin", "Read URL from stdin (pipe-friendly: pbpaste | sis admin configure-upload --stdin)").action(async (urlArg, opts) => {
|
|
5350
5399
|
let rawUrl;
|
|
5351
5400
|
const fromStdin = opts.stdin || urlArg === "-" || !urlArg && process.stdin.isTTY === false;
|
|
5352
5401
|
const fromInteractive = !urlArg && !opts.stdin && process.stdin.isTTY === true;
|
|
@@ -5355,7 +5404,7 @@ function registerConfigureUpload(program2) {
|
|
|
5355
5404
|
} else {
|
|
5356
5405
|
rawUrl = urlArg;
|
|
5357
5406
|
console.warn(
|
|
5358
|
-
"warning: passing the token on argv exposes it via `ps` and shell history; pipe it on stdin instead: `pbpaste |
|
|
5407
|
+
"warning: passing the token on argv exposes it via `ps` and shell history; pipe it on stdin instead: `pbpaste | sis admin configure-upload --stdin`"
|
|
5359
5408
|
);
|
|
5360
5409
|
}
|
|
5361
5410
|
let parsed;
|
|
@@ -5396,7 +5445,7 @@ function registerConfigureUpload(program2) {
|
|
|
5396
5445
|
}
|
|
5397
5446
|
|
|
5398
5447
|
// src/cli/commands/getting-started.ts
|
|
5399
|
-
import { execSync as
|
|
5448
|
+
import { execSync as execSync13 } from "child_process";
|
|
5400
5449
|
import { dirname as dirname7, join as join18 } from "path";
|
|
5401
5450
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5402
5451
|
function templatePath(name) {
|
|
@@ -5408,7 +5457,7 @@ function isClaudeCode() {
|
|
|
5408
5457
|
function printNonClaudeMessage() {
|
|
5409
5458
|
console.log(`
|
|
5410
5459
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
5411
|
-
\u2551
|
|
5460
|
+
\u2551 sis admin getting-started \u2014 Interactive Tutorial \u2551
|
|
5412
5461
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
5413
5462
|
|
|
5414
5463
|
This command provides an interactive tutorial best experienced
|
|
@@ -5416,11 +5465,11 @@ function printNonClaudeMessage() {
|
|
|
5416
5465
|
|
|
5417
5466
|
To start:
|
|
5418
5467
|
1. Open Claude Code: claude
|
|
5419
|
-
2. Run:
|
|
5468
|
+
2. Run: sis admin getting-started
|
|
5420
5469
|
|
|
5421
5470
|
If you just want the quick reference, run:
|
|
5422
|
-
|
|
5423
|
-
|
|
5471
|
+
sis --help
|
|
5472
|
+
sis admin doctor
|
|
5424
5473
|
`);
|
|
5425
5474
|
}
|
|
5426
5475
|
function printStep0() {
|
|
@@ -5450,14 +5499,14 @@ This tutorial has 6 steps. Share this overview so the user knows what's coming a
|
|
|
5450
5499
|
|
|
5451
5500
|
| Step | Topic | Command |
|
|
5452
5501
|
|------|-------|---------|
|
|
5453
|
-
| 0 | Entry & tmux gate (you are here) | \`
|
|
5502
|
+
| 0 | Entry & tmux gate (you are here) | \`sis admin getting-started\` |
|
|
5454
5503
|
| 1 | Tmux basics \u2014 sessions, panes, navigation | \`--tutorial 1\` |
|
|
5455
5504
|
| 2 | Nvim basics \u2014 open, save, quit (optional) | \`--tutorial 2\` |
|
|
5456
5505
|
| 3 | Sisyphus concepts \u2014 session model & keybinds | \`--tutorial 3\` |
|
|
5457
5506
|
| 4 | Live demo \u2014 launch and observe a real session | \`--tutorial 4\` |
|
|
5458
5507
|
| 5 | What's next \u2014 real usage guidance & suggestions | \`--tutorial 5\` |
|
|
5459
5508
|
|
|
5460
|
-
Tell the user they can skip to any step with \`
|
|
5509
|
+
Tell the user they can skip to any step with \`sis admin getting-started --tutorial <N>\`.
|
|
5461
5510
|
|
|
5462
5511
|
## Instructions for Claude
|
|
5463
5512
|
|
|
@@ -5467,8 +5516,8 @@ You are guiding a user through the Sisyphus interactive tutorial.
|
|
|
5467
5516
|
|
|
5468
5517
|
Ask the user if they'd like the interactive walkthrough. If they decline, give this quick summary and stop:
|
|
5469
5518
|
|
|
5470
|
-
> Sisyphus is a multi-agent orchestrator for Claude Code. Start a session with \`
|
|
5471
|
-
> monitor with \`
|
|
5519
|
+
> Sisyphus is a multi-agent orchestrator for Claude Code. Start a session with \`sis start "task"\`,
|
|
5520
|
+
> monitor with \`sis dashboard\`, and check health with \`sis admin doctor\`.
|
|
5472
5521
|
|
|
5473
5522
|
### If they want the tutorial:
|
|
5474
5523
|
|
|
@@ -5490,17 +5539,17 @@ Ask the user if they'd like the interactive walkthrough. If they decline, give t
|
|
|
5490
5539
|
- Tell them to install tmux:
|
|
5491
5540
|
- macOS: \`brew install tmux\`
|
|
5492
5541
|
- Linux: \`apt install tmux\` or their package manager
|
|
5493
|
-
- After install, re-run: \`
|
|
5542
|
+
- After install, re-run: \`sis admin getting-started --tutorial 0\` to verify
|
|
5494
5543
|
|
|
5495
5544
|
**Case 2: tmux installed but NOT in a tmux session (inTmux: false)**
|
|
5496
5545
|
- Tell the user they need to be inside a tmux session for the tutorial
|
|
5497
5546
|
- Have them run: \`tmux new-session\`
|
|
5498
5547
|
- Then resume the conversation with Claude in the new tmux session: \`claude\`
|
|
5499
|
-
- Then re-run: \`
|
|
5548
|
+
- Then re-run: \`sis admin getting-started --tutorial 0\` to verify
|
|
5500
5549
|
|
|
5501
5550
|
**Case 3: In tmux (inTmux: true)**
|
|
5502
5551
|
- Tell the user they're all set \u2014 tmux is running
|
|
5503
|
-
- Proceed by running: \`
|
|
5552
|
+
- Proceed by running: \`sis admin getting-started --tutorial 1\`
|
|
5504
5553
|
</claude-instructions>
|
|
5505
5554
|
`);
|
|
5506
5555
|
}
|
|
@@ -5578,7 +5627,7 @@ Ask the user to confirm: "Can you navigate between panes with Ctrl+h and Ctrl+l?
|
|
|
5578
5627
|
|
|
5579
5628
|
Once confirmed, proceed:
|
|
5580
5629
|
\`\`\`
|
|
5581
|
-
|
|
5630
|
+
sis admin getting-started --tutorial 2
|
|
5582
5631
|
\`\`\`
|
|
5583
5632
|
</claude-instructions>
|
|
5584
5633
|
`);
|
|
@@ -5620,7 +5669,7 @@ Ask if they were able to edit and save the file (or if they skipped).
|
|
|
5620
5669
|
|
|
5621
5670
|
Proceed:
|
|
5622
5671
|
\`\`\`
|
|
5623
|
-
|
|
5672
|
+
sis admin getting-started --tutorial 3
|
|
5624
5673
|
\`\`\`
|
|
5625
5674
|
</claude-instructions>
|
|
5626
5675
|
`);
|
|
@@ -5681,7 +5730,7 @@ function printStep3() {
|
|
|
5681
5730
|
> typing special characters (accents, symbols). The right Option key
|
|
5682
5731
|
> becomes your "Meta" key for tmux/sisyphus keybinds.
|
|
5683
5732
|
|
|
5684
|
-
After they change it, have them verify by re-running \`
|
|
5733
|
+
After they change it, have them verify by re-running \`sis admin doctor\` \u2014 look for "Right Option Key: Esc+".
|
|
5685
5734
|
|
|
5686
5735
|
- **rightOptionKeyStatus: not-iterm** \u2014 They're not using iTerm2. Explain:
|
|
5687
5736
|
> Sisyphus keybinds use Option as Meta. In iTerm2 this is configured via
|
|
@@ -5727,12 +5776,12 @@ Two keybinds to remember (both use the RIGHT Option key):
|
|
|
5727
5776
|
|
|
5728
5777
|
### 4. Verify keybinds are installed
|
|
5729
5778
|
|
|
5730
|
-
Run \`
|
|
5779
|
+
Run \`sis admin doctor\` and check the output. Look for:
|
|
5731
5780
|
- "Cycle script" \u2014 should be \u2713
|
|
5732
5781
|
- "Tmux keybind" \u2014 should be \u2713
|
|
5733
5782
|
- "Right Option Key" \u2014 should be "Esc+"
|
|
5734
5783
|
|
|
5735
|
-
If cycle script or keybind is missing, run: \`
|
|
5784
|
+
If cycle script or keybind is missing, run: \`sis admin setup-keybind\`
|
|
5736
5785
|
|
|
5737
5786
|
### 5. Test the keybind
|
|
5738
5787
|
|
|
@@ -5744,12 +5793,12 @@ If they see \`\xDF\` or similar, circle back to the Right Option Key setup above
|
|
|
5744
5793
|
|
|
5745
5794
|
Confirm:
|
|
5746
5795
|
- They understand the two-session model (their session vs sisyphus session)
|
|
5747
|
-
- \`
|
|
5796
|
+
- \`sis admin doctor\` shows keybinds installed AND Right Option Key: Esc+
|
|
5748
5797
|
- Right Option + s doesn't produce a special character
|
|
5749
5798
|
|
|
5750
5799
|
Proceed:
|
|
5751
5800
|
\`\`\`
|
|
5752
|
-
|
|
5801
|
+
sis admin getting-started --tutorial 4
|
|
5753
5802
|
\`\`\`
|
|
5754
5803
|
</claude-instructions>
|
|
5755
5804
|
`);
|
|
@@ -5765,12 +5814,12 @@ This is the grand finale \u2014 a live demo session.
|
|
|
5765
5814
|
|
|
5766
5815
|
### 1. Health check
|
|
5767
5816
|
|
|
5768
|
-
Run \`
|
|
5817
|
+
Run \`sis admin doctor\` first. If any checks are failing, help the user fix them before proceeding.
|
|
5769
5818
|
All core checks (tmux, daemon, keybinds) should be \u2713.
|
|
5770
5819
|
|
|
5771
5820
|
### 2. BEFORE launching: Teach navigation
|
|
5772
5821
|
|
|
5773
|
-
**This is critical.** When \`
|
|
5822
|
+
**This is critical.** When \`sis start\` runs, it auto-opens the dashboard in a new tmux window. The user will suddenly be looking at the dashboard and may feel "stuck". Teach them how to navigate BEFORE launching:
|
|
5774
5823
|
|
|
5775
5824
|
Explain clearly:
|
|
5776
5825
|
|
|
@@ -5815,7 +5864,7 @@ Tell the user:
|
|
|
5815
5864
|
|
|
5816
5865
|
Then launch from the demo directory:
|
|
5817
5866
|
\`\`\`
|
|
5818
|
-
cd /tmp/sisyphus-tutorial-demo &&
|
|
5867
|
+
cd /tmp/sisyphus-tutorial-demo && sis start "Add three improvements to this todo app: (1) add a priority field (high/medium/low) to todos, (2) add a GET /todos/stats endpoint that returns counts of total/done/pending todos, (3) add tests for the new features. Explain your thinking at each step." -c "TUTORIAL DEMO: A user is watching this session to learn how sisyphus works. Be EXTRA VERBOSE \u2014 explain your reasoning, narrate what you're doing, and make your planning visible. When spawning agents, give each agent context that this is a tutorial demo and they should explain their work clearly. Keep scope small: 2-3 agents, 1-2 cycles."
|
|
5819
5868
|
\`\`\`
|
|
5820
5869
|
|
|
5821
5870
|
After launching, tell them:
|
|
@@ -5828,7 +5877,7 @@ Wait for them to confirm they're back, then start live commentary.
|
|
|
5828
5877
|
|
|
5829
5878
|
**This is the most important part of the demo.** Don't just launch and wait \u2014 actively narrate.
|
|
5830
5879
|
|
|
5831
|
-
Once the user is back, start a polling loop. Every ~45 seconds, run \`
|
|
5880
|
+
Once the user is back, start a polling loop. Every ~45 seconds, run \`sis status --verbose <session-id>\` and provide SHORT, contextual commentary about what's happening. The \`--verbose\` flag shows agent instructions, full roadmap, cycle logs, and live pane output from the orchestrator and running agents \u2014 use this rich data to narrate what's actually happening, not just phase names.
|
|
5832
5881
|
|
|
5833
5882
|
**How to narrate each phase:**
|
|
5834
5883
|
|
|
@@ -5867,7 +5916,7 @@ Once the session shows "completed":
|
|
|
5867
5916
|
|
|
5868
5917
|
Tell the user the demo is done. Then run:
|
|
5869
5918
|
\`\`\`
|
|
5870
|
-
|
|
5919
|
+
sis admin getting-started --tutorial 5
|
|
5871
5920
|
\`\`\`
|
|
5872
5921
|
</claude-instructions>
|
|
5873
5922
|
`);
|
|
@@ -5876,11 +5925,11 @@ function printStep5() {
|
|
|
5876
5925
|
let recentCommits = "";
|
|
5877
5926
|
let topLevelFiles = "";
|
|
5878
5927
|
try {
|
|
5879
|
-
recentCommits =
|
|
5928
|
+
recentCommits = execSync13("git log --oneline -15 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
5880
5929
|
} catch {
|
|
5881
5930
|
}
|
|
5882
5931
|
try {
|
|
5883
|
-
topLevelFiles =
|
|
5932
|
+
topLevelFiles = execSync13("ls -1 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
5884
5933
|
} catch {
|
|
5885
5934
|
}
|
|
5886
5935
|
console.log(`
|
|
@@ -5943,7 +5992,7 @@ This is the most important part. Explain clearly:
|
|
|
5943
5992
|
The easiest way is the \`/sisyphus:begin\` slash command inside Claude Code. Just tell Claude
|
|
5944
5993
|
what you want to build and it'll hand it off to sisyphus with the right context.
|
|
5945
5994
|
|
|
5946
|
-
Or directly: \`
|
|
5995
|
+
Or directly: \`sis start "your task" -c "any background context"\`
|
|
5947
5996
|
|
|
5948
5997
|
### 4. Suggest real tasks for THEIR codebase
|
|
5949
5998
|
|
|
@@ -5965,7 +6014,7 @@ Tell them:
|
|
|
5965
6014
|
> to understand the philosophy, or you want a deeper rundown on the dashboard,
|
|
5966
6015
|
> monitoring, configuration, or how to steer sessions \u2014 just ask and I'll explain.
|
|
5967
6016
|
|
|
5968
|
-
If the user says yes or asks to learn more, run \`
|
|
6017
|
+
If the user says yes or asks to learn more, run \`sis admin getting-started --explain\`
|
|
5969
6018
|
and use its output to explain the system to them conversationally. Don't dump the whole
|
|
5970
6019
|
thing \u2014 answer what they're curious about, using the reference as your source material.
|
|
5971
6020
|
</claude-instructions>
|
|
@@ -5981,17 +6030,17 @@ function buildCommandTable(program2) {
|
|
|
5981
6030
|
const fmtArgs = (c2) => c2.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
|
|
5982
6031
|
if (subs.length === 0) {
|
|
5983
6032
|
const args2 = fmtArgs(cmd);
|
|
5984
|
-
const usage = args2 ? `
|
|
6033
|
+
const usage = args2 ? `sis ${cmd.name()} ${args2}` : `sis ${cmd.name()}`;
|
|
5985
6034
|
lines.push(`| \`${usage}\` | ${cmd.description()} |`);
|
|
5986
6035
|
} else {
|
|
5987
6036
|
if (hasOwnAction) {
|
|
5988
6037
|
const args2 = fmtArgs(cmd);
|
|
5989
|
-
const usage = args2 ? `
|
|
6038
|
+
const usage = args2 ? `sis ${cmd.name()} ${args2}` : `sis ${cmd.name()}`;
|
|
5990
6039
|
lines.push(`| \`${usage}\` | ${cmd.description()} |`);
|
|
5991
6040
|
}
|
|
5992
6041
|
for (const sub of subs) {
|
|
5993
6042
|
const args2 = fmtArgs(sub);
|
|
5994
|
-
const usage = args2 ? `
|
|
6043
|
+
const usage = args2 ? `sis ${cmd.name()} ${sub.name()} ${args2}` : `sis ${cmd.name()} ${sub.name()}`;
|
|
5995
6044
|
lines.push(`| \`${usage}\` | ${sub.description()} |`);
|
|
5996
6045
|
}
|
|
5997
6046
|
}
|
|
@@ -6141,7 +6190,7 @@ code that looks right and code that works.
|
|
|
6141
6190
|
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
6142
6191
|
\u2502 SESSION LIFECYCLE \u2502
|
|
6143
6192
|
\u2502 \u2502
|
|
6144
|
-
\u2502
|
|
6193
|
+
\u2502 sis start "task" \u2502
|
|
6145
6194
|
\u2502 \u2502 \u2502
|
|
6146
6195
|
\u2502 \u25BC \u2502
|
|
6147
6196
|
\u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 spawn agents \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
@@ -6149,7 +6198,7 @@ code that looks right and code that works.
|
|
|
6149
6198
|
\u2502 \u2502 plans \u2502 then yields \u2502 in parallel \u2502 \u2502
|
|
6150
6199
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
6151
6200
|
\u2502 \u2502 \u2502 each calls \u2502
|
|
6152
|
-
\u2502 \u2502 orchestrator \u2502
|
|
6201
|
+
\u2502 \u2502 orchestrator \u2502 sis agent submit \u2502
|
|
6153
6202
|
\u2502 \u2502 is KILLED \u2502 when done \u2502
|
|
6154
6203
|
\u2502 \u2502 \u25BC \u2502
|
|
6155
6204
|
\u2502 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
@@ -6184,7 +6233,7 @@ This means it never runs out of context, no matter how many cycles a session tak
|
|
|
6184
6233
|
|
|
6185
6234
|
## The Dashboard
|
|
6186
6235
|
|
|
6187
|
-
The dashboard is a real-time TUI that shows session state. Launch with \`
|
|
6236
|
+
The dashboard is a real-time TUI that shows session state. Launch with \`sis dashboard\`
|
|
6188
6237
|
or it auto-opens when a session starts.
|
|
6189
6238
|
|
|
6190
6239
|
**Dashboard sections:**
|
|
@@ -6223,14 +6272,14 @@ Sisyphus sessions should be actively monitored. Here's what to watch for:
|
|
|
6223
6272
|
|
|
6224
6273
|
**When to intervene:**
|
|
6225
6274
|
- Use \`m\` in the dashboard to message the orchestrator with corrections
|
|
6226
|
-
- Use \`
|
|
6227
|
-
- Use \`
|
|
6275
|
+
- Use \`sis session kill <id>\` to stop a runaway session
|
|
6276
|
+
- Use \`sis session resume <id> "new instructions"\` to restart with different direction
|
|
6228
6277
|
|
|
6229
6278
|
**Useful monitoring commands:**
|
|
6230
6279
|
\`\`\`
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6280
|
+
sis status <id> # Quick status check
|
|
6281
|
+
sis status --verbose <id> # Full detail: roadmap, pane output, agent instructions
|
|
6282
|
+
sis dashboard # Interactive TUI
|
|
6234
6283
|
tail -f ~/.sisyphus/daemon.log # Daemon activity log
|
|
6235
6284
|
\`\`\`
|
|
6236
6285
|
|
|
@@ -6296,11 +6345,11 @@ Then describe your task. Claude will hand it off with the right context.
|
|
|
6296
6345
|
|
|
6297
6346
|
**Direct CLI:**
|
|
6298
6347
|
\`\`\`
|
|
6299
|
-
|
|
6300
|
-
|
|
6348
|
+
sis start "task description" -c "background context"
|
|
6349
|
+
sis start "Implement @requirements.md" -n my-feature
|
|
6301
6350
|
\`\`\`
|
|
6302
6351
|
|
|
6303
|
-
**Reference files with @**: \`
|
|
6352
|
+
**Reference files with @**: \`sis start "Build @docs/spec.md"\` \u2014 the orchestrator
|
|
6304
6353
|
will read the referenced file as part of its planning.
|
|
6305
6354
|
|
|
6306
6355
|
**The -c flag** adds background context the orchestrator sees but doesn't act on directly.
|
|
@@ -6322,10 +6371,10 @@ sisyphusd restart
|
|
|
6322
6371
|
**Keybinds not working (special characters appear):**
|
|
6323
6372
|
iTerm2 \u2192 Settings \u2192 Profiles \u2192 Keys \u2192 Right Option Key \u2192 Esc+
|
|
6324
6373
|
|
|
6325
|
-
**Agents stuck:** Check \`
|
|
6374
|
+
**Agents stuck:** Check \`sis status --verbose <id>\` to see pane output. If an
|
|
6326
6375
|
agent is waiting for input, kill the session and restart with clearer instructions.
|
|
6327
6376
|
|
|
6328
|
-
**Dashboard not opening:** Run \`
|
|
6377
|
+
**Dashboard not opening:** Run \`sis dashboard\` manually. Must be inside tmux.
|
|
6329
6378
|
|
|
6330
6379
|
**Session seems hung:** Check \`tail -20 ~/.sisyphus/daemon.log\` for errors.
|
|
6331
6380
|
The daemon polls panes every 2s \u2014 if a pane dies unexpectedly, it'll be detected.
|
|
@@ -6645,7 +6694,7 @@ function showSession(idOrName, opts) {
|
|
|
6645
6694
|
console.log(`${DIM3}Compute:${RESET3} ${formatDuration(computeMs)} ${DIM3}Interactive:${RESET3} ${formatDuration(interactiveMs)} ${DIM3}(TUI wait time, not compute)${RESET3}`);
|
|
6646
6695
|
}
|
|
6647
6696
|
if (s.userBlockedMs > 0) {
|
|
6648
|
-
console.log(`${DIM3}Waiting on user:${RESET3} ${formatDuration(s.userBlockedMs)} ${DIM3}(blocked on
|
|
6697
|
+
console.log(`${DIM3}Waiting on user:${RESET3} ${formatDuration(s.userBlockedMs)} ${DIM3}(blocked on sis ask, not compute)${RESET3}`);
|
|
6649
6698
|
}
|
|
6650
6699
|
console.log("");
|
|
6651
6700
|
console.log(`${BOLD3}Task${RESET3}`);
|
|
@@ -7047,7 +7096,7 @@ function registerExport(program2) {
|
|
|
7047
7096
|
}
|
|
7048
7097
|
if (!sessionId) {
|
|
7049
7098
|
console.error("Error: No session ID provided and no active session found.");
|
|
7050
|
-
console.error("Usage:
|
|
7099
|
+
console.error("Usage: sis admin export [session-id]");
|
|
7051
7100
|
process.exit(1);
|
|
7052
7101
|
}
|
|
7053
7102
|
try {
|
|
@@ -7171,13 +7220,13 @@ function registerUpload(program2) {
|
|
|
7171
7220
|
}
|
|
7172
7221
|
if (!sessionId) {
|
|
7173
7222
|
console.error("Error: No session ID provided and no active session found.");
|
|
7174
|
-
console.error("Usage:
|
|
7223
|
+
console.error("Usage: sis admin upload [session-id]");
|
|
7175
7224
|
process.exit(1);
|
|
7176
7225
|
}
|
|
7177
7226
|
const config = loadConfig(cwd);
|
|
7178
7227
|
if (!isUploadConfigured(config.upload)) {
|
|
7179
7228
|
console.error(
|
|
7180
|
-
"Error: upload not configured. Run '
|
|
7229
|
+
"Error: upload not configured. Run 'sis admin configure-upload <url-with-token>' or set { upload: { url, token } } in .sisyphus/config.json."
|
|
7181
7230
|
);
|
|
7182
7231
|
process.exit(1);
|
|
7183
7232
|
}
|
|
@@ -7236,12 +7285,12 @@ function registerUpload(program2) {
|
|
|
7236
7285
|
}
|
|
7237
7286
|
|
|
7238
7287
|
// src/cli/commands/scratch.ts
|
|
7239
|
-
import { execSync as
|
|
7288
|
+
import { execSync as execSync14 } from "child_process";
|
|
7240
7289
|
function findHomeSession(cwd) {
|
|
7241
7290
|
const normalizedCwd = cwd.replace(/\/+$/, "");
|
|
7242
7291
|
let output;
|
|
7243
7292
|
try {
|
|
7244
|
-
output =
|
|
7293
|
+
output = execSync14('tmux list-sessions -F "#{session_id}|#{session_name}"', {
|
|
7245
7294
|
encoding: "utf-8",
|
|
7246
7295
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7247
7296
|
}).trim();
|
|
@@ -7255,7 +7304,7 @@ function findHomeSession(cwd) {
|
|
|
7255
7304
|
const name = line.slice(pipeIdx + 1);
|
|
7256
7305
|
if (name.startsWith("ssyph_")) continue;
|
|
7257
7306
|
try {
|
|
7258
|
-
const val =
|
|
7307
|
+
const val = execSync14(
|
|
7259
7308
|
`tmux show-options -t ${shellQuote(sessId)} -v @sisyphus_cwd`,
|
|
7260
7309
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
7261
7310
|
).trim();
|
|
@@ -7271,7 +7320,7 @@ function registerScratch(program2) {
|
|
|
7271
7320
|
const cwd = opts.cwd ?? process.env["SISYPHUS_CWD"] ?? process.cwd();
|
|
7272
7321
|
const homeSession = findHomeSession(cwd);
|
|
7273
7322
|
if (!homeSession) {
|
|
7274
|
-
const current =
|
|
7323
|
+
const current = execSync14('tmux display-message -p "#{session_name}"', {
|
|
7275
7324
|
encoding: "utf-8"
|
|
7276
7325
|
}).trim();
|
|
7277
7326
|
openScratchWindow(current, cwd, promptParts.join(" "));
|
|
@@ -7281,7 +7330,7 @@ function registerScratch(program2) {
|
|
|
7281
7330
|
});
|
|
7282
7331
|
}
|
|
7283
7332
|
function openScratchWindow(tmuxSession, cwd, prompt) {
|
|
7284
|
-
const windowId =
|
|
7333
|
+
const windowId = execSync14(
|
|
7285
7334
|
`tmux new-window -t ${shellQuote(tmuxSession + ":")} -n "scratch" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
|
|
7286
7335
|
{ encoding: "utf-8" }
|
|
7287
7336
|
).trim();
|
|
@@ -7289,7 +7338,7 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
|
|
|
7289
7338
|
if (prompt) {
|
|
7290
7339
|
cmd += ` -p ${shellQuote(prompt)}`;
|
|
7291
7340
|
}
|
|
7292
|
-
|
|
7341
|
+
execSync14(
|
|
7293
7342
|
`tmux send-keys -t ${shellQuote(windowId)} ${shellQuote(cmd)} Enter`
|
|
7294
7343
|
);
|
|
7295
7344
|
console.log(`Scratch session opened in ${tmuxSession}`);
|
|
@@ -7331,14 +7380,14 @@ File resolution (first match wins):
|
|
|
7331
7380
|
3. Most recent session with a requirements.json
|
|
7332
7381
|
|
|
7333
7382
|
Examples:
|
|
7334
|
-
$
|
|
7335
|
-
$
|
|
7336
|
-
$
|
|
7337
|
-
$
|
|
7338
|
-
$
|
|
7339
|
-
$
|
|
7340
|
-
$
|
|
7341
|
-
$
|
|
7383
|
+
$ sis admin requirements Auto-detect from current session
|
|
7384
|
+
$ sis admin requirements path/to/requirements.json Open a specific file
|
|
7385
|
+
$ sis admin requirements --session-id abc123 Target a specific session
|
|
7386
|
+
$ sis admin requirements --schema Print the JSON schema
|
|
7387
|
+
$ sis admin requirements --annotated Print schema with writing guidance
|
|
7388
|
+
$ sis admin requirements --export Render requirements.md from JSON
|
|
7389
|
+
$ sis admin requirements --export --session-id abc123 Target a specific session
|
|
7390
|
+
$ sis admin requirements --export --force Overwrite even if hand-edited
|
|
7342
7391
|
`).action(async (file, opts) => {
|
|
7343
7392
|
if (opts.force && !opts.export) {
|
|
7344
7393
|
console.error("Error: --force requires --export");
|
|
@@ -7492,7 +7541,7 @@ var REQUIREMENTS_SCHEMA = {
|
|
|
7492
7541
|
var REQUIREMENTS_ANNOTATED = `# requirements.json \u2014 Annotated Writing Guide
|
|
7493
7542
|
#
|
|
7494
7543
|
# This is NOT valid JSON \u2014 it's a reference showing every field with
|
|
7495
|
-
# inline guidance. Run \`
|
|
7544
|
+
# inline guidance. Run \`sis admin requirements --schema\` for the raw
|
|
7496
7545
|
# JSON Schema.
|
|
7497
7546
|
#
|
|
7498
7547
|
# Safe assumptions must satisfy the same EARS shape requirements as
|
|
@@ -9543,7 +9592,7 @@ async function discoverNodeWithRetry(requestedName, maxRetries = 30, intervalMs
|
|
|
9543
9592
|
const node = discoverNode(requestedName);
|
|
9544
9593
|
if (node) return node;
|
|
9545
9594
|
if (i < maxRetries - 1) {
|
|
9546
|
-
await new Promise((
|
|
9595
|
+
await new Promise((resolve12) => setTimeout(resolve12, intervalMs));
|
|
9547
9596
|
}
|
|
9548
9597
|
}
|
|
9549
9598
|
return null;
|
|
@@ -9585,14 +9634,14 @@ async function mintTailscaleKey(opts) {
|
|
|
9585
9634
|
}
|
|
9586
9635
|
if (env.oauthClientId && env.oauthClientSecret) {
|
|
9587
9636
|
if (!env.tag) {
|
|
9588
|
-
throw new Error("Tailscale tag is missing from ~/.sisyphus/deploy/tailscale.env. Re-run `
|
|
9637
|
+
throw new Error("Tailscale tag is missing from ~/.sisyphus/deploy/tailscale.env. Re-run `sis deploy auth tailscale`.");
|
|
9589
9638
|
}
|
|
9590
9639
|
return mintViaOAuth(env.oauthClientId, env.oauthClientSecret, env.tag, opts.hostname);
|
|
9591
9640
|
}
|
|
9592
9641
|
if (env.authKey) {
|
|
9593
9642
|
return env.authKey;
|
|
9594
9643
|
}
|
|
9595
|
-
throw new Error("Tailscale not configured. Run `
|
|
9644
|
+
throw new Error("Tailscale not configured. Run `sis deploy auth tailscale`.");
|
|
9596
9645
|
}
|
|
9597
9646
|
async function firstRunPrompt() {
|
|
9598
9647
|
console.log("");
|
|
@@ -9621,12 +9670,12 @@ async function firstRunPrompt() {
|
|
|
9621
9670
|
}
|
|
9622
9671
|
throw new Error(`Invalid choice: ${choice}`);
|
|
9623
9672
|
}
|
|
9624
|
-
async function mintViaOAuth(clientId, clientSecret, tag,
|
|
9673
|
+
async function mintViaOAuth(clientId, clientSecret, tag, hostname2) {
|
|
9625
9674
|
const token = await fetchAccessToken(clientId, clientSecret);
|
|
9626
9675
|
try {
|
|
9627
|
-
const removed = await deleteStaleDevicesForHostname(token,
|
|
9676
|
+
const removed = await deleteStaleDevicesForHostname(token, hostname2);
|
|
9628
9677
|
if (removed > 0) {
|
|
9629
|
-
console.log(`Tailscale: removed ${removed} stale offline node(s) named "${
|
|
9678
|
+
console.log(`Tailscale: removed ${removed} stale offline node(s) named "${hostname2}".`);
|
|
9630
9679
|
}
|
|
9631
9680
|
} catch (err) {
|
|
9632
9681
|
console.log(`Tailscale: skipped stale-node cleanup (${err.message}). Add 'devices:write' scope to clean up automatically.`);
|
|
@@ -9643,7 +9692,7 @@ async function mintViaOAuth(clientId, clientSecret, tag, hostname) {
|
|
|
9643
9692
|
}
|
|
9644
9693
|
},
|
|
9645
9694
|
expirySeconds: KEY_EXPIRY_SECONDS,
|
|
9646
|
-
description: `sisyphus deploy: ${
|
|
9695
|
+
description: `sisyphus deploy: ${hostname2}`
|
|
9647
9696
|
};
|
|
9648
9697
|
const res = await fetch(`${TS_API}/tailnet/-/keys`, {
|
|
9649
9698
|
method: "POST",
|
|
@@ -9661,7 +9710,7 @@ async function mintViaOAuth(clientId, clientSecret, tag, hostname) {
|
|
|
9661
9710
|
if (!data.key) throw new Error("Tailscale API returned no key.");
|
|
9662
9711
|
return data.key;
|
|
9663
9712
|
}
|
|
9664
|
-
async function deleteStaleDevicesForHostname(token,
|
|
9713
|
+
async function deleteStaleDevicesForHostname(token, hostname2) {
|
|
9665
9714
|
const res = await fetch(`${TS_API}/tailnet/-/devices`, {
|
|
9666
9715
|
headers: { Authorization: `Bearer ${token}` }
|
|
9667
9716
|
});
|
|
@@ -9672,7 +9721,7 @@ async function deleteStaleDevicesForHostname(token, hostname) {
|
|
|
9672
9721
|
const STALE_AFTER_MS = 5 * 60 * 1e3;
|
|
9673
9722
|
const now = Date.now();
|
|
9674
9723
|
const stale = data.devices.filter((d) => {
|
|
9675
|
-
if (d.hostname !==
|
|
9724
|
+
if (d.hostname !== hostname2) return false;
|
|
9676
9725
|
const seen = Date.parse(d.lastSeen);
|
|
9677
9726
|
if (Number.isNaN(seen)) return false;
|
|
9678
9727
|
return now - seen > STALE_AFTER_MS;
|
|
@@ -9715,7 +9764,7 @@ async function authTailscale() {
|
|
|
9715
9764
|
console.log("");
|
|
9716
9765
|
console.log("Verifying credentials...");
|
|
9717
9766
|
await fetchAccessToken(env.oauthClientId, env.oauthClientSecret);
|
|
9718
|
-
console.log("OAuth client verified \u2014 `
|
|
9767
|
+
console.log("OAuth client verified \u2014 `sis deploy <provider> up` will mint ephemeral keys.");
|
|
9719
9768
|
} else {
|
|
9720
9769
|
console.log("");
|
|
9721
9770
|
console.log("Auth key saved. Note: reusable auth keys are less secure than OAuth clients.");
|
|
@@ -9835,7 +9884,7 @@ async function deployUp(provider, opts) {
|
|
|
9835
9884
|
const outputs = readOutputs(provider);
|
|
9836
9885
|
if (!outputs) {
|
|
9837
9886
|
console.log(`
|
|
9838
|
-
Applied \u2014 but could not parse outputs. Run \`
|
|
9887
|
+
Applied \u2014 but could not parse outputs. Run \`sis deploy ${provider} status\`.`);
|
|
9839
9888
|
return;
|
|
9840
9889
|
}
|
|
9841
9890
|
console.log("");
|
|
@@ -9868,8 +9917,8 @@ Applied \u2014 but could not parse outputs. Run \`sisyphus deploy ${provider} st
|
|
|
9868
9917
|
console.log(" (Local `tailscale` CLI not on PATH \u2014 skipping tailnet discovery.)");
|
|
9869
9918
|
}
|
|
9870
9919
|
console.log("");
|
|
9871
|
-
console.log(` Tail provisioning:
|
|
9872
|
-
console.log(` Verify daemon:
|
|
9920
|
+
console.log(` Tail provisioning: sis deploy ${provider} logs`);
|
|
9921
|
+
console.log(` Verify daemon: sis deploy ${provider} ssh -- sis admin doctor`);
|
|
9873
9922
|
console.log("");
|
|
9874
9923
|
}
|
|
9875
9924
|
async function deployDown(provider, opts) {
|
|
@@ -9915,7 +9964,7 @@ function deployStatus(provider) {
|
|
|
9915
9964
|
}
|
|
9916
9965
|
const outputs = readOutputs(provider);
|
|
9917
9966
|
if (!outputs) {
|
|
9918
|
-
console.log(`${provider}: state present but outputs unreadable. Try \`
|
|
9967
|
+
console.log(`${provider}: state present but outputs unreadable. Try \`sis deploy ${provider} up\` to reconcile.`);
|
|
9919
9968
|
return;
|
|
9920
9969
|
}
|
|
9921
9970
|
const runtime = readRuntimeState(provider);
|
|
@@ -9948,7 +9997,7 @@ function effectiveSshTarget(provider) {
|
|
|
9948
9997
|
if (runtime) return `sisyphus@${runtime.tailscaleHostname}`;
|
|
9949
9998
|
const outputs = readOutputs(provider);
|
|
9950
9999
|
if (!outputs) {
|
|
9951
|
-
throw new Error(`${provider} not provisioned. Run \`
|
|
10000
|
+
throw new Error(`${provider} not provisioned. Run \`sis deploy ${provider} up\`.`);
|
|
9952
10001
|
}
|
|
9953
10002
|
return `sisyphus@${outputs.tailscale_hostname}`;
|
|
9954
10003
|
}
|
|
@@ -9993,8 +10042,8 @@ async function confirmReprovision(provider, yes) {
|
|
|
9993
10042
|
console.log("Cloud-init won't re-run on the live box, and the freshly-minted Tailscale key will be wasted.");
|
|
9994
10043
|
}
|
|
9995
10044
|
console.log("");
|
|
9996
|
-
console.log(`To push a new sisyphus version:
|
|
9997
|
-
console.log(`To rebuild from scratch:
|
|
10045
|
+
console.log(`To push a new sisyphus version: sis deploy ${provider} update`);
|
|
10046
|
+
console.log(`To rebuild from scratch: sis deploy ${provider} down && sis deploy ${provider} up`);
|
|
9998
10047
|
console.log("");
|
|
9999
10048
|
if (yes) return true;
|
|
10000
10049
|
const confirmed = await confirm2('Type "yes" to proceed anyway:');
|
|
@@ -10003,7 +10052,7 @@ async function confirmReprovision(provider, yes) {
|
|
|
10003
10052
|
}
|
|
10004
10053
|
|
|
10005
10054
|
// src/cli/commands/deploy.ts
|
|
10006
|
-
|
|
10055
|
+
init_creds();
|
|
10007
10056
|
function assertArch(raw) {
|
|
10008
10057
|
if (raw === "arm" || raw === "x86") return raw;
|
|
10009
10058
|
throw new Error(`Invalid --arch: ${raw}. Must be 'arm' or 'x86'.`);
|
|
@@ -10060,6 +10109,374 @@ function registerDeploy(program2) {
|
|
|
10060
10109
|
}
|
|
10061
10110
|
}
|
|
10062
10111
|
|
|
10112
|
+
// src/cli/cloud/runner.ts
|
|
10113
|
+
init_paths();
|
|
10114
|
+
import { spawn as spawn4 } from "child_process";
|
|
10115
|
+
import { hostname } from "os";
|
|
10116
|
+
init_creds();
|
|
10117
|
+
|
|
10118
|
+
// src/cli/deploy/ssh-exec.ts
|
|
10119
|
+
import { spawn as spawn3, spawnSync as spawnSync4 } from "child_process";
|
|
10120
|
+
function runOnBox(provider, cmd) {
|
|
10121
|
+
const target = effectiveSshTarget(provider);
|
|
10122
|
+
const result = spawnSync4("ssh", [target, cmd], {
|
|
10123
|
+
encoding: "utf-8",
|
|
10124
|
+
env: EXEC_ENV
|
|
10125
|
+
});
|
|
10126
|
+
if (typeof result.stdout !== "string" || typeof result.stderr !== "string") {
|
|
10127
|
+
throw new Error("Internal: ssh spawn did not capture output as string");
|
|
10128
|
+
}
|
|
10129
|
+
return {
|
|
10130
|
+
stdout: result.stdout,
|
|
10131
|
+
stderr: result.stderr,
|
|
10132
|
+
// status is null when killed by signal — treat as failure.
|
|
10133
|
+
exitCode: result.status === null ? 1 : result.status
|
|
10134
|
+
};
|
|
10135
|
+
}
|
|
10136
|
+
function runOnBoxStreaming(provider, cmd) {
|
|
10137
|
+
const target = effectiveSshTarget(provider);
|
|
10138
|
+
return new Promise((resolve12, reject) => {
|
|
10139
|
+
const child = spawn3("ssh", [target, cmd], {
|
|
10140
|
+
stdio: "inherit",
|
|
10141
|
+
env: EXEC_ENV
|
|
10142
|
+
});
|
|
10143
|
+
child.on("error", reject);
|
|
10144
|
+
child.on("exit", (code) => resolve12(code === null ? 1 : code));
|
|
10145
|
+
});
|
|
10146
|
+
}
|
|
10147
|
+
|
|
10148
|
+
// src/cli/cloud/grove.ts
|
|
10149
|
+
var GROVE_VERSION = "0.2.13";
|
|
10150
|
+
function ensureGroveInstalled(provider) {
|
|
10151
|
+
const probe = runOnBox(provider, "command -v grove >/dev/null 2>&1");
|
|
10152
|
+
if (probe.exitCode === 0) return;
|
|
10153
|
+
process.stderr.write("Installing grove on box...\n");
|
|
10154
|
+
const install = runOnBox(provider, `sudo npm i -g @crouton-kit/grove@${GROVE_VERSION}`);
|
|
10155
|
+
if (install.exitCode !== 0) {
|
|
10156
|
+
throw new Error(`Failed to install grove: ${install.stderr || install.stdout}`);
|
|
10157
|
+
}
|
|
10158
|
+
}
|
|
10159
|
+
function ensureGroveRegistered(provider, repo, instancePath) {
|
|
10160
|
+
const cmd = `grove register --update --name ${shellQuote(repo)} ${shellQuote(instancePath)}`;
|
|
10161
|
+
const result = runOnBox(provider, cmd);
|
|
10162
|
+
if (result.exitCode !== 0) {
|
|
10163
|
+
throw new Error(`Failed to register grove project ${repo}: ${result.stderr || result.stdout}`);
|
|
10164
|
+
}
|
|
10165
|
+
}
|
|
10166
|
+
|
|
10167
|
+
// src/cli/cloud/repo.ts
|
|
10168
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
10169
|
+
import { existsSync as existsSync28 } from "fs";
|
|
10170
|
+
import { basename as basename6, join as join26 } from "path";
|
|
10171
|
+
function captureGit(args2) {
|
|
10172
|
+
const result = spawnSync5("git", args2, {
|
|
10173
|
+
encoding: "utf-8",
|
|
10174
|
+
env: EXEC_ENV
|
|
10175
|
+
});
|
|
10176
|
+
if (typeof result.stdout !== "string") {
|
|
10177
|
+
throw new Error("Internal: git spawn did not capture stdout as string");
|
|
10178
|
+
}
|
|
10179
|
+
return { stdout: result.stdout.trim(), ok: result.status === 0 };
|
|
10180
|
+
}
|
|
10181
|
+
function inferRepoName() {
|
|
10182
|
+
const { stdout, ok } = captureGit(["rev-parse", "--show-toplevel"]);
|
|
10183
|
+
if (!ok) {
|
|
10184
|
+
throw new Error("Not inside a git repository. Run from a repo or pass --name.");
|
|
10185
|
+
}
|
|
10186
|
+
if (!stdout) {
|
|
10187
|
+
throw new Error("git rev-parse returned empty toplevel.");
|
|
10188
|
+
}
|
|
10189
|
+
return basename6(stdout);
|
|
10190
|
+
}
|
|
10191
|
+
function getOriginUrl() {
|
|
10192
|
+
const { stdout, ok } = captureGit(["remote", "get-url", "origin"]);
|
|
10193
|
+
if (!ok) return null;
|
|
10194
|
+
return stdout.length > 0 ? stdout : null;
|
|
10195
|
+
}
|
|
10196
|
+
function getRepoToplevel() {
|
|
10197
|
+
const { stdout, ok } = captureGit(["rev-parse", "--show-toplevel"]);
|
|
10198
|
+
if (!ok) {
|
|
10199
|
+
throw new Error("Not inside a git repository.");
|
|
10200
|
+
}
|
|
10201
|
+
return stdout;
|
|
10202
|
+
}
|
|
10203
|
+
var DEFAULT_EXCLUDES = [
|
|
10204
|
+
".sisyphus/",
|
|
10205
|
+
".terraform/",
|
|
10206
|
+
"node_modules/",
|
|
10207
|
+
"dist/",
|
|
10208
|
+
".next/",
|
|
10209
|
+
".turbo/",
|
|
10210
|
+
"coverage/",
|
|
10211
|
+
"tmp/",
|
|
10212
|
+
".git/lfs/",
|
|
10213
|
+
".DS_Store"
|
|
10214
|
+
];
|
|
10215
|
+
function buildRsyncArgs(localDir, remoteTarget) {
|
|
10216
|
+
const src = localDir.endsWith("/") ? localDir : `${localDir}/`;
|
|
10217
|
+
return [
|
|
10218
|
+
"-avz",
|
|
10219
|
+
"--filter=:- .gitignore",
|
|
10220
|
+
...DEFAULT_EXCLUDES.map((e) => `--exclude=${e}`),
|
|
10221
|
+
"-e",
|
|
10222
|
+
"ssh",
|
|
10223
|
+
src,
|
|
10224
|
+
remoteTarget
|
|
10225
|
+
];
|
|
10226
|
+
}
|
|
10227
|
+
function detectPackageManager(toplevel) {
|
|
10228
|
+
if (existsSync28(join26(toplevel, "pnpm-lock.yaml"))) return "pnpm";
|
|
10229
|
+
if (existsSync28(join26(toplevel, "bun.lockb"))) return "bun";
|
|
10230
|
+
if (existsSync28(join26(toplevel, "yarn.lock"))) return "yarn";
|
|
10231
|
+
if (existsSync28(join26(toplevel, "package-lock.json"))) return "npm";
|
|
10232
|
+
return null;
|
|
10233
|
+
}
|
|
10234
|
+
function packageManagerInstallCmd(pm) {
|
|
10235
|
+
switch (pm) {
|
|
10236
|
+
case "pnpm":
|
|
10237
|
+
return "pnpm install";
|
|
10238
|
+
case "bun":
|
|
10239
|
+
return "bun install";
|
|
10240
|
+
case "yarn":
|
|
10241
|
+
return "yarn install";
|
|
10242
|
+
case "npm":
|
|
10243
|
+
return "npm install";
|
|
10244
|
+
default:
|
|
10245
|
+
return null;
|
|
10246
|
+
}
|
|
10247
|
+
}
|
|
10248
|
+
|
|
10249
|
+
// src/cli/cloud/sidecar.ts
|
|
10250
|
+
init_paths();
|
|
10251
|
+
function readSidecar(provider, repo) {
|
|
10252
|
+
const path = boxCloudSidecarPath(repo);
|
|
10253
|
+
const result = runOnBox(provider, `cat ${shellQuote(path)} 2>/dev/null`);
|
|
10254
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return null;
|
|
10255
|
+
try {
|
|
10256
|
+
const parsed = JSON.parse(result.stdout);
|
|
10257
|
+
return parsed;
|
|
10258
|
+
} catch {
|
|
10259
|
+
return null;
|
|
10260
|
+
}
|
|
10261
|
+
}
|
|
10262
|
+
function writeSidecar(provider, repo, data) {
|
|
10263
|
+
const dir = boxCloudSidecarDir();
|
|
10264
|
+
const path = boxCloudSidecarPath(repo);
|
|
10265
|
+
const json = JSON.stringify(data, null, 2);
|
|
10266
|
+
const cmd = [
|
|
10267
|
+
`mkdir -p ${shellQuote(dir)}`,
|
|
10268
|
+
`cat > ${shellQuote(path)} <<'SISYPHUS_CLOUD_SIDECAR_EOF'`,
|
|
10269
|
+
json,
|
|
10270
|
+
"SISYPHUS_CLOUD_SIDECAR_EOF"
|
|
10271
|
+
].join("\n");
|
|
10272
|
+
const result = runOnBox(provider, cmd);
|
|
10273
|
+
if (result.exitCode !== 0) {
|
|
10274
|
+
throw new Error(`Failed to write sidecar for ${repo}: ${result.stderr || result.stdout}`);
|
|
10275
|
+
}
|
|
10276
|
+
}
|
|
10277
|
+
|
|
10278
|
+
// src/cli/cloud/runner.ts
|
|
10279
|
+
async function cloudSync(provider, repo, opts) {
|
|
10280
|
+
const target = effectiveSshTarget(provider);
|
|
10281
|
+
const remoteDir = boxRepoPath(repo);
|
|
10282
|
+
const localOrigin = getOriginUrl();
|
|
10283
|
+
ensureGroveInstalled(provider);
|
|
10284
|
+
const existing = readSidecar(provider, repo);
|
|
10285
|
+
if (existing && existing.originUrl && localOrigin && existing.originUrl !== localOrigin) {
|
|
10286
|
+
throw new Error(
|
|
10287
|
+
`Repo "${repo}" on the box is registered to a different origin:
|
|
10288
|
+
box: ${existing.originUrl}
|
|
10289
|
+
local: ${localOrigin}
|
|
10290
|
+
Pass --name <slug> to disambiguate, or --fresh to overwrite.`
|
|
10291
|
+
);
|
|
10292
|
+
}
|
|
10293
|
+
if (opts.fresh) {
|
|
10294
|
+
if (!localOrigin) {
|
|
10295
|
+
throw new Error("--fresh requires an `origin` remote on the local repo.");
|
|
10296
|
+
}
|
|
10297
|
+
if (!opts.yes) {
|
|
10298
|
+
console.log(`This will wipe ~/projects/${repo} on the box and re-clone from ${localOrigin}.`);
|
|
10299
|
+
const confirmed = (await promptLine('Continue? Type "yes": ', false)).toLowerCase() === "yes";
|
|
10300
|
+
if (!confirmed) {
|
|
10301
|
+
console.log("Aborted.");
|
|
10302
|
+
return;
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
console.log(`\u2192 wiping ${remoteDir} and cloning ${localOrigin} on box...`);
|
|
10306
|
+
const cloneCmd = [
|
|
10307
|
+
`rm -rf ${shellQuote(remoteDir)}`,
|
|
10308
|
+
`mkdir -p ${shellQuote("~/projects")}`,
|
|
10309
|
+
`git clone ${shellQuote(localOrigin)} ${shellQuote(remoteDir)}`
|
|
10310
|
+
].join(" && ");
|
|
10311
|
+
const code = await runOnBoxStreaming(provider, cloneCmd);
|
|
10312
|
+
if (code !== 0) throw new Error(`fresh clone failed (exit ${code})`);
|
|
10313
|
+
} else {
|
|
10314
|
+
const mkdir = runOnBox(provider, `mkdir -p ${shellQuote(remoteDir)}`);
|
|
10315
|
+
if (mkdir.exitCode !== 0) {
|
|
10316
|
+
throw new Error(`Failed to mkdir on box: ${mkdir.stderr}`);
|
|
10317
|
+
}
|
|
10318
|
+
const toplevel = getRepoToplevel();
|
|
10319
|
+
const args2 = buildRsyncArgs(toplevel, `${target}:${remoteDir}/`);
|
|
10320
|
+
console.log(`\u2192 rsync ${toplevel}/ \u2192 ${target}:${remoteDir}/`);
|
|
10321
|
+
const code = await runRsync(args2);
|
|
10322
|
+
if (code !== 0) throw new Error(`rsync failed (exit ${code})`);
|
|
10323
|
+
}
|
|
10324
|
+
ensureGroveRegistered(provider, repo, remoteDir);
|
|
10325
|
+
const sidecar = {
|
|
10326
|
+
originUrl: localOrigin,
|
|
10327
|
+
localHostname: hostname(),
|
|
10328
|
+
lastSync: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10329
|
+
packageManager: existing?.packageManager,
|
|
10330
|
+
lastInstall: existing?.lastInstall
|
|
10331
|
+
};
|
|
10332
|
+
writeSidecar(provider, repo, sidecar);
|
|
10333
|
+
console.log(`\u2713 synced ${repo} \u2192 ${target}:${remoteDir}/`);
|
|
10334
|
+
}
|
|
10335
|
+
function runRsync(args2) {
|
|
10336
|
+
return new Promise((resolve12, reject) => {
|
|
10337
|
+
const child = spawn4("rsync", args2, { stdio: "inherit", env: EXEC_ENV });
|
|
10338
|
+
child.on("error", reject);
|
|
10339
|
+
child.on("exit", (code) => resolve12(code === null ? 1 : code));
|
|
10340
|
+
});
|
|
10341
|
+
}
|
|
10342
|
+
async function cloudInstall(provider, repo) {
|
|
10343
|
+
const remoteDir = boxRepoPath(repo);
|
|
10344
|
+
const toplevel = getRepoToplevel();
|
|
10345
|
+
const pm = detectPackageManager(toplevel);
|
|
10346
|
+
const cmd = packageManagerInstallCmd(pm);
|
|
10347
|
+
if (!cmd) {
|
|
10348
|
+
console.log("No lockfile detected \u2014 skipping install.");
|
|
10349
|
+
return;
|
|
10350
|
+
}
|
|
10351
|
+
console.log(`\u2192 ${pm} install in ${remoteDir} on box...`);
|
|
10352
|
+
const remoteCmd = `cd ${shellQuote(remoteDir)} && ${cmd}`;
|
|
10353
|
+
const code = await runOnBoxStreaming(provider, remoteCmd);
|
|
10354
|
+
if (code !== 0) throw new Error(`${pm} install failed (exit ${code})`);
|
|
10355
|
+
const existing = readSidecar(provider, repo);
|
|
10356
|
+
const sidecar = {
|
|
10357
|
+
originUrl: existing && existing.originUrl !== void 0 ? existing.originUrl : getOriginUrl(),
|
|
10358
|
+
localHostname: existing ? existing.localHostname : hostname(),
|
|
10359
|
+
lastSync: existing?.lastSync,
|
|
10360
|
+
lastInstall: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10361
|
+
packageManager: pm
|
|
10362
|
+
};
|
|
10363
|
+
writeSidecar(provider, repo, sidecar);
|
|
10364
|
+
console.log(`\u2713 installed ${repo} (${pm})`);
|
|
10365
|
+
}
|
|
10366
|
+
async function cloudSession(provider, repo) {
|
|
10367
|
+
const remoteDir = boxRepoPath(repo);
|
|
10368
|
+
const cmd = `sis admin home-init ${shellQuote(repo)} ${shellQuote(remoteDir)}`;
|
|
10369
|
+
console.log(`\u2192 initializing tmux home session "${repo}" on box...`);
|
|
10370
|
+
const result = runOnBox(provider, cmd);
|
|
10371
|
+
if (result.exitCode !== 0) {
|
|
10372
|
+
throw new Error(`home-init failed: ${result.stderr || result.stdout}`);
|
|
10373
|
+
}
|
|
10374
|
+
if (result.stdout.trim()) console.log(result.stdout.trim());
|
|
10375
|
+
console.log(`\u2713 session "${repo}" ready on box`);
|
|
10376
|
+
}
|
|
10377
|
+
function cloudAttach(provider, repo) {
|
|
10378
|
+
if (process.env.TMUX) {
|
|
10379
|
+
throw new Error(
|
|
10380
|
+
`Refusing to attach from inside tmux \u2014 would nest the cloud tmux client.
|
|
10381
|
+
Use a fresh terminal, or run from outside tmux:
|
|
10382
|
+
tmux new-window 'ssh -t ${effectiveSshTarget(provider)} tmux attach -t ${repo}'`
|
|
10383
|
+
);
|
|
10384
|
+
}
|
|
10385
|
+
const target = effectiveSshTarget(provider);
|
|
10386
|
+
const child = spawn4("ssh", ["-t", target, `tmux attach-session -t ${shellQuote(repo)}`], {
|
|
10387
|
+
stdio: "inherit",
|
|
10388
|
+
env: EXEC_ENV
|
|
10389
|
+
});
|
|
10390
|
+
child.on("exit", (code) => process.exit(code === null ? 1 : code));
|
|
10391
|
+
}
|
|
10392
|
+
async function cloudStart(provider, repo, opts) {
|
|
10393
|
+
await cloudSync(provider, repo, { fresh: opts.fresh, yes: opts.yes });
|
|
10394
|
+
await cloudInstall(provider, repo);
|
|
10395
|
+
await cloudSession(provider, repo);
|
|
10396
|
+
console.log("");
|
|
10397
|
+
console.log(`Box-side dashboard ready. Attach with:`);
|
|
10398
|
+
console.log(` tmux new-window 'ssh -t ${effectiveSshTarget(provider)} tmux attach -t ${repo}'`);
|
|
10399
|
+
console.log("(or run inside the slash command, which does this for you.)");
|
|
10400
|
+
}
|
|
10401
|
+
function cloudStatus(provider, repo) {
|
|
10402
|
+
const target = effectiveSshTarget(provider);
|
|
10403
|
+
const sidecar = readSidecar(provider, repo);
|
|
10404
|
+
const sessionProbe = runOnBox(provider, `tmux has-session -t ${shellQuote(repo)} 2>/dev/null`);
|
|
10405
|
+
const sessionRunning = sessionProbe.exitCode === 0;
|
|
10406
|
+
console.log(`Cloud status for "${repo}":`);
|
|
10407
|
+
console.log(` Provider: ${provider}`);
|
|
10408
|
+
console.log(` Target: ${target}`);
|
|
10409
|
+
console.log(` Planted: ${sidecar ? "yes" : "no"}`);
|
|
10410
|
+
if (sidecar) {
|
|
10411
|
+
console.log(` Origin: ${sidecar.originUrl ? sidecar.originUrl : "(none)"}`);
|
|
10412
|
+
console.log(` Last sync: ${sidecar.lastSync ? sidecar.lastSync : "(never)"}`);
|
|
10413
|
+
console.log(` Last install: ${sidecar.lastInstall ? sidecar.lastInstall : "(never)"}`);
|
|
10414
|
+
console.log(` Package manager: ${sidecar.packageManager ? sidecar.packageManager : "(none)"}`);
|
|
10415
|
+
}
|
|
10416
|
+
console.log(` Session: ${sessionRunning ? "running" : "absent"}`);
|
|
10417
|
+
if (sessionRunning) {
|
|
10418
|
+
console.log(` Attach: tmux new-window 'ssh -t ${target} tmux attach -t ${repo}'`);
|
|
10419
|
+
}
|
|
10420
|
+
}
|
|
10421
|
+
|
|
10422
|
+
// src/cli/deploy/provider-pick.ts
|
|
10423
|
+
init_creds();
|
|
10424
|
+
function pickProvider(explicit) {
|
|
10425
|
+
if (explicit) {
|
|
10426
|
+
if (!isValidProvider(explicit)) {
|
|
10427
|
+
throw new Error(`Unknown provider "${explicit}". Valid: ${PROVIDERS.join(", ")}.`);
|
|
10428
|
+
}
|
|
10429
|
+
return explicit;
|
|
10430
|
+
}
|
|
10431
|
+
const provisioned = PROVIDERS.filter((p) => isProvisioned(p));
|
|
10432
|
+
if (provisioned.length === 1) return provisioned[0];
|
|
10433
|
+
if (provisioned.length === 0) {
|
|
10434
|
+
throw new Error(
|
|
10435
|
+
"No cloud provider provisioned. Run `sis deploy <hetzner|aws> up` first."
|
|
10436
|
+
);
|
|
10437
|
+
}
|
|
10438
|
+
throw new Error(
|
|
10439
|
+
`Multiple providers provisioned (${provisioned.join(", ")}). Pass --provider <name>.`
|
|
10440
|
+
);
|
|
10441
|
+
}
|
|
10442
|
+
|
|
10443
|
+
// src/cli/commands/cloud.ts
|
|
10444
|
+
function resolve11(raw) {
|
|
10445
|
+
const provider = pickProvider(raw.provider);
|
|
10446
|
+
const repo = raw.name ? raw.name : inferRepoName();
|
|
10447
|
+
if (!validateRepoName(repo)) {
|
|
10448
|
+
throw new Error(`Invalid --name "${repo}": must not contain '/' '\\' or '..'.`);
|
|
10449
|
+
}
|
|
10450
|
+
return { provider, repo };
|
|
10451
|
+
}
|
|
10452
|
+
function registerCloud(program2) {
|
|
10453
|
+
const cloud = program2.command("cloud").description("Per-repo workflow on the shared cloud box (sync, install, dashboard).");
|
|
10454
|
+
cloud.command("sync").description("Rsync this repo to the cloud box; ensures grove is installed and the repo is registered.").option("--fresh", "Wipe the box-side dir and `git clone` from origin instead of rsync.").option("-y, --yes", "Skip the --fresh confirmation prompt.").option("--name <repo>", "Override the repo name (default: basename of git toplevel).").option("--provider <name>", "Cloud provider (default: auto-pick if exactly one is provisioned).").action(async (raw) => {
|
|
10455
|
+
const { provider, repo } = resolve11(raw);
|
|
10456
|
+
await cloudSync(provider, repo, { fresh: raw.fresh === true, yes: raw.yes === true });
|
|
10457
|
+
});
|
|
10458
|
+
cloud.command("install").description("Run the repo's package-manager install on the box.").option("--name <repo>", "Override the repo name.").option("--provider <name>", "Cloud provider.").action(async (raw) => {
|
|
10459
|
+
const { provider, repo } = resolve11(raw);
|
|
10460
|
+
await cloudInstall(provider, repo);
|
|
10461
|
+
});
|
|
10462
|
+
cloud.command("session").description("Create or refresh the box-side tmux home session for this repo.").option("--name <repo>", "Override the repo name.").option("--provider <name>", "Cloud provider.").action(async (raw) => {
|
|
10463
|
+
const { provider, repo } = resolve11(raw);
|
|
10464
|
+
await cloudSession(provider, repo);
|
|
10465
|
+
});
|
|
10466
|
+
cloud.command("attach").description("Attach to the box-side tmux home session for this repo.").option("--name <repo>", "Override the repo name.").option("--provider <name>", "Cloud provider.").action((raw) => {
|
|
10467
|
+
const { provider, repo } = resolve11(raw);
|
|
10468
|
+
cloudAttach(provider, repo);
|
|
10469
|
+
});
|
|
10470
|
+
cloud.command("start").description("Sync, install, and start the dashboard session in one shot. (Stops short of attach.)").option("--fresh", "Wipe the box-side dir and `git clone` from origin instead of rsync.").option("-y, --yes", "Skip the --fresh confirmation prompt.").option("--name <repo>", "Override the repo name.").option("--provider <name>", "Cloud provider.").action(async (raw) => {
|
|
10471
|
+
const { provider, repo } = resolve11(raw);
|
|
10472
|
+
await cloudStart(provider, repo, { fresh: raw.fresh === true, yes: raw.yes === true });
|
|
10473
|
+
});
|
|
10474
|
+
cloud.command("status").description("Print box-side status for this repo (planted, session running, last sync/install).").option("--name <repo>", "Override the repo name.").option("--provider <name>", "Cloud provider.").action((raw) => {
|
|
10475
|
+
const { provider, repo } = resolve11(raw);
|
|
10476
|
+
cloudStatus(provider, repo);
|
|
10477
|
+
});
|
|
10478
|
+
}
|
|
10479
|
+
|
|
10063
10480
|
// src/cli/commands/notify.ts
|
|
10064
10481
|
function attachNotify(diagnostic2) {
|
|
10065
10482
|
const notify = diagnostic2.command("notify").description("Internal notifications (fire-and-forget)");
|
|
@@ -10073,11 +10490,11 @@ function attachNotify(diagnostic2) {
|
|
|
10073
10490
|
}
|
|
10074
10491
|
|
|
10075
10492
|
// src/cli/commands/tmux-status.ts
|
|
10076
|
-
import { execSync as
|
|
10493
|
+
import { execSync as execSync15 } from "child_process";
|
|
10077
10494
|
function attachTmuxStatus(diagnostic2) {
|
|
10078
10495
|
diagnostic2.command("tmux-status").description("Output session status dots for tmux status bar").action(() => {
|
|
10079
10496
|
try {
|
|
10080
|
-
const status =
|
|
10497
|
+
const status = execSync15(
|
|
10081
10498
|
"tmux show-option -gv @sisyphus_status",
|
|
10082
10499
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
10083
10500
|
).trim();
|
|
@@ -10089,8 +10506,8 @@ function attachTmuxStatus(diagnostic2) {
|
|
|
10089
10506
|
|
|
10090
10507
|
// src/cli/commands/tmux-sessions.ts
|
|
10091
10508
|
init_paths();
|
|
10092
|
-
import { execSync as
|
|
10093
|
-
import { readFileSync as readFileSync30, existsSync as
|
|
10509
|
+
import { execSync as execSync16 } from "child_process";
|
|
10510
|
+
import { readFileSync as readFileSync30, existsSync as existsSync29 } from "fs";
|
|
10094
10511
|
var DOT_MAP = {
|
|
10095
10512
|
"orchestrator:processing": { icon: "\u25CF", color: "#d4ad6a" },
|
|
10096
10513
|
"orchestrator:idle": { icon: "\u25CF", color: "#d47766" },
|
|
@@ -10101,7 +10518,7 @@ var DOT_MAP = {
|
|
|
10101
10518
|
};
|
|
10102
10519
|
function readManifest() {
|
|
10103
10520
|
const p = sessionsManifestPath();
|
|
10104
|
-
if (!
|
|
10521
|
+
if (!existsSync29(p)) return null;
|
|
10105
10522
|
try {
|
|
10106
10523
|
return JSON.parse(readFileSync30(p, "utf-8"));
|
|
10107
10524
|
} catch {
|
|
@@ -10110,7 +10527,7 @@ function readManifest() {
|
|
|
10110
10527
|
}
|
|
10111
10528
|
function tmuxExec(cmd) {
|
|
10112
10529
|
try {
|
|
10113
|
-
return
|
|
10530
|
+
return execSync16(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10114
10531
|
} catch {
|
|
10115
10532
|
return null;
|
|
10116
10533
|
}
|
|
@@ -10147,9 +10564,9 @@ if (nodeVersion < 22) {
|
|
|
10147
10564
|
process.exit(1);
|
|
10148
10565
|
}
|
|
10149
10566
|
var program = new Command();
|
|
10150
|
-
program.name("
|
|
10567
|
+
program.name("sis").description("tmux-integrated orchestration daemon for Claude Code").version(
|
|
10151
10568
|
JSON.parse(
|
|
10152
|
-
readFileSync31(
|
|
10569
|
+
readFileSync31(join27(dirname12(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
|
|
10153
10570
|
).version
|
|
10154
10571
|
);
|
|
10155
10572
|
program.configureHelp({
|
|
@@ -10190,6 +10607,7 @@ registerSegmentUnregister(segment);
|
|
|
10190
10607
|
var admin = program.command("admin").description("Admin / setup commands");
|
|
10191
10608
|
registerSetup(admin);
|
|
10192
10609
|
registerSetupKeybind(admin);
|
|
10610
|
+
registerHomeInit(admin);
|
|
10193
10611
|
registerDoctor(admin);
|
|
10194
10612
|
registerInit(admin);
|
|
10195
10613
|
registerUninstall(admin);
|
|
@@ -10202,27 +10620,28 @@ registerScratch(admin);
|
|
|
10202
10620
|
registerReview(admin);
|
|
10203
10621
|
registerCompanion(program);
|
|
10204
10622
|
registerDeploy(program);
|
|
10623
|
+
registerCloud(program);
|
|
10205
10624
|
var diagnostic = program.command("diagnostic", { hidden: true });
|
|
10206
10625
|
attachNotify(diagnostic);
|
|
10207
10626
|
attachTmuxStatus(diagnostic);
|
|
10208
10627
|
attachTmuxSessions(diagnostic);
|
|
10209
10628
|
program.addHelpText("after", `
|
|
10210
10629
|
Examples:
|
|
10211
|
-
$
|
|
10212
|
-
$
|
|
10213
|
-
$
|
|
10214
|
-
$
|
|
10215
|
-
$
|
|
10630
|
+
$ sis start "Implement auth system" Start a new session
|
|
10631
|
+
$ sis start "Build @reqs.md" -n auth Start with name + requirements
|
|
10632
|
+
$ sis status Check current sessions
|
|
10633
|
+
$ sis dashboard Open the TUI
|
|
10634
|
+
$ sis admin doctor Verify installation
|
|
10216
10635
|
|
|
10217
|
-
Run '
|
|
10636
|
+
Run 'sis admin getting-started' for a complete usage guide.
|
|
10218
10637
|
`);
|
|
10219
10638
|
var args = process.argv.slice(2);
|
|
10220
10639
|
var firstArg = args[0];
|
|
10221
10640
|
var skipWelcome = ["admin", "help", "--help", "-h", "--version", "-V"];
|
|
10222
|
-
if (!
|
|
10641
|
+
if (!existsSync30(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
|
|
10223
10642
|
mkdirSync14(globalDir(), { recursive: true });
|
|
10224
10643
|
console.log("");
|
|
10225
|
-
console.log(" Welcome to Sisyphus. Run '
|
|
10644
|
+
console.log(" Welcome to Sisyphus. Run 'sis admin setup' to get started.");
|
|
10226
10645
|
console.log("");
|
|
10227
10646
|
}
|
|
10228
10647
|
program.parseAsync(process.argv).catch((err) => {
|