boxdown 1.0.0 → 1.2.1

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 +135 -12
  2. package/assets/devcontainer/README.md +65 -21
  3. package/assets/devcontainer/devcontainer.json +27 -15
  4. package/assets/devcontainer/hooks/initialize.sh +76 -22
  5. package/assets/devcontainer/hooks/post-create.sh +70 -12
  6. package/assets/devcontainer/hooks/post-start.sh +20 -13
  7. package/assets/devcontainer/ssh-config-install.sh +12 -3
  8. package/assets/devcontainer/start.sh +721 -44
  9. package/assets/devcontainer/utils/coding-agent-cli-update.sh +267 -3
  10. package/assets/devcontainer/utils/deps-install.sh +68 -0
  11. package/assets/devcontainer/utils/git-config-bootstrap.sh +128 -0
  12. package/assets/devcontainer/utils/git-signing-bootstrap.sh +109 -0
  13. package/assets/devcontainer/utils/python-bootstrap.sh +69 -0
  14. package/assets/devcontainer/utils/secret-env-bootstrap.sh +22 -0
  15. package/assets/devcontainer/utils/ssh-agent-proxy-bootstrap.sh +27 -0
  16. package/assets/devcontainer/utils/ssh-agent-proxy.mjs +39 -0
  17. package/assets/devcontainer/utils/ssh-bootstrap.sh +9 -0
  18. package/dist/bin/cli.cjs +1 -1
  19. package/dist/bin/cli.mjs +1 -1
  20. package/dist/main-BDgyf2t5.cjs +5758 -0
  21. package/dist/main-J4_2Up3o.mjs +5718 -0
  22. package/dist/main-J4_2Up3o.mjs.map +1 -0
  23. package/dist/main.cjs +5 -2
  24. package/dist/main.d.cts +501 -4
  25. package/dist/main.d.cts.map +1 -1
  26. package/dist/main.d.mts +501 -4
  27. package/dist/main.d.mts.map +1 -1
  28. package/dist/main.mjs +2 -2
  29. package/docs/README.md +1 -0
  30. package/docs/architecture.md +32 -0
  31. package/docs/development.md +13 -6
  32. package/docs/features/README.md +2 -0
  33. package/docs/features/commit-signing.md +94 -0
  34. package/docs/features/generated-config-and-state.md +73 -5
  35. package/docs/features/github-auth-refresh.md +15 -2
  36. package/docs/features/lifecycle.md +103 -11
  37. package/docs/features/setup.md +66 -0
  38. package/docs/features/ssh-config-and-proxy.md +228 -7
  39. package/docs/features/start-and-shell.md +45 -5
  40. package/docs/superpowers/plans/2026-07-11-default-commit-signing.md +110 -0
  41. package/docs/superpowers/plans/2026-07-11-progress-ci-regression.md +125 -0
  42. package/docs/superpowers/plans/2026-07-17-node-ssh-agent-proxy-signing.md +132 -0
  43. package/docs/superpowers/plans/2026-07-17-runtime-secret-environment.md +174 -0
  44. package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +416 -0
  45. package/docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md +43 -0
  46. package/docs/superpowers/specs/2026-07-17-node-ssh-agent-proxy-design.md +63 -0
  47. package/docs/superpowers/specs/2026-07-17-runtime-secret-environment-design.md +194 -0
  48. package/docs/testing.md +35 -2
  49. package/docs/todo.md +2 -2
  50. package/package.json +1 -1
  51. package/src/claude-app-config.ts +304 -0
  52. package/src/cli-style.ts +43 -0
  53. package/src/codex-app-config.ts +656 -0
  54. package/src/config.ts +80 -10
  55. package/src/constants.ts +12 -0
  56. package/src/devcontainer.ts +511 -64
  57. package/src/doctor.ts +292 -30
  58. package/src/git-signing.ts +267 -0
  59. package/src/interactive-prompts.ts +692 -0
  60. package/src/list.ts +16 -2
  61. package/src/logging.ts +164 -0
  62. package/src/main.ts +1214 -63
  63. package/src/metadata.ts +52 -3
  64. package/src/package-info.ts +18 -0
  65. package/src/paths.ts +71 -11
  66. package/src/process.ts +80 -2
  67. package/src/progress.ts +593 -0
  68. package/src/purge.ts +216 -0
  69. package/src/shell.ts +25 -0
  70. package/src/ssh-config.ts +134 -16
  71. package/src/ssh-install-targets.ts +111 -0
  72. package/src/ssh-key.ts +53 -13
  73. package/src/status.ts +5 -0
  74. package/dist/main-BuEptwlL.cjs +0 -1707
  75. package/dist/main-ZFTrSVgt.mjs +0 -1685
  76. package/dist/main-ZFTrSVgt.mjs.map +0 -1
@@ -17,7 +17,7 @@
17
17
  # @options
18
18
  # --shell Start the dev container and attach an interactive shell (default).
19
19
  # --ssh-proxy Start/reuse the dev container and proxy SSH over docker exec.
20
- # ssh-config install
20
+ # ssh install
21
21
  # Install/update the host SSH config alias and exit.
22
22
  # --refresh-gh-token
23
23
  # Start/reuse the dev container, then refresh container GitHub CLI
@@ -27,11 +27,12 @@
27
27
  # when the dev container is already running.
28
28
  # --recreate Remove the existing dev container for this workspace before `up`, so
29
29
  # changes to runArgs (or other create-time settings) take effect.
