shipwright-cli 2.4.0 → 3.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 (161) hide show
  1. package/README.md +16 -11
  2. package/completions/_shipwright +1 -1
  3. package/completions/shipwright.bash +3 -8
  4. package/completions/shipwright.fish +1 -1
  5. package/config/defaults.json +111 -0
  6. package/config/event-schema.json +81 -0
  7. package/config/policy.json +13 -18
  8. package/dashboard/coverage/coverage-summary.json +14 -0
  9. package/dashboard/public/index.html +1 -1
  10. package/dashboard/server.ts +306 -17
  11. package/dashboard/src/components/charts/bar.test.ts +79 -0
  12. package/dashboard/src/components/charts/donut.test.ts +68 -0
  13. package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
  14. package/dashboard/src/components/charts/sparkline.test.ts +125 -0
  15. package/dashboard/src/core/api.test.ts +309 -0
  16. package/dashboard/src/core/helpers.test.ts +301 -0
  17. package/dashboard/src/core/router.test.ts +307 -0
  18. package/dashboard/src/core/router.ts +7 -0
  19. package/dashboard/src/core/sse.test.ts +144 -0
  20. package/dashboard/src/views/metrics.test.ts +186 -0
  21. package/dashboard/src/views/overview.test.ts +173 -0
  22. package/dashboard/src/views/pipelines.test.ts +183 -0
  23. package/dashboard/src/views/team.test.ts +253 -0
  24. package/dashboard/vitest.config.ts +14 -5
  25. package/docs/TIPS.md +1 -1
  26. package/docs/patterns/README.md +1 -1
  27. package/package.json +5 -7
  28. package/scripts/adapters/docker-deploy.sh +1 -1
  29. package/scripts/adapters/tmux-adapter.sh +11 -1
  30. package/scripts/adapters/wezterm-adapter.sh +1 -1
  31. package/scripts/check-version-consistency.sh +1 -1
  32. package/scripts/lib/architecture.sh +126 -0
  33. package/scripts/lib/bootstrap.sh +75 -0
  34. package/scripts/lib/compat.sh +89 -6
  35. package/scripts/lib/config.sh +91 -0
  36. package/scripts/lib/daemon-adaptive.sh +3 -3
  37. package/scripts/lib/daemon-dispatch.sh +39 -16
  38. package/scripts/lib/daemon-health.sh +1 -1
  39. package/scripts/lib/daemon-patrol.sh +24 -12
  40. package/scripts/lib/daemon-poll.sh +37 -25
  41. package/scripts/lib/daemon-state.sh +115 -23
  42. package/scripts/lib/daemon-triage.sh +30 -8
  43. package/scripts/lib/fleet-failover.sh +63 -0
  44. package/scripts/lib/helpers.sh +30 -6
  45. package/scripts/lib/pipeline-detection.sh +2 -2
  46. package/scripts/lib/pipeline-github.sh +9 -9
  47. package/scripts/lib/pipeline-intelligence.sh +85 -35
  48. package/scripts/lib/pipeline-quality-checks.sh +16 -16
  49. package/scripts/lib/pipeline-quality.sh +1 -1
  50. package/scripts/lib/pipeline-stages.sh +242 -28
  51. package/scripts/lib/pipeline-state.sh +40 -4
  52. package/scripts/lib/test-helpers.sh +247 -0
  53. package/scripts/postinstall.mjs +3 -11
  54. package/scripts/sw +10 -4
  55. package/scripts/sw-activity.sh +1 -11
  56. package/scripts/sw-adaptive.sh +109 -85
  57. package/scripts/sw-adversarial.sh +4 -14
  58. package/scripts/sw-architecture-enforcer.sh +1 -11
  59. package/scripts/sw-auth.sh +8 -17
  60. package/scripts/sw-autonomous.sh +111 -49
  61. package/scripts/sw-changelog.sh +1 -11
  62. package/scripts/sw-checkpoint.sh +144 -20
  63. package/scripts/sw-ci.sh +2 -12
  64. package/scripts/sw-cleanup.sh +13 -17
  65. package/scripts/sw-code-review.sh +16 -36
  66. package/scripts/sw-connect.sh +5 -12
  67. package/scripts/sw-context.sh +9 -26
  68. package/scripts/sw-cost.sh +6 -16
  69. package/scripts/sw-daemon.sh +75 -70
  70. package/scripts/sw-dashboard.sh +57 -17
  71. package/scripts/sw-db.sh +506 -15
  72. package/scripts/sw-decompose.sh +1 -11
  73. package/scripts/sw-deps.sh +15 -25
  74. package/scripts/sw-developer-simulation.sh +1 -11
  75. package/scripts/sw-discovery.sh +112 -30
  76. package/scripts/sw-doc-fleet.sh +7 -17
  77. package/scripts/sw-docs-agent.sh +6 -16
  78. package/scripts/sw-docs.sh +4 -12
  79. package/scripts/sw-doctor.sh +134 -43
  80. package/scripts/sw-dora.sh +11 -19
  81. package/scripts/sw-durable.sh +35 -52
  82. package/scripts/sw-e2e-orchestrator.sh +11 -27
  83. package/scripts/sw-eventbus.sh +115 -115
  84. package/scripts/sw-evidence.sh +114 -30
  85. package/scripts/sw-feedback.sh +3 -13
  86. package/scripts/sw-fix.sh +2 -20
  87. package/scripts/sw-fleet-discover.sh +1 -11
  88. package/scripts/sw-fleet-viz.sh +10 -18
  89. package/scripts/sw-fleet.sh +13 -17
  90. package/scripts/sw-github-app.sh +6 -16
  91. package/scripts/sw-github-checks.sh +1 -11
  92. package/scripts/sw-github-deploy.sh +1 -11
  93. package/scripts/sw-github-graphql.sh +2 -12
  94. package/scripts/sw-guild.sh +1 -11
  95. package/scripts/sw-heartbeat.sh +49 -12
  96. package/scripts/sw-hygiene.sh +45 -43
  97. package/scripts/sw-incident.sh +48 -74
  98. package/scripts/sw-init.sh +35 -37
  99. package/scripts/sw-instrument.sh +1 -11
  100. package/scripts/sw-intelligence.sh +362 -51
  101. package/scripts/sw-jira.sh +5 -14
  102. package/scripts/sw-launchd.sh +2 -12
  103. package/scripts/sw-linear.sh +8 -17
  104. package/scripts/sw-logs.sh +4 -12
  105. package/scripts/sw-loop.sh +641 -90
  106. package/scripts/sw-memory.sh +243 -17
  107. package/scripts/sw-mission-control.sh +2 -12
  108. package/scripts/sw-model-router.sh +73 -34
  109. package/scripts/sw-otel.sh +11 -21
  110. package/scripts/sw-oversight.sh +1 -11
  111. package/scripts/sw-patrol-meta.sh +5 -11
  112. package/scripts/sw-pipeline-composer.sh +7 -17
  113. package/scripts/sw-pipeline-vitals.sh +1 -11
  114. package/scripts/sw-pipeline.sh +478 -122
  115. package/scripts/sw-pm.sh +2 -12
  116. package/scripts/sw-pr-lifecycle.sh +27 -25
  117. package/scripts/sw-predictive.sh +16 -22
  118. package/scripts/sw-prep.sh +6 -16
  119. package/scripts/sw-ps.sh +1 -11
  120. package/scripts/sw-public-dashboard.sh +2 -12
  121. package/scripts/sw-quality.sh +77 -10
  122. package/scripts/sw-reaper.sh +1 -11
  123. package/scripts/sw-recruit.sh +15 -25
  124. package/scripts/sw-regression.sh +11 -21
  125. package/scripts/sw-release-manager.sh +19 -28
  126. package/scripts/sw-release.sh +8 -16
  127. package/scripts/sw-remote.sh +1 -11
  128. package/scripts/sw-replay.sh +48 -44
  129. package/scripts/sw-retro.sh +70 -92
  130. package/scripts/sw-review-rerun.sh +1 -1
  131. package/scripts/sw-scale.sh +109 -32
  132. package/scripts/sw-security-audit.sh +12 -22
  133. package/scripts/sw-self-optimize.sh +239 -23
  134. package/scripts/sw-session.sh +3 -13
  135. package/scripts/sw-setup.sh +8 -18
  136. package/scripts/sw-standup.sh +5 -15
  137. package/scripts/sw-status.sh +32 -23
  138. package/scripts/sw-strategic.sh +129 -13
  139. package/scripts/sw-stream.sh +1 -11
  140. package/scripts/sw-swarm.sh +76 -36
  141. package/scripts/sw-team-stages.sh +10 -20
  142. package/scripts/sw-templates.sh +4 -14
  143. package/scripts/sw-testgen.sh +3 -13
  144. package/scripts/sw-tmux-pipeline.sh +1 -19
  145. package/scripts/sw-tmux-role-color.sh +0 -10
  146. package/scripts/sw-tmux-status.sh +3 -11
  147. package/scripts/sw-tmux.sh +2 -20
  148. package/scripts/sw-trace.sh +1 -19
  149. package/scripts/sw-tracker-github.sh +0 -10
  150. package/scripts/sw-tracker-jira.sh +1 -11
  151. package/scripts/sw-tracker-linear.sh +1 -11
  152. package/scripts/sw-tracker.sh +7 -24
  153. package/scripts/sw-triage.sh +24 -34
  154. package/scripts/sw-upgrade.sh +5 -23
  155. package/scripts/sw-ux.sh +1 -19
  156. package/scripts/sw-webhook.sh +18 -32
  157. package/scripts/sw-widgets.sh +3 -21
  158. package/scripts/sw-worktree.sh +11 -27
  159. package/scripts/update-homebrew-sha.sh +67 -0
  160. package/templates/pipelines/tdd.json +72 -0
  161. package/scripts/sw-pipeline.sh.mock +0 -7
