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.
Files changed (105) hide show
  1. package/.claude/agents/code-reviewer.md +90 -0
  2. package/.claude/agents/devops-engineer.md +142 -0
  3. package/.claude/agents/pipeline-agent.md +80 -0
  4. package/.claude/agents/shell-script-specialist.md +150 -0
  5. package/.claude/agents/test-specialist.md +196 -0
  6. package/.claude/hooks/post-tool-use.sh +38 -0
  7. package/.claude/hooks/pre-tool-use.sh +25 -0
  8. package/.claude/hooks/session-started.sh +37 -0
  9. package/README.md +212 -814
  10. package/claude-code/CLAUDE.md.shipwright +54 -0
  11. package/claude-code/hooks/notify-idle.sh +2 -2
  12. package/claude-code/hooks/session-start.sh +24 -0
  13. package/claude-code/hooks/task-completed.sh +6 -2
  14. package/claude-code/settings.json.template +12 -0
  15. package/dashboard/public/app.js +4422 -0
  16. package/dashboard/public/index.html +816 -0
  17. package/dashboard/public/styles.css +4755 -0
  18. package/dashboard/server.ts +4315 -0
  19. package/docs/KNOWN-ISSUES.md +18 -10
  20. package/docs/TIPS.md +38 -26
  21. package/docs/patterns/README.md +33 -23
  22. package/package.json +9 -5
  23. package/scripts/adapters/iterm2-adapter.sh +1 -1
  24. package/scripts/adapters/tmux-adapter.sh +52 -23
  25. package/scripts/adapters/wezterm-adapter.sh +26 -14
  26. package/scripts/lib/compat.sh +200 -0
  27. package/scripts/lib/helpers.sh +72 -0
  28. package/scripts/postinstall.mjs +72 -13
  29. package/scripts/{cct → sw} +109 -21
  30. package/scripts/sw-adversarial.sh +274 -0
  31. package/scripts/sw-architecture-enforcer.sh +330 -0
  32. package/scripts/sw-checkpoint.sh +390 -0
  33. package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
  34. package/scripts/sw-connect.sh +619 -0
  35. package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
  36. package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
  37. package/scripts/sw-dashboard.sh +477 -0
  38. package/scripts/sw-developer-simulation.sh +252 -0
  39. package/scripts/sw-docs.sh +635 -0
  40. package/scripts/sw-doctor.sh +907 -0
  41. package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
  42. package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
  43. package/scripts/sw-github-checks.sh +521 -0
  44. package/scripts/sw-github-deploy.sh +533 -0
  45. package/scripts/sw-github-graphql.sh +972 -0
  46. package/scripts/sw-heartbeat.sh +293 -0
  47. package/scripts/{cct-init.sh → sw-init.sh} +144 -11
  48. package/scripts/sw-intelligence.sh +1196 -0
  49. package/scripts/sw-jira.sh +643 -0
  50. package/scripts/sw-launchd.sh +364 -0
  51. package/scripts/sw-linear.sh +648 -0
  52. package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
  53. package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
  54. package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
  55. package/scripts/sw-patrol-meta.sh +417 -0
  56. package/scripts/sw-pipeline-composer.sh +455 -0
  57. package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
  58. package/scripts/sw-predictive.sh +820 -0
  59. package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
  60. package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
  61. package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
  62. package/scripts/sw-remote.sh +687 -0
  63. package/scripts/sw-self-optimize.sh +947 -0
  64. package/scripts/sw-session.sh +519 -0
  65. package/scripts/sw-setup.sh +234 -0
  66. package/scripts/sw-status.sh +605 -0
  67. package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
  68. package/scripts/sw-tmux.sh +591 -0
  69. package/scripts/sw-tracker-jira.sh +277 -0
  70. package/scripts/sw-tracker-linear.sh +292 -0
  71. package/scripts/sw-tracker.sh +409 -0
  72. package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
  73. package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
  74. package/templates/pipelines/autonomous.json +27 -5
  75. package/templates/pipelines/full.json +12 -0
  76. package/templates/pipelines/standard.json +12 -0
  77. package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
  78. package/tmux/templates/accessibility.json +34 -0
  79. package/tmux/templates/api-design.json +35 -0
  80. package/tmux/templates/architecture.json +1 -0
  81. package/tmux/templates/bug-fix.json +9 -0
  82. package/tmux/templates/code-review.json +1 -0
  83. package/tmux/templates/compliance.json +36 -0
  84. package/tmux/templates/data-pipeline.json +36 -0
  85. package/tmux/templates/debt-paydown.json +34 -0
  86. package/tmux/templates/devops.json +1 -0
  87. package/tmux/templates/documentation.json +1 -0
  88. package/tmux/templates/exploration.json +1 -0
  89. package/tmux/templates/feature-dev.json +1 -0
  90. package/tmux/templates/full-stack.json +8 -0
  91. package/tmux/templates/i18n.json +34 -0
  92. package/tmux/templates/incident-response.json +36 -0
  93. package/tmux/templates/migration.json +1 -0
  94. package/tmux/templates/observability.json +35 -0
  95. package/tmux/templates/onboarding.json +33 -0
  96. package/tmux/templates/performance.json +35 -0
  97. package/tmux/templates/refactor.json +1 -0
  98. package/tmux/templates/release.json +35 -0
  99. package/tmux/templates/security-audit.json +8 -0
  100. package/tmux/templates/spike.json +34 -0
  101. package/tmux/templates/testing.json +1 -0
  102. package/tmux/tmux.conf +98 -9
  103. package/scripts/cct-doctor.sh +0 -414
  104. package/scripts/cct-session.sh +0 -284
  105. package/scripts/cct-status.sh +0 -169