30
+ # --verbose Stream raw devcontainer, Docker, and hook output.
30
31
  # --help, -h Print usage and exit.
31
32
  #
32
33
  # @example
33
34
  # .devcontainer/start.sh
34
- # .devcontainer/start.sh ssh-config install
35
+ # .devcontainer/start.sh ssh install
35
36
  # .devcontainer/start.sh --ssh-proxy
36
37
  # .devcontainer/start.sh --refresh-gh-token
37
38
  # .devcontainer/start.sh --refresh-gh-token-running
@@ -51,7 +52,13 @@ SSH_KEY_DIR="${DEVCONTAINER_SSH_KEY_DIR:-${SCRIPT_DIR}/.ssh}"
51
52
  SSH_KEY_PATH="${DEVCONTAINER_SSH_KEY_PATH:-${SSH_KEY_DIR}/id_ed25519}"
52
53
  MODE="shell"
53
54
  RECREATE=false
55
+ VERBOSE=false
54
56
  CONTAINER_ID=""
57
+ PROGRESS_STEP_IDS=()
58
+ PROGRESS_STEP_LABELS=()
59
+ PROGRESS_STEP_STATES=()
60
+ PROGRESS_RENDERED_STEP_LINE_COUNT=0
61
+ PROGRESS_CHECKLIST_PID=""
55
62
 
