sisyphi 1.1.24 → 1.1.26

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.
Files changed (76) hide show
  1. package/README.md +35 -35
  2. package/deploy/aws/main.tf +1 -1
  3. package/deploy/aws/variables.tf +1 -1
  4. package/deploy/aws/versions.tf +1 -1
  5. package/deploy/hetzner/variables.tf +1 -1
  6. package/deploy/hetzner/versions.tf +1 -1
  7. package/deploy/shared/cloud-init.yaml.tpl +1 -1
  8. package/dist/cli.js +911 -242
  9. package/dist/cli.js.map +1 -1
  10. package/dist/daemon.js +27 -11
  11. package/dist/daemon.js.map +1 -1
  12. package/dist/deploy/aws/main.tf +1 -1
  13. package/dist/deploy/aws/variables.tf +1 -1
  14. package/dist/deploy/aws/versions.tf +1 -1
  15. package/dist/deploy/hetzner/variables.tf +1 -1
  16. package/dist/deploy/hetzner/versions.tf +1 -1
  17. package/dist/deploy/shared/cloud-init.yaml.tpl +1 -1
  18. package/dist/templates/agent-plugin/agents/explore.md +2 -2
  19. package/dist/templates/agent-plugin/agents/implementor.md +2 -2
  20. package/dist/templates/agent-plugin/agents/operator.md +3 -3
  21. package/dist/templates/agent-plugin/agents/plan.md +2 -2
  22. package/dist/templates/agent-plugin/agents/problem.md +8 -8
  23. package/dist/templates/agent-plugin/agents/review-plan/CLAUDE.md +1 -1
  24. package/dist/templates/agent-plugin/agents/spec/requirements-writer.md +1 -1
  25. package/dist/templates/agent-plugin/agents/spec.md +19 -19
  26. package/dist/templates/agent-plugin/skills/humanloop/SKILL.md +9 -8
  27. package/dist/templates/agent-plugin/skills/perspective-fanout/SKILL.md +2 -2
  28. package/dist/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +2 -2
  29. package/dist/templates/agent-suffix.md +3 -3
  30. package/dist/templates/dashboard-claude.md +13 -13
  31. package/dist/templates/orchestrator-base.md +13 -13
  32. package/dist/templates/orchestrator-completion.md +11 -11
  33. package/dist/templates/orchestrator-discovery.md +5 -5
  34. package/dist/templates/orchestrator-impl.md +8 -8
  35. package/dist/templates/orchestrator-planning.md +6 -6
  36. package/dist/templates/orchestrator-plugin/commands/sisyphus/scratch.md +1 -1
  37. package/dist/templates/orchestrator-plugin/commands/sisyphus/strategize.md +2 -2
  38. package/dist/templates/orchestrator-plugin/skills/humanloop/SKILL.md +11 -10
  39. package/dist/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +1 -1
  40. package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +1 -1
  41. package/dist/templates/orchestrator-plugin/skills/orchestration/strategy.md +4 -4
  42. package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +2 -2
  43. package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +1 -1
  44. package/dist/templates/orchestrator-validation.md +5 -5
  45. package/dist/templates/termrender-haiku-system.md +1 -1
  46. package/dist/tui.js +8 -8
  47. package/dist/tui.js.map +1 -1
  48. package/package.json +2 -1
  49. package/templates/agent-plugin/agents/explore.md +2 -2
  50. package/templates/agent-plugin/agents/implementor.md +2 -2
  51. package/templates/agent-plugin/agents/operator.md +3 -3
  52. package/templates/agent-plugin/agents/plan.md +2 -2
  53. package/templates/agent-plugin/agents/problem.md +8 -8
  54. package/templates/agent-plugin/agents/review-plan/CLAUDE.md +1 -1
  55. package/templates/agent-plugin/agents/spec/requirements-writer.md +1 -1
  56. package/templates/agent-plugin/agents/spec.md +19 -19
  57. package/templates/agent-plugin/skills/humanloop/SKILL.md +9 -8
  58. package/templates/agent-plugin/skills/perspective-fanout/SKILL.md +2 -2
  59. package/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +2 -2
  60. package/templates/agent-suffix.md +3 -3
  61. package/templates/dashboard-claude.md +13 -13
  62. package/templates/orchestrator-base.md +13 -13
  63. package/templates/orchestrator-completion.md +11 -11
  64. package/templates/orchestrator-discovery.md +5 -5
  65. package/templates/orchestrator-impl.md +8 -8
  66. package/templates/orchestrator-planning.md +6 -6
  67. package/templates/orchestrator-plugin/commands/sisyphus/scratch.md +1 -1
  68. package/templates/orchestrator-plugin/commands/sisyphus/strategize.md +2 -2
  69. package/templates/orchestrator-plugin/skills/humanloop/SKILL.md +11 -10
  70. package/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +1 -1
  71. package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +1 -1
  72. package/templates/orchestrator-plugin/skills/orchestration/strategy.md +4 -4
  73. package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +2 -2
  74. package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +1 -1
  75. package/templates/orchestrator-validation.md +5 -5
  76. package/templates/termrender-haiku-system.md +1 -1
