shipwright-cli 1.10.0 → 2.1.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.
- package/README.md +221 -55
- package/completions/_shipwright +264 -32
- package/completions/shipwright.bash +118 -26
- package/completions/shipwright.fish +80 -2
- package/dashboard/server.ts +208 -0
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
- package/docs/tmux-research/TMUX-AUDIT.md +925 -0
- package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
- package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
- package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
- package/package.json +4 -2
- package/scripts/lib/helpers.sh +7 -0
- package/scripts/sw +323 -2
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +754 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +698 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +44 -3
- package/scripts/sw-daemon.sh +568 -138
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1380 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +107 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +479 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +4 -4
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +656 -0
- package/scripts/sw-init.sh +237 -24
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +267 -21
- package/scripts/sw-memory.sh +18 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +764 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +947 -35
- package/scripts/sw-pm.sh +758 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +8 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +2248 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +263 -127
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +44 -2
- package/scripts/sw-strategic.sh +806 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +620 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +4 -4
- package/scripts/sw-testgen.sh +566 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux-role-color.sh +58 -0
- package/scripts/sw-tmux-status.sh +128 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +627 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- package/scripts/sw-worktree.sh +1 -1
- package/templates/pipelines/autonomous.json +2 -2
- package/tmux/shipwright-overlay.conf +35 -17
- package/tmux/tmux.conf +23 -21
|
@@ -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.1.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.md)
|
|
328
|
+
[](./TEST_RESULTS.md)
|
|
329
|
+
[](./CHANGELOG.md)
|
|
330
|
+
[](./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.md)
|
|
343
|
+
[](./TEST_RESULTS.md)
|
|
344
|
+
[](./CHANGELOG.md)
|
|
345
|
+
[](./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
|
package/scripts/sw-worktree.sh
CHANGED
|
@@ -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.
|
|
8
|
+
VERSION="2.1.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|
|
@@ -27,9 +27,10 @@
|
|
|
27
27
|
set -g pane-border-status top
|
|
28
28
|
|
|
29
29
|
# Format: pane index, agent name, running command, zoom indicator
|
|
30
|
-
# Active pane
|
|
30
|
+
# Active pane: bold + bright │ Inactive: muted, recessed
|
|
31
31
|
# Uses #{pane_title} which each agent sets via OSC 2 escape sequence
|
|
32
|
-
|
|
32
|
+
# The ● dot gives a visual "alive" indicator on the active pane
|
|
33
|
+
set -g pane-border-format "#{?pane_active,#[fg=#00d4ff]#[bold] ● #P │ #{pane_title} #[fg=#52525b]│#[fg=#a1a1aa] #{pane_current_command}#{?window_zoomed_flag, #[fg=#facc15]#[bold]⊞ FOCUS,},#[fg=#52525b] #P │ #[fg=#71717a]#{pane_title} #[fg=#333355]│ #[fg=#52525b]#{pane_current_command}}"
|
|
33
34
|
|
|
34
35
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
35
36
|
# LAYOUT — Optimize for multi-agent pane layouts
|
|
@@ -40,19 +41,35 @@ set -g pane-border-format "#{?pane_active,#[fg=#00d4ff]#[bold],#[fg=#71717a]} #P
|
|
|
40
41
|
setw -g aggressive-resize on
|
|
41
42
|
|
|
42
43
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
-
# DARK THEME
|
|
44
|
+
# DARK THEME — Active pane gets a subtle background lift
|
|
44
45
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
# Inactive panes: deep navy, dimmed text — recedes visually
|
|
47
|
+
# Active pane: subtle bg lift, softer warm gray text — easy on the eyes
|
|
48
|
+
# NOTE: Claude Code renders its own colors for TUI elements. These fg values
|
|
49
|
+
# only affect plain terminal output (shell prompts, command output, etc.)
|
|
50
|
+
# The bg lift (#1a1a2e → #1e1e36) gives the active pane a "floating" feel.
|
|
51
|
+
set -g window-style 'bg=#1a1a2e,fg=#8888a0'
|
|
52
|
+
set -g window-active-style 'bg=#1e1e36,fg=#b4b4c8'
|
|
49
53
|
|
|
50
|
-
# Hooks
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
set-hook -g after-
|
|
54
|
-
set-hook -g after-new-
|
|
55
|
-
|
|
54
|
+
# Hooks: set pane colors on creation to prevent white flash.
|
|
55
|
+
# New panes start with inactive colors; active pane lift applies automatically.
|
|
56
|
+
set-hook -g after-split-window "select-pane -P 'bg=#1a1a2e,fg=#8888a0'"
|
|
57
|
+
set-hook -g after-new-window "select-pane -P 'bg=#1a1a2e,fg=#8888a0'"
|
|
58
|
+
set-hook -g after-new-session "select-pane -P 'bg=#1a1a2e,fg=#8888a0'"
|
|
59
|
+
|
|
60
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
61
|
+
# ROLE-COLORED BORDERS — Active pane border reflects agent role
|
|
62
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
63
|
+
# When you switch panes, a hook reads the pane title and sets the active
|
|
64
|
+
# border color to match the agent's role:
|
|
65
|
+
# leader/pm → cyan (#00d4ff) builder/dev → blue (#0066ff)
|
|
66
|
+
# reviewer → orange (#f97316) tester → yellow (#facc15)
|
|
67
|
+
# security → red (#ef4444) docs → violet (#a78bfa)
|
|
68
|
+
# optimizer → green (#4ade80) researcher → purple (#7c3aed)
|
|
69
|
+
#
|
|
70
|
+
# The hook script must exist; -q makes source-file silent if missing.
|
|
71
|
+
# Falls back to cyan for non-agent panes (plain shell, vim, etc.)
|
|
72
|
+
set-hook -g pane-focus-in "run-shell -b 'bash ~/.shipwright/scripts/sw-tmux-role-color.sh 2>/dev/null || true'"
|
|
56
73
|
|
|
57
74
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
58
75
|
# SAFETY — Confirm before killing agent panes
|
|
@@ -88,13 +105,14 @@ bind M-t setw synchronize-panes \; display-message "Team sync #{?synchronize-pan
|
|
|
88
105
|
bind M-l next-layout
|
|
89
106
|
|
|
90
107
|
# prefix + M-s → capture current pane's full scrollback to file
|
|
91
|
-
bind M-s run-shell "tmux capture-pane -pS - > /tmp/claude-pane-#{pane_title}
|
|
108
|
+
bind M-s run-shell "tmux capture-pane -pS - > /tmp/claude-pane-#{pane_title}-\$(date +%%s).txt && tmux display-message 'Captured #{pane_title} to /tmp/'"
|
|
92
109
|
|
|
93
110
|
# prefix + M-a → capture ALL panes in current window (full scrollback each)
|
|
111
|
+
# NOTE: All $() must be escaped as \$() so they run at keypress time, not config load
|
|
94
112
|
bind M-a run-shell "\
|
|
95
|
-
for pane_id in
|
|
96
|
-
title
|
|
97
|
-
tmux capture-pane -t \$pane_id -pS - > /tmp/claude-pane-\${title:-\$pane_id}
|
|
113
|
+
for pane_id in \$(tmux list-panes -F '#{pane_id}'); do \
|
|
114
|
+
title=\$(tmux display-message -t \$pane_id -p '#{pane_title}'); \
|
|
115
|
+
tmux capture-pane -t \$pane_id -pS - > /tmp/claude-pane-\${title:-\$pane_id}-\$(date +%%s).txt; \
|
|
98
116
|
done && tmux display-message 'Captured all panes to /tmp/'"
|
|
99
117
|
|
|
100
118
|
# ═══════════════════════════════════════════════════════════════════════════
|