56
63
  usage() {
57
64
  cat <<EOF
@@ -74,10 +81,11 @@ Options:
74
81
  when the dev container is already running.
75
82
  --recreate Remove the existing dev container for this workspace before starting,
76
83
  so Docker picks up new settings (e.g. runArgs / port mappings).
84
+ --verbose Stream raw devcontainer, Docker, and hook output.
77
85
  --help, -h Show this help and exit.
78
86
 
79
87
  Commands:
80
- ssh-config install
88
+ ssh install
81
89
  Install/update the host SSH config alias and exit.
82
90
 
83
91
  Notes:
@@ -100,6 +108,382 @@ log() {
100
108
  fi
101
109
  }
102
110
 
111
+ log_raw() {
112
+ if [ "$MODE" = "ssh-proxy" ]; then
113
+ printf '%b' "$*" >&2
114
+ else
115
+ printf '%b' "$*"
116
+ fi
117
+ }
118
+
119
+ supports_progress_tty() {
120
+ if [ "$MODE" = "ssh-proxy" ]; then
121
+ [ -t 2 ]
122
+ else
123
+ [ -t 1 ]
124
+ fi
125
+ }
126
+
127
+ style() {
128
+ local code="$1"
129
+ local text="$2"
130
+
131
+ if supports_progress_tty; then
132
+ printf '\033[%sm%s\033[0m' "$code" "$text"
133
+ else
134
+ printf '%s' "$text"
135
+ fi
136
+ }
137
+
138
+ style_bold() { style 1 "$1"; }
139
+ style_cyan() { style 36 "$1"; }
140
+ style_dim() { style 2 "$1"; }
141
+ style_green() { style 32 "$1"; }
142
+ progress_rail() { style_cyan "│"; }
143
+
144
+ progress_section() {
145
+ if [ "$VERBOSE" = true ]; then
146
+ return 0
147
+ fi
148
+
149
+ log "$(style_cyan "◆") $(style_bold "$*")"
150
+ }
151
+
152
+ progress_end() {
153
+ if [ "$VERBOSE" = true ]; then
154
+ return 0
155
+ fi
156
+
157
+ stop_spinner clear
158
+ progress_stop_checklist_timer
159
+ log "$(style_cyan "└")"
160
+ PROGRESS_STEP_IDS=()
161
+ PROGRESS_STEP_LABELS=()
162
+ PROGRESS_STEP_STATES=()
163
+ PROGRESS_RENDERED_STEP_LINE_COUNT=0
164
+ }
165
+
166
+ progress_item() {
167
+ if [ "$VERBOSE" = true ]; then
168
+ return 0
169
+ fi
170
+
171
+ log "$(progress_rail) $(style_green "■") $*"
172
+ }
173
+
174
+ progress_running() {
175
+ if [ "$VERBOSE" = true ]; then
176
+ return 0
177
+ fi
178
+
179
+ log "$(progress_rail) $(style_cyan "◒") $*"
180
+ }
181
+
182
+ progress_detail() {
183
+ if [ "$VERBOSE" = true ]; then
184
+ return 0
185
+ fi
186
+
187
+ log "$(progress_rail) $(style_dim "$*")"
188
+ }
189
+
190
+ progress_warn() {
191
+ if [ "$VERBOSE" = true ]; then
192
+ return 0
193
+ fi
194
+
195
+ log "$(progress_rail) $(style_dim "!") $*"
196
+ }
197
+
198
+ progress_has_steps() {
199
+ [ "${#PROGRESS_STEP_IDS[@]}" -gt 0 ]
200
+ }
201
+
202
+ progress_step_index() {
203
+ local id="$1"
204
+ local index
205
+
206
+ for index in "${!PROGRESS_STEP_IDS[@]}"; do
207
+ if [ "${PROGRESS_STEP_IDS[$index]}" = "$id" ]; then
208
+ printf '%s\n' "$index"
209
+ return 0
210
+ fi
211
+ done
212
+
213
+ return 1
214
+ }
215
+
216
+ progress_has_step() {
217
+ progress_step_index "$1" >/dev/null 2>&1
218
+ }
219
+
220
+ progress_set_steps() {
221
+ local step
222
+ local id
223
+ local label
224
+
225
+ if [ "$VERBOSE" = true ]; then
226
+ return 0
227
+ fi
228
+
229
+ progress_stop_checklist_timer
230
+ PROGRESS_STEP_IDS=()
231
+ PROGRESS_STEP_LABELS=()
232
+ PROGRESS_STEP_STATES=()
233
+ PROGRESS_RENDERED_STEP_LINE_COUNT=0
234
+
235
+ for step in "$@"; do
236
+ id="${step%%:*}"
237
+ label="${step#*:}"
238
+ PROGRESS_STEP_IDS+=("$id")
239
+ PROGRESS_STEP_LABELS+=("$label")
240
+ PROGRESS_STEP_STATES+=("pending")
241
+ done
242
+
243
+ progress_render_checklist
244
+ }
245
+
246
+ progress_step_mark() {
247
+ local state="$1"
248
+ local frame="$2"
249
+
250
+ case "$state" in
251
+ running)
252
+ style_cyan "$frame"
253
+ ;;
254
+ complete)
255
+ style_green "✔"
256
+ ;;
257
+ failed)
258
+ style_dim "!"
259
+ ;;
260
+ *)
261
+ style_dim "□"
262
+ ;;
263
+ esac
264
+ }
265
+
266
+ progress_step_line() {
267
+ local index="$1"
268
+ local frame="$2"
269
+ local state="${PROGRESS_STEP_STATES[$index]}"
270
+ local label="${PROGRESS_STEP_LABELS[$index]}"
271
+
272
+ if [ "$state" = "skipped" ]; then
273
+ label="$(style_dim "$label")"
274
+ fi
275
+
276
+ printf '%s %s %s' "$(progress_rail)" "$(progress_step_mark "$state" "$frame")" "$label"
277
+ }
278
+
279
+ progress_render_checklist_frame() {
280
+ local frame="$1"
281
+ local index
282
+
283
+ if [ "$VERBOSE" = true ] || ! progress_has_steps; then
284
+ return 0
285
+ fi
286
+
287
+ if supports_progress_tty; then
288
+ if [ "$PROGRESS_RENDERED_STEP_LINE_COUNT" -gt 0 ]; then
289
+ log_raw "\033[${PROGRESS_RENDERED_STEP_LINE_COUNT}A"
290
+ fi
291
+
292
+ for index in "${!PROGRESS_STEP_IDS[@]}"; do
293
+ log_raw "\033[2K\r$(progress_step_line "$index" "$frame")\n"
294
+ done
295
+
296
+ PROGRESS_RENDERED_STEP_LINE_COUNT="${#PROGRESS_STEP_IDS[@]}"
297
+ return 0
298
+ fi
299
+
300
+ if [ "$PROGRESS_RENDERED_STEP_LINE_COUNT" -eq 0 ]; then
301
+ for index in "${!PROGRESS_STEP_IDS[@]}"; do
302
+ log "$(progress_step_line "$index" "$frame")"
303
+ done
304
+ PROGRESS_RENDERED_STEP_LINE_COUNT="${#PROGRESS_STEP_IDS[@]}"
305
+ fi
306
+ }
307
+
308
+ progress_render_checklist() {
309
+ progress_render_checklist_frame "◐"
310
+ }
311
+
312
+ progress_update_step() {
313
+ local id="$1"
314
+ local state="$2"
315
+ local index
316
+
317
+ index="$(progress_step_index "$id")" || return 0
318
+ PROGRESS_STEP_STATES[$index]="$state"
319
+ progress_render_checklist
320
+ }
321
+
322
+ progress_start_step() {
323
+ progress_update_step "$1" "running"
324
+ progress_start_checklist_timer
325
+ }
326
+
327
+ progress_complete_step() {
328
+ progress_stop_checklist_timer
329
+ progress_update_step "$1" "complete"
330
+ }
331
+
332
+ progress_fail_step() {
333
+ progress_stop_checklist_timer
334
+ progress_update_step "$1" "failed"
335
+ }
336
+
337
+ progress_skip_step() {
338
+ progress_stop_checklist_timer
339
+ progress_update_step "$1" "skipped"
340
+ }
341
+
342
+ progress_any_running_step() {
343
+ local state
344
+
345
+ for state in "${PROGRESS_STEP_STATES[@]}"; do
346
+ if [ "$state" = "running" ]; then
347
+ return 0
348
+ fi
349
+ done
350
+
351
+ return 1
352
+ }
353
+
354
+ progress_start_checklist_timer() {
355
+ if [ "$VERBOSE" = true ] || ! supports_progress_tty || [ -n "$PROGRESS_CHECKLIST_PID" ]; then
356
+ return 0
357
+ fi
358
+
359
+ if ! progress_any_running_step; then
360
+ return 0
361
+ fi
362
+
363
+ (
364
+ frames=("◒" "◐" "◓" "◑")
365
+ index=0
366
+ while true; do
367
+ progress_render_checklist_frame "${frames[$index]}"
368
+ index=$(( (index + 1) % ${#frames[@]} ))
369
+ sleep 0.12
370
+ done
371
+ ) &
372
+ PROGRESS_CHECKLIST_PID="$!"
373
+ }
374
+
375
+ progress_stop_checklist_timer() {
376
+ if [ -z "$PROGRESS_CHECKLIST_PID" ]; then
377
+ return 0
378
+ fi
379
+
380
+ kill "$PROGRESS_CHECKLIST_PID" >/dev/null 2>&1 || true
381
+ wait "$PROGRESS_CHECKLIST_PID" 2>/dev/null || true
382
+ PROGRESS_CHECKLIST_PID=""
383
+ }
384
+
385
+ SPINNER_PID=""
386
+ SPINNER_LABEL=""
387
+ SPINNER_TTY=false
388
+
389
+ start_spinner() {
390
+ local label="$*"
391
+
392
+ if [ "$VERBOSE" = true ]; then
393
+ return 0
394
+ fi
395
+
396
+ stop_spinner clear
397
+ SPINNER_LABEL="$label"
398
+
399
+ if ! supports_progress_tty; then
400
+ SPINNER_TTY=false
401
+ progress_running "$label"
402
+ return 0
403
+ fi
404
+
405
+ SPINNER_TTY=true
406
+ (
407
+ frames=("◒" "◐" "◓" "◑")
408
+ index=0
409
+ while true; do
410
+ log_raw "\r\033[2K$(progress_rail) $(style_cyan "${frames[$index]}") ${label}"
411
+ index=$(( (index + 1) % ${#frames[@]} ))
412
+ sleep 0.12
413
+ done
414
+ ) &
415
+ SPINNER_PID="$!"
416
+ }
417
+
418
+ stop_spinner() {
419
+ local status="${1:-clear}"
420
+ local label="$SPINNER_LABEL"
421
+
422
+ if [ -n "$SPINNER_PID" ]; then
423
+ kill "$SPINNER_PID" >/dev/null 2>&1 || true
424
+ wait "$SPINNER_PID" 2>/dev/null || true
425
+ fi
426
+
427
+ if [ "$SPINNER_TTY" = true ]; then
428
+ log_raw "\r\033[2K"
429
+ fi
430
+
431
+ SPINNER_PID=""
432
+ SPINNER_LABEL=""
433
+ SPINNER_TTY=false
434
+
435
+ if [ "$status" = "complete" ] && [ -n "$label" ]; then
436
+ progress_item "$label"
437
+ fi
438
+ }
439
+
440
+ trap 'stop_spinner clear; progress_stop_checklist_timer' EXIT
441
+
442
+ export_progress_env() {
443
+ if [ "$VERBOSE" = true ]; then
444
+ export BOXDOWN_VERBOSE=1
445
+ export BOXDOWN_PROGRESS=0
446
+ else
447
+ export BOXDOWN_VERBOSE=0
448
+ export BOXDOWN_PROGRESS=1
449
+ fi
450
+ }
451
+
452
+ print_progress_markers() {
453
+ local output="$1"
454
+ local line
455
+
456
+ if progress_has_steps; then
457
+ return 0
458
+ fi
459
+
460
+ while IFS= read -r line; do
461
+ case "$line" in
462
+ BOXDOWN_PROGRESS:*)
463
+ progress_item "${line#BOXDOWN_PROGRESS: }"
464
+ ;;
465
+ esac
466
+ done <<< "$output"
467
+ }
468
+
469
+ print_command_failure() {
470
+ local label="$1"
471
+ local code="$2"
472
+ local output="$3"
473
+ local filtered
474
+
475
+ progress_warn "${label} failed with exit code ${code}."
476
+ log "$(progress_rail) $(style_dim "Rerun with --verbose to see full command output.")"
477
+
478
+ filtered="$(printf '%s\n' "$output" | sed '/^[[:space:]]*BOXDOWN_PROGRESS:/d' | sed '/^[[:space:]]*$/d' | tail -n 20)"
479
+ if [ -n "$filtered" ]; then
480
+ log "$(progress_rail) $(style_dim "output tail:")"
481
+ while IFS= read -r line; do
482
+ progress_detail "$line"
483
+ done <<< "$filtered"
484
+ fi
485
+ }
486
+
103
487
  die() {
104
488
  printf '%s\n' "$*" >&2
105
489
  exit 1
@@ -160,12 +544,32 @@ ensure_host_ssh_key() {
160
544
  chmod 0700 "$SSH_KEY_DIR"
161
545
 
162
546
  if [ ! -f "$SSH_KEY_PATH" ]; then
163
- log "Generating devcontainer SSH identity: $SSH_KEY_PATH"
164
- ssh-keygen -t ed25519 -f "$SSH_KEY_PATH" -N "" -C "${REPO_NAME}-devcontainer" >/dev/null
547
+ if [ "$VERBOSE" = true ]; then
548
+ log "Generating devcontainer SSH identity: $SSH_KEY_PATH"
549
+ ssh-keygen -t ed25519 -f "$SSH_KEY_PATH" -N "" -C "${REPO_NAME}-devcontainer" >/dev/null
550
+ elif progress_has_step "ssh-identity"; then
551
+ ssh-keygen -t ed25519 -f "$SSH_KEY_PATH" -N "" -C "${REPO_NAME}-devcontainer" >/dev/null 2>&1 || return $?
552
+ else
553
+ progress_detail "$SSH_KEY_PATH"
554
+ start_spinner "Generating devcontainer SSH identity"
555
+ if ssh-keygen -t ed25519 -f "$SSH_KEY_PATH" -N "" -C "${REPO_NAME}-devcontainer" >/dev/null 2>&1; then
556
+ stop_spinner complete
557
+ else
558
+ code=$?
559
+ stop_spinner clear
560
+ return "$code"
561
+ fi
562
+ fi
165
563
  fi
166
564
 
167
565
  if [ ! -f "${SSH_KEY_PATH}.pub" ]; then
168
- ssh-keygen -y -f "$SSH_KEY_PATH" > "${SSH_KEY_PATH}.pub"
566
+ if [ "$VERBOSE" = true ]; then
567
+ ssh-keygen -y -f "$SSH_KEY_PATH" > "${SSH_KEY_PATH}.pub"
568
+ elif progress_has_step "ssh-identity"; then
569
+ ssh-keygen -y -f "$SSH_KEY_PATH" > "${SSH_KEY_PATH}.pub" 2>/dev/null || return $?
570
+ else
571
+ ssh-keygen -y -f "$SSH_KEY_PATH" > "${SSH_KEY_PATH}.pub"
572
+ fi
169
573
  fi
170
574
 
171
575
  chmod 0600 "$SSH_KEY_PATH"
@@ -184,34 +588,94 @@ install_ssh_config_alias() {
184
588
  start_devcontainer() {
185
589
  local up_output
186
590
  local up_args
591
+ local up_code
187
592
 
188
593
  if [ "$MODE" = "ssh-proxy" ] && [ "$RECREATE" = false ]; then
189
594
  CONTAINER_ID="$(find_running_container_id)"
190
595
  if [ -n "$CONTAINER_ID" ]; then
191
- log "Using running devcontainer for: $WORKSPACE_FOLDER"
596
+ if [ "$VERBOSE" = true ]; then
597
+ log "Using running devcontainer for: $WORKSPACE_FOLDER"
598
+ elif progress_has_step "devcontainer-start"; then
599
+ progress_start_step "devcontainer-start"
600
+ progress_complete_step "devcontainer-start"
601
+ else
602
+ progress_item "Using running devcontainer"
603
+ progress_detail "$CONTAINER_ID"
604
+ fi
192
605
  return 0
193
606
  fi
194
607
  fi
195
608
 
196
- log "Starting devcontainer for: $WORKSPACE_FOLDER"
609
+ if [ "$VERBOSE" = true ]; then
610
+ log "Starting devcontainer for: $WORKSPACE_FOLDER"
611
+ elif progress_has_step "devcontainer-start"; then
612
+ progress_start_step "devcontainer-start"
613
+ else
614
+ start_spinner "Starting devcontainer"
615
+ fi
197
616
 
198
617
  up_args=(--workspace-folder "$WORKSPACE_FOLDER")
199
618
  if [ "$RECREATE" = true ]; then
200
619
  up_args+=(--remove-existing-container)
201
- log "Removing existing dev container so create-time settings (e.g. runArgs) apply."
620
+ if [ "$VERBOSE" = true ]; then
621
+ log "Removing existing dev container so create-time settings (e.g. runArgs) apply."
622
+ elif progress_has_step "devcontainer-start"; then
623
+ :
624
+ else
625
+ stop_spinner clear
626
+ progress_item "Removing existing devcontainer before start"
627
+ start_spinner "Starting devcontainer"
628
+ fi
202
629
  fi
203
630
 
631
+ export_progress_env
632
+
204
633
  if [ "$MODE" = "ssh-proxy" ]; then
205
- if ! up_output="$(devcontainer_cli up "${up_args[@]}" </dev/null 2>&1)"; then
634
+ if up_output="$(devcontainer_cli up "${up_args[@]}" </dev/null 2>&1)"; then
635
+ :
636
+ else
637
+ up_code=$?
638
+ if [ "$VERBOSE" = true ]; then
639
+ [ -z "$up_output" ] || log "$up_output"
640
+ else
641
+ if progress_has_step "devcontainer-start"; then
642
+ progress_fail_step "devcontainer-start"
643
+ else
644
+ stop_spinner clear
645
+ fi
646
+ print_progress_markers "$up_output"
647
+ print_command_failure "devcontainer up" "$up_code" "$up_output"
648
+ fi
649
+ return "$up_code"
650
+ fi
651
+ elif up_output="$(devcontainer_cli up "${up_args[@]}" 2>&1)"; then
652
+ :
653
+ else
654
+ up_code=$?
655
+ if [ "$VERBOSE" = true ]; then
206
656
  [ -z "$up_output" ] || log "$up_output"
207
- return 1
657
+ else
658
+ if progress_has_step "devcontainer-start"; then
659
+ progress_fail_step "devcontainer-start"
660
+ else
661
+ stop_spinner clear
662
+ fi
663
+ print_progress_markers "$up_output"
664
+ print_command_failure "devcontainer up" "$up_code" "$up_output"
208
665
  fi
209
- elif ! up_output="$(devcontainer_cli up "${up_args[@]}" 2>&1)"; then
210
- [ -z "$up_output" ] || log "$up_output"
211
- return 1
666
+ return "$up_code"
212
667
  fi
213
668
 
214
- log "$up_output"
669
+ if [ "$VERBOSE" = true ]; then
670
+ log "$up_output"
671
+ elif progress_has_step "devcontainer-start"; then
672
+ print_progress_markers "$up_output"
673
+ progress_complete_step "devcontainer-start"
674
+ else
675
+ stop_spinner clear
676
+ print_progress_markers "$up_output"
677
+ progress_item "Starting devcontainer"
678
+ fi
215
679
 
216
680
  CONTAINER_ID="$(printf '%s\n' "$up_output" | parse_container_id_from_up_output)"
217
681
  if [ -z "$CONTAINER_ID" ]; then
@@ -219,6 +683,9 @@ start_devcontainer() {
219
683
  fi
220
684
 
221
685
  if [ -z "$CONTAINER_ID" ]; then
686
+ if progress_has_step "devcontainer-start"; then
687
+ progress_fail_step "devcontainer-start"
688
+ fi
222
689
  die "Could not resolve devcontainer ID for: $WORKSPACE_FOLDER"
223
690
  fi
224
691
  }
@@ -244,28 +711,109 @@ host_gh_token_or_empty() {
244
711
  refresh_container_gh_auth() {
245
712
  local token
246
713
 
714
+ if progress_has_step "gh-token-read"; then
715
+ progress_start_step "gh-token-read"
716
+ fi
717
+
247
718
  token="$(host_gh_token_or_empty)"
719
+ if progress_has_step "gh-token-read"; then
720
+ progress_complete_step "gh-token-read"
721
+ fi
722
+
248
723
  if [ -z "$token" ]; then
724
+ progress_skip_step "gh-auth-refresh"
725
+ progress_skip_step "gh-git-auth"
726
+ progress_skip_step "gh-auth-verify"
727
+ if [ "$VERBOSE" = false ]; then
728
+ progress_warn "Host GitHub CLI token unavailable; skipping container auth refresh."
729
+ fi
249
730
  return 0
250
731
  fi
251
732
 
252
- if ! printf '%s\n' "$token" | devcontainer_cli exec \
253
- --workspace-folder "$WORKSPACE_FOLDER" \
254
- -- gh auth login --hostname github.com --git-protocol https --with-token --insecure-storage >/dev/null 2>&1; then
255
- log "Warning: could not refresh GitHub CLI auth inside the devcontainer."
256
- return 0
733
+ if [ "$VERBOSE" = true ]; then
734
+ if ! printf '%s\n' "$token" | devcontainer_cli exec \
735
+ --workspace-folder "$WORKSPACE_FOLDER" \
736
+ -- gh auth login --hostname github.com --git-protocol https --with-token --insecure-storage >/dev/null 2>&1; then
737
+ log "Warning: could not refresh GitHub CLI auth inside the devcontainer."
738
+ return 0
739
+ fi
740
+ else
741
+ if progress_has_step "gh-auth-refresh"; then
742
+ progress_start_step "gh-auth-refresh"
743
+ else
744
+ start_spinner "Refreshing GitHub CLI auth inside the devcontainer"
745
+ fi
746
+
747
+ if printf '%s\n' "$token" | devcontainer_cli exec \
748
+ --workspace-folder "$WORKSPACE_FOLDER" \
749
+ -- gh auth login --hostname github.com --git-protocol https --with-token --insecure-storage >/dev/null 2>&1; then
750
+ if progress_has_step "gh-auth-refresh"; then
751
+ progress_complete_step "gh-auth-refresh"
752
+ else
753
+ stop_spinner complete
754
+ fi
755
+ else
756
+ if progress_has_step "gh-auth-refresh"; then
757
+ progress_fail_step "gh-auth-refresh"
758
+ else
759
+ stop_spinner clear
760
+ fi
761
+ progress_skip_step "gh-git-auth"
762
+ progress_skip_step "gh-auth-verify"
763
+ progress_warn "Could not refresh GitHub CLI auth inside the devcontainer."
764
+ return 0
765
+ fi
766
+ fi
767
+
768
+ if progress_has_step "gh-git-auth"; then
769
+ progress_start_step "gh-git-auth"
770
+ else
771
+ progress_item "Configuring workspace GitHub Git auth"
257
772
  fi
258
773
 
259
774
  if ! configure_container_github_git_auth; then
260
- log "Warning: GitHub CLI auth refreshed, but GitHub Git auth was not configured for this workspace."
775
+ progress_fail_step "gh-git-auth"
776
+ if [ "$VERBOSE" = true ]; then
777
+ log "Warning: GitHub CLI auth refreshed, but GitHub Git auth was not configured for this workspace."
778
+ else
779
+ progress_warn "GitHub CLI auth refreshed, but GitHub Git auth was not configured for this workspace."
780
+ fi
781
+ elif progress_has_step "gh-git-auth"; then
782
+ progress_complete_step "gh-git-auth"
261
783
  fi
262
784
 
263
- if devcontainer_cli exec \
264
- --workspace-folder "$WORKSPACE_FOLDER" \
265
- -- gh auth status --hostname github.com >/dev/null 2>&1; then
266
- log "GitHub CLI auth refreshed inside the devcontainer."
785
+ if [ "$VERBOSE" = true ]; then
786
+ if devcontainer_cli exec \
787
+ --workspace-folder "$WORKSPACE_FOLDER" \
788
+ -- gh auth status --hostname github.com >/dev/null 2>&1; then
789
+ log "GitHub CLI auth refreshed inside the devcontainer."
790
+ else
791
+ log "Warning: GitHub CLI auth refresh completed, but verification failed."
792
+ fi
267
793
  else
268
- log "Warning: GitHub CLI auth refresh completed, but verification failed."
794
+ if progress_has_step "gh-auth-verify"; then
795
+ progress_start_step "gh-auth-verify"
796
+ else
797
+ start_spinner "Verifying GitHub CLI auth inside the devcontainer"
798
+ fi
799
+
800
+ if devcontainer_cli exec \
801
+ --workspace-folder "$WORKSPACE_FOLDER" \
802
+ -- gh auth status --hostname github.com >/dev/null 2>&1; then
803
+ if progress_has_step "gh-auth-verify"; then
804
+ progress_complete_step "gh-auth-verify"
805
+ else
806
+ stop_spinner complete
807
+ progress_item "GitHub CLI auth refreshed inside the devcontainer"
808
+ fi
809
+ else
810
+ if progress_has_step "gh-auth-verify"; then
811
+ progress_fail_step "gh-auth-verify"
812
+ else
813
+ stop_spinner clear
814
+ fi
815
+ progress_warn "GitHub CLI auth refresh completed, but verification failed."
816
+ fi
269
817
  fi
270
818
  }
271
819
 
@@ -353,17 +901,51 @@ EOF
353
901
  }
354
902
 
355
903
  run_refresh_gh_token() {
356
- start_devcontainer
357
- refresh_container_gh_auth
904
+ progress_section "Boxdown GitHub auth refresh"
905
+ progress_detail "Workspace: $WORKSPACE_FOLDER"
906
+ progress_set_steps \
907
+ "devcontainer-start:Starting devcontainer" \
908
+ "gh-token-read:Reading host GitHub CLI token" \
909
+ "gh-auth-refresh:Refreshing GitHub CLI auth inside the devcontainer" \
910
+ "gh-git-auth:Configuring workspace GitHub Git auth" \
911
+ "gh-auth-verify:Verifying GitHub CLI auth inside the devcontainer"
912
+ start_devcontainer || {
913
+ code=$?
914
+ progress_end
915
+ return "$code"
916
+ }
917
+ refresh_container_gh_auth || {
918
+ code=$?
919
+ progress_end
920
+ return "$code"
921
+ }
922
+ progress_end
358
923
  }
359
924
 
360
925
  run_refresh_gh_token_running() {
926
+ progress_section "Boxdown GitHub auth refresh"
927
+ progress_detail "Workspace: $WORKSPACE_FOLDER"
928
+ progress_set_steps \
929
+ "devcontainer-running:Using running devcontainer" \
930
+ "gh-token-read:Reading host GitHub CLI token" \
931
+ "gh-auth-refresh:Refreshing GitHub CLI auth inside the devcontainer" \
932
+ "gh-git-auth:Configuring workspace GitHub Git auth" \
933
+ "gh-auth-verify:Verifying GitHub CLI auth inside the devcontainer"
361
934
  CONTAINER_ID="$(find_running_container_id)"
362
935
  if [ -z "$CONTAINER_ID" ]; then
936
+ progress_fail_step "devcontainer-running"
937
+ progress_end
363
938
  die "No running devcontainer found for: $WORKSPACE_FOLDER"
364
939
  fi
365
940
 
366
- refresh_container_gh_auth
941
+ progress_start_step "devcontainer-running"
942
+ progress_complete_step "devcontainer-running"
943
+ refresh_container_gh_auth || {
944
+ code=$?
945
+ progress_end
946
+ return "$code"
947
+ }
948
+ progress_end
367
949
  }
368
950
 
369
951
  print_port_hint() {
@@ -387,24 +969,98 @@ print_port_hint() {
387
969
 
388
970
  open_shell() {
389
971
  echo "Dropping into container shell..."
390
- # Resolve TERM to something the container's terminfo knows about.
391
- # Terminals like kitty, ghostty, alacritty set custom TERM values the container won't have.
392
- # Fall back to xterm-256color (truecolor still works via COLORTERM=truecolor).
393
- if ! infocmp "${TERM:-xterm-256color}" &>/dev/null 2>&1; then
394
- TERM=xterm-256color
395
- fi
396
- TERM="${TERM:-xterm-256color}" devcontainer_cli exec --workspace-folder "$WORKSPACE_FOLDER" -- env TERM="${TERM:-xterm-256color}" COLORTERM=truecolor bash
972
+ TERM="${TERM:-xterm-256color}" devcontainer_cli exec --workspace-folder "$WORKSPACE_FOLDER" -- env TERM="${TERM:-xterm-256color}" COLORTERM=truecolor bash -c '
973
+ if ! infocmp "${TERM:-xterm-256color}" >/dev/null 2>&1; then
974
+ export TERM=xterm-256color
975
+ fi
976
+ export COLORTERM="${COLORTERM:-truecolor}"
977
+ exec bash -i
978
+ '
397
979
  }
398
980
 
399
981
  ensure_container_sshd_runtime() {
400
- devcontainer_cli exec --workspace-folder "$WORKSPACE_FOLDER" -- bash .devcontainer/utils/ssh-bootstrap.sh runtime </dev/null >&2
982
+ local output
983
+ local code
984
+ local -a env_args
985
+
986
+ if [ "$VERBOSE" = true ]; then
987
+ env_args=(BOXDOWN_VERBOSE=1 BOXDOWN_PROGRESS=0)
988
+ else
989
+ env_args=(BOXDOWN_VERBOSE=0 BOXDOWN_PROGRESS=1)
990
+ fi
991
+
992
+ if [ "$VERBOSE" = true ]; then
993
+ devcontainer_cli exec --workspace-folder "$WORKSPACE_FOLDER" -- env "${env_args[@]}" bash .devcontainer/utils/ssh-bootstrap.sh runtime </dev/null >&2
994
+ return $?
995
+ fi
996
+
997
+ if progress_has_step "ssh-runtime"; then
998
+ progress_start_step "ssh-runtime"
999
+ else
1000
+ start_spinner "Preparing container SSH runtime"
1001
+ fi
1002
+
1003
+ if output="$(devcontainer_cli exec --workspace-folder "$WORKSPACE_FOLDER" -- env "${env_args[@]}" bash .devcontainer/utils/ssh-bootstrap.sh runtime </dev/null 2>&1)"; then
1004
+ if progress_has_step "ssh-runtime"; then
1005
+ progress_complete_step "ssh-runtime"
1006
+ else
1007
+ stop_spinner clear
1008
+ fi
1009
+ print_progress_markers "$output"
1010
+ if ! progress_has_step "ssh-runtime"; then
1011
+ progress_item "Preparing container SSH runtime"
1012
+ fi
1013
+ return 0
1014
+ fi
1015
+
1016
+ code=$?
1017
+ if progress_has_step "ssh-runtime"; then
1018
+ progress_fail_step "ssh-runtime"
1019
+ else
1020
+ stop_spinner clear
1021
+ fi
1022
+ print_progress_markers "$output"
1023
+ print_command_failure "prepare SSH runtime" "$code" "$output"
1024
+ return "$code"
401
1025
  }
402
1026
 
403
1027
  run_ssh_proxy() {
404
- install_ssh_config_alias --quiet >&2
405
- ensure_host_ssh_key
406
- start_devcontainer
407
- ensure_container_sshd_runtime
1028
+ progress_section "Boxdown SSH proxy"
1029
+ progress_detail "Workspace: $WORKSPACE_FOLDER"
1030
+ progress_set_steps \
1031
+ "ssh-alias:Updating SSH alias" \
1032
+ "ssh-identity:Preparing SSH identity" \
1033
+ "devcontainer-start:Starting devcontainer" \
1034
+ "ssh-runtime:Preparing container SSH runtime"
1035
+ progress_start_step "ssh-alias"
1036
+ if install_ssh_config_alias --quiet >&2; then
1037
+ progress_complete_step "ssh-alias"
1038
+ else
1039
+ progress_fail_step "ssh-alias"
1040
+ progress_end
1041
+ return 1
1042
+ fi
1043
+
1044
+ progress_start_step "ssh-identity"
1045
+ if ensure_host_ssh_key; then
1046
+ progress_complete_step "ssh-identity"
1047
+ else
1048
+ progress_fail_step "ssh-identity"
1049
+ progress_end
1050
+ return 1
1051
+ fi
1052
+
1053
+ start_devcontainer || {
1054
+ code=$?
1055
+ progress_end
1056
+ return "$code"
1057
+ }
1058
+ ensure_container_sshd_runtime || {
1059
+ code=$?
1060
+ progress_end
1061
+ return "$code"
1062
+ }
1063
+ progress_end
408
1064
 
409
1065
  exec docker exec -i "$CONTAINER_ID" /usr/sbin/sshd -i \
410
1066
  -o LogLevel=QUIET \
@@ -417,7 +1073,16 @@ run_ssh_proxy() {
417
1073
  }
418
1074
 
419
1075
  run_shell() {
420
- start_devcontainer
1076
+ progress_section "Boxdown start"
1077
+ progress_detail "Workspace: $WORKSPACE_FOLDER"
1078
+ progress_set_steps \
1079
+ "devcontainer-start:Starting devcontainer"
1080
+ start_devcontainer || {
1081
+ code=$?
1082
+ progress_end
1083
+ return "$code"
1084
+ }
1085
+ progress_end
421
1086
  print_port_hint
422
1087
  open_shell
423
1088
  }
@@ -436,14 +1101,14 @@ while [ $# -gt 0 ]; do
436
1101
  MODE="ssh-proxy"
437
1102
  shift
438
1103
  ;;
439
- ssh-config)
1104
+ ssh)
440
1105
  MODE="ssh-config-install"
441
1106
  if [ "${2:-}" = "install" ]; then
442
1107
  shift 2
443
1108
  elif [ "$#" -eq 1 ] || [ "${2#-}" != "$2" ]; then
444
1109
  shift
445
1110
  else
446
- die "Unknown command: ssh-config ${2}"
1111
+ die "Unknown command: ssh ${2}"
447
1112
  fi
448
1113
  ;;
449
1114
  --refresh-gh-token)
@@ -458,6 +1123,10 @@ while [ $# -gt 0 ]; do
458
1123
  RECREATE=true
459
1124
  shift
460
1125
  ;;
1126
+ --verbose)
1127
+ VERBOSE=true
1128
+ shift
1129
+ ;;
461
1130
  *)
462
1131
  echo "Unknown option: $1" >&2
463
1132
  echo "Run with --help for usage." >&2
@@ -466,6 +1135,14 @@ while [ $# -gt 0 ]; do
466
1135
  esac
467
1136
  done
468
1137
 
1138
+ if [ "$VERBOSE" = false ]; then
1139
+ if [ -n "${CI:-}" ] && [ "${CI:-}" != "0" ] && [ "${CI:-}" != "false" ]; then
1140
+ VERBOSE=true
1141
+ elif ! supports_progress_tty; then
1142
+ VERBOSE=true
1143
+ fi
1144
+ fi
1145
+
469
1146
  case "$MODE" in
470
1147
  shell)
471
1148
  run_shell