shipwright-cli 1.7.1 → 1.9.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/.claude/agents/code-reviewer.md +90 -0
- package/.claude/agents/devops-engineer.md +142 -0
- package/.claude/agents/pipeline-agent.md +80 -0
- package/.claude/agents/shell-script-specialist.md +150 -0
- package/.claude/agents/test-specialist.md +196 -0
- package/.claude/hooks/post-tool-use.sh +38 -0
- package/.claude/hooks/pre-tool-use.sh +25 -0
- package/.claude/hooks/session-started.sh +37 -0
- package/README.md +212 -814
- package/claude-code/CLAUDE.md.shipwright +54 -0
- package/claude-code/hooks/notify-idle.sh +2 -2
- package/claude-code/hooks/session-start.sh +24 -0
- package/claude-code/hooks/task-completed.sh +6 -2
- package/claude-code/settings.json.template +12 -0
- package/dashboard/public/app.js +4422 -0
- package/dashboard/public/index.html +816 -0
- package/dashboard/public/styles.css +4755 -0
- package/dashboard/server.ts +4315 -0
- package/docs/KNOWN-ISSUES.md +18 -10
- package/docs/TIPS.md +38 -26
- package/docs/patterns/README.md +33 -23
- package/package.json +9 -5
- package/scripts/adapters/iterm2-adapter.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +52 -23
- package/scripts/adapters/wezterm-adapter.sh +26 -14
- package/scripts/lib/compat.sh +200 -0
- package/scripts/lib/helpers.sh +72 -0
- package/scripts/postinstall.mjs +72 -13
- package/scripts/{cct → sw} +109 -21
- package/scripts/sw-adversarial.sh +274 -0
- package/scripts/sw-architecture-enforcer.sh +330 -0
- package/scripts/sw-checkpoint.sh +390 -0
- package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
- package/scripts/sw-connect.sh +619 -0
- package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
- package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
- package/scripts/sw-dashboard.sh +477 -0
- package/scripts/sw-developer-simulation.sh +252 -0
- package/scripts/sw-docs.sh +635 -0
- package/scripts/sw-doctor.sh +907 -0
- package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
- package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
- package/scripts/sw-github-checks.sh +521 -0
- package/scripts/sw-github-deploy.sh +533 -0
- package/scripts/sw-github-graphql.sh +972 -0
- package/scripts/sw-heartbeat.sh +293 -0
- package/scripts/{cct-init.sh → sw-init.sh} +144 -11
- package/scripts/sw-intelligence.sh +1196 -0
- package/scripts/sw-jira.sh +643 -0
- package/scripts/sw-launchd.sh +364 -0
- package/scripts/sw-linear.sh +648 -0
- package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
- package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
- package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
- package/scripts/sw-patrol-meta.sh +417 -0
- package/scripts/sw-pipeline-composer.sh +455 -0
- package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
- package/scripts/sw-predictive.sh +820 -0
- package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
- package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
- package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
- package/scripts/sw-remote.sh +687 -0
- package/scripts/sw-self-optimize.sh +947 -0
- package/scripts/sw-session.sh +519 -0
- package/scripts/sw-setup.sh +234 -0
- package/scripts/sw-status.sh +605 -0
- package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
- package/scripts/sw-tmux.sh +591 -0
- package/scripts/sw-tracker-jira.sh +277 -0
- package/scripts/sw-tracker-linear.sh +292 -0
- package/scripts/sw-tracker.sh +409 -0
- package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
- package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
- package/templates/pipelines/autonomous.json +27 -5
- package/templates/pipelines/full.json +12 -0
- package/templates/pipelines/standard.json +12 -0
- package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
- package/tmux/templates/accessibility.json +34 -0
- package/tmux/templates/api-design.json +35 -0
- package/tmux/templates/architecture.json +1 -0
- package/tmux/templates/bug-fix.json +9 -0
- package/tmux/templates/code-review.json +1 -0
- package/tmux/templates/compliance.json +36 -0
- package/tmux/templates/data-pipeline.json +36 -0
- package/tmux/templates/debt-paydown.json +34 -0
- package/tmux/templates/devops.json +1 -0
- package/tmux/templates/documentation.json +1 -0
- package/tmux/templates/exploration.json +1 -0
- package/tmux/templates/feature-dev.json +1 -0
- package/tmux/templates/full-stack.json +8 -0
- package/tmux/templates/i18n.json +34 -0
- package/tmux/templates/incident-response.json +36 -0
- package/tmux/templates/migration.json +1 -0
- package/tmux/templates/observability.json +35 -0
- package/tmux/templates/onboarding.json +33 -0
- package/tmux/templates/performance.json +35 -0
- package/tmux/templates/refactor.json +1 -0
- package/tmux/templates/release.json +35 -0
- package/tmux/templates/security-audit.json +8 -0
- package/tmux/templates/spike.json +34 -0
- package/tmux/templates/testing.json +1 -0
- package/tmux/tmux.conf +98 -9
- package/scripts/cct-doctor.sh +0 -414
- package/scripts/cct-session.sh +0 -284
- package/scripts/cct-status.sh +0 -169
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright heartbeat — File-based agent heartbeat protocol ║
|
|
4
|
+
# ║ Write · Check · List · Clear heartbeats for autonomous agents ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
|
+
|
|
9
|
+
VERSION="1.9.0"
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
|
|
12
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
13
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
14
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
15
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
16
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
17
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
18
|
+
RED='\033[38;2;248;113;113m' # error
|
|
19
|
+
DIM='\033[2m'
|
|
20
|
+
BOLD='\033[1m'
|
|
21
|
+
RESET='\033[0m'
|
|
22
|
+
|
|
23
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
24
|
+
# shellcheck source=lib/compat.sh
|
|
25
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
26
|
+
|
|
27
|
+
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
28
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
29
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
30
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
31
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
32
|
+
|
|
33
|
+
# ─── Constants ──────────────────────────────────────────────────────────────
|
|
34
|
+
HEARTBEAT_DIR="$HOME/.shipwright/heartbeats"
|
|
35
|
+
|
|
36
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
37
|
+
|
|
38
|
+
# ─── Ensure heartbeat directory exists ──────────────────────────────────────
|
|
39
|
+
ensure_dir() {
|
|
40
|
+
mkdir -p "$HEARTBEAT_DIR"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ─── Help ───────────────────────────────────────────────────────────────────
|
|
44
|
+
show_help() {
|
|
45
|
+
echo ""
|
|
46
|
+
echo -e "${CYAN}${BOLD} Shipwright Heartbeat${RESET} ${DIM}v${VERSION}${RESET}"
|
|
47
|
+
echo -e "${DIM} ══════════════════════════════════════════${RESET}"
|
|
48
|
+
echo ""
|
|
49
|
+
echo -e " ${BOLD}USAGE${RESET}"
|
|
50
|
+
echo -e " shipwright heartbeat <command> [options]"
|
|
51
|
+
echo ""
|
|
52
|
+
echo -e " ${BOLD}COMMANDS${RESET}"
|
|
53
|
+
echo -e " ${CYAN}write${RESET} <job-id> Write/update heartbeat for a job"
|
|
54
|
+
echo -e " ${CYAN}check${RESET} <job-id> Check if a job is alive (exit 0) or stale (exit 1)"
|
|
55
|
+
echo -e " ${CYAN}list${RESET} List all active heartbeats as JSON"
|
|
56
|
+
echo -e " ${CYAN}clear${RESET} <job-id> Remove heartbeat file for a job"
|
|
57
|
+
echo ""
|
|
58
|
+
echo -e " ${BOLD}WRITE OPTIONS${RESET}"
|
|
59
|
+
echo -e " --pid <pid> Process ID (default: current PID)"
|
|
60
|
+
echo -e " --issue <num> Issue number"
|
|
61
|
+
echo -e " --stage <stage> Pipeline stage name"
|
|
62
|
+
echo -e " --iteration <n> Build iteration number"
|
|
63
|
+
echo -e " --activity <desc> Description of current activity"
|
|
64
|
+
echo ""
|
|
65
|
+
echo -e " ${BOLD}CHECK OPTIONS${RESET}"
|
|
66
|
+
echo -e " --timeout <secs> Staleness threshold (default: 120)"
|
|
67
|
+
echo ""
|
|
68
|
+
echo -e " ${BOLD}EXAMPLES${RESET}"
|
|
69
|
+
echo -e " ${DIM}# Agent writes heartbeat every 30s${RESET}"
|
|
70
|
+
echo -e " shipwright heartbeat write job-42 --stage build --iteration 3 --activity \"Running tests\""
|
|
71
|
+
echo ""
|
|
72
|
+
echo -e " ${DIM}# Daemon checks if agent is alive${RESET}"
|
|
73
|
+
echo -e " shipwright heartbeat check job-42 --timeout 120"
|
|
74
|
+
echo ""
|
|
75
|
+
echo -e " ${DIM}# Dashboard lists all heartbeats${RESET}"
|
|
76
|
+
echo -e " shipwright heartbeat list"
|
|
77
|
+
echo ""
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ─── Write Heartbeat ───────────────────────────────────────────────────────
|
|
81
|
+
cmd_write() {
|
|
82
|
+
local job_id="${1:-}"
|
|
83
|
+
if [[ -z "$job_id" ]]; then
|
|
84
|
+
error "Usage: shipwright heartbeat write <job-id> [options]"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
shift
|
|
88
|
+
|
|
89
|
+
local pid="$$"
|
|
90
|
+
local issue=""
|
|
91
|
+
local stage=""
|
|
92
|
+
local iteration=""
|
|
93
|
+
local activity=""
|
|
94
|
+
|
|
95
|
+
while [[ $# -gt 0 ]]; do
|
|
96
|
+
case "$1" in
|
|
97
|
+
--pid) pid="${2:-}"; shift 2 ;;
|
|
98
|
+
--issue) issue="${2:-}"; shift 2 ;;
|
|
99
|
+
--stage) stage="${2:-}"; shift 2 ;;
|
|
100
|
+
--iteration) iteration="${2:-}"; shift 2 ;;
|
|
101
|
+
--activity) activity="${2:-}"; shift 2 ;;
|
|
102
|
+
*)
|
|
103
|
+
warn "Unknown option: $1"
|
|
104
|
+
shift
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
done
|
|
108
|
+
|
|
109
|
+
# Collect resource metrics from the process
|
|
110
|
+
local memory_mb=0
|
|
111
|
+
local cpu_pct=0
|
|
112
|
+
|
|
113
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
114
|
+
local rss_kb
|
|
115
|
+
rss_kb="$(ps -o rss= -p "$pid" 2>/dev/null || true)"
|
|
116
|
+
rss_kb="$(echo "$rss_kb" | tr -d ' ')"
|
|
117
|
+
if [[ -n "$rss_kb" && "$rss_kb" =~ ^[0-9]+$ ]]; then
|
|
118
|
+
memory_mb=$((rss_kb / 1024))
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
local cpu_raw
|
|
122
|
+
cpu_raw="$(ps -o %cpu= -p "$pid" 2>/dev/null || true)"
|
|
123
|
+
cpu_raw="$(echo "$cpu_raw" | tr -d ' ')"
|
|
124
|
+
if [[ -n "$cpu_raw" ]]; then
|
|
125
|
+
# Truncate to integer for JSON safety
|
|
126
|
+
cpu_pct="${cpu_raw%%.*}"
|
|
127
|
+
cpu_pct="${cpu_pct:-0}"
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
ensure_dir
|
|
132
|
+
|
|
133
|
+
local tmp_file
|
|
134
|
+
tmp_file="$(mktemp "${HEARTBEAT_DIR}/.tmp.XXXXXX")"
|
|
135
|
+
|
|
136
|
+
# Build JSON with jq for proper escaping
|
|
137
|
+
jq -n \
|
|
138
|
+
--argjson pid "$pid" \
|
|
139
|
+
--arg issue "$issue" \
|
|
140
|
+
--arg stage "$stage" \
|
|
141
|
+
--arg iteration "$iteration" \
|
|
142
|
+
--argjson memory_mb "$memory_mb" \
|
|
143
|
+
--arg cpu_pct "$cpu_pct" \
|
|
144
|
+
--arg last_activity "$activity" \
|
|
145
|
+
--arg updated_at "$(now_iso)" \
|
|
146
|
+
'{
|
|
147
|
+
pid: $pid,
|
|
148
|
+
issue: (if $issue == "" then null else ($issue | tonumber) end),
|
|
149
|
+
stage: (if $stage == "" then null else $stage end),
|
|
150
|
+
iteration: (if $iteration == "" then null else ($iteration | tonumber) end),
|
|
151
|
+
memory_mb: $memory_mb,
|
|
152
|
+
cpu_pct: ($cpu_pct | tonumber),
|
|
153
|
+
last_activity: $last_activity,
|
|
154
|
+
updated_at: $updated_at
|
|
155
|
+
}' > "$tmp_file" || { rm -f "$tmp_file"; return 1; }
|
|
156
|
+
|
|
157
|
+
# Atomic write
|
|
158
|
+
mv "$tmp_file" "${HEARTBEAT_DIR}/${job_id}.json"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# ─── Check Heartbeat ───────────────────────────────────────────────────────
|
|
162
|
+
cmd_check() {
|
|
163
|
+
local job_id="${1:-}"
|
|
164
|
+
if [[ -z "$job_id" ]]; then
|
|
165
|
+
error "Usage: shipwright heartbeat check <job-id> [--timeout <secs>]"
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
168
|
+
shift
|
|
169
|
+
|
|
170
|
+
local timeout=120
|
|
171
|
+
|
|
172
|
+
while [[ $# -gt 0 ]]; do
|
|
173
|
+
case "$1" in
|
|
174
|
+
--timeout) timeout="${2:-120}"; shift 2 ;;
|
|
175
|
+
*)
|
|
176
|
+
warn "Unknown option: $1"
|
|
177
|
+
shift
|
|
178
|
+
;;
|
|
179
|
+
esac
|
|
180
|
+
done
|
|
181
|
+
|
|
182
|
+
local hb_file="${HEARTBEAT_DIR}/${job_id}.json"
|
|
183
|
+
|
|
184
|
+
if [[ ! -f "$hb_file" ]]; then
|
|
185
|
+
error "No heartbeat found for job: ${job_id}"
|
|
186
|
+
return 1
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
local updated_at
|
|
190
|
+
updated_at="$(jq -r '.updated_at' "$hb_file" 2>/dev/null || true)"
|
|
191
|
+
|
|
192
|
+
if [[ -z "$updated_at" || "$updated_at" == "null" ]]; then
|
|
193
|
+
error "Invalid heartbeat file for job: ${job_id}"
|
|
194
|
+
return 1
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# Convert ISO timestamp to epoch for comparison
|
|
198
|
+
local hb_epoch now_epoch age_secs
|
|
199
|
+
|
|
200
|
+
# macOS date -j -f vs GNU date -d (TZ=UTC since timestamps are UTC)
|
|
201
|
+
if TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s &>/dev/null; then
|
|
202
|
+
hb_epoch="$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated_at" +%s 2>/dev/null)"
|
|
203
|
+
else
|
|
204
|
+
hb_epoch="$(date -d "$updated_at" +%s 2>/dev/null || echo 0)"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
now_epoch="$(date +%s)"
|
|
208
|
+
age_secs=$((now_epoch - hb_epoch))
|
|
209
|
+
|
|
210
|
+
if [[ "$age_secs" -le "$timeout" ]]; then
|
|
211
|
+
success "Job ${job_id} alive (${age_secs}s ago)"
|
|
212
|
+
return 0
|
|
213
|
+
else
|
|
214
|
+
warn "Job ${job_id} stale (${age_secs}s ago, timeout: ${timeout}s)"
|
|
215
|
+
return 1
|
|
216
|
+
fi
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# ─── List Heartbeats ───────────────────────────────────────────────────────
|
|
220
|
+
cmd_list() {
|
|
221
|
+
ensure_dir
|
|
222
|
+
|
|
223
|
+
local result="["
|
|
224
|
+
local first=true
|
|
225
|
+
|
|
226
|
+
for hb_file in "${HEARTBEAT_DIR}"/*.json; do
|
|
227
|
+
# Handle no matches (glob returns literal pattern)
|
|
228
|
+
if [[ ! -f "$hb_file" ]]; then
|
|
229
|
+
continue
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
local job_id
|
|
233
|
+
job_id="$(basename "$hb_file" .json)"
|
|
234
|
+
|
|
235
|
+
local content
|
|
236
|
+
content="$(jq -c --arg job_id "$job_id" '. + {job_id: $job_id}' "$hb_file" 2>/dev/null || true)"
|
|
237
|
+
|
|
238
|
+
if [[ -z "$content" ]]; then
|
|
239
|
+
continue
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
if [[ "$first" == "true" ]]; then
|
|
243
|
+
first=false
|
|
244
|
+
else
|
|
245
|
+
result="${result},"
|
|
246
|
+
fi
|
|
247
|
+
result="${result}${content}"
|
|
248
|
+
done
|
|
249
|
+
|
|
250
|
+
result="${result}]"
|
|
251
|
+
echo "$result" | jq '.'
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# ─── Clear Heartbeat ───────────────────────────────────────────────────────
|
|
255
|
+
cmd_clear() {
|
|
256
|
+
local job_id="${1:-}"
|
|
257
|
+
if [[ -z "$job_id" ]]; then
|
|
258
|
+
error "Usage: shipwright heartbeat clear <job-id>"
|
|
259
|
+
exit 1
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
local hb_file="${HEARTBEAT_DIR}/${job_id}.json"
|
|
263
|
+
|
|
264
|
+
if [[ -f "$hb_file" ]]; then
|
|
265
|
+
rm -f "$hb_file"
|
|
266
|
+
success "Cleared heartbeat for job: ${job_id}"
|
|
267
|
+
else
|
|
268
|
+
warn "No heartbeat found for job: ${job_id}"
|
|
269
|
+
fi
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# ─── Command Router ────────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
main() {
|
|
275
|
+
local cmd="${1:-help}"
|
|
276
|
+
shift 2>/dev/null || true
|
|
277
|
+
|
|
278
|
+
case "$cmd" in
|
|
279
|
+
write) cmd_write "$@" ;;
|
|
280
|
+
check) cmd_check "$@" ;;
|
|
281
|
+
list) cmd_list "$@" ;;
|
|
282
|
+
clear) cmd_clear "$@" ;;
|
|
283
|
+
help|--help|-h) show_help ;;
|
|
284
|
+
*)
|
|
285
|
+
error "Unknown command: ${cmd}"
|
|
286
|
+
echo ""
|
|
287
|
+
show_help
|
|
288
|
+
exit 1
|
|
289
|
+
;;
|
|
290
|
+
esac
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
main "$@"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
-
# ║ shipwright init — Complete setup for Shipwright +
|
|
3
|
+
# ║ shipwright init — Complete setup for Shipwright + Shipwright ║
|
|
4
4
|
# ║ ║
|
|
5
5
|
# ║ Installs: tmux config, overlay, team & pipeline templates, Claude Code ║
|
|
6
6
|
# ║ settings (with agent teams enabled), quality gate hooks, CLAUDE.md ║
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
# ║ ║
|
|
9
9
|
# ║ --deploy Detect platform and generate deployed.json template ║
|
|
10
10
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
11
|
+
VERSION="1.9.0"
|
|
11
12
|
set -euo pipefail
|
|
13
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
12
14
|
|
|
13
15
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
16
|
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
@@ -23,6 +25,9 @@ DIM='\033[2m'
|
|
|
23
25
|
BOLD='\033[1m'
|
|
24
26
|
RESET='\033[0m'
|
|
25
27
|
|
|
28
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
29
|
+
# shellcheck source=lib/compat.sh
|
|
30
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
26
31
|
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
27
32
|
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
28
33
|
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
@@ -71,26 +76,95 @@ echo -e "${DIM}═════════════════════
|
|
|
71
76
|
echo ""
|
|
72
77
|
|
|
73
78
|
# ─── tmux.conf ────────────────────────────────────────────────────────────────
|
|
79
|
+
TOOK_FULL_TMUX_CONF=false
|
|
74
80
|
if [[ -f "$REPO_DIR/tmux/tmux.conf" ]]; then
|
|
75
81
|
if [[ -f "$HOME/.tmux.conf" ]]; then
|
|
76
82
|
cp "$HOME/.tmux.conf" "$HOME/.tmux.conf.bak"
|
|
77
83
|
warn "Backed up existing ~/.tmux.conf → ~/.tmux.conf.bak"
|
|
84
|
+
read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Overwrite ~/.tmux.conf with the Shipwright config? [Y/n] ")" tmux_confirm
|
|
85
|
+
if [[ -z "$tmux_confirm" || "$(echo "$tmux_confirm" | tr '[:upper:]' '[:lower:]')" != "n" ]]; then
|
|
86
|
+
cp "$REPO_DIR/tmux/tmux.conf" "$HOME/.tmux.conf"
|
|
87
|
+
success "Installed ~/.tmux.conf"
|
|
88
|
+
TOOK_FULL_TMUX_CONF=true
|
|
89
|
+
else
|
|
90
|
+
info "Kept existing ~/.tmux.conf"
|
|
91
|
+
fi
|
|
92
|
+
else
|
|
93
|
+
cp "$REPO_DIR/tmux/tmux.conf" "$HOME/.tmux.conf"
|
|
94
|
+
success "Installed ~/.tmux.conf"
|
|
95
|
+
TOOK_FULL_TMUX_CONF=true
|
|
78
96
|
fi
|
|
79
|
-
cp "$REPO_DIR/tmux/tmux.conf" "$HOME/.tmux.conf"
|
|
80
|
-
success "Installed ~/.tmux.conf"
|
|
81
97
|
else
|
|
82
98
|
warn "tmux.conf not found in package — skipping"
|
|
83
99
|
fi
|
|
84
100
|
|
|
85
101
|
# ─── Overlay ──────────────────────────────────────────────────────────────────
|
|
86
|
-
if [[ -f "$REPO_DIR/tmux/
|
|
102
|
+
if [[ -f "$REPO_DIR/tmux/shipwright-overlay.conf" ]]; then
|
|
87
103
|
mkdir -p "$HOME/.tmux"
|
|
88
|
-
cp "$REPO_DIR/tmux/
|
|
89
|
-
success "Installed ~/.tmux/
|
|
104
|
+
cp "$REPO_DIR/tmux/shipwright-overlay.conf" "$HOME/.tmux/shipwright-overlay.conf"
|
|
105
|
+
success "Installed ~/.tmux/shipwright-overlay.conf"
|
|
90
106
|
else
|
|
91
107
|
warn "Overlay not found in package — skipping"
|
|
92
108
|
fi
|
|
93
109
|
|
|
110
|
+
# ─── Overlay injection ───────────────────────────────────────────────────────
|
|
111
|
+
# If user kept their own tmux.conf, ensure it sources the overlay
|
|
112
|
+
if [[ "$TOOK_FULL_TMUX_CONF" == "false" && -f "$HOME/.tmux.conf" ]]; then
|
|
113
|
+
if ! grep -q "shipwright-overlay" "$HOME/.tmux.conf" 2>/dev/null; then
|
|
114
|
+
read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Add Shipwright overlay source to ~/.tmux.conf? [Y/n] ")" overlay_confirm
|
|
115
|
+
if [[ -z "$overlay_confirm" || "$(echo "$overlay_confirm" | tr '[:upper:]' '[:lower:]')" != "n" ]]; then
|
|
116
|
+
{
|
|
117
|
+
echo ""
|
|
118
|
+
echo "# Shipwright agent overlay"
|
|
119
|
+
echo "source-file -q ~/.tmux/shipwright-overlay.conf"
|
|
120
|
+
} >> "$HOME/.tmux.conf"
|
|
121
|
+
success "Appended overlay source to ~/.tmux.conf"
|
|
122
|
+
else
|
|
123
|
+
info "Skipped overlay injection. Add manually:"
|
|
124
|
+
echo -e " ${DIM}source-file -q ~/.tmux/shipwright-overlay.conf${RESET}"
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# ─── TPM (Tmux Plugin Manager) ────────────────────────────────────────────
|
|
130
|
+
if [[ ! -d "$HOME/.tmux/plugins/tpm" ]]; then
|
|
131
|
+
info "Installing TPM (Tmux Plugin Manager)..."
|
|
132
|
+
if git clone https://github.com/tmux-plugins/tpm "$HOME/.tmux/plugins/tpm" 2>/dev/null; then
|
|
133
|
+
success "TPM installed"
|
|
134
|
+
else
|
|
135
|
+
warn "Could not install TPM — install manually or run: shipwright tmux install"
|
|
136
|
+
fi
|
|
137
|
+
else
|
|
138
|
+
success "TPM already installed"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# ─── Install TPM plugins ──────────────────────────────────────────────────
|
|
142
|
+
if [[ -x "$HOME/.tmux/plugins/tpm/bin/install_plugins" ]]; then
|
|
143
|
+
info "Installing tmux plugins..."
|
|
144
|
+
"$HOME/.tmux/plugins/tpm/bin/install_plugins" 2>/dev/null && \
|
|
145
|
+
success "Plugins installed (sensible, resurrect, continuum, yank, fzf)" || \
|
|
146
|
+
warn "Some plugins may not have installed — press prefix + I inside tmux"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# ─── Reload tmux config if inside tmux ─────────────────────────────────────
|
|
150
|
+
if [[ -n "${TMUX:-}" ]]; then
|
|
151
|
+
tmux source-file "$HOME/.tmux.conf" 2>/dev/null && \
|
|
152
|
+
success "Reloaded tmux config (passthrough, extended-keys, plugins active)" || true
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# ─── Fix iTerm2 mouse reporting if disabled ────────────────────────────────
|
|
156
|
+
if [[ "${LC_TERMINAL:-${TERM_PROGRAM:-}}" == *iTerm* ]]; then
|
|
157
|
+
ITERM_MOUSE="$(defaults read com.googlecode.iterm2 "New Bookmarks" 2>/dev/null \
|
|
158
|
+
| grep '"Mouse Reporting"' | head -1 | grep -oE '[0-9]+' || echo "unknown")"
|
|
159
|
+
if [[ "$ITERM_MOUSE" == "0" ]]; then
|
|
160
|
+
warn "iTerm2 mouse reporting is disabled — tmux can't receive mouse clicks"
|
|
161
|
+
/usr/libexec/PlistBuddy -c "Set ':New Bookmarks:0:Mouse Reporting' 1" \
|
|
162
|
+
~/Library/Preferences/com.googlecode.iterm2.plist 2>/dev/null && \
|
|
163
|
+
success "Enabled iTerm2 mouse reporting (open a new tab to activate)" || \
|
|
164
|
+
warn "Could not auto-fix — enable manually: Preferences → Profiles → Terminal → Report mouse clicks"
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
94
168
|
# ─── Team Templates ──────────────────────────────────────────────────────────
|
|
95
169
|
SHIPWRIGHT_DIR="$HOME/.shipwright"
|
|
96
170
|
TEMPLATES_SRC="$REPO_DIR/tmux/templates"
|
|
@@ -101,10 +175,10 @@ if [[ -d "$TEMPLATES_SRC" ]]; then
|
|
|
101
175
|
cp "$tpl" "$SHIPWRIGHT_DIR/templates/$(basename "$tpl")"
|
|
102
176
|
done
|
|
103
177
|
# Also install to legacy path for backward compatibility
|
|
104
|
-
mkdir -p "$HOME/.
|
|
178
|
+
mkdir -p "$HOME/.shipwright/templates"
|
|
105
179
|
for tpl in "$TEMPLATES_SRC"/*.json; do
|
|
106
180
|
[[ -f "$tpl" ]] || continue
|
|
107
|
-
cp "$tpl" "$HOME/.
|
|
181
|
+
cp "$tpl" "$HOME/.shipwright/templates/$(basename "$tpl")"
|
|
108
182
|
done
|
|
109
183
|
tpl_count=$(find "$SHIPWRIGHT_DIR/templates" -name '*.json' -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
110
184
|
success "Installed ${tpl_count} team templates → ~/.shipwright/templates/"
|
|
@@ -150,7 +224,10 @@ if [[ -f "$SETTINGS_FILE" ]]; then
|
|
|
150
224
|
fi
|
|
151
225
|
fi
|
|
152
226
|
elif [[ -f "$SETTINGS_TEMPLATE" ]]; then
|
|
153
|
-
|
|
227
|
+
# Strip JSONC comments (// lines) so jq can parse on subsequent runs
|
|
228
|
+
tmp=$(mktemp)
|
|
229
|
+
sed '/^[[:space:]]*\/\//d' "$SETTINGS_TEMPLATE" > "$tmp"
|
|
230
|
+
mv "$tmp" "$SETTINGS_FILE"
|
|
154
231
|
success "Installed ~/.claude/settings.json (with agent teams enabled)"
|
|
155
232
|
else
|
|
156
233
|
# Create minimal settings.json with agent teams
|
|
@@ -189,6 +266,62 @@ if [[ -d "$HOOKS_SRC" ]]; then
|
|
|
189
266
|
fi
|
|
190
267
|
fi
|
|
191
268
|
|
|
269
|
+
# ─── Wire Hooks into settings.json ──────────────────────────────────────────
|
|
270
|
+
# Ensure each installed hook has a matching event config in settings.json
|
|
271
|
+
if [[ -f "$SETTINGS_FILE" ]] && jq -e '.' "$SETTINGS_FILE" &>/dev/null; then
|
|
272
|
+
hooks_wired=0
|
|
273
|
+
|
|
274
|
+
# Ensure .hooks object exists
|
|
275
|
+
if ! jq -e '.hooks' "$SETTINGS_FILE" &>/dev/null; then
|
|
276
|
+
tmp=$(mktemp)
|
|
277
|
+
jq '.hooks = {}' "$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
# TeammateIdle
|
|
281
|
+
if [[ -f "$CLAUDE_DIR/hooks/teammate-idle.sh" ]] && ! jq -e '.hooks.TeammateIdle' "$SETTINGS_FILE" &>/dev/null; then
|
|
282
|
+
tmp=$(mktemp)
|
|
283
|
+
jq '.hooks.TeammateIdle = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/teammate-idle.sh", "timeout": 30, "statusMessage": "Running typecheck before idle..."}]}]' \
|
|
284
|
+
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
285
|
+
hooks_wired=$((hooks_wired + 1))
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
# TaskCompleted
|
|
289
|
+
if [[ -f "$CLAUDE_DIR/hooks/task-completed.sh" ]] && ! jq -e '.hooks.TaskCompleted' "$SETTINGS_FILE" &>/dev/null; then
|
|
290
|
+
tmp=$(mktemp)
|
|
291
|
+
jq '.hooks.TaskCompleted = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/task-completed.sh", "timeout": 60, "statusMessage": "Running quality checks..."}]}]' \
|
|
292
|
+
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
293
|
+
hooks_wired=$((hooks_wired + 1))
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
# Notification
|
|
297
|
+
if [[ -f "$CLAUDE_DIR/hooks/notify-idle.sh" ]] && ! jq -e '.hooks.Notification' "$SETTINGS_FILE" &>/dev/null; then
|
|
298
|
+
tmp=$(mktemp)
|
|
299
|
+
jq '.hooks.Notification = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/notify-idle.sh", "async": true}]}]' \
|
|
300
|
+
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
301
|
+
hooks_wired=$((hooks_wired + 1))
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
# PreCompact
|
|
305
|
+
if [[ -f "$CLAUDE_DIR/hooks/pre-compact-save.sh" ]] && ! jq -e '.hooks.PreCompact' "$SETTINGS_FILE" &>/dev/null; then
|
|
306
|
+
tmp=$(mktemp)
|
|
307
|
+
jq '.hooks.PreCompact = [{"matcher": "auto", "hooks": [{"type": "command", "command": "~/.claude/hooks/pre-compact-save.sh", "statusMessage": "Saving context before compaction..."}]}]' \
|
|
308
|
+
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
309
|
+
hooks_wired=$((hooks_wired + 1))
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
# SessionStart
|
|
313
|
+
if [[ -f "$CLAUDE_DIR/hooks/session-start.sh" ]] && ! jq -e '.hooks.SessionStart' "$SETTINGS_FILE" &>/dev/null; then
|
|
314
|
+
tmp=$(mktemp)
|
|
315
|
+
jq '.hooks.SessionStart = [{"hooks": [{"type": "command", "command": "~/.claude/hooks/session-start.sh", "timeout": 5}]}]' \
|
|
316
|
+
"$SETTINGS_FILE" > "$tmp" && mv "$tmp" "$SETTINGS_FILE"
|
|
317
|
+
hooks_wired=$((hooks_wired + 1))
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
if [[ $hooks_wired -gt 0 ]]; then
|
|
321
|
+
success "Wired ${hooks_wired} hooks into settings.json"
|
|
322
|
+
fi
|
|
323
|
+
fi
|
|
324
|
+
|
|
192
325
|
# ─── CLAUDE.md — Global agent instructions ────────────────────────────────────
|
|
193
326
|
CLAUDE_MD_SRC="$REPO_DIR/claude-code/CLAUDE.md.shipwright"
|
|
194
327
|
GLOBAL_CLAUDE_MD="$CLAUDE_DIR/CLAUDE.md"
|
|
@@ -236,7 +369,7 @@ fi
|
|
|
236
369
|
echo ""
|
|
237
370
|
echo -e "${CYAN}${BOLD}Running doctor...${RESET}"
|
|
238
371
|
echo ""
|
|
239
|
-
"$SCRIPT_DIR/
|
|
372
|
+
"$SCRIPT_DIR/sw-doctor.sh" || true
|
|
240
373
|
|
|
241
374
|
echo ""
|
|
242
375
|
echo -e "${BOLD}Quick start:${RESET}"
|
|
@@ -316,7 +449,7 @@ else
|
|
|
316
449
|
|
|
317
450
|
# Confirm with user
|
|
318
451
|
read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Configure deploy for ${BOLD}${DEPLOY_PLATFORM}${RESET}? [Y/n] ")" confirm
|
|
319
|
-
if [[ "$
|
|
452
|
+
if [[ "$(echo "$confirm" | tr '[:upper:]' '[:lower:]')" == "n" ]]; then
|
|
320
453
|
info "Aborted. Use --platform to specify manually."
|
|
321
454
|
exit 0
|
|
322
455
|
fi
|