@walwal-harness/cli 5.0.2 → 5.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "5.0.2",
3
+ "version": "5.0.4",
4
4
  "description": "Production harness for AI agent engineering — Solo/Team mode, Planner, Generator(BE/FE), Evaluator(Func/Visual), optional Brainstormer. Supports React, Next.js, and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -316,6 +316,46 @@ render_team_bottleneck() {
316
316
  fi
317
317
  }
318
318
 
319
+ # ══════════════════════════════════════════
320
+ # Archive Prompt — 처리 완료된 프롬프트 목록
321
+ # ══════════════════════════════════════════
322
+ render_archive_prompts() {
323
+ if [ ! -f "$PROGRESS_LOG" ]; then return; fi
324
+
325
+ # user-prompt 엔트리 중 이후에 result/pass/completed 액션이 있는 것 = 처리 완료
326
+ local all_prompts
327
+ all_prompts=$(grep '| user-prompt |' "$PROGRESS_LOG" 2>/dev/null)
328
+ if [ -z "$all_prompts" ]; then return; fi
329
+
330
+ local total_prompts
331
+ total_prompts=$(echo "$all_prompts" | wc -l | tr -d ' ')
332
+
333
+ # 마지막 프롬프트를 제외한 모든 프롬프트 = archived (처리 완료)
334
+ local archived
335
+ if [ "$total_prompts" -le 1 ]; then return; fi
336
+ archived=$(echo "$all_prompts" | head -n $((total_prompts - 1)) | tail -8)
337
+
338
+ echo ""
339
+ echo -e "${BOLD}Archive Prompt${RESET} ${DIM}(처리 완료)${RESET}"
340
+
341
+ echo "$archived" | while IFS= read -r line; do
342
+ local ts detail
343
+ ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
344
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
345
+
346
+ local short_ts
347
+ short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
348
+
349
+ if [ ${#detail} -gt 45 ]; then detail="${detail:0:43}.."; fi
350
+
351
+ echo -e " ${GREEN}✓${RESET} ${DIM}${short_ts}${RESET} ${detail}"
352
+ done
353
+
354
+ if [ "$total_prompts" -gt 9 ]; then
355
+ echo -e " ${DIM}… +$((total_prompts - 9)) more${RESET}"
356
+ fi
357
+ }
358
+
319
359
  # ══════════════════════════════════════════
320
360
  # Render dispatch
321
361
  # ══════════════════════════════════════════
@@ -331,6 +371,7 @@ render_dashboard() {
331
371
  render_team_status
332
372
  render_team_features
333
373
  render_team_bottleneck
374
+ render_archive_prompts
334
375
  ;;
335
376
  *)
336
377
  render_solo_sprint_overview
@@ -341,8 +382,7 @@ render_dashboard() {
341
382
  fi
342
383
 
343
384
  render_solo_agent_info
344
- echo ""
345
- render_solo_prompt_history
385
+ render_archive_prompts
346
386
  ;;
347
387
  esac
348
388
  }
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
- # harness-prompt-history.sh — Prompt History pane for tmux layout
3
- # progress.log + audit.log에서 유저 프롬프트/에이전트 활동 히스토리를 표시
2
+ # harness-prompt-history.sh — User Prompt History pane
3
+ # 사용자가 입력한 프롬프트만 표시 + 현재 처리 단계(Planner/Team) 표시
4
4
  # Usage: bash scripts/harness-prompt-history.sh [project-root]
5
5
 
6
6
  set -uo pipefail
@@ -14,7 +14,8 @@ if [ -z "$PROJECT_ROOT" ]; then
14
14
  fi
15
15
 
16
16
  PROGRESS_LOG="$PROJECT_ROOT/.harness/progress.log"
17
- AUDIT_LOG="$PROJECT_ROOT/.harness/actions/audit.log"
17
+ PROGRESS="$PROJECT_ROOT/.harness/progress.json"
18
+ QUEUE="$PROJECT_ROOT/.harness/actions/feature-queue.json"
18
19
 
19
20
  BOLD="\033[1m"
20
21
  DIM="\033[2m"
@@ -23,93 +24,138 @@ YELLOW="\033[33m"
23
24
  RED="\033[31m"
24
25
  CYAN="\033[36m"
25
26
  MAGENTA="\033[35m"
27
+ WHITE="\033[37m"
26
28
  RESET="\033[0m"
29
+ BG_BLUE="\033[44m"
30
+ BG_GREEN="\033[42m"
31
+ BG_YELLOW="\033[43m"
27
32
 
28
33
  render_header() {
29
- echo -e "${BOLD}PROMPT HISTORY${RESET} ${DIM}$(date +%H:%M:%S)${RESET}"
34
+ echo -e "${BOLD}USER PROMPTS${RESET} ${DIM}$(date +%H:%M:%S)${RESET}"
30
35
  echo ""
31
36
  }
32
37
 
33
- render_progress_log() {
38
+ # 현재 처리 상태 배지
39
+ render_processing_status() {
40
+ local mode="solo" current_agent="none" agent_status="pending"
41
+
42
+ if [ -f "$PROGRESS" ] && command -v jq &>/dev/null; then
43
+ mode=$(jq -r '.mode // "solo"' "$PROGRESS" 2>/dev/null)
44
+ current_agent=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null)
45
+ agent_status=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null)
46
+ fi
47
+
48
+ local badge=""
49
+ case "$mode" in
50
+ team)
51
+ # Team 모드: 각 팀의 현재 상태 표시
52
+ badge="${BG_BLUE}${WHITE}${BOLD} TEAM MODE ${RESET}"
53
+ if [ -f "$QUEUE" ]; then
54
+ local team_info=""
55
+ for i in 1 2 3; do
56
+ local t_status t_feature t_phase
57
+ t_status=$(jq -r ".teams[\"$i\"].status // \"idle\"" "$QUEUE" 2>/dev/null)
58
+ t_feature=$(jq -r ".teams[\"$i\"].feature // \"\"" "$QUEUE" 2>/dev/null)
59
+ if [ "$t_status" = "busy" ] && [ -n "$t_feature" ] && [ "$t_feature" != "null" ]; then
60
+ t_phase=$(jq -r --arg f "$t_feature" '.queue.in_progress[$f].phase // "?"' "$QUEUE" 2>/dev/null)
61
+ team_info+=" ${CYAN}T${i}${RESET}:${t_feature}(${t_phase})"
62
+ else
63
+ team_info+=" ${DIM}T${i}:idle${RESET}"
64
+ fi
65
+ done
66
+ echo -e " ${badge}${team_info}"
67
+ else
68
+ echo -e " ${badge}"
69
+ fi
70
+ ;;
71
+ paused)
72
+ echo -e " ${BG_YELLOW}${WHITE}${BOLD} PAUSED ${RESET} ${DIM}/harness-team 또는 /harness-solo${RESET}"
73
+ ;;
74
+ *)
75
+ # Solo 모드: 현재 에이전트 표시
76
+ if [ "$current_agent" != "none" ] && [ "$current_agent" != "null" ]; then
77
+ local agent_color="$RESET"
78
+ case "$current_agent" in
79
+ planner*) agent_color="$YELLOW" ;;
80
+ generator*) agent_color="$GREEN" ;;
81
+ eval*) agent_color="$RED" ;;
82
+ dispatcher*) agent_color="$MAGENTA" ;;
83
+ esac
84
+ local status_icon="·"
85
+ case "$agent_status" in
86
+ running) status_icon="▶" ;;
87
+ completed) status_icon="✓" ;;
88
+ failed) status_icon="✗" ;;
89
+ esac
90
+ echo -e " ${BG_GREEN}${WHITE}${BOLD} SOLO ${RESET} ${agent_color}${status_icon} ${current_agent}${RESET}"
91
+ else
92
+ echo -e " ${BG_GREEN}${WHITE}${BOLD} SOLO ${RESET} ${DIM}대기 중${RESET}"
93
+ fi
94
+ ;;
95
+ esac
96
+ echo ""
97
+ }
98
+
99
+ # 사용자 프롬프트만 필터하여 표시
100
+ render_user_prompts() {
34
101
  if [ ! -f "$PROGRESS_LOG" ]; then
35
- echo -e " ${DIM}(no progress.log yet)${RESET}"
102
+ echo -e " ${DIM}(프롬프트 기록 없음)${RESET}"
36
103
  return
37
104
  fi
38
105
 
39
- grep -v '^#' "$PROGRESS_LOG" 2>/dev/null | grep -v '^$' | tail -20 | while IFS= read -r line; do
40
- local ts agent action detail
41
- ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
42
- agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
43
- action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
44
- detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
106
+ local term_height
107
+ term_height=$(tput lines 2>/dev/null || echo 30)
108
+ local max_lines=$((term_height - 10))
109
+ if [ "$max_lines" -lt 5 ]; then max_lines=5; fi
45
110
 
46
- local short_ts icon color
47
- short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
48
-
49
- case "$agent" in
50
- dispatcher*) icon="▸" ; color="$MAGENTA" ;;
51
- brainstormer) icon="◇" ; color="$CYAN" ;;
52
- planner*) icon="□" ; color="$YELLOW" ;;
53
- generator*) icon="▶" ; color="$GREEN" ;;
54
- eval*) icon="✦" ; color="$RED" ;;
55
- user|manual) icon="★" ; color="$BOLD" ;;
56
- team*) icon="⊕" ; color="$CYAN" ;;
57
- *) icon="·" ; color="$DIM" ;;
58
- esac
59
-
60
- if [ ${#detail} -gt 40 ]; then detail="${detail:0:38}.."; fi
61
-
62
- printf " %b%b%b %b%-8s%b %b%s%b %b%s%b\n" \
63
- "$color" "$icon" "$RESET" \
64
- "$DIM" "$short_ts" "$RESET" \
65
- "$RESET" "$agent" "$RESET" \
66
- "$DIM" "$action" "$RESET"
67
- if [ -n "$detail" ] && [ "$detail" != " " ]; then
68
- echo -e " ${DIM}${detail}${RESET}"
69
- fi
70
- done
71
- }
111
+ # user-prompt 행만 필터
112
+ local prompts
113
+ prompts=$(grep '| user-prompt |' "$PROGRESS_LOG" 2>/dev/null | tail -"$max_lines")
72
114
 
73
- render_audit_tail() {
74
- if [ ! -f "$AUDIT_LOG" ]; then return; fi
115
+ if [ -z "$prompts" ]; then
116
+ echo -e " ${DIM}(사용자 프롬프트 없음)${RESET}"
117
+ return
118
+ fi
75
119
 
76
- echo ""
77
- echo -e "${BOLD}AUDIT${RESET}"
120
+ local line_num=0
121
+ local total_prompts
122
+ total_prompts=$(grep -c '| user-prompt |' "$PROGRESS_LOG" 2>/dev/null || echo 0)
123
+
124
+ echo "$prompts" | while IFS= read -r line; do
125
+ line_num=$((line_num + 1))
78
126
 
79
- grep -v '^#' "$AUDIT_LOG" 2>/dev/null | grep -v '^$' | tail -10 | while IFS= read -r line; do
80
- local ts agent action status target
127
+ local ts detail
81
128
  ts=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$1); print $1}')
