shellmates 0.1.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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/bin/shellmates.js +112 -0
  4. package/lib/commands/config.js +88 -0
  5. package/lib/commands/init.js +56 -0
  6. package/lib/commands/install-hook.js +83 -0
  7. package/lib/commands/spawn.js +98 -0
  8. package/lib/commands/status.js +69 -0
  9. package/lib/utils/config.js +35 -0
  10. package/lib/utils/logo.js +84 -0
  11. package/lib/utils/tmux.js +46 -0
  12. package/package.json +39 -0
  13. package/scripts/dispatch.sh +331 -0
  14. package/scripts/launch-full-team.sh +77 -0
  15. package/scripts/launch.sh +183 -0
  16. package/scripts/monitor.sh +113 -0
  17. package/scripts/spawn-team.sh +302 -0
  18. package/scripts/status.sh +168 -0
  19. package/scripts/teardown.sh +211 -0
  20. package/scripts/view-session.sh +98 -0
  21. package/scripts/watch-inbox.sh +71 -0
  22. package/templates/.codex/agents/default.toml +5 -0
  23. package/templates/.codex/agents/executor.toml +7 -0
  24. package/templates/.codex/agents/explorer.toml +5 -0
  25. package/templates/.codex/agents/planner.toml +6 -0
  26. package/templates/.codex/agents/researcher.toml +6 -0
  27. package/templates/.codex/agents/reviewer.toml +5 -0
  28. package/templates/.codex/agents/verifier.toml +6 -0
  29. package/templates/.codex/agents/worker.toml +5 -0
  30. package/templates/.codex/config.toml +43 -0
  31. package/templates/AGENTS.md +109 -0
  32. package/templates/CLAUDE.md +50 -0
  33. package/templates/GEMINI.md +136 -0
  34. package/templates/config.json +10 -0
  35. package/templates/gitignore-additions.txt +2 -0
  36. package/templates/hooks/settings-addition.json +20 -0
  37. package/templates/hooks/shellmates-notify.sh +77 -0
  38. package/templates/task-header.txt +10 -0
