shipwright-cli 1.7.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/LICENSE +21 -0
- package/README.md +926 -0
- package/claude-code/CLAUDE.md.shipwright +125 -0
- package/claude-code/hooks/notify-idle.sh +35 -0
- package/claude-code/hooks/pre-compact-save.sh +57 -0
- package/claude-code/hooks/task-completed.sh +170 -0
- package/claude-code/hooks/teammate-idle.sh +68 -0
- package/claude-code/settings.json.template +184 -0
- package/completions/_shipwright +140 -0
- package/completions/shipwright.bash +89 -0
- package/completions/shipwright.fish +107 -0
- package/docs/KNOWN-ISSUES.md +199 -0
- package/docs/TIPS.md +331 -0
- package/docs/definition-of-done.example.md +16 -0
- package/docs/patterns/README.md +139 -0
- package/docs/patterns/audit-loop.md +149 -0
- package/docs/patterns/bug-hunt.md +183 -0
- package/docs/patterns/feature-implementation.md +159 -0
- package/docs/patterns/refactoring.md +183 -0
- package/docs/patterns/research-exploration.md +144 -0
- package/docs/patterns/test-generation.md +173 -0
- package/package.json +49 -0
- package/scripts/adapters/docker-deploy.sh +50 -0
- package/scripts/adapters/fly-deploy.sh +41 -0
- package/scripts/adapters/iterm2-adapter.sh +122 -0
- package/scripts/adapters/railway-deploy.sh +34 -0
- package/scripts/adapters/tmux-adapter.sh +87 -0
- package/scripts/adapters/vercel-deploy.sh +35 -0
- package/scripts/adapters/wezterm-adapter.sh +103 -0
- package/scripts/cct +242 -0
- package/scripts/cct-cleanup.sh +172 -0
- package/scripts/cct-cost.sh +590 -0
- package/scripts/cct-daemon.sh +3189 -0
- package/scripts/cct-doctor.sh +328 -0
- package/scripts/cct-fix.sh +478 -0
- package/scripts/cct-fleet.sh +904 -0
- package/scripts/cct-init.sh +282 -0
- package/scripts/cct-logs.sh +273 -0
- package/scripts/cct-loop.sh +1332 -0
- package/scripts/cct-memory.sh +1148 -0
- package/scripts/cct-pipeline.sh +3844 -0
- package/scripts/cct-prep.sh +1352 -0
- package/scripts/cct-ps.sh +168 -0
- package/scripts/cct-reaper.sh +390 -0
- package/scripts/cct-session.sh +284 -0
- package/scripts/cct-status.sh +169 -0
- package/scripts/cct-templates.sh +242 -0
- package/scripts/cct-upgrade.sh +422 -0
- package/scripts/cct-worktree.sh +405 -0
- package/scripts/postinstall.mjs +96 -0
- package/templates/pipelines/autonomous.json +71 -0
- package/templates/pipelines/cost-aware.json +95 -0
- package/templates/pipelines/deployed.json +79 -0
- package/templates/pipelines/enterprise.json +114 -0
- package/templates/pipelines/fast.json +63 -0
- package/templates/pipelines/full.json +104 -0
- package/templates/pipelines/hotfix.json +63 -0
- package/templates/pipelines/standard.json +91 -0
- package/tmux/claude-teams-overlay.conf +109 -0
- package/tmux/templates/architecture.json +19 -0
- package/tmux/templates/bug-fix.json +24 -0
- package/tmux/templates/code-review.json +24 -0
- package/tmux/templates/devops.json +19 -0
- package/tmux/templates/documentation.json +19 -0
- package/tmux/templates/exploration.json +19 -0
- package/tmux/templates/feature-dev.json +24 -0
- package/tmux/templates/full-stack.json +24 -0
- package/tmux/templates/migration.json +24 -0
- package/tmux/templates/refactor.json +19 -0
- package/tmux/templates/security-audit.json +24 -0
- package/tmux/templates/testing.json +24 -0
- package/tmux/tmux.conf +167 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright init — One-command tmux setup + optional deploy configuration ║
|
|
4
|
+
# ║ ║
|
|
5
|
+
# ║ Installs tmux config, overlay, and templates. No interactive prompts, ║
|
|
6
|
+
# ║ no hooks, no Claude Code settings — just tmux config. ║
|
|
7
|
+
# ║ ║
|
|
8
|
+
# ║ --deploy Detect platform and generate deployed.json template ║
|
|
9
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
14
|
+
ADAPTERS_DIR="$SCRIPT_DIR/adapters"
|
|
15
|
+
|
|
16
|
+
# ─── Colors ──────────────────────────────────────────────────────────────────
|
|
17
|
+
CYAN='\033[38;2;0;212;255m'
|
|
18
|
+
GREEN='\033[38;2;74;222;128m'
|
|
19
|
+
YELLOW='\033[38;2;250;204;21m'
|
|
20
|
+
RED='\033[38;2;248;113;113m'
|
|
21
|
+
DIM='\033[2m'
|
|
22
|
+
BOLD='\033[1m'
|
|
23
|
+
RESET='\033[0m'
|
|
24
|
+
|
|
25
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
26
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
27
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
28
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
29
|
+
|
|
30
|
+
# ─── Flag parsing ───────────────────────────────────────────────────────────
|
|
31
|
+
DEPLOY_SETUP=false
|
|
32
|
+
DEPLOY_PLATFORM=""
|
|
33
|
+
SKIP_CLAUDE_MD=false
|
|
34
|
+
|
|
35
|
+
while [[ $# -gt 0 ]]; do
|
|
36
|
+
case "$1" in
|
|
37
|
+
--deploy)
|
|
38
|
+
DEPLOY_SETUP=true
|
|
39
|
+
shift
|
|
40
|
+
;;
|
|
41
|
+
--platform)
|
|
42
|
+
DEPLOY_PLATFORM="${2:-}"
|
|
43
|
+
[[ -z "$DEPLOY_PLATFORM" ]] && { error "Missing value for --platform"; exit 1; }
|
|
44
|
+
shift 2
|
|
45
|
+
;;
|
|
46
|
+
--no-claude-md)
|
|
47
|
+
SKIP_CLAUDE_MD=true
|
|
48
|
+
shift
|
|
49
|
+
;;
|
|
50
|
+
--help|-h)
|
|
51
|
+
echo "Usage: shipwright init [--deploy] [--platform vercel|fly|railway|docker] [--no-claude-md]"
|
|
52
|
+
echo ""
|
|
53
|
+
echo "Options:"
|
|
54
|
+
echo " --deploy Detect deploy platform and generate deployed.json"
|
|
55
|
+
echo " --platform PLATFORM Skip detection, use specified platform"
|
|
56
|
+
echo " --no-claude-md Skip creating .claude/CLAUDE.md"
|
|
57
|
+
echo " --help, -h Show this help"
|
|
58
|
+
exit 0
|
|
59
|
+
;;
|
|
60
|
+
*)
|
|
61
|
+
warn "Unknown option: $1"
|
|
62
|
+
shift
|
|
63
|
+
;;
|
|
64
|
+
esac
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
echo ""
|
|
68
|
+
echo -e "${CYAN}${BOLD}shipwright init${RESET} — Quick tmux setup"
|
|
69
|
+
echo -e "${DIM}══════════════════════════════════════════${RESET}"
|
|
70
|
+
echo ""
|
|
71
|
+
|
|
72
|
+
# ─── tmux.conf ────────────────────────────────────────────────────────────────
|
|
73
|
+
if [[ -f "$HOME/.tmux.conf" ]]; then
|
|
74
|
+
cp "$HOME/.tmux.conf" "$HOME/.tmux.conf.bak"
|
|
75
|
+
warn "Backed up existing ~/.tmux.conf → ~/.tmux.conf.bak"
|
|
76
|
+
fi
|
|
77
|
+
cp "$REPO_DIR/tmux/tmux.conf" "$HOME/.tmux.conf"
|
|
78
|
+
success "Installed ~/.tmux.conf"
|
|
79
|
+
|
|
80
|
+
# ─── Overlay ──────────────────────────────────────────────────────────────────
|
|
81
|
+
mkdir -p "$HOME/.tmux"
|
|
82
|
+
cp "$REPO_DIR/tmux/claude-teams-overlay.conf" "$HOME/.tmux/claude-teams-overlay.conf"
|
|
83
|
+
success "Installed ~/.tmux/claude-teams-overlay.conf"
|
|
84
|
+
|
|
85
|
+
# ─── Templates ────────────────────────────────────────────────────────────────
|
|
86
|
+
mkdir -p "$HOME/.claude-teams/templates"
|
|
87
|
+
for tpl in "$REPO_DIR"/tmux/templates/*.json; do
|
|
88
|
+
[[ -f "$tpl" ]] || continue
|
|
89
|
+
cp "$tpl" "$HOME/.claude-teams/templates/$(basename "$tpl")"
|
|
90
|
+
done
|
|
91
|
+
success "Installed templates → ~/.claude-teams/templates/"
|
|
92
|
+
|
|
93
|
+
# ─── CLAUDE.md — Agent instructions ──────────────────────────────────────────
|
|
94
|
+
CLAUDE_MD_SRC="$REPO_DIR/claude-code/CLAUDE.md.shipwright"
|
|
95
|
+
CLAUDE_MD_DST=".claude/CLAUDE.md"
|
|
96
|
+
|
|
97
|
+
if [[ "$SKIP_CLAUDE_MD" == "false" && -f "$CLAUDE_MD_SRC" ]]; then
|
|
98
|
+
if [[ -f "$CLAUDE_MD_DST" ]]; then
|
|
99
|
+
# Check if it already contains Shipwright instructions
|
|
100
|
+
if grep -q "Shipwright" "$CLAUDE_MD_DST" 2>/dev/null; then
|
|
101
|
+
info "CLAUDE.md already contains Shipwright instructions — skipping"
|
|
102
|
+
else
|
|
103
|
+
# Append Shipwright section to existing CLAUDE.md
|
|
104
|
+
{
|
|
105
|
+
echo ""
|
|
106
|
+
echo "---"
|
|
107
|
+
echo ""
|
|
108
|
+
cat "$CLAUDE_MD_SRC"
|
|
109
|
+
} >> "$CLAUDE_MD_DST"
|
|
110
|
+
success "Appended Shipwright instructions to ${CLAUDE_MD_DST}"
|
|
111
|
+
fi
|
|
112
|
+
else
|
|
113
|
+
mkdir -p ".claude"
|
|
114
|
+
cp "$CLAUDE_MD_SRC" "$CLAUDE_MD_DST"
|
|
115
|
+
success "Created ${CLAUDE_MD_DST} with Shipwright agent instructions"
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# ─── Reload tmux if inside a session ──────────────────────────────────────────
|
|
120
|
+
if [[ -n "${TMUX:-}" ]]; then
|
|
121
|
+
tmux source-file "$HOME/.tmux.conf" 2>/dev/null && \
|
|
122
|
+
success "Reloaded tmux config" || \
|
|
123
|
+
warn "Could not reload tmux config (reload manually with prefix + r)"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# ─── Quick-start instructions ─────────────────────────────────────────────────
|
|
127
|
+
echo ""
|
|
128
|
+
echo -e "${BOLD}Done!${RESET} tmux is configured for Claude Code Teams."
|
|
129
|
+
echo ""
|
|
130
|
+
echo -e "${BOLD}Quick start:${RESET}"
|
|
131
|
+
if [[ -z "${TMUX:-}" ]]; then
|
|
132
|
+
echo -e " ${DIM}1.${RESET} tmux new -s dev"
|
|
133
|
+
echo -e " ${DIM}2.${RESET} shipwright session my-feature --template feature-dev"
|
|
134
|
+
else
|
|
135
|
+
echo -e " ${DIM}1.${RESET} shipwright session my-feature --template feature-dev"
|
|
136
|
+
fi
|
|
137
|
+
echo ""
|
|
138
|
+
echo -e "${BOLD}Layout keybindings:${RESET}"
|
|
139
|
+
echo -e " ${CYAN}prefix + M-1${RESET} main-horizontal (leader 65% left)"
|
|
140
|
+
echo -e " ${CYAN}prefix + M-2${RESET} main-vertical (leader 60% top)"
|
|
141
|
+
echo -e " ${CYAN}prefix + M-3${RESET} tiled (equal sizes)"
|
|
142
|
+
echo ""
|
|
143
|
+
|
|
144
|
+
# ─── Deploy setup (--deploy) ─────────────────────────────────────────────────
|
|
145
|
+
[[ "$DEPLOY_SETUP" == "false" ]] && exit 0
|
|
146
|
+
|
|
147
|
+
echo -e "${CYAN}${BOLD}Deploy Setup${RESET}"
|
|
148
|
+
echo -e "${DIM}══════════════════════════════════════════${RESET}"
|
|
149
|
+
echo ""
|
|
150
|
+
|
|
151
|
+
# Platform detection
|
|
152
|
+
detect_deploy_platform() {
|
|
153
|
+
local detected=""
|
|
154
|
+
|
|
155
|
+
for adapter_file in "$ADAPTERS_DIR"/*-deploy.sh; do
|
|
156
|
+
[[ -f "$adapter_file" ]] || continue
|
|
157
|
+
# Source the adapter in a subshell to get detection
|
|
158
|
+
if ( source "$adapter_file" && detect_platform ); then
|
|
159
|
+
local name
|
|
160
|
+
name=$(basename "$adapter_file" | sed 's/-deploy\.sh$//')
|
|
161
|
+
if [[ -n "$detected" ]]; then
|
|
162
|
+
detected="$detected $name"
|
|
163
|
+
else
|
|
164
|
+
detected="$name"
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
done
|
|
168
|
+
|
|
169
|
+
echo "$detected"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if [[ -n "$DEPLOY_PLATFORM" ]]; then
|
|
173
|
+
# User specified --platform, validate it
|
|
174
|
+
if [[ ! -f "$ADAPTERS_DIR/${DEPLOY_PLATFORM}-deploy.sh" ]]; then
|
|
175
|
+
error "Unknown platform: $DEPLOY_PLATFORM"
|
|
176
|
+
echo -e " Available: vercel, fly, railway, docker"
|
|
177
|
+
exit 1
|
|
178
|
+
fi
|
|
179
|
+
info "Using specified platform: ${BOLD}${DEPLOY_PLATFORM}${RESET}"
|
|
180
|
+
else
|
|
181
|
+
info "Detecting deploy platform..."
|
|
182
|
+
detected=$(detect_deploy_platform)
|
|
183
|
+
|
|
184
|
+
if [[ -z "$detected" ]]; then
|
|
185
|
+
warn "No platform detected in current directory"
|
|
186
|
+
echo ""
|
|
187
|
+
echo -e " Supported platforms:"
|
|
188
|
+
echo -e " ${CYAN}vercel${RESET} — vercel.json or .vercel/"
|
|
189
|
+
echo -e " ${CYAN}fly${RESET} — fly.toml"
|
|
190
|
+
echo -e " ${CYAN}railway${RESET} — railway.toml or .railway/"
|
|
191
|
+
echo -e " ${CYAN}docker${RESET} — Dockerfile or docker-compose.yml"
|
|
192
|
+
echo ""
|
|
193
|
+
echo -e " Specify manually: ${DIM}shipwright init --deploy --platform vercel${RESET}"
|
|
194
|
+
exit 1
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# If multiple platforms detected, use the first and warn
|
|
198
|
+
platform_count=$(echo "$detected" | wc -w | tr -d ' ')
|
|
199
|
+
DEPLOY_PLATFORM=$(echo "$detected" | awk '{print $1}')
|
|
200
|
+
|
|
201
|
+
if [[ "$platform_count" -gt 1 ]]; then
|
|
202
|
+
warn "Multiple platforms detected: ${BOLD}${detected}${RESET}"
|
|
203
|
+
info "Using: ${BOLD}${DEPLOY_PLATFORM}${RESET}"
|
|
204
|
+
echo -e " ${DIM}Override with: shipwright init --deploy --platform <name>${RESET}"
|
|
205
|
+
echo ""
|
|
206
|
+
else
|
|
207
|
+
success "Detected platform: ${BOLD}${DEPLOY_PLATFORM}${RESET}"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# Confirm with user
|
|
211
|
+
read -rp "$(echo -e "${CYAN}${BOLD}▸${RESET} Configure deploy for ${BOLD}${DEPLOY_PLATFORM}${RESET}? [Y/n] ")" confirm
|
|
212
|
+
if [[ "${confirm,,}" == "n" ]]; then
|
|
213
|
+
info "Aborted. Use --platform to specify manually."
|
|
214
|
+
exit 0
|
|
215
|
+
fi
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Source the adapter to get command values
|
|
219
|
+
ADAPTER_FILE="$ADAPTERS_DIR/${DEPLOY_PLATFORM}-deploy.sh"
|
|
220
|
+
source "$ADAPTER_FILE"
|
|
221
|
+
|
|
222
|
+
staging_cmd=$(get_staging_cmd)
|
|
223
|
+
production_cmd=$(get_production_cmd)
|
|
224
|
+
rollback_cmd=$(get_rollback_cmd)
|
|
225
|
+
health_url=$(get_health_url)
|
|
226
|
+
smoke_cmd=$(get_smoke_cmd)
|
|
227
|
+
|
|
228
|
+
# Generate deployed.json from template
|
|
229
|
+
TEMPLATE_SRC="$REPO_DIR/templates/pipelines/deployed.json"
|
|
230
|
+
TEMPLATE_DST=".claude/pipeline-templates/deployed.json"
|
|
231
|
+
|
|
232
|
+
if [[ ! -f "$TEMPLATE_SRC" ]]; then
|
|
233
|
+
error "Template not found: $TEMPLATE_SRC"
|
|
234
|
+
exit 1
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
mkdir -p ".claude/pipeline-templates"
|
|
238
|
+
|
|
239
|
+
# Use jq to properly fill in the template values
|
|
240
|
+
jq --arg staging "$staging_cmd" \
|
|
241
|
+
--arg production "$production_cmd" \
|
|
242
|
+
--arg rollback "$rollback_cmd" \
|
|
243
|
+
--arg health "$health_url" \
|
|
244
|
+
--arg smoke "$smoke_cmd" \
|
|
245
|
+
--arg platform "$DEPLOY_PLATFORM" \
|
|
246
|
+
'
|
|
247
|
+
.name = "deployed-" + $platform |
|
|
248
|
+
.description = "Autonomous pipeline with " + $platform + " deploy — generated by shipwright init --deploy" |
|
|
249
|
+
(.stages[] | select(.id == "deploy") | .config) |= {
|
|
250
|
+
staging_cmd: $staging,
|
|
251
|
+
production_cmd: $production,
|
|
252
|
+
rollback_cmd: $rollback
|
|
253
|
+
} |
|
|
254
|
+
(.stages[] | select(.id == "validate") | .config) |= {
|
|
255
|
+
smoke_cmd: $smoke,
|
|
256
|
+
health_url: $health,
|
|
257
|
+
close_issue: true
|
|
258
|
+
} |
|
|
259
|
+
(.stages[] | select(.id == "monitor") | .config) |= (
|
|
260
|
+
.health_url = $health |
|
|
261
|
+
.rollback_cmd = $rollback
|
|
262
|
+
)
|
|
263
|
+
' "$TEMPLATE_SRC" > "$TEMPLATE_DST"
|
|
264
|
+
|
|
265
|
+
success "Generated ${BOLD}${TEMPLATE_DST}${RESET}"
|
|
266
|
+
|
|
267
|
+
echo ""
|
|
268
|
+
echo -e "${BOLD}Deploy configured for ${DEPLOY_PLATFORM}!${RESET}"
|
|
269
|
+
echo ""
|
|
270
|
+
echo -e "${BOLD}Commands configured:${RESET}"
|
|
271
|
+
echo -e " ${DIM}staging:${RESET} $staging_cmd"
|
|
272
|
+
echo -e " ${DIM}production:${RESET} $production_cmd"
|
|
273
|
+
echo -e " ${DIM}rollback:${RESET} $rollback_cmd"
|
|
274
|
+
if [[ -n "$health_url" ]]; then
|
|
275
|
+
echo -e " ${DIM}health:${RESET} $health_url"
|
|
276
|
+
fi
|
|
277
|
+
echo ""
|
|
278
|
+
echo -e "${BOLD}Usage:${RESET}"
|
|
279
|
+
echo -e " ${DIM}shipwright pipeline start --issue 42 --template .claude/pipeline-templates/deployed.json${RESET}"
|
|
280
|
+
echo ""
|
|
281
|
+
echo -e "${DIM}Edit ${TEMPLATE_DST} to customize deploy commands, gates, or thresholds.${RESET}"
|
|
282
|
+
echo ""
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ cct-logs.sh — View and search agent pane logs ║
|
|
4
|
+
# ║ ║
|
|
5
|
+
# ║ Captures tmux pane scrollback and provides log browsing/search. ║
|
|
6
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# ─── Colors ──────────────────────────────────────────────────────────────────
|
|
10
|
+
CYAN='\033[38;2;0;212;255m'
|
|
11
|
+
PURPLE='\033[38;2;124;58;237m'
|
|
12
|
+
BLUE='\033[38;2;0;102;255m'
|
|
13
|
+
GREEN='\033[38;2;74;222;128m'
|
|
14
|
+
YELLOW='\033[38;2;250;204;21m'
|
|
15
|
+
RED='\033[38;2;248;113;113m'
|
|
16
|
+
DIM='\033[2m'
|
|
17
|
+
BOLD='\033[1m'
|
|
18
|
+
RESET='\033[0m'
|
|
19
|
+
|
|
20
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
21
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
22
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
23
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
24
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
25
|
+
|
|
26
|
+
LOGS_DIR="$HOME/.claude-teams/logs"
|
|
27
|
+
|
|
28
|
+
show_usage() {
|
|
29
|
+
echo -e "${CYAN}${BOLD}shipwright logs${RESET} — View agent pane logs"
|
|
30
|
+
echo ""
|
|
31
|
+
echo -e "${BOLD}USAGE${RESET}"
|
|
32
|
+
echo -e " ${CYAN}shipwright logs${RESET} List available log directories"
|
|
33
|
+
echo -e " ${CYAN}shipwright logs${RESET} <team> Show logs for a team (captures live)"
|
|
34
|
+
echo -e " ${CYAN}shipwright logs${RESET} <team> ${DIM}--pane <agent>${RESET} Show specific agent's log"
|
|
35
|
+
echo -e " ${CYAN}shipwright logs${RESET} <team> ${DIM}--follow${RESET} Tail logs in real-time"
|
|
36
|
+
echo -e " ${CYAN}shipwright logs${RESET} <team> ${DIM}--grep <pattern>${RESET} Search logs for a pattern"
|
|
37
|
+
echo -e " ${CYAN}shipwright logs${RESET} ${DIM}--capture${RESET} Capture all team pane scrollback now"
|
|
38
|
+
echo ""
|
|
39
|
+
echo -e "${BOLD}OPTIONS${RESET}"
|
|
40
|
+
echo -e " ${DIM}--pane <name>${RESET} Filter to a specific agent pane by title"
|
|
41
|
+
echo -e " ${DIM}--follow, -f${RESET} Tail the most recent log file"
|
|
42
|
+
echo -e " ${DIM}--grep <pat>${RESET} Search across log files with a pattern"
|
|
43
|
+
echo -e " ${DIM}--capture${RESET} Capture current scrollback from all team panes"
|
|
44
|
+
echo ""
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# ─── Capture scrollback from all claude-* windows ────────────────────────────
|
|
48
|
+
capture_logs() {
|
|
49
|
+
local timestamp
|
|
50
|
+
timestamp="$(date +%Y%m%d-%H%M%S)"
|
|
51
|
+
local captured=0
|
|
52
|
+
|
|
53
|
+
while IFS='|' read -r session_window window_name pane_id pane_title; do
|
|
54
|
+
[[ -z "$window_name" ]] && continue
|
|
55
|
+
echo "$window_name" | grep -qi "claude" || continue
|
|
56
|
+
|
|
57
|
+
# Sanitize names for filesystem
|
|
58
|
+
local safe_window safe_title
|
|
59
|
+
safe_window="$(echo "$window_name" | tr '/' '-')"
|
|
60
|
+
safe_title="$(echo "${pane_title:-pane-$pane_id}" | tr '/' '-')"
|
|
61
|
+
|
|
62
|
+
local log_dir="${LOGS_DIR}/${safe_window}"
|
|
63
|
+
mkdir -p "$log_dir"
|
|
64
|
+
|
|
65
|
+
local log_file="${log_dir}/${safe_title}-${timestamp}.log"
|
|
66
|
+
tmux capture-pane -t "$pane_id" -pS - > "$log_file" 2>/dev/null || continue
|
|
67
|
+
captured=$((captured + 1))
|
|
68
|
+
done < <(tmux list-panes -a -F '#{session_name}:#{window_index}|#{window_name}|#{pane_id}|#{pane_title}' 2>/dev/null || true)
|
|
69
|
+
|
|
70
|
+
if [[ $captured -gt 0 ]]; then
|
|
71
|
+
success "Captured ${captured} pane(s) to ${LOGS_DIR}/"
|
|
72
|
+
else
|
|
73
|
+
warn "No Claude team panes found to capture"
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# ─── List available logs ────────────────────────────────────────────────────
|
|
78
|
+
list_logs() {
|
|
79
|
+
echo ""
|
|
80
|
+
echo -e "${CYAN}${BOLD} Agent Logs${RESET}"
|
|
81
|
+
echo -e "${DIM} ══════════════════════════════════════════${RESET}"
|
|
82
|
+
echo ""
|
|
83
|
+
|
|
84
|
+
if [[ ! -d "$LOGS_DIR" ]]; then
|
|
85
|
+
echo -e " ${DIM}No logs directory yet.${RESET}"
|
|
86
|
+
echo -e " ${DIM}Capture logs with: ${CYAN}shipwright logs --capture${RESET}"
|
|
87
|
+
echo ""
|
|
88
|
+
return
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
local has_logs=false
|
|
92
|
+
while IFS= read -r team_dir; do
|
|
93
|
+
[[ -z "$team_dir" ]] && continue
|
|
94
|
+
has_logs=true
|
|
95
|
+
local team_name
|
|
96
|
+
team_name="$(basename "$team_dir")"
|
|
97
|
+
local file_count
|
|
98
|
+
file_count="$(find "$team_dir" -name '*.log' -type f 2>/dev/null | wc -l | tr -d ' ')"
|
|
99
|
+
local latest=""
|
|
100
|
+
latest="$(find "$team_dir" -name '*.log' -type f -print0 2>/dev/null | xargs -0 ls -t 2>/dev/null | head -1)"
|
|
101
|
+
local latest_time=""
|
|
102
|
+
if [[ -n "$latest" ]]; then
|
|
103
|
+
latest_time="$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' "$latest" 2>/dev/null || stat --format='%y' "$latest" 2>/dev/null | cut -d. -f1)"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
echo -e " ${BLUE}●${RESET} ${BOLD}${team_name}${RESET} ${DIM}${file_count} logs${RESET}"
|
|
107
|
+
if [[ -n "$latest_time" ]]; then
|
|
108
|
+
echo -e " ${DIM}└─ latest: ${latest_time}${RESET}"
|
|
109
|
+
fi
|
|
110
|
+
done < <(find "$LOGS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
|
|
111
|
+
|
|
112
|
+
if ! $has_logs; then
|
|
113
|
+
echo -e " ${DIM}No log directories found.${RESET}"
|
|
114
|
+
echo -e " ${DIM}Capture logs with: ${CYAN}shipwright logs --capture${RESET}"
|
|
115
|
+
fi
|
|
116
|
+
echo ""
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# ─── Show logs for a team ────────────────────────────────────────────────────
|
|
120
|
+
show_team_logs() {
|
|
121
|
+
local team="$1"
|
|
122
|
+
local pane_filter="${2:-}"
|
|
123
|
+
local grep_pattern="${3:-}"
|
|
124
|
+
local follow="${4:-false}"
|
|
125
|
+
|
|
126
|
+
# Try exact match first, then prefix match on claude-*
|
|
127
|
+
local team_dir="${LOGS_DIR}/${team}"
|
|
128
|
+
if [[ ! -d "$team_dir" ]]; then
|
|
129
|
+
team_dir="${LOGS_DIR}/claude-${team}"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
if [[ ! -d "$team_dir" ]]; then
|
|
133
|
+
# Capture live first if no logs exist
|
|
134
|
+
info "No saved logs for '${team}'. Capturing live scrollback..."
|
|
135
|
+
capture_logs
|
|
136
|
+
|
|
137
|
+
# Re-check
|
|
138
|
+
team_dir="${LOGS_DIR}/${team}"
|
|
139
|
+
[[ ! -d "$team_dir" ]] && team_dir="${LOGS_DIR}/claude-${team}"
|
|
140
|
+
if [[ ! -d "$team_dir" ]]; then
|
|
141
|
+
error "No team panes matching '${team}' found"
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
local team_name
|
|
147
|
+
team_name="$(basename "$team_dir")"
|
|
148
|
+
|
|
149
|
+
echo ""
|
|
150
|
+
echo -e "${CYAN}${BOLD} Logs — ${team_name}${RESET}"
|
|
151
|
+
echo -e "${DIM} ══════════════════════════════════════════${RESET}"
|
|
152
|
+
echo ""
|
|
153
|
+
|
|
154
|
+
# Build file list, optionally filtered by pane
|
|
155
|
+
local log_files=()
|
|
156
|
+
while IFS= read -r f; do
|
|
157
|
+
[[ -z "$f" ]] && continue
|
|
158
|
+
if [[ -n "$pane_filter" ]]; then
|
|
159
|
+
local base
|
|
160
|
+
base="$(basename "$f")"
|
|
161
|
+
echo "$base" | grep -qi "$pane_filter" || continue
|
|
162
|
+
fi
|
|
163
|
+
log_files+=("$f")
|
|
164
|
+
done < <(find "$team_dir" -name '*.log' -type f -print0 2>/dev/null | xargs -0 ls -t 2>/dev/null)
|
|
165
|
+
|
|
166
|
+
if [[ ${#log_files[@]} -eq 0 ]]; then
|
|
167
|
+
if [[ -n "$pane_filter" ]]; then
|
|
168
|
+
warn "No logs matching pane '${pane_filter}' in ${team_name}"
|
|
169
|
+
else
|
|
170
|
+
warn "No log files in ${team_name}"
|
|
171
|
+
fi
|
|
172
|
+
return
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# --follow: tail the most recent file
|
|
176
|
+
if [[ "$follow" == "true" ]]; then
|
|
177
|
+
local latest="${log_files[0]}"
|
|
178
|
+
info "Tailing: $(basename "$latest")"
|
|
179
|
+
echo -e "${DIM} (Ctrl+C to stop)${RESET}"
|
|
180
|
+
echo ""
|
|
181
|
+
tail -f "$latest"
|
|
182
|
+
return
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# --grep: search across all files
|
|
186
|
+
if [[ -n "$grep_pattern" ]]; then
|
|
187
|
+
info "Searching for '${grep_pattern}' in ${#log_files[@]} log file(s)..."
|
|
188
|
+
echo ""
|
|
189
|
+
local found=false
|
|
190
|
+
for f in "${log_files[@]}"; do
|
|
191
|
+
local matches
|
|
192
|
+
matches="$(grep -n --color=always "$grep_pattern" "$f" 2>/dev/null || true)"
|
|
193
|
+
if [[ -n "$matches" ]]; then
|
|
194
|
+
found=true
|
|
195
|
+
echo -e " ${BLUE}──${RESET} ${BOLD}$(basename "$f")${RESET}"
|
|
196
|
+
echo "$matches" | while IFS= read -r line; do
|
|
197
|
+
echo -e " ${line}"
|
|
198
|
+
done
|
|
199
|
+
echo ""
|
|
200
|
+
fi
|
|
201
|
+
done
|
|
202
|
+
if ! $found; then
|
|
203
|
+
warn "No matches for '${grep_pattern}'"
|
|
204
|
+
fi
|
|
205
|
+
return
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# Default: list files then show the most recent
|
|
209
|
+
info "${#log_files[@]} log file(s):"
|
|
210
|
+
for f in "${log_files[@]}"; do
|
|
211
|
+
local size
|
|
212
|
+
size="$(wc -l < "$f" | tr -d ' ')"
|
|
213
|
+
echo -e " ${DIM}•${RESET} $(basename "$f") ${DIM}(${size} lines)${RESET}"
|
|
214
|
+
done
|
|
215
|
+
|
|
216
|
+
echo ""
|
|
217
|
+
local latest="${log_files[0]}"
|
|
218
|
+
info "Most recent: ${BOLD}$(basename "$latest")${RESET}"
|
|
219
|
+
echo -e "${DIM} ──────────────────────────────────────────${RESET}"
|
|
220
|
+
cat "$latest"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# ─── Parse Arguments ─────────────────────────────────────────────────────────
|
|
224
|
+
TEAM=""
|
|
225
|
+
PANE=""
|
|
226
|
+
GREP_PATTERN=""
|
|
227
|
+
FOLLOW=false
|
|
228
|
+
DO_CAPTURE=false
|
|
229
|
+
|
|
230
|
+
while [[ $# -gt 0 ]]; do
|
|
231
|
+
case "$1" in
|
|
232
|
+
--help|-h)
|
|
233
|
+
show_usage
|
|
234
|
+
exit 0
|
|
235
|
+
;;
|
|
236
|
+
--capture)
|
|
237
|
+
DO_CAPTURE=true
|
|
238
|
+
shift
|
|
239
|
+
;;
|
|
240
|
+
--pane)
|
|
241
|
+
PANE="${2:-}"
|
|
242
|
+
[[ -z "$PANE" ]] && { error "--pane requires an agent name"; exit 1; }
|
|
243
|
+
shift 2
|
|
244
|
+
;;
|
|
245
|
+
--follow|-f)
|
|
246
|
+
FOLLOW=true
|
|
247
|
+
shift
|
|
248
|
+
;;
|
|
249
|
+
--grep)
|
|
250
|
+
GREP_PATTERN="${2:-}"
|
|
251
|
+
[[ -z "$GREP_PATTERN" ]] && { error "--grep requires a pattern"; exit 1; }
|
|
252
|
+
shift 2
|
|
253
|
+
;;
|
|
254
|
+
-*)
|
|
255
|
+
error "Unknown option: $1"
|
|
256
|
+
show_usage
|
|
257
|
+
exit 1
|
|
258
|
+
;;
|
|
259
|
+
*)
|
|
260
|
+
TEAM="$1"
|
|
261
|
+
shift
|
|
262
|
+
;;
|
|
263
|
+
esac
|
|
264
|
+
done
|
|
265
|
+
|
|
266
|
+
# ─── Dispatch ────────────────────────────────────────────────────────────────
|
|
267
|
+
if $DO_CAPTURE; then
|
|
268
|
+
capture_logs
|
|
269
|
+
elif [[ -z "$TEAM" ]]; then
|
|
270
|
+
list_logs
|
|
271
|
+
else
|
|
272
|
+
show_team_logs "$TEAM" "$PANE" "$GREP_PATTERN" "$FOLLOW"
|
|
273
|
+
fi
|