@@ -5,7 +5,7 @@
5
5
  # ╚═══════════════════════════════════════════════════════════════════════════╝
6
6
  set -euo pipefail
7
7
 
8
- VERSION="2.4.0"
8
+ VERSION="3.0.0"
9
9
 
10
10
  # ─── Script directory resolution ────────────────────────────────────────────
11
11
  SOURCE="${BASH_SOURCE[0]}"
@@ -35,24 +35,6 @@ if [[ "$(type -t now_iso 2>/dev/null)" != "function" ]]; then
35
35
  now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
36
36
  now_epoch() { date +%s; }
37
37
  fi
38
- if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
39
- emit_event() {
40
- local event_type="$1"; shift; mkdir -p "${HOME}/.shipwright"
41
- local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
42
- while [[ $# -gt 0 ]]; do local key="${1%%=*}" val="${1#*=}"; payload="${payload},\"${key}\":\"${val}\""; shift; done
43
- echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
44
- }
45
- fi
46
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
47
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
48
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
49
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
50
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
51
- RED="${RED:-\033[38;2;248;113;113m}"
52
- DIM="${DIM:-\033[2m}"
53
- BOLD="${BOLD:-\033[1m}"
54
- RESET="${RESET:-\033[0m}"
55
-
56
38
  # E2E-specific: log suite result to RESULTS_LOG (different from canonical emit_event)
57
39
  _log_suite_result() {
58
40
  local event_type="$1"
@@ -250,9 +232,11 @@ run_suite() {
250
232
  info "Running: $suite_name ($features)"
251
233
  local start_time=$(date +%s)
252
234
 
253
- # Run with timeout
235
+ # Run with timeout (gtimeout on macOS, timeout on Linux)
236
+ local timeout_cmd="timeout"
237
+ command -v gtimeout >/dev/null 2>&1 && timeout_cmd="gtimeout"
254
238
  local exit_code=0
255
- timeout "$timeout" bash "$test_script" || exit_code=$?
239
+ "$timeout_cmd" "$timeout" bash "$test_script" || exit_code=$?
256
240
 
257
241
  local end_time=$(date +%s)
258
242
  local duration=$((end_time - start_time))
@@ -306,8 +290,8 @@ run_parallel() {
306
290
  for (( i = 0; i < max_parallel && i < ${#suite_array[@]}; i++ )); do
307
291
  run_suite "${suite_array[$i]}" &
308
292
  pids+=($!)
309
- ((running++))
310
- ((idx++))
293
+ running=$((running + 1))
294
+ idx=$((idx + 1))
311
295
  done
312
296
 
313
297
  # Process remaining suites as workers finish
@@ -321,7 +305,7 @@ run_parallel() {
321
305
  if [[ $idx -lt ${#suite_array[@]} ]]; then
322
306
  run_suite "${suite_array[$idx]}" &
323
307
  pids[$i]=$!
324
- ((idx++))
308
+ idx=$((idx + 1))
325
309
  else
326
310
  unset 'pids[$i]'
327
311
  fi
@@ -359,11 +343,11 @@ cmd_report() {
359
343
  while IFS= read -r line; do
360
344
  local exit_code=$(echo "$line" | jq -r '.exit_code // 0')
361
345
  if [[ $exit_code -eq 0 ]]; then
362
- ((pass++))
346
+ pass=$((pass + 1))
363
347
  elif [[ $exit_code -eq 124 ]]; then
364
- ((timeout++))
348
+ timeout=$((timeout + 1))
365
349
  else
366
- ((fail++))
350
+ fail=$((fail + 1))
367
351
  fi
368
352
  done < "$RESULTS_LOG"
369
353
 
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
9
9
 
10
- VERSION="2.4.0"
10
+ VERSION="3.0.0"
11
11
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
12
 
13
13
  # ─── Cross-platform compatibility ──────────────────────────────────────────
@@ -17,6 +17,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
17
  # Canonical helpers (colors, output, events)
18
18
  # shellcheck source=lib/helpers.sh
19
19
  [[ -f "$SCRIPT_DIR/lib/helpers.sh" ]] && source "$SCRIPT_DIR/lib/helpers.sh"
20
+ # shellcheck source=sw-db.sh
21
+ [[ -f "$SCRIPT_DIR/sw-db.sh" ]] && source "$SCRIPT_DIR/sw-db.sh"
20
22
  # Fallbacks when helpers not loaded (e.g. test env with overridden SCRIPT_DIR)
21
23
  [[ "$(type -t info 2>/dev/null)" == "function" ]] || info() { echo -e "\033[38;2;0;212;255m\033[1m▸\033[0m $*"; }
22
24
  [[ "$(type -t success 2>/dev/null)" == "function" ]] || success() { echo -e "\033[38;2;74;222;128m\033[1m✓\033[0m $*"; }
@@ -34,24 +36,14 @@ if [[ "$(type -t emit_event 2>/dev/null)" != "function" ]]; then
34
36
  echo "${payload}}" >> "${HOME}/.shipwright/events.jsonl"
35
37
  }
36
38
  fi
37
- CYAN="${CYAN:-\033[38;2;0;212;255m}"
38
- PURPLE="${PURPLE:-\033[38;2;124;58;237m}"
39
- BLUE="${BLUE:-\033[38;2;0;102;255m}"
40
- GREEN="${GREEN:-\033[38;2;74;222;128m}"
41
- YELLOW="${YELLOW:-\033[38;2;250;204;21m}"
42
- RED="${RED:-\033[38;2;248;113;113m}"
43
- DIM="${DIM:-\033[2m}"
44
- BOLD="${BOLD:-\033[1m}"
45
- RESET="${RESET:-\033[0m}"
46
-
47
39
  # ─── Configuration ──────────────────────────────────────────────────────────
48
- EVENTBUS_FILE="${HOME}/.shipwright/eventbus.jsonl"
40
+ EVENTS_FILE="${EVENTS_FILE:-${HOME}/.shipwright/events.jsonl}"
49
41
  EVENT_TTL_DAYS=7 # Default TTL for events (seconds = 7 * 86400)
50
42
 
51
43
  # ─── Initialize eventbus directory ──────────────────────────────────────────
52
44
  ensure_eventbus_dir() {
53
45
  local dir
54
- dir="$(dirname "$EVENTBUS_FILE")"
46
+ dir="$(dirname "$EVENTS_FILE")"
55
47
  [[ -d "$dir" ]] || mkdir -p "$dir"
56
48
  }
57
49
 
@@ -66,7 +58,7 @@ generate_uuid() {
66
58
  cmd_publish() {
67
59
  local event_type="$1"
68
60
  local source="${2:-unknown}"
69
- local correlation_id="$3"
61
+ local correlation_id="${3:-}"
70
62
  local payload_json="${4:-{}}"
71
63
 
72
64
  if [[ -z "$event_type" ]]; then
@@ -80,47 +72,54 @@ cmd_publish() {
80
72
  correlation_id="$(generate_uuid)"
81
73
  fi
82
74
 
83
- local timestamp
84
- timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
85
-
86
- # Build event JSON on single line
87
- local event_json
88
- event_json="{\"type\": \"$event_type\", \"source\": \"$source\", \"correlation_id\": \"$correlation_id\", \"timestamp\": \"$timestamp\", \"payload\": $payload_json}"
89
-
90
-
91
- # Atomic write (append to JSONL file)
92
- local tmp_file
93
- tmp_file="$(mktemp)"
94
- if [[ -f "$EVENTBUS_FILE" ]]; then
95
- cat "$EVENTBUS_FILE" > "$tmp_file"
96
- fi
97
- echo "$event_json" >> "$tmp_file"
98
- mv "$tmp_file" "$EVENTBUS_FILE"
75
+ emit_event "$event_type" "source=$source" "correlation_id=$correlation_id" "payload=$payload_json"
99
76
 
100
77
  success "Published event: $event_type (correlation_id: $correlation_id)"
101
78
  }
102
79
 
103
80
  # ─── Subscribe command ──────────────────────────────────────────────────────
104
81
  cmd_subscribe() {
105
- local event_type_filter="${1:-}"
106
- local max_lines="${2:-}"
107
-
108
- ensure_eventbus_dir
82
+ local filter="${1:-}"
83
+ local poll_interval=1
84
+ local last_id=0
109
85
 
110
- [[ ! -f "$EVENTBUS_FILE" ]] && {
111
- warn "Event bus is empty or does not exist yet"
112
- return 0
113
- }
86
+ # Try to resume from last consumer offset
87
+ if db_available 2>/dev/null; then
88
+ last_id=$(db_get_consumer_offset "eventbus-subscribe-$$" 2>/dev/null || echo "0")
89
+ fi
114
90
 
115
- info "Subscribing to event bus (${event_type_filter:-(all types)})..."
91
+ info "Subscribing to events (${filter:-(all types)})..."
116
92
  echo ""
117
93
 
118
- # Tail the file with optional grep filter
119
- if [[ -n "$event_type_filter" ]]; then
120
- tail -f "$EVENTBUS_FILE" | grep "\"type\": \"$event_type_filter\""
121
- else
122
- tail -f "$EVENTBUS_FILE"
123
- fi
94
+ while true; do
95
+ if db_available 2>/dev/null; then
96
+ local events batch_last_id=0
97
+ events=$(sqlite3 -json "$DB_FILE" "SELECT * FROM events WHERE id > $last_id ORDER BY id ASC LIMIT 50;" 2>/dev/null || echo "[]")
98
+ if [[ "$events" != "[]" && -n "$events" ]]; then
99
+ while IFS= read -r event; do
100
+ [[ -z "$event" ]] && continue
101
+ local etype
102
+ etype=$(echo "$event" | jq -r '.type // ""')
103
+ if [[ -z "$filter" || "$etype" == *"$filter"* ]]; then
104
+ echo "$event"
105
+ fi
106
+ batch_last_id=$(echo "$event" | jq -r '.id // 0')
107
+ [[ "${batch_last_id:-0}" -gt 0 ]] && last_id="$batch_last_id"
108
+ done < <(echo "$events" | jq -c '.[]' 2>/dev/null)
109
+ [[ "$last_id" -gt 0 ]] && db_set_consumer_offset "eventbus-subscribe-$$" "$last_id" 2>/dev/null || true
110
+ fi
111
+ else
112
+ # Fallback: tail the JSONL
113
+ [[ ! -f "$EVENTS_FILE" ]] && touch "$EVENTS_FILE"
114
+ tail -n 0 -f "$EVENTS_FILE" 2>/dev/null | while IFS= read -r line; do
115
+ if [[ -z "$filter" ]] || echo "$line" | jq -r '.type // ""' 2>/dev/null | grep -q "$filter"; then
116
+ echo "$line"
117
+ fi
118
+ done
119
+ return
120
+ fi
121
+ sleep "$poll_interval"
122
+ done
124
123
  }
125
124
 
126
125
  # ─── Process reaper (SIGCHLD monitor) ──────────────────────────────────────
@@ -176,14 +175,14 @@ cmd_watch() {
176
175
  echo ""
177
176
 
178
177
  # Determine platform and use appropriate watcher
179
- if command -v fswatch &>/dev/null; then
178
+ if command -v fswatch >/dev/null 2>&1; then
180
179
  # macOS with fswatch
181
180
  fswatch -r "$watch_dir" | while read -r file; do
182
181
  local payload
183
182
  payload="{\"file\": \"$file\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
184
183
  cmd_publish "file.changed" "watcher" "$(generate_uuid)" "$payload"
185
184
  done
186
- elif command -v inotifywait &>/dev/null; then
185
+ elif command -v inotifywait >/dev/null 2>&1; then
187
186
  # Linux with inotify-tools
188
187
  inotifywait -m -r "$watch_dir" | while read -r dir action file; do
189
188
  local filepath="${dir}${file}"
@@ -204,28 +203,27 @@ cmd_replay() {
204
203
 
205
204
  ensure_eventbus_dir
206
205
 
207
- [[ ! -f "$EVENTBUS_FILE" ]] && {
208
- warn "Event bus is empty or does not exist yet"
209
- return 0
210
- }
211
-
212
206
  info "Replaying events from the last ${minutes} minutes..."
213
207
  echo ""
214
208
 
215
- # Calculate cutoff timestamp
216
209
  local cutoff_epoch
217
210
  cutoff_epoch=$(($(date +%s) - (minutes * 60)))
218
- local cutoff_iso
219
- cutoff_iso="$(date -u -j -f %s "$cutoff_epoch" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d @"$cutoff_epoch" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null)"
220
211
 
221
- # Grep and display events after cutoff
222
- grep "timestamp" "$EVENTBUS_FILE" | while read -r line; do
223
- local ts
224
- ts=$(echo "$line" | grep -o '"timestamp": "[^"]*"' | cut -d'"' -f4)
225
- if [[ "$ts" > "$cutoff_iso" ]]; then
226
- echo "$line"
227
- fi
228
- done
212
+ if db_available 2>/dev/null; then
213
+ sqlite3 -json "$DB_FILE" "SELECT * FROM events WHERE ts_epoch >= $cutoff_epoch ORDER BY id ASC;" 2>/dev/null | jq -c '.[]' 2>/dev/null | while IFS= read -r event; do
214
+ [[ -n "$event" ]] && echo "$event"
215
+ done
216
+ elif [[ -f "$EVENTS_FILE" ]]; then
217
+ local cutoff_iso
218
+ cutoff_iso="$(date -u -j -f %s "$cutoff_epoch" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d @"$cutoff_epoch" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null)"
219
+ grep "ts" "$EVENTS_FILE" 2>/dev/null | while read -r line; do
220
+ local ts
221
+ ts=$(echo "$line" | jq -r '.ts // ""' 2>/dev/null)
222
+ [[ -n "$ts" && "$ts" > "$cutoff_iso" ]] && echo "$line"
223
+ done
224
+ else
225
+ warn "Event bus is empty or does not exist yet"
226
+ fi
229
227
  }
230
228
 
231
229
  # ─── Status command ────────────────────────────────────────────────────────
@@ -237,31 +235,37 @@ cmd_status() {
237
235
  echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S')${RESET}"
238
236
  echo ""
239
237
 
240
- if [[ ! -f "$EVENTBUS_FILE" ]]; then
241
- echo -e " ${YELLOW}Event bus not yet initialized${RESET}"
238
+ if db_available 2>/dev/null; then
239
+ local total_events last_event_ts
240
+ total_events=$(_db_query "SELECT COUNT(*) FROM events;" 2>/dev/null || echo "0")
241
+ last_event_ts=$(_db_query "SELECT ts FROM events ORDER BY id DESC LIMIT 1;" 2>/dev/null || echo "never")
242
+ echo -e " ${CYAN}Event Store:${RESET} SQLite ($DB_FILE)"
243
+ echo -e " ${CYAN}Total Events:${RESET} ${BOLD}${total_events}${RESET}"
244
+ echo -e " ${CYAN}Last Event:${RESET} $last_event_ts"
245
+ echo ""
246
+ if [[ "${total_events:-0}" -gt 0 ]]; then
247
+ echo -e " ${PURPLE}${BOLD}Events by Type${RESET}"
248
+ _db_query "SELECT type, COUNT(*) as cnt FROM events GROUP BY type ORDER BY cnt DESC;" 2>/dev/null | while IFS='|' read -r etype count; do
249
+ printf " ${DIM}%-40s${RESET} %3d events\n" "$etype" "$count"
250
+ done
251
+ fi
252
+ elif [[ -f "$EVENTS_FILE" ]]; then
253
+ local total_events last_event_ts
254
+ total_events=$(wc -l < "$EVENTS_FILE" || echo 0)
255
+ last_event_ts=$(tail -1 "$EVENTS_FILE" | jq -r '.ts // "never"' 2>/dev/null || echo "never")
256
+ echo -e " ${CYAN}Event Store:${RESET} $EVENTS_FILE (file fallback)"
257
+ echo -e " ${CYAN}Total Events:${RESET} ${BOLD}${total_events}${RESET}"
258
+ echo -e " ${CYAN}Last Event:${RESET} $last_event_ts"
242
259
  echo ""
243
- return 0
260
+ if [[ "${total_events:-0}" -gt 0 ]]; then
261
+ echo -e " ${PURPLE}${BOLD}Events by Type${RESET}"
262
+ jq -r '.type' "$EVENTS_FILE" 2>/dev/null | sort | uniq -c | sort -rn | while read -r count type; do
263
+ printf " ${DIM}%-40s${RESET} %3d events\n" "$type" "$count"
264
+ done
265
+ fi
266
+ else
267
+ echo -e " ${YELLOW}Event bus not yet initialized${RESET}"
244
268
  fi
245
-
246
- local total_events
247
- total_events=$(wc -l < "$EVENTBUS_FILE" || echo 0)
248
-
249
- local last_event_ts
250
- last_event_ts=$(tail -1 "$EVENTBUS_FILE" | grep -o '"timestamp": "[^"]*"' | cut -d'"' -f4 || echo "never")
251
-
252
- echo -e " ${CYAN}Event Bus:${RESET} $EVENTBUS_FILE"
253
- echo -e " ${CYAN}Total Events:${RESET} ${BOLD}${total_events}${RESET}"
254
- echo -e " ${CYAN}Last Event:${RESET} $last_event_ts"
255
- echo ""
256
-
257
- # Count events by type
258
- if [[ $total_events -gt 0 ]]; then
259
- echo -e " ${PURPLE}${BOLD}Events by Type${RESET}"
260
- grep '"type"' "$EVENTBUS_FILE" | cut -d'"' -f4 | sort | uniq -c | sort -rn | while read -r count type; do
261
- printf " ${DIM}%-40s${RESET} %3d events\n" "$type" "$count"
262
- done
263
- fi
264
-
265
269
  echo ""
266
270
  }
267
271
 
@@ -271,40 +275,36 @@ cmd_clean() {
271
275
 
272
276
  ensure_eventbus_dir
273
277
 
274
- [[ ! -f "$EVENTBUS_FILE" ]] && {
275
- success "Event bus is empty"
276
- return 0
277
- }
278
-
279
- info "Cleaning events older than ${ttl_days} days..."
280
-
281
- # Calculate cutoff timestamp
282
278
  local cutoff_epoch
283
279
  cutoff_epoch=$(($(date +%s) - (ttl_days * 86400)))
284
280
  local cutoff_iso
285
281
  cutoff_iso="$(date -u -j -f %s "$cutoff_epoch" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d @"$cutoff_epoch" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null)"
286
282
 
287
- local old_count
288
- old_count=$(grep -c "timestamp" "$EVENTBUS_FILE" 2>/dev/null || echo 0)
289
-
290
- # Keep only recent events
291
- local tmp_file
292
- tmp_file="$(mktemp)"
293
- grep "timestamp" "$EVENTBUS_FILE" | while read -r line; do
294
- local ts
295
- ts=$(echo "$line" | grep -o '"timestamp": "[^"]*"' | cut -d'"' -f4)
296
- if [[ "$ts" > "$cutoff_iso" ]]; then
297
- echo "$line" >> "$tmp_file"
298
- fi
299
- done
300
-
301
- mv "$tmp_file" "$EVENTBUS_FILE"
302
-
303
- local new_count
304
- new_count=$(wc -l < "$EVENTBUS_FILE" || echo 0)
305
- local removed=$((old_count - new_count))
306
-
307
- success "Removed $removed old events. Remaining: $new_count"
283
+ if db_available 2>/dev/null; then
284
+ local old_count new_count removed
285
+ old_count=$(_db_query "SELECT COUNT(*) FROM events;" 2>/dev/null || echo 0)
286
+ _db_exec "DELETE FROM events WHERE ts < '${cutoff_iso}';" 2>/dev/null || true
287
+ new_count=$(_db_query "SELECT COUNT(*) FROM events;" 2>/dev/null || echo 0)
288
+ removed=$((old_count - new_count))
289
+ success "Removed $removed old events. Remaining: $new_count"
290
+ elif [[ -f "$EVENTS_FILE" ]]; then
291
+ info "Cleaning events older than ${ttl_days} days..."
292
+ local old_count tmp_file new_count removed
293
+ old_count=$(grep -c "ts" "$EVENTS_FILE" 2>/dev/null || echo 0)
294
+ tmp_file="$(mktemp)"
295
+ while IFS= read -r line; do
296
+ [[ -z "$line" ]] && continue
297
+ local ts
298
+ ts=$(echo "$line" | jq -r '.ts // ""' 2>/dev/null)
299
+ [[ -n "$ts" && "$ts" > "$cutoff_iso" ]] && echo "$line" >> "$tmp_file"
300
+ done < "$EVENTS_FILE"
301
+ mv "$tmp_file" "$EVENTS_FILE"
302
+ new_count=$(wc -l < "$EVENTS_FILE" || echo 0)
303
+ removed=$((old_count - new_count))
304
+ success "Removed $removed old events. Remaining: $new_count"
305
+ else
306
+ success "Event bus is empty"
307
+ fi
308
308
  }
309
309
 
310
310
  # ─── Help command ──────────────────────────────────────────────────────────