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,500 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright activity — Live agent activity stream ║
4
+ # ║ Watch Claude think in real-time with formatted event streaming ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
+
9
+ VERSION="2.0.0"
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
13
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
14
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
15
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
16
+ GREEN='\033[38;2;74;222;128m' # success
17
+ YELLOW='\033[38;2;250;204;21m' # warning
18
+ RED='\033[38;2;248;113;113m' # error
19
+ DIM='\033[2m'
20
+ BOLD='\033[1m'
21
+ RESET='\033[0m'
22
+
23
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
24
+ # shellcheck source=lib/compat.sh
25
+ [ -f "$SCRIPT_DIR/lib/compat.sh" ] && source "$SCRIPT_DIR/lib/compat.sh"
26
+
27
+ # ─── Output Helpers ─────────────────────────────────────────────────────────
28
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
29
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
30
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
31
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
32
+
33
+ # ─── Event File & Filters ─────────────────────────────────────────────────────
34
+ EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
35
+ FILTER_TYPE=""
36
+ FILTER_AGENT=""
37
+ FILTER_TEAM=""
38
+ FILTER_STAGE=""
39
+ FILTER_START=""
40
+ FILTER_END=""
41
+
42
+ # Event type icons for terminal display (bash 3.2 compatible)
43
+ get_icon_for_type() {
44
+ local type="$1"
45
+ case "$type" in
46
+ commit) echo "📦" ;;
47
+ test.passed) echo "✅" ;;
48
+ test.failed) echo "❌" ;;
49
+ build) echo "🔨" ;;
50
+ review) echo "👀" ;;
51
+ stage.started) echo "▶" ;;
52
+ stage.completed) echo "⏹" ;;
53
+ pipeline.started) echo "🚀" ;;
54
+ pipeline.completed) echo "🎯" ;;
55
+ file.modified) echo "✏" ;;
56
+ error) echo "⚠" ;;
57
+ *) echo "•" ;;
58
+ esac
59
+ }
60
+
61
+ # Color agents by a simple hash (bash 3.2 compatible)
62
+ agent_color() {
63
+ local agent="$1"
64
+ local hash=0
65
+ local i
66
+ for (( i=0; i<${#agent}; i++ )); do
67
+ hash=$(( (hash << 5) - hash + $(printf '%d' "'${agent:$i:1}") ))
68
+ done
69
+ # Simple modulo to pick a color
70
+ local idx=$(( (hash % 5 + 5) % 5 ))
71
+ case "$idx" in
72
+ 0) echo "$CYAN" ;;
73
+ 1) echo "$PURPLE" ;;
74
+ 2) echo "$BLUE" ;;
75
+ 3) echo "$GREEN" ;;
76
+ 4) echo "$YELLOW" ;;
77
+ esac
78
+ }
79
+
80
+ # Emit structured event (for testing/logging)
81
+ emit_event() {
82
+ local event_type="$1"
83
+ shift
84
+ local json_fields=""
85
+ for kv in "$@"; do
86
+ local key="${kv%%=*}"
87
+ local val="${kv#*=}"
88
+ if echo "$val" | grep -qE '^-?[0-9]+\.?[0-9]*$'; then
89
+ json_fields="${json_fields},\"${key}\":${val}"
90
+ else
91
+ local escaped_val
92
+ escaped_val=$(printf '%s' "$val" | jq -Rs '.' 2>/dev/null || printf '"%s"' "${val//\"/\\\"}")
93
+ json_fields="${json_fields},\"${key}\":${escaped_val}"
94
+ fi
95
+ done
96
+ mkdir -p "${HOME}/.shipwright"
97
+ local iso_time
98
+ iso_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
99
+ local epoch_time
100
+ epoch_time=$(date +%s)
101
+ echo "{\"ts\":\"${iso_time}\",\"ts_epoch\":${epoch_time},\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
102
+ }
103
+
104
+ # ─── Formatting Helpers ────────────────────────────────────────────────────────
105
+ format_timestamp() {
106
+ local ts="$1"
107
+ echo "$ts" | sed 's/T/ /; s/Z//'
108
+ }
109
+
110
+ get_event_icon() {
111
+ local event_type="$1"
112
+ get_icon_for_type "$event_type"
113
+ }
114
+
115
+ format_event_line() {
116
+ local ts="$1"
117
+ local type="$2"
118
+ local agent="${3:-system}"
119
+ local message="$4"
120
+
121
+ local ts_fmt
122
+ ts_fmt=$(format_timestamp "$ts")
123
+ local icon
124
+ icon=$(get_event_icon "$type")
125
+ local agent_color_code
126
+ agent_color_code=$(agent_color "$agent")
127
+
128
+ printf "%s %s ${BOLD}%s${RESET} %s %s\n" \
129
+ "${DIM}${ts_fmt}${RESET}" \
130
+ "$icon" \
131
+ "${agent_color_code}${agent:0:12}${RESET}" \
132
+ "${BLUE}${type}${RESET}" \
133
+ "$message"
134
+ }
135
+
136
+ # ─── Live Watch Mode (default) ──────────────────────────────────────────────────
137
+ cmd_watch() {
138
+ local poll_interval=1
139
+ local last_size=0
140
+
141
+ info "Watching agent activity (Ctrl+C to stop)..."
142
+ echo ""
143
+
144
+ # Create initial size check
145
+ if [ ! -f "$EVENTS_FILE" ]; then
146
+ warn "No events file yet (waiting for first pipeline run)..."
147
+ touch "$EVENTS_FILE"
148
+ fi
149
+
150
+ last_size=$(wc -c < "$EVENTS_FILE" 2>/dev/null || echo 0)
151
+
152
+ while true; do
153
+ local current_size
154
+ current_size=$(wc -c < "$EVENTS_FILE" 2>/dev/null || echo 0)
155
+
156
+ # If file grew, tail new lines
157
+ if [ $current_size -gt $last_size ]; then
158
+ tail -c $((current_size - last_size)) "$EVENTS_FILE" 2>/dev/null | while IFS= read -r line; do
159
+ [ -z "$line" ] && continue
160
+
161
+ # Extract JSON fields
162
+ local ts type agent stage issue
163
+ ts=$(echo "$line" | jq -r '.ts // ""' 2>/dev/null || true)
164
+ type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null || true)
165
+ agent=$(echo "$line" | jq -r '.agent // "system"' 2>/dev/null || true)
166
+ stage=$(echo "$line" | jq -r '.stage // ""' 2>/dev/null || true)
167
+ issue=$(echo "$line" | jq -r '.issue // ""' 2>/dev/null || true)
168
+
169
+ # Apply filters
170
+ if [ -n "$FILTER_TYPE" ] && [ "$type" != "$FILTER_TYPE" ]; then
171
+ continue
172
+ fi
173
+ if [ -n "$FILTER_AGENT" ] && [ "$agent" != "$FILTER_AGENT" ]; then
174
+ continue
175
+ fi
176
+ if [ -n "$FILTER_STAGE" ] && [ "$stage" != "$FILTER_STAGE" ]; then
177
+ continue
178
+ fi
179
+
180
+ # Format message based on event type
181
+ local msg=""
182
+ case "$type" in
183
+ stage.started)
184
+ msg="Started ${stage} stage"
185
+ if [ -n "$issue" ]; then
186
+ msg="$msg (issue #${issue})"
187
+ fi
188
+ ;;
189
+ stage.completed)
190
+ local duration
191
+ duration=$(echo "$line" | jq -r '.duration_s // "?"' 2>/dev/null || true)
192
+ msg="Completed ${stage} in ${duration}s"
193
+ ;;
194
+ pipeline.started)
195
+ local pipeline
196
+ pipeline=$(echo "$line" | jq -r '.pipeline // "unknown"' 2>/dev/null || true)
197
+ msg="Started ${pipeline} pipeline"
198
+ ;;
199
+ pipeline.completed)
200
+ local result
201
+ result=$(echo "$line" | jq -r '.result // "?"' 2>/dev/null || true)
202
+ msg="Pipeline finished: ${result}"
203
+ ;;
204
+ file.modified)
205
+ local file
206
+ file=$(echo "$line" | jq -r '.file // "?"' 2>/dev/null || true)
207
+ msg="Modified: ${file}"
208
+ ;;
209
+ test.passed)
210
+ local test_count
211
+ test_count=$(echo "$line" | jq -r '.count // "1"' 2>/dev/null || true)
212
+ msg="Tests passed: ${test_count}"
213
+ ;;
214
+ test.failed)
215
+ local failure
216
+ failure=$(echo "$line" | jq -r '.reason // "unknown"' 2>/dev/null || true)
217
+ msg="Test failed: ${failure}"
218
+ ;;
219
+ commit)
220
+ local commit_msg
221
+ commit_msg=$(echo "$line" | jq -r '.message // "?"' 2>/dev/null | cut -c1-50)
222
+ msg="Committed: ${commit_msg}"
223
+ ;;
224
+ *)
225
+ msg="$type"
226
+ ;;
227
+ esac
228
+
229
+ format_event_line "$ts" "$type" "$agent" "$msg"
230
+ done
231
+ last_size=$current_size
232
+ fi
233
+
234
+ sleep "$poll_interval"
235
+ done
236
+ }
237
+
238
+ # ─── Snapshot Mode ────────────────────────────────────────────────────────────
239
+ cmd_snapshot() {
240
+ [ ! -f "$EVENTS_FILE" ] && { error "No events yet"; exit 1; }
241
+
242
+ info "Current agent activity snapshot:"
243
+ echo ""
244
+
245
+ # Group by agent, show last event for each
246
+ local last_agent=""
247
+ local last_time=""
248
+ local last_event=""
249
+
250
+ tac "$EVENTS_FILE" 2>/dev/null | while IFS= read -r line; do
251
+ [ -z "$line" ] && continue
252
+
253
+ local agent stage ts
254
+ agent=$(echo "$line" | jq -r '.agent // "system"' 2>/dev/null || true)
255
+ stage=$(echo "$line" | jq -r '.stage // ""' 2>/dev/null || true)
256
+ ts=$(echo "$line" | jq -r '.ts // ""' 2>/dev/null || true)
257
+
258
+ # Skip if we've seen this agent already
259
+ if [ "$agent" = "$last_agent" ]; then
260
+ continue
261
+ fi
262
+
263
+ local msg
264
+ local type
265
+ type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null || true)
266
+ msg=$(echo "$line" | jq -r '.type // "unknown"' 2>/dev/null || true)
267
+
268
+ printf "%s ${BOLD}%s${RESET}\n" \
269
+ "${DIM}${ts}${RESET}" \
270
+ "$(agent_color "$agent")${agent}${RESET}"
271
+ printf " └─ %s (${DIM}${type}${RESET})\n" "$msg"
272
+ echo ""
273
+
274
+ last_agent="$agent"
275
+ done
276
+ }
277
+
278
+ # ─── History Mode ────────────────────────────────────────────────────────────
279
+ cmd_history() {
280
+ local range="$1" # "1h", "10m", "all", or ISO timestamp
281
+
282
+ [ ! -f "$EVENTS_FILE" ] && { error "No events yet"; exit 1; }
283
+
284
+ local cutoff_epoch=0
285
+
286
+ # Parse time range
287
+ if [ "$range" = "all" ]; then
288
+ cutoff_epoch=0
289
+ elif echo "$range" | grep -qE '^[0-9]+[smhd]$'; then
290
+ local num="${range%[smhd]}"
291
+ local unit="${range##[0-9]}"
292
+ local seconds=0
293
+ case "$unit" in
294
+ s) seconds="$num" ;;
295
+ m) seconds=$((num * 60)) ;;
296
+ h) seconds=$((num * 3600)) ;;
297
+ d) seconds=$((num * 86400)) ;;
298
+ esac
299
+ cutoff_epoch=$(($(date +%s) - seconds))
300
+ else
301
+ # Assume ISO timestamp
302
+ cutoff_epoch=$(date -d "$range" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$range" +%s 2>/dev/null || echo 0)
303
+ fi
304
+
305
+ info "Activity from last ${range}:"
306
+ echo ""
307
+
308
+ grep -v '^$' "$EVENTS_FILE" 2>/dev/null | while IFS= read -r line; do
309
+ [ -z "$line" ] && continue
310
+
311
+ local epoch agent ts type
312
+ epoch=$(echo "$line" | jq -r '.ts_epoch // 0' 2>/dev/null || true)
313
+ agent=$(echo "$line" | jq -r '.agent // "system"' 2>/dev/null || true)
314
+ ts=$(echo "$line" | jq -r '.ts // ""' 2>/dev/null || true)
315
+ type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null || true)
316
+
317
+ if [ $epoch -lt $cutoff_epoch ]; then
318
+ continue
319
+ fi
320
+
321
+ local msg="$type"
322
+ case "$type" in
323
+ stage.started)
324
+ local stage
325
+ stage=$(echo "$line" | jq -r '.stage // ""' 2>/dev/null || true)
326
+ msg="Started ${stage} stage"
327
+ ;;
328
+ pipeline.completed)
329
+ local result
330
+ result=$(echo "$line" | jq -r '.result // "?"' 2>/dev/null || true)
331
+ msg="Pipeline completed: ${result}"
332
+ ;;
333
+ esac
334
+
335
+ format_event_line "$ts" "$type" "$agent" "$msg"
336
+ done
337
+ }
338
+
339
+ # ─── Statistics Mode ────────────────────────────────────────────────────────────
340
+ cmd_stats() {
341
+ [ ! -f "$EVENTS_FILE" ] && { error "No events yet"; exit 1; }
342
+
343
+ info "Activity statistics:"
344
+ echo ""
345
+
346
+ local total_events=0
347
+ local commits=0
348
+ local tests=0
349
+ local stages=0
350
+ local pipelines=0
351
+ local agents_seen=""
352
+ local start_time=""
353
+ local end_time=""
354
+
355
+ # Read directly to avoid subshell issues
356
+ echo "DEBUG: Starting read loop..." >&2
357
+ while IFS= read -r line; do
358
+ [ -z "$line" ] && continue
359
+
360
+ total_events=$((total_events + 1))
361
+
362
+ local type agent ts
363
+ type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null || true)
364
+ agent=$(echo "$line" | jq -r '.agent // "system"' 2>/dev/null || true)
365
+ ts=$(echo "$line" | jq -r '.ts // ""' 2>/dev/null || true)
366
+
367
+ # Track unique agents
368
+ if [ -n "$agent" ] && [ "$agent" != "system" ]; then
369
+ agents_seen="${agents_seen}${agent}"$'\n'
370
+ fi
371
+
372
+ # Track event types
373
+ case "$type" in
374
+ commit) commits=$((commits + 1)) ;;
375
+ test.*) tests=$((tests + 1)) ;;
376
+ stage.*) stages=$((stages + 1)) ;;
377
+ pipeline.*) pipelines=$((pipelines + 1)) ;;
378
+ esac
379
+
380
+ # Track first and last event times
381
+ if [ -z "$start_time" ]; then
382
+ start_time="$ts"
383
+ fi
384
+ end_time="$ts"
385
+ done < <(grep -v '^$' "$EVENTS_FILE" 2>/dev/null)
386
+
387
+ echo "DEBUG: Read complete, total=$total_events" >&2
388
+ echo "DEBUG: agents_seen length: ${#agents_seen}" >&2
389
+ local unique_agents
390
+ echo "DEBUG: About to compute unique..." >&2
391
+ unique_agents=$(sort -u <<< "$agents_seen" | grep -v '^$' | wc -l | tr -d ' ')
392
+ echo "DEBUG: unique_agents=$unique_agents" >&2
393
+
394
+ echo "DEBUG: About to print results..." >&2
395
+ printf "${BOLD}Total Events:${RESET} %d\n" "$total_events"
396
+ printf "${BOLD}Commits:${RESET} %d\n" "$commits"
397
+ printf "${BOLD}Tests:${RESET} %d\n" "$tests"
398
+ printf "${BOLD}Stages:${RESET} %d\n" "$stages"
399
+ printf "${BOLD}Pipelines:${RESET} %d\n" "$pipelines"
400
+ printf "${BOLD}Unique Agents:${RESET} %d\n" "$unique_agents"
401
+ printf "${BOLD}Time Range:${RESET} ${DIM}%s${RESET} to ${DIM}%s${RESET}\n" "$start_time" "$end_time"
402
+ }
403
+
404
+ # ─── Agents Mode ────────────────────────────────────────────────────────────────
405
+ cmd_agents() {
406
+ [ ! -f "$EVENTS_FILE" ] && { error "No events yet"; exit 1; }
407
+
408
+ info "Known agents and last activity:"
409
+ echo ""
410
+
411
+ # Use tac to go backwards and capture unique agents
412
+ tac "$EVENTS_FILE" 2>/dev/null | while IFS= read -r line; do
413
+ [ -z "$line" ] && continue
414
+
415
+ local agent ts type
416
+ agent=$(echo "$line" | jq -r '.agent // "system"' 2>/dev/null || true)
417
+ ts=$(echo "$line" | jq -r '.ts // ""' 2>/dev/null || true)
418
+ type=$(echo "$line" | jq -r '.type // ""' 2>/dev/null || true)
419
+
420
+ if [ "$agent" = "system" ] || [ -z "$agent" ]; then
421
+ continue
422
+ fi
423
+
424
+ # Only print once per agent (first occurrence in reverse iteration)
425
+ local seen_file="${HOME}/.shipwright/activity-agents-seen"
426
+ mkdir -p "$(dirname "$seen_file")"
427
+ if ! grep -q "^${agent}$" "$seen_file" 2>/dev/null; then
428
+ printf "%s ${BOLD}%s${RESET} ${DIM}%s${RESET} ${BLUE}%s${RESET}\n" \
429
+ "$ts" \
430
+ "$(agent_color "$agent")${agent}${RESET}" \
431
+ "$type" \
432
+ ""
433
+ echo "$agent" >> "$seen_file"
434
+ fi
435
+ done
436
+ }
437
+
438
+ # ─── Help ────────────────────────────────────────────────────────────────────
439
+ cmd_help() {
440
+ echo -e "${CYAN}${BOLD}shipwright activity${RESET} — Live agent activity stream"
441
+ echo ""
442
+ echo -e "${BOLD}USAGE${RESET}"
443
+ echo " shipwright activity [subcommand] [options]"
444
+ echo ""
445
+ echo -e "${BOLD}SUBCOMMANDS${RESET}"
446
+ echo " watch Live stream of agent activity (default)"
447
+ echo " snapshot Current state of all active agents"
448
+ echo " history [range] Replay past activity (e.g., '1h', '10m', 'all')"
449
+ echo " stats Running counters (events, commits, tests, agents)"
450
+ echo " agents List known agents and last activity"
451
+ echo " help Show this help message"
452
+ echo ""
453
+ echo -e "${BOLD}OPTIONS${RESET}"
454
+ echo " --type <type> Filter events by type (e.g., 'stage.completed')"
455
+ echo " --agent <name> Filter by agent name"
456
+ echo " --team <name> Filter by team"
457
+ echo " --stage <name> Filter by pipeline stage (e.g., 'build')"
458
+ echo ""
459
+ echo -e "${BOLD}EXAMPLES${RESET}"
460
+ echo " ${DIM}shipwright activity${RESET} # Live stream"
461
+ echo " ${DIM}shipwright activity watch --type stage.*${RESET} # Only stage events"
462
+ echo " ${DIM}shipwright activity history 1h${RESET} # Last hour"
463
+ echo " ${DIM}shipwright activity snapshot${RESET} # Current state"
464
+ echo " ${DIM}shipwright activity stats${RESET} # Counters"
465
+ }
466
+
467
+ # ─── Main ────────────────────────────────────────────────────────────────────────
468
+ main() {
469
+ local cmd="${1:-watch}"
470
+ shift 2>/dev/null || true
471
+
472
+ # Parse global options
473
+ while [ $# -gt 0 ]; do
474
+ case "$1" in
475
+ --type) FILTER_TYPE="$2"; shift 2 ;;
476
+ --agent) FILTER_AGENT="$2"; shift 2 ;;
477
+ --team) FILTER_TEAM="$2"; shift 2 ;;
478
+ --stage) FILTER_STAGE="$2"; shift 2 ;;
479
+ *) break ;;
480
+ esac
481
+ done
482
+
483
+ case "$cmd" in
484
+ watch) cmd_watch "$@" ;;
485
+ snapshot) cmd_snapshot ;;
486
+ history) cmd_history "${1:-1h}" ;;
487
+ stats) cmd_stats ;;
488
+ agents) cmd_agents ;;
489
+ help|-h|--help) cmd_help ;;
490
+ *)
491
+ error "Unknown command: $cmd"
492
+ cmd_help
493
+ exit 1
494
+ ;;
495
+ esac
496
+ }
497
+
498
+ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
499
+ main "$@"
500
+ fi