@@ -0,0 +1,35 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs'
2
+ import { homedir } from 'os'
3
+ import { join } from 'path'
4
+
5
+ export const CONFIG_DIR = join(homedir(), '.shellmates')
6
+ export const CONFIG_PATH = join(CONFIG_DIR, 'config.json')
7
+ export const INBOX_DIR = join(CONFIG_DIR, 'inbox')
8
+
9
+ const DEFAULTS = {
10
+ permission_mode: 'default',
11
+ default_agent: 'gemini',
12
+ orchestrator: 'claude',
13
+ }
14
+
15
+ export function readConfig() {
16
+ if (!existsSync(CONFIG_PATH)) return { ...DEFAULTS }
17
+ try {
18
+ const raw = readFileSync(CONFIG_PATH, 'utf8')
19
+ return { ...DEFAULTS, ...JSON.parse(raw) }
20
+ } catch {
21
+ return { ...DEFAULTS }
22
+ }
23
+ }
24
+
25
+ export function writeConfig(config) {
26
+ mkdirSync(CONFIG_DIR, { recursive: true })
27
+ // Strip internal _docs key before writing user-facing config
28
+ const { _docs, ...clean } = config
29
+ writeFileSync(CONFIG_PATH, JSON.stringify(clean, null, 2) + '\n')
30
+ }
31
+
32
+ export function ensureDirs() {
33
+ mkdirSync(CONFIG_DIR, { recursive: true })
34
+ mkdirSync(INBOX_DIR, { recursive: true })
35
+ }
@@ -0,0 +1,84 @@
1
+ import chalk from 'chalk'
2
+
3
+ // Pixel grid definitions — 1 = filled block, 0 = empty
4
+ // 3 pixels wide × 5 pixels tall per letter (compact, fits 80-char terminals)
5
+ // Each pixel renders as '██' (2 chars), gap between letters = ' ' (1 empty pixel)
6
+
7
+ const LETTERS = {
8
+ S: [
9
+ [1,1,1],
10
+ [1,0,0],
11
+ [1,1,1],
12
+ [0,0,1],
13
+ [1,1,1],
14
+ ],
15
+ H: [
16
+ [1,0,1],
17
+ [1,0,1],
18
+ [1,1,1],
19
+ [1,0,1],
20
+ [1,0,1],
21
+ ],
22
+ E: [
23
+ [1,1,1],
24
+ [1,0,0],
25
+ [1,1,0],
26
+ [1,0,0],
27
+ [1,1,1],
28
+ ],
29
+ L: [
30
+ [1,0,0],
31
+ [1,0,0],
32
+ [1,0,0],
33
+ [1,0,0],
34
+ [1,1,1],
35
+ ],
36
+ M: [
37
+ [1,0,1],
38
+ [1,1,1],
39
+ [1,0,1],
40
+ [1,0,1],
41
+ [1,0,1],
42
+ ],
43
+ A: [
44
+ [0,1,0],
45
+ [1,0,1],
46
+ [1,1,1],
47
+ [1,0,1],
48
+ [1,0,1],
49
+ ],
50
+ T: [
51
+ [1,1,1],
52
+ [0,1,0],
53
+ [0,1,0],
54
+ [0,1,0],
55
+ [0,1,0],
56
+ ],
57
+ }
58
+
59
+ // SHELLMATES = S H E L L M A T E S
60
+ const WORD = ['S','H','E','L','L','M','A','T','E','S']
61
+
62
+ const FILLED = chalk.hex('#4FC3F7')('██')
63
+ const EMPTY = ' '
64
+
65
+ export function printLogo(version) {
66
+ const numRows = 5
67
+ const rows = Array.from({ length: numRows }, (_, rowIdx) => {
68
+ return WORD.map(ch => {
69
+ const grid = LETTERS[ch]
70
+ return grid[rowIdx].map(p => p ? FILLED : EMPTY).join('')
71
+ }).join(EMPTY) // 1-pixel gap between letters
72
+ })
73
+
74
+ const pad = ' '
75
+ console.log('')
76
+ for (const row of rows) {
77
+ console.log(pad + row)
78
+ }
79
+ console.log('')
80
+ if (version) {
81
+ console.log(pad + chalk.dim(`v${version} · tmux multi-agent orchestration`))
82
+ }
83
+ console.log('')
84
+ }
@@ -0,0 +1,46 @@
1
+ import { execSync, spawnSync } from 'child_process'
2
+ import { readdirSync, existsSync } from 'fs'
3
+
4
+ export function tmuxAvailable() {
5
+ try {
6
+ execSync('which tmux', { stdio: 'ignore' })
7
+ return true
8
+ } catch {
9
+ return false
10
+ }
11
+ }
12
+
13
+ export function listSessions() {
14
+ try {
15
+ const out = execSync('tmux list-sessions -F "#{session_name}"', { encoding: 'utf8' })
16
+ return out.trim().split('\n').filter(Boolean)
17
+ } catch {
18
+ return []
19
+ }
20
+ }
21
+
22
+ export function sessionExists(name) {
23
+ return listSessions().includes(name)
24
+ }
25
+
26
+ export function listInboxFiles(inboxDir) {
27
+ if (!existsSync(inboxDir)) return []
28
+ return readdirSync(inboxDir).filter(f => f.endsWith('.txt'))
29
+ }
30
+
31
+ export function killSession(name) {
32
+ try {
33
+ execSync(`tmux kill-session -t ${name}`, { stdio: 'ignore' })
34
+ return true
35
+ } catch {
36
+ return false
37
+ }
38
+ }
39
+
40
+ export function runScript(scriptPath, args = [], { inherit = true } = {}) {
41
+ const result = spawnSync('bash', [scriptPath, ...args], {
42
+ stdio: inherit ? 'inherit' : 'pipe',
43
+ encoding: 'utf8',
44
+ })
45
+ return result
46
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "shellmates",
3
+ "version": "0.1.0",
4
+ "description": "Seamless tmux multi-agent orchestration for Claude, Gemini, and Codex",
5
+ "keywords": [
6
+ "tmux",
7
+ "ai",
8
+ "agents",
9
+ "claude",
10
+ "gemini",
11
+ "codex",
12
+ "orchestration"
13
+ ],
14
+ "homepage": "https://github.com/rs07-git/shellmates",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/rs07-git/shellmates.git"
18
+ },
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "bin": {
22
+ "shellmates": "bin/shellmates.js"
23
+ },
24
+ "files": [
25
+ "bin/",
26
+ "lib/",
27
+ "scripts/",
28
+ "templates/"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ },
33
+ "dependencies": {
34
+ "commander": "^12.1.0",
35
+ "inquirer": "^9.3.7",
36
+ "chalk": "^5.3.0",
37
+ "ora": "^8.1.1"
38
+ }
39
+ }
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env bash
2
+ # dispatch.sh — Reliably send a task to a sub-agent pane
3
+ #
4
+ # Fixes addressed:
5
+ # 1. Startup timing: waits for agent prompt to be ready before sending
6
+ # 2. Permission prompts: enables auto-edit mode (Shift+Tab) before dispatching
7
+ # 3. Token efficiency: prepends agent communication protocol header to every task
8
+ # 4. Completion notification: starts background watcher + writes to inbox file
9
+ # 5. Session visibility: shows or opens a live view of the worker after dispatch
10
+ #
11
+ # Usage:
12
+ # bash scripts/dispatch.sh --pane %46 --task-file /tmp/task.txt
13
+ # bash scripts/dispatch.sh --pane %46 --task "one-liner task"
14
+ # bash scripts/dispatch.sh --pane orchestra:0.0 --task-file /tmp/task.txt --no-ping
15
+ #
16
+ # Options:
17
+ # --pane Target pane (stable %ID or positional session:0.0)
18
+ # --task-file Path to task file (preferred for multi-step tasks)
19
+ # --task Inline task (for simple one-liners)
20
+ # --job-id Job ID for inbox result file (default: auto-generated)
21
+ # --ping-back Pane to notify when done (default: caller's $TMUX_PANE)
22
+ # --task-name Short label for status display
23
+ # --no-ping Skip ping-back / inbox watcher (fire-and-forget)
24
+ # --no-view Don't show/open the session viewer after dispatch
25
+ # --no-header Skip prepending the agent communication protocol header
26
+
27
+ set -euo pipefail
28
+
29
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+ HEADER_FILE="${SCRIPT_DIR}/../templates/task-header.txt"
31
+ INBOX_DIR="${HOME}/.shellmates/inbox"
32
+ PROJECT_TASKS_DIR="" # set to project-relative path once we know the pane's cwd
33
+
34
+ PANE=""
35
+ TASK_FILE=""
36
+ TASK_INLINE=""
37
+ JOB_ID=""
38
+ PING_BACK_PANE=""
39
+ TASK_NAME=""
40
+ NO_PING=false
41
+ NO_VIEW=false
42
+ NO_HEADER=false
43
+
44
+ while [[ $# -gt 0 ]]; do
45
+ case "$1" in
46
+ --pane) PANE="$2"; shift 2 ;;
47
+ --task-file) TASK_FILE="$2"; shift 2 ;;
48
+ --task) TASK_INLINE="$2"; shift 2 ;;
49
+ --job-id) JOB_ID="$2"; shift 2 ;;
50
+ --ping-back) PING_BACK_PANE="$2"; shift 2 ;;
51
+ --task-name) TASK_NAME="$2"; shift 2 ;;
52
+ --no-ping) NO_PING=true; shift ;;
53
+ --no-view) NO_VIEW=true; shift ;;
54
+ --no-header) NO_HEADER=false; shift ;; # reserved
55
+ -h|--help)
56
+ sed -n '2,20p' "$0" | sed 's/^# //' | sed 's/^#//'
57
+ exit 0 ;;
58
+ *) echo "ERROR: Unknown option: $1"; exit 1 ;;
59
+ esac
60
+ done
61
+
62
+ # ── Validate ──────────────────────────────────────────────────────────────────
63
+
64
+ if [[ -z "$PANE" ]]; then
65
+ echo "ERROR: --pane is required"
66
+ exit 1
67
+ fi
68
+
69
+ if [[ -z "$TASK_FILE" && -z "$TASK_INLINE" ]]; then
70
+ echo "ERROR: --task-file or --task is required"
71
+ exit 1
72
+ fi
73
+
74
+ # ── Resolve ping-back pane ────────────────────────────────────────────────────
75
+
76
+ if [[ -z "$PING_BACK_PANE" && "$NO_PING" == "false" ]]; then
77
+ if [[ -n "${TMUX_PANE:-}" ]]; then
78
+ PING_BACK_PANE="$TMUX_PANE"
79
+ elif [[ -n "${TMUX:-}" ]]; then
80
+ PING_BACK_PANE=$(tmux display-message -p '#{pane_id}' 2>/dev/null || echo "")
81
+ fi
82
+
83
+ if [[ -z "$PING_BACK_PANE" ]]; then
84
+ echo "NOTE: Not inside tmux — ping-back via tmux disabled."
85
+ echo " Inbox watcher will still write to ~/.shellmates/inbox/"
86
+ echo " Pass --ping-back PANE_ID to enable active notification."
87
+ fi
88
+ fi
89
+
90
+ # ── Preflight: verify pane is running an agent ────────────────────────────────
91
+
92
+ PANE_CMD=$(tmux display-message -p -t "$PANE" '#{pane_current_command}' 2>/dev/null || echo "unknown")
93
+ SHELL_CMDS=("bash" "zsh" "sh" "fish")
94
+
95
+ for SHELL_CMD in "${SHELL_CMDS[@]}"; do
96
+ if [[ "$PANE_CMD" == "$SHELL_CMD" ]]; then
97
+ echo "ERROR: Pane $PANE is running $PANE_CMD — no agent detected."
98
+ echo " Start the agent first: tmux send-keys -t $PANE 'gemini' Enter"
99
+ exit 1
100
+ fi
101
+ done
102
+
103
+ echo "Target pane: $PANE (process: $PANE_CMD)"
104
+
105
+ # Resolve the pane's working directory (used to place task files inside the project)
106
+ PANE_CWD=$(tmux display-message -p -t "$PANE" '#{pane_current_path}' 2>/dev/null || echo "/tmp")
107
+ PROJECT_TASKS_DIR="${PANE_CWD}/.shellmates/tasks"
108
+
109
+ # ── Wait for agent prompt and dismiss startup banner ──────────────────────────
110
+ # Gemini shows a startup banner AFTER the initial prompt appears.
111
+ # If we send the task immediately after seeing "Type your message", the banner
112
+ # appears and steals the Enter keystroke — task never submits.
113
+ # Fix: explicitly detect and dismiss the banner before dispatching.
114
+
115
+ wait_for_prompt() {
116
+ local pane="$1"
117
+ local max_wait=20
118
+ local elapsed=0
119
+ echo -n "Waiting for agent prompt..."
120
+ while [[ $elapsed -lt $max_wait ]]; do
121
+ local output
122
+ output=$(tmux capture-pane -t "$pane" -p 2>/dev/null)
123
+ if echo "$output" | grep -q "Type your message"; then
124
+ echo " ready."
125
+ return 0
126
+ fi
127
+ sleep 1
128
+ elapsed=$((elapsed + 1))
129
+ done
130
+ echo " (timeout — proceeding anyway)"
131
+ }
132
+
133
+ dismiss_startup_banner() {
134
+ local pane="$1"
135
+ local output
136
+ output=$(tmux capture-pane -t "$pane" -p 2>/dev/null)
137
+ # Gemini shows an announcement banner with "What's Changing" or similar
138
+ if echo "$output" | grep -qE "What's Changing|We're making changes|Read more:.*goo\.gle"; then
139
+ echo "Dismissing startup banner..."
140
+ tmux send-keys -t "$pane" "" Enter
141
+ sleep 1
142
+ # Wait for prompt to reappear after dismiss
143
+ wait_for_prompt "$pane"
144
+ fi
145
+ }
146
+
147
+ # Verify the pane's input bar contains the expected string.
148
+ # Returns 0 if found, 1 if not.
149
+ input_bar_contains() {
150
+ local pane="$1"
151
+ local expected="$2"
152
+ tmux capture-pane -t "$pane" -p 2>/dev/null | grep -qF "$expected"
153
+ }
154
+
155
+ # Ensure a string is in the input bar before submitting. If the banner
156
+ # cleared it (by consuming @filepath or the prior Enter), re-type it.
157
+ ensure_input_ready() {
158
+ local pane="$1"
159
+ local text="$2"
160
+ local max_retries=3
161
+ local attempt=0
162
+
163
+ while [[ $attempt -lt $max_retries ]]; do
164
+ if input_bar_contains "$pane" "$text"; then
165
+ echo "Input verified: $(basename "$text" 2>/dev/null || echo "$text")"
166
+ return 0
167
+ fi
168
+
169
+ attempt=$((attempt + 1))
170
+ echo "Input bar missing expected text (attempt $attempt) — re-typing..."
171
+ # Clear whatever might be half-typed and retype
172
+ tmux send-keys -t "$pane" "C-c" 2>/dev/null || true
173
+ sleep 0.2
174
+ wait_for_prompt "$pane"
175
+ dismiss_startup_banner "$pane"
176
+ tmux send-keys -t "$pane" "$text"
177
+ sleep 0.4
178
+ done
179
+
180
+ echo "WARNING: Could not verify input bar after $max_retries attempts — submitting anyway"
181
+ }
182
+
183
+ wait_for_prompt "$PANE"
184
+ dismiss_startup_banner "$PANE"
185
+
186
+ # ── Enable auto-edit mode if not already in bypass mode ──────────────────────
187
+ # If the agent was NOT started with --yolo/--full-auto (i.e. permission_mode=default),
188
+ # send Shift+Tab to at least enable auto-edit for this session (approves file tools).
189
+ # If bypass mode is active, the agent already handles this at startup — skip it.
190
+
191
+ CONFIG_FILE="${HOME}/.shellmates/config.json"
192
+ PERMISSION_MODE=$(python3 -c "
193
+ import json, os
194
+ cfg = '${CONFIG_FILE}'
195
+ if os.path.exists(cfg):
196
+ d = json.load(open(cfg))
197
+ print(d.get('permission_mode', 'default'))
198
+ else:
199
+ print('default')
200
+ " 2>/dev/null || echo "default")
201
+
202
+ if [[ "$PERMISSION_MODE" != "bypass" ]]; then
203
+ if [[ "$PANE_CMD" == "node" || "$PANE_CMD" == "gemini" ]]; then
204
+ # Gemini: Shift+Tab toggles auto-edit (approves file read/write tools)
205
+ tmux send-keys -t "$PANE" "BTab"
206
+ sleep 0.3
207
+ fi
208
+ fi
209
+
210
+ # ── Build final task file ─────────────────────────────────────────────────────
211
+
212
+ # Resolve inline task to file
213
+ TEMP_TASK_FILE=""
214
+ if [[ -n "$TASK_INLINE" ]]; then
215
+ TEMP_TASK_FILE="/tmp/.shellmates-task-$$.txt"
216
+ echo "$TASK_INLINE" > "$TEMP_TASK_FILE"
217
+ TASK_FILE="$TEMP_TASK_FILE"
218
+ fi
219
+
220
+ # Auto-generate job ID
221
+ if [[ -z "$JOB_ID" ]]; then
222
+ JOB_ID="job-$$-$(date +%s)"
223
+ fi
224
+
225
+ # Auto-generate task name
226
+ if [[ -z "$TASK_NAME" ]]; then
227
+ TASK_NAME=$(grep -m1 '.' "$TASK_FILE" | sed 's/^#* *//' | cut -c1-60)
228
+ fi
229
+
230
+ # Build the final task: header + original task + completion footer
231
+ # Place the file INSIDE the project dir so agents don't need a separate
232
+ # permission prompt to read it (Gemini --yolo still prompts for /tmp reads).
233
+ mkdir -p "$PROJECT_TASKS_DIR" 2>/dev/null || true
234
+ if [[ -d "$PROJECT_TASKS_DIR" ]]; then
235
+ FINAL_TASK_FILE="${PROJECT_TASKS_DIR}/.dispatch-$$.txt"
236
+ else
237
+ FINAL_TASK_FILE="/tmp/.shellmates-dispatch-$$.txt"
238
+ fi
239
+ mkdir -p "$INBOX_DIR"
240
+
241
+ {
242
+ # Token efficiency protocol header
243
+ if [[ -f "$HEADER_FILE" ]]; then
244
+ cat "$HEADER_FILE"
245
+ echo ""
246
+ fi
247
+
248
+ # Original task content
249
+ cat "$TASK_FILE"
250
+
251
+ # Completion footer: write result to inbox file
252
+ cat << FOOTER
253
+
254
+ ---
255
+ When your task is complete, write your result to this file:
256
+ ${INBOX_DIR}/${JOB_ID}.txt
257
+
258
+ Use this exact format (concise — no prose):
259
+ \`\`\`
260
+ AGENT: $(echo "$PANE_CMD" | tr '[:upper:]' '[:lower:]')
261
+ JOB: ${JOB_ID}
262
+ STATUS: complete
263
+ CHANGED: <comma-separated file paths, or none>
264
+ RESULT: <≤5 line summary of what was done>
265
+ \`\`\`
266
+
267
+ Write the file with:
268
+ mkdir -p "${INBOX_DIR}" && cat > "${INBOX_DIR}/${JOB_ID}.txt" << 'EOF'
269
+ AGENT: gemini
270
+ JOB: ${JOB_ID}
271
+ STATUS: complete
272
+ CHANGED: <files>
273
+ RESULT: <summary>
274
+ EOF
275
+
276
+ Then output: PHASE_COMPLETE: ${TASK_NAME}
277
+ FOOTER
278
+ } > "$FINAL_TASK_FILE"
279
+
280
+ # Cleanup inline temp file
281
+ [[ -n "$TEMP_TASK_FILE" ]] && rm -f "$TEMP_TASK_FILE"
282
+
283
+ # ── Dispatch ──────────────────────────────────────────────────────────────────
284
+
285
+ if [[ "$PANE_CMD" == "node" || "$PANE_CMD" == "gemini" ]]; then
286
+ echo "Dispatching via @filepath (Gemini CLI)..."
287
+ # Type the filepath but don't submit yet — Gemini may re-show the startup
288
+ # banner between the @filepath and the Enter. We:
289
+ # 1. Type the @filepath (no Enter)
290
+ # 2. Dismiss any banner that appeared
291
+ # 3. Verify the input bar still contains the filepath (re-type if not)
292
+ # 4. Submit
293
+ tmux send-keys -t "$PANE" "@${FINAL_TASK_FILE}"
294
+ sleep 0.5
295
+ dismiss_startup_banner "$PANE"
296
+ ensure_input_ready "$PANE" "@${FINAL_TASK_FILE}"
297
+ tmux send-keys -t "$PANE" "" Enter
298
+ else
299
+ echo "Dispatching via direct send..."
300
+ tmux send-keys -t "$PANE" "$(cat "$FINAL_TASK_FILE")"
301
+ tmux send-keys -t "$PANE" "" Enter
302
+ fi
303
+
304
+ echo "Task dispatched: '${TASK_NAME}'"
305
+ echo "Job ID: ${JOB_ID}"
306
+ echo "Result will appear in: ${INBOX_DIR}/${JOB_ID}.txt"
307
+
308
+ # ── Start background watcher ──────────────────────────────────────────────────
309
+
310
+ if [[ "$NO_PING" == "false" ]]; then
311
+ WATCH_ARGS="$JOB_ID"
312
+ [[ -n "$PING_BACK_PANE" ]] && WATCH_ARGS="$WATCH_ARGS $PING_BACK_PANE"
313
+
314
+ bash "$SCRIPT_DIR/watch-inbox.sh" $WATCH_ARGS &
315
+ WATCHER_PID=$!
316
+ echo "Background watcher started (PID: $WATCHER_PID)"
317
+ fi
318
+
319
+ # ── Show session view ─────────────────────────────────────────────────────────
320
+
321
+ if [[ "$NO_VIEW" == "false" ]]; then
322
+ SESSION=$(tmux display-message -p -t "$PANE" '#S' 2>/dev/null || echo "")
323
+ if [[ -n "$SESSION" ]]; then
324
+ echo ""
325
+ bash "$SCRIPT_DIR/view-session.sh" "$SESSION" "$PANE"
326
+ fi
327
+ fi
328
+
329
+ echo ""
330
+ echo "Task file: $FINAL_TASK_FILE"
331
+ echo "Monitor: tmux capture-pane -t $PANE -p | tail -20"
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bash
2
+ # launch-full-team.sh — 4-pane multi-agent session
3
+ #
4
+ # Layout:
5
+ # ┌──────────────┬──────────────┐
6
+ # │ gemini-1 │ claude │
7
+ # │ (worker A) │ (orchestrat) │
8
+ # ├──────────────┼──────────────┤
9
+ # │ gemini-2 │ codex │
10
+ # │ (worker B) │ (executor) │
11
+ # └──────────────┴──────────────┘
12
+ #
13
+ # Pane targets:
14
+ # full:0.0 — Gemini CLI worker A
15
+ # full:0.1 — Claude Code orchestrator
16
+ # full:0.2 — Gemini CLI worker B
17
+ # full:0.3 — Codex CLI executor
18
+ #
19
+ # Usage:
20
+ # ./scripts/launch-full-team.sh [--session name] [--dir path]
21
+
22
+ set -euo pipefail
23
+
24
+ SESSION="full"
25
+ PROJECT_DIR="${PWD}"
26
+
27
+ while [[ $# -gt 0 ]]; do
28
+ case "$1" in
29
+ --session) SESSION="$2"; shift 2 ;;
30
+ --dir) PROJECT_DIR="$2"; shift 2 ;;
31
+ -h|--help)
32
+ echo "Usage: $0 [--session name] [--dir path]"
33
+ exit 0 ;;
34
+ *) echo "Unknown option: $1"; exit 1 ;;
35
+ esac
36
+ done
37
+
38
+ if tmux has-session -t "$SESSION" 2>/dev/null; then
39
+ echo "Session '$SESSION' already exists. Attaching..."
40
+ tmux attach-session -t "$SESSION"
41
+ exit 0
42
+ fi
43
+
44
+ echo "Creating 4-pane session '$SESSION'..."
45
+
46
+ # Create and tile into 4 panes
47
+ tmux new-session -d -s "$SESSION" -c "$PROJECT_DIR" # pane 0
48
+ tmux split-window -h -t "$SESSION:0" -c "$PROJECT_DIR" # pane 1 (right)
49
+ tmux split-window -v -t "$SESSION:0.0" -c "$PROJECT_DIR" # pane 2 (bottom-left)
50
+ tmux split-window -v -t "$SESSION:0.1" -c "$PROJECT_DIR" # pane 3 (bottom-right)
51
+
52
+ # Label panes
53
+ tmux select-pane -t "$SESSION:0.0" -T "gemini-A"
54
+ tmux select-pane -t "$SESSION:0.1" -T "orchestrator (claude)"
55
+ tmux select-pane -t "$SESSION:0.2" -T "gemini-B"
56
+ tmux select-pane -t "$SESSION:0.3" -T "codex"
57
+
58
+ # Start sub-agents
59
+ tmux send-keys -t "$SESSION:0.0" "gemini" Enter
60
+ tmux send-keys -t "$SESSION:0.2" "gemini" Enter
61
+ tmux send-keys -t "$SESSION:0.3" "codex" Enter
62
+
63
+ # Start orchestrator last
64
+ sleep 1
65
+ tmux send-keys -t "$SESSION:0.1" "claude" Enter
66
+
67
+ tmux select-pane -t "$SESSION:0.1"
68
+
69
+ echo ""
70
+ echo "4-pane session ready."
71
+ echo " Pane 0.0 — Gemini worker A"
72
+ echo " Pane 0.1 — Claude orchestrator"
73
+ echo " Pane 0.2 — Gemini worker B"
74
+ echo " Pane 0.3 — Codex executor"
75
+ echo ""
76
+
77
+ tmux attach-session -t "$SESSION"