82
- agent=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
83
- action=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
84
- status=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
85
- target=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$5); print $5}')
86
-
87
- local color="$RESET" icon="·"
88
- case "$status" in
89
- start) color="$CYAN"; icon="▶" ;;
90
- complete) color="$GREEN"; icon="✓" ;;
91
- fail) color="$RED"; icon="✗" ;;
92
- pass) color="$GREEN"; icon="✓" ;;
93
- skip) color="$DIM"; icon="–" ;;
94
- esac
129
+ detail=$(echo "$line" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
95
130
 
96
131
  local short_ts
97
- short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' || echo "$ts")
132
+ short_ts=$(echo "$ts" | grep -oE '[0-9]{2}:[0-9]{2}' | tail -1 || echo "$ts")
133
+
134
+ # 프롬프트 내용 truncate
135
+ local max_width
136
+ max_width=$(( $(tput cols 2>/dev/null || echo 40) - 12 ))
137
+ if [ "$max_width" -lt 20 ]; then max_width=20; fi
138
+ if [ ${#detail} -gt "$max_width" ]; then
139
+ detail="${detail:0:$((max_width - 2))}.."
140
+ fi
98
141
 
99
- if [ ${#target} -gt 25 ]; then target="${target:0:23}.."; fi
142
+ # 처리 상태 찾기: 이 프롬프트 이후의 첫 번째 에이전트 액션
143
+ local processed_by=""
144
+ local prompt_ts_epoch
145
+ prompt_ts_epoch=$(echo "$ts" | sed 's/[^0-9:]//g')
100
146
 
101
- printf " %b%b%b %b%s%b %-10s %b%s%b\n" \
102
- "$color" "$icon" "$RESET" \
103
- "$DIM" "$short_ts" "$RESET" \
104
- "$agent" \
105
- "$DIM" "$target" "$RESET"
147
+ # 간단히: 마지막 프롬프트인지 확인 → 현재 처리 중 표시
148
+ echo -e " ${DIM}${short_ts}${RESET} ${WHITE}${detail}${RESET}"
106
149
  done
150
+
151
+ echo ""
152
+ echo -e " ${DIM}── ${total_prompts} prompts total ──${RESET}"
107
153
  }
108
154
 
109
155
  render_all() {
110
156
  render_header
111
- render_progress_log
112
- render_audit_tail
157
+ render_processing_status
158
+ render_user_prompts
113
159
  }
114
160
 
115
161
  # ── Main loop ──
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # harness-tmux.sh — Unified Harness Studio tmux layout
2
+ # harness-tmux.sh — Unified Harness Studio layout
3
3
  #
4
4
  # Team Mode (--team):
5
5
  # ┌──────────────┬──────────────┬──────────────┐
@@ -12,14 +12,10 @@
12
12
  # │ Codex) │ │ TEAM 3 │
13
13
  # └──────────────┴──────────────┴──────────────┘
14
14
  #
15
- # Solo Mode (--solo):
16
- # ┌──────────────┬──────────────┬──────────────┐
17
- # │ Dashboard │ Monitor │ Agent │
18
- # │ │ (compact) │ Session │
19
- # ├──────────────┤ │ │
20
- # │ Prompt │ │ │
21
- # │ History │ │ │
22
- # └──────────────┴──────────────┴──────────────┘
15
+ # Rendering strategy:
16
+ # 1. iTerm2 detected → native split panes (no tmux needed)
17
+ # 2. tmux available → tmux session
18
+ # 3. Fallback → open new terminal windows
23
19
  #
24
20
  # Usage:
25
21
  # bash scripts/harness-tmux.sh [project-root] --team
@@ -41,7 +37,22 @@ for arg in "$@"; do
41
37
  --solo) MODE="solo" ;;
42
38
  --detach) DETACH=true ;;
43
39
  --kill)
44
- tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "Killed." || echo "No session."
40
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null && echo "tmux session killed." || true
41
+ # Also try to close iTerm2 studio tab if exists
42
+ if [ "${TERM_PROGRAM:-}" = "iTerm.app" ]; then
43
+ osascript -e '
44
+ tell application "iTerm2"
45
+ repeat with w in windows
46
+ repeat with t in tabs of w
47
+ if name of current session of t contains "harness-studio" then
48
+ close t
49
+ end if
50
+ end repeat
51
+ end repeat
52
+ end tell
53
+ ' 2>/dev/null || true
54
+ fi
55
+ echo "Killed."
45
56
  exit 0
46
57
  ;;
47
58
  *)
