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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +926 -0
  3. package/claude-code/CLAUDE.md.shipwright +125 -0
  4. package/claude-code/hooks/notify-idle.sh +35 -0
  5. package/claude-code/hooks/pre-compact-save.sh +57 -0
  6. package/claude-code/hooks/task-completed.sh +170 -0
  7. package/claude-code/hooks/teammate-idle.sh +68 -0
  8. package/claude-code/settings.json.template +184 -0
  9. package/completions/_shipwright +140 -0
  10. package/completions/shipwright.bash +89 -0
  11. package/completions/shipwright.fish +107 -0
  12. package/docs/KNOWN-ISSUES.md +199 -0
  13. package/docs/TIPS.md +331 -0
  14. package/docs/definition-of-done.example.md +16 -0
  15. package/docs/patterns/README.md +139 -0
  16. package/docs/patterns/audit-loop.md +149 -0
  17. package/docs/patterns/bug-hunt.md +183 -0
  18. package/docs/patterns/feature-implementation.md +159 -0
  19. package/docs/patterns/refactoring.md +183 -0
  20. package/docs/patterns/research-exploration.md +144 -0
  21. package/docs/patterns/test-generation.md +173 -0
  22. package/package.json +49 -0
  23. package/scripts/adapters/docker-deploy.sh +50 -0
  24. package/scripts/adapters/fly-deploy.sh +41 -0
  25. package/scripts/adapters/iterm2-adapter.sh +122 -0
  26. package/scripts/adapters/railway-deploy.sh +34 -0
  27. package/scripts/adapters/tmux-adapter.sh +87 -0
  28. package/scripts/adapters/vercel-deploy.sh +35 -0
  29. package/scripts/adapters/wezterm-adapter.sh +103 -0
  30. package/scripts/cct +242 -0
  31. package/scripts/cct-cleanup.sh +172 -0
  32. package/scripts/cct-cost.sh +590 -0
  33. package/scripts/cct-daemon.sh +3189 -0
  34. package/scripts/cct-doctor.sh +328 -0
  35. package/scripts/cct-fix.sh +478 -0
  36. package/scripts/cct-fleet.sh +904 -0
  37. package/scripts/cct-init.sh +282 -0
  38. package/scripts/cct-logs.sh +273 -0
  39. package/scripts/cct-loop.sh +1332 -0
  40. package/scripts/cct-memory.sh +1148 -0
  41. package/scripts/cct-pipeline.sh +3844 -0
  42. package/scripts/cct-prep.sh +1352 -0
  43. package/scripts/cct-ps.sh +168 -0
  44. package/scripts/cct-reaper.sh +390 -0
  45. package/scripts/cct-session.sh +284 -0
  46. package/scripts/cct-status.sh +169 -0
  47. package/scripts/cct-templates.sh +242 -0
  48. package/scripts/cct-upgrade.sh +422 -0
  49. package/scripts/cct-worktree.sh +405 -0
  50. package/scripts/postinstall.mjs +96 -0
  51. package/templates/pipelines/autonomous.json +71 -0
  52. package/templates/pipelines/cost-aware.json +95 -0
  53. package/templates/pipelines/deployed.json +79 -0
  54. package/templates/pipelines/enterprise.json +114 -0
  55. package/templates/pipelines/fast.json +63 -0
  56. package/templates/pipelines/full.json +104 -0
  57. package/templates/pipelines/hotfix.json +63 -0
  58. package/templates/pipelines/standard.json +91 -0
  59. package/tmux/claude-teams-overlay.conf +109 -0
  60. package/tmux/templates/architecture.json +19 -0
  61. package/tmux/templates/bug-fix.json +24 -0
  62. package/tmux/templates/code-review.json +24 -0
  63. package/tmux/templates/devops.json +19 -0
  64. package/tmux/templates/documentation.json +19 -0
  65. package/tmux/templates/exploration.json +19 -0
  66. package/tmux/templates/feature-dev.json +24 -0
  67. package/tmux/templates/full-stack.json +24 -0
  68. package/tmux/templates/migration.json +24 -0
  69. package/tmux/templates/refactor.json +19 -0
  70. package/tmux/templates/security-audit.json +24 -0
  71. package/tmux/templates/testing.json +24 -0
  72. package/tmux/tmux.conf +167 -0
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ wezterm-adapter.sh — Terminal adapter for WezTerm pane management ║
4
+ # ║ ║
5
+ # ║ Uses `wezterm cli` to spawn panes/tabs with named titles and working ║
6
+ # ║ directories. Cross-platform. ║
7
+ # ║ Sourced by cct-session.sh — exports: spawn_agent, list_agents, ║
8
+ # ║ kill_agent, focus_agent. ║
9
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
10
+
11
+ # Verify wezterm CLI is available
12
+ if ! command -v wezterm &>/dev/null; then
13
+ echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m wezterm CLI not found. Install WezTerm first." >&2
14
+ exit 1
15
+ fi
16
+
17
+ # Track spawned pane IDs for agent management
18
+ declare -A _WEZTERM_AGENT_PANES
19
+
20
+ spawn_agent() {
21
+ local name="$1"
22
+ local working_dir="${2:-$PWD}"
23
+ local command="${3:-}"
24
+
25
+ # Resolve working_dir — tmux format won't work here
26
+ if [[ "$working_dir" == *"pane_current_path"* || "$working_dir" == "." ]]; then
27
+ working_dir="$PWD"
28
+ fi
29
+
30
+ local pane_id
31
+
32
+ # Spawn a new pane in the current tab (split right by default)
33
+ if [[ ${#_WEZTERM_AGENT_PANES[@]} -eq 0 ]]; then
34
+ # First agent: create a new tab
35
+ pane_id=$(wezterm cli spawn --cwd "$working_dir" 2>/dev/null)
36
+ else
37
+ # Subsequent agents: split from the first pane
38
+ local first_pane="${_WEZTERM_AGENT_PANES[${!_WEZTERM_AGENT_PANES[*]}]}"
39
+ pane_id=$(wezterm cli split-pane --cwd "$working_dir" --right --pane-id "${first_pane:-0}" 2>/dev/null) || \
40
+ pane_id=$(wezterm cli split-pane --cwd "$working_dir" --bottom 2>/dev/null)
41
+ fi
42
+
43
+ if [[ -z "$pane_id" ]]; then
44
+ echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m Failed to spawn WezTerm pane for '${name}'." >&2
45
+ return 1
46
+ fi
47
+
48
+ # Store mapping
49
+ _WEZTERM_AGENT_PANES["$name"]="$pane_id"
50
+
51
+ # Set the pane title
52
+ wezterm cli set-tab-title --pane-id "$pane_id" "$name" 2>/dev/null || true
53
+
54
+ # Clear the pane
55
+ wezterm cli send-text --pane-id "$pane_id" -- "clear" 2>/dev/null
56
+ wezterm cli send-text --pane-id "$pane_id" --no-paste $'\n' 2>/dev/null || true
57
+
58
+ # Run the command if provided
59
+ if [[ -n "$command" ]]; then
60
+ sleep 0.2
61
+ wezterm cli send-text --pane-id "$pane_id" -- "$command" 2>/dev/null
62
+ wezterm cli send-text --pane-id "$pane_id" --no-paste $'\n' 2>/dev/null || true
63
+ fi
64
+ }
65
+
66
+ list_agents() {
67
+ # List panes via wezterm CLI
68
+ wezterm cli list 2>/dev/null | while IFS=$'\t' read -r pane_id title workspace rest; do
69
+ echo "${pane_id}: ${title}"
70
+ done
71
+
72
+ # Also show our tracked agents
73
+ if [[ ${#_WEZTERM_AGENT_PANES[@]} -gt 0 ]]; then
74
+ echo ""
75
+ echo "Tracked agents:"
76
+ for name in "${!_WEZTERM_AGENT_PANES[@]}"; do
77
+ echo " ${name} → pane ${_WEZTERM_AGENT_PANES[$name]}"
78
+ done
79
+ fi
80
+ }
81
+
82
+ kill_agent() {
83
+ local name="$1"
84
+ local pane_id="${_WEZTERM_AGENT_PANES[$name]:-}"
85
+
86
+ if [[ -z "$pane_id" ]]; then
87
+ return 1
88
+ fi
89
+
90
+ wezterm cli kill-pane --pane-id "$pane_id" 2>/dev/null
91
+ unset '_WEZTERM_AGENT_PANES[$name]'
92
+ }
93
+
94
+ focus_agent() {
95
+ local name="$1"
96
+ local pane_id="${_WEZTERM_AGENT_PANES[$name]:-}"
97
+
98
+ if [[ -z "$pane_id" ]]; then
99
+ return 1
100
+ fi
101
+
102
+ wezterm cli activate-pane --pane-id "$pane_id" 2>/dev/null
103
+ }
package/scripts/cct ADDED
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ Shipwright CLI (cct) ║
4
+ # ║ Orchestrate autonomous Claude Code agent teams in tmux ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+
8
+ VERSION="1.7.0"
9
+
10
+ # Resolve symlinks (required for npm global install where bin/ symlinks to node_modules/)
11
+ SOURCE="${BASH_SOURCE[0]}"
12
+ while [[ -L "$SOURCE" ]]; do
13
+ DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
14
+ SOURCE="$(readlink "$SOURCE")"
15
+ [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
16
+ done
17
+ SCRIPT_DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
18
+
19
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
20
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
21
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
22
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
23
+ GREEN='\033[38;2;74;222;128m' # success
24
+ YELLOW='\033[38;2;250;204;21m' # warning
25
+ RED='\033[38;2;248;113;113m' # error
26
+ DIM='\033[2m'
27
+ BOLD='\033[1m'
28
+ RESET='\033[0m'
29
+
30
+ # ─── Helpers ─────────────────────────────────────────────────────────────────
31
+
32
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
33
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
34
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
35
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
36
+
37
+ check_tmux() {
38
+ if ! command -v tmux &>/dev/null; then
39
+ error "tmux is not installed. Install it first:"
40
+ echo -e " ${DIM}brew install tmux${RESET} (macOS)"
41
+ echo -e " ${DIM}sudo apt install tmux${RESET} (Ubuntu/Debian)"
42
+ exit 1
43
+ fi
44
+ }
45
+
46
+ check_in_tmux() {
47
+ if [[ -z "${TMUX:-}" ]]; then
48
+ warn "Not inside a tmux session."
49
+ echo -e " Start one with: ${DIM}tmux new -s work${RESET}"
50
+ echo -e " Or attach: ${DIM}tmux attach${RESET}"
51
+ exit 1
52
+ fi
53
+ }
54
+
55
+ show_version() {
56
+ echo -e "${CYAN}${BOLD}shipwright${RESET} ${DIM}v${VERSION}${RESET} — Orchestrate AI Coding Teams ${DIM}(aliases: sw, cct)${RESET}"
57
+ }
58
+
59
+ show_help() {
60
+ show_version
61
+ echo ""
62
+ echo -e "${BOLD}USAGE${RESET}"
63
+ echo -e " ${CYAN}shipwright${RESET} <command> [options] ${DIM}(also: sw, cct)${RESET}"
64
+ echo ""
65
+ echo -e "${BOLD}COMMANDS${RESET}"
66
+ echo -e " ${CYAN}session${RESET} [name] Create a new tmux window for a Claude team"
67
+ echo -e " ${CYAN}status${RESET} Show dashboard of running teams and agents"
68
+ echo -e " ${CYAN}ps${RESET} Show running agent processes and status"
69
+ echo -e " ${CYAN}logs${RESET} [team] [opts] View and search agent pane logs"
70
+ echo -e " ${CYAN}templates${RESET} [list|show] Manage team composition templates"
71
+ echo -e " ${CYAN}doctor${RESET} Validate your setup and check for issues"
72
+ echo -e " ${CYAN}cleanup${RESET} [--force] Clean up orphaned team sessions"
73
+ echo -e " ${CYAN}reaper${RESET} [--watch] Automatic pane cleanup when agents exit"
74
+ echo -e " ${CYAN}upgrade${RESET} [--apply] Check for updates from the repo (dry-run by default)"
75
+ echo -e " ${CYAN}loop${RESET} \"goal\" [opts] ${BOLD}Continuous agent loop${RESET} — run until goal is achieved"
76
+ echo -e " ${CYAN}pipeline${RESET} <cmd> ${BOLD}Full delivery pipeline${RESET} — idea to production"
77
+ echo -e " ${CYAN}worktree${RESET} <cmd> Manage git worktrees for agent isolation"
78
+ echo -e " ${CYAN}prep${RESET} [options] ${BOLD}Repo preparation${RESET} — generate .claude/ configs for agents"
79
+ echo -e " ${CYAN}daemon${RESET} <cmd> ${BOLD}Issue watcher${RESET} — auto-process GitHub issues"
80
+ echo -e " ${CYAN}memory${RESET} <cmd> ${BOLD}Persistent memory${RESET} — learn from every pipeline run"
81
+ echo -e " ${CYAN}cost${RESET} <cmd> ${BOLD}Cost intelligence${RESET} — track tokens, budgets, model routing"
82
+ echo -e " ${CYAN}fleet${RESET} <cmd> ${BOLD}Multi-repo fleet${RESET} — orchestrate daemons across repos"
83
+ echo -e " ${CYAN}fix${RESET} \"goal\" [opts] ${BOLD}Bulk fix${RESET} — apply a fix across multiple repos"
84
+ echo -e " ${CYAN}init${RESET} ${BOLD}Quick tmux setup${RESET} — one command, no prompts"
85
+ echo -e " ${CYAN}help${RESET} Show this help message"
86
+ echo -e " ${CYAN}version${RESET} Show version"
87
+ echo ""
88
+ echo -e "${BOLD}CONTINUOUS LOOP${RESET} ${DIM}(autonomous agent operation)${RESET}"
89
+ echo -e " ${DIM}shipwright loop \"Build auth\" --test-cmd \"npm test\"${RESET}"
90
+ echo -e " ${DIM}shipwright loop \"Fix bugs\" --max-iterations 10 --model sonnet${RESET}"
91
+ echo -e " ${DIM}shipwright loop \"Build API\" --agents 3 --skip-permissions${RESET}"
92
+ echo -e " ${DIM}shipwright loop --resume${RESET} # Resume interrupted loop"
93
+ echo ""
94
+ echo -e "${BOLD}DELIVERY PIPELINE${RESET} ${DIM}(idea to production)${RESET}"
95
+ echo -e " ${DIM}shipwright pipeline start --goal \"Add auth\" --pipeline standard${RESET}"
96
+ echo -e " ${DIM}shipwright pipeline start --issue 123 --skip-gates${RESET}"
97
+ echo -e " ${DIM}shipwright pipeline resume${RESET} # Resume from last stage"
98
+ echo -e " ${DIM}shipwright pipeline status${RESET} # Show progress dashboard"
99
+ echo ""
100
+ echo -e "${BOLD}AUTONOMOUS DAEMON${RESET} ${DIM}(watch GitHub, auto-deliver)${RESET}"
101
+ echo -e " ${DIM}shipwright daemon start${RESET} # Start issue watcher (foreground)"
102
+ echo -e " ${DIM}shipwright daemon start --detach${RESET} # Start in background tmux session"
103
+ echo -e " ${DIM}shipwright daemon status${RESET} # Show active pipelines and queue"
104
+ echo -e " ${DIM}shipwright daemon metrics${RESET} # DORA/DX metrics dashboard"
105
+ echo -e " ${DIM}shipwright daemon stop${RESET} # Graceful shutdown"
106
+ echo ""
107
+ echo -e "${BOLD}MEMORY & COST${RESET} ${DIM}(intelligence layer)${RESET}"
108
+ echo -e " ${DIM}shipwright memory show${RESET} # Show learned patterns for this repo"
109
+ echo -e " ${DIM}shipwright memory search \"auth\"${RESET} # Search across all memories"
110
+ echo -e " ${DIM}shipwright memory stats${RESET} # Memory usage and coverage"
111
+ echo -e " ${DIM}shipwright cost show${RESET} # Token usage and spend overview"
112
+ echo -e " ${DIM}shipwright cost budget set 10.00${RESET} # Set daily budget"
113
+ echo -e " ${DIM}shipwright cost show --period 30 --by-stage${RESET} # Detailed cost report"
114
+ echo ""
115
+ echo -e "${BOLD}FLEET OPERATIONS${RESET} ${DIM}(multi-repo management)${RESET}"
116
+ echo -e " ${DIM}shipwright fleet start${RESET} # Start daemons for all configured repos"
117
+ echo -e " ${DIM}shipwright fleet status${RESET} # Show fleet-wide status"
118
+ echo -e " ${DIM}shipwright fleet metrics${RESET} # Cross-repo DORA metrics"
119
+ echo -e " ${DIM}shipwright fix \"Bump deps\" --repos ~/api,~/web${RESET} # Bulk fix"
120
+ echo ""
121
+ echo -e "${BOLD}REPO PREPARATION${RESET} ${DIM}(prep repo for agents)${RESET}"
122
+ echo -e " ${DIM}shipwright prep${RESET} # Analyze repo, generate .claude/ configs"
123
+ echo -e " ${DIM}shipwright prep --check${RESET} # Audit existing prep quality"
124
+ echo -e " ${DIM}shipwright prep --with-claude${RESET} # Deep analysis using Claude Code"
125
+ echo ""
126
+ echo -e "${BOLD}TEST SUITES${RESET} ${DIM}(validate shipwright components)${RESET}"
127
+ echo -e " ${DIM}shipwright daemon test${RESET} # Run daemon test suite"
128
+ echo -e " ${DIM}shipwright prep test${RESET} # Run prep test suite"
129
+ echo -e " ${DIM}shipwright pipeline test${RESET} # Run pipeline test suite"
130
+ echo ""
131
+ echo -e "${BOLD}AUDIT & QUALITY${RESET} ${DIM}(loop guardrails)${RESET}"
132
+ echo -e " ${DIM}shipwright loop \"goal\" --audit${RESET} # Self-reflection each iteration"
133
+ echo -e " ${DIM}shipwright loop \"goal\" --audit-agent${RESET} # Separate auditor reviews work"
134
+ echo -e " ${DIM}shipwright loop \"goal\" --quality-gates${RESET} # Automated quality checks"
135
+ echo -e " ${DIM}shipwright loop \"goal\" --definition-of-done dod.md${RESET} # Custom completion checklist"
136
+ echo ""
137
+ echo -e "${BOLD}TMUX KEYBINDINGS${RESET} ${DIM}(with the bundled tmux.conf)${RESET}"
138
+ echo -e " ${PURPLE}prefix + T${RESET} Launch a team session (runs ${DIM}shipwright session${RESET})"
139
+ echo -e " ${PURPLE}prefix + Ctrl-t${RESET} Show team dashboard (runs ${DIM}shipwright status${RESET})"
140
+ echo ""
141
+ echo -e "${BOLD}EXAMPLES${RESET}"
142
+ echo -e " ${DIM}shipwright session refactor${RESET} # New window named 'claude-refactor'"
143
+ echo -e " ${DIM}shipwright session${RESET} # New window with timestamp name"
144
+ echo -e " ${DIM}shipwright status${RESET} # See all active teams"
145
+ echo -e " ${DIM}shipwright ps${RESET} # Show agent processes"
146
+ echo -e " ${DIM}shipwright logs myteam${RESET} # View logs for a team"
147
+ echo -e " ${DIM}shipwright logs myteam --follow${RESET} # Tail agent logs"
148
+ echo -e " ${DIM}shipwright doctor${RESET} # Check setup health"
149
+ echo -e " ${DIM}shipwright cleanup${RESET} # Dry-run: show what would be cleaned"
150
+ echo -e " ${DIM}shipwright cleanup --force${RESET} # Actually kill orphaned sessions"
151
+ echo -e " ${DIM}shipwright upgrade${RESET} # Check what's changed since install"
152
+ echo -e " ${DIM}shipwright upgrade --apply${RESET} # Apply available updates"
153
+ echo ""
154
+ echo -e "${DIM}Docs: https://sethdford.github.io/shipwright | GitHub: https://github.com/sethdford/shipwright${RESET}"
155
+ }
156
+
157
+ # ─── Command Router ──────────────────────────────────────────────────────────
158
+
159
+ main() {
160
+ local cmd="${1:-help}"
161
+ shift 2>/dev/null || true
162
+
163
+ case "$cmd" in
164
+ session)
165
+ check_tmux
166
+ check_in_tmux
167
+ exec "$SCRIPT_DIR/cct-session.sh" "$@"
168
+ ;;
169
+ status)
170
+ check_tmux
171
+ exec "$SCRIPT_DIR/cct-status.sh" "$@"
172
+ ;;
173
+ ps)
174
+ check_tmux
175
+ exec "$SCRIPT_DIR/cct-ps.sh" "$@"
176
+ ;;
177
+ logs)
178
+ exec "$SCRIPT_DIR/cct-logs.sh" "$@"
179
+ ;;
180
+ templates)
181
+ exec "$SCRIPT_DIR/cct-templates.sh" "$@"
182
+ ;;
183
+ doctor)
184
+ exec "$SCRIPT_DIR/cct-doctor.sh" "$@"
185
+ ;;
186
+ cleanup)
187
+ check_tmux
188
+ exec "$SCRIPT_DIR/cct-cleanup.sh" "$@"
189
+ ;;
190
+ reaper)
191
+ check_tmux
192
+ exec "$SCRIPT_DIR/cct-reaper.sh" "$@"
193
+ ;;
194
+ upgrade)
195
+ exec "$SCRIPT_DIR/cct-upgrade.sh" "$@"
196
+ ;;
197
+ loop)
198
+ exec "$SCRIPT_DIR/cct-loop.sh" "$@"
199
+ ;;
200
+ pipeline)
201
+ exec "$SCRIPT_DIR/cct-pipeline.sh" "$@"
202
+ ;;
203
+ worktree)
204
+ exec "$SCRIPT_DIR/cct-worktree.sh" "$@"
205
+ ;;
206
+ prep)
207
+ exec "$SCRIPT_DIR/cct-prep.sh" "$@"
208
+ ;;
209
+ daemon)
210
+ exec "$SCRIPT_DIR/cct-daemon.sh" "$@"
211
+ ;;
212
+ memory)
213
+ exec "$SCRIPT_DIR/cct-memory.sh" "$@"
214
+ ;;
215
+ cost)
216
+ exec "$SCRIPT_DIR/cct-cost.sh" "$@"
217
+ ;;
218
+ fleet)
219
+ exec "$SCRIPT_DIR/cct-fleet.sh" "$@"
220
+ ;;
221
+ fix)
222
+ exec "$SCRIPT_DIR/cct-fix.sh" "$@"
223
+ ;;
224
+ init)
225
+ exec "$SCRIPT_DIR/cct-init.sh" "$@"
226
+ ;;
227
+ help|--help|-h)
228
+ show_help
229
+ ;;
230
+ version|--version|-v)
231
+ show_version
232
+ ;;
233
+ *)
234
+ error "Unknown command: ${cmd}"
235
+ echo ""
236
+ show_help
237
+ exit 1
238
+ ;;
239
+ esac
240
+ }
241
+
242
+ main "$@"
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ cct-cleanup.sh — Clean up orphaned Claude team sessions ║
4
+ # ║ ║
5
+ # ║ Default: dry-run (shows what would be cleaned). ║
6
+ # ║ Use --force to actually kill sessions and remove files. ║
7
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
8
+ set -euo pipefail
9
+
10
+ # ─── Colors ──────────────────────────────────────────────────────────────────
11
+ CYAN='\033[38;2;0;212;255m'
12
+ PURPLE='\033[38;2;124;58;237m'
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
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
21
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
22
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
23
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
24
+
25
+ # ─── Parse Args ──────────────────────────────────────────────────────────────
26
+
27
+ FORCE=false
28
+ for arg in "$@"; do
29
+ case "$arg" in
30
+ --force|-f) FORCE=true ;;
31
+ --help|-h)
32
+ echo -e "${CYAN}${BOLD}shipwright cleanup${RESET} — Clean up orphaned Claude team sessions"
33
+ echo ""
34
+ echo -e "${BOLD}USAGE${RESET}"
35
+ echo -e " shipwright cleanup ${DIM}# Dry-run: show what would be cleaned${RESET}"
36
+ echo -e " shipwright cleanup --force ${DIM}# Actually kill sessions and remove files${RESET}"
37
+ exit 0
38
+ ;;
39
+ *)
40
+ error "Unknown option: ${arg}"
41
+ exit 1
42
+ ;;
43
+ esac
44
+ done
45
+
46
+ # ─── Track cleanup stats ────────────────────────────────────────────────────
47
+
48
+ WINDOWS_FOUND=0
49
+ WINDOWS_KILLED=0
50
+ TEAM_DIRS_FOUND=0
51
+ TEAM_DIRS_REMOVED=0
52
+ TASK_DIRS_FOUND=0
53
+ TASK_DIRS_REMOVED=0
54
+
55
+ # ─── 1. Find orphaned tmux windows ──────────────────────────────────────────
56
+
57
+ echo ""
58
+ if $FORCE; then
59
+ info "Cleaning up Claude team sessions ${RED}${BOLD}(FORCE MODE)${RESET}"
60
+ else
61
+ info "Scanning for orphaned Claude team sessions ${DIM}(dry-run)${RESET}"
62
+ fi
63
+ echo ""
64
+
65
+ echo -e "${BOLD}Tmux Windows${RESET}"
66
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
67
+
68
+ # Look for windows with "claude" in the name across all sessions
69
+ CLAUDE_WINDOWS=()
70
+ while IFS= read -r line; do
71
+ [[ -n "$line" ]] && CLAUDE_WINDOWS+=("$line")
72
+ done < <(tmux list-windows -a -F '#{session_name}:#{window_index} #{window_name}' 2>/dev/null | grep -i "claude" || true)
73
+
74
+ if [[ ${#CLAUDE_WINDOWS[@]} -eq 0 ]]; then
75
+ echo -e " ${DIM}No Claude team windows found.${RESET}"
76
+ else
77
+ for win in "${CLAUDE_WINDOWS[@]}"; do
78
+ WINDOWS_FOUND=$((WINDOWS_FOUND + 1))
79
+ local_target="$(echo "$win" | cut -d' ' -f1)"
80
+ local_name="$(echo "$win" | cut -d' ' -f2-)"
81
+
82
+ if $FORCE; then
83
+ tmux kill-window -t "$local_target" 2>/dev/null && {
84
+ echo -e " ${RED}✗${RESET} Killed: ${local_name} ${DIM}(${local_target})${RESET}"
85
+ WINDOWS_KILLED=$((WINDOWS_KILLED + 1))
86
+ } || {
87
+ warn " Could not kill: ${local_name} (${local_target})"
88
+ }
89
+ else
90
+ echo -e " ${YELLOW}○${RESET} Would kill: ${local_name} ${DIM}(${local_target})${RESET}"
91
+ fi
92
+ done
93
+ fi
94
+
95
+ # ─── 2. Clean up ~/.claude/teams/ ───────────────────────────────────────────
96
+
97
+ echo ""
98
+ echo -e "${BOLD}Team Configs${RESET} ${DIM}~/.claude/teams/${RESET}"
99
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
100
+
101
+ TEAMS_DIR="${HOME}/.claude/teams"
102
+ if [[ -d "$TEAMS_DIR" ]]; then
103
+ while IFS= read -r team_dir; do
104
+ [[ -z "$team_dir" ]] && continue
105
+ TEAM_DIRS_FOUND=$((TEAM_DIRS_FOUND + 1))
106
+ team_name="$(basename "$team_dir")"
107
+
108
+ if $FORCE; then
109
+ rm -rf "$team_dir" && {
110
+ echo -e " ${RED}✗${RESET} Removed: ${team_name}/"
111
+ TEAM_DIRS_REMOVED=$((TEAM_DIRS_REMOVED + 1))
112
+ }
113
+ else
114
+ # Count files inside
115
+ file_count=$(find "$team_dir" -type f 2>/dev/null | wc -l | tr -d ' ')
116
+ echo -e " ${YELLOW}○${RESET} Would remove: ${team_name}/ ${DIM}(${file_count} files)${RESET}"
117
+ fi
118
+ done < <(find "$TEAMS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
119
+ else
120
+ echo -e " ${DIM}No team configs found.${RESET}"
121
+ fi
122
+
123
+ # ─── 3. Clean up ~/.claude/tasks/ ───────────────────────────────────────────
124
+
125
+ echo ""
126
+ echo -e "${BOLD}Task Lists${RESET} ${DIM}~/.claude/tasks/${RESET}"
127
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
128
+
129
+ TASKS_DIR="${HOME}/.claude/tasks"
130
+ if [[ -d "$TASKS_DIR" ]]; then
131
+ while IFS= read -r task_dir; do
132
+ [[ -z "$task_dir" ]] && continue
133
+ TASK_DIRS_FOUND=$((TASK_DIRS_FOUND + 1))
134
+ task_name="$(basename "$task_dir")"
135
+
136
+ if $FORCE; then
137
+ rm -rf "$task_dir" && {
138
+ echo -e " ${RED}✗${RESET} Removed: ${task_name}/"
139
+ TASK_DIRS_REMOVED=$((TASK_DIRS_REMOVED + 1))
140
+ }
141
+ else
142
+ task_count=$(find "$task_dir" -type f -name '*.json' 2>/dev/null | wc -l | tr -d ' ')
143
+ echo -e " ${YELLOW}○${RESET} Would remove: ${task_name}/ ${DIM}(${task_count} tasks)${RESET}"
144
+ fi
145
+ done < <(find "$TASKS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
146
+ else
147
+ echo -e " ${DIM}No task directories found.${RESET}"
148
+ fi
149
+
150
+ # ─── Summary ─────────────────────────────────────────────────────────────────
151
+
152
+ echo ""
153
+ echo -e "${DIM}────────────────────────────────────────${RESET}"
154
+
155
+ TOTAL_FOUND=$((WINDOWS_FOUND + TEAM_DIRS_FOUND + TASK_DIRS_FOUND))
156
+
157
+ if $FORCE; then
158
+ TOTAL_CLEANED=$((WINDOWS_KILLED + TEAM_DIRS_REMOVED + TASK_DIRS_REMOVED))
159
+ if [[ $TOTAL_CLEANED -gt 0 ]]; then
160
+ success "Cleaned ${TOTAL_CLEANED} items (${WINDOWS_KILLED} windows, ${TEAM_DIRS_REMOVED} team dirs, ${TASK_DIRS_REMOVED} task dirs)"
161
+ else
162
+ success "Nothing to clean up."
163
+ fi
164
+ else
165
+ if [[ $TOTAL_FOUND -gt 0 ]]; then
166
+ warn "Found ${TOTAL_FOUND} items to clean. Run with ${BOLD}--force${RESET} to remove them:"
167
+ echo -e " ${DIM}shipwright cleanup --force${RESET}"
168
+ else
169
+ success "Everything is clean. No orphaned sessions found."
170
+ fi
171
+ fi
172
+ echo ""