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,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}"
|