@@ -72,134 +83,215 @@ if [ -z "$MODE" ]; then
72
83
  fi
73
84
 
74
85
  echo "Project: $PROJECT_ROOT"
75
- echo "Session: $SESSION_NAME"
76
86
  echo "Mode: $MODE"
77
87
 
78
- tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
79
-
80
88
  # ══════════════════════════════════════════
81
- # Team Mode: 3-column layout (6 panes)
89
+ # Strategy 1: iTerm2 native split panes
82
90
  # ══════════════════════════════════════════
83
- if [ "$MODE" = "team" ]; then
91
+ launch_iterm2_team() {
92
+ osascript <<APPLESCRIPT
93
+ tell application "iTerm2"
94
+ activate
84
95
 
85
- # 1. Create session → Left pane (will become Prompt History)
86
- PANE_LEFT=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 \
87
- -P -F '#{pane_id}')
96
+ -- Create new window with first session (Prompt History)
97
+ set studioWindow to (create window with default profile)
88
98
 
89
- # 2. Split horizontally: Left 30% | Right 70%
90
- PANE_MID=$(tmux split-window -h -p 70 -t "$PANE_LEFT" -c "$PROJECT_ROOT" \
91
- -P -F '#{pane_id}')
99
+ tell studioWindow
100
+ tell current session of current tab
101
+ set name to "harness-studio"
102
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-prompt-history.sh' '${PROJECT_ROOT}'"
92
103
 
