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,530 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ sw-widgets.sh — Embeddable Status Widgets ║
4
+ # ║ ║
5
+ # ║ Generate badges, Slack messages, markdown blocks, and JSON exports ║
6
+ # ║ for embedding Shipwright status in external dashboards and README ║
7
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
8
+ set -euo pipefail
9
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
10
+
11
+ VERSION="2.0.0"
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
14
+
15
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
16
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
17
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
18
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
19
+ GREEN='\033[38;2;74;222;128m' # success
20
+ YELLOW='\033[38;2;250;204;21m' # warning
21
+ RED='\033[38;2;248;113;113m' # error
22
+ DIM='\033[2m'
23
+ BOLD='\033[1m'
24
+ RESET='\033[0m'
25
+
26
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
27
+ _COMPAT="$SCRIPT_DIR/lib/compat.sh"
28
+ # shellcheck source=lib/compat.sh
29
+ [[ -f "$_COMPAT" ]] && source "$_COMPAT"
30
+
31
+ # ─── Output Helpers ────────────────────────────────────────────────────────
32
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
33
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
34
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
35
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
36
+
37
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
38
+
39
+ # ─── Configuration ─────────────────────────────────────────────────────────
40
+ CONFIG_DIR="${HOME}/.shipwright"
41
+ CONFIG_FILE="${CONFIG_DIR}/widgets-config.json"
42
+ EVENTS_FILE="${CONFIG_DIR}/events.jsonl"
43
+ PIPELINE_STATE="${REPO_DIR}/.claude/pipeline-state.md"
44
+ COSTS_FILE="${CONFIG_DIR}/costs.json"
45
+
46
+ # ─── Helpers ───────────────────────────────────────────────────────────────
47
+
48
+ # Safely extract numeric values
49
+ _safe_num() {
50
+ local val="${1:-0}"
51
+ if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
52
+ echo "$val"
53
+ else
54
+ echo "0"
55
+ fi
56
+ }
57
+
58
+ # Safely extract string values from JSON
59
+ _safe_str() {
60
+ local val="${1:-unknown}"
61
+ echo "$val" | sed 's/"/\\"/g'
62
+ }
63
+
64
+ # Get current pipeline status
65
+ _get_pipeline_status() {
66
+ if [[ ! -f "$PIPELINE_STATE" ]]; then
67
+ echo "unknown"
68
+ return
69
+ fi
70
+
71
+ # Try to extract status from pipeline state markdown
72
+ if grep -qi "status.*passing" "$PIPELINE_STATE" 2>/dev/null; then
73
+ echo "passing"
74
+ elif grep -qi "status.*failing" "$PIPELINE_STATE" 2>/dev/null; then
75
+ echo "failing"
76
+ elif grep -qi "status.*running" "$PIPELINE_STATE" 2>/dev/null; then
77
+ echo "running"
78
+ else
79
+ echo "unknown"
80
+ fi
81
+ }
82
+
83
+ # Get test pass rate
84
+ _get_test_stats() {
85
+ if [[ ! -f "$EVENTS_FILE" ]]; then
86
+ echo "0"
87
+ return
88
+ fi
89
+
90
+ # Count test-related events
91
+ local pass_count
92
+ pass_count=$(grep -i "test.*passed" "$EVENTS_FILE" 2>/dev/null | wc -l || echo "0")
93
+ pass_count=$(_safe_num "$pass_count")
94
+
95
+ echo "$pass_count"
96
+ }
97
+
98
+ # Get current version
99
+ _get_version() {
100
+ if [[ -f "$REPO_DIR/package.json" ]]; then
101
+ jq -r '.version // "unknown"' "$REPO_DIR/package.json" 2>/dev/null || echo "unknown"
102
+ else
103
+ echo "unknown"
104
+ fi
105
+ }
106
+
107
+ # Get health score (0-100)
108
+ _get_health_score() {
109
+ if [[ ! -f "$EVENTS_FILE" ]]; then
110
+ echo "50"
111
+ return
112
+ fi
113
+
114
+ # Simple health calculation: recent successful stages vs failed
115
+ local recent_events
116
+ recent_events=$(tail -n 100 "$EVENTS_FILE" 2>/dev/null || true)
117
+
118
+ local success_count
119
+ success_count=$(echo "$recent_events" | grep -i "stage.*completed" | wc -l || echo "0")
120
+ success_count=$(_safe_num "$success_count")
121
+
122
+ local fail_count
123
+ fail_count=$(echo "$recent_events" | grep -i "stage.*failed" | wc -l || echo "0")
124
+ fail_count=$(_safe_num "$fail_count")
125
+
126
+ local total=$((success_count + fail_count))
127
+ if [[ $total -eq 0 ]]; then
128
+ echo "50"
129
+ else
130
+ awk "BEGIN {printf \"%.0f\", ($success_count / $total) * 100}"
131
+ fi
132
+ }
133
+
134
+ # ─── Badge Generation (shields.io) ─────────────────────────────────────────
135
+
136
+ badge_pipeline() {
137
+ local status
138
+ status=$(_get_pipeline_status)
139
+
140
+ local color
141
+ case "$status" in
142
+ passing) color="brightgreen" ;;
143
+ failing) color="red" ;;
144
+ running) color="blue" ;;
145
+ *) color="lightgrey" ;;
146
+ esac
147
+
148
+ echo "https://img.shields.io/badge/pipeline-${status}-${color}"
149
+ }
150
+
151
+ badge_tests() {
152
+ local count
153
+ count=$(_get_test_stats)
154
+
155
+ echo "https://img.shields.io/badge/tests-${count}%2B%20passing-brightgreen"
156
+ }
157
+
158
+ badge_version() {
159
+ local version
160
+ version=$(_get_version)
161
+
162
+ # URL-encode dots as %2E
163
+ version="${version//./%2E}"
164
+ echo "https://img.shields.io/badge/version-v${version}-blue"
165
+ }
166
+
167
+ badge_health() {
168
+ local score
169
+ score=$(_get_health_score)
170
+
171
+ local color
172
+ if [[ $score -ge 80 ]]; then
173
+ color="brightgreen"
174
+ elif [[ $score -ge 60 ]]; then
175
+ color="yellow"
176
+ else
177
+ color="red"
178
+ fi
179
+
180
+ echo "https://img.shields.io/badge/health-${score}%25-${color}"
181
+ }
182
+
183
+ # ─── Command: badge ──────────────────────────────────────────────────────────
184
+
185
+ cmd_badge() {
186
+ local type="${1:-pipeline}"
187
+
188
+ case "$type" in
189
+ pipeline)
190
+ badge_pipeline
191
+ ;;
192
+ tests)
193
+ badge_tests
194
+ ;;
195
+ version)
196
+ badge_version
197
+ ;;
198
+ health)
199
+ badge_health
200
+ ;;
201
+ all)
202
+ echo "Pipeline: $(badge_pipeline)"
203
+ echo "Tests: $(badge_tests)"
204
+ echo "Version: $(badge_version)"
205
+ echo "Health: $(badge_health)"
206
+ ;;
207
+ *)
208
+ error "Unknown badge type: $type"
209
+ echo " Valid: pipeline, tests, version, health, all"
210
+ exit 1
211
+ ;;
212
+ esac
213
+ }
214
+
215
+ # ─── Command: slack ───────────────────────────────────────────────────────────
216
+
217
+ cmd_slack() {
218
+ local webhook_url=""
219
+ local channel=""
220
+
221
+ while [[ $# -gt 0 ]]; do
222
+ case "$1" in
223
+ --webhook)
224
+ webhook_url="$2"
225
+ shift 2
226
+ ;;
227
+ --channel)
228
+ channel="$2"
229
+ shift 2
230
+ ;;
231
+ *)
232
+ error "Unknown option: $1"
233
+ exit 1
234
+ ;;
235
+ esac
236
+ done
237
+
238
+ # Try to load webhook from config if not provided
239
+ if [[ -z "$webhook_url" ]] && [[ -f "$CONFIG_FILE" ]]; then
240
+ webhook_url=$(jq -r '.slack.webhook_url // empty' "$CONFIG_FILE" 2>/dev/null || true)
241
+ fi
242
+
243
+ if [[ -z "$webhook_url" ]]; then
244
+ error "No webhook URL provided. Use --webhook or configure in $CONFIG_FILE"
245
+ exit 1
246
+ fi
247
+
248
+ # Gather status data
249
+ local status
250
+ status=$(_get_pipeline_status)
251
+ local tests
252
+ tests=$(_get_test_stats)
253
+ local health
254
+ health=$(_get_health_score)
255
+
256
+ # Determine color based on status
257
+ local color
258
+ case "$status" in
259
+ passing) color="#4ade80" ;;
260
+ failing) color="#f87171" ;;
261
+ running) color="#60a5fa" ;;
262
+ *) color="#9ca3af" ;;
263
+ esac
264
+
265
+ # Build Slack message
266
+ local message_json
267
+ message_json=$(jq -n \
268
+ --arg channel "$channel" \
269
+ --arg status "$status" \
270
+ --arg tests "$tests" \
271
+ --arg health "$health" \
272
+ --arg color "$color" \
273
+ '{
274
+ channel: $channel,
275
+ attachments: [
276
+ {
277
+ color: $color,
278
+ title: "Shipwright Pipeline Status",
279
+ fields: [
280
+ {title: "Status", value: $status, short: true},
281
+ {title: "Tests Passing", value: $tests, short: true},
282
+ {title: "Health Score", value: ($health + "%"), short: true},
283
+ {title: "Updated", value: "'$(now_iso)'", short: true}
284
+ ],
285
+ footer: "Shipwright Status Widget",
286
+ ts: '$(date +%s)'
287
+ }
288
+ ]
289
+ }' | sed 's/"channel":""/"channel": null/' \
290
+ )
291
+
292
+ # Send to webhook
293
+ if command -v curl &>/dev/null; then
294
+ response=$(curl -s -X POST "$webhook_url" \
295
+ -H 'Content-Type: application/json' \
296
+ -d "$message_json" 2>&1)
297
+
298
+ if echo "$response" | grep -qi "ok"; then
299
+ success "Slack message sent"
300
+ else
301
+ warn "Slack response: $response"
302
+ fi
303
+ else
304
+ error "curl is required for Slack integration"
305
+ exit 1
306
+ fi
307
+ }
308
+
309
+ # ─── Command: markdown ──────────────────────────────────────────────────────
310
+
311
+ cmd_markdown() {
312
+ local pipeline_badge
313
+ local tests_badge
314
+ local version_badge
315
+ local health_badge
316
+
317
+ pipeline_badge=$(badge_pipeline)
318
+ tests_badge=$(badge_tests)
319
+ version_badge=$(badge_version)
320
+ health_badge=$(badge_health)
321
+
322
+ cat <<EOF
323
+ <!-- Shipwright Status Widgets -->
324
+
325
+ ## Status Badges
326
+
327
+ [![Pipeline](${pipeline_badge})](./PIPELINE.md)
328
+ [![Tests](${tests_badge})](./TEST_RESULTS.md)
329
+ [![Version](${version_badge})](./CHANGELOG.md)
330
+ [![Health](${health_badge})](./HEALTH.md)
331
+
332
+ ### Pipeline Status
333
+ - **Current Status**: $(_get_pipeline_status)
334
+ - **Tests Passing**: $(_get_test_stats)+
335
+ - **Health Score**: $(_get_health_score)%
336
+ - **Last Updated**: $(now_iso)
337
+
338
+ ### Getting Started
339
+ To add these badges to your README.md:
340
+
341
+ \`\`\`markdown
342
+ [![Pipeline](${pipeline_badge})](./PIPELINE.md)
343
+ [![Tests](${tests_badge})](./TEST_RESULTS.md)
344
+ [![Version](${version_badge})](./CHANGELOG.md)
345
+ [![Health](${health_badge})](./HEALTH.md)
346
+ \`\`\`
347
+
348
+ ---
349
+ Generated by [Shipwright](https://github.com/sethdford/shipwright)
350
+ EOF
351
+ }
352
+
353
+ # ─── Command: json ──────────────────────────────────────────────────────────
354
+
355
+ cmd_json() {
356
+ local pipeline_status
357
+ local test_stats
358
+ local health_score
359
+ local version
360
+
361
+ pipeline_status=$(_get_pipeline_status)
362
+ test_stats=$(_get_test_stats)
363
+ health_score=$(_get_health_score)
364
+ version=$(_get_version)
365
+
366
+ # Extract last deploy time from events if available
367
+ local last_deploy="unknown"
368
+ if [[ -f "$EVENTS_FILE" ]]; then
369
+ last_deploy=$(grep -i "deploy.*completed" "$EVENTS_FILE" | tail -1 | jq -r '.ts // "unknown"' 2>/dev/null || echo "unknown")
370
+ fi
371
+
372
+ # Build JSON status
373
+ jq -n \
374
+ --arg timestamp "$(now_iso)" \
375
+ --arg status "$pipeline_status" \
376
+ --arg tests "$test_stats" \
377
+ --argjson health "$health_score" \
378
+ --arg version "$version" \
379
+ --arg last_deploy "$last_deploy" \
380
+ --arg pipeline_badge "$(badge_pipeline)" \
381
+ --arg tests_badge "$(badge_tests)" \
382
+ --arg version_badge "$(badge_version)" \
383
+ --arg health_badge "$(badge_health)" \
384
+ '{
385
+ timestamp: $timestamp,
386
+ pipeline: {
387
+ status: $status,
388
+ tests_passing: ($tests | tonumber),
389
+ health_score: $health,
390
+ last_deploy: $last_deploy
391
+ },
392
+ version: $version,
393
+ badges: {
394
+ pipeline: $pipeline_badge,
395
+ tests: $tests_badge,
396
+ version: $version_badge,
397
+ health: $health_badge
398
+ }
399
+ }'
400
+ }
401
+
402
+ # ─── Command: notify ──────────────────────────────────────────────────────────
403
+
404
+ cmd_notify() {
405
+ local notify_on="${1:-always}"
406
+ local status
407
+ status=$(_get_pipeline_status)
408
+
409
+ case "$notify_on" in
410
+ success)
411
+ if [[ "$status" == "passing" ]]; then
412
+ success "Pipeline is passing!"
413
+ fi
414
+ ;;
415
+ failure)
416
+ if [[ "$status" == "failing" ]]; then
417
+ error "Pipeline is failing!"
418
+ exit 1
419
+ fi
420
+ ;;
421
+ always)
422
+ info "Pipeline status: $status"
423
+ ;;
424
+ *)
425
+ error "Unknown notify type: $notify_on"
426
+ exit 1
427
+ ;;
428
+ esac
429
+ }
430
+
431
+ # ─── Command: help ───────────────────────────────────────────────────────────
432
+
433
+ cmd_help() {
434
+ cat <<EOF
435
+ ${CYAN}${BOLD}shipwright widgets${RESET} — Embeddable Status Widgets
436
+
437
+ ${BOLD}USAGE${RESET}
438
+ shipwright widgets <command> [options]
439
+
440
+ ${BOLD}COMMANDS${RESET}
441
+ ${CYAN}badge${RESET} [type] Generate shields.io badge URLs
442
+ Types: pipeline, tests, version, health, all
443
+ ${CYAN}slack${RESET} [options] Send pipeline status to Slack
444
+ --webhook URL Slack webhook URL
445
+ --channel #ch (optional) channel override
446
+ ${CYAN}markdown${RESET} Generate markdown status block for README
447
+ ${CYAN}json${RESET} Export current status as JSON
448
+ ${CYAN}notify${RESET} [type] Send notifications based on status
449
+ Types: success, failure, always
450
+ ${CYAN}help${RESET} Show this help message
451
+
452
+ ${BOLD}EXAMPLES${RESET}
453
+ # Generate pipeline badge URL
454
+ shipwright widgets badge pipeline
455
+
456
+ # Get all badges
457
+ shipwright widgets badge all
458
+
459
+ # Send Slack notification
460
+ shipwright widgets slack --webhook https://hooks.slack.com/... --channel #ops
461
+
462
+ # Generate markdown block for README
463
+ shipwright widgets markdown > STATUS.md
464
+
465
+ # Get JSON status for dashboards
466
+ shipwright widgets json | jq
467
+
468
+ # Notify if pipeline is passing
469
+ shipwright widgets notify success
470
+
471
+ ${BOLD}CONFIGURATION${RESET}
472
+ Slack webhooks can be stored in: ${CONFIG_FILE}
473
+
474
+ Example:
475
+ {
476
+ "slack": {
477
+ "webhook_url": "https://hooks.slack.com/services/..."
478
+ }
479
+ }
480
+
481
+ ${BOLD}INTEGRATION${RESET}
482
+ Use in CI/CD pipelines:
483
+ - GitHub Actions: Add badge URLs to job summaries
484
+ - README.md: Embed markdown status block
485
+ - External dashboards: Query JSON endpoint
486
+ - Slack: Post status updates on workflow completion
487
+
488
+ EOF
489
+ }
490
+
491
+ # ─── Main ──────────────────────────────────────────────────────────────────
492
+
493
+ main() {
494
+ local cmd="${1:-help}"
495
+ shift 2>/dev/null || true
496
+
497
+ case "$cmd" in
498
+ badge)
499
+ cmd_badge "$@"
500
+ ;;
501
+ slack)
502
+ cmd_slack "$@"
503
+ ;;
504
+ markdown)
505
+ cmd_markdown "$@"
506
+ ;;
507
+ json)
508
+ cmd_json "$@"
509
+ ;;
510
+ notify)
511
+ cmd_notify "$@"
512
+ ;;
513
+ help|--help|-h)
514
+ cmd_help
515
+ ;;
516
+ version|--version|-v)
517
+ echo "Shipwright widgets v${VERSION}"
518
+ ;;
519
+ *)
520
+ error "Unknown command: $cmd"
521
+ echo " Try: shipwright widgets help"
522
+ exit 1
523
+ ;;
524
+ esac
525
+ }
526
+
527
+ # ─── Guard: allow sourcing ────────────────────────────────────────────────────
528
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
529
+ main "$@"
530
+ fi
@@ -5,7 +5,7 @@
5
5
  # ║ Each agent gets its own worktree so parallel agents don't clobber ║
6
6
  # ║ each other's files. Worktrees live in .worktrees/ relative to root. ║
7
7
  # ╚═══════════════════════════════════════════════════════════════════════════╝
8
- VERSION="1.10.0"
8
+ VERSION="2.0.0"
9
9
  set -euo pipefail
10
10
  trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
11