shipwright-cli 1.10.0 → 2.0.0

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 (108) hide show
  1. package/README.md +114 -36
  2. package/completions/_shipwright +212 -32
  3. package/completions/shipwright.bash +97 -25
  4. package/docs/strategy/01-market-research.md +619 -0
  5. package/docs/strategy/02-mission-and-brand.md +587 -0
  6. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  7. package/docs/strategy/QUICK-START.txt +289 -0
  8. package/docs/strategy/README.md +172 -0
  9. package/package.json +4 -2
  10. package/scripts/sw +208 -1
  11. package/scripts/sw-activity.sh +500 -0
  12. package/scripts/sw-adaptive.sh +925 -0
  13. package/scripts/sw-adversarial.sh +1 -1
  14. package/scripts/sw-architecture-enforcer.sh +1 -1
  15. package/scripts/sw-auth.sh +613 -0
  16. package/scripts/sw-autonomous.sh +664 -0
  17. package/scripts/sw-changelog.sh +704 -0
  18. package/scripts/sw-checkpoint.sh +1 -1
  19. package/scripts/sw-ci.sh +602 -0
  20. package/scripts/sw-cleanup.sh +1 -1
  21. package/scripts/sw-code-review.sh +637 -0
  22. package/scripts/sw-connect.sh +1 -1
  23. package/scripts/sw-context.sh +605 -0
  24. package/scripts/sw-cost.sh +1 -1
  25. package/scripts/sw-daemon.sh +432 -130
  26. package/scripts/sw-dashboard.sh +1 -1
  27. package/scripts/sw-db.sh +540 -0
  28. package/scripts/sw-decompose.sh +539 -0
  29. package/scripts/sw-deps.sh +551 -0
  30. package/scripts/sw-developer-simulation.sh +1 -1
  31. package/scripts/sw-discovery.sh +412 -0
  32. package/scripts/sw-docs-agent.sh +539 -0
  33. package/scripts/sw-docs.sh +1 -1
  34. package/scripts/sw-doctor.sh +59 -1
  35. package/scripts/sw-dora.sh +615 -0
  36. package/scripts/sw-durable.sh +710 -0
  37. package/scripts/sw-e2e-orchestrator.sh +535 -0
  38. package/scripts/sw-eventbus.sh +393 -0
  39. package/scripts/sw-feedback.sh +471 -0
  40. package/scripts/sw-fix.sh +1 -1
  41. package/scripts/sw-fleet-discover.sh +567 -0
  42. package/scripts/sw-fleet-viz.sh +404 -0
  43. package/scripts/sw-fleet.sh +8 -1
  44. package/scripts/sw-github-app.sh +596 -0
  45. package/scripts/sw-github-checks.sh +1 -1
  46. package/scripts/sw-github-deploy.sh +1 -1
  47. package/scripts/sw-github-graphql.sh +1 -1
  48. package/scripts/sw-guild.sh +569 -0
  49. package/scripts/sw-heartbeat.sh +1 -1
  50. package/scripts/sw-hygiene.sh +559 -0
  51. package/scripts/sw-incident.sh +617 -0
  52. package/scripts/sw-init.sh +88 -1
  53. package/scripts/sw-instrument.sh +699 -0
  54. package/scripts/sw-intelligence.sh +1 -1
  55. package/scripts/sw-jira.sh +1 -1
  56. package/scripts/sw-launchd.sh +363 -28
  57. package/scripts/sw-linear.sh +1 -1
  58. package/scripts/sw-logs.sh +1 -1
  59. package/scripts/sw-loop.sh +64 -3
  60. package/scripts/sw-memory.sh +1 -1
  61. package/scripts/sw-mission-control.sh +487 -0
  62. package/scripts/sw-model-router.sh +545 -0
  63. package/scripts/sw-otel.sh +596 -0
  64. package/scripts/sw-oversight.sh +689 -0
  65. package/scripts/sw-pipeline-composer.sh +1 -1
  66. package/scripts/sw-pipeline-vitals.sh +1 -1
  67. package/scripts/sw-pipeline.sh +687 -24
  68. package/scripts/sw-pm.sh +693 -0
  69. package/scripts/sw-pr-lifecycle.sh +522 -0
  70. package/scripts/sw-predictive.sh +1 -1
  71. package/scripts/sw-prep.sh +1 -1
  72. package/scripts/sw-ps.sh +1 -1
  73. package/scripts/sw-public-dashboard.sh +798 -0
  74. package/scripts/sw-quality.sh +595 -0
  75. package/scripts/sw-reaper.sh +1 -1
  76. package/scripts/sw-recruit.sh +573 -0
  77. package/scripts/sw-regression.sh +642 -0
  78. package/scripts/sw-release-manager.sh +736 -0
  79. package/scripts/sw-release.sh +706 -0
  80. package/scripts/sw-remote.sh +1 -1
  81. package/scripts/sw-replay.sh +520 -0
  82. package/scripts/sw-retro.sh +691 -0
  83. package/scripts/sw-scale.sh +444 -0
  84. package/scripts/sw-security-audit.sh +505 -0
  85. package/scripts/sw-self-optimize.sh +1 -1
  86. package/scripts/sw-session.sh +1 -1
  87. package/scripts/sw-setup.sh +1 -1
  88. package/scripts/sw-standup.sh +712 -0
  89. package/scripts/sw-status.sh +1 -1
  90. package/scripts/sw-strategic.sh +658 -0
  91. package/scripts/sw-stream.sh +450 -0
  92. package/scripts/sw-swarm.sh +583 -0
  93. package/scripts/sw-team-stages.sh +511 -0
  94. package/scripts/sw-templates.sh +1 -1
  95. package/scripts/sw-testgen.sh +515 -0
  96. package/scripts/sw-tmux-pipeline.sh +554 -0
  97. package/scripts/sw-tmux.sh +1 -1
  98. package/scripts/sw-trace.sh +485 -0
  99. package/scripts/sw-tracker-github.sh +188 -0
  100. package/scripts/sw-tracker-jira.sh +172 -0
  101. package/scripts/sw-tracker-linear.sh +251 -0
  102. package/scripts/sw-tracker.sh +117 -2
  103. package/scripts/sw-triage.sh +603 -0
  104. package/scripts/sw-upgrade.sh +1 -1
  105. package/scripts/sw-ux.sh +677 -0
  106. package/scripts/sw-webhook.sh +627 -0
  107. package/scripts/sw-widgets.sh +530 -0
  108. package/scripts/sw-worktree.sh +1 -1
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="1.10.0"
9
+ VERSION="2.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="1.10.0"
9
+ VERSION="2.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env bash
2
2
  # ╔═══════════════════════════════════════════════════════════════════════════╗
