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
@@ -0,0 +1,450 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ sw-stream.sh — Live terminal output streaming from agent panes ║
4
+ # ║ ║
5
+ # ║ Streams tmux pane output in real-time to the dashboard or CLI. ║
6
+ # ║ Captures output periodically, tags by agent/team, supports replay. ║
7
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
8
+ VERSION="2.0.0"
9
+ set -euo pipefail
10
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
+
12
+ # ─── Colors ──────────────────────────────────────────────────────────────────
13
+ CYAN='\033[38;2;0;212;255m'
14
+ PURPLE='\033[38;2;124;58;237m'
15
+ BLUE='\033[38;2;0;102;255m'
16
+ GREEN='\033[38;2;74;222;128m'
17
+ YELLOW='\033[38;2;250;204;21m'
18
+ RED='\033[38;2;248;113;113m'
19
+ DIM='\033[2m'
20
+ BOLD='\033[1m'
21
+ RESET='\033[0m'
22
+
23
+ # ─── Helpers ─────────────────────────────────────────────────────────────────
24
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
25
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
26
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
27
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
28
+
29
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
30
+ _COMPAT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/compat.sh"
31
+ # shellcheck source=lib/compat.sh
32
+ [[ -f "$_COMPAT" ]] && source "$_COMPAT"
33
+
34
+ # ─── Event logging ─────────────────────────────────────────────────────────
35
+ emit_event() {
36
+ local event_type="$1"
37
+ shift
38
+ local json_fields=""
39
+ for kv in "$@"; do
40
+ local key="${kv%%=*}"
41
+ local value="${kv#*=}"
42
+ value="${value//\"/\\\"}" # Escape quotes
43
+ json_fields="${json_fields}\"${key}\": \"${value}\", "
44
+ done
45
+ json_fields="${json_fields%, }" # Remove trailing comma
46
+ local ts
47
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
48
+ echo "{\"timestamp\": \"${ts}\", \"type\": \"${event_type}\", ${json_fields}}" >> \
49
+ "${HOME}/.shipwright/events.jsonl" 2>/dev/null || true
50
+ }
51
+
52
+ # ─── Stream configuration ─────────────────────────────────────────────────────
53
+ STREAM_CONFIG="${HOME}/.shipwright/stream-config.json"
54
+ STREAM_DIR="${HOME}/.shipwright/streams"
55
+ OUTPUT_FORMAT="jsonl" # jsonl, json, or text
56
+ CAPTURE_INTERVAL=1
57
+ BUFFER_LINES=500
58
+ RUNNING_PID_FILE="${HOME}/.shipwright/stream.pid"
59
+
60
+ # Load config if exists
61
+ load_config() {
62
+ if [[ -f "$STREAM_CONFIG" ]]; then
63
+ CAPTURE_INTERVAL=$(jq -r '.capture_interval_seconds // 1' "$STREAM_CONFIG" 2>/dev/null || echo 1)
64
+ BUFFER_LINES=$(jq -r '.buffer_lines // 500' "$STREAM_CONFIG" 2>/dev/null || echo 500)
65
+ OUTPUT_FORMAT=$(jq -r '.output_format // "jsonl"' "$STREAM_CONFIG" 2>/dev/null || echo "jsonl")
66
+ fi
67
+ }
68
+
69
+ # ─── Stream management ─────────────────────────────────────────────────────
70
+ init_stream_dir() {
71
+ mkdir -p "$STREAM_DIR"
72
+ mkdir -p "${HOME}/.shipwright"
73
+ }
74
+
75
+ get_pane_agent_name() {
76
+ local pane_id="$1"
77
+ tmux display-message -p -t "$pane_id" '#{pane_title}' 2>/dev/null || echo "unknown"
78
+ }
79
+
80
+ get_pane_window_name() {
81
+ local pane_id="$1"
82
+ tmux display-message -p -t "$pane_id" '#{window_name}' 2>/dev/null || echo "unknown"
83
+ }
84
+
85
+ # Extract team name from window name (e.g., "claude-myteam" → "myteam")
86
+ extract_team_name() {
87
+ local window_name="$1"
88
+ echo "$window_name" | sed 's/^claude-//; s/-[0-9]*$//'
89
+ }
90
+
91
+ # ─── Capture a single pane's output ──────────────────────────────────────────
92
+ capture_pane_output() {
93
+ local pane_id="$1"
94
+ local agent_name="$2"
95
+ local team_name="$3"
96
+
97
+ local pane_file="${STREAM_DIR}/${team_name}/${agent_name}.jsonl"
98
+ mkdir -p "${STREAM_DIR}/${team_name}"
99
+
100
+ local ts
101
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
102
+
103
+ # Capture last N lines from pane
104
+ local pane_output
105
+ pane_output=$(tmux capture-pane -p -t "$pane_id" -S "-${BUFFER_LINES}" 2>/dev/null || echo "")
106
+
107
+ if [[ -z "$pane_output" ]]; then
108
+ return 0
109
+ fi
110
+
111
+ # Write JSONL entry with timestamp, pane_id, agent, team, content
112
+ local tmp_file
113
+ tmp_file=$(mktemp)
114
+ trap "rm -f '$tmp_file'" RETURN
115
+
116
+ {
117
+ while IFS= read -r line; do
118
+ # Escape newlines and quotes in output
119
+ line="${line//\"/\\\"}"
120
+ printf '{"timestamp":"%s","pane_id":"%s","agent_name":"%s","team":"%s","content":"%s"}\n' \
121
+ "$ts" "$pane_id" "$agent_name" "$team_name" "$line"
122
+ done <<< "$pane_output"
123
+ } >> "$tmp_file"
124
+
125
+ # Atomic write: append to pane file and trim to buffer size
126
+ cat "$tmp_file" >> "$pane_file" 2>/dev/null || true
127
+
128
+ # Trim to buffer size (keep latest N lines)
129
+ local line_count
130
+ line_count=$(wc -l < "$pane_file" 2>/dev/null || echo 0)
131
+ if [[ "$line_count" -gt "$BUFFER_LINES" ]]; then
132
+ local skip=$((line_count - BUFFER_LINES))
133
+ tail -n "$BUFFER_LINES" "$pane_file" > "${pane_file}.tmp"
134
+ mv "${pane_file}.tmp" "$pane_file"
135
+ fi
136
+ }
137
+
138
+ # ─── Capture all agent panes ──────────────────────────────────────────────────
139
+ capture_all_panes() {
140
+ local filter_team="${1:-}"
141
+ local filter_agent="${2:-}"
142
+
143
+ # Find all claude-* windows and capture their panes
144
+ tmux list-panes -a -F '#{pane_id}|#{window_name}' 2>/dev/null | while IFS='|' read -r pane_id window_name; do
145
+ [[ -z "$pane_id" ]] && continue
146
+
147
+ # Only claude-* windows
148
+ echo "$window_name" | grep -q "^claude" || continue
149
+
150
+ local agent_name
151
+ agent_name=$(get_pane_agent_name "$pane_id")
152
+
153
+ local team_name
154
+ team_name=$(extract_team_name "$window_name")
155
+
156
+ # Apply filters
157
+ if [[ -n "$filter_team" ]]; then
158
+ [[ "$team_name" != "$filter_team" ]] && continue
159
+ fi
160
+ if [[ -n "$filter_agent" ]]; then
161
+ [[ "$agent_name" != "$filter_agent" ]] && continue
162
+ fi
163
+
164
+ capture_pane_output "$pane_id" "$agent_name" "$team_name"
165
+ done
166
+
167
+ emit_event "stream.capture_cycle" \
168
+ "team=$filter_team" \
169
+ "agent=$filter_agent" \
170
+ "interval=$CAPTURE_INTERVAL"
171
+ }
172
+
173
+ # ─── Stream start (background polling) ───────────────────────────────────────
174
+ stream_start() {
175
+ local team="${1:-}"
176
+
177
+ init_stream_dir
178
+ load_config
179
+
180
+ if [[ -f "$RUNNING_PID_FILE" ]]; then
181
+ local pid
182
+ pid=$(cat "$RUNNING_PID_FILE")
183
+ if kill -0 "$pid" 2>/dev/null; then
184
+ warn "Stream is already running (PID $pid)"
185
+ return 0
186
+ else
187
+ rm -f "$RUNNING_PID_FILE"
188
+ fi
189
+ fi
190
+
191
+ # Start background capture loop
192
+ (
193
+ while true; do
194
+ capture_all_panes "$team"
195
+ sleep "$CAPTURE_INTERVAL"
196
+ done
197
+ ) &
198
+ local loop_pid=$!
199
+ echo "$loop_pid" > "$RUNNING_PID_FILE"
200
+
201
+ success "Stream started (PID $loop_pid)"
202
+ info "Capturing every ${CAPTURE_INTERVAL}s from team: ${team:-all}"
203
+ emit_event "stream.started" "team=$team" "pid=$loop_pid"
204
+ }
205
+
206
+ # ─── Stream stop ─────────────────────────────────────────────────────────────
207
+ stream_stop() {
208
+ if [[ ! -f "$RUNNING_PID_FILE" ]]; then
209
+ warn "Stream is not running"
210
+ return 1
211
+ fi
212
+
213
+ local pid
214
+ pid=$(cat "$RUNNING_PID_FILE")
215
+
216
+ if kill -0 "$pid" 2>/dev/null; then
217
+ kill "$pid"
218
+ rm -f "$RUNNING_PID_FILE"
219
+ success "Stream stopped (PID $pid)"
220
+ emit_event "stream.stopped" "pid=$pid"
221
+ else
222
+ warn "Stream process (PID $pid) is not running"
223
+ rm -f "$RUNNING_PID_FILE"
224
+ fi
225
+ }
226
+
227
+ # ─── Live watch (tail stream in terminal) ────────────────────────────────────
228
+ stream_watch() {
229
+ local team="${1:-}"
230
+ local agent="${2:-}"
231
+
232
+ init_stream_dir
233
+ load_config
234
+
235
+ if [[ -z "$team" ]]; then
236
+ warn "Usage: shipwright stream watch <team> [agent]"
237
+ return 1
238
+ fi
239
+
240
+ local watch_path="${STREAM_DIR}/${team}"
241
+ if [[ -n "$agent" ]]; then
242
+ watch_path="${watch_path}/${agent}.jsonl"
243
+ fi
244
+
245
+ if [[ ! -e "$watch_path" ]]; then
246
+ error "No stream data for team '$team'${agent:+ agent '$agent'}"
247
+ return 1
248
+ fi
249
+
250
+ info "Watching $watch_path..."
251
+ echo ""
252
+
253
+ tail -f "$watch_path" | while IFS= read -r line; do
254
+ # Parse JSONL and pretty-print
255
+ local timestamp agent_name content
256
+ timestamp=$(echo "$line" | jq -r '.timestamp // ""' 2>/dev/null || echo "")
257
+ agent_name=$(echo "$line" | jq -r '.agent_name // ""' 2>/dev/null || echo "")
258
+ content=$(echo "$line" | jq -r '.content // ""' 2>/dev/null || echo "")
259
+
260
+ if [[ -n "$timestamp" && -n "$agent_name" && -n "$content" ]]; then
261
+ printf "${DIM}%s${RESET} ${CYAN}[%s]${RESET} %s\n" "$timestamp" "$agent_name" "$content"
262
+ fi
263
+ done
264
+ }
265
+
266
+ # ─── List active streams ─────────────────────────────────────────────────────
267
+ stream_list() {
268
+ init_stream_dir
269
+
270
+ if [[ ! -d "$STREAM_DIR" ]] || [[ -z "$(find "$STREAM_DIR" -type f -name '*.jsonl' 2>/dev/null)" ]]; then
271
+ warn "No active streams"
272
+ return 0
273
+ fi
274
+
275
+ echo ""
276
+ info "Active Streams:"
277
+ echo ""
278
+
279
+ find "$STREAM_DIR" -type f -name '*.jsonl' | sort | while read -r stream_file; do
280
+ # Extract team and agent from path
281
+ local relative_path
282
+ relative_path="${stream_file#$STREAM_DIR/}"
283
+ local team_name
284
+ team_name=$(echo "$relative_path" | cut -d'/' -f1)
285
+ local agent_name
286
+ agent_name=$(basename "$relative_path" .jsonl)
287
+
288
+ # Get file size and line count
289
+ local file_size lines_count
290
+ file_size=$(stat -f%z "$stream_file" 2>/dev/null || stat -c%s "$stream_file" 2>/dev/null || echo 0)
291
+ lines_count=$(wc -l < "$stream_file" 2>/dev/null || echo 0)
292
+
293
+ # Get latest timestamp
294
+ local latest_ts
295
+ latest_ts=$(tail -1 "$stream_file" 2>/dev/null | jq -r '.timestamp // ""' 2>/dev/null || echo "")
296
+
297
+ printf " ${CYAN}%-20s${RESET} ${PURPLE}%-20s${RESET} %s ${DIM}(%s lines, %s bytes)${RESET}\n" \
298
+ "$team_name" "$agent_name" "$latest_ts" "$lines_count" "$file_size"
299
+ done
300
+
301
+ echo ""
302
+ }
303
+
304
+ # ─── Replay recent output for a pane ──────────────────────────────────────────
305
+ stream_replay() {
306
+ local team="${1:-}"
307
+ local agent="${2:-}"
308
+ local lines="${3:-50}"
309
+
310
+ init_stream_dir
311
+
312
+ if [[ -z "$team" ]] || [[ -z "$agent" ]]; then
313
+ warn "Usage: shipwright stream replay <team> <agent> [lines]"
314
+ return 1
315
+ fi
316
+
317
+ local stream_file="${STREAM_DIR}/${team}/${agent}.jsonl"
318
+
319
+ if [[ ! -f "$stream_file" ]]; then
320
+ error "No stream data for team '$team' agent '$agent'"
321
+ return 1
322
+ fi
323
+
324
+ info "Replay (last ${lines} lines from ${team}/${agent}):"
325
+ echo ""
326
+
327
+ tail -n "$lines" "$stream_file" | while IFS= read -r line; do
328
+ local timestamp agent_name content
329
+ timestamp=$(echo "$line" | jq -r '.timestamp // ""' 2>/dev/null || echo "")
330
+ agent_name=$(echo "$line" | jq -r '.agent_name // ""' 2>/dev/null || echo "")
331
+ content=$(echo "$line" | jq -r '.content // ""' 2>/dev/null || echo "")
332
+
333
+ if [[ -n "$timestamp" && -n "$agent_name" && -n "$content" ]]; then
334
+ printf "${DIM}%s${RESET} ${CYAN}[%s]${RESET} %s\n" "$timestamp" "$agent_name" "$content"
335
+ fi
336
+ done
337
+
338
+ echo ""
339
+ }
340
+
341
+ # ─── Configure stream settings ───────────────────────────────────────────────
342
+ stream_config() {
343
+ local key="${1:-}"
344
+ local value="${2:-}"
345
+
346
+ if [[ -z "$key" ]]; then
347
+ warn "Usage: shipwright stream config <key> <value>"
348
+ echo " Available keys: capture_interval_seconds, buffer_lines, output_format"
349
+ return 1
350
+ fi
351
+
352
+ mkdir -p "${HOME}/.shipwright"
353
+
354
+ # Load existing config or create new
355
+ local config="{}"
356
+ if [[ -f "$STREAM_CONFIG" ]]; then
357
+ config=$(cat "$STREAM_CONFIG")
358
+ fi
359
+
360
+ # Create tmp file for atomic write
361
+ local tmp_file
362
+ tmp_file=$(mktemp)
363
+ trap "rm -f '$tmp_file'" RETURN
364
+
365
+ case "$key" in
366
+ capture_interval_seconds)
367
+ echo "$config" | jq ".capture_interval_seconds = ($value | tonumber)" > "$tmp_file"
368
+ ;;
369
+ buffer_lines)
370
+ echo "$config" | jq ".buffer_lines = ($value | tonumber)" > "$tmp_file"
371
+ ;;
372
+ output_format)
373
+ echo "$config" | jq ".output_format = \"$value\"" > "$tmp_file"
374
+ ;;
375
+ *)
376
+ error "Unknown config key: $key"
377
+ return 1
378
+ ;;
379
+ esac
380
+
381
+ mv "$tmp_file" "$STREAM_CONFIG"
382
+ success "Config updated: $key = $value"
383
+ emit_event "stream.config_updated" "key=$key" "value=$value"
384
+ }
385
+
386
+ # ─── Help ───────────────────────────────────────────────────────────────────
387
+ show_help() {
388
+ echo ""
389
+ echo -e "${CYAN}${BOLD}shipwright stream${RESET} — Live terminal output streaming"
390
+ echo ""
391
+ echo -e "${BOLD}USAGE${RESET}"
392
+ echo -e " ${CYAN}shipwright stream${RESET} <command> [options]"
393
+ echo ""
394
+ echo -e "${BOLD}COMMANDS${RESET}"
395
+ echo -e " ${CYAN}start${RESET} [team] Start streaming agent panes (all or by team)"
396
+ echo -e " ${CYAN}stop${RESET} Stop streaming"
397
+ echo -e " ${CYAN}watch${RESET} <team> [agent] Live tail of stream output in terminal"
398
+ echo -e " ${CYAN}list${RESET} Show active streams"
399
+ echo -e " ${CYAN}replay${RESET} <team> <agent> [N] Show recent N lines from stream (default 50)"
400
+ echo -e " ${CYAN}config${RESET} <key> <value> Set stream configuration"
401
+ echo -e " ${CYAN}help${RESET} Show this help message"
402
+ echo ""
403
+ echo -e "${BOLD}EXAMPLES${RESET}"
404
+ echo -e " ${DIM}shipwright stream start${RESET} # Start streaming all teams"
405
+ echo -e " ${DIM}shipwright stream start myteam${RESET} # Stream only 'myteam'"
406
+ echo -e " ${DIM}shipwright stream watch myteam builder${RESET} # Watch builder agent in myteam"
407
+ echo -e " ${DIM}shipwright stream replay myteam builder 100${RESET} # Show last 100 lines"
408
+ echo -e " ${DIM}shipwright stream config capture_interval_seconds 2${RESET} # Capture every 2s"
409
+ echo -e " ${DIM}shipwright stream list${RESET} # Show all active streams"
410
+ echo ""
411
+ }
412
+
413
+ # ─── Main ───────────────────────────────────────────────────────────────────
414
+ main() {
415
+ local cmd="${1:-help}"
416
+
417
+ case "$cmd" in
418
+ start)
419
+ stream_start "${2:-}"
420
+ ;;
421
+ stop)
422
+ stream_stop
423
+ ;;
424
+ watch)
425
+ stream_watch "${2:-}" "${3:-}"
426
+ ;;
427
+ list)
428
+ stream_list
429
+ ;;
430
+ replay)
431
+ stream_replay "${2:-}" "${3:-}" "${4:-50}"
432
+ ;;
433
+ config)
434
+ stream_config "${2:-}" "${3:-}"
435
+ ;;
436
+ help|--help|-h)
437
+ show_help
438
+ ;;
439
+ *)
440
+ error "Unknown command: $cmd"
441
+ echo ""
442
+ show_help
443
+ exit 1
444
+ ;;
445
+ esac
446
+ }
447
+
448
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
449
+ main "$@"
450
+ fi