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.
- package/README.md +114 -36
- package/completions/_shipwright +212 -32
- package/completions/shipwright.bash +97 -25
- 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/package.json +4 -2
- package/scripts/sw +208 -1
- 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 +664 -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 +637 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +432 -130
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +540 -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 +59 -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 +471 -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 +1 -1
- 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 +617 -0
- package/scripts/sw-init.sh +88 -1
- 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 +64 -3
- package/scripts/sw-memory.sh +1 -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 +689 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +687 -24
- package/scripts/sw-pm.sh +693 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +1 -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 +573 -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 +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +658 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +583 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +515 -0
- package/scripts/sw-tmux-pipeline.sh +554 -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 +603 -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
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright fleet-viz — Multi-Repo Fleet Visualization ║
|
|
4
|
+
# ║ Cross-repo insights, queue management, worker allocation, cost tracking ║
|
|
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
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
|
+
|
|
13
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
14
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
15
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
16
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
17
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
18
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
19
|
+
RED='\033[38;2;248;113;113m' # error
|
|
20
|
+
DIM='\033[2m'
|
|
21
|
+
BOLD='\033[1m'
|
|
22
|
+
RESET='\033[0m'
|
|
23
|
+
|
|
24
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
25
|
+
# shellcheck source=lib/compat.sh
|
|
26
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
27
|
+
|
|
28
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
29
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
30
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
31
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
32
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
33
|
+
|
|
34
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
35
|
+
now_epoch() { date +%s; }
|
|
36
|
+
|
|
37
|
+
format_duration() {
|
|
38
|
+
local secs="$1"
|
|
39
|
+
if [[ "$secs" -ge 3600 ]]; then
|
|
40
|
+
printf "%dh %dm %ds" $((secs/3600)) $((secs%3600/60)) $((secs%60))
|
|
41
|
+
elif [[ "$secs" -ge 60 ]]; then
|
|
42
|
+
printf "%dm %ds" $((secs/60)) $((secs%60))
|
|
43
|
+
else
|
|
44
|
+
printf "%ds" "$secs"
|
|
45
|
+
fi
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ─── Data Paths ────────────────────────────────────────────────────────────
|
|
49
|
+
FLEET_DIR="${HOME}/.shipwright"
|
|
50
|
+
FLEET_STATE="${FLEET_DIR}/fleet-state.json"
|
|
51
|
+
EVENTS_FILE="${FLEET_DIR}/events.jsonl"
|
|
52
|
+
COSTS_FILE="${FLEET_DIR}/costs.json"
|
|
53
|
+
MACHINES_FILE="${FLEET_DIR}/machines.json"
|
|
54
|
+
|
|
55
|
+
# ─── Health Status Helpers ─────────────────────────────────────────────────
|
|
56
|
+
get_health_status() {
|
|
57
|
+
local repo="$1"
|
|
58
|
+
# Calculate health from recent events/pipeline state
|
|
59
|
+
# healthy (green) = no recent failures
|
|
60
|
+
# degraded (yellow) = some failures but recovering
|
|
61
|
+
# failing (red) = persistent failures
|
|
62
|
+
|
|
63
|
+
if ! command -v jq &>/dev/null; then
|
|
64
|
+
echo "unknown"
|
|
65
|
+
return
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Check if any active jobs are stuck (queued >30min)
|
|
69
|
+
local stuck_count
|
|
70
|
+
stuck_count=$(jq -r "[.active_jobs[] | select(.repo==\"$repo\" and (.queued_at|todateiso8601|length) and (now - (.queued_at | fromdateiso8601) > 1800))] | length" "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
71
|
+
|
|
72
|
+
if [[ "$stuck_count" -gt 0 ]]; then
|
|
73
|
+
echo "failing"
|
|
74
|
+
else
|
|
75
|
+
echo "healthy"
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
color_health() {
|
|
80
|
+
local status="$1"
|
|
81
|
+
case "$status" in
|
|
82
|
+
healthy) echo "${GREEN}${status}${RESET}" ;;
|
|
83
|
+
degraded) echo "${YELLOW}${status}${RESET}" ;;
|
|
84
|
+
failing) echo "${RED}${status}${RESET}" ;;
|
|
85
|
+
*) echo "${DIM}${status}${RESET}" ;;
|
|
86
|
+
esac
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# ─── Overview Subcommand ───────────────────────────────────────────────────
|
|
90
|
+
show_overview() {
|
|
91
|
+
if ! command -v jq &>/dev/null; then
|
|
92
|
+
error "jq is required for fleet visualization"
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
[[ ! -f "$FLEET_STATE" ]] && {
|
|
97
|
+
warn "No fleet state found at $FLEET_STATE"
|
|
98
|
+
echo "Run 'shipwright fleet start' first"
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
echo ""
|
|
103
|
+
echo -e "${PURPLE}${BOLD}━━━ Fleet Overview ━━━${RESET}"
|
|
104
|
+
echo ""
|
|
105
|
+
|
|
106
|
+
# Extract metrics
|
|
107
|
+
local active_pipelines queued_agents repos
|
|
108
|
+
active_pipelines=$(jq '[.active_jobs[]? | select(.status=="running")] | length' "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
109
|
+
queued_agents=$(jq '[.active_jobs[]? | select(.status=="queued")] | length' "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
110
|
+
repos=$(jq '[.active_jobs[]? | .repo] | unique | length' "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
111
|
+
|
|
112
|
+
echo -e "${BOLD}Active:${RESET} ${CYAN}${active_pipelines}${RESET} pipelines | ${BOLD}Queued:${RESET} ${YELLOW}${queued_agents}${RESET} jobs | ${BOLD}Repos:${RESET} ${PURPLE}${repos}${RESET}"
|
|
113
|
+
echo ""
|
|
114
|
+
|
|
115
|
+
# Per-repo breakdown
|
|
116
|
+
if [[ "$(jq '.active_jobs | length' "$FLEET_STATE" 2>/dev/null || echo "0")" -gt 0 ]]; then
|
|
117
|
+
echo -e "${BOLD}Repos:${RESET}"
|
|
118
|
+
echo ""
|
|
119
|
+
|
|
120
|
+
jq -r '.active_jobs[]? | .repo' "$FLEET_STATE" 2>/dev/null | sort -u | while read -r repo; do
|
|
121
|
+
local repo_active repo_queued health
|
|
122
|
+
repo_active=$(jq "[.active_jobs[]? | select(.repo==\"$repo\" and .status==\"running\")] | length" "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
123
|
+
repo_queued=$(jq "[.active_jobs[]? | select(.repo==\"$repo\" and .status==\"queued\")] | length" "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
124
|
+
health=$(get_health_status "$repo")
|
|
125
|
+
|
|
126
|
+
echo -e " ${CYAN}$(basename "$repo")${RESET} ${repo_active} active ${repo_queued} queued [$(color_health "$health")]"
|
|
127
|
+
done
|
|
128
|
+
else
|
|
129
|
+
echo -e "${DIM}No active pipelines${RESET}"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
echo ""
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# ─── Workers Subcommand ────────────────────────────────────────────────────
|
|
136
|
+
show_workers() {
|
|
137
|
+
if ! command -v jq &>/dev/null; then
|
|
138
|
+
error "jq is required for fleet visualization"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
[[ ! -f "$FLEET_STATE" ]] && {
|
|
143
|
+
warn "No fleet state found at $FLEET_STATE"
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
[[ ! -f "$MACHINES_FILE" ]] && {
|
|
148
|
+
warn "No machines file found at $MACHINES_FILE"
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
echo ""
|
|
153
|
+
echo -e "${PURPLE}${BOLD}━━━ Worker Allocation ━━━${RESET}"
|
|
154
|
+
echo ""
|
|
155
|
+
|
|
156
|
+
# Get per-repo worker counts
|
|
157
|
+
local total_workers_allocated=0
|
|
158
|
+
jq -r '.active_jobs[]? | .repo' "$FLEET_STATE" 2>/dev/null | sort -u | while read -r repo; do
|
|
159
|
+
local worker_count job_count utilization
|
|
160
|
+
worker_count=$(jq "[.active_jobs[]? | select(.repo==\"$repo\")] | map(.worker_id) | unique | length" "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
161
|
+
job_count=$(jq "[.active_jobs[]? | select(.repo==\"$repo\")] | length" "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
162
|
+
|
|
163
|
+
if [[ "$worker_count" -gt 0 ]]; then
|
|
164
|
+
utilization=$((job_count * 100 / worker_count))
|
|
165
|
+
else
|
|
166
|
+
utilization=0
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
echo -e " ${CYAN}$(basename "$repo")${RESET} ${worker_count} workers ${job_count} jobs ${utilization}% util"
|
|
170
|
+
done
|
|
171
|
+
|
|
172
|
+
# Remote machines
|
|
173
|
+
echo ""
|
|
174
|
+
echo -e "${BOLD}Remote Machines:${RESET}"
|
|
175
|
+
echo ""
|
|
176
|
+
|
|
177
|
+
if jq '.machines[]?' "$MACHINES_FILE" 2>/dev/null | grep -q .; then
|
|
178
|
+
jq -r '.machines[]? | "\(.name) (\(.hostname)) — \(.status) — \(.active_jobs // 0) active"' "$MACHINES_FILE" 2>/dev/null | while read -r machine; do
|
|
179
|
+
echo -e " ${machine}"
|
|
180
|
+
done
|
|
181
|
+
else
|
|
182
|
+
echo -e " ${DIM}No remote machines configured${RESET}"
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
echo ""
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# ─── Insights Subcommand ───────────────────────────────────────────────────
|
|
189
|
+
show_insights() {
|
|
190
|
+
if ! command -v jq &>/dev/null; then
|
|
191
|
+
error "jq is required for fleet visualization"
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
[[ ! -f "$EVENTS_FILE" ]] && {
|
|
196
|
+
warn "No events found at $EVENTS_FILE"
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
echo ""
|
|
201
|
+
echo -e "${PURPLE}${BOLD}━━━ Fleet Insights ━━━${RESET}"
|
|
202
|
+
echo ""
|
|
203
|
+
|
|
204
|
+
# Fleet-wide success rate (last 30 days)
|
|
205
|
+
local total_pipelines successful_pipelines
|
|
206
|
+
total_pipelines=$(grep '"type":"pipeline_complete"' "$EVENTS_FILE" 2>/dev/null | tail -5000 | wc -l || echo "0")
|
|
207
|
+
successful_pipelines=$(grep '"type":"pipeline_complete".*"status":"success"' "$EVENTS_FILE" 2>/dev/null | tail -5000 | wc -l || echo "0")
|
|
208
|
+
|
|
209
|
+
local success_rate=0
|
|
210
|
+
if [[ "$total_pipelines" -gt 0 ]]; then
|
|
211
|
+
success_rate=$((successful_pipelines * 100 / total_pipelines))
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
echo -e "${BOLD}Success Rate:${RESET} ${success_rate}% (${successful_pipelines}/${total_pipelines})"
|
|
215
|
+
|
|
216
|
+
# Most expensive repo
|
|
217
|
+
if [[ -f "$COSTS_FILE" ]]; then
|
|
218
|
+
echo ""
|
|
219
|
+
echo -e "${BOLD}Cost Leaders:${RESET}"
|
|
220
|
+
jq -r '.entries[]? | select(.repo != null) | .repo as $repo | {repo: $repo, cost: .cost} | @csv' "$COSTS_FILE" 2>/dev/null | \
|
|
221
|
+
awk -F, '{r=$1; gsub(/"/, "", r); sum[r]+=$2} END {for (r in sum) print r, sum[r]}' | sort -k2 -nr | head -5 | while read -r repo cost; do
|
|
222
|
+
printf " ${CYAN}%s${RESET} — \$%.2f\n" "$repo" "$cost"
|
|
223
|
+
done
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
echo ""
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# ─── Queue Subcommand ──────────────────────────────────────────────────────
|
|
230
|
+
show_queue() {
|
|
231
|
+
if ! command -v jq &>/dev/null; then
|
|
232
|
+
error "jq is required for fleet visualization"
|
|
233
|
+
exit 1
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
[[ ! -f "$FLEET_STATE" ]] && {
|
|
237
|
+
warn "No fleet state found at $FLEET_STATE"
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
echo ""
|
|
242
|
+
echo -e "${PURPLE}${BOLD}━━━ Issue Queue ━━━${RESET}"
|
|
243
|
+
echo ""
|
|
244
|
+
|
|
245
|
+
local queued_count
|
|
246
|
+
queued_count=$(jq '[.active_jobs[]? | select(.status=="queued")] | length' "$FLEET_STATE" 2>/dev/null || echo "0")
|
|
247
|
+
|
|
248
|
+
if [[ "$queued_count" -eq 0 ]]; then
|
|
249
|
+
echo -e "${GREEN}✓${RESET} No queued issues"
|
|
250
|
+
echo ""
|
|
251
|
+
return
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
echo -e "${BOLD}${queued_count} Issues Queued:${RESET}"
|
|
255
|
+
echo ""
|
|
256
|
+
echo -e " ${BOLD}Repo${RESET} ${BOLD}Issue${RESET} ${BOLD}Priority${RESET} ${BOLD}Wait Time${RESET}"
|
|
257
|
+
echo -e " ${DIM}─────────────────────────────────────────────────${RESET}"
|
|
258
|
+
|
|
259
|
+
jq -r '.active_jobs[]? | select(.status=="queued") | "\(.repo) #\(.issue_number) \(.priority // "normal") \(.queued_for // "0s")"' "$FLEET_STATE" 2>/dev/null | while read -r repo issue priority wait; do
|
|
260
|
+
local priority_color
|
|
261
|
+
case "$priority" in
|
|
262
|
+
urgent|hotfix) priority_color="${RED}${priority}${RESET}" ;;
|
|
263
|
+
high) priority_color="${YELLOW}${priority}${RESET}" ;;
|
|
264
|
+
*) priority_color="${DIM}${priority}${RESET}" ;;
|
|
265
|
+
esac
|
|
266
|
+
printf " %-20s %-8s %-10b %-12s\n" "$(basename "$repo")" "$issue" "$priority_color" "$wait"
|
|
267
|
+
done
|
|
268
|
+
|
|
269
|
+
echo ""
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# ─── Costs Subcommand ──────────────────────────────────────────────────────
|
|
273
|
+
show_costs() {
|
|
274
|
+
if ! command -v jq &>/dev/null; then
|
|
275
|
+
error "jq is required for fleet visualization"
|
|
276
|
+
exit 1
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
[[ ! -f "$COSTS_FILE" ]] && {
|
|
280
|
+
warn "No cost data found at $COSTS_FILE"
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
echo ""
|
|
285
|
+
echo -e "${PURPLE}${BOLD}━━━ Fleet Costs ━━━${RESET}"
|
|
286
|
+
echo ""
|
|
287
|
+
|
|
288
|
+
# Total spend
|
|
289
|
+
local total_spend
|
|
290
|
+
total_spend=$(jq '[.entries[]? | .cost // 0] | add // 0' "$COSTS_FILE" 2>/dev/null || echo "0")
|
|
291
|
+
printf "${BOLD}Total Spend:${RESET} \$%.2f\n" "$total_spend"
|
|
292
|
+
|
|
293
|
+
# Per-repo breakdown
|
|
294
|
+
echo ""
|
|
295
|
+
echo -e "${BOLD}Per-Repo:${RESET}"
|
|
296
|
+
jq -r '.entries[]? | select(.repo != null) | "\(.repo) \(.cost // 0) \(.model // "unknown")"' "$COSTS_FILE" 2>/dev/null | \
|
|
297
|
+
awk '{repo=$1; split(repo,a,"/"); r=a[length(a)]; cost=$2; model=$3; sum[r]+=cost} END {for (r in sum) print r, sum[r]}' | \
|
|
298
|
+
sort -k2 -nr | while read -r repo cost; do
|
|
299
|
+
printf " %-20s \$%.2f\n" "$repo" "$cost"
|
|
300
|
+
done
|
|
301
|
+
|
|
302
|
+
# Per-model breakdown
|
|
303
|
+
echo ""
|
|
304
|
+
echo -e "${BOLD}Per-Model:${RESET}"
|
|
305
|
+
jq -r '.entries[]? | .model // "unknown"' "$COSTS_FILE" 2>/dev/null | sort | uniq -c | sort -rn | while read -r count model; do
|
|
306
|
+
local model_cost
|
|
307
|
+
model_cost=$(jq "[.entries[]? | select(.model==\"$model\") | .cost // 0] | add // 0" "$COSTS_FILE" 2>/dev/null || echo "0")
|
|
308
|
+
printf " %-20s %d uses \$%.2f\n" "$model" "$count" "$model_cost"
|
|
309
|
+
done
|
|
310
|
+
|
|
311
|
+
echo ""
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# ─── Export Subcommand ─────────────────────────────────────────────────────
|
|
315
|
+
show_export() {
|
|
316
|
+
if ! command -v jq &>/dev/null; then
|
|
317
|
+
error "jq is required for fleet visualization"
|
|
318
|
+
exit 1
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
[[ ! -f "$FLEET_STATE" ]] && {
|
|
322
|
+
error "No fleet state found at $FLEET_STATE"
|
|
323
|
+
exit 1
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# Merge all available data into single JSON export
|
|
327
|
+
local export_json
|
|
328
|
+
export_json=$(cat "$FLEET_STATE" 2>/dev/null || echo '{}')
|
|
329
|
+
|
|
330
|
+
if [[ -f "$COSTS_FILE" ]]; then
|
|
331
|
+
export_json=$(echo "$export_json" | jq --slurpfile costs "$COSTS_FILE" '.costs = $costs[0]')
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
if [[ -f "$MACHINES_FILE" ]]; then
|
|
335
|
+
export_json=$(echo "$export_json" | jq --slurpfile machines "$MACHINES_FILE" '.machines = $machines[0]')
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
echo "$export_json" | jq .
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
# ─── Help ──────────────────────────────────────────────────────────────────
|
|
342
|
+
show_help() {
|
|
343
|
+
echo ""
|
|
344
|
+
echo -e "${PURPLE}${BOLD}━━━ shipwright fleet-viz v${VERSION} ━━━${RESET}"
|
|
345
|
+
echo ""
|
|
346
|
+
echo -e "${BOLD}USAGE${RESET}"
|
|
347
|
+
echo -e " ${CYAN}shipwright fleet-viz${RESET} <command> [options]"
|
|
348
|
+
echo ""
|
|
349
|
+
echo -e "${BOLD}COMMANDS${RESET}"
|
|
350
|
+
echo -e " ${CYAN}overview${RESET} Show fleet-wide status (pipelines, queues, repos)"
|
|
351
|
+
echo -e " ${CYAN}workers${RESET} Show worker allocation and remote machines"
|
|
352
|
+
echo -e " ${CYAN}insights${RESET} Show cross-repo metrics and trends"
|
|
353
|
+
echo -e " ${CYAN}queue${RESET} Show combined queue across all repos"
|
|
354
|
+
echo -e " ${CYAN}costs${RESET} Show fleet-wide cost breakdown"
|
|
355
|
+
echo -e " ${CYAN}export${RESET} Export fleet state as JSON"
|
|
356
|
+
echo -e " ${CYAN}help${RESET} Show this help message"
|
|
357
|
+
echo ""
|
|
358
|
+
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
359
|
+
echo -e " ${DIM}shipwright fleet-viz overview${RESET} # Fleet dashboard"
|
|
360
|
+
echo -e " ${DIM}shipwright fleet-viz workers${RESET} # Worker status"
|
|
361
|
+
echo -e " ${DIM}shipwright fleet-viz queue${RESET} # Show issue queue"
|
|
362
|
+
echo -e " ${DIM}shipwright fleet-viz export | jq .${RESET} # Export as JSON"
|
|
363
|
+
echo ""
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
# ─── Main Router ───────────────────────────────────────────────────────────
|
|
367
|
+
main() {
|
|
368
|
+
local cmd="${1:-overview}"
|
|
369
|
+
shift 2>/dev/null || true
|
|
370
|
+
|
|
371
|
+
case "$cmd" in
|
|
372
|
+
overview)
|
|
373
|
+
show_overview
|
|
374
|
+
;;
|
|
375
|
+
workers)
|
|
376
|
+
show_workers
|
|
377
|
+
;;
|
|
378
|
+
insights)
|
|
379
|
+
show_insights
|
|
380
|
+
;;
|
|
381
|
+
queue)
|
|
382
|
+
show_queue
|
|
383
|
+
;;
|
|
384
|
+
costs)
|
|
385
|
+
show_costs
|
|
386
|
+
;;
|
|
387
|
+
export)
|
|
388
|
+
show_export
|
|
389
|
+
;;
|
|
390
|
+
help|--help|-h)
|
|
391
|
+
show_help
|
|
392
|
+
;;
|
|
393
|
+
*)
|
|
394
|
+
error "Unknown command: $cmd"
|
|
395
|
+
echo ""
|
|
396
|
+
show_help
|
|
397
|
+
exit 1
|
|
398
|
+
;;
|
|
399
|
+
esac
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
403
|
+
main "$@"
|
|
404
|
+
fi
|
package/scripts/sw-fleet.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
8
|
|
|
9
|
-
VERSION="
|
|
9
|
+
VERSION="2.0.0"
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
12
|
|
|
@@ -130,6 +130,7 @@ show_help() {
|
|
|
130
130
|
echo -e " ${CYAN}stop${RESET} Stop all fleet daemons"
|
|
131
131
|
echo -e " ${CYAN}status${RESET} Show fleet dashboard"
|
|
132
132
|
echo -e " ${CYAN}metrics${RESET} [--period N] [--json] Aggregate DORA metrics across repos"
|
|
133
|
+
echo -e " ${CYAN}discover${RESET} --org <name> [options] Auto-discover repos from GitHub org"
|
|
133
134
|
echo -e " ${CYAN}init${RESET} Generate fleet-config.json"
|
|
134
135
|
echo -e " ${CYAN}help${RESET} Show this help"
|
|
135
136
|
echo ""
|
|
@@ -138,6 +139,7 @@ show_help() {
|
|
|
138
139
|
echo ""
|
|
139
140
|
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
140
141
|
echo -e " ${DIM}shipwright fleet init${RESET} # Generate config"
|
|
142
|
+
echo -e " ${DIM}shipwright fleet discover --org myorg${RESET} # Auto-discover repos"
|
|
141
143
|
echo -e " ${DIM}shipwright fleet start${RESET} # Start all daemons"
|
|
142
144
|
echo -e " ${DIM}shipwright fleet start --config my-fleet.json${RESET} # Custom config"
|
|
143
145
|
echo -e " ${DIM}shipwright fleet status${RESET} # Fleet dashboard"
|
|
@@ -1365,6 +1367,11 @@ case "$SUBCOMMAND" in
|
|
|
1365
1367
|
metrics)
|
|
1366
1368
|
fleet_metrics
|
|
1367
1369
|
;;
|
|
1370
|
+
discover)
|
|
1371
|
+
# Delegate to fleet-discover script
|
|
1372
|
+
shift 2>/dev/null || true
|
|
1373
|
+
exec "$SCRIPT_DIR/sw-fleet-discover.sh" "$@"
|
|
1374
|
+
;;
|
|
1368
1375
|
init)
|
|
1369
1376
|
fleet_init
|
|
1370
1377
|
;;
|