@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 +1 -1
- package/scripts/harness-tmux.sh +189 -97
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "5.0.
|
|
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"
|
package/scripts/harness-tmux.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# harness-tmux.sh — Unified Harness Studio
|
|
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
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
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 "
|
|
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
|
-
#
|
|
89
|
+
# Strategy 1: iTerm2 native split panes
|
|
82
90
|
# ══════════════════════════════════════════
|
|
83
|
-
|
|
91
|
+
launch_iterm2_team() {
|
|
92
|
+
osascript <<APPLESCRIPT
|
|
93
|
+
tell application "iTerm2"
|
|
94
|
+
activate
|
|
84
95
|
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
244
|
+
HAS_TMUX=false
|
|
245
|
+
if command -v tmux &>/dev/null; then
|
|
246
|
+
HAS_TMUX=true
|
|
247
|
+
fi
|
|
174
248
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if [ "$
|
|
184
|
-
|
|
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
|
-
|
|
193
|
-
echo "Layout ready"
|
|
263
|
+
launch_tmux_solo
|
|
194
264
|
fi
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|