@walwal-harness/cli 5.0.2 → 5.0.3

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.3",
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"
@@ -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