package/dist/cli.js CHANGED
@@ -111,12 +111,24 @@ function deployStatePath(provider) {
111
111
  function deployStateBackupPath(provider) {
112
112
  return join(deployProviderDir(provider), "terraform.tfstate.bak");
113
113
  }
114
+ function deployRuntimePath(provider) {
115
+ return join(deployProviderDir(provider), "runtime.json");
116
+ }
114
117
  function deployCredsPath(provider) {
115
118
  return join(deployDir(), `${provider}.env`);
116
119
  }
117
120
  function deployTailscaleEnvPath() {
118
121
  return join(deployDir(), "tailscale.env");
119
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
+ }
120
132
  var init_paths = __esm({
121
133
  "src/shared/paths.ts"() {
122
134
  "use strict";
@@ -135,16 +147,16 @@ function atomicWrite(filePath, data) {
135
147
  }
136
148
  async function withLock(key, fn) {
137
149
  const prev = locks.get(key) ?? Promise.resolve();
138
- let resolve11;
150
+ let resolve12;
139
151
  const next = new Promise((r) => {
140
- resolve11 = r;
152
+ resolve12 = r;
141
153
  });
142
154
  locks.set(key, next);
143
155
  await prev;
144
156
  try {
145
157
  return fn();
146
158
  } finally {
147
- resolve11();
159
+ resolve12();
148
160
  if (locks.get(key) === next) {
149
161
  locks.delete(key);
150
162
  }
@@ -161,7 +173,9 @@ var init_atomic = __esm({
161
173
  // src/cli/deploy/creds.ts
162
174
  var creds_exports = {};
163
175
  __export(creds_exports, {
176
+ PROVIDERS: () => PROVIDERS,
164
177
  ensureDeployDir: () => ensureDeployDir,
178
+ isValidProvider: () => isValidProvider,
165
179
  loadProviderCreds: () => loadProviderCreds,
166
180
  maskValue: () => maskValue,
167
181
  promptLine: () => promptLine,
@@ -169,7 +183,10 @@ __export(creds_exports, {
169
183
  writeTailscaleEnv: () => writeTailscaleEnv
170
184
  });
171
185
  import { chmodSync as chmodSync3, existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync27 } from "fs";
172
- import { createInterface as createInterface3 } from "readline";
186
+ import { createInterface as createInterface4 } from "readline";
187
+ function isValidProvider(value) {
188
+ return PROVIDERS.includes(value);
189
+ }
173
190
  function ensureDeployDir() {
174
191
  const dir = deployDir();
175
192
  if (!existsSync24(dir)) mkdirSync12(dir, { recursive: true, mode: 448 });
@@ -191,7 +208,7 @@ function parseEnvFile(text) {
191
208
  return out;
192
209
  }
193
210
  function serializeEnvFile(values) {
194
- const lines = ["# Managed by `sisyphus deploy`. Do not edit unless you know what you are doing."];
211
+ const lines = ["# Managed by `sis deploy`. Do not edit unless you know what you are doing."];
195
212
  for (const [k, v] of Object.entries(values)) {
196
213
  lines.push(`${k}=${v}`);
197
214
  }
@@ -207,7 +224,7 @@ function writeEnvFile(path, values) {
207
224
  chmodSync3(path, 384);
208
225
  }
209
226
  async function promptLine(question, hidden) {
210
- const rl = createInterface3({ input: process.stdin, output: process.stdout, terminal: true });
227
+ const rl = createInterface4({ input: process.stdin, output: process.stdout, terminal: true });
211
228
  if (hidden) {
212
229
  const stdout = process.stdout;
213
230
  const originalWrite = stdout.write.bind(stdout);
@@ -219,7 +236,7 @@ async function promptLine(question, hidden) {
219
236
  }
220
237
  };
221
238
  }
222
- const answer = await new Promise((resolve11) => rl.question(question, resolve11));
239
+ const answer = await new Promise((resolve12) => rl.question(question, resolve12));
223
240
  rl.close();
224
241
  if (hidden) process.stdout.write("\n");
225
242
  return answer.trim();
@@ -269,12 +286,13 @@ function writeTailscaleEnv(env) {
269
286
  if (env.tag) values.TS_TAG = env.tag;
270
287
  writeEnvFile(deployTailscaleEnvPath(), values);
271
288
  }
272
- var SPECS;
289
+ var PROVIDERS, SPECS;
273
290
  var init_creds = __esm({
274
291
  "src/cli/deploy/creds.ts"() {
275
292
  "use strict";
276
293
  init_atomic();
277
294
  init_paths();
295
+ PROVIDERS = ["hetzner", "aws"];
278
296
  SPECS = {
279
297
  hetzner: {
280
298
  provider: "hetzner",
@@ -293,8 +311,8 @@ var init_creds = __esm({
293
311
 
294
312
  // src/cli/index.ts
295
313
  import { Command } from "commander";
296
- import { existsSync as existsSync28, mkdirSync as mkdirSync14, readFileSync as readFileSync30 } from "fs";
297
- import { dirname as dirname12, join as join26 } from "path";
314
+ import { existsSync as existsSync30, mkdirSync as mkdirSync14, readFileSync as readFileSync31 } from "fs";
315
+ import { dirname as dirname12, join as join27 } from "path";
298
316
  import { fileURLToPath as fileURLToPath5 } from "url";
299
317
 
300
318
  // src/cli/commands/start.ts
@@ -306,13 +324,13 @@ init_paths();
306
324
  import { connect } from "net";
307
325
  function rawSend(request, timeoutMs = 1e4) {
308
326
  const sock = socketPath();
309
- return new Promise((resolve11, reject) => {
327
+ return new Promise((resolve12, reject) => {
310
328
  const socket = connect(sock);
311
329
  let data = "";
312
330
  const timeout = setTimeout(() => {
313
331
  socket.destroy();
314
332
  reject(new Error(`Request timed out after ${(timeoutMs / 1e3).toFixed(0)}s. The daemon may be overloaded.
315
- Check: sisyphus admin doctor
333
+ Check: sis admin doctor
316
334
  Logs: tail -20 ~/.sisyphus/daemon.log`));
317
335
  }, timeoutMs);
318
336
  socket.on("connect", () => {
@@ -326,7 +344,7 @@ function rawSend(request, timeoutMs = 1e4) {
326
344
  const line = data.slice(0, newlineIdx);
327
345
  socket.destroy();
328
346
  try {
329
- resolve11(JSON.parse(line));
347
+ resolve12(JSON.parse(line));
330
348
  } catch {
331
349
  reject(new Error(`Invalid JSON response from daemon: ${line}`));
332
350
  }
@@ -354,6 +372,7 @@ import { execSync } from "child_process";
354
372
  import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, unlinkSync } from "fs";
355
373
  import { homedir as homedir2 } from "os";
356
374
  import { join as join2 } from "path";
375
+ import { createInterface } from "readline";
357
376
 
358
377
  // src/shared/keymap.ts
359
378
  function formatHelpForKeymap(km) {
@@ -590,7 +609,7 @@ while IFS=$'\\t' read -r type name scwd phase sid; do
590
609
  [ "$sid" = "$current_id" ] && { cwd="$scwd"; break; }
591
610
  done < "$MANIFEST"
592
611
  if [ -z "$cwd" ]; then
593
- tmux display-message "sisyphus: '$current_name' has no @sisyphus_cwd \u2014 run 'sisyphus start' here to register"
612
+ tmux display-message "sisyphus: '$current_name' has no @sisyphus_cwd \u2014 run 'sis start' here to register"
594
613
  exit 0
595
614
  fi
596
615
  session_ids=()
@@ -695,7 +714,7 @@ tmpfile=$(mktemp /tmp/sisyphus-new-XXXXXX.md)
695
714
  trap 'rm -f "$tmpfile"' EXIT
696
715
  nvim "$tmpfile"
697
716
  grep -q '[^[:space:]]' "$tmpfile" || exit 0
698
- exec sisyphus start "$(cat "$tmpfile")"
717
+ exec sis start "$(cat "$tmpfile")"
699
718
  `;
700
719
  var MESSAGE_SCRIPT = `#!/bin/bash
701
720
  # Open nvim to compose a message for the current session's orchestrator
@@ -726,7 +745,7 @@ tmpfile=$(mktemp /tmp/sisyphus-msg-XXXXXX.md)
726
745
  trap 'rm -f "$tmpfile"' EXIT
727
746
  nvim "$tmpfile"
728
747
  grep -q '[^[:space:]]' "$tmpfile" || exit 0
729
- exec sisyphus message --session "$session_id" "$(cat "$tmpfile")"
748
+ exec sis message --session "$session_id" "$(cat "$tmpfile")"
730
749
  `;
731
750
  var SESSION_RESOLVE = `
732
751
  tmux_sid=$(tmux display-message -p '#{session_id}')
@@ -762,7 +781,7 @@ var KILL_SESSION_SCRIPT = `#!/bin/bash
762
781
  # Kill the sisyphus session associated with the current tmux session
763
782
  ${SESSION_RESOLVE}
764
783
 
765
- sisyphus session kill "$session_id" >/dev/null 2>&1
784
+ sis session kill "$session_id" >/dev/null 2>&1
766
785
  ${GO_HOME_AFTER}
767
786
  `;
768
787
  var DELETE_SESSION_SCRIPT = `#!/bin/bash
@@ -772,7 +791,7 @@ ${SESSION_RESOLVE}
772
791
  printf "\\033[31mType 'yes' to confirm:\\033[0m "
773
792
  read -r answer
774
793
  [ "$answer" = "yes" ] || exit 0
775
- sisyphus session delete "$session_id" --cwd "$cwd" >/dev/null 2>&1
794
+ sis session delete "$session_id" --cwd "$cwd" >/dev/null 2>&1
776
795
  ${GO_HOME_AFTER}
777
796
  `;
778
797
  var HELP_SCRIPT = `#!/bin/bash
@@ -806,9 +825,9 @@ if [ -z "$session_id" ]; then
806
825
  fi
807
826
 
808
827
  if [ -n "$session_id" ]; then
809
- sisyphus status "$session_id" 2>&1 | less -R
828
+ sis status "$session_id" 2>&1 | less -R
810
829
  else
811
- sisyphus list 2>&1 | less -R
830
+ sis list 2>&1 | less -R
812
831
  fi
813
832
  `;
814
833
  var PICK_SESSION_SCRIPT = `#!/bin/bash
@@ -867,7 +886,7 @@ short_id="\${session_id:0:8}"
867
886
  printf "\\033[33mContinue session %s...?\\033[0m (y/n) " "$short_id"
868
887
  read -r answer
869
888
  [ "$answer" = "y" ] || [ "$answer" = "yes" ] || exit 0
870
- sisyphus session continue --session "$session_id"
889
+ sis session continue --session "$session_id"
871
890
  sleep 1
872
891
  `;
873
892
  var OPEN_ROADMAP_SCRIPT = `#!/bin/bash
@@ -891,18 +910,18 @@ var EXPORT_SESSION_SCRIPT = `#!/bin/bash
891
910
  ${SESSION_RESOLVE}
892
911
 
893
912
  echo "Exporting session \${session_id:0:8}..."
894
- sisyphus admin export "$session_id" --cwd "$cwd"
913
+ sis admin export "$session_id" --cwd "$cwd"
895
914
  echo ""
896
915
  read -n 1 -s -r -p "Press a key to close."
897
916
  `;
898
917
  var RESTART_AGENT_SCRIPT = `#!/bin/bash
899
918
  # Pick a sisyphus agent and restart it (fzf picker with confirm for running agents).
900
- # Assumes macOS (fzf optional). Requires \`sisyphus status --json\`.
919
+ # Assumes macOS (fzf optional). Requires \`sis status --json\`.
901
920
  ${SESSION_RESOLVE}
902
921
 
903
922
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
904
923
 
905
- agents_json=$(sisyphus status "$session_id" --json 2>/dev/null)
924
+ agents_json=$(sis status "$session_id" --json 2>/dev/null)
906
925
  if [ -z "$agents_json" ]; then
907
926
  echo "Failed to read session status"; sleep 1; exit 1
908
927
  fi
@@ -939,7 +958,7 @@ if [ "\${statuses[$idx]}" = "running" ]; then
939
958
  [ "$answer" = "yes" ] || exit 0
940
959
  fi
941
960
 
942
- sisyphus agent restart "\${ids[$idx]}" --session "$session_id"
961
+ sis agent restart "\${ids[$idx]}" --session "$session_id"
943
962
  echo ""
944
963
  read -n 1 -s -r -p "Press a key to close."
945
964
  `;
@@ -989,9 +1008,9 @@ nvim "$tmpfile"
989
1008
  body=$(grep -v '^[[:space:]]*#' "$tmpfile" | sed '/^[[:space:]]*$/d')
990
1009
 
991
1010
  if [ -z "$body" ]; then
992
- exec sisyphus session resume "$session_id"
1011
+ exec sis session resume "$session_id"
993
1012
  else
994
- exec sisyphus session resume "$session_id" "$body"
1013
+ exec sis session resume "$session_id" "$body"
995
1014
  fi
996
1015
  `;
997
1016
  var ROLLBACK_SESSION_SCRIPT = `#!/bin/bash
@@ -1018,7 +1037,7 @@ if [ "$cycle_input" -lt 1 ]; then
1018
1037
  exit 0
1019
1038
  fi
1020
1039
 
1021
- sisyphus session rollback "$session_id" "$cycle_input"
1040
+ sis session rollback "$session_id" "$cycle_input"
1022
1041
  echo ""
1023
1042
  echo "Rolled back to cycle $cycle_input \u2014 use [C-s S r] to resume."
1024
1043
  read -n 1 -s -r -p "Press a key to close."
@@ -1055,12 +1074,12 @@ fi
1055
1074
 
1056
1075
  # Fallback: orchestrator window is gone. Open last claude session in a popup.
1057
1076
  state="$cwd/.sisyphus/sessions/$session_id/state.json"
1058
- [ ! -f "$state" ] && { tmux display-message "Window dead and no state.json \u2014 try sisyphus session resume"; exit 0; }
1077
+ [ ! -f "$state" ] && { tmux display-message "Window dead and no state.json \u2014 try sis session resume"; exit 0; }
1059
1078
 
1060
1079
  claude_sid=$(jq -r '[.orchestratorCycles[].claudeSessionId] | last // empty' "$state")
1061
1080
 
1062
1081
  if [ -z "$claude_sid" ]; then
1063
- tmux display-message "No orchestrator claude session id found \u2014 try sisyphus session resume"
1082
+ tmux display-message "No orchestrator claude session id found \u2014 try sis session resume"
1064
1083
  exit 0
1065
1084
  fi
1066
1085
 
@@ -1082,7 +1101,7 @@ nvim "$tmpfile"
1082
1101
  body=$(grep -v '^[[:space:]]*#' "$tmpfile" | sed '/^[[:space:]]*$/d')
1083
1102
  [ -z "$body" ] && exit 0
1084
1103
 
1085
- exec sisyphus agent spawn --session "$session_id" --name "agent" --instruction "$body"
1104
+ exec sis agent spawn --session "$session_id" --name "agent" --instruction "$body"
1086
1105
  `;
1087
1106
  var SEARCH_REPORTS_SCRIPT = `#!/bin/bash
1088
1107
  # fzf over reports/*.md across all sessions for the current cwd.
@@ -1121,12 +1140,12 @@ fi
1121
1140
  `;
1122
1141
  var JUMP_TO_PANE_SCRIPT = `#!/bin/bash
1123
1142
  # Pick a sisyphus agent and jump to its tmux pane.
1124
- # Assumes macOS (pbcopy, fzf optional). Requires \`sisyphus status --json\`.
1143
+ # Assumes macOS (pbcopy, fzf optional). Requires \`sis status --json\`.
1125
1144
  ${SESSION_RESOLVE}
1126
1145
 
1127
1146
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
1128
1147
 
1129
- agents_json=$(sisyphus status "$session_id" --json 2>/dev/null)
1148
+ agents_json=$(sis status "$session_id" --json 2>/dev/null)
1130
1149
  if [ -z "$agents_json" ]; then
1131
1150
  echo "Failed to read session status"; sleep 1; exit 1
1132
1151
  fi
@@ -1167,12 +1186,12 @@ tmux select-pane -t "$target_pane"
1167
1186
  `;
1168
1187
  var MSG_AGENT_SCRIPT = `#!/bin/bash
1169
1188
  # Pick a sisyphus agent and send it a message via nvim.
1170
- # Assumes macOS (fzf optional). Requires \`sisyphus status --json\` and \`--agent\` on message.
1189
+ # Assumes macOS (fzf optional). Requires \`sis status --json\` and \`--agent\` on message.
1171
1190
  ${SESSION_RESOLVE}
1172
1191
 
1173
1192
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
1174
1193
 
1175
- agents_json=$(sisyphus status "$session_id" --json 2>/dev/null)
1194
+ agents_json=$(sis status "$session_id" --json 2>/dev/null)
1176
1195
  if [ -z "$agents_json" ]; then
1177
1196
  echo "Failed to read session status"; sleep 1; exit 1
1178
1197
  fi
@@ -1205,16 +1224,16 @@ tmpfile=$(mktemp /tmp/sisyphus-msg-agent-XXXX.md)
1205
1224
  trap 'rm -f "$tmpfile"' EXIT
1206
1225
  nvim "$tmpfile"
1207
1226
  grep -q '[^[:space:]]' "$tmpfile" || exit 0
1208
- exec sisyphus message --session "$session_id" --agent "\${ids[$idx]}" "$(cat "$tmpfile")"
1227
+ exec sis message --session "$session_id" --agent "\${ids[$idx]}" "$(cat "$tmpfile")"
1209
1228
  `;
1210
1229
  var RERUN_AGENT_SCRIPT = `#!/bin/bash
1211
1230
  # Pick a sisyphus agent and spawn a retry with its original instruction.
1212
- # Assumes macOS (fzf optional). Requires \`sisyphus status --json\`.
1231
+ # Assumes macOS (fzf optional). Requires \`sis status --json\`.
1213
1232
  ${SESSION_RESOLVE}
1214
1233
 
1215
1234
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
1216
1235
 
1217
- agents_json=$(sisyphus status "$session_id" --json 2>/dev/null)
1236
+ agents_json=$(sis status "$session_id" --json 2>/dev/null)
1218
1237
  if [ -z "$agents_json" ]; then
1219
1238
  echo "Failed to read session status"; sleep 1; exit 1
1220
1239
  fi
@@ -1256,11 +1275,11 @@ if [ "\${#instr}" -lt 20 ]; then
1256
1275
  exit 1
1257
1276
  fi
1258
1277
 
1259
- exec sisyphus agent spawn --session "$session_id" --agent-type "\${atypes[$idx]}" --name "\${anames[$idx]}-retry-$(date +%s)" --instruction "$instr"
1278
+ exec sis agent spawn --session "$session_id" --agent-type "\${atypes[$idx]}" --name "\${anames[$idx]}-retry-$(date +%s)" --instruction "$instr"
1260
1279
  `;
1261
1280
  var OPEN_CLAUDE_AGENT_SCRIPT = `#!/bin/bash
1262
1281
  # Pick a sisyphus agent or orchestrator cycle and resume its Claude session.
1263
- # Assumes macOS (fzf optional). Requires \`sisyphus status --json\`.
1282
+ # Assumes macOS (fzf optional). Requires \`sis status --json\`.
1264
1283
  ${SESSION_RESOLVE}
1265
1284
 
1266
1285
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
@@ -1268,7 +1287,7 @@ command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
1268
1287
  state="$cwd/.sisyphus/sessions/$session_id/state.json"
1269
1288
  [ ! -f "$state" ] && { echo "No state.json for this session"; sleep 1; exit 1; }
1270
1289
 
1271
- agents_json=$(sisyphus status "$session_id" --json 2>/dev/null)
1290
+ agents_json=$(sis status "$session_id" --json 2>/dev/null)
1272
1291
  if [ -z "$agents_json" ]; then
1273
1292
  echo "Failed to read session status"; sleep 1; exit 1
1274
1293
  fi
@@ -1308,12 +1327,12 @@ cd "$cwd" && exec claude --resume "$cid"
1308
1327
  var TAIL_AGENT_LOGS_SCRIPT = `#!/bin/bash
1309
1328
  # Pick a sisyphus agent and view its tmux pane scrollback (last 2000 lines) in less.
1310
1329
  # Uses tmux capture-pane \u2014 no tail -f, no pipe-pane side effects.
1311
- # Assumes macOS (fzf optional). Requires \`sisyphus status --json\`.
1330
+ # Assumes macOS (fzf optional). Requires \`sis status --json\`.
1312
1331
  ${SESSION_RESOLVE}
1313
1332
 
1314
1333
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
1315
1334
 
1316
- agents_json=$(sisyphus status "$session_id" --json 2>/dev/null)
1335
+ agents_json=$(sis status "$session_id" --json 2>/dev/null)
1317
1336
  if [ -z "$agents_json" ]; then
1318
1337
  echo "Failed to read session status"; sleep 1; exit 1
1319
1338
  fi
@@ -1350,12 +1369,12 @@ tmux capture-pane -t "$target_pane" -p -S -2000 | less +G
1350
1369
  `;
1351
1370
  var KILL_AGENT_SCRIPT = `#!/bin/bash
1352
1371
  # Pick a sisyphus agent and kill it (with red confirmation prompt).
1353
- # Assumes macOS (fzf optional). Requires \`sisyphus status --json\` and \`sisyphus agent kill\`.
1372
+ # Assumes macOS (fzf optional). Requires \`sis status --json\` and \`sis agent kill\`.
1354
1373
  ${SESSION_RESOLVE}
1355
1374
 
1356
1375
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
1357
1376
 
1358
- agents_json=$(sisyphus status "$session_id" --json 2>/dev/null)
1377
+ agents_json=$(sis status "$session_id" --json 2>/dev/null)
1359
1378
  if [ -z "$agents_json" ]; then
1360
1379
  echo "Failed to read session status"; sleep 1; exit 1
1361
1380
  fi
@@ -1387,18 +1406,18 @@ fi
1387
1406
  printf '\\033[31mKill %s? (yes/no): \\033[0m' "\${ids[$idx]}"
1388
1407
  read -r answer
1389
1408
  [ "$answer" = "yes" ] || exit 0
1390
- sisyphus agent kill "\${ids[$idx]}" --session "$session_id"
1409
+ sis agent kill "\${ids[$idx]}" --session "$session_id"
1391
1410
  echo ""
1392
1411
  read -n 1 -s -r -p "Press a key to close."
1393
1412
  `;
1394
1413
  var COPY_AGENT_ID_SCRIPT = `#!/bin/bash
1395
1414
  # Pick a sisyphus agent and copy its ID to clipboard.
1396
- # Assumes macOS (pbcopy, fzf optional). Requires \`sisyphus status --json\`.
1415
+ # Assumes macOS (pbcopy, fzf optional). Requires \`sis status --json\`.
1397
1416
  ${SESSION_RESOLVE}
1398
1417
 
1399
1418
  command -v jq &>/dev/null || { echo "jq required"; sleep 1; exit 1; }
1400
1419
 
1401
- agents_json=$(sisyphus status "$session_id" --json 2>/dev/null)
1420
+ agents_json=$(sis status "$session_id" --json 2>/dev/null)
1402
1421
  if [ -z "$agents_json" ]; then
1403
1422
  echo "Failed to read session status"; sleep 1; exit 1
1404
1423
  fi
@@ -1473,10 +1492,10 @@ tmux display-message "Copied session ID"
1473
1492
  `;
1474
1493
  var COPY_CONTEXT_SCRIPT = `#!/bin/bash
1475
1494
  # Copy the session context XML to clipboard.
1476
- # Assumes macOS (pbcopy). Requires \`sisyphus session context\`.
1495
+ # Assumes macOS (pbcopy). Requires \`sis session context\`.
1477
1496
  ${SESSION_RESOLVE}
1478
1497
 
1479
- sisyphus session context "$session_id" --cwd "$cwd" | pbcopy
1498
+ sis session context "$session_id" --cwd "$cwd" | pbcopy
1480
1499
  tmux display-message "Copied session context (XML)"
1481
1500
  `;
1482
1501
  var EDIT_CONTEXT_FILE_SCRIPT = `#!/bin/bash
@@ -1537,7 +1556,7 @@ if [ "\${#instruction}" -lt 20 ]; then
1537
1556
  fi
1538
1557
 
1539
1558
  name="explore-$(date +%s)"
1540
- sisyphus agent spawn \\
1559
+ sis agent spawn \\
1541
1560
  --agent-type sisyphus:explore \\
1542
1561
  --name "$name" \\
1543
1562
  --session "$session_id" \\
@@ -1571,7 +1590,7 @@ if [ "\${#instruction}" -lt 20 ]; then
1571
1590
  fi
1572
1591
 
1573
1592
  name="debug-$(date +%s)"
1574
- sisyphus agent spawn \\
1593
+ sis agent spawn \\
1575
1594
  --agent-type sisyphus:debug \\
1576
1595
  --name "$name" \\
1577
1596
  --session "$session_id" \\
@@ -1619,7 +1638,7 @@ args=()
1619
1638
  [ -n "$clone_name" ] && args+=(--name "$clone_name")
1620
1639
  [ "$copy_strategy" = "y" ] || [ "$copy_strategy" = "Y" ] && args+=(--strategy)
1621
1640
 
1622
- sisyphus session clone "\${args[@]}" "$goal"
1641
+ sis session clone "\${args[@]}" "$goal"
1623
1642
  exit_code=$?
1624
1643
  read -n 1 -s -r -p "Press a key to close."
1625
1644
  exit $exit_code
@@ -1627,12 +1646,12 @@ exit $exit_code
1627
1646
  var HISTORY_SCRIPT = `#!/bin/bash
1628
1647
  # Show rich session detail (history command's per-session view) in a popup.
1629
1648
  ${SESSION_RESOLVE}
1630
- sisyphus admin history "$session_id" 2>&1 | less -R
1649
+ sis admin history "$session_id" 2>&1 | less -R
1631
1650
  `;
1632
1651
  var RECONNECT_SCRIPT = `#!/bin/bash
1633
1652
  # Reconnect daemon to an orphaned tmux session for the current cwd.
1634
1653
  ${SESSION_RESOLVE}
1635
- sisyphus session reconnect "$session_id"
1654
+ sis session reconnect "$session_id"
1636
1655
  exit_code=$?
1637
1656
  read -n 1 -s -r -p "Press a key to close."
1638
1657
  exit $exit_code
@@ -1640,7 +1659,7 @@ exit $exit_code
1640
1659
  var OPEN_SCRATCH_SCRIPT = `#!/bin/bash
1641
1660
  # Open a standalone Claude scratch window in the home tmux session for this cwd.
1642
1661
  # scratch resolves the home session itself via @sisyphus_cwd; no session_id needed.
1643
- exec sisyphus admin scratch
1662
+ exec sis admin scratch
1644
1663
  `;
1645
1664
  function installScript(name, content) {
1646
1665
  mkdirSync(join2(globalDir(), "bin"), { recursive: true });
@@ -1717,7 +1736,22 @@ function getExistingBinding(key, table = "root") {
1717
1736
  function isSisyphusBinding(binding) {
1718
1737
  return binding.includes("sisyphus");
1719
1738
  }
1720
- function setupTmuxKeybind(cycleKey = DEFAULT_CYCLE_KEY, prefixKey = DEFAULT_PREFIX_KEY) {
1739
+ async function confirmConfAppend(userConf, line) {
1740
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
1741
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1742
+ return new Promise((resolve12) => {
1743
+ const question = `
1744
+ Sisyphus needs to append one line to ${userConf} so its tmux keybindings persist:
1745
+ ${line}
1746
+
1747
+ Append it now? (y/N) `;
1748
+ rl.question(question, (answer) => {
1749
+ rl.close();
1750
+ resolve12(answer.trim().toLowerCase() === "y");
1751
+ });
1752
+ });
1753
+ }
1754
+ async function setupTmuxKeybind(cycleKey = DEFAULT_CYCLE_KEY, prefixKey = DEFAULT_PREFIX_KEY, opts = {}) {
1721
1755
  installAllScripts();
1722
1756
  if (!tmuxVersionAtLeast(3, 2)) {
1723
1757
  let version = "unknown";
@@ -1735,7 +1769,7 @@ function setupTmuxKeybind(cycleKey = DEFAULT_CYCLE_KEY, prefixKey = DEFAULT_PREF
1735
1769
  if (existing !== null && !isSisyphusBinding(existing)) {
1736
1770
  return {
1737
1771
  status: "conflict",
1738
- message: `Tmux key ${key} (${label}) is already bound to something else. Run "sisyphus admin setup-keybind <key>" to use a different key.`,
1772
+ message: `Tmux key ${key} (${label}) is already bound to something else. Run "sis admin setup-keybind <key>" to use a different key.`,
1739
1773
  existingBinding: existing
1740
1774
  };
1741
1775
  }
@@ -1757,14 +1791,22 @@ ${bindings.join("\n")}
1757
1791
  const userConf = userTmuxConfPath();
1758
1792
  const markedSourceLine = `source-file ${confPath} ${SISYPHUS_CONF_MARKER}`;
1759
1793
  let persistedToConf = false;
1794
+ let appendDeclined = false;
1760
1795
  if (userConf !== null) {
1761
1796
  const contents = readFileSync(userConf, "utf8");
1762
- if (!contents.includes(confPath)) {
1763
- const separator = contents.endsWith("\n") ? "" : "\n";
1764
- writeFileSync(userConf, `${contents}${separator}${markedSourceLine}
1797
+ if (contents.includes(confPath)) {
1798
+ persistedToConf = true;
1799
+ } else {
1800
+ const shouldAppend = opts.assumeYes ? true : await confirmConfAppend(userConf, markedSourceLine);
1801
+ if (shouldAppend) {
1802
+ const separator = contents.endsWith("\n") ? "" : "\n";
1803
+ writeFileSync(userConf, `${contents}${separator}${markedSourceLine}
1765
1804
  `, "utf8");
1805
+ persistedToConf = true;
1806
+ } else {
1807
+ appendDeclined = true;
1808
+ }
1766
1809
  }
1767
- persistedToConf = true;
1768
1810
  }
1769
1811
  try {
1770
1812
  for (const b of bindings) {
@@ -1772,6 +1814,16 @@ ${bindings.join("\n")}
1772
1814
  }
1773
1815
  } catch {
1774
1816
  }
1817
+ if (appendDeclined && userConf !== null) {
1818
+ return {
1819
+ status: "conf-modification-declined",
1820
+ message: `Tmux keybindings applied to the live session, but not persisted.
1821
+ To persist them, add this line to ${userConf}:
1822
+ ${markedSourceLine}`,
1823
+ manualLine: markedSourceLine,
1824
+ userConf
1825
+ };
1826
+ }
1775
1827
  if (getExistingBinding(cycleKey) !== null && isSisyphusBinding(getExistingBinding(cycleKey))) {
1776
1828
  return {
1777
1829
  status: "already-installed",
@@ -2061,7 +2113,7 @@ async function ensureDaemonInstalled() {
2061
2113
  const plist = generatePlist(nodePath, daemonPath, logPath);
2062
2114
  writeFileSync2(plistPath(), plist, "utf8");
2063
2115
  execSync3(`launchctl load -w ${plistPath()}`);
2064
- const keybindResult = setupTmuxKeybind();
2116
+ const keybindResult = await setupTmuxKeybind();
2065
2117
  await ensureRequiredPlugins(process.cwd());
2066
2118
  printGettingStarted(keybindResult, sisyphusPlugin);
2067
2119
  }
@@ -2102,24 +2154,26 @@ function printGettingStarted(keybindResult, sisyphusPlugin) {
2102
2154
  lines.push(`Tmux keybind: ${keybindResult.message}`, "");
2103
2155
  } else if (keybindResult.status === "conflict") {
2104
2156
  lines.push(`Keybind: ${keybindResult.message}`, "");
2157
+ } else if (keybindResult.status === "conf-modification-declined") {
2158
+ lines.push(keybindResult.message, "");
2105
2159
  }
2106
2160
  if (sisyphusPlugin.installed && sisyphusPlugin.autoInstalled) {
2107
2161
  lines.push(`Sisyphus plugin installed: sisyphus@sisyphus \u2192 ${sisyphusPlugin.installPath}`, "");
2108
2162
  } else if (!sisyphusPlugin.installed) {
2109
- lines.push("Sisyphus plugin: failed to install (run `sisyphus admin setup` to retry; needs `claude` CLI)", "");
2163
+ lines.push("Sisyphus plugin: failed to install (run `sis admin setup` to retry; needs `claude` CLI)", "");
2110
2164
  }
2111
2165
  lines.push(
2112
- "Run `sisyphus admin getting-started` for a complete usage guide.",
2166
+ "Run `sis admin getting-started` for a complete usage guide.",
2113
2167
  ""
2114
2168
  );
2115
2169
  console.log(lines.join("\n"));
2116
2170
  }
2117
2171
  function testConnection() {
2118
- return new Promise((resolve11, reject) => {
2172
+ return new Promise((resolve12, reject) => {
2119
2173
  const sock = connect2(socketPath());
2120
2174
  sock.on("connect", () => {
2121
2175
  sock.destroy();
2122
- resolve11();
2176
+ resolve12();
2123
2177
  });
2124
2178
  sock.on("error", (err) => {
2125
2179
  sock.destroy();
@@ -2128,7 +2182,7 @@ function testConnection() {
2128
2182
  });
2129
2183
  }
2130
2184
  function sleep(ms) {
2131
- return new Promise((resolve11) => setTimeout(resolve11, ms));
2185
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
2132
2186
  }
2133
2187
  async function waitForDaemon(maxWaitMs = 6e3) {
2134
2188
  const start = Date.now();
@@ -2164,7 +2218,7 @@ function rawSend2(request, timeoutMs = 1e4) {
2164
2218
  return rawSend(request, timeoutMs);
2165
2219
  }
2166
2220
  async function sendRequest(request, timeoutMs) {
2167
- const sleep2 = (ms) => new Promise((resolve11) => setTimeout(resolve11, ms));
2221
+ const sleep2 = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
2168
2222
  const MAX_ATTEMPTS = 5;
2169
2223
  const RETRY_DELAY_MS = 2e3;
2170
2224
  let installedDaemon = false;
@@ -2218,7 +2272,7 @@ async function sendRequest(request, timeoutMs) {
2218
2272
  }
2219
2273
  lines.push(
2220
2274
  "",
2221
- " Diagnose: sisyphus admin doctor",
2275
+ " Diagnose: sis admin doctor",
2222
2276
  " Logs: tail -f ~/.sisyphus/daemon.log"
2223
2277
  );
2224
2278
  throw new Error(lines.join("\n"));
@@ -2252,6 +2306,9 @@ function getTmuxSessionInfo() {
2252
2306
  function shellQuote(s) {
2253
2307
  return `'${s.replace(/'/g, "'\\''")}'`;
2254
2308
  }
2309
+ function validateRepoName(repo) {
2310
+ return !repo.includes("/") && !repo.includes("\\") && !repo.includes("..");
2311
+ }
2255
2312
  function escapeAppleScript(s) {
2256
2313
  return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2257
2314
  }
@@ -2353,7 +2410,7 @@ function registerStart(program2) {
2353
2410
  console.log(`Tmux session: ${tmuxSessionName}`);
2354
2411
  console.log(` tmux attach -t ${tmuxSessionName}`);
2355
2412
  }
2356
- console.log(`Monitor: sisyphus status ${sessionId}`);
2413
+ console.log(`Monitor: sis status ${sessionId}`);
2357
2414
  return;
2358
2415
  }
2359
2416
  let tmuxSession;
@@ -2397,7 +2454,7 @@ function registerStart(program2) {
2397
2454
  if (!process.env["TMUX"]) {
2398
2455
  attachToTmuxSession(tmuxSession);
2399
2456
  }
2400
- console.log(`Monitor: sisyphus status ${sessionId}`);
2457
+ console.log(`Monitor: sis status ${sessionId}`);
2401
2458
  });
2402
2459
  }
2403
2460
 
@@ -2720,7 +2777,7 @@ function registerList(program2) {
2720
2777
  if (sessions.length === 0) {
2721
2778
  if (filtered && totalCount && totalCount > 0) {
2722
2779
  console.log(`No sessions in this project. ${totalCount} session(s) in other projects.`);
2723
- console.log(`${DIM2}Run ${RESET2}sisyphus list --all${DIM2} to show all.${RESET2}`);
2780
+ console.log(`${DIM2}Run ${RESET2}sis list --all${DIM2} to show all.${RESET2}`);
2724
2781
  } else {
2725
2782
  console.log("No sessions");
2726
2783
  }
@@ -2738,7 +2795,7 @@ function registerList(program2) {
2738
2795
  if (filtered && totalCount && totalCount > sessions.length) {
2739
2796
  const otherCount = totalCount - sessions.length;
2740
2797
  console.log(`
2741
- ${DIM2}${otherCount} more session(s) in other projects. Run ${RESET2}sisyphus list --all${DIM2} to show all.${RESET2}`);
2798
+ ${DIM2}${otherCount} more session(s) in other projects. Run ${RESET2}sis list --all${DIM2} to show all.${RESET2}`);
2742
2799
  }
2743
2800
  } else {
2744
2801
  console.error(`Error: ${response.error}`);
@@ -3073,17 +3130,21 @@ function inlineBodyPath(deckPath, bodyPath) {
3073
3130
  const deckDir = dirname2(deckPath);
3074
3131
  const joined = resolve2(deckDir, bodyPath);
3075
3132
  if (!existsSync5(joined)) {
3076
- throw new Error(`bodyPath does not exist: ${bodyPath}`);
3133
+ throw new Error(
3134
+ `bodyPath does not exist: '${bodyPath}' (resolved against deck dir '${deckDir}'). bodyPath is interpreted relative to the deck JSON's directory; place the body file there and use a relative path (e.g. "completion-summary.md").`
3135
+ );
3077
3136
  }
3078
3137
  const stat = lstatSync(joined);
3079
3138
  if (!stat.isFile()) {
3080
- throw new Error(`bodyPath must be a regular file: ${bodyPath}`);
3139
+ throw new Error(`bodyPath must be a regular file (not a symlink, directory, or special file): ${bodyPath}`);
3081
3140
  }
3082
3141
  const realResolved = realpathSync(joined);
3083
3142
  const realDeckDir = realpathSync(deckDir);
3084
3143
  const prefix = realDeckDir + sep;
3085
3144
  if (realResolved !== realDeckDir && !realResolved.startsWith(prefix)) {
3086
- throw new Error(`bodyPath escapes deck directory (outside): ${bodyPath}`);
3145
+ throw new Error(
3146
+ `bodyPath '${bodyPath}' escapes the deck's directory ('${realDeckDir}'). bodyPath is resolved relative to the deck JSON file and must stay inside its directory (no '..', absolute paths pointing elsewhere, or symlinks out). Fix: write the deck JSON next to the body file (e.g. both inside $SISYPHUS_SESSION_DIR/context/) and use a relative path like "completion-summary.md".`
3147
+ );
3087
3148
  }
3088
3149
  return readFileSync8(joined, "utf-8");
3089
3150
  }
@@ -3464,12 +3525,47 @@ Posts a deck of questions to the user's dashboard inbox. They walk through it an
3464
3525
 
3465
3526
  The CLI always blocks until the user answers (which can take 10+ minutes).
3466
3527
 
3467
- - **Orchestrator:** invoke synchronously so the orchestrator's pane stays alive while the bash blocks. Daemon refuses \`sisyphus orch yield\` while orchestrator owns a pending deck; foreground is the supported pattern.
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.
3468
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.
3469
3530
 
3470
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.
3471
3532
 
3472
- Deck JSON: an object with \`interactions: [{ id, title, options, kind?, allowFreetext?, body? | bodyPath?, ... }]\`. Validation errors at submit are precise \u2014 trust them.
3533
+ DECK JSON SCHEMA
3534
+ { "title"?: string, "interactions": Interaction[] } // interactions[] non-empty
3535
+
3536
+ Interaction:
3537
+ id string, /^[A-Za-z0-9_-]+$/, max 64 chars, unique within deck
3538
+ title string (required, non-empty)
3539
+ subtitle? string
3540
+ body? string // markdown rendered in dashboard
3541
+ bodyPath? string // path RELATIVE to the deck JSON's directory
3542
+ // and must resolve INSIDE that directory
3543
+ // (no '..', no symlinks out, no absolute
3544
+ // paths pointing elsewhere). Mutually
3545
+ // exclusive with 'body'. To use bodyPath,
3546
+ // write the deck JSON next to the markdown
3547
+ // file (e.g. both in
3548
+ // $SISYPHUS_SESSION_DIR/context/) and pass
3549
+ // a basename like "summary.md".
3550
+ kind? "notify" | "validation" | "decision" | "context" | "error"
3551
+ // display hint for inbox icon/sort weight.
3552
+ // No other values accepted.
3553
+ options Option[] // 2\u20134 options recommended (see humanloop)
3554
+ allowFreetext? boolean
3555
+ freetextLabel? string
3556
+
3557
+ Option:
3558
+ id string (required)
3559
+ label string (required)
3560
+ description? string
3561
+ shortcut? string
3562
+
3563
+ OUTPUT
3564
+ On answer, stdout is one line of JSON:
3565
+ { "responses": [{ "id", "selectedOptionId"?, "freetext"? }, ...], "completedAt" }
3566
+ Branch on each response by its interaction \`id\`.
3567
+
3568
+ Validation errors at submit are precise \u2014 read them, don't guess.
3473
3569
  `).action(async (file, opts) => {
3474
3570
  if (!file) {
3475
3571
  ask.help();
@@ -3739,7 +3835,7 @@ function registerComplete(program2) {
3739
3835
  console.log("Session completed.");
3740
3836
  console.log(`
3741
3837
  To keep working in this session:`);
3742
- console.log(` sisyphus session continue # reactivate session and clear roadmap for new work`);
3838
+ console.log(` sis session continue # reactivate session and clear roadmap for new work`);
3743
3839
  } else {
3744
3840
  console.error(`Error: ${response.error}`);
3745
3841
  process.exit(1);
@@ -3761,7 +3857,7 @@ function registerRollback(program2) {
3761
3857
  if (response.ok) {
3762
3858
  const data = response.data;
3763
3859
  console.log(`Session ${sessionId} rolled back to cycle ${data.restoredToCycle}.`);
3764
- console.log(`Session is now paused. Use 'sisyphus session resume ${sessionId}' to respawn the orchestrator.`);
3860
+ console.log(`Session is now paused. Use 'sis session resume ${sessionId}' to respawn the orchestrator.`);
3765
3861
  } else {
3766
3862
  console.error(`Error: ${response.error}`);
3767
3863
  process.exit(1);
@@ -3797,7 +3893,7 @@ function registerClone(program2) {
3797
3893
  }
3798
3894
  const agentId = process.env.SISYPHUS_AGENT_ID;
3799
3895
  if (agentId !== "orchestrator") {
3800
- console.error("Error: clone can only be called by the orchestrator. Use sisyphus message to ask the orchestrator to clone.");
3896
+ console.error("Error: clone can only be called by the orchestrator. Use sis message to ask the orchestrator to clone.");
3801
3897
  process.exit(1);
3802
3898
  }
3803
3899
  const request = {
@@ -4037,12 +4133,12 @@ import { join as join14, resolve as resolve5 } from "path";
4037
4133
  // src/cli/stdin.ts
4038
4134
  function readStdin2() {
4039
4135
  if (process.stdin.isTTY) return Promise.resolve(null);
4040
- return new Promise((resolve11, reject) => {
4136
+ return new Promise((resolve12, reject) => {
4041
4137
  const chunks = [];
4042
4138
  process.stdin.on("data", (chunk) => chunks.push(chunk));
4043
4139
  process.stdin.on("end", () => {
4044
4140
  const text = Buffer.concat(chunks).toString("utf-8").trim();
4045
- resolve11(text || null);
4141
+ resolve12(text || null);
4046
4142
  });
4047
4143
  process.stdin.on("error", reject);
4048
4144
  });
@@ -4198,11 +4294,11 @@ function registerSpawn(program2) {
4198
4294
  if (response.ok) {
4199
4295
  const agentId = response.data?.agentId;
4200
4296
  console.log(`Agent spawned: ${agentId}`);
4201
- console.log(`Tip: \`sisyphus agent await ${agentId}\` blocks for the report and consumes it inline (won't appear in next cycle).`);
4202
- console.log("Run `sisyphus orch yield` when done spawning agents.");
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.");
4203
4299
  } else {
4204
4300
  console.error(`Error: ${response.error}`);
4205
- if (response.error?.includes("Unknown session")) console.error("Hint: run `sisyphus list` to see active sessions.");
4301
+ if (response.error?.includes("Unknown session")) console.error("Hint: run `sis list` to see active sessions.");
4206
4302
  process.exit(1);
4207
4303
  }
4208
4304
  });
@@ -4819,11 +4915,11 @@ function printResults(result, daemonOk, keybindMsg) {
4819
4915
  }
4820
4916
  }
4821
4917
  console.log("");
4822
- console.log("Run 'sisyphus admin getting-started' for a usage guide.");
4918
+ console.log("Run 'sis admin getting-started' for a usage guide.");
4823
4919
  console.log("");
4824
4920
  }
4825
4921
  function registerSetup(program2) {
4826
- program2.command("setup").description("One-time setup: install dependencies, daemon, keybindings, and commands").action(async () => {
4922
+ program2.command("setup").description("One-time setup: install dependencies, daemon, keybindings, and commands").option("-y, --yes", "Skip confirmation prompts (e.g. before modifying ~/.tmux.conf)").action(async (opts) => {
4827
4923
  const result = runOnboarding();
4828
4924
  let daemonOk = false;
4829
4925
  try {
@@ -4832,7 +4928,11 @@ function registerSetup(program2) {
4832
4928
  } catch {
4833
4929
  daemonOk = isInstalled();
4834
4930
  }
4835
- const keybindResult = setupTmuxKeybind();
4931
+ const keybindResult = await setupTmuxKeybind(
4932
+ DEFAULT_CYCLE_KEY,
4933
+ DEFAULT_PREFIX_KEY,
4934
+ { assumeYes: opts.yes }
4935
+ );
4836
4936
  let keybindMsg;
4837
4937
  if (keybindResult.status === "installed" || keybindResult.status === "already-installed") {
4838
4938
  keybindMsg = `${DEFAULT_CYCLE_KEY} cycle, ${DEFAULT_PREFIX_KEY} prefix (h=dashboard, x=kill)`;
@@ -4845,9 +4945,9 @@ function registerSetup(program2) {
4845
4945
 
4846
4946
  // src/cli/commands/setup-keybind.ts
4847
4947
  function registerSetupKeybind(program2) {
4848
- program2.command("setup-keybind [cycle-key]").description("Install sisyphus tmux keybindings (default: M-s cycle, C-s prefix)").action(async (key) => {
4948
+ program2.command("setup-keybind [cycle-key]").description("Install sisyphus tmux keybindings (default: M-s cycle, C-s prefix)").option("-y, --yes", "Skip confirmation prompt before modifying ~/.tmux.conf").action(async (key, opts) => {
4849
4949
  const resolvedKey = key ?? DEFAULT_CYCLE_KEY;
4850
- const result = setupTmuxKeybind(resolvedKey);
4950
+ const result = await setupTmuxKeybind(resolvedKey, void 0, { assumeYes: opts.yes });
4851
4951
  switch (result.status) {
4852
4952
  case "installed":
4853
4953
  console.log(result.message);
@@ -4861,20 +4961,56 @@ function registerSetupKeybind(program2) {
4861
4961
  console.log(` ${result.existingBinding}`);
4862
4962
  console.log("");
4863
4963
  console.log("Use a different key, e.g.:");
4864
- console.log(" sisyphus admin setup-keybind M-S");
4865
- console.log(" sisyphus admin setup-keybind M-w");
4866
- console.log(" sisyphus admin setup-keybind M-j");
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");
4867
4967
  break;
4868
4968
  case "unsupported-tmux":
4869
4969
  console.log(result.message);
4870
4970
  break;
4971
+ case "conf-modification-declined":
4972
+ console.log(result.message);
4973
+ console.log("");
4974
+ console.log("Re-run with --yes to skip the prompt.");
4975
+ break;
4871
4976
  }
4872
4977
  });
4873
4978
  }
4874
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
+
4875
5011
  // src/cli/commands/doctor.ts
4876
5012
  init_paths();
4877
- import { execSync as execSync11 } from "child_process";
5013
+ import { execSync as execSync12 } from "child_process";
4878
5014
  import { existsSync as existsSync15, statSync as statSync3 } from "fs";
4879
5015
  import { homedir as homedir9 } from "os";
4880
5016
  import { join as join16 } from "path";
@@ -4887,7 +5023,7 @@ function checkNodeVersion() {
4887
5023
  }
4888
5024
  function checkClaudeCli() {
4889
5025
  try {
4890
- execSync11("which claude", { stdio: "pipe" });
5026
+ execSync12("which claude", { stdio: "pipe" });
4891
5027
  return { name: "Claude CLI", status: "ok", detail: "Found on PATH" };
4892
5028
  } catch {
4893
5029
  return {
@@ -4900,7 +5036,7 @@ function checkClaudeCli() {
4900
5036
  }
4901
5037
  function checkGit() {
4902
5038
  try {
4903
- const version = execSync11("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
5039
+ const version = execSync12("git --version", { encoding: "utf-8", stdio: "pipe" }).trim();
4904
5040
  return { name: "git", status: "ok", detail: version };
4905
5041
  } catch {
4906
5042
  return { name: "git", status: "fail", detail: "Not found on PATH", fix: "Install git: https://git-scm.com/downloads" };
@@ -4908,7 +5044,7 @@ function checkGit() {
4908
5044
  }
4909
5045
  function checkTmuxVersion() {
4910
5046
  try {
4911
- const version = execSync11("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
5047
+ const version = execSync12("tmux -V", { encoding: "utf-8", stdio: "pipe" }).trim();
4912
5048
  const match = version.match(/(\d+\.\d+)/);
4913
5049
  if (!match) return { name: "tmux version", status: "warn", detail: `Could not parse version: ${version}` };
4914
5050
  const ver = parseFloat(match[1]);
@@ -4930,7 +5066,7 @@ function checkDaemonInstalled() {
4930
5066
  name: "Daemon plist",
4931
5067
  status: "fail",
4932
5068
  detail: "Not installed",
4933
- fix: 'Run any sisyphus command to auto-install, or: sisyphus start "test"'
5069
+ fix: 'Run any sis command to auto-install, or: sis start "test"'
4934
5070
  };
4935
5071
  }
4936
5072
  const pid = daemonPidPath();
@@ -4957,7 +5093,7 @@ function checkDaemonRunning() {
4957
5093
  }
4958
5094
  try {
4959
5095
  const sock = socketPath();
4960
- execSync11(`test -S "${sock}"`, { stdio: "pipe" });
5096
+ execSync12(`test -S "${sock}"`, { stdio: "pipe" });
4961
5097
  return { name: "Daemon process", status: "ok", detail: `Socket at ${sock}` };
4962
5098
  } catch {
4963
5099
  return {
@@ -4970,13 +5106,13 @@ function checkDaemonRunning() {
4970
5106
  }
4971
5107
  function checkTmux() {
4972
5108
  try {
4973
- execSync11("which tmux", { stdio: "pipe" });
5109
+ execSync12("which tmux", { stdio: "pipe" });
4974
5110
  } catch {
4975
5111
  const installHint = process.platform === "darwin" ? "brew install tmux" : "apt install tmux (Debian/Ubuntu) or your package manager";
4976
5112
  return { name: "tmux", status: "fail", detail: "Not found on PATH", fix: installHint };
4977
5113
  }
4978
5114
  try {
4979
- execSync11("tmux list-sessions", { stdio: "pipe" });
5115
+ execSync12("tmux list-sessions", { stdio: "pipe" });
4980
5116
  return { name: "tmux", status: "ok", detail: "Running" };
4981
5117
  } catch {
4982
5118
  return { name: "tmux", status: "warn", detail: "Installed but no server running" };
@@ -4989,7 +5125,7 @@ function checkCycleScript() {
4989
5125
  name: "Cycle script",
4990
5126
  status: "fail",
4991
5127
  detail: `Not found at ${path}`,
4992
- fix: "sisyphus admin setup-keybind"
5128
+ fix: "sis admin setup-keybind"
4993
5129
  };
4994
5130
  }
4995
5131
  try {
@@ -5020,7 +5156,7 @@ function checkTmuxKeybind() {
5020
5156
  name: `Tmux keybind (${DEFAULT_CYCLE_KEY})`,
5021
5157
  status: "fail",
5022
5158
  detail: "Not bound",
5023
- fix: "sisyphus admin setup-keybind"
5159
+ fix: "sis admin setup-keybind"
5024
5160
  };
5025
5161
  }
5026
5162
  if (isSisyphusBinding(existing)) {
@@ -5030,7 +5166,7 @@ function checkTmuxKeybind() {
5030
5166
  name: `Tmux keybind (${DEFAULT_CYCLE_KEY})`,
5031
5167
  status: "warn",
5032
5168
  detail: `Bound to something else: ${existing}`,
5033
- fix: "sisyphus admin setup-keybind M-S (or another free key)"
5169
+ fix: "sis admin setup-keybind M-S (or another free key)"
5034
5170
  };
5035
5171
  }
5036
5172
  function checkGlobalDir() {
@@ -5081,13 +5217,13 @@ function checkSisyphusPlugin() {
5081
5217
  name: "sisyphus@sisyphus plugin",
5082
5218
  status: "warn",
5083
5219
  detail: "Not installed (slash commands /sisyphus:begin, /sisyphus:autopsy, /sisyphus:configure-upload unavailable)",
5084
- fix: "sisyphus admin setup"
5220
+ fix: "sis admin setup"
5085
5221
  };
5086
5222
  }
5087
5223
  function checkTermrender() {
5088
5224
  if (isTermrenderAvailable()) {
5089
5225
  try {
5090
- const version = execSync11("termrender --version", { encoding: "utf-8", stdio: "pipe" }).trim();
5226
+ const version = execSync12("termrender --version", { encoding: "utf-8", stdio: "pipe" }).trim();
5091
5227
  return { name: "termrender", status: "ok", detail: version };
5092
5228
  } catch {
5093
5229
  return { name: "termrender", status: "ok", detail: "installed" };
@@ -5106,7 +5242,7 @@ function checkNvim() {
5106
5242
  return { name: "nvim", status: "warn", detail: "Not installed", fix };
5107
5243
  }
5108
5244
  try {
5109
- const version = execSync11("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
5245
+ const version = execSync12("nvim --version", { encoding: "utf-8", stdio: "pipe" }).split("\n")[0]?.replace("NVIM ", "");
5110
5246
  return { name: "nvim", status: "ok", detail: version ?? "installed" };
5111
5247
  } catch {
5112
5248
  return { name: "nvim", status: "ok", detail: "installed" };
@@ -5208,13 +5344,13 @@ function registerInit(program2) {
5208
5344
  }
5209
5345
 
5210
5346
  // src/cli/commands/uninstall.ts
5211
- import { createInterface } from "readline";
5347
+ import { createInterface as createInterface2 } from "readline";
5212
5348
  async function confirm(question) {
5213
- const rl = createInterface({ input: process.stdin, output: process.stdout });
5214
- return new Promise((resolve11) => {
5349
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
5350
+ return new Promise((resolve12) => {
5215
5351
  rl.question(question, (answer) => {
5216
5352
  rl.close();
5217
- resolve11(answer.trim().toLowerCase() === "y");
5353
+ resolve12(answer.trim().toLowerCase() === "y");
5218
5354
  });
5219
5355
  });
5220
5356
  }
@@ -5235,31 +5371,31 @@ function registerUninstall(program2) {
5235
5371
  // src/cli/commands/configure-upload.ts
5236
5372
  init_paths();
5237
5373
  import { chmodSync as chmodSync2, existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
5238
- import { createInterface as createInterface2 } from "readline";
5374
+ import { createInterface as createInterface3 } from "readline";
5239
5375
  import { dirname as dirname6 } from "path";
5240
5376
  async function readUrlFromInput(interactive) {
5241
5377
  if (interactive) {
5242
- return new Promise((resolve11) => {
5243
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
5378
+ return new Promise((resolve12) => {
5379
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
5244
5380
  rl.question("Paste the upload URL (with embedded ?token=): ", (answer) => {
5245
5381
  rl.close();
5246
- resolve11(answer.trim());
5382
+ resolve12(answer.trim());
5247
5383
  });
5248
5384
  });
5249
5385
  }
5250
- return new Promise((resolve11) => {
5386
+ return new Promise((resolve12) => {
5251
5387
  const chunks = [];
5252
5388
  process.stdin.setEncoding("utf-8");
5253
5389
  process.stdin.on("data", (chunk) => {
5254
5390
  chunks.push(chunk);
5255
5391
  });
5256
5392
  process.stdin.on("end", () => {
5257
- resolve11(chunks.join("").trim());
5393
+ resolve12(chunks.join("").trim());
5258
5394
  });
5259
5395
  });
5260
5396
  }
5261
5397
  function registerConfigureUpload(program2) {
5262
- 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 | sisyphus admin configure-upload --stdin)").action(async (urlArg, opts) => {
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) => {
5263
5399
  let rawUrl;
5264
5400
  const fromStdin = opts.stdin || urlArg === "-" || !urlArg && process.stdin.isTTY === false;
5265
5401
  const fromInteractive = !urlArg && !opts.stdin && process.stdin.isTTY === true;
@@ -5268,7 +5404,7 @@ function registerConfigureUpload(program2) {
5268
5404
  } else {
5269
5405
  rawUrl = urlArg;
5270
5406
  console.warn(
5271
- "warning: passing the token on argv exposes it via `ps` and shell history; pipe it on stdin instead: `pbpaste | sisyphus admin configure-upload --stdin`"
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`"
5272
5408
  );
5273
5409
  }
5274
5410
  let parsed;
@@ -5309,7 +5445,7 @@ function registerConfigureUpload(program2) {
5309
5445
  }
5310
5446
 
5311
5447
  // src/cli/commands/getting-started.ts
5312
- import { execSync as execSync12 } from "child_process";
5448
+ import { execSync as execSync13 } from "child_process";
5313
5449
  import { dirname as dirname7, join as join18 } from "path";
5314
5450
  import { fileURLToPath as fileURLToPath3 } from "url";
5315
5451
  function templatePath(name) {
@@ -5321,7 +5457,7 @@ function isClaudeCode() {
5321
5457
  function printNonClaudeMessage() {
5322
5458
  console.log(`
5323
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
5324
- \u2551 sisyphus admin getting-started \u2014 Interactive Tutorial \u2551
5460
+ \u2551 sis admin getting-started \u2014 Interactive Tutorial \u2551
5325
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
5326
5462
 
5327
5463
  This command provides an interactive tutorial best experienced
@@ -5329,11 +5465,11 @@ function printNonClaudeMessage() {
5329
5465
 
5330
5466
  To start:
5331
5467
  1. Open Claude Code: claude
5332
- 2. Run: sisyphus admin getting-started
5468
+ 2. Run: sis admin getting-started
5333
5469
 
5334
5470
  If you just want the quick reference, run:
5335
- sisyphus --help
5336
- sisyphus admin doctor
5471
+ sis --help
5472
+ sis admin doctor
5337
5473
  `);
5338
5474
  }
5339
5475
  function printStep0() {
@@ -5363,14 +5499,14 @@ This tutorial has 6 steps. Share this overview so the user knows what's coming a
5363
5499
 
5364
5500
  | Step | Topic | Command |
5365
5501
  |------|-------|---------|
5366
- | 0 | Entry & tmux gate (you are here) | \`sisyphus admin getting-started\` |
5502
+ | 0 | Entry & tmux gate (you are here) | \`sis admin getting-started\` |
5367
5503
  | 1 | Tmux basics \u2014 sessions, panes, navigation | \`--tutorial 1\` |
5368
5504
  | 2 | Nvim basics \u2014 open, save, quit (optional) | \`--tutorial 2\` |
5369
5505
  | 3 | Sisyphus concepts \u2014 session model & keybinds | \`--tutorial 3\` |
5370
5506
  | 4 | Live demo \u2014 launch and observe a real session | \`--tutorial 4\` |
5371
5507
  | 5 | What's next \u2014 real usage guidance & suggestions | \`--tutorial 5\` |
5372
5508
 
5373
- Tell the user they can skip to any step with \`sisyphus admin getting-started --tutorial <N>\`.
5509
+ Tell the user they can skip to any step with \`sis admin getting-started --tutorial <N>\`.
5374
5510
 
5375
5511
  ## Instructions for Claude
5376
5512
 
@@ -5380,8 +5516,8 @@ You are guiding a user through the Sisyphus interactive tutorial.
5380
5516
 
5381
5517
  Ask the user if they'd like the interactive walkthrough. If they decline, give this quick summary and stop:
5382
5518
 
5383
- > Sisyphus is a multi-agent orchestrator for Claude Code. Start a session with \`sisyphus start "task"\`,
5384
- > monitor with \`sisyphus dashboard\`, and check health with \`sisyphus admin doctor\`.
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\`.
5385
5521
 
5386
5522
  ### If they want the tutorial:
5387
5523
 
@@ -5403,17 +5539,17 @@ Ask the user if they'd like the interactive walkthrough. If they decline, give t
5403
5539
  - Tell them to install tmux:
5404
5540
  - macOS: \`brew install tmux\`
5405
5541
  - Linux: \`apt install tmux\` or their package manager
5406
- - After install, re-run: \`sisyphus admin getting-started --tutorial 0\` to verify
5542
+ - After install, re-run: \`sis admin getting-started --tutorial 0\` to verify
5407
5543
 
5408
5544
  **Case 2: tmux installed but NOT in a tmux session (inTmux: false)**
5409
5545
  - Tell the user they need to be inside a tmux session for the tutorial
5410
5546
  - Have them run: \`tmux new-session\`
5411
5547
  - Then resume the conversation with Claude in the new tmux session: \`claude\`
5412
- - Then re-run: \`sisyphus admin getting-started --tutorial 0\` to verify
5548
+ - Then re-run: \`sis admin getting-started --tutorial 0\` to verify
5413
5549
 
5414
5550
  **Case 3: In tmux (inTmux: true)**
5415
5551
  - Tell the user they're all set \u2014 tmux is running
5416
- - Proceed by running: \`sisyphus admin getting-started --tutorial 1\`
5552
+ - Proceed by running: \`sis admin getting-started --tutorial 1\`
5417
5553
  </claude-instructions>
5418
5554
  `);
5419
5555
  }
@@ -5491,7 +5627,7 @@ Ask the user to confirm: "Can you navigate between panes with Ctrl+h and Ctrl+l?
5491
5627
 
5492
5628
  Once confirmed, proceed:
5493
5629
  \`\`\`
5494
- sisyphus admin getting-started --tutorial 2
5630
+ sis admin getting-started --tutorial 2
5495
5631
  \`\`\`
5496
5632
  </claude-instructions>
5497
5633
  `);
@@ -5533,7 +5669,7 @@ Ask if they were able to edit and save the file (or if they skipped).
5533
5669
 
5534
5670
  Proceed:
5535
5671
  \`\`\`
5536
- sisyphus admin getting-started --tutorial 3
5672
+ sis admin getting-started --tutorial 3
5537
5673
  \`\`\`
5538
5674
  </claude-instructions>
5539
5675
  `);
@@ -5594,7 +5730,7 @@ function printStep3() {
5594
5730
  > typing special characters (accents, symbols). The right Option key
5595
5731
  > becomes your "Meta" key for tmux/sisyphus keybinds.
5596
5732
 
5597
- After they change it, have them verify by re-running \`sisyphus admin doctor\` \u2014 look for "Right Option Key: Esc+".
5733
+ After they change it, have them verify by re-running \`sis admin doctor\` \u2014 look for "Right Option Key: Esc+".
5598
5734
 
5599
5735
  - **rightOptionKeyStatus: not-iterm** \u2014 They're not using iTerm2. Explain:
5600
5736
  > Sisyphus keybinds use Option as Meta. In iTerm2 this is configured via
@@ -5640,12 +5776,12 @@ Two keybinds to remember (both use the RIGHT Option key):
5640
5776
 
5641
5777
  ### 4. Verify keybinds are installed
5642
5778
 
5643
- Run \`sisyphus admin doctor\` and check the output. Look for:
5779
+ Run \`sis admin doctor\` and check the output. Look for:
5644
5780
  - "Cycle script" \u2014 should be \u2713
5645
5781
  - "Tmux keybind" \u2014 should be \u2713
5646
5782
  - "Right Option Key" \u2014 should be "Esc+"
5647
5783
 
5648
- If cycle script or keybind is missing, run: \`sisyphus admin setup-keybind\`
5784
+ If cycle script or keybind is missing, run: \`sis admin setup-keybind\`
5649
5785
 
5650
5786
  ### 5. Test the keybind
5651
5787
 
@@ -5657,12 +5793,12 @@ If they see \`\xDF\` or similar, circle back to the Right Option Key setup above
5657
5793
 
5658
5794
  Confirm:
5659
5795
  - They understand the two-session model (their session vs sisyphus session)
5660
- - \`sisyphus admin doctor\` shows keybinds installed AND Right Option Key: Esc+
5796
+ - \`sis admin doctor\` shows keybinds installed AND Right Option Key: Esc+
5661
5797
  - Right Option + s doesn't produce a special character
5662
5798
 
5663
5799
  Proceed:
5664
5800
  \`\`\`
5665
- sisyphus admin getting-started --tutorial 4
5801
+ sis admin getting-started --tutorial 4
5666
5802
  \`\`\`
5667
5803
  </claude-instructions>
5668
5804
  `);
@@ -5678,12 +5814,12 @@ This is the grand finale \u2014 a live demo session.
5678
5814
 
5679
5815
  ### 1. Health check
5680
5816
 
5681
- Run \`sisyphus admin doctor\` first. If any checks are failing, help the user fix them before proceeding.
5817
+ Run \`sis admin doctor\` first. If any checks are failing, help the user fix them before proceeding.
5682
5818
  All core checks (tmux, daemon, keybinds) should be \u2713.
5683
5819
 
5684
5820
  ### 2. BEFORE launching: Teach navigation
5685
5821
 
5686
- **This is critical.** When \`sisyphus 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:
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:
5687
5823
 
5688
5824
  Explain clearly:
5689
5825
 
@@ -5728,7 +5864,7 @@ Tell the user:
5728
5864
 
5729
5865
  Then launch from the demo directory:
5730
5866
  \`\`\`
5731
- cd /tmp/sisyphus-tutorial-demo && sisyphus 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."
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."
5732
5868
  \`\`\`
5733
5869
 
5734
5870
  After launching, tell them:
@@ -5741,7 +5877,7 @@ Wait for them to confirm they're back, then start live commentary.
5741
5877
 
5742
5878
  **This is the most important part of the demo.** Don't just launch and wait \u2014 actively narrate.
5743
5879
 
5744
- Once the user is back, start a polling loop. Every ~45 seconds, run \`sisyphus 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.
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.
5745
5881
 
5746
5882
  **How to narrate each phase:**
5747
5883
 
@@ -5780,7 +5916,7 @@ Once the session shows "completed":
5780
5916
 
5781
5917
  Tell the user the demo is done. Then run:
5782
5918
  \`\`\`
5783
- sisyphus admin getting-started --tutorial 5
5919
+ sis admin getting-started --tutorial 5
5784
5920
  \`\`\`
5785
5921
  </claude-instructions>
5786
5922
  `);
@@ -5789,11 +5925,11 @@ function printStep5() {
5789
5925
  let recentCommits = "";
5790
5926
  let topLevelFiles = "";
5791
5927
  try {
5792
- recentCommits = execSync12("git log --oneline -15 2>/dev/null", { encoding: "utf-8" }).trim();
5928
+ recentCommits = execSync13("git log --oneline -15 2>/dev/null", { encoding: "utf-8" }).trim();
5793
5929
  } catch {
5794
5930
  }
5795
5931
  try {
5796
- topLevelFiles = execSync12("ls -1 2>/dev/null", { encoding: "utf-8" }).trim();
5932
+ topLevelFiles = execSync13("ls -1 2>/dev/null", { encoding: "utf-8" }).trim();
5797
5933
  } catch {
5798
5934
  }
5799
5935
  console.log(`
@@ -5856,7 +5992,7 @@ This is the most important part. Explain clearly:
5856
5992
  The easiest way is the \`/sisyphus:begin\` slash command inside Claude Code. Just tell Claude
5857
5993
  what you want to build and it'll hand it off to sisyphus with the right context.
5858
5994
 
5859
- Or directly: \`sisyphus start "your task" -c "any background context"\`
5995
+ Or directly: \`sis start "your task" -c "any background context"\`
5860
5996
 
5861
5997
  ### 4. Suggest real tasks for THEIR codebase
5862
5998
 
@@ -5878,7 +6014,7 @@ Tell them:
5878
6014
  > to understand the philosophy, or you want a deeper rundown on the dashboard,
5879
6015
  > monitoring, configuration, or how to steer sessions \u2014 just ask and I'll explain.
5880
6016
 
5881
- If the user says yes or asks to learn more, run \`sisyphus admin getting-started --explain\`
6017
+ If the user says yes or asks to learn more, run \`sis admin getting-started --explain\`
5882
6018
  and use its output to explain the system to them conversationally. Don't dump the whole
5883
6019
  thing \u2014 answer what they're curious about, using the reference as your source material.
5884
6020
  </claude-instructions>
@@ -5894,17 +6030,17 @@ function buildCommandTable(program2) {
5894
6030
  const fmtArgs = (c2) => c2.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
5895
6031
  if (subs.length === 0) {
5896
6032
  const args2 = fmtArgs(cmd);
5897
- const usage = args2 ? `sisyphus ${cmd.name()} ${args2}` : `sisyphus ${cmd.name()}`;
6033
+ const usage = args2 ? `sis ${cmd.name()} ${args2}` : `sis ${cmd.name()}`;
5898
6034
  lines.push(`| \`${usage}\` | ${cmd.description()} |`);
5899
6035
  } else {
5900
6036
  if (hasOwnAction) {
5901
6037
  const args2 = fmtArgs(cmd);
5902
- const usage = args2 ? `sisyphus ${cmd.name()} ${args2}` : `sisyphus ${cmd.name()}`;
6038
+ const usage = args2 ? `sis ${cmd.name()} ${args2}` : `sis ${cmd.name()}`;
5903
6039
  lines.push(`| \`${usage}\` | ${cmd.description()} |`);
5904
6040
  }
5905
6041
  for (const sub of subs) {
5906
6042
  const args2 = fmtArgs(sub);
5907
- const usage = args2 ? `sisyphus ${cmd.name()} ${sub.name()} ${args2}` : `sisyphus ${cmd.name()} ${sub.name()}`;
6043
+ const usage = args2 ? `sis ${cmd.name()} ${sub.name()} ${args2}` : `sis ${cmd.name()} ${sub.name()}`;
5908
6044
  lines.push(`| \`${usage}\` | ${sub.description()} |`);
5909
6045
  }
5910
6046
  }
@@ -6054,7 +6190,7 @@ code that looks right and code that works.
6054
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
6055
6191
  \u2502 SESSION LIFECYCLE \u2502
6056
6192
  \u2502 \u2502
6057
- \u2502 sisyphus start "task" \u2502
6193
+ \u2502 sis start "task" \u2502
6058
6194
  \u2502 \u2502 \u2502
6059
6195
  \u2502 \u25BC \u2502
6060
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
@@ -6062,7 +6198,7 @@ code that looks right and code that works.
6062
6198
  \u2502 \u2502 plans \u2502 then yields \u2502 in parallel \u2502 \u2502
6063
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
6064
6200
  \u2502 \u2502 \u2502 each calls \u2502
6065
- \u2502 \u2502 orchestrator \u2502 sisyphus agent submit \u2502
6201
+ \u2502 \u2502 orchestrator \u2502 sis agent submit \u2502
6066
6202
  \u2502 \u2502 is KILLED \u2502 when done \u2502
6067
6203
  \u2502 \u2502 \u25BC \u2502
6068
6204
  \u2502 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
@@ -6097,7 +6233,7 @@ This means it never runs out of context, no matter how many cycles a session tak
6097
6233
 
6098
6234
  ## The Dashboard
6099
6235
 
6100
- The dashboard is a real-time TUI that shows session state. Launch with \`sisyphus dashboard\`
6236
+ The dashboard is a real-time TUI that shows session state. Launch with \`sis dashboard\`
6101
6237
  or it auto-opens when a session starts.
6102
6238
 
6103
6239
  **Dashboard sections:**
@@ -6136,14 +6272,14 @@ Sisyphus sessions should be actively monitored. Here's what to watch for:
6136
6272
 
6137
6273
  **When to intervene:**
6138
6274
  - Use \`m\` in the dashboard to message the orchestrator with corrections
6139
- - Use \`sisyphus session kill <id>\` to stop a runaway session
6140
- - Use \`sisyphus session resume <id> "new instructions"\` to restart with different direction
6275
+ - Use \`sis session kill <id>\` to stop a runaway session
6276
+ - Use \`sis session resume <id> "new instructions"\` to restart with different direction
6141
6277
 
6142
6278
  **Useful monitoring commands:**
6143
6279
  \`\`\`
6144
- sisyphus status <id> # Quick status check
6145
- sisyphus status --verbose <id> # Full detail: roadmap, pane output, agent instructions
6146
- sisyphus dashboard # Interactive TUI
6280
+ sis status <id> # Quick status check
6281
+ sis status --verbose <id> # Full detail: roadmap, pane output, agent instructions
6282
+ sis dashboard # Interactive TUI
6147
6283
  tail -f ~/.sisyphus/daemon.log # Daemon activity log
6148
6284
  \`\`\`
6149
6285
 
@@ -6209,11 +6345,11 @@ Then describe your task. Claude will hand it off with the right context.
6209
6345
 
6210
6346
  **Direct CLI:**
6211
6347
  \`\`\`
6212
- sisyphus start "task description" -c "background context"
6213
- sisyphus start "Implement @requirements.md" -n my-feature
6348
+ sis start "task description" -c "background context"
6349
+ sis start "Implement @requirements.md" -n my-feature
6214
6350
  \`\`\`
6215
6351
 
6216
- **Reference files with @**: \`sisyphus start "Build @docs/spec.md"\` \u2014 the orchestrator
6352
+ **Reference files with @**: \`sis start "Build @docs/spec.md"\` \u2014 the orchestrator
6217
6353
  will read the referenced file as part of its planning.
6218
6354
 
6219
6355
  **The -c flag** adds background context the orchestrator sees but doesn't act on directly.
@@ -6235,10 +6371,10 @@ sisyphusd restart
6235
6371
  **Keybinds not working (special characters appear):**
6236
6372
  iTerm2 \u2192 Settings \u2192 Profiles \u2192 Keys \u2192 Right Option Key \u2192 Esc+
6237
6373
 
6238
- **Agents stuck:** Check \`sisyphus status --verbose <id>\` to see pane output. If an
6374
+ **Agents stuck:** Check \`sis status --verbose <id>\` to see pane output. If an
6239
6375
  agent is waiting for input, kill the session and restart with clearer instructions.
6240
6376
 
6241
- **Dashboard not opening:** Run \`sisyphus dashboard\` manually. Must be inside tmux.
6377
+ **Dashboard not opening:** Run \`sis dashboard\` manually. Must be inside tmux.
6242
6378
 
6243
6379
  **Session seems hung:** Check \`tail -20 ~/.sisyphus/daemon.log\` for errors.
6244
6380
  The daemon polls panes every 2s \u2014 if a pane dies unexpectedly, it'll be detected.
@@ -6558,7 +6694,7 @@ function showSession(idOrName, opts) {
6558
6694
  console.log(`${DIM3}Compute:${RESET3} ${formatDuration(computeMs)} ${DIM3}Interactive:${RESET3} ${formatDuration(interactiveMs)} ${DIM3}(TUI wait time, not compute)${RESET3}`);
6559
6695
  }
6560
6696
  if (s.userBlockedMs > 0) {
6561
- console.log(`${DIM3}Waiting on user:${RESET3} ${formatDuration(s.userBlockedMs)} ${DIM3}(blocked on sisyphus ask, not compute)${RESET3}`);
6697
+ console.log(`${DIM3}Waiting on user:${RESET3} ${formatDuration(s.userBlockedMs)} ${DIM3}(blocked on sis ask, not compute)${RESET3}`);
6562
6698
  }
6563
6699
  console.log("");
6564
6700
  console.log(`${BOLD3}Task${RESET3}`);
@@ -6960,7 +7096,7 @@ function registerExport(program2) {
6960
7096
  }
6961
7097
  if (!sessionId) {
6962
7098
  console.error("Error: No session ID provided and no active session found.");
6963
- console.error("Usage: sisyphus admin export [session-id]");
7099
+ console.error("Usage: sis admin export [session-id]");
6964
7100
  process.exit(1);
6965
7101
  }
6966
7102
  try {
@@ -7084,13 +7220,13 @@ function registerUpload(program2) {
7084
7220
  }
7085
7221
  if (!sessionId) {
7086
7222
  console.error("Error: No session ID provided and no active session found.");
7087
- console.error("Usage: sisyphus admin upload [session-id]");
7223
+ console.error("Usage: sis admin upload [session-id]");
7088
7224
  process.exit(1);
7089
7225
  }
7090
7226
  const config = loadConfig(cwd);
7091
7227
  if (!isUploadConfigured(config.upload)) {
7092
7228
  console.error(
7093
- "Error: upload not configured. Run 'sisyphus admin configure-upload <url-with-token>' or set { upload: { url, token } } in .sisyphus/config.json."
7229
+ "Error: upload not configured. Run 'sis admin configure-upload <url-with-token>' or set { upload: { url, token } } in .sisyphus/config.json."
7094
7230
  );
7095
7231
  process.exit(1);
7096
7232
  }
@@ -7149,12 +7285,12 @@ function registerUpload(program2) {
7149
7285
  }
7150
7286
 
7151
7287
  // src/cli/commands/scratch.ts
7152
- import { execSync as execSync13 } from "child_process";
7288
+ import { execSync as execSync14 } from "child_process";
7153
7289
  function findHomeSession(cwd) {
7154
7290
  const normalizedCwd = cwd.replace(/\/+$/, "");
7155
7291
  let output;
7156
7292
  try {
7157
- output = execSync13('tmux list-sessions -F "#{session_id}|#{session_name}"', {
7293
+ output = execSync14('tmux list-sessions -F "#{session_id}|#{session_name}"', {
7158
7294
  encoding: "utf-8",
7159
7295
  stdio: ["pipe", "pipe", "pipe"]
7160
7296
  }).trim();
@@ -7168,7 +7304,7 @@ function findHomeSession(cwd) {
7168
7304
  const name = line.slice(pipeIdx + 1);
7169
7305
  if (name.startsWith("ssyph_")) continue;
7170
7306
  try {
7171
- const val = execSync13(
7307
+ const val = execSync14(
7172
7308
  `tmux show-options -t ${shellQuote(sessId)} -v @sisyphus_cwd`,
7173
7309
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
7174
7310
  ).trim();
@@ -7184,7 +7320,7 @@ function registerScratch(program2) {
7184
7320
  const cwd = opts.cwd ?? process.env["SISYPHUS_CWD"] ?? process.cwd();
7185
7321
  const homeSession = findHomeSession(cwd);
7186
7322
  if (!homeSession) {
7187
- const current = execSync13('tmux display-message -p "#{session_name}"', {
7323
+ const current = execSync14('tmux display-message -p "#{session_name}"', {
7188
7324
  encoding: "utf-8"
7189
7325
  }).trim();
7190
7326
  openScratchWindow(current, cwd, promptParts.join(" "));
@@ -7194,7 +7330,7 @@ function registerScratch(program2) {
7194
7330
  });
7195
7331
  }
7196
7332
  function openScratchWindow(tmuxSession, cwd, prompt) {
7197
- const windowId = execSync13(
7333
+ const windowId = execSync14(
7198
7334
  `tmux new-window -t ${shellQuote(tmuxSession + ":")} -n "scratch" -c ${shellQuote(cwd)} -P -F "#{window_id}"`,
7199
7335
  { encoding: "utf-8" }
7200
7336
  ).trim();
@@ -7202,7 +7338,7 @@ function openScratchWindow(tmuxSession, cwd, prompt) {
7202
7338
  if (prompt) {
7203
7339
  cmd += ` -p ${shellQuote(prompt)}`;
7204
7340
  }
7205
- execSync13(
7341
+ execSync14(
7206
7342
  `tmux send-keys -t ${shellQuote(windowId)} ${shellQuote(cmd)} Enter`
7207
7343
  );
7208
7344
  console.log(`Scratch session opened in ${tmuxSession}`);
@@ -7244,14 +7380,14 @@ File resolution (first match wins):
7244
7380
  3. Most recent session with a requirements.json
7245
7381
 
7246
7382
  Examples:
7247
- $ sisyphus admin requirements Auto-detect from current session
7248
- $ sisyphus admin requirements path/to/requirements.json Open a specific file
7249
- $ sisyphus admin requirements --session-id abc123 Target a specific session
7250
- $ sisyphus admin requirements --schema Print the JSON schema
7251
- $ sisyphus admin requirements --annotated Print schema with writing guidance
7252
- $ sisyphus admin requirements --export Render requirements.md from JSON
7253
- $ sisyphus admin requirements --export --session-id abc123 Target a specific session
7254
- $ sisyphus admin requirements --export --force Overwrite even if hand-edited
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
7255
7391
  `).action(async (file, opts) => {
7256
7392
  if (opts.force && !opts.export) {
7257
7393
  console.error("Error: --force requires --export");
@@ -7405,7 +7541,7 @@ var REQUIREMENTS_SCHEMA = {
7405
7541
  var REQUIREMENTS_ANNOTATED = `# requirements.json \u2014 Annotated Writing Guide
7406
7542
  #
7407
7543
  # This is NOT valid JSON \u2014 it's a reference showing every field with
7408
- # inline guidance. Run \`sisyphus admin requirements --schema\` for the raw
7544
+ # inline guidance. Run \`sis admin requirements --schema\` for the raw
7409
7545
  # JSON Schema.
7410
7546
  #
7411
7547
  # Safe assumptions must satisfy the same EARS shape requirements as
@@ -9366,7 +9502,7 @@ import { join as join25 } from "path";
9366
9502
  // src/cli/deploy/runner.ts
9367
9503
  init_paths();
9368
9504
  import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
9369
- import { copyFileSync as copyFileSync2, existsSync as existsSync26, mkdirSync as mkdirSync13, readFileSync as readFileSync28 } from "fs";
9505
+ import { copyFileSync as copyFileSync2, existsSync as existsSync27, mkdirSync as mkdirSync13, readFileSync as readFileSync29 } from "fs";
9370
9506
  init_creds();
9371
9507
 
9372
9508
  // src/cli/deploy/pricing.ts
@@ -9399,16 +9535,82 @@ function formatCostLine(provider, instanceType) {
9399
9535
  return `Estimated cost: ~$${cost.toFixed(2)}/mo (pricing last verified ${LAST_VERIFIED}; verify against your bill for current rates).`;
9400
9536
  }
9401
9537
 
9538
+ // src/cli/deploy/runtime.ts
9539
+ init_atomic();
9540
+ init_paths();
9541
+ import { existsSync as existsSync25, readFileSync as readFileSync28, unlinkSync as unlinkSync4 } from "fs";
9542
+ function readRuntimeState(provider) {
9543
+ const path = deployRuntimePath(provider);
9544
+ if (!existsSync25(path)) return null;
9545
+ try {
9546
+ return JSON.parse(readFileSync28(path, "utf-8"));
9547
+ } catch {
9548
+ return null;
9549
+ }
9550
+ }
9551
+ function writeRuntimeState(provider, state) {
9552
+ atomicWrite(deployRuntimePath(provider), JSON.stringify(state, null, 2) + "\n");
9553
+ }
9554
+ function clearRuntimeState(provider) {
9555
+ const path = deployRuntimePath(provider);
9556
+ if (existsSync25(path)) unlinkSync4(path);
9557
+ }
9558
+
9559
+ // src/cli/deploy/tailnet.ts
9560
+ function discoverNode(requestedName) {
9561
+ const json = execSafe("tailscale status --json");
9562
+ if (!json) return null;
9563
+ let status;
9564
+ try {
9565
+ status = JSON.parse(json);
9566
+ } catch {
9567
+ return null;
9568
+ }
9569
+ const peers = status.Peer === void 0 ? [] : Object.values(status.Peer);
9570
+ const candidates = peers.filter(
9571
+ (p) => p.HostName === requestedName && p.Online === true
9572
+ );
9573
+ if (candidates.length === 0) return null;
9574
+ candidates.sort((a, b) => {
9575
+ const ac = a.Created === void 0 ? "" : a.Created;
9576
+ const bc = b.Created === void 0 ? "" : b.Created;
9577
+ return bc.localeCompare(ac);
9578
+ });
9579
+ const peer = candidates[0];
9580
+ const dnsRaw = peer.DNSName === void 0 ? "" : peer.DNSName;
9581
+ const dns = dnsRaw.replace(/\.$/, "");
9582
+ if (!dns) return null;
9583
+ const shortName = dns.split(".")[0];
9584
+ const ips = peer.TailscaleIPs === void 0 ? [] : peer.TailscaleIPs;
9585
+ const ipv4 = ips.find((ip) => /^\d+\.\d+\.\d+\.\d+$/.test(ip));
9586
+ if (!ipv4) return null;
9587
+ const ipv6 = ips.find((ip) => ip.includes(":")) ?? null;
9588
+ return { shortName, magicDnsName: dns, ipv4, ipv6 };
9589
+ }
9590
+ async function discoverNodeWithRetry(requestedName, maxRetries = 30, intervalMs = 2e3) {
9591
+ for (let i = 0; i < maxRetries; i++) {
9592
+ const node = discoverNode(requestedName);
9593
+ if (node) return node;
9594
+ if (i < maxRetries - 1) {
9595
+ await new Promise((resolve12) => setTimeout(resolve12, intervalMs));
9596
+ }
9597
+ }
9598
+ return null;
9599
+ }
9600
+ function isTailscaleAvailable() {
9601
+ return execSafe("tailscale version") !== null;
9602
+ }
9603
+
9402
9604
  // src/cli/deploy/templates.ts
9403
- import { existsSync as existsSync25 } from "fs";
9605
+ import { existsSync as existsSync26 } from "fs";
9404
9606
  import { dirname as dirname11, resolve as resolve10 } from "path";
9405
9607
  import { fileURLToPath as fileURLToPath4 } from "url";
9406
9608
  function deployRoot() {
9407
9609
  const here = dirname11(fileURLToPath4(import.meta.url));
9408
9610
  const bundled = resolve10(here, "..", "deploy");
9409
- if (existsSync25(bundled)) return bundled;
9611
+ if (existsSync26(bundled)) return bundled;
9410
9612
  const sourceRoot = resolve10(here, "..", "..", "..", "deploy");
9411
- if (existsSync25(sourceRoot)) return sourceRoot;
9613
+ if (existsSync26(sourceRoot)) return sourceRoot;
9412
9614
  throw new Error(
9413
9615
  `Could not locate deploy/ templates. Looked at:
9414
9616
  ${bundled}
@@ -9432,22 +9634,23 @@ async function mintTailscaleKey(opts) {
9432
9634
  }
9433
9635
  if (env.oauthClientId && env.oauthClientSecret) {
9434
9636
  if (!env.tag) {
9435
- throw new Error("Tailscale tag is missing from ~/.sisyphus/deploy/tailscale.env. Re-run `sisyphus deploy auth tailscale`.");
9637
+ throw new Error("Tailscale tag is missing from ~/.sisyphus/deploy/tailscale.env. Re-run `sis deploy auth tailscale`.");
9436
9638
  }
9437
9639
  return mintViaOAuth(env.oauthClientId, env.oauthClientSecret, env.tag, opts.hostname);
9438
9640
  }
9439
9641
  if (env.authKey) {
9440
9642
  return env.authKey;
9441
9643
  }
9442
- throw new Error("Tailscale not configured. Run `sisyphus deploy auth tailscale`.");
9644
+ throw new Error("Tailscale not configured. Run `sis deploy auth tailscale`.");
9443
9645
  }
9444
9646
  async function firstRunPrompt() {
9445
9647
  console.log("");
9446
9648
  console.log("Tailscale credentials not configured. Pick one:");
9447
9649
  console.log("");
9448
- console.log(" 1) OAuth client (recommended) \u2014 mints fresh ephemeral keys per box.");
9650
+ console.log(" 1) OAuth client (recommended) \u2014 mints fresh ephemeral keys per box");
9651
+ console.log(" and auto-cleans stale offline nodes so hostnames don't get suffixed.");
9449
9652
  console.log(" Create at https://login.tailscale.com/admin/settings/oauth");
9450
- console.log(" with scope `auth_keys:write` and tag `tag:sisyphus`.");
9653
+ console.log(" with scopes `auth_keys:write` + `devices:write` and tag `tag:sisyphus`.");
9451
9654
  console.log(" 2) Reusable auth key (simpler) \u2014 paste from");
9452
9655
  console.log(" https://login.tailscale.com/admin/settings/keys");
9453
9656
  console.log("");
@@ -9467,8 +9670,16 @@ async function firstRunPrompt() {
9467
9670
  }
9468
9671
  throw new Error(`Invalid choice: ${choice}`);
9469
9672
  }
9470
- async function mintViaOAuth(clientId, clientSecret, tag, hostname) {
9673
+ async function mintViaOAuth(clientId, clientSecret, tag, hostname2) {
9471
9674
  const token = await fetchAccessToken(clientId, clientSecret);
9675
+ try {
9676
+ const removed = await deleteStaleDevicesForHostname(token, hostname2);
9677
+ if (removed > 0) {
9678
+ console.log(`Tailscale: removed ${removed} stale offline node(s) named "${hostname2}".`);
9679
+ }
9680
+ } catch (err) {
9681
+ console.log(`Tailscale: skipped stale-node cleanup (${err.message}). Add 'devices:write' scope to clean up automatically.`);
9682
+ }
9472
9683
  const body = {
9473
9684
  capabilities: {
9474
9685
  devices: {
@@ -9481,7 +9692,7 @@ async function mintViaOAuth(clientId, clientSecret, tag, hostname) {
9481
9692
  }
9482
9693
  },
9483
9694
  expirySeconds: KEY_EXPIRY_SECONDS,
9484
- description: `sisyphus deploy: ${hostname}`
9695
+ description: `sisyphus deploy: ${hostname2}`
9485
9696
  };
9486
9697
  const res = await fetch(`${TS_API}/tailnet/-/keys`, {
9487
9698
  method: "POST",
@@ -9499,6 +9710,32 @@ async function mintViaOAuth(clientId, clientSecret, tag, hostname) {
9499
9710
  if (!data.key) throw new Error("Tailscale API returned no key.");
9500
9711
  return data.key;
9501
9712
  }
9713
+ async function deleteStaleDevicesForHostname(token, hostname2) {
9714
+ const res = await fetch(`${TS_API}/tailnet/-/devices`, {
9715
+ headers: { Authorization: `Bearer ${token}` }
9716
+ });
9717
+ if (!res.ok) {
9718
+ throw new Error(`HTTP ${res.status} listing devices`);
9719
+ }
9720
+ const data = await res.json();
9721
+ const STALE_AFTER_MS = 5 * 60 * 1e3;
9722
+ const now = Date.now();
9723
+ const stale = data.devices.filter((d) => {
9724
+ if (d.hostname !== hostname2) return false;
9725
+ const seen = Date.parse(d.lastSeen);
9726
+ if (Number.isNaN(seen)) return false;
9727
+ return now - seen > STALE_AFTER_MS;
9728
+ });
9729
+ let removed = 0;
9730
+ for (const device of stale) {
9731
+ const r = await fetch(`${TS_API}/device/${device.id}`, {
9732
+ method: "DELETE",
9733
+ headers: { Authorization: `Bearer ${token}` }
9734
+ });
9735
+ if (r.ok) removed++;
9736
+ }
9737
+ return removed;
9738
+ }
9502
9739
  async function fetchAccessToken(clientId, clientSecret) {
9503
9740
  const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
9504
9741
  const res = await fetch(`${TS_API}/oauth/token`, {
@@ -9527,7 +9764,7 @@ async function authTailscale() {
9527
9764
  console.log("");
9528
9765
  console.log("Verifying credentials...");
9529
9766
  await fetchAccessToken(env.oauthClientId, env.oauthClientSecret);
9530
- console.log("OAuth client verified \u2014 `sisyphus deploy <provider> up` will mint ephemeral keys.");
9767
+ console.log("OAuth client verified \u2014 `sis deploy <provider> up` will mint ephemeral keys.");
9531
9768
  } else {
9532
9769
  console.log("");
9533
9770
  console.log("Auth key saved. Note: reusable auth keys are less secure than OAuth clients.");
@@ -9557,14 +9794,14 @@ function ensureTerraformInstalled() {
9557
9794
  function ensureProviderStateDir(provider) {
9558
9795
  ensureDeployDir();
9559
9796
  const dir = deployProviderDir(provider);
9560
- if (!existsSync26(dir)) mkdirSync13(dir, { recursive: true, mode: 448 });
9797
+ if (!existsSync27(dir)) mkdirSync13(dir, { recursive: true, mode: 448 });
9561
9798
  }
9562
9799
  function backupState(provider) {
9563
9800
  const src = deployStatePath(provider);
9564
- if (existsSync26(src)) copyFileSync2(src, deployStateBackupPath(provider));
9801
+ if (existsSync27(src)) copyFileSync2(src, deployStateBackupPath(provider));
9565
9802
  }
9566
9803
  function readSshPubkey(path) {
9567
- if (!existsSync26(path)) {
9804
+ if (!existsSync27(path)) {
9568
9805
  const privateKeyPath = path.replace(/\.pub$/, "");
9569
9806
  throw new Error(
9570
9807
  `SSH pubkey not found at ${path}. Generate one with:
@@ -9572,7 +9809,7 @@ function readSshPubkey(path) {
9572
9809
  or pass --ssh-key <path>.`
9573
9810
  );
9574
9811
  }
9575
- return readFileSync28(path, "utf-8").trim();
9812
+ return readFileSync29(path, "utf-8").trim();
9576
9813
  }
9577
9814
  function readOutputs(provider) {
9578
9815
  const result = spawnSync3("terraform", ["output", "-json", `-state=${deployStatePath(provider)}`], {
@@ -9599,10 +9836,13 @@ function readOutputs(provider) {
9599
9836
  }
9600
9837
  }
9601
9838
  function isProvisioned(provider) {
9602
- if (!existsSync26(deployStatePath(provider))) return false;
9839
+ if (!existsSync27(deployStatePath(provider))) return false;
9603
9840
  return readOutputs(provider) !== null;
9604
9841
  }
9605
9842
  async function deployUp(provider, opts) {
9843
+ if (isProvisioned(provider) && !await confirmReprovision(provider, opts.yes)) {
9844
+ return;
9845
+ }
9606
9846
  const sshPubkey = readSshPubkey(opts.sshKey);
9607
9847
  const creds = await loadProviderCreds(provider);
9608
9848
  const tsAuthKey = await mintTailscaleKey({ hostname: opts.name });
@@ -9644,22 +9884,45 @@ async function deployUp(provider, opts) {
9644
9884
  const outputs = readOutputs(provider);
9645
9885
  if (!outputs) {
9646
9886
  console.log(`
9647
- Applied \u2014 but could not parse outputs. Run \`sisyphus deploy ${provider} status\`.`);
9887
+ Applied \u2014 but could not parse outputs. Run \`sis deploy ${provider} status\`.`);
9648
9888
  return;
9649
9889
  }
9650
9890
  console.log("");
9651
9891
  console.log("Box provisioned. Cloud-init will run for ~3\u20135 minutes before the daemon is reachable.");
9652
9892
  console.log("");
9653
- console.log(` IP: ${outputs.ipv4}`);
9654
- console.log(` Tailscale hostname: ${outputs.tailscale_hostname}`);
9655
- console.log(` SSH: ${outputs.ssh_command}`);
9893
+ console.log(` Public IP: ${outputs.ipv4}`);
9894
+ console.log(` Requested hostname: ${outputs.tailscale_hostname}`);
9895
+ if (isTailscaleAvailable()) {
9896
+ process.stdout.write(" Waiting for tailnet join...");
9897
+ const node = await discoverNodeWithRetry(opts.name);
9898
+ if (node) {
9899
+ writeRuntimeState(provider, {
9900
+ tailscaleHostname: node.shortName,
9901
+ tailscaleFqdn: node.magicDnsName,
9902
+ tailscaleIpv4: node.ipv4,
9903
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString()
9904
+ });
9905
+ process.stdout.write(` joined as ${node.shortName} (${node.ipv4})
9906
+ `);
9907
+ if (node.shortName !== opts.name) {
9908
+ console.log(` Note: Tailscale suffixed the hostname because "${opts.name}" was already`);
9909
+ console.log(" claimed by an offline node. Delete the stale node at");
9910
+ console.log(" https://login.tailscale.com/admin/machines to reuse the original name.");
9911
+ }
9912
+ } else {
9913
+ process.stdout.write(" no peer matched after 60s\n");
9914
+ console.log(" (Cloud-init may still be installing Tailscale. Re-run `status` later.)");
9915
+ }
9916
+ } else {
9917
+ console.log(" (Local `tailscale` CLI not on PATH \u2014 skipping tailnet discovery.)");
9918
+ }
9656
9919
  console.log("");
9657
- console.log(` Tail provisioning: sisyphus deploy ${provider} logs`);
9658
- console.log(` Verify daemon: sisyphus deploy ${provider} ssh -- sisyphus admin doctor`);
9920
+ console.log(` Tail provisioning: sis deploy ${provider} logs`);
9921
+ console.log(` Verify daemon: sis deploy ${provider} ssh -- sis admin doctor`);
9659
9922
  console.log("");
9660
9923
  }
9661
9924
  async function deployDown(provider, opts) {
9662
- if (!existsSync26(deployStatePath(provider))) {
9925
+ if (!existsSync27(deployStatePath(provider))) {
9663
9926
  console.log(`No ${provider} state found at ${deployStatePath(provider)}. Nothing to destroy.`);
9664
9927
  return;
9665
9928
  }
@@ -9690,6 +9953,7 @@ async function deployDown(provider, opts) {
9690
9953
  creds
9691
9954
  );
9692
9955
  if (code !== 0) throw new Error(`terraform destroy failed (exit ${code})`);
9956
+ clearRuntimeState(provider);
9693
9957
  console.log(`
9694
9958
  ${provider} box destroyed.`);
9695
9959
  }
@@ -9700,13 +9964,24 @@ function deployStatus(provider) {
9700
9964
  }
9701
9965
  const outputs = readOutputs(provider);
9702
9966
  if (!outputs) {
9703
- console.log(`${provider}: state present but outputs unreadable. Try \`sisyphus deploy ${provider} up\` to reconcile.`);
9967
+ console.log(`${provider}: state present but outputs unreadable. Try \`sis deploy ${provider} up\` to reconcile.`);
9704
9968
  return;
9705
9969
  }
9970
+ const runtime = readRuntimeState(provider);
9971
+ const effectiveHost = runtime ? runtime.tailscaleHostname : outputs.tailscale_hostname;
9706
9972
  console.log(`${provider}: provisioned`);
9707
- console.log(` IP: ${outputs.ipv4}`);
9708
- console.log(` Tailscale hostname: ${outputs.tailscale_hostname}`);
9709
- console.log(` SSH: ${outputs.ssh_command}`);
9973
+ console.log(` Public IP: ${outputs.ipv4}`);
9974
+ console.log(` Tailscale hostname: ${effectiveHost}`);
9975
+ if (runtime) {
9976
+ console.log(` Tailscale IPv4: ${runtime.tailscaleIpv4}`);
9977
+ console.log(` MagicDNS FQDN: ${runtime.tailscaleFqdn}`);
9978
+ if (runtime.tailscaleHostname !== outputs.tailscale_hostname) {
9979
+ console.log(` (Requested "${outputs.tailscale_hostname}" but Tailscale assigned "${runtime.tailscaleHostname}".)`);
9980
+ }
9981
+ } else {
9982
+ console.log(" (No tailnet runtime state \u2014 re-run `up` or check that local tailscale is logged in.)");
9983
+ }
9984
+ console.log(` SSH: ssh sisyphus@${effectiveHost}`);
9710
9985
  console.log(` Instance type: ${outputs.instance_type}`);
9711
9986
  console.log(` ${formatCostLine(provider, outputs.instance_type)}`);
9712
9987
  }
@@ -9717,16 +9992,17 @@ function deployListProviders() {
9717
9992
  console.log(` ${p.padEnd(10)} ${status}`);
9718
9993
  }
9719
9994
  }
9720
- function requireOutputs(provider) {
9721
- const o = readOutputs(provider);
9722
- if (!o) {
9723
- throw new Error(`${provider} not provisioned. Run \`sisyphus deploy ${provider} up\`.`);
9995
+ function effectiveSshTarget(provider) {
9996
+ const runtime = readRuntimeState(provider);
9997
+ if (runtime) return `sisyphus@${runtime.tailscaleHostname}`;
9998
+ const outputs = readOutputs(provider);
9999
+ if (!outputs) {
10000
+ throw new Error(`${provider} not provisioned. Run \`sis deploy ${provider} up\`.`);
9724
10001
  }
9725
- return o;
10002
+ return `sisyphus@${outputs.tailscale_hostname}`;
9726
10003
  }
9727
10004
  function deploySsh(provider, remoteCmd) {
9728
- const outputs = requireOutputs(provider);
9729
- const target = `sisyphus@${outputs.tailscale_hostname}`;
10005
+ const target = effectiveSshTarget(provider);
9730
10006
  const moshAvailable = spawnSync3("mosh", ["--version"], { stdio: "pipe", env: EXEC_ENV }).status === 0;
9731
10007
  const bin = moshAvailable && remoteCmd.length === 0 ? "mosh" : "ssh";
9732
10008
  const args2 = remoteCmd.length > 0 ? [target, ...remoteCmd] : [target];
@@ -9734,15 +10010,13 @@ function deploySsh(provider, remoteCmd) {
9734
10010
  child.on("exit", (code) => process.exit(code === null ? 1 : code));
9735
10011
  }
9736
10012
  function deployLogs(provider) {
9737
- const outputs = requireOutputs(provider);
9738
- const target = `sisyphus@${outputs.tailscale_hostname}`;
10013
+ const target = effectiveSshTarget(provider);
9739
10014
  const remoteCmd = "tail -F -n 200 /var/log/cloud-init-output.log ~/.sisyphus/daemon.log 2>/dev/null";
9740
10015
  const child = spawn2("ssh", [target, remoteCmd], { stdio: "inherit", env: EXEC_ENV });
9741
10016
  child.on("exit", (code) => process.exit(code === null ? 1 : code));
9742
10017
  }
9743
10018
  function deployUpdate(provider) {
9744
- const outputs = requireOutputs(provider);
9745
- const target = `sisyphus@${outputs.tailscale_hostname}`;
10019
+ const target = effectiveSshTarget(provider);
9746
10020
  const remoteCmd = "sudo npm i -g sisyphi@latest && systemctl --user restart sisyphusd && sisyphusd --version || true";
9747
10021
  const child = spawn2("ssh", [target, remoteCmd], { stdio: "inherit", env: EXEC_ENV });
9748
10022
  child.on("exit", (code) => process.exit(code === null ? 1 : code));
@@ -9752,9 +10026,33 @@ async function confirm2(prompt) {
9752
10026
  const answer = await promptLine2(`${prompt} `, false);
9753
10027
  return answer.toLowerCase() === "yes";
9754
10028
  }
10029
+ async function confirmReprovision(provider, yes) {
10030
+ const outputs = readOutputs(provider);
10031
+ console.log("");
10032
+ if (outputs) {
10033
+ console.log(`${provider} is already provisioned: "${outputs.tailscale_hostname}" (${outputs.instance_type}, ${outputs.ipv4}).`);
10034
+ } else {
10035
+ console.log(`${provider} state already exists at ${deployStatePath(provider)}.`);
10036
+ }
10037
+ if (provider === "hetzner") {
10038
+ console.log("Re-running `up` on Hetzner will DESTROY and RECREATE the box (user_data is ForceNew).");
10039
+ console.log("All on-box state \u2014 daemon history, sessions, anything not in your repo \u2014 will be lost.");
10040
+ } else {
10041
+ console.log("Re-running `up` on AWS updates user_data in state but does NOT recreate the instance.");
10042
+ console.log("Cloud-init won't re-run on the live box, and the freshly-minted Tailscale key will be wasted.");
10043
+ }
10044
+ console.log("");
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`);
10047
+ console.log("");
10048
+ if (yes) return true;
10049
+ const confirmed = await confirm2('Type "yes" to proceed anyway:');
10050
+ if (!confirmed) console.log("Aborted.");
10051
+ return confirmed;
10052
+ }
9755
10053
 
9756
10054
  // src/cli/commands/deploy.ts
9757
- var PROVIDERS = ["hetzner", "aws"];
10055
+ init_creds();
9758
10056
  function assertArch(raw) {
9759
10057
  if (raw === "arm" || raw === "x86") return raw;
9760
10058
  throw new Error(`Invalid --arch: ${raw}. Must be 'arm' or 'x86'.`);
@@ -9771,7 +10069,8 @@ function resolveUpOptions(provider, raw) {
9771
10069
  const size = raw.size === void 0 ? null : raw.size;
9772
10070
  const withChromium = raw.chromium !== false;
9773
10071
  const enableAutoUpdate = raw.autoUpdate !== false;
9774
- return { region, arch, size, sshKey: raw.sshKey, name: raw.name, withChromium, enableAutoUpdate };
10072
+ const yes = raw.yes === true;
10073
+ return { region, arch, size, sshKey: raw.sshKey, name: raw.name, withChromium, enableAutoUpdate, yes };
9775
10074
  }
9776
10075
  function registerDeploy(program2) {
9777
10076
  const deploy = program2.command("deploy").description("Provision a Tailscale-only sisyphus box on Hetzner or AWS via Terraform.");
@@ -9788,7 +10087,7 @@ function registerDeploy(program2) {
9788
10087
  });
9789
10088
  for (const provider of PROVIDERS) {
9790
10089
  const sub = deploy.command(provider).description(`${provider} commands.`);
9791
- sub.command("up").description(`Provision the ${provider} box (terraform init \u2192 plan \u2192 apply).`).option("--region <region>", `Provider region (defaults: hetzner=nbg1, aws=us-east-1).`).option("--arch <arch>", "'arm' (default) or 'x86'. Picks the default --size and image.", "arm").option("--size <size>", "Instance type override (defaults follow --arch).").option("--ssh-key <path>", "Path to SSH public key.", join25(homedir11(), ".ssh", "id_ed25519.pub")).option("--no-chromium", "Skip headless Chromium install.").option("--no-auto-update", "Skip the daily auto-update systemd timer.").option("--name <name>", "Box hostname / Tailscale node name.", "sisyphus").action(async (raw) => {
10090
+ sub.command("up").description(`Provision the ${provider} box (terraform init \u2192 plan \u2192 apply).`).option("--region <region>", `Provider region (defaults: hetzner=nbg1, aws=us-east-1).`).option("--arch <arch>", "'arm' (default) or 'x86'. Picks the default --size and image.", "arm").option("--size <size>", "Instance type override (defaults follow --arch).").option("--ssh-key <path>", "Path to SSH public key.", join25(homedir11(), ".ssh", "id_ed25519.pub")).option("--no-chromium", "Skip headless Chromium install.").option("--no-auto-update", "Skip the daily auto-update systemd timer.").option("--name <name>", "Box hostname / Tailscale node name.", "sisyphus").option("-y, --yes", "Skip the re-provision confirmation prompt when state already exists.").action(async (raw) => {
9792
10091
  const opts = resolveUpOptions(provider, raw);
9793
10092
  await deployUp(provider, opts);
9794
10093
  });
@@ -9810,6 +10109,374 @@ function registerDeploy(program2) {
9810
10109
  }
9811
10110
  }
9812
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
+
9813
10480
  // src/cli/commands/notify.ts
9814
10481
  function attachNotify(diagnostic2) {
9815
10482
  const notify = diagnostic2.command("notify").description("Internal notifications (fire-and-forget)");
@@ -9823,11 +10490,11 @@ function attachNotify(diagnostic2) {
9823
10490
  }
9824
10491
 
9825
10492
  // src/cli/commands/tmux-status.ts
9826
- import { execSync as execSync14 } from "child_process";
10493
+ import { execSync as execSync15 } from "child_process";
9827
10494
  function attachTmuxStatus(diagnostic2) {
9828
10495
  diagnostic2.command("tmux-status").description("Output session status dots for tmux status bar").action(() => {
9829
10496
  try {
9830
- const status = execSync14(
10497
+ const status = execSync15(
9831
10498
  "tmux show-option -gv @sisyphus_status",
9832
10499
  { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
9833
10500
  ).trim();
@@ -9839,8 +10506,8 @@ function attachTmuxStatus(diagnostic2) {
9839
10506
 
9840
10507
  // src/cli/commands/tmux-sessions.ts
9841
10508
  init_paths();
9842
- import { execSync as execSync15 } from "child_process";
9843
- import { readFileSync as readFileSync29, existsSync as existsSync27 } from "fs";
10509
+ import { execSync as execSync16 } from "child_process";
10510
+ import { readFileSync as readFileSync30, existsSync as existsSync29 } from "fs";
9844
10511
  var DOT_MAP = {
9845
10512
  "orchestrator:processing": { icon: "\u25CF", color: "#d4ad6a" },
9846
10513
  "orchestrator:idle": { icon: "\u25CF", color: "#d47766" },
@@ -9851,16 +10518,16 @@ var DOT_MAP = {
9851
10518
  };
9852
10519
  function readManifest() {
9853
10520
  const p = sessionsManifestPath();
9854
- if (!existsSync27(p)) return null;
10521
+ if (!existsSync29(p)) return null;
9855
10522
  try {
9856
- return JSON.parse(readFileSync29(p, "utf-8"));
10523
+ return JSON.parse(readFileSync30(p, "utf-8"));
9857
10524
  } catch {
9858
10525
  return null;
9859
10526
  }
9860
10527
  }
9861
10528
  function tmuxExec(cmd) {
9862
10529
  try {
9863
- return execSync15(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10530
+ return execSync16(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
9864
10531
  } catch {
9865
10532
  return null;
9866
10533
  }
@@ -9897,9 +10564,9 @@ if (nodeVersion < 22) {
9897
10564
  process.exit(1);
9898
10565
  }
9899
10566
  var program = new Command();
9900
- program.name("sisyphus").description("tmux-integrated orchestration daemon for Claude Code").version(
10567
+ program.name("sis").description("tmux-integrated orchestration daemon for Claude Code").version(
9901
10568
  JSON.parse(
9902
- readFileSync30(join26(dirname12(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
10569
+ readFileSync31(join27(dirname12(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
9903
10570
  ).version
9904
10571
  );
9905
10572
  program.configureHelp({
@@ -9940,6 +10607,7 @@ registerSegmentUnregister(segment);
9940
10607
  var admin = program.command("admin").description("Admin / setup commands");
9941
10608
  registerSetup(admin);
9942
10609
  registerSetupKeybind(admin);
10610
+ registerHomeInit(admin);
9943
10611
  registerDoctor(admin);
9944
10612
  registerInit(admin);
9945
10613
  registerUninstall(admin);
@@ -9952,27 +10620,28 @@ registerScratch(admin);
9952
10620
  registerReview(admin);
9953
10621
  registerCompanion(program);
9954
10622
  registerDeploy(program);
10623
+ registerCloud(program);
9955
10624
  var diagnostic = program.command("diagnostic", { hidden: true });
9956
10625
  attachNotify(diagnostic);
9957
10626
  attachTmuxStatus(diagnostic);
9958
10627
  attachTmuxSessions(diagnostic);
9959
10628
  program.addHelpText("after", `
9960
10629
  Examples:
9961
- $ sisyphus start "Implement auth system" Start a new session
9962
- $ sisyphus start "Build @reqs.md" -n auth Start with name + requirements
9963
- $ sisyphus status Check current sessions
9964
- $ sisyphus dashboard Open the TUI
9965
- $ sisyphus admin doctor Verify installation
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
9966
10635
 
9967
- Run 'sisyphus admin getting-started' for a complete usage guide.
10636
+ Run 'sis admin getting-started' for a complete usage guide.
9968
10637
  `);
9969
10638
  var args = process.argv.slice(2);
9970
10639
  var firstArg = args[0];
9971
10640
  var skipWelcome = ["admin", "help", "--help", "-h", "--version", "-V"];
9972
- if (!existsSync28(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
10641
+ if (!existsSync30(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
9973
10642
  mkdirSync14(globalDir(), { recursive: true });
9974
10643
  console.log("");
9975
- console.log(" Welcome to Sisyphus. Run 'sisyphus admin setup' to get started.");
10644
+ console.log(" Welcome to Sisyphus. Run 'sis admin setup' to get started.");
9976
10645
  console.log("");
9977
10646
  }
9978
10647
  program.parseAsync(process.argv).catch((err) => {