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,554 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright tmux-pipeline — Spawn and manage pipelines in tmux windows ║
|
|
4
|
+
# ║ Native tmux integration for pipeline visibility and control ║
|
|
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
|
+
|
|
30
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
31
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
32
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
33
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
34
|
+
|
|
35
|
+
emit_event() {
|
|
36
|
+
local event_type="$1"; shift
|
|
37
|
+
local events_file="${HOME}/.shipwright/events.jsonl"
|
|
38
|
+
mkdir -p "$(dirname "$events_file")"
|
|
39
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
40
|
+
while [[ $# -gt 0 ]]; do
|
|
41
|
+
local key="${1%%=*}" val="${1#*=}"
|
|
42
|
+
payload="${payload},\"${key}\":\"${val}\""
|
|
43
|
+
shift
|
|
44
|
+
done
|
|
45
|
+
payload="${payload}}"
|
|
46
|
+
echo "$payload" >> "$events_file"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Get daemon session name
|
|
50
|
+
get_daemon_session() {
|
|
51
|
+
echo "sw-daemon"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Get pipeline window name from issue number
|
|
55
|
+
get_window_name() {
|
|
56
|
+
local issue_num="$1"
|
|
57
|
+
echo "pipeline-${issue_num}"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# ─── Spawn subcommand ──────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
cmd_spawn() {
|
|
63
|
+
local issue_num=""
|
|
64
|
+
local daemon_session
|
|
65
|
+
daemon_session="$(get_daemon_session)"
|
|
66
|
+
|
|
67
|
+
# Parse arguments
|
|
68
|
+
while [[ $# -gt 0 ]]; do
|
|
69
|
+
case "$1" in
|
|
70
|
+
--issue)
|
|
71
|
+
issue_num="$2"
|
|
72
|
+
shift 2
|
|
73
|
+
;;
|
|
74
|
+
*)
|
|
75
|
+
error "Unknown option: $1"
|
|
76
|
+
return 1
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
79
|
+
done
|
|
80
|
+
|
|
81
|
+
if [[ -z "$issue_num" ]]; then
|
|
82
|
+
error "Issue number required: --issue <number>"
|
|
83
|
+
return 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Check if daemon session exists
|
|
87
|
+
if ! tmux has-session -t "$daemon_session" 2>/dev/null; then
|
|
88
|
+
error "Daemon session not running: $daemon_session"
|
|
89
|
+
echo " Start with: ${DIM}shipwright daemon start --detach${RESET}"
|
|
90
|
+
return 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
local window_name
|
|
94
|
+
window_name="$(get_window_name "$issue_num")"
|
|
95
|
+
|
|
96
|
+
# Check if window already exists
|
|
97
|
+
if tmux list-windows -t "$daemon_session" 2>/dev/null | grep -q "^[0-9]*: $window_name"; then
|
|
98
|
+
warn "Pipeline window already exists: $window_name"
|
|
99
|
+
return 1
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Create new window in daemon session
|
|
103
|
+
info "Creating pipeline window: ${CYAN}${window_name}${RESET}"
|
|
104
|
+
local pane_id
|
|
105
|
+
pane_id=$(tmux new-window -t "$daemon_session" -n "$window_name" -P -F "#{pane_id}")
|
|
106
|
+
|
|
107
|
+
if [[ -z "$pane_id" ]]; then
|
|
108
|
+
error "Failed to create tmux window"
|
|
109
|
+
return 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Send pipeline command to pane
|
|
113
|
+
local pipeline_cmd="cd '$REPO_DIR' && env -u CLAUDECODE '$SCRIPT_DIR/sw-pipeline.sh' start --issue $issue_num"
|
|
114
|
+
tmux send-keys -t "$pane_id" "$pipeline_cmd" Enter
|
|
115
|
+
|
|
116
|
+
# Store pane ID in heartbeat
|
|
117
|
+
local heartbeat_file
|
|
118
|
+
heartbeat_file="${HOME}/.shipwright/heartbeats/pipeline-${issue_num}.json"
|
|
119
|
+
mkdir -p "$(dirname "$heartbeat_file")"
|
|
120
|
+
|
|
121
|
+
local tmp_file
|
|
122
|
+
tmp_file=$(mktemp)
|
|
123
|
+
cat > "$tmp_file" << EOF
|
|
124
|
+
{
|
|
125
|
+
"job_id": "pipeline-${issue_num}",
|
|
126
|
+
"type": "pipeline",
|
|
127
|
+
"issue": "$issue_num",
|
|
128
|
+
"pane_id": "$pane_id",
|
|
129
|
+
"window": "$window_name",
|
|
130
|
+
"started_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
131
|
+
"status": "running"
|
|
132
|
+
}
|
|
133
|
+
EOF
|
|
134
|
+
mv "$tmp_file" "$heartbeat_file"
|
|
135
|
+
|
|
136
|
+
success "Pipeline spawned in window: ${CYAN}${window_name}${RESET}"
|
|
137
|
+
echo -e " Pane ID: ${DIM}${pane_id}${RESET}"
|
|
138
|
+
echo -e " Attach: ${DIM}tmux attach-session -t $daemon_session -c $window_name${RESET}"
|
|
139
|
+
|
|
140
|
+
emit_event "pipeline_spawn" "issue=$issue_num" "pane_id=$pane_id" "window=$window_name"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# ─── Attach subcommand ────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
cmd_attach() {
|
|
146
|
+
local issue_num=""
|
|
147
|
+
local daemon_session
|
|
148
|
+
daemon_session="$(get_daemon_session)"
|
|
149
|
+
|
|
150
|
+
# Parse arguments
|
|
151
|
+
while [[ $# -gt 0 ]]; do
|
|
152
|
+
case "$1" in
|
|
153
|
+
--issue)
|
|
154
|
+
issue_num="$2"
|
|
155
|
+
shift 2
|
|
156
|
+
;;
|
|
157
|
+
*)
|
|
158
|
+
issue_num="$1"
|
|
159
|
+
shift
|
|
160
|
+
;;
|
|
161
|
+
esac
|
|
162
|
+
done
|
|
163
|
+
|
|
164
|
+
if [[ -z "$issue_num" ]]; then
|
|
165
|
+
error "Issue number required"
|
|
166
|
+
return 1
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Check if daemon session exists
|
|
170
|
+
if ! tmux has-session -t "$daemon_session" 2>/dev/null; then
|
|
171
|
+
error "Daemon session not running: $daemon_session"
|
|
172
|
+
return 1
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
local window_name
|
|
176
|
+
window_name="$(get_window_name "$issue_num")"
|
|
177
|
+
|
|
178
|
+
# Check if window exists
|
|
179
|
+
if ! tmux list-windows -t "$daemon_session" 2>/dev/null | grep -q "^[0-9]*: $window_name"; then
|
|
180
|
+
error "Pipeline window not found: $window_name"
|
|
181
|
+
return 1
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
info "Attaching to: ${CYAN}${window_name}${RESET}"
|
|
185
|
+
tmux select-window -t "$daemon_session:$window_name"
|
|
186
|
+
tmux attach-session -t "$daemon_session"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# ─── Capture subcommand ───────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
cmd_capture() {
|
|
192
|
+
local issue_num=""
|
|
193
|
+
local daemon_session
|
|
194
|
+
daemon_session="$(get_daemon_session)"
|
|
195
|
+
|
|
196
|
+
# Parse arguments
|
|
197
|
+
while [[ $# -gt 0 ]]; do
|
|
198
|
+
case "$1" in
|
|
199
|
+
--issue)
|
|
200
|
+
issue_num="$2"
|
|
201
|
+
shift 2
|
|
202
|
+
;;
|
|
203
|
+
*)
|
|
204
|
+
issue_num="$1"
|
|
205
|
+
shift
|
|
206
|
+
;;
|
|
207
|
+
esac
|
|
208
|
+
done
|
|
209
|
+
|
|
210
|
+
if [[ -z "$issue_num" ]]; then
|
|
211
|
+
error "Issue number required"
|
|
212
|
+
return 1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
local window_name
|
|
216
|
+
window_name="$(get_window_name "$issue_num")"
|
|
217
|
+
|
|
218
|
+
# Check if window exists
|
|
219
|
+
if ! tmux list-windows -t "$daemon_session" 2>/dev/null | grep -q "^[0-9]*: $window_name"; then
|
|
220
|
+
error "Pipeline window not found: $window_name"
|
|
221
|
+
return 1
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# Get pane ID from window
|
|
225
|
+
local pane_id
|
|
226
|
+
pane_id=$(tmux list-panes -t "$daemon_session:$window_name" -F "#{pane_id}" | head -1)
|
|
227
|
+
|
|
228
|
+
if [[ -z "$pane_id" ]]; then
|
|
229
|
+
error "Failed to get pane ID for window: $window_name"
|
|
230
|
+
return 1
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
# Capture pane output
|
|
234
|
+
info "Capturing output from: ${CYAN}${window_name}${RESET}"
|
|
235
|
+
tmux capture-pane -t "$pane_id" -p
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# ─── Stream subcommand ────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
cmd_stream() {
|
|
241
|
+
local issue_num=""
|
|
242
|
+
local daemon_session
|
|
243
|
+
daemon_session="$(get_daemon_session)"
|
|
244
|
+
|
|
245
|
+
# Parse arguments
|
|
246
|
+
while [[ $# -gt 0 ]]; do
|
|
247
|
+
case "$1" in
|
|
248
|
+
--issue)
|
|
249
|
+
issue_num="$2"
|
|
250
|
+
shift 2
|
|
251
|
+
;;
|
|
252
|
+
*)
|
|
253
|
+
issue_num="$1"
|
|
254
|
+
shift
|
|
255
|
+
;;
|
|
256
|
+
esac
|
|
257
|
+
done
|
|
258
|
+
|
|
259
|
+
if [[ -z "$issue_num" ]]; then
|
|
260
|
+
error "Issue number required"
|
|
261
|
+
return 1
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
local window_name
|
|
265
|
+
window_name="$(get_window_name "$issue_num")"
|
|
266
|
+
|
|
267
|
+
# Check if window exists
|
|
268
|
+
if ! tmux list-windows -t "$daemon_session" 2>/dev/null | grep -q "^[0-9]*: $window_name"; then
|
|
269
|
+
error "Pipeline window not found: $window_name"
|
|
270
|
+
return 1
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
# Get pane ID from window
|
|
274
|
+
local pane_id
|
|
275
|
+
pane_id=$(tmux list-panes -t "$daemon_session:$window_name" -F "#{pane_id}" | head -1)
|
|
276
|
+
|
|
277
|
+
if [[ -z "$pane_id" ]]; then
|
|
278
|
+
error "Failed to get pane ID for window: $window_name"
|
|
279
|
+
return 1
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
info "Streaming output from: ${CYAN}${window_name}${RESET}"
|
|
283
|
+
info "Press Ctrl-C to stop streaming"
|
|
284
|
+
echo ""
|
|
285
|
+
|
|
286
|
+
# Stream with continuous capture
|
|
287
|
+
while true; do
|
|
288
|
+
tmux capture-pane -t "$pane_id" -p -S -100
|
|
289
|
+
sleep 1
|
|
290
|
+
# Clear previous output
|
|
291
|
+
printf '\033[2J\033[H'
|
|
292
|
+
done
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# ─── List subcommand ──────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
cmd_list() {
|
|
298
|
+
local daemon_session
|
|
299
|
+
daemon_session="$(get_daemon_session)"
|
|
300
|
+
|
|
301
|
+
# Check if daemon session exists
|
|
302
|
+
if ! tmux has-session -t "$daemon_session" 2>/dev/null; then
|
|
303
|
+
warn "Daemon session not running: $daemon_session"
|
|
304
|
+
return
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
info "Pipeline windows in: ${CYAN}${daemon_session}${RESET}"
|
|
308
|
+
echo ""
|
|
309
|
+
|
|
310
|
+
local has_pipelines=false
|
|
311
|
+
while IFS= read -r line; do
|
|
312
|
+
[[ -z "$line" ]] && continue
|
|
313
|
+
|
|
314
|
+
# Parse window line: "0: pipeline-42 (1 panes)"
|
|
315
|
+
local window_num window_name status
|
|
316
|
+
window_num=$(echo "$line" | cut -d: -f1)
|
|
317
|
+
window_name=$(echo "$line" | cut -d' ' -f2)
|
|
318
|
+
|
|
319
|
+
# Check if it's a pipeline window
|
|
320
|
+
if [[ "$window_name" =~ ^pipeline- ]]; then
|
|
321
|
+
has_pipelines=true
|
|
322
|
+
|
|
323
|
+
# Extract issue number
|
|
324
|
+
local issue_num="${window_name#pipeline-}"
|
|
325
|
+
|
|
326
|
+
# Get pane info
|
|
327
|
+
local pane_id
|
|
328
|
+
pane_id=$(tmux list-panes -t "$daemon_session:$window_num" -F "#{pane_id}" | head -1)
|
|
329
|
+
|
|
330
|
+
# Get pane status
|
|
331
|
+
local pane_status
|
|
332
|
+
pane_status=$(tmux list-panes -t "$daemon_session:$window_num" -F "#{pane_title}" | head -1)
|
|
333
|
+
[[ -z "$pane_status" ]] && pane_status="running"
|
|
334
|
+
|
|
335
|
+
echo -e " ${CYAN}#${issue_num}${RESET} ${DIM}(window ${window_num})${RESET} — ${pane_status}"
|
|
336
|
+
echo -e " ${DIM}pane: ${pane_id}${RESET}"
|
|
337
|
+
fi
|
|
338
|
+
done < <(tmux list-windows -t "$daemon_session" 2>/dev/null)
|
|
339
|
+
|
|
340
|
+
if ! $has_pipelines; then
|
|
341
|
+
warn "No pipeline windows found"
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
echo ""
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# ─── Kill subcommand ──────────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
cmd_kill() {
|
|
350
|
+
local issue_num=""
|
|
351
|
+
local daemon_session
|
|
352
|
+
daemon_session="$(get_daemon_session)"
|
|
353
|
+
|
|
354
|
+
# Parse arguments
|
|
355
|
+
while [[ $# -gt 0 ]]; do
|
|
356
|
+
case "$1" in
|
|
357
|
+
--issue)
|
|
358
|
+
issue_num="$2"
|
|
359
|
+
shift 2
|
|
360
|
+
;;
|
|
361
|
+
*)
|
|
362
|
+
issue_num="$1"
|
|
363
|
+
shift
|
|
364
|
+
;;
|
|
365
|
+
esac
|
|
366
|
+
done
|
|
367
|
+
|
|
368
|
+
if [[ -z "$issue_num" ]]; then
|
|
369
|
+
error "Issue number required"
|
|
370
|
+
return 1
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
local window_name
|
|
374
|
+
window_name="$(get_window_name "$issue_num")"
|
|
375
|
+
|
|
376
|
+
# Check if window exists
|
|
377
|
+
if ! tmux list-windows -t "$daemon_session" 2>/dev/null | grep -q "^[0-9]*: $window_name"; then
|
|
378
|
+
warn "Pipeline window not found: $window_name"
|
|
379
|
+
return
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
# Get window number
|
|
383
|
+
local window_num
|
|
384
|
+
window_num=$(tmux list-windows -t "$daemon_session" 2>/dev/null | grep "^[0-9]*: $window_name" | cut -d: -f1)
|
|
385
|
+
|
|
386
|
+
info "Killing pipeline window: ${CYAN}${window_name}${RESET}"
|
|
387
|
+
tmux kill-window -t "$daemon_session:$window_num"
|
|
388
|
+
|
|
389
|
+
# Clean up heartbeat
|
|
390
|
+
local heartbeat_file
|
|
391
|
+
heartbeat_file="${HOME}/.shipwright/heartbeats/pipeline-${issue_num}.json"
|
|
392
|
+
rm -f "$heartbeat_file"
|
|
393
|
+
|
|
394
|
+
success "Pipeline killed"
|
|
395
|
+
emit_event "pipeline_kill" "issue=$issue_num" "window=$window_name"
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
# ─── Layout subcommand ────────────────────────────────────────────────────
|
|
399
|
+
|
|
400
|
+
cmd_layout() {
|
|
401
|
+
local layout="${1:-tiled}"
|
|
402
|
+
local daemon_session
|
|
403
|
+
daemon_session="$(get_daemon_session)"
|
|
404
|
+
|
|
405
|
+
# Check if daemon session exists
|
|
406
|
+
if ! tmux has-session -t "$daemon_session" 2>/dev/null; then
|
|
407
|
+
error "Daemon session not running: $daemon_session"
|
|
408
|
+
return 1
|
|
409
|
+
fi
|
|
410
|
+
|
|
411
|
+
info "Applying layout: ${CYAN}${layout}${RESET}"
|
|
412
|
+
|
|
413
|
+
case "$layout" in
|
|
414
|
+
tiled|tile)
|
|
415
|
+
tmux select-layout -t "$daemon_session" tiled
|
|
416
|
+
success "Layout applied: tiled"
|
|
417
|
+
;;
|
|
418
|
+
even-horizontal|horizontal|h)
|
|
419
|
+
tmux select-layout -t "$daemon_session" even-horizontal
|
|
420
|
+
success "Layout applied: even-horizontal"
|
|
421
|
+
;;
|
|
422
|
+
even-vertical|vertical|v)
|
|
423
|
+
tmux select-layout -t "$daemon_session" even-vertical
|
|
424
|
+
success "Layout applied: even-vertical"
|
|
425
|
+
;;
|
|
426
|
+
*)
|
|
427
|
+
error "Unknown layout: $layout"
|
|
428
|
+
echo " Available: tiled, horizontal, vertical"
|
|
429
|
+
return 1
|
|
430
|
+
;;
|
|
431
|
+
esac
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# ─── Help subcommand ──────────────────────────────────────────────────────
|
|
435
|
+
|
|
436
|
+
cmd_help() {
|
|
437
|
+
cat << 'EOF'
|
|
438
|
+
shipwright tmux-pipeline — Spawn and manage pipelines in tmux windows
|
|
439
|
+
|
|
440
|
+
USAGE
|
|
441
|
+
shipwright tmux-pipeline <command> [options]
|
|
442
|
+
|
|
443
|
+
COMMANDS
|
|
444
|
+
spawn --issue <N>
|
|
445
|
+
Create a new tmux window for a pipeline and run it
|
|
446
|
+
Window name: pipeline-<N>
|
|
447
|
+
Stores pane ID in ~/.shipwright/heartbeats/pipeline-<N>.json
|
|
448
|
+
|
|
449
|
+
attach [--issue] <N>
|
|
450
|
+
Attach to a running pipeline's tmux window
|
|
451
|
+
Example: shipwright tmux-pipeline attach 42
|
|
452
|
+
|
|
453
|
+
capture [--issue] <N>
|
|
454
|
+
Capture and print current output of a pipeline pane
|
|
455
|
+
Example: shipwright tmux-pipeline capture 42
|
|
456
|
+
|
|
457
|
+
stream [--issue] <N>
|
|
458
|
+
Continuously stream a pipeline's output to stdout
|
|
459
|
+
Like "tail -f" for tmux panes
|
|
460
|
+
Press Ctrl-C to stop
|
|
461
|
+
|
|
462
|
+
list
|
|
463
|
+
Show all pipeline windows with status
|
|
464
|
+
Displays issue numbers, pane IDs, and status
|
|
465
|
+
|
|
466
|
+
kill [--issue] <N>
|
|
467
|
+
Terminate a pipeline window gracefully
|
|
468
|
+
Cleans up associated heartbeat file
|
|
469
|
+
Example: shipwright tmux-pipeline kill 42
|
|
470
|
+
|
|
471
|
+
layout <type>
|
|
472
|
+
Arrange pipeline windows in a grid layout
|
|
473
|
+
Types: tiled, horizontal, vertical
|
|
474
|
+
|
|
475
|
+
help
|
|
476
|
+
Show this help message
|
|
477
|
+
|
|
478
|
+
OPTIONS
|
|
479
|
+
--issue <N> Specify issue number (can also be positional arg)
|
|
480
|
+
|
|
481
|
+
EXAMPLES
|
|
482
|
+
# Create and spawn a pipeline for issue #42
|
|
483
|
+
shipwright tmux-pipeline spawn --issue 42
|
|
484
|
+
|
|
485
|
+
# Attach to the pipeline window
|
|
486
|
+
shipwright tmux-pipeline attach 42
|
|
487
|
+
|
|
488
|
+
# Capture current output
|
|
489
|
+
shipwright tmux-pipeline capture 42
|
|
490
|
+
|
|
491
|
+
# Stream output continuously
|
|
492
|
+
shipwright tmux-pipeline stream 42
|
|
493
|
+
|
|
494
|
+
# List all running pipelines
|
|
495
|
+
shipwright tmux-pipeline list
|
|
496
|
+
|
|
497
|
+
# Kill the pipeline
|
|
498
|
+
shipwright tmux-pipeline kill 42
|
|
499
|
+
|
|
500
|
+
# Arrange windows in a tiled grid
|
|
501
|
+
shipwright tmux-pipeline layout tiled
|
|
502
|
+
|
|
503
|
+
INTEGRATION
|
|
504
|
+
Works with: shipwright daemon start --detach
|
|
505
|
+
Requires: tmux session "sw-daemon" running
|
|
506
|
+
State: ~/.shipwright/heartbeats/pipeline-<N>.json
|
|
507
|
+
|
|
508
|
+
EOF
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
# ─── Main router ──────────────────────────────────────────────────────────
|
|
512
|
+
|
|
513
|
+
main() {
|
|
514
|
+
local cmd="${1:-help}"
|
|
515
|
+
shift 2>/dev/null || true
|
|
516
|
+
|
|
517
|
+
case "$cmd" in
|
|
518
|
+
spawn)
|
|
519
|
+
cmd_spawn "$@"
|
|
520
|
+
;;
|
|
521
|
+
attach)
|
|
522
|
+
cmd_attach "$@"
|
|
523
|
+
;;
|
|
524
|
+
capture)
|
|
525
|
+
cmd_capture "$@"
|
|
526
|
+
;;
|
|
527
|
+
stream)
|
|
528
|
+
cmd_stream "$@"
|
|
529
|
+
;;
|
|
530
|
+
list)
|
|
531
|
+
cmd_list "$@"
|
|
532
|
+
;;
|
|
533
|
+
kill)
|
|
534
|
+
cmd_kill "$@"
|
|
535
|
+
;;
|
|
536
|
+
layout)
|
|
537
|
+
cmd_layout "$@"
|
|
538
|
+
;;
|
|
539
|
+
help|--help|-h)
|
|
540
|
+
cmd_help
|
|
541
|
+
;;
|
|
542
|
+
*)
|
|
543
|
+
error "Unknown command: $cmd"
|
|
544
|
+
echo ""
|
|
545
|
+
cmd_help
|
|
546
|
+
exit 1
|
|
547
|
+
;;
|
|
548
|
+
esac
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
# Only run main if this script is executed directly (not sourced)
|
|
552
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
553
|
+
main "$@"
|
|
554
|
+
fi
|
package/scripts/sw-tmux.sh
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# ║ shipwright tmux fix — Auto-fix common issues ║
|
|
12
12
|
# ║ shipwright tmux reload — Reload tmux config ║
|
|
13
13
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
14
|
-
VERSION="
|
|
14
|
+
VERSION="2.0.0"
|
|
15
15
|
set -euo pipefail
|
|
16
16
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
17
17
|
|