3
- # ║ shipwright launchd — Process supervision on macOS
4
- # ║ Auto-start daemon + dashboard on boot via launchd
3
+ # ║ shipwright launchd — Process supervision (macOS + Linux)
4
+ # ║ Auto-start daemon + dashboard on boot via launchd (macOS) or systemd
5
5
  # ╚═══════════════════════════════════════════════════════════════════════════╝
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="1.10.0"
9
+ VERSION="2.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -31,48 +31,111 @@ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
31
31
  warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
32
32
  error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
33
33
 
34
- # ─── Constants ──────────────────────────────────────────────────────────────
35
- PLIST_DIR="$HOME/Library/LaunchAgents"
36
- DAEMON_PLIST="$PLIST_DIR/com.shipwright.daemon.plist"
37
- DASHBOARD_PLIST="$PLIST_DIR/com.shipwright.dashboard.plist"
38
- CONNECT_PLIST="$PLIST_DIR/com.shipwright.connect.plist"
39
- LOG_DIR="$HOME/.shipwright/logs"
40
- TEAM_CONFIG="$HOME/.shipwright/team-config.json"
41
-
42
- # ─── Check macOS ─────────────────────────────────────────────────────────────
43
- check_macos() {
44
- if [[ "$OSTYPE" != "darwin"* ]]; then
45
- error "launchd is only available on macOS"
46
- exit 1
34
+ # ─── OS Detection ───────────────────────────────────────────────────────────
35
+ detect_os() {
36
+ if [[ "$OSTYPE" == "darwin"* ]]; then
37
+ echo "macos"
38
+ elif [[ "$OSTYPE" == "linux-gnu"* ]] || [[ "$OSTYPE" == "linux"* ]]; then
39
+ echo "linux"
40
+ else
41
+ echo "unknown"
47
42
  fi
48
43
  }
49
44
 
45
+ OS_TYPE=$(detect_os)
46
+
47
+ # ─── Platform-specific Constants ────────────────────────────────────────────
48
+ if [[ "$OS_TYPE" == "macos" ]]; then
49
+ PLIST_DIR="$HOME/Library/LaunchAgents"
50
+ DAEMON_PLIST="$PLIST_DIR/com.shipwright.daemon.plist"
51
+ DASHBOARD_PLIST="$PLIST_DIR/com.shipwright.dashboard.plist"
52
+ CONNECT_PLIST="$PLIST_DIR/com.shipwright.connect.plist"
53
+ LOG_DIR="$HOME/.shipwright/logs"
54
+ elif [[ "$OS_TYPE" == "linux" ]]; then
55
+ SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
56
+ DAEMON_SERVICE="$SYSTEMD_USER_DIR/shipwright-daemon.service"
57
+ DASHBOARD_SERVICE="$SYSTEMD_USER_DIR/shipwright-dashboard.service"
58
+ CONNECT_SERVICE="$SYSTEMD_USER_DIR/shipwright-connect.service"
59
+ LOG_DIR="$HOME/.shipwright/logs"
60
+ fi
61
+
62
+ TEAM_CONFIG="$HOME/.shipwright/team-config.json"
63
+
50
64
  # ─── Help ───────────────────────────────────────────────────────────────────
51
65
  show_help() {
52
66
  echo ""
53
- echo -e "${CYAN}${BOLD} Shipwright launchd${RESET} ${DIM}v${VERSION}${RESET}"
67
+ echo -e "${CYAN}${BOLD} Shipwright Supervisor${RESET} ${DIM}v${VERSION}${RESET}"
54
68
  echo -e "${DIM} ════════════════════════════════════════════${RESET}"
55
69
  echo ""
56
70
  echo -e " ${BOLD}USAGE${RESET}"
57
71
  echo -e " shipwright launchd <command>"
58
72
  echo ""
73
+ echo -e " ${BOLD}PLATFORM${RESET}: $OS_TYPE"
74
+ echo ""
59
75
  echo -e " ${BOLD}COMMANDS${RESET}"
60
- echo -e " ${CYAN}install${RESET} Install launchd agents for daemon and dashboard (auto-start on boot)"
61
- echo -e " ${CYAN}uninstall${RESET} Remove launchd agents and stop services"
62
- echo -e " ${CYAN}status${RESET} Check status of launchd services"
76
+ echo -e " ${CYAN}install${RESET} Install services for daemon and dashboard (auto-start on boot)"
77
+ echo -e " ${CYAN}uninstall${RESET} Remove services and stop running daemons"
78
+ echo -e " ${CYAN}status${RESET} Check status of services"
79
+ echo -e " ${CYAN}logs${RESET} Tail service logs (systemd only)"
63
80
  echo -e " ${CYAN}help${RESET} Show this help message"
64
81
  echo ""
65
82
  echo -e " ${BOLD}EXAMPLES${RESET}"
66
83
  echo -e " ${DIM}shipwright launchd install${RESET} # Set up auto-start on boot"
67
84
  echo -e " ${DIM}shipwright launchd status${RESET} # Check if services are running"
85
+ echo -e " ${DIM}shipwright launchd logs${RESET} # View service logs"
68
86
  echo -e " ${DIM}shipwright launchd uninstall${RESET} # Remove auto-start"
69
87
  echo ""
70
88
  }
71
89
 
90
+ # ─── Systemd Unit File Generator (Linux) ──────────────────────────────────
91
+ create_systemd_unit() {
92
+ local unit_name="$1"
93
+ local description="$2"
94
+ local exec_start="$3"
95
+ local output_file="$4"
96
+
97
+ mkdir -p "$(dirname "$output_file")"
98
+
99
+ cat > "$output_file" <<EOF
100
+ [Unit]
101
+ Description=${description}
102
+ After=network.target
103
+ Wants=network-online.target
104
+
105
+ [Service]
106
+ Type=simple
107
+ ExecStart=${exec_start}
108
+ Restart=on-failure
109
+ RestartSec=10
110
+ StandardOutput=journal
111
+ StandardError=journal
112
+ SyslogIdentifier=${unit_name}
113
+ Environment="PATH=/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:\$HOME/.local/bin"
114
+ Environment="HOME=\$HOME"
115
+
116
+ [Install]
117
+ WantedBy=default.target
118
+ EOF
119
+
120
+ chmod 644 "$output_file"
121
+ }
122
+
72
123
  # ─── Install Command ─────────────────────────────────────────────────────────
73
124
  cmd_install() {
74
- check_macos
125
+ if [[ "$OS_TYPE" == "unknown" ]]; then
126
+ error "Unsupported OS type: $OSTYPE"
127
+ exit 1
128
+ fi
129
+
130
+ if [[ "$OS_TYPE" == "macos" ]]; then
131
+ cmd_install_macos
132
+ else
133
+ cmd_install_linux
134
+ fi
135
+ }
75
136
 
137
+ # ─── Install macOS launchd agents ──────────────────────────────────────────
138
+ cmd_install_macos() {
76
139
  info "Installing launchd agents..."
77
140
 
78
141
  # Create directories
@@ -250,10 +313,135 @@ EOF
250
313
  info "Uninstall: ${DIM}shipwright launchd uninstall${RESET}"
251
314
  }
252
315
 
316
+ # ─── Install Linux systemd user services ───────────────────────────────────
317
+ cmd_install_linux() {
318
+ info "Installing systemd user services..."
319
+
320
+ # Create directories
321
+ mkdir -p "$SYSTEMD_USER_DIR" "$LOG_DIR"
322
+
323
+ # Find the full path to the sw CLI
324
+ local sw_bin
325
+ if [[ -x "$SCRIPT_DIR/sw" ]]; then
326
+ sw_bin="$SCRIPT_DIR/sw"
327
+ else
328
+ # Try to find it via PATH
329
+ sw_bin=$(command -v sw 2>/dev/null || echo "")
330
+ if [[ -z "$sw_bin" ]]; then
331
+ error "Could not find 'sw' binary — make sure Shipwright is installed"
332
+ exit 1
333
+ fi
334
+ fi
335
+
336
+ # Resolve symlinks in case sw is symlinked
337
+ sw_bin=$(cd "$(dirname "$sw_bin")" && pwd)/$(basename "$sw_bin")
338
+
339
+ # ─── Create Daemon Service ─────────────────────────────────────────────────
340
+ create_systemd_unit "shipwright-daemon" \
341
+ "Shipwright Daemon - Autonomous Issue Processor" \
342
+ "env -u CLAUDECODE ${sw_bin} daemon start --detach" \
343
+ "$DAEMON_SERVICE"
344
+
345
+ success "Created daemon service: ${DAEMON_SERVICE}"
346
+
347
+ # ─── Create Dashboard Service ──────────────────────────────────────────────
348
+ local bun_bin
349
+ bun_bin=$(command -v bun 2>/dev/null || echo "bun")
350
+
351
+ local server_file="$REPO_DIR/dashboard/server.ts"
352
+ if [[ ! -f "$server_file" ]]; then
353
+ warn "server.ts not found at $server_file — dashboard service will reference a missing file"
354
+ fi
355
+
356
+ create_systemd_unit "shipwright-dashboard" \
357
+ "Shipwright Dashboard - Real-time Team Status" \
358
+ "${bun_bin} run ${server_file}" \
359
+ "$DASHBOARD_SERVICE"
360
+
361
+ success "Created dashboard service: ${DASHBOARD_SERVICE}"
362
+
363
+ # ─── Create Connect Service (only if team-config.json exists) ──────────────
364
+ if [[ -f "$TEAM_CONFIG" ]]; then
365
+ create_systemd_unit "shipwright-connect" \
366
+ "Shipwright Connect - Team Sync Service" \
367
+ "env -u CLAUDECODE ${sw_bin} connect start" \
368
+ "$CONNECT_SERVICE"
369
+
370
+ success "Created connect service: ${CONNECT_SERVICE}"
371
+ else
372
+ info "Skipping connect service — ${TEAM_CONFIG} not found"
373
+ fi
374
+
375
+ # ─── Enable and Start Services ─────────────────────────────────────────────
376
+ info "Enabling systemd user services..."
377
+
378
+ if systemctl --user enable shipwright-daemon.service 2>/dev/null; then
379
+ success "Enabled daemon service"
380
+ else
381
+ warn "Could not enable daemon service"
382
+ fi
383
+
384
+ if systemctl --user enable shipwright-dashboard.service 2>/dev/null; then
385
+ success "Enabled dashboard service"
386
+ else
387
+ warn "Could not enable dashboard service"
388
+ fi
389
+
390
+ if [[ -f "$CONNECT_SERVICE" ]]; then
391
+ if systemctl --user enable shipwright-connect.service 2>/dev/null; then
392
+ success "Enabled connect service"
393
+ else
394
+ warn "Could not enable connect service"
395
+ fi
396
+ fi
397
+
398
+ # Start services immediately
399
+ info "Starting systemd user services..."
400
+
401
+ if systemctl --user start shipwright-daemon.service 2>/dev/null; then
402
+ success "Started daemon service"
403
+ else
404
+ warn "Could not start daemon service — enable user lingering first: loginctl enable-linger"
405
+ fi
406
+
407
+ if systemctl --user start shipwright-dashboard.service 2>/dev/null; then
408
+ success "Started dashboard service"
409
+ else
410
+ warn "Could not start dashboard service"
411
+ fi
412
+
413
+ if [[ -f "$CONNECT_SERVICE" ]]; then
414
+ if systemctl --user start shipwright-connect.service 2>/dev/null; then
415
+ success "Started connect service"
416
+ else
417
+ warn "Could not start connect service"
418
+ fi
419
+ fi
420
+
421
+ echo ""
422
+ info "Services will auto-start on next login (with systemd lingering enabled)"
423
+ info "Enable lingering: ${DIM}loginctl enable-linger${RESET}"
424
+ info "View logs: ${DIM}journalctl --user -u shipwright-daemon -f${RESET}"
425
+ info "View all logs: ${DIM}journalctl --user -u shipwright-* -f${RESET}"
426
+ info "Uninstall: ${DIM}shipwright launchd uninstall${RESET}"
427
+ }
428
+
253
429
  # ─── Uninstall Command ──────────────────────────────────────────────────────
254
430
  cmd_uninstall() {
255
- check_macos
431
+ if [[ "$OS_TYPE" == "unknown" ]]; then
432
+ error "Unsupported OS type: $OSTYPE"
433
+ exit 1
434
+ fi
435
+
436
+ if [[ "$OS_TYPE" == "macos" ]]; then
437
+ cmd_uninstall_macos
438
+ else
439
+ cmd_uninstall_linux
440
+ fi
441
+ }
256
442
 
443
+ # ─── Uninstall macOS launchd agents ────────────────────────────────────────
444
+ cmd_uninstall_macos() {
257
445
  info "Uninstalling launchd agents..."
258
446
 
259
447
  # Unload daemon
@@ -293,31 +481,112 @@ cmd_uninstall() {
293
481
  success "Uninstalled all launchd agents"
294
482
  }
295
483
 
484
+ # ─── Uninstall Linux systemd user services ────────────────────────────────
485
+ cmd_uninstall_linux() {
486
+ info "Uninstalling systemd user services..."
487
+
488
+ # Stop and disable daemon
489
+ if systemctl --user is-active --quiet shipwright-daemon.service 2>/dev/null; then
490
+ if systemctl --user stop shipwright-daemon.service 2>/dev/null; then
491
+ success "Stopped daemon service"
492
+ else
493
+ warn "Could not stop daemon service"
494
+ fi
495
+ fi
496
+
497
+ if systemctl --user is-enabled --quiet shipwright-daemon.service 2>/dev/null; then
498
+ if systemctl --user disable shipwright-daemon.service 2>/dev/null; then
499
+ success "Disabled daemon service"
500
+ else
501
+ warn "Could not disable daemon service"
502
+ fi
503
+ fi
504
+
505
+ # Stop and disable dashboard
506
+ if systemctl --user is-active --quiet shipwright-dashboard.service 2>/dev/null; then
507
+ if systemctl --user stop shipwright-dashboard.service 2>/dev/null; then
508
+ success "Stopped dashboard service"
509
+ else
510
+ warn "Could not stop dashboard service"
511
+ fi
512
+ fi
513
+
514
+ if systemctl --user is-enabled --quiet shipwright-dashboard.service 2>/dev/null; then
515
+ if systemctl --user disable shipwright-dashboard.service 2>/dev/null; then
516
+ success "Disabled dashboard service"
517
+ else
518
+ warn "Could not disable dashboard service"
519
+ fi
520
+ fi
521
+
522
+ # Stop and disable connect
523
+ if systemctl --user is-active --quiet shipwright-connect.service 2>/dev/null; then
524
+ if systemctl --user stop shipwright-connect.service 2>/dev/null; then
525
+ success "Stopped connect service"
526
+ else
527
+ warn "Could not stop connect service"
528
+ fi
529
+ fi
530
+
531
+ if systemctl --user is-enabled --quiet shipwright-connect.service 2>/dev/null; then
532
+ if systemctl --user disable shipwright-connect.service 2>/dev/null; then
533
+ success "Disabled connect service"
534
+ else
535
+ warn "Could not disable connect service"
536
+ fi
537
+ fi
538
+
539
+ # Remove service files
540
+ [[ -f "$DAEMON_SERVICE" ]] && rm -f "$DAEMON_SERVICE" && success "Removed daemon service file"
541
+ [[ -f "$DASHBOARD_SERVICE" ]] && rm -f "$DASHBOARD_SERVICE" && success "Removed dashboard service file"
542
+ [[ -f "$CONNECT_SERVICE" ]] && rm -f "$CONNECT_SERVICE" && success "Removed connect service file"
543
+
544
+ # Reload systemd daemon
545
+ if systemctl --user daemon-reload 2>/dev/null; then
546
+ success "Reloaded systemd user daemon"
547
+ fi
548
+
549
+ echo ""
550
+ success "Uninstalled all systemd user services"
551
+ }
552
+
296
553
  # ─── Status Command ─────────────────────────────────────────────────────────
297
554
  cmd_status() {
298
- check_macos
555
+ if [[ "$OS_TYPE" == "unknown" ]]; then
556
+ error "Unsupported OS type: $OSTYPE"
557
+ exit 1
558
+ fi
299
559
 
560
+ if [[ "$OS_TYPE" == "macos" ]]; then
561
+ cmd_status_macos
562
+ else
563
+ cmd_status_linux
564
+ fi
565
+ }
566
+
567
+ # ─── Status macOS launchd services ─────────────────────────────────────────
568
+ cmd_status_macos() {
300
569
  echo ""
301
570
  echo -e "${CYAN}${BOLD}Launchd Services${RESET}"
302
571
  echo -e "${DIM}════════════════════════════════════════════${RESET}"
303
572
  echo ""
304
573
 
305
574
  # Check daemon
306
- if launchctl list | grep -q "com.shipwright.daemon" 2>/dev/null; then
575
+ if launchctl list 2>/dev/null | grep -q "com.shipwright.daemon"; then
307
576
  echo -e " ${GREEN}●${RESET} Daemon service is ${GREEN}loaded${RESET}"
308
577
  else
309
578
  echo -e " ${RED}○${RESET} Daemon service is ${RED}not loaded${RESET}"
310
579
  fi
311
580
 
312
581
  # Check dashboard
313
- if launchctl list | grep -q "com.shipwright.dashboard" 2>/dev/null; then
582
+ if launchctl list 2>/dev/null | grep -q "com.shipwright.dashboard"; then
314
583
  echo -e " ${GREEN}●${RESET} Dashboard service is ${GREEN}loaded${RESET}"
315
584
  else
316
585
  echo -e " ${RED}○${RESET} Dashboard service is ${RED}not loaded${RESET}"
317
586
  fi
318
587
 
319
588
  # Check connect
320
- if launchctl list | grep -q "com.shipwright.connect" 2>/dev/null; then
589
+ if launchctl list 2>/dev/null | grep -q "com.shipwright.connect"; then
321
590
  echo -e " ${GREEN}●${RESET} Connect service is ${GREEN}loaded${RESET}"
322
591
  else
323
592
  echo -e " ${RED}○${RESET} Connect service is ${RED}not loaded${RESET}"
@@ -330,11 +599,72 @@ cmd_status() {
330
599
  # Show recent log entries
331
600
  if [[ -f "$LOG_DIR/daemon.stdout.log" ]]; then
332
601
  echo -e "${DIM}Recent daemon logs:${RESET}"
333
- tail -3 "$LOG_DIR/daemon.stdout.log" | sed 's/^/ /'
602
+ tail -3 "$LOG_DIR/daemon.stdout.log" 2>/dev/null | sed 's/^/ /'
334
603
  echo ""
335
604
  fi
336
605
  }
337
606
 
607
+ # ─── Status Linux systemd services ────────────────────────────────────────
608
+ cmd_status_linux() {
609
+ echo ""
610
+ echo -e "${CYAN}${BOLD}Systemd User Services${RESET}"
611
+ echo -e "${DIM}════════════════════════════════════════════${RESET}"
612
+ echo ""
613
+
614
+ # Check daemon
615
+ if systemctl --user is-active --quiet shipwright-daemon.service 2>/dev/null; then
616
+ echo -e " ${GREEN}●${RESET} Daemon service is ${GREEN}running${RESET}"
617
+ else
618
+ echo -e " ${RED}○${RESET} Daemon service is ${RED}not running${RESET}"
619
+ fi
620
+
621
+ # Check dashboard
622
+ if systemctl --user is-active --quiet shipwright-dashboard.service 2>/dev/null; then
623
+ echo -e " ${GREEN}●${RESET} Dashboard service is ${GREEN}running${RESET}"
624
+ else
625
+ echo -e " ${RED}○${RESET} Dashboard service is ${RED}not running${RESET}"
626
+ fi
627
+
628
+ # Check connect
629
+ if systemctl --user is-active --quiet shipwright-connect.service 2>/dev/null; then
630
+ echo -e " ${GREEN}●${RESET} Connect service is ${GREEN}running${RESET}"
631
+ else
632
+ echo -e " ${RED}○${RESET} Connect service is ${RED}not running${RESET}"
633
+ fi
634
+
635
+ echo ""
636
+ echo -e " Services: ${DIM}${SYSTEMD_USER_DIR}${RESET}"
637
+ echo -e " Logs: ${DIM}journalctl --user${RESET}"
638
+ echo ""
639
+
640
+ # Show recent journal entries for daemon
641
+ if command -v journalctl &>/dev/null; then
642
+ echo -e "${DIM}Recent daemon logs:${RESET}"
643
+ journalctl --user -u shipwright-daemon.service -n 3 --no-pager 2>/dev/null | tail -3 | sed 's/^/ /' || true
644
+ echo ""
645
+ fi
646
+ }
647
+
648
+ # ─── Logs Command (systemd only) ────────────────────────────────────────────
649
+ cmd_logs() {
650
+ if [[ "$OS_TYPE" != "linux" ]]; then
651
+ error "logs command is only available on Linux (systemd)"
652
+ exit 1
653
+ fi
654
+
655
+ local service="${1:-shipwright-daemon.service}"
656
+
657
+ if ! systemctl --user is-active --quiet "$service" 2>/dev/null; then
658
+ warn "Service $service is not running"
659
+ fi
660
+
661
+ echo ""
662
+ info "Tailing logs for $service (Ctrl-C to stop)..."
663
+ echo ""
664
+
665
+ journalctl --user -u "$service" -f --no-pager
666
+ }
667
+
338
668
  # ─── Main ───────────────────────────────────────────────────────────────────
339
669
  main() {
340
670
  local cmd="${1:-help}"
@@ -349,6 +679,9 @@ main() {
349
679
  status)
350
680
  cmd_status
351
681
  ;;
682
+ logs)
683
+ cmd_logs "${2:-}"
684
+ ;;
352
685
  help|--help|-h)
353
686
  show_help
354
687
  ;;
@@ -361,4 +694,6 @@ main() {
361
694
  esac
362
695
  }
363
696
 
364
- main "$@"
697
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
698
+ main "$@"
699
+ fi
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="1.10.0"
9
+ VERSION="2.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12
 
@@ -4,7 +4,7 @@
4
4
  # ║ ║
5
5
  # ║ Captures tmux pane scrollback and provides log browsing/search. ║
6
6
  # ╚═══════════════════════════════════════════════════════════════════════════╝
7
- VERSION="1.10.0"
7
+ VERSION="2.0.0"
8
8
  set -euo pipefail
9
9
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
10
 
@@ -10,6 +10,11 @@
10
10
  set -euo pipefail
11
11
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
12
12
 
13
+ # Allow spawning Claude CLI from within a Claude Code session (daemon, fleet, etc.)
14
+ unset CLAUDECODE 2>/dev/null || true
15
+ # Ignore SIGHUP so tmux attach/detach doesn't kill long-running agent sessions
16
+ trap '' HUP
17
+
13
18
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
19
 
15
20
  # ─── Colors (matches shipwright theme) ──────────────────────────────────────────────
@@ -52,7 +57,7 @@ MAX_ITERATIONS_EXPLICIT=false
52
57
  MAX_RESTARTS=0
53
58
  SESSION_RESTART=false
54
59
  RESTART_COUNT=0
55
- VERSION="1.10.0"
60
+ VERSION="2.0.0"
56
61
 
57
62
  # ─── Flexible Iteration Defaults ────────────────────────────────────────────
58
63
  AUTO_EXTEND=true # Auto-extend iterations when work is incomplete
@@ -668,11 +673,50 @@ git_auto_commit() {
668
673
  return 0
669
674
  }
670
675
 
676
+ # ─── Fatal Error Detection ────────────────────────────────────────────────────
677
+
678
+ check_fatal_error() {
679
+ local log_file="$1"
680
+ local cli_exit_code="${2:-0}"
681
+ [[ -f "$log_file" ]] || return 1
682
+
683
+ # Known fatal error patterns from Claude CLI / Anthropic API
684
+ local fatal_patterns="Invalid API key|invalid_api_key|authentication_error|API key expired"
685
+ fatal_patterns="${fatal_patterns}|rate_limit_error|overloaded_error|billing"
686
+ fatal_patterns="${fatal_patterns}|Could not resolve host|connection refused|ECONNREFUSED"
687
+ fatal_patterns="${fatal_patterns}|ANTHROPIC_API_KEY.*not set|No API key"
688
+
689
+ if grep -qiE "$fatal_patterns" "$log_file" 2>/dev/null; then
690
+ local match
691
+ match=$(grep -iE "$fatal_patterns" "$log_file" 2>/dev/null | head -1 | cut -c1-120)
692
+ error "Fatal CLI error: $match"
693
+ return 0 # fatal error detected
694
+ fi
695
+
696
+ # Non-zero exit + tiny output = likely CLI crash
697
+ if [[ "$cli_exit_code" -ne 0 ]]; then
698
+ local line_count
699
+ line_count=$(grep -cv '^$' "$log_file" 2>/dev/null || echo 0)
700
+ if [[ "$line_count" -lt 3 ]]; then
701
+ local content
702
+ content=$(head -3 "$log_file" 2>/dev/null | cut -c1-120)
703
+ error "CLI exited $cli_exit_code with minimal output: $content"
704
+ return 0
705
+ fi
706
+ fi
707
+
708
+ return 1 # no fatal error
709
+ }
710
+
671
711
  # ─── Progress & Circuit Breaker ───────────────────────────────────────────────
672
712
 
673
713
  check_progress() {
674
714
  local changes
675
- changes="$(git -C "$PROJECT_ROOT" diff --stat HEAD~1 2>/dev/null | tail -1 || echo "")"
715
+ # Exclude loop bookkeeping files only count real code changes as progress
716
+ changes="$(git -C "$PROJECT_ROOT" diff --stat HEAD~1 \
717
+ -- . ':!.claude/loop-state.md' ':!.claude/pipeline-state.md' \
718
+ ':!**/progress.md' ':!**/error-summary.json' \
719
+ 2>/dev/null | tail -1 || echo "")"
676
720
  local insertions
677
721
  insertions="$(echo "$changes" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo 0)"
678
722
  if [[ "${insertions:-0}" -lt "$MIN_PROGRESS_LINES" ]]; then
@@ -1545,7 +1589,14 @@ extract_summary() {
1545
1589
  local summary
1546
1590
  summary="$(grep -v '^$' "$log_file" | tail -5 | head -3 2>/dev/null || echo "(no output)")"
1547
1591
  # Truncate long lines
1548
- echo "$summary" | cut -c1-120
1592
+ summary="$(echo "$summary" | cut -c1-120)"
1593
+
1594
+ # Sanitize: if summary is just a CLI/API error, replace with generic text
1595
+ if echo "$summary" | grep -qiE 'Invalid API key|authentication_error|rate_limit|API key expired|ANTHROPIC_API_KEY'; then
1596
+ summary="(CLI error — no useful output this iteration)"
1597
+ fi
1598
+
1599
+ echo "$summary"
1549
1600
  }
1550
1601
 
1551
1602
  # ─── Display Helpers ─────────────────────────────────────────────────────────
@@ -2009,6 +2060,16 @@ ${GOAL}"
2009
2060
 
2010
2061
  local log_file="$LOG_DIR/iteration-${ITERATION}.log"
2011
2062
 
2063
+ # Detect fatal CLI errors (API key, auth, network) — abort immediately
2064
+ if check_fatal_error "$log_file" "$exit_code"; then
2065
+ STATUS="error"
2066
+ write_state
2067
+ write_progress
2068
+ error "Fatal CLI error detected — aborting loop (see iteration log)"
2069
+ show_summary
2070
+ return 1
2071
+ fi
2072
+
2012
2073
  # Mid-loop memory refresh — re-query with current error context after iteration 3
2013
2074
  if [[ "$ITERATION" -ge 3 ]] && type memory_inject_context &>/dev/null 2>&1; then
2014
2075
  local refresh_ctx
@@ -6,7 +6,7 @@
6
6
  set -euo pipefail
7
7
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
8
 
9
- VERSION="1.10.0"
9
+ VERSION="2.0.0"
10
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
11
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
12