@@ -0,0 +1,519 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ sw-session.sh — Launch a Claude Code team session in a new tmux window║
4
+ # ║ ║
5
+ # ║ Uses new-window (NOT split-window) to avoid the tmux send-keys race ║
6
+ # ║ condition that affects 4+ agents. See KNOWN-ISSUES.md for details. ║
7
+ # ║ ║
8
+ # ║ Supports --template to scaffold from a team template and --terminal ║
9
+ # ║ to select a terminal adapter (tmux, iterm2, wezterm). ║
10
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
11
+ VERSION="1.9.0"
12
+ set -euo pipefail
13
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+
17
+ # ─── Colors ──────────────────────────────────────────────────────────────────
18
+ CYAN='\033[38;2;0;212;255m'
19
+ PURPLE='\033[38;2;124;58;237m'
20
+ GREEN='\033[38;2;74;222;128m'
21
+ YELLOW='\033[38;2;250;204;21m'
22
+ RED='\033[38;2;248;113;113m'
23
+ DIM='\033[2m'
24
+ BOLD='\033[1m'
25
+ RESET='\033[0m'
26
+
27
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
28
+ # shellcheck source=lib/compat.sh
29
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
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
+ # ─── Parse Arguments ────────────────────────────────────────────────────────
36
+
37
+ TEAM_NAME=""
38
+ TEMPLATE_NAME=""
39
+ TERMINAL_ADAPTER=""
40
+ AUTO_LAUNCH=true
41
+ DRY_RUN=false
42
+ SKIP_PERMISSIONS="auto"
43
+ GOAL=""
44
+
45
+ while [[ $# -gt 0 ]]; do
46
+ case "$1" in
47
+ --template|-t)
48
+ TEMPLATE_NAME="${2:-}"
49
+ [[ -z "$TEMPLATE_NAME" ]] && { error "Missing template name after --template"; exit 1; }
50
+ shift 2
51
+ ;;
52
+ --terminal)
53
+ TERMINAL_ADAPTER="${2:-}"
54
+ [[ -z "$TERMINAL_ADAPTER" ]] && { error "Missing adapter name after --terminal"; exit 1; }
55
+ shift 2
56
+ ;;
57
+ --goal|-g)
58
+ GOAL="${2:-}"
59
+ [[ -z "$GOAL" ]] && { error "Missing goal after --goal"; exit 1; }
60
+ shift 2
61
+ ;;
62
+ --no-launch)
63
+ AUTO_LAUNCH=false
64
+ shift
65
+ ;;
66
+ --dry-run)
67
+ DRY_RUN=true
68
+ shift
69
+ ;;
70
+ --skip-permissions)
71
+ SKIP_PERMISSIONS=true
72
+ shift
73
+ ;;
74
+ --no-skip-permissions)
75
+ SKIP_PERMISSIONS=false
76
+ shift
77
+ ;;
78
+ --help|-h)
79
+ echo -e "${CYAN}${BOLD}shipwright session${RESET} — Create and launch a team session"
80
+ echo ""
81
+ echo -e "${BOLD}USAGE${RESET}"
82
+ echo -e " shipwright session [name] [--template <name>] [--goal \"...\"]"
83
+ echo ""
84
+ echo -e "${BOLD}OPTIONS${RESET}"
85
+ echo -e " ${CYAN}--template, -t${RESET} <name> Use a team template (see: shipwright templates list)"
86
+ echo -e " ${CYAN}--goal, -g${RESET} <text> Goal for the team (what to build/fix/refactor)"
87
+ echo -e " ${CYAN}--terminal${RESET} <adapter> Terminal adapter: tmux (default), iterm2, wezterm"
88
+ echo -e " ${CYAN}--no-launch${RESET} Create window only, don't auto-launch Claude"
89
+ echo -e " ${CYAN}--skip-permissions${RESET} Pass --dangerously-skip-permissions (default with agents)"
90
+ echo -e " ${CYAN}--no-skip-permissions${RESET} Require permission prompts even with agents"
91
+ echo -e " ${CYAN}--dry-run${RESET} Print team prompt and launcher script, don't create anything"
92
+ echo ""
93
+ echo -e "${BOLD}EXAMPLES${RESET}"
94
+ echo -e " ${DIM}shipwright session auth-refactor -t feature-dev -g \"Refactor auth to use JWT\"${RESET}"
95
+ echo -e " ${DIM}shipwright session my-feature --template feature-dev${RESET}"
96
+ echo -e " ${DIM}shipwright session bugfix -t bug-fix -g \"Fix login timeout issue\"${RESET}"
97
+ echo -e " ${DIM}shipwright session explore --no-launch${RESET}"
98
+ exit 0
99
+ ;;
100
+ -*)
101
+ error "Unknown option: $1"
102
+ exit 1
103
+ ;;
104
+ *)
105
+ # Positional: team name
106
+ [[ -z "$TEAM_NAME" ]] && TEAM_NAME="$1" || { error "Unexpected argument: $1"; exit 1; }
107
+ shift
108
+ ;;
109
+ esac
110
+ done
111
+
112
+ TEAM_NAME="${TEAM_NAME:-team-$(date +%s)}"
113
+ WINDOW_NAME="claude-${TEAM_NAME}"
114
+
115
+ # ─── Template Suggestion ──────────────────────────────────────────────────────
116
+
117
+ suggest_template() {
118
+ local goal="$1" templates_dir="$2"
119
+ local best="" best_score=0
120
+ [[ -d "$templates_dir" ]] || return 1
121
+
122
+ local goal_lower
123
+ goal_lower=$(echo "$goal" | tr '[:upper:]' '[:lower:]')
124
+
125
+ for tpl in "$templates_dir"/*.json; do
126
+ [[ -f "$tpl" ]] || continue
127
+ local name score=0
128
+ name=$(jq -r '.name // ""' "$tpl" 2>/dev/null) || continue
129
+
130
+ while IFS= read -r kw; do
131
+ [[ -z "$kw" ]] && continue
132
+ if echo "$goal_lower" | grep -qi "$kw"; then
133
+ score=$((score + 1))
134
+ fi
135
+ done < <(jq -r '(.keywords // []) | .[]' "$tpl" 2>/dev/null)
136
+
137
+ if [[ $score -gt $best_score ]]; then
138
+ best_score=$score
139
+ best="$name"
140
+ fi
141
+ done
142
+
143
+ [[ $best_score -gt 0 ]] && echo "$best"
144
+ }
145
+
146
+ # ─── Template Auto-Suggestion ────────────────────────────────────────────
147
+ if [[ -z "$TEMPLATE_NAME" && -n "$GOAL" ]]; then
148
+ REPO_TPL_DIR="$(cd "$SCRIPT_DIR/../tmux/templates" 2>/dev/null && pwd)" || REPO_TPL_DIR=""
149
+ USER_TPL_DIR="${HOME}/.shipwright/templates"
150
+ SUGGESTED=""
151
+
152
+ if [[ -d "$USER_TPL_DIR" ]]; then
153
+ SUGGESTED=$(suggest_template "$GOAL" "$USER_TPL_DIR") || true
154
+ fi
155
+ if [[ -z "$SUGGESTED" && -n "$REPO_TPL_DIR" ]]; then
156
+ SUGGESTED=$(suggest_template "$GOAL" "$REPO_TPL_DIR") || true
157
+ fi
158
+
159
+ if [[ -n "$SUGGESTED" ]]; then
160
+ info "Auto-suggesting template: ${PURPLE}${BOLD}${SUGGESTED}${RESET}"
161
+ TEMPLATE_NAME="$SUGGESTED"
162
+ fi
163
+ fi
164
+
165
+ # ─── Template Loading ───────────────────────────────────────────────────────
166
+
167
+ TEMPLATE_FILE=""
168
+ TEMPLATE_LAYOUT=""
169
+ TEMPLATE_LAYOUT_STYLE=""
170
+ TEMPLATE_MAIN_PANE_PERCENT=""
171
+ TEMPLATE_DESC=""
172
+ TEMPLATE_AGENTS=() # Populated as "name|role|focus" entries
173
+
174
+ if [[ -n "$TEMPLATE_NAME" ]]; then
175
+ # Search for template: user dir first, then repo dir
176
+ USER_TEMPLATES_DIR="${HOME}/.shipwright/templates"
177
+ REPO_TEMPLATES_DIR="$(cd "$SCRIPT_DIR/../tmux/templates" 2>/dev/null && pwd)" || REPO_TEMPLATES_DIR=""
178
+
179
+ TEMPLATE_NAME="${TEMPLATE_NAME%.json}"
180
+
181
+ if [[ -f "$USER_TEMPLATES_DIR/${TEMPLATE_NAME}.json" ]]; then
182
+ TEMPLATE_FILE="$USER_TEMPLATES_DIR/${TEMPLATE_NAME}.json"
183
+ elif [[ -n "$REPO_TEMPLATES_DIR" && -f "$REPO_TEMPLATES_DIR/${TEMPLATE_NAME}.json" ]]; then
184
+ TEMPLATE_FILE="$REPO_TEMPLATES_DIR/${TEMPLATE_NAME}.json"
185
+ else
186
+ error "Template '${TEMPLATE_NAME}' not found."
187
+ echo -e " Run ${DIM}shipwright templates list${RESET} to see available templates."
188
+ exit 1
189
+ fi
190
+
191
+ info "Loading template: ${PURPLE}${BOLD}${TEMPLATE_NAME}${RESET}"
192
+
193
+ # Parse template — single jq call extracts all fields + agents in one pass
194
+ if command -v jq &>/dev/null; then
195
+ # Single jq call: outputs metadata lines then agent lines
196
+ # Format: META<tab>field<tab>value for metadata, AGENT<tab>name|role|focus for agents
197
+ while IFS=$'\t' read -r tag key value; do
198
+ case "$tag" in
199
+ META)
200
+ case "$key" in
201
+ description) TEMPLATE_DESC="$value" ;;
202
+ layout) TEMPLATE_LAYOUT="$value" ;;
203
+ layout_style) TEMPLATE_LAYOUT_STYLE="$value" ;;
204
+ main_pane_percent) TEMPLATE_MAIN_PANE_PERCENT="$value" ;;
205
+ esac
206
+ ;;
207
+ AGENT) [[ -n "$key" ]] && TEMPLATE_AGENTS+=("$key") ;;
208
+ esac
209
+ done < <(jq -r '
210
+ "META\tdescription\t\(.description // "")",
211
+ "META\tlayout\t\(.layout // "tiled")",
212
+ "META\tlayout_style\t\(.layout_style // "")",
213
+ "META\tmain_pane_percent\t\(.main_pane_percent // "")",
214
+ (.agents // [] | .[] | "AGENT\t\(.name)|\(.role // "")|\(.focus // "")\t")
215
+ ' "$TEMPLATE_FILE")
216
+ else
217
+ error "jq is required for template parsing."
218
+ echo -e " ${DIM}brew install jq${RESET}"
219
+ exit 1
220
+ fi
221
+
222
+ echo -e " ${DIM}${TEMPLATE_DESC}${RESET}"
223
+ echo -e " ${DIM}Agents: ${#TEMPLATE_AGENTS[@]} Layout: ${TEMPLATE_LAYOUT}${RESET}"
224
+ fi
225
+
226
+ # ─── Resolve Permissions ──────────────────────────────────────────────────
227
+ # Default: skip permissions when agents are being spawned (autonomous teams)
228
+ if [[ "$SKIP_PERMISSIONS" == "auto" ]]; then
229
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 || -n "$GOAL" ]]; then
230
+ SKIP_PERMISSIONS=true
231
+ else
232
+ SKIP_PERMISSIONS=false
233
+ fi
234
+ fi
235
+
236
+ # ─── Resolve Terminal Adapter ───────────────────────────────────────────────
237
+
238
+ # Auto-detect if not specified
239
+ if [[ -z "$TERMINAL_ADAPTER" ]]; then
240
+ TERMINAL_ADAPTER="tmux"
241
+ fi
242
+
243
+ ADAPTER_FILE="$SCRIPT_DIR/adapters/${TERMINAL_ADAPTER}-adapter.sh"
244
+ if [[ -f "$ADAPTER_FILE" ]]; then
245
+ # shellcheck source=/dev/null
246
+ source "$ADAPTER_FILE"
247
+ else
248
+ # Default to inline tmux behavior (backwards compatible)
249
+ if [[ "$TERMINAL_ADAPTER" != "tmux" ]]; then
250
+ error "Terminal adapter '${TERMINAL_ADAPTER}' not found."
251
+ echo -e " Available: tmux (default), iterm2, wezterm"
252
+ echo -e " Adapter dir: ${DIM}${SCRIPT_DIR}/adapters/${RESET}"
253
+ exit 1
254
+ fi
255
+ fi
256
+
257
+ # ─── Build Team Prompt ───────────────────────────────────────────────────────
258
+ # Claude Code's TeamCreate + Task tools handle pane creation automatically.
259
+ # We create ONE window, launch claude with a team setup prompt, and let
260
+ # Claude orchestrate the agents. No pre-splitting needed.
261
+
262
+ build_team_prompt() {
263
+ local prompt=""
264
+ local project_dir
265
+ project_dir="$(tmux display-message -p '#{pane_current_path}' 2>/dev/null || pwd)"
266
+
267
+ local memory_context=""
268
+ if [[ -x "$SCRIPT_DIR/sw-memory.sh" ]]; then
269
+ memory_context=$(bash "$SCRIPT_DIR/sw-memory.sh" inject "build" 2>/dev/null) || true
270
+ fi
271
+
272
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 ]]; then
273
+ if [[ -n "$GOAL" ]]; then
274
+ prompt="GOAL: ${GOAL}"
275
+ prompt+=$'\n\n'
276
+ fi
277
+
278
+ prompt+="You are the team lead for \"${TEAM_NAME}\". You are in: ${project_dir}"
279
+ prompt+=$'\n\n'"Follow these steps:"
280
+ prompt+=$'\n'"1. Call TeamCreate with team_name=\"${TEAM_NAME}\""
281
+ prompt+=$'\n'"2. Create tasks using TaskCreate for each agent's work"
282
+ prompt+=$'\n'"3. Spawn each agent using the Task tool with team_name=\"${TEAM_NAME}\" and the agent name below"
283
+ prompt+=$'\n'"4. Assign tasks using TaskUpdate with owner set to each agent's name"
284
+ prompt+=$'\n'"5. Coordinate work and monitor progress"
285
+ prompt+=$'\n\n'"Agents to spawn:"
286
+
287
+ for agent_entry in "${TEMPLATE_AGENTS[@]}"; do
288
+ IFS='|' read -r aname arole afocus <<< "$agent_entry"
289
+ prompt+=$'\n'"- name=\"${aname}\": ${arole}"
290
+ if [[ -n "$afocus" ]]; then
291
+ prompt+=". Focus on files: ${afocus}"
292
+ fi
293
+ done
294
+
295
+ prompt+=$'\n\n'"Give each agent a detailed prompt describing their role and which files they own. Agents should work on DIFFERENT files to avoid merge conflicts."
296
+ else
297
+ # No template — simple team creation prompt
298
+ if [[ -n "$GOAL" ]]; then
299
+ prompt="GOAL: ${GOAL}"
300
+ prompt+=$'\n\n'"You are the team lead for \"${TEAM_NAME}\". You are in: ${project_dir}"
301
+ prompt+=$'\n\n'"Follow these steps:"
302
+ prompt+=$'\n'"1. Call TeamCreate with team_name=\"${TEAM_NAME}\""
303
+ prompt+=$'\n'"2. Decide the right number and types of agents for this goal"
304
+ prompt+=$'\n'"3. Create tasks using TaskCreate, then spawn agents with the Task tool (team_name=\"${TEAM_NAME}\")"
305
+ prompt+=$'\n'"4. Assign tasks and coordinate work"
306
+ prompt+=$'\n\n'"Assign different files to each agent to avoid merge conflicts."
307
+ fi
308
+ fi
309
+
310
+ if [[ -n "$prompt" ]]; then
311
+ if [[ -n "$memory_context" ]]; then
312
+ prompt+=$'\n\n'"Historical context (lessons from previous runs):"
313
+ prompt+=$'\n'"${memory_context}"
314
+ fi
315
+
316
+ prompt+=$'\n\n'"IMPORTANT: Read .claude/CLAUDE.md for project-specific conventions, patterns, and instructions."
317
+ fi
318
+
319
+ echo "$prompt"
320
+ }
321
+
322
+ # ─── Dry Run ────────────────────────────────────────────────────────────────
323
+
324
+ if [[ "$DRY_RUN" == true ]]; then
325
+ TEAM_PROMPT="$(build_team_prompt)"
326
+ PROJECT_DIR="$(tmux display-message -p '#{pane_current_path}' 2>/dev/null || pwd)"
327
+ DRY_RUN_FLAGS=""
328
+ [[ "$SKIP_PERMISSIONS" == true ]] && DRY_RUN_FLAGS=" --dangerously-skip-permissions"
329
+
330
+ echo -e "${CYAN}${BOLD}═══ Team Prompt ═══${RESET}"
331
+ echo ""
332
+ if [[ -n "$TEAM_PROMPT" ]]; then
333
+ echo "$TEAM_PROMPT"
334
+ else
335
+ echo -e "${DIM}(empty — no template or goal specified)${RESET}"
336
+ fi
337
+
338
+ echo ""
339
+ echo -e "${CYAN}${BOLD}═══ Launcher Script ═══${RESET}"
340
+ echo ""
341
+ if [[ -n "$TEAM_PROMPT" ]]; then
342
+ cat << EOF
343
+ #!/usr/bin/env bash
344
+ # Auto-generated by shipwright session — safe to delete
345
+ cd ${PROJECT_DIR} || exit 1
346
+ printf '\\033]2;${TEAM_NAME}-lead\\033\\\\'
347
+ PROMPT=\$(cat <prompt-file>)
348
+ rm -f <prompt-file> "\$0"
349
+ claude${DRY_RUN_FLAGS} "\$PROMPT"
350
+ echo ""
351
+ echo "Claude exited. Type 'claude' to restart, or 'exit' to close."
352
+ exec "\$SHELL" -l
353
+ EOF
354
+ else
355
+ cat << EOF
356
+ #!/usr/bin/env bash
357
+ cd ${PROJECT_DIR} || exit 1
358
+ printf '\\033]2;${TEAM_NAME}-lead\\033\\\\'
359
+ rm -f "\$0"
360
+ claude${DRY_RUN_FLAGS}
361
+ echo ""
362
+ echo "Claude exited. Type 'claude' to restart, or 'exit' to close."
363
+ exec "\$SHELL" -l
364
+ EOF
365
+ fi
366
+
367
+ echo ""
368
+ echo -e "${DIM}Window name: ${WINDOW_NAME}${RESET}"
369
+ echo -e "${DIM}Terminal adapter: ${TERMINAL_ADAPTER}${RESET}"
370
+ echo -e "${DIM}Auto-launch: ${AUTO_LAUNCH}${RESET}"
371
+ exit 0
372
+ fi
373
+
374
+ # ─── Create Session ──────────────────────────────────────────────────────────
375
+
376
+ if [[ "$TERMINAL_ADAPTER" == "tmux" ]]; then
377
+ # ─── tmux session creation ─────────────────────────────────────────────
378
+ # Uses launcher script passed to `tmux new-window` as command argument
379
+ # to eliminate the send-keys race condition (shell startup vs keystrokes).
380
+
381
+ # Secure temp directory — restrictive permissions for prompt/launcher files
382
+ SECURE_TMPDIR=$(mktemp -d) || { error "Cannot create temp dir"; exit 1; }
383
+ chmod 700 "$SECURE_TMPDIR"
384
+ trap 'rm -rf "$SECURE_TMPDIR"' EXIT
385
+
386
+ # Check if a window with this name already exists
387
+ if tmux list-windows -F '#W' 2>/dev/null | grep -qx "$WINDOW_NAME"; then
388
+ warn "Window '${WINDOW_NAME}' already exists. Switching to it."
389
+ tmux select-window -t "$WINDOW_NAME"
390
+ exit 0
391
+ fi
392
+
393
+ info "Creating team session: ${CYAN}${BOLD}${TEAM_NAME}${RESET}"
394
+ if [[ "$SKIP_PERMISSIONS" == true ]]; then
395
+ warn "${YELLOW}--dangerously-skip-permissions enabled${RESET}"
396
+ fi
397
+
398
+ # Resolve project directory (use current pane's path)
399
+ PROJECT_DIR="$(tmux display-message -p '#{pane_current_path}' 2>/dev/null || pwd)"
400
+
401
+ TEAM_PROMPT="$(build_team_prompt)"
402
+
403
+ if [[ "$AUTO_LAUNCH" == true && -n "$TEAM_PROMPT" ]]; then
404
+ info "Launching Claude Code with team setup..."
405
+
406
+ # Write prompt to a file (avoids all quoting/escaping issues)
407
+ PROMPT_FILE="$SECURE_TMPDIR/prompt.txt"
408
+ printf '%s' "$TEAM_PROMPT" > "$PROMPT_FILE"
409
+
410
+ # Build launcher — quoted heredoc (no expansion), then sed for variables.
411
+ # When claude exits, falls back to interactive shell so pane stays alive.
412
+ LAUNCHER="$SECURE_TMPDIR/launcher.sh"
413
+ cat > "$LAUNCHER" << 'LAUNCHER_STATIC'
414
+ #!/usr/bin/env bash
415
+ # Auto-generated by shipwright session — safe to delete
416
+ cd __DIR__ || exit 1
417
+ printf '\033]2;__TEAM__-lead\033\\'
418
+ PROMPT=$(cat __PROMPT__)
419
+ rm -f __PROMPT__ "$0"
420
+ claude __CLAUDE_FLAGS__ "$PROMPT"
421
+ echo ""
422
+ echo "Claude exited. Type 'claude' to restart, or 'exit' to close."
423
+ exec "$SHELL" -l
424
+ LAUNCHER_STATIC
425
+ CLAUDE_FLAGS=""
426
+ if [[ "$SKIP_PERMISSIONS" == true ]]; then
427
+ CLAUDE_FLAGS="--dangerously-skip-permissions"
428
+ fi
429
+ sed "s|__DIR__|${PROJECT_DIR}|g;s|__TEAM__|${TEAM_NAME}|g;s|__PROMPT__|${PROMPT_FILE}|g;s|__CLAUDE_FLAGS__|${CLAUDE_FLAGS}|g" \
430
+ "$LAUNCHER" > "${LAUNCHER}.tmp" && mv "${LAUNCHER}.tmp" "$LAUNCHER"
431
+ chmod +x "$LAUNCHER"
432
+
433
+ # Create window with command — no race condition!
434
+ # bash --login loads PATH (needed for ~/.local/bin/claude)
435
+ tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR" \
436
+ "bash --login ${LAUNCHER}"
437
+
438
+ elif [[ "$AUTO_LAUNCH" == true && -z "$TEAM_PROMPT" ]]; then
439
+ # No template and no goal — just launch claude interactively
440
+ info "Launching Claude Code..."
441
+
442
+ LAUNCHER="$SECURE_TMPDIR/launcher.sh"
443
+ cat > "$LAUNCHER" << 'LAUNCHER_STATIC'
444
+ #!/usr/bin/env bash
445
+ cd __DIR__ || exit 1
446
+ printf '\033]2;__TEAM__-lead\033\\'
447
+ rm -f "$0"
448
+ claude __CLAUDE_FLAGS__
449
+ echo ""
450
+ echo "Claude exited. Type 'claude' to restart, or 'exit' to close."
451
+ exec "$SHELL" -l
452
+ LAUNCHER_STATIC
453
+ CLAUDE_FLAGS=""
454
+ if [[ "$SKIP_PERMISSIONS" == true ]]; then
455
+ CLAUDE_FLAGS="--dangerously-skip-permissions"
456
+ fi
457
+ sed "s|__DIR__|${PROJECT_DIR}|g;s|__TEAM__|${TEAM_NAME}|g;s|__CLAUDE_FLAGS__|${CLAUDE_FLAGS}|g" \
458
+ "$LAUNCHER" > "${LAUNCHER}.tmp" && mv "${LAUNCHER}.tmp" "$LAUNCHER"
459
+ chmod +x "$LAUNCHER"
460
+
461
+ tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR" \
462
+ "bash --login ${LAUNCHER}"
463
+
464
+ else
465
+ # --no-launch: create window with a regular shell
466
+ tmux new-window -n "$WINDOW_NAME" -c "$PROJECT_DIR"
467
+ info "Window ready. Launch Claude manually: ${DIM}claude${RESET}"
468
+ fi
469
+
470
+ # Apply dark theme (safe to run immediately — no race with pane content)
471
+ tmux select-pane -t "$WINDOW_NAME" -P 'bg=#1a1a2e,fg=#e4e4e7'
472
+
473
+ elif [[ -f "$ADAPTER_FILE" ]] && type -t spawn_agent &>/dev/null; then
474
+ # ─── Non-tmux adapter session (iterm2, wezterm, etc.) ──────────────────
475
+ info "Creating team session: ${CYAN}${BOLD}${TEAM_NAME}${RESET} ${DIM}(${TERMINAL_ADAPTER})${RESET}"
476
+
477
+ # Spawn leader only — Claude Code handles agent pane creation
478
+ spawn_agent "${TEAM_NAME}-lead" "#{pane_current_path}" ""
479
+
480
+ else
481
+ error "Terminal adapter '${TERMINAL_ADAPTER}' not available."
482
+ exit 1
483
+ fi
484
+
485
+ # ─── Summary ────────────────────────────────────────────────────────────────
486
+
487
+ echo ""
488
+ success "Team session ${CYAN}${BOLD}${TEAM_NAME}${RESET} launched!"
489
+
490
+ if [[ ${#TEMPLATE_AGENTS[@]} -gt 0 ]]; then
491
+ echo ""
492
+ echo -e "${BOLD}Team from template ${PURPLE}${TEMPLATE_NAME}${RESET}${BOLD}:${RESET}"
493
+ echo -e " ${CYAN}${BOLD}lead${RESET} ${DIM}— Team coordinator (Claude Code)${RESET}"
494
+ for agent_entry in "${TEMPLATE_AGENTS[@]}"; do
495
+ IFS='|' read -r aname arole afocus <<< "$agent_entry"
496
+ echo -e " ${PURPLE}${BOLD}${aname}${RESET} ${DIM}— ${arole}${RESET}"
497
+ done
498
+ if [[ -n "$GOAL" ]]; then
499
+ echo ""
500
+ echo -e "${BOLD}Goal:${RESET} ${GOAL}"
501
+ fi
502
+ fi
503
+
504
+ if [[ "$AUTO_LAUNCH" == true ]]; then
505
+ echo ""
506
+ echo -e "${GREEN}${BOLD}Claude Code is starting in window ${DIM}${WINDOW_NAME}${RESET}"
507
+ echo -e "${DIM}Claude will create the team, spawn agents in their own panes, and begin work.${RESET}"
508
+ WIN_NUM="$(tmux list-windows -F '#I #W' 2>/dev/null | grep "$WINDOW_NAME" | cut -d' ' -f1)" || WIN_NUM="?"
509
+ echo -e "${DIM}Switch to it: prefix + ${WIN_NUM}${RESET}"
510
+ else
511
+ echo ""
512
+ echo -e "${BOLD}Next steps:${RESET}"
513
+ WIN_NUM="$(tmux list-windows -F '#I #W' 2>/dev/null | grep "$WINDOW_NAME" | cut -d' ' -f1)" || WIN_NUM="?"
514
+ echo -e " ${CYAN}1.${RESET} Switch to window ${DIM}${WINDOW_NAME}${RESET} ${DIM}(prefix + ${WIN_NUM})${RESET}"
515
+ echo -e " ${CYAN}2.${RESET} Start ${DIM}claude${RESET} and ask it to create a team"
516
+ fi
517
+
518
+ echo ""
519
+ echo -e "${DIM}Keybinding: prefix + T re-runs this command${RESET}"