93
- # 3. Split right section: Middle 40% | Right 60% (Teams)
94
- PANE_T1=$(tmux split-window -h -p 60 -t "$PANE_MID" -c "$PROJECT_ROOT" \
95
- -P -F '#{pane_id}' \
96
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 1'")
104
+ -- Split right → Dashboard
105
+ set dashPane to (split vertically with default profile)
106
+ tell dashPane
107
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-dashboard.sh' '${PROJECT_ROOT}'"
97
108
 
98
- # 4. Split Team 1 vertically → Team 2 (bottom 66%)
99
- PANE_T2=$(tmux split-window -v -p 66 -t "$PANE_T1" -c "$PROJECT_ROOT" \
100
- -P -F '#{pane_id}' \
101
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 2'")
109
+ -- Split right → Team 1
110
+ set t1Pane to (split vertically with default profile)
111
+ tell t1Pane
112
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-monitor.sh' '${PROJECT_ROOT}' --team 1"
102
113
 
103
- # 5. Split Team 2 vertically → Team 3 (bottom 50%)
104
- PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" \
105
- -P -F '#{pane_id}' \
106
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 3'")
114
+ -- Split down → Team 2
115
+ set t2Pane to (split horizontally with default profile)
116
+ tell t2Pane
117
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-monitor.sh' '${PROJECT_ROOT}' --team 2"
107
118
 
108
- # 6. Split left pane vertically: Prompt History (top 45%) | Controller (bottom 55%)
109
- PANE_CTRL=$(tmux split-window -v -p 55 -t "$PANE_LEFT" -c "$PROJECT_ROOT" \
110
- -P -F '#{pane_id}')
119
+ -- Split down → Team 3
120
+ set t3Pane to (split horizontally with default profile)
121
+ tell t3Pane
122
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-monitor.sh' '${PROJECT_ROOT}' --team 3"
123
+ end tell
124
+ end tell
125
+ end tell
126
+ end tell
111
127
 
112
- # 7. Start Prompt History in left-top
113
- tmux send-keys -t "$PANE_LEFT" "bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"" Enter
128
+ -- Split down → Controller (bottom-left)
129
+ set ctrlPane to (split horizontally with default profile)
130
+ tell ctrlPane
131
+ write text "cd '${PROJECT_ROOT}' && clear"
132
+ end tell
133
+ end tell
134
+ end tell
135
+ end tell
136
+ APPLESCRIPT
137
+ }
114
138
 
115
- # 8. Start Dashboard in middle pane
116
- tmux send-keys -t "$PANE_MID" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
139
+ launch_iterm2_solo() {
140
+ osascript <<APPLESCRIPT
141
+ tell application "iTerm2"
142
+ activate
143
+
144
+ set studioWindow to (create window with default profile)
145
+
146
+ tell studioWindow
147
+ tell current session of current tab
148
+ set name to "harness-studio"
149
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-dashboard.sh' '${PROJECT_ROOT}'"
150
+
151
+ -- Split right → Monitor
152
+ set monPane to (split vertically with default profile)
153
+ tell monPane
154
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-monitor.sh' '${PROJECT_ROOT}'"
155
+
156
+ -- Split right → Agent Session
157
+ set agentPane to (split vertically with default profile)
158
+ tell agentPane
159
+ write text "cd '${PROJECT_ROOT}' && clear"
160
+ end tell
161
+ end tell
117
162
 
118
- # 9. Controller pane — ready for Claude/Codex
119
- tmux send-keys -t "$PANE_CTRL" "unset npm_config_prefix 2>/dev/null" Enter
163
+ -- Split down → Prompt History (bottom-left)
164
+ set histPane to (split horizontally with default profile)
165
+ tell histPane
166
+ write text "cd '${PROJECT_ROOT}' && bash '${SCRIPT_DIR}/harness-prompt-history.sh' '${PROJECT_ROOT}'"
167
+ end tell
168
+ end tell
169
+ end tell
170
+ end tell
171
+ APPLESCRIPT
172
+ }
173
+
174
+ # ══════════════════════════════════════════
175
+ # Strategy 2: tmux
176
+ # ══════════════════════════════════════════
177
+ launch_tmux_team() {
178
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
179
+
180
+ PANE_LEFT=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 220 -y 55 -P -F '#{pane_id}')
181
+ PANE_MID=$(tmux split-window -h -p 70 -t "$PANE_LEFT" -c "$PROJECT_ROOT" -P -F '#{pane_id}')
182
+ PANE_T1=$(tmux split-window -h -p 60 -t "$PANE_MID" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
183
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 1'")
184
+ PANE_T2=$(tmux split-window -v -p 66 -t "$PANE_T1" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
185
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 2'")
186
+ PANE_T3=$(tmux split-window -v -p 50 -t "$PANE_T2" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
187
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\" --team 3'")
188
+ PANE_CTRL=$(tmux split-window -v -p 55 -t "$PANE_LEFT" -c "$PROJECT_ROOT" -P -F '#{pane_id}')
189
+
190
+ tmux send-keys -t "$PANE_LEFT" "bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"" Enter
191
+ tmux send-keys -t "$PANE_MID" "bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"" Enter
120
192
  tmux send-keys -t "$PANE_CTRL" "clear" Enter
121
193
 
122
- # Pane titles
123
194
  tmux select-pane -t "$PANE_LEFT" -T "Prompt History"
124
195
  tmux select-pane -t "$PANE_CTRL" -T "Controller"
125
196
  tmux select-pane -t "$PANE_MID" -T "Dashboard"
126
197
  tmux select-pane -t "$PANE_T1" -T "TEAM 1"
127
198
  tmux select-pane -t "$PANE_T2" -T "TEAM 2"
128
199
  tmux select-pane -t "$PANE_T3" -T "TEAM 3"
129
-
130
- # Focus on Controller
131
200
  tmux select-pane -t "$PANE_CTRL"
132
201
 
133
- # ══════════════════════════════════════════
134
- # Solo Mode: 3-column layout (5 panes)
135
- # ══════════════════════════════════════════
136
- else
202
+ tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
203
+ tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
204
+ }
137
205
 
138
- # 1. Create session → Dashboard pane
139
- PANE_DASHBOARD=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50 \
140
- -P -F '#{pane_id}' \
141
- "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"'")
206
+ launch_tmux_solo() {
207
+ tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true
142
208
 
143
- # 2. Split horizontally: Left 40% | Right 60%
144
- PANE_MONITOR=$(tmux split-window -h -p 60 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" \
145
- -P -F '#{pane_id}' \
209
+ PANE_DASHBOARD=$(tmux new-session -d -s "$SESSION_NAME" -c "$PROJECT_ROOT" -x 200 -y 50 -P -F '#{pane_id}' \
210
+ "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-dashboard.sh\" \"${PROJECT_ROOT}\"'")
211
+ PANE_MONITOR=$(tmux split-window -h -p 60 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
146
212
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-monitor.sh\" \"${PROJECT_ROOT}\"'")
147
-
148
- # 3. Split Dashboard vertically: Dashboard (top 70%) | Prompt History (bottom 30%)
149
- PANE_HISTORY=$(tmux split-window -v -p 30 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" \
150
- -P -F '#{pane_id}' \
213
+ PANE_HISTORY=$(tmux split-window -v -p 30 -t "$PANE_DASHBOARD" -c "$PROJECT_ROOT" -P -F '#{pane_id}' \
151
214
  "bash --norc --noprofile -c 'exec bash \"${SCRIPT_DIR}/harness-prompt-history.sh\" \"${PROJECT_ROOT}\"'")
215
+ PANE_AGENT=$(tmux split-window -v -p 85 -t "$PANE_MONITOR" -c "$PROJECT_ROOT" -P -F '#{pane_id}')
152
216
 
153
- # 4. Split Monitor: Monitor (top 15%) | Agent Session (bottom 85%)
154
- PANE_AGENT=$(tmux split-window -v -p 85 -t "$PANE_MONITOR" -c "$PROJECT_ROOT" \
155
- -P -F '#{pane_id}')
156
-
157
- # 5. Agent Session — empty shell ready for claude
158
- tmux send-keys -t "$PANE_AGENT" "unset npm_config_prefix 2>/dev/null" Enter
159
217
  tmux send-keys -t "$PANE_AGENT" "clear" Enter
160
218
 
161
- # Pane titles
162
219
  tmux select-pane -t "$PANE_DASHBOARD" -T "Dashboard"
163
220
  tmux select-pane -t "$PANE_MONITOR" -T "Monitor"
164
221
  tmux select-pane -t "$PANE_HISTORY" -T "Prompt History"
165
222
  tmux select-pane -t "$PANE_AGENT" -T "Agent Session"
166
-
167
- # Focus on Agent Session
168
223
  tmux select-pane -t "$PANE_AGENT"
224
+
225
+ tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
226
+ tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
227
+ }
228
+
229
+ # ══════════════════════════════════════════
230
+ # Dispatch: pick best strategy
231
+ # ══════════════════════════════════════════
232
+
233
+ # Detect iTerm2 — works even from Claude Code's non-TTY Bash
234
+ IS_ITERM=false
235
+ if [ "${TERM_PROGRAM:-}" = "iTerm.app" ]; then
236
+ IS_ITERM=true
237
+ elif [ "$(uname)" = "Darwin" ]; then
238
+ # Check if iTerm2 is running (for non-TTY environments like Claude Code)
239
+ if pgrep -q "iTerm2" 2>/dev/null; then
240
+ IS_ITERM=true
241
+ fi
169
242
  fi
170
243
 
171
- # ── Common settings ──
172
- tmux set-option -t "$SESSION_NAME" pane-border-status top 2>/dev/null || true
173
- tmux set-option -t "$SESSION_NAME" pane-border-format " #{pane_title} " 2>/dev/null || true
244
+ HAS_TMUX=false
245
+ if command -v tmux &>/dev/null; then
246
+ HAS_TMUX=true
247
+ fi
174
248
 
175
- # ── Attach ──
176
- if [ "$DETACH" = true ]; then
177
- echo ""
178
- echo "Session created. Attach: tmux attach -t $SESSION_NAME"
249
+ if [ "$IS_ITERM" = true ]; then
250
+ echo "Strategy: iTerm2 native split panes"
251
+ if [ "$MODE" = "team" ]; then
252
+ launch_iterm2_team
253
+ else
254
+ launch_iterm2_solo
255
+ fi
179
256
  echo "Layout ready"
180
- elif ! tty -s 2>/dev/null; then
181
- # Non-terminal environment (e.g., Claude Code agent session)
182
- # Try opening Terminal.app on macOS, otherwise detach silently
183
- if [ "$(uname)" = "Darwin" ]; then
184
- osascript -e "
185
- tell application \"Terminal\"
186
- do script \"tmux attach -t $SESSION_NAME\"
187
- activate
188
- end tell
189
- " 2>/dev/null && \
190
- echo "OPENED_TERMINAL=true" || echo "Layout ready"
257
+
258
+ elif [ "$HAS_TMUX" = true ]; then
259
+ echo "Strategy: tmux"
260
+ if [ "$MODE" = "team" ]; then
261
+ launch_tmux_team
191
262
  else
192
- echo "Session created (non-terminal). Attach: tmux attach -t $SESSION_NAME"
193
- echo "Layout ready"
263
+ launch_tmux_solo
194
264
  fi
195
- else
196
- if [ -n "${TMUX:-}" ]; then
197
- tmux switch-client -t "$SESSION_NAME"
265
+
266
+ # Attach to tmux
267
+ if [ "$DETACH" = true ]; then
268
+ echo "Session created. Attach: tmux attach -t $SESSION_NAME"
269
+ echo "Layout ready"
270
+ elif ! tty -s 2>/dev/null; then
271
+ # Non-TTY (e.g., Claude Code Bash tool) — open in new terminal
272
+ if [ "$(uname)" = "Darwin" ]; then
273
+ osascript -e "
274
+ tell application \"Terminal\"
275
+ do script \"tmux attach -t $SESSION_NAME\"
276
+ activate
277
+ end tell
278
+ " 2>/dev/null && echo "OPENED_TERMINAL=true" || echo "Layout ready"
279
+ else
280
+ echo "Session created. Attach: tmux attach -t $SESSION_NAME"
281
+ echo "Layout ready"
282
+ fi
198
283
  else
199
- echo ""
200
- echo "Launching Harness Studio ($MODE mode)..."
201
- echo " Stop: bash scripts/harness-tmux.sh --kill"
202
- echo ""
203
- tmux attach -t "$SESSION_NAME"
284
+ if [ -n "${TMUX:-}" ]; then
285
+ tmux switch-client -t "$SESSION_NAME"
286
+ else
287
+ echo "Launching Harness Studio ($MODE mode)..."
288
+ tmux attach -t "$SESSION_NAME"
289
+ fi
204
290
  fi
291
+
292
+ else
293
+ echo "ERROR: Neither iTerm2 nor tmux found."
294
+ echo "Install tmux: brew install tmux"
295
+ echo "Or use iTerm2: https://iterm2.com"
296
+ exit 1
205
297
  fi