@humanu/orchestra 0.5.65 → 0.5.69
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/resources/api/tmux.sh +303 -130
- package/resources/prebuilt/linux-x64/gw-env-copy +0 -0
- package/resources/prebuilt/linux-x64/orchestra +0 -0
- package/resources/prebuilt/macos-arm64/gw-env-copy +0 -0
- package/resources/prebuilt/macos-arm64/orchestra +0 -0
- package/resources/prebuilt/macos-intel/gw-env-copy +0 -0
- package/resources/prebuilt/macos-intel/orchestra +0 -0
- package/resources/scripts/gw-bridge.sh +2 -2
- package/resources/scripts/orchestra-local.sh +4 -0
- package/resources/scripts/shell/bridge/ai.sh +18 -53
- package/resources/scripts/shell/bridge/tmux.sh +23 -3
- package/resources/scripts/shell/build/build_dependencies.sh +10 -0
- package/resources/scripts/shell/gwr_usage.sh +8 -4
- package/resources/scripts/shell/orchestra-command-hook.sh +151 -1
- package/resources/scripts/shell/orchestra-local.sh +4 -0
package/package.json
CHANGED
package/resources/api/tmux.sh
CHANGED
|
@@ -109,6 +109,57 @@ _tmux_normalize_app_from_command() {
|
|
|
109
109
|
printf '%s' "$base"
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
_tmux_truthy() {
|
|
113
|
+
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
|
|
114
|
+
1|yes|true|on)
|
|
115
|
+
return 0
|
|
116
|
+
;;
|
|
117
|
+
esac
|
|
118
|
+
return 1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_tmux_is_known_tui_app() {
|
|
122
|
+
local app
|
|
123
|
+
app="$(_tmux_normalize_app_from_command "$1")"
|
|
124
|
+
case "$app" in
|
|
125
|
+
opencode|claude|vim|nvim|vi|lazygit|gitui|tig|top|htop|btop|k9s|fzf|yazi|ranger|nnn|less|man)
|
|
126
|
+
return 0
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
return 1
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_tmux_is_tui_context() {
|
|
133
|
+
local pane_cmd="$1"
|
|
134
|
+
local alternate_on="$2"
|
|
135
|
+
local mouse_any_flag="$3"
|
|
136
|
+
local pane_mode="$4"
|
|
137
|
+
local window_name="$5"
|
|
138
|
+
local pane_title="$6"
|
|
139
|
+
|
|
140
|
+
if _tmux_truthy "$alternate_on" || _tmux_truthy "$mouse_any_flag"; then
|
|
141
|
+
return 0
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
if [[ -n "$pane_mode" ]]; then
|
|
145
|
+
return 0
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if _tmux_is_known_tui_app "$pane_cmd"; then
|
|
149
|
+
return 0
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
local combined
|
|
153
|
+
combined="$(printf '%s %s' "$window_name" "$pane_title" | tr '[:upper:]' '[:lower:]')"
|
|
154
|
+
case "$combined" in
|
|
155
|
+
*opencode*|*claude*|*vim*|*nvim*|*lazygit*|*gitui*|*tig*|*k9s*|*fzf*|*yazi*|*ranger*|*nnn*)
|
|
156
|
+
return 0
|
|
157
|
+
;;
|
|
158
|
+
esac
|
|
159
|
+
|
|
160
|
+
return 1
|
|
161
|
+
}
|
|
162
|
+
|
|
112
163
|
# Helper: absolute path to the command hook script (if present)
|
|
113
164
|
_orchestra_command_hook() {
|
|
114
165
|
local hook=""
|
|
@@ -145,6 +196,10 @@ _orchestra_command_hook() {
|
|
|
145
196
|
echo ""
|
|
146
197
|
}
|
|
147
198
|
|
|
199
|
+
_orchestra_bridge_script() {
|
|
200
|
+
echo "$(dirname "$_TMUX_API_DIR")/gw-bridge.sh"
|
|
201
|
+
}
|
|
202
|
+
|
|
148
203
|
# Source the command hook inside a tmux session to enable command history logging
|
|
149
204
|
_tmux_source_command_hook() {
|
|
150
205
|
local session="$1"
|
|
@@ -156,6 +211,8 @@ _tmux_source_command_hook() {
|
|
|
156
211
|
if ! tmux_session_exists "$session"; then
|
|
157
212
|
return
|
|
158
213
|
fi
|
|
214
|
+
local bridge
|
|
215
|
+
bridge="$(_orchestra_bridge_script)"
|
|
159
216
|
local panes
|
|
160
217
|
panes=$(tmux list-panes -t "$session" -F '#{pane_id} #{pane_current_command}' 2>/dev/null || echo "")
|
|
161
218
|
if [[ -z "$panes" ]]; then
|
|
@@ -168,7 +225,7 @@ _tmux_source_command_hook() {
|
|
|
168
225
|
pane_cmd="${line#* }"
|
|
169
226
|
case "$pane_cmd" in
|
|
170
227
|
bash|zsh)
|
|
171
|
-
tmux send-keys -t "$pane_id" ". '$hook'" C-m 2>/dev/null || true
|
|
228
|
+
tmux send-keys -t "$pane_id" "export ORCHESTRA_BRIDGE_PATH='$bridge'; . '$hook'" C-m 2>/dev/null || true
|
|
172
229
|
;;
|
|
173
230
|
*)
|
|
174
231
|
;;
|
|
@@ -179,6 +236,51 @@ _tmux_source_command_hook() {
|
|
|
179
236
|
# Helper: orchestra prefix including delimiter
|
|
180
237
|
_tmux_orch_prefix() { printf "orchestra%s" "$( _tmux_delim )"; }
|
|
181
238
|
|
|
239
|
+
_tmux_status_escape_text() {
|
|
240
|
+
local text="$1"
|
|
241
|
+
text="${text//\#/##}"
|
|
242
|
+
printf '%s' "$text"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
_tmux_orchestra_status_left() {
|
|
246
|
+
local worktree_name="$1"
|
|
247
|
+
local session_display_name="$2"
|
|
248
|
+
worktree_name="$(_tmux_status_escape_text "$worktree_name")"
|
|
249
|
+
session_display_name="$(_tmux_status_escape_text "$session_display_name")"
|
|
250
|
+
printf '#[fg=white,bg=colour22,bold] %s #[default] Rename: Ctrl+b,r | Go back (detach): Ctrl+b,d | Copy (scroll) mode: Ctrl+b,[ | Worktree: %s' "$session_display_name" "$worktree_name"
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_tmux_configure_orchestra_bindings() {
|
|
254
|
+
local bridge
|
|
255
|
+
bridge="$(_orchestra_bridge_script)"
|
|
256
|
+
[[ -f "$bridge" ]] || return
|
|
257
|
+
|
|
258
|
+
local quoted_bridge rename_command prompt_command
|
|
259
|
+
printf -v quoted_bridge '%q' "$bridge"
|
|
260
|
+
rename_command="$quoted_bridge manual-rename-session \\\"#{session_name}\\\" \\\"%%\\\" >/dev/null 2>&1"
|
|
261
|
+
prompt_command="command-prompt -p 'Rename Orchestra session:' 'run-shell -b \"$rename_command\"'"
|
|
262
|
+
|
|
263
|
+
tmux bind-key -T prefix r if-shell -F '#{@orchestra_display_name}' \
|
|
264
|
+
"$prompt_command" 'refresh-client -S' >/dev/null 2>&1 || true
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
_tmux_configure_orchestra_status() {
|
|
268
|
+
local session_name="$1"
|
|
269
|
+
local worktree_name="$2"
|
|
270
|
+
local session_display_name="${3:-}"
|
|
271
|
+
local status_left
|
|
272
|
+
if [[ -z "$session_display_name" ]]; then
|
|
273
|
+
session_display_name="$(tmux_format_session_display "$session_name" without-timestamp)"
|
|
274
|
+
fi
|
|
275
|
+
status_left="$(_tmux_orchestra_status_left "$worktree_name" "$session_display_name")"
|
|
276
|
+
|
|
277
|
+
tmux set-option -t "$session_name" @orchestra_display_name "$session_display_name" >/dev/null 2>&1 || true
|
|
278
|
+
tmux set-option -t "$session_name" @orchestra_worktree_name "$worktree_name" >/dev/null 2>&1 || true
|
|
279
|
+
tmux set-option -t "$session_name" status-left "$status_left" >/dev/null 2>&1 || true
|
|
280
|
+
tmux set-option -t "$session_name" status-left-length 220 >/dev/null 2>&1 || true
|
|
281
|
+
_tmux_configure_orchestra_bindings
|
|
282
|
+
}
|
|
283
|
+
|
|
182
284
|
# Helper: split a string by multi-char delimiter into bash array named by ref
|
|
183
285
|
# Usage: _tmux_split_by_delim "string" "::" out_array_name
|
|
184
286
|
_tmux_split_by_delim() {
|
|
@@ -289,10 +391,7 @@ tmux_create_session() {
|
|
|
289
391
|
else
|
|
290
392
|
worktree_name="$(basename "$working_dir")"
|
|
291
393
|
fi
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
# Increase status-left length to accommodate the message
|
|
295
|
-
tmux set-option -t "$session_name" status-left-length 120 >/dev/null 2>&1 || true
|
|
394
|
+
_tmux_configure_orchestra_status "$session_name" "$worktree_name"
|
|
296
395
|
fi
|
|
297
396
|
|
|
298
397
|
echo "$session_name"
|
|
@@ -432,8 +531,7 @@ tmux_attach_session() {
|
|
|
432
531
|
worktree_name="$branch_name"
|
|
433
532
|
fi
|
|
434
533
|
fi
|
|
435
|
-
|
|
436
|
-
tmux set-option -t "$sess" status-left-length 120 >/dev/null 2>&1 || true
|
|
534
|
+
_tmux_configure_orchestra_status "$sess" "$worktree_name"
|
|
437
535
|
|
|
438
536
|
if tmux_inside_session; then
|
|
439
537
|
tmux switch-client -t "$sess" >/dev/null 2>&1 || true
|
|
@@ -511,6 +609,24 @@ tmux_rename_session() {
|
|
|
511
609
|
err "Failed to rename session"
|
|
512
610
|
return 1
|
|
513
611
|
}
|
|
612
|
+
local worktree_name
|
|
613
|
+
worktree_name="$(tmux show-option -t "$new_session" -qv @orchestra_worktree_name 2>/dev/null || true)"
|
|
614
|
+
if [[ -z "$worktree_name" ]]; then
|
|
615
|
+
local session_dir old_pwd branch_name
|
|
616
|
+
session_dir="$(tmux display-message -t "$new_session" -p '#{pane_current_path}' 2>/dev/null || echo "")"
|
|
617
|
+
if [[ -n "$session_dir" && -d "$session_dir" ]]; then
|
|
618
|
+
old_pwd="$PWD"
|
|
619
|
+
cd "$session_dir" 2>/dev/null || true
|
|
620
|
+
branch_name="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")"
|
|
621
|
+
cd "$old_pwd" 2>/dev/null || true
|
|
622
|
+
if [[ -n "$branch_name" && "$branch_name" != "detached" ]]; then
|
|
623
|
+
worktree_name="$branch_name"
|
|
624
|
+
else
|
|
625
|
+
worktree_name="$(basename "$session_dir")"
|
|
626
|
+
fi
|
|
627
|
+
fi
|
|
628
|
+
fi
|
|
629
|
+
_tmux_configure_orchestra_status "$new_session" "$worktree_name"
|
|
514
630
|
>&2 echo "✏️ Renamed session to: $new_name"
|
|
515
631
|
return 0
|
|
516
632
|
}
|
|
@@ -595,66 +711,119 @@ tmux_find_session() {
|
|
|
595
711
|
|
|
596
712
|
# Helper function to format session display names
|
|
597
713
|
# Input: session_content (e.g., "opencode_fixing_auth_bug" or "my_feature_work")
|
|
598
|
-
# Output: "Opencode
|
|
714
|
+
# Output: "Opencode Fixing Auth Bug" or "My Feature Work"
|
|
599
715
|
format_session_display_name() {
|
|
600
716
|
local session_content="$1"
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
local
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
# Check if session starts with a known app prefix
|
|
608
|
-
for app in "${known_apps[@]}"; do
|
|
609
|
-
if [[ "$session_content" =~ ^${app}_ ]]; then
|
|
610
|
-
app_prefix="$app"
|
|
611
|
-
# Remove the app prefix and following underscore
|
|
612
|
-
description="${session_content#${app}_}"
|
|
613
|
-
break
|
|
614
|
-
fi
|
|
615
|
-
done
|
|
616
|
-
|
|
617
|
-
# If no app prefix found, treat whole string as description
|
|
618
|
-
if [[ -z "$app_prefix" ]]; then
|
|
619
|
-
description="$session_content"
|
|
717
|
+
local d
|
|
718
|
+
d="$(_tmux_delim)"
|
|
719
|
+
local ORCH_PREFIX
|
|
720
|
+
ORCH_PREFIX="$(_tmux_orch_prefix)"
|
|
721
|
+
if [[ "$session_content" == ${ORCH_PREFIX}* ]]; then
|
|
722
|
+
session_content="${session_content#${ORCH_PREFIX}}"
|
|
620
723
|
fi
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
724
|
+
if [[ "$session_content" == auto_* ]]; then
|
|
725
|
+
session_content="${session_content#auto_}"
|
|
726
|
+
fi
|
|
727
|
+
local description
|
|
728
|
+
description="$(echo "$session_content" | tr '_-' ' ')"
|
|
626
729
|
description="$(echo "$description" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2));}1')"
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
730
|
+
echo "$description"
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
_tmux_format_session_timestamp() {
|
|
734
|
+
local date_part="$1"
|
|
735
|
+
local time_part="$2"
|
|
736
|
+
|
|
737
|
+
if [[ ! "$date_part" =~ ^[0-9]{8}$ || ! "$time_part" =~ ^[0-9]{6}$ ]]; then
|
|
738
|
+
printf '%s %s\n' "$date_part" "$time_part"
|
|
739
|
+
return
|
|
740
|
+
fi
|
|
741
|
+
|
|
742
|
+
local month day hour minute ampm display_hour
|
|
743
|
+
month=$((10#${date_part:4:2}))
|
|
744
|
+
day=$((10#${date_part:6:2}))
|
|
745
|
+
hour=$((10#${time_part:0:2}))
|
|
746
|
+
minute="${time_part:2:2}"
|
|
747
|
+
|
|
748
|
+
ampm="am"
|
|
749
|
+
display_hour="$hour"
|
|
750
|
+
if (( hour >= 12 )); then
|
|
751
|
+
ampm="pm"
|
|
752
|
+
if (( hour > 12 )); then
|
|
753
|
+
display_hour=$((hour - 12))
|
|
754
|
+
fi
|
|
635
755
|
fi
|
|
756
|
+
if (( hour == 0 )); then
|
|
757
|
+
display_hour=12
|
|
758
|
+
fi
|
|
759
|
+
|
|
760
|
+
local month_names=("" "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec")
|
|
761
|
+
local month_name="???"
|
|
762
|
+
if (( month > 0 && month <= 12 )); then
|
|
763
|
+
month_name="${month_names[$month]}"
|
|
764
|
+
fi
|
|
765
|
+
|
|
766
|
+
printf '%s %s %s:%s%s\n' "$month_name" "$day" "$display_hour" "$minute" "$ampm"
|
|
636
767
|
}
|
|
637
768
|
|
|
638
769
|
# Parse session name and format for display
|
|
639
770
|
# Input: session_name (format: worktreename_repohash_YYYYMMDD_HHMMSS_readable_name or old formats)
|
|
640
|
-
# Output:
|
|
771
|
+
# Output: formatted display name
|
|
641
772
|
# Usage: tmux_format_session_display <session_name>
|
|
642
773
|
tmux_format_session_display() {
|
|
643
774
|
local session_name="$1"
|
|
644
|
-
|
|
775
|
+
local timestamp_mode="${2:-with-timestamp}"
|
|
776
|
+
|
|
645
777
|
# Handle temporary renaming sessions first
|
|
646
778
|
if [[ "$session_name" =~ _renaming$ ]]; then
|
|
647
779
|
# Extract base name before _renaming suffix
|
|
648
780
|
local base_name="${session_name%_renaming}"
|
|
649
781
|
# Recursively process the base name to get proper display
|
|
650
|
-
tmux_format_session_display "$base_name"
|
|
782
|
+
tmux_format_session_display "$base_name" "$timestamp_mode"
|
|
651
783
|
return $?
|
|
652
784
|
fi
|
|
653
785
|
|
|
654
|
-
|
|
655
|
-
|
|
786
|
+
local d ORCH_PREFIX
|
|
787
|
+
d="$(_tmux_delim)"
|
|
788
|
+
ORCH_PREFIX="$(_tmux_orch_prefix)"
|
|
789
|
+
|
|
790
|
+
if [[ "$session_name" == ${ORCH_PREFIX}* ]]; then
|
|
791
|
+
session_name="${session_name#${ORCH_PREFIX}}"
|
|
792
|
+
elif [[ "$session_name" =~ ^orchestra_(.+)$ ]]; then
|
|
656
793
|
session_name="${BASH_REMATCH[1]}"
|
|
657
794
|
fi
|
|
795
|
+
|
|
796
|
+
local parts
|
|
797
|
+
_tmux_split_by_delim "$session_name" "$d" parts
|
|
798
|
+
local idx_time=-1 idx_date=-1 i
|
|
799
|
+
for (( i=${#parts[@]}-1; i>=0; i-- )); do
|
|
800
|
+
if [[ ${parts[$i]} =~ ^[0-9]{6}$ ]] && (( i > 0 )) && [[ ${parts[$((i-1))]} =~ ^[0-9]{8}$ ]]; then
|
|
801
|
+
idx_time=$i
|
|
802
|
+
idx_date=$((i-1))
|
|
803
|
+
break
|
|
804
|
+
fi
|
|
805
|
+
done
|
|
806
|
+
if (( idx_time >= 0 && idx_date >= 0 )); then
|
|
807
|
+
local readable_name=""
|
|
808
|
+
local j
|
|
809
|
+
for (( j=idx_time+1; j<${#parts[@]}; j++ )); do
|
|
810
|
+
if [[ -n "$readable_name" ]]; then
|
|
811
|
+
readable_name+="$d"
|
|
812
|
+
fi
|
|
813
|
+
readable_name+="${parts[$j]}"
|
|
814
|
+
done
|
|
815
|
+
if [[ -n "$readable_name" ]]; then
|
|
816
|
+
local formatted_name timestamp
|
|
817
|
+
formatted_name="$(format_session_display_name "$readable_name")"
|
|
818
|
+
timestamp="$(_tmux_format_session_timestamp "${parts[$idx_date]}" "${parts[$idx_time]}")"
|
|
819
|
+
if [[ "$timestamp_mode" == "without-timestamp" ]]; then
|
|
820
|
+
echo "$formatted_name"
|
|
821
|
+
else
|
|
822
|
+
echo "${formatted_name} (${timestamp})"
|
|
823
|
+
fi
|
|
824
|
+
return 0
|
|
825
|
+
fi
|
|
826
|
+
fi
|
|
658
827
|
|
|
659
828
|
# New format with repo hash: worktreename_repohash_YYYYMMDD_HHMMSS_readable_name
|
|
660
829
|
if [[ "$session_name" =~ ^[^_]+_[a-f0-9]{8}_([0-9]{8})_([0-9]{6})_(.+)$ ]]; then
|
|
@@ -696,13 +865,17 @@ tmux_format_session_display() {
|
|
|
696
865
|
|
|
697
866
|
# Format the readable name with proper formatting
|
|
698
867
|
local formatted_name="$(format_session_display_name "$readable_name")"
|
|
699
|
-
|
|
700
|
-
|
|
868
|
+
|
|
869
|
+
if [[ "$timestamp_mode" == "without-timestamp" ]]; then
|
|
870
|
+
echo "$formatted_name"
|
|
871
|
+
else
|
|
872
|
+
echo "${formatted_name} (${month_name} ${display_day} ${display_hour}:${minute}${ampm})"
|
|
873
|
+
fi
|
|
701
874
|
# Fallback: Try format without repo hash: appname_description_YYYYMMDD_HHMMSS
|
|
702
875
|
elif [[ "$session_name" =~ ^(.*)_[0-9]{8}_[0-9]{6}$ ]]; then
|
|
703
876
|
local session_content="${BASH_REMATCH[1]}" # appname_description
|
|
704
877
|
|
|
705
|
-
# Convert to proper display format
|
|
878
|
+
# Convert to proper display format
|
|
706
879
|
local formatted_name="$(format_session_display_name "$session_content")"
|
|
707
880
|
echo "$formatted_name"
|
|
708
881
|
else
|
|
@@ -746,8 +919,12 @@ tmux_format_session_display() {
|
|
|
746
919
|
|
|
747
920
|
# Format the readable name with proper formatting
|
|
748
921
|
local formatted_name="$(format_session_display_name "$readable_name")"
|
|
749
|
-
|
|
750
|
-
|
|
922
|
+
|
|
923
|
+
if [[ "$timestamp_mode" == "without-timestamp" ]]; then
|
|
924
|
+
echo "$formatted_name"
|
|
925
|
+
else
|
|
926
|
+
echo "${formatted_name} (${month_name} ${display_day} ${display_hour}:${minute}${ampm})"
|
|
927
|
+
fi
|
|
751
928
|
else
|
|
752
929
|
# Fallback for sessions that don't match either format
|
|
753
930
|
echo "$session_name"
|
|
@@ -1045,7 +1222,6 @@ NODE
|
|
|
1045
1222
|
# Usage: tmux_generate_ai_session_name <session_name>
|
|
1046
1223
|
tmux_generate_ai_session_name() {
|
|
1047
1224
|
local session="$1"
|
|
1048
|
-
TMUX_AI_APP_SOURCE=""
|
|
1049
1225
|
|
|
1050
1226
|
# Load AI provider config and API keys
|
|
1051
1227
|
tmux_load_ai_primary_provider
|
|
@@ -1077,20 +1253,17 @@ tmux_generate_ai_session_name() {
|
|
|
1077
1253
|
|
|
1078
1254
|
local openai_model="${OPENAI_MODEL:-gpt-4o-mini}"
|
|
1079
1255
|
|
|
1080
|
-
|
|
1081
|
-
local content
|
|
1082
|
-
content="$(tmux capture-pane -t "$session" -p -S -200 2>/dev/null)" || {
|
|
1083
|
-
err "Failed to capture tmux pane content"
|
|
1084
|
-
return 1
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
local pane_cmd
|
|
1256
|
+
local pane_cmd pane_mode window_name pane_title alternate_on mouse_any_flag
|
|
1088
1257
|
pane_cmd="$(tmux display-message -t "$session" -p '#{pane_current_command}' 2>/dev/null || echo "")"
|
|
1258
|
+
pane_mode="$(tmux display-message -t "$session" -p '#{pane_mode}' 2>/dev/null || echo "")"
|
|
1259
|
+
window_name="$(tmux display-message -t "$session" -p '#{window_name}' 2>/dev/null || echo "")"
|
|
1260
|
+
pane_title="$(tmux display-message -t "$session" -p '#{pane_title}' 2>/dev/null || echo "")"
|
|
1261
|
+
alternate_on="$(tmux display-message -t "$session" -p '#{alternate_on}' 2>/dev/null || echo "0")"
|
|
1262
|
+
mouse_any_flag="$(tmux display-message -t "$session" -p '#{mouse_any_flag}' 2>/dev/null || echo "0")"
|
|
1089
1263
|
|
|
1090
1264
|
local history_temp=""
|
|
1091
1265
|
local history_arg=""
|
|
1092
1266
|
local history_dir="${ORCHESTRA_HISTORY_DIR:-$HOME/.orchestra/history}"
|
|
1093
|
-
local app_prefix=""
|
|
1094
1267
|
local pane_id
|
|
1095
1268
|
pane_id="$(tmux display-message -t "$session" -p '#{pane_id}' 2>/dev/null || echo "")"
|
|
1096
1269
|
local pane_key=""
|
|
@@ -1100,18 +1273,25 @@ tmux_generate_ai_session_name() {
|
|
|
1100
1273
|
local session_key
|
|
1101
1274
|
session_key="$(_orchestra_history_key "$session")"
|
|
1102
1275
|
|
|
1103
|
-
local
|
|
1104
|
-
if
|
|
1105
|
-
|
|
1106
|
-
elif [[ -n "$session_key" && -f "$history_dir/$session_key.last_app" ]]; then
|
|
1107
|
-
app_source_path="$history_dir/$session_key.last_app"
|
|
1108
|
-
fi
|
|
1109
|
-
if [[ -n "$app_source_path" ]]; then
|
|
1110
|
-
IFS= read -r app_prefix < "$app_source_path" || true
|
|
1276
|
+
local is_tui="false"
|
|
1277
|
+
if _tmux_is_tui_context "$pane_cmd" "$alternate_on" "$mouse_any_flag" "$pane_mode" "$window_name" "$pane_title"; then
|
|
1278
|
+
is_tui="true"
|
|
1111
1279
|
fi
|
|
1112
1280
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1281
|
+
local content=""
|
|
1282
|
+
local visible_content=""
|
|
1283
|
+
if [[ "$is_tui" == "true" ]]; then
|
|
1284
|
+
visible_content="$(tmux capture-pane -e -p -q -a -t "${pane_id:-$session}" 2>/dev/null || echo "")"
|
|
1285
|
+
fi
|
|
1286
|
+
local scrollback_content
|
|
1287
|
+
scrollback_content="$(tmux capture-pane -t "$session" -p -S -200 2>/dev/null)" || {
|
|
1288
|
+
err "Failed to capture tmux pane content"
|
|
1289
|
+
return 1
|
|
1290
|
+
}
|
|
1291
|
+
if [[ -n "$visible_content" ]]; then
|
|
1292
|
+
content="### Visible terminal view\n${visible_content}\n\n### Recent scrollback\n${scrollback_content}"
|
|
1293
|
+
else
|
|
1294
|
+
content="$scrollback_content"
|
|
1115
1295
|
fi
|
|
1116
1296
|
|
|
1117
1297
|
local history_path=""
|
|
@@ -1131,6 +1311,16 @@ tmux_generate_ai_session_name() {
|
|
|
1131
1311
|
history_arg="$history_temp"
|
|
1132
1312
|
fi
|
|
1133
1313
|
|
|
1314
|
+
local metadata_block=""
|
|
1315
|
+
metadata_block+="Session metadata:\n"
|
|
1316
|
+
metadata_block+="- pane_current_command: ${pane_cmd:-unknown}\n"
|
|
1317
|
+
metadata_block+="- window_name: ${window_name:-}\n"
|
|
1318
|
+
metadata_block+="- pane_title: ${pane_title:-}\n"
|
|
1319
|
+
metadata_block+="- pane_mode: ${pane_mode:-}\n"
|
|
1320
|
+
metadata_block+="- alternate_screen_active: ${alternate_on:-0}\n"
|
|
1321
|
+
metadata_block+="- mouse_mode_active: ${mouse_any_flag:-0}\n"
|
|
1322
|
+
metadata_block+="- likely_tui_app: ${is_tui}\n"
|
|
1323
|
+
|
|
1134
1324
|
# If content is empty or too short, keep a placeholder
|
|
1135
1325
|
if [[ ${#content} -lt 10 ]]; then
|
|
1136
1326
|
content="(no terminal output captured)"
|
|
@@ -1153,11 +1343,10 @@ tmux_generate_ai_session_name() {
|
|
|
1153
1343
|
|
|
1154
1344
|
# Prepare the API request using Python for proper JSON encoding
|
|
1155
1345
|
local request_body=""
|
|
1156
|
-
local preloaded_app_prefix="$app_prefix"
|
|
1157
1346
|
if have_cmd python3; then
|
|
1158
1347
|
# Use Python to safely build the request body and extract typed commands from the capture
|
|
1159
1348
|
request_payload=$(
|
|
1160
|
-
python3 - "$temp_file" "$history_arg" "$provider" "$openai_model" 2>/dev/null <<'PYCODE'
|
|
1349
|
+
python3 - "$temp_file" "$history_arg" "$provider" "$openai_model" "$pane_cmd" "$window_name" "$pane_title" "$pane_mode" "$alternate_on" "$mouse_any_flag" "$is_tui" 2>/dev/null <<'PYCODE'
|
|
1161
1350
|
import json
|
|
1162
1351
|
import re
|
|
1163
1352
|
import sys
|
|
@@ -1168,11 +1357,17 @@ content = temp_path.read_text()
|
|
|
1168
1357
|
history_arg = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
1169
1358
|
provider = sys.argv[3] if len(sys.argv) > 3 else "anthropic"
|
|
1170
1359
|
openai_model = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else "gpt-4o-mini"
|
|
1360
|
+
pane_cmd = sys.argv[5] if len(sys.argv) > 5 else ""
|
|
1361
|
+
window_name = sys.argv[6] if len(sys.argv) > 6 else ""
|
|
1362
|
+
pane_title = sys.argv[7] if len(sys.argv) > 7 else ""
|
|
1363
|
+
pane_mode = sys.argv[8] if len(sys.argv) > 8 else ""
|
|
1364
|
+
alternate_on = sys.argv[9] if len(sys.argv) > 9 else "0"
|
|
1365
|
+
mouse_any_flag = sys.argv[10] if len(sys.argv) > 10 else "0"
|
|
1366
|
+
is_tui = sys.argv[11] if len(sys.argv) > 11 else "false"
|
|
1171
1367
|
|
|
1172
1368
|
lines = content.splitlines()
|
|
1173
1369
|
command_candidates = [] # Commands captured with high confidence
|
|
1174
1370
|
fallback_candidates = [] # All prompt-like lines (used if we fail to classify)
|
|
1175
|
-
app_command_candidates = [] # Command-only strings for app prefix
|
|
1176
1371
|
|
|
1177
1372
|
history_commands = []
|
|
1178
1373
|
if history_arg:
|
|
@@ -1380,7 +1575,6 @@ for raw_line in lines:
|
|
|
1380
1575
|
normalized_command = " ".join(tokens)
|
|
1381
1576
|
formatted_line = f"{prompt_part}:{normalized_command}"
|
|
1382
1577
|
fallback_candidates.append(formatted_line)
|
|
1383
|
-
app_command_candidates.append(normalized_command)
|
|
1384
1578
|
|
|
1385
1579
|
allowed = (
|
|
1386
1580
|
first_token in common_commands
|
|
@@ -1444,11 +1638,28 @@ description_guidance = """Description rules:
|
|
|
1444
1638
|
- Do NOT include any app/tool name or prefix in the output.
|
|
1445
1639
|
- If git activity is visible, the description should reflect it (e.g., reviewing changes), but do not add 'git_'."""
|
|
1446
1640
|
|
|
1641
|
+
metadata_summary = "\n".join(
|
|
1642
|
+
line
|
|
1643
|
+
for line in [
|
|
1644
|
+
f"pane_current_command: {pane_cmd}" if pane_cmd else "",
|
|
1645
|
+
f"window_name: {window_name}" if window_name else "",
|
|
1646
|
+
f"pane_title: {pane_title}" if pane_title else "",
|
|
1647
|
+
f"pane_mode: {pane_mode}" if pane_mode else "",
|
|
1648
|
+
f"alternate_screen_active: {alternate_on}",
|
|
1649
|
+
f"mouse_mode_active: {mouse_any_flag}",
|
|
1650
|
+
f"likely_tui_app: {is_tui}",
|
|
1651
|
+
]
|
|
1652
|
+
if line
|
|
1653
|
+
)
|
|
1654
|
+
|
|
1447
1655
|
prompt = f"""Analyze this terminal session and create a descriptive tmux session name based on what the user actually did.
|
|
1448
1656
|
|
|
1449
1657
|
Terminal output (last capture):
|
|
1450
1658
|
{content}
|
|
1451
1659
|
|
|
1660
|
+
Tmux metadata (use as supporting hints, not as the primary source of truth):
|
|
1661
|
+
{metadata_summary}
|
|
1662
|
+
|
|
1452
1663
|
Extracted command history (oldest to newest):
|
|
1453
1664
|
{command_history}
|
|
1454
1665
|
|
|
@@ -1460,6 +1671,8 @@ Focus: produce a concise description of the activity. Follow these rules:
|
|
|
1460
1671
|
|
|
1461
1672
|
{'No clear prompt/command lines were detected. Rely on the terminal output context.' if not commands_detected else ''}
|
|
1462
1673
|
|
|
1674
|
+
{'An interactive TUI is likely running. Prefer the visible screen content plus recent command history over generic app names.' if is_tui.lower() in {'1', 'true', 'yes', 'on'} else ''}
|
|
1675
|
+
|
|
1463
1676
|
{'First 10 lines of terminal output (useful for identifying running app/service):\n' + first_ten_lines if not commands_detected and first_ten_lines else ''}
|
|
1464
1677
|
|
|
1465
1678
|
If no commands were detected, fall back to terminal output analysis but describe the main activity in the name. Identify the dominant application or process implied by the output (e.g., web server, test runner, build tool).
|
|
@@ -1496,56 +1709,26 @@ else:
|
|
|
1496
1709
|
]
|
|
1497
1710
|
}
|
|
1498
1711
|
|
|
1499
|
-
def normalize_app(cmd: str) -> str:
|
|
1500
|
-
cmd = cmd.strip()
|
|
1501
|
-
if not cmd:
|
|
1502
|
-
return ""
|
|
1503
|
-
for lead in ("$ ", "> "):
|
|
1504
|
-
if cmd.startswith(lead):
|
|
1505
|
-
cmd = cmd[len(lead):].strip()
|
|
1506
|
-
parts = cmd.split()
|
|
1507
|
-
if not parts:
|
|
1508
|
-
return ""
|
|
1509
|
-
if parts[0] in ("sudo", "env", "command"):
|
|
1510
|
-
parts = parts[1:]
|
|
1511
|
-
if not parts:
|
|
1512
|
-
return ""
|
|
1513
|
-
base = parts[0].split("/")[-1].lower()
|
|
1514
|
-
base = re.sub(r"[^a-z0-9]+", "_", base).strip("_")
|
|
1515
|
-
if base in {"bash", "zsh", "sh", "fish", "tmux", "login", "sudo", "man", "less", "more", "cat", "tail", "watch", "source", "export", "set", "alias", "unalias", "history", "bindkey"}:
|
|
1516
|
-
return ""
|
|
1517
|
-
return base
|
|
1518
|
-
|
|
1519
|
-
app_commands = history_commands if history_commands else app_command_candidates
|
|
1520
|
-
app_prefix = ""
|
|
1521
|
-
for candidate in reversed(app_commands):
|
|
1522
|
-
app_prefix = normalize_app(candidate)
|
|
1523
|
-
if app_prefix:
|
|
1524
|
-
break
|
|
1525
|
-
|
|
1526
|
-
print(app_prefix)
|
|
1527
1712
|
print(json.dumps(request))
|
|
1528
1713
|
PYCODE
|
|
1529
1714
|
)
|
|
1530
|
-
|
|
1531
|
-
parsed_app_prefix="${request_payload%%$'\n'*}"
|
|
1532
|
-
request_body="${request_payload#*$'\n'}"
|
|
1533
|
-
if [[ -n "$preloaded_app_prefix" ]]; then
|
|
1534
|
-
app_prefix="$preloaded_app_prefix"
|
|
1535
|
-
else
|
|
1536
|
-
app_prefix="$parsed_app_prefix"
|
|
1537
|
-
fi
|
|
1538
|
-
if [[ "$request_body" == "$request_payload" ]]; then
|
|
1539
|
-
request_body="$request_payload"
|
|
1540
|
-
if [[ -z "$preloaded_app_prefix" ]]; then
|
|
1541
|
-
app_prefix=""
|
|
1542
|
-
fi
|
|
1543
|
-
fi
|
|
1715
|
+
request_body="$request_payload"
|
|
1544
1716
|
fi
|
|
1545
1717
|
if [[ -z "$request_body" ]]; then
|
|
1546
1718
|
# Fallback: base64 encode the content to avoid escaping issues
|
|
1547
1719
|
local encoded_content
|
|
1548
|
-
|
|
1720
|
+
local fallback_file
|
|
1721
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
1722
|
+
fallback_file=$(mktemp -t gw_fb)
|
|
1723
|
+
else
|
|
1724
|
+
fallback_file=$(mktemp)
|
|
1725
|
+
fi
|
|
1726
|
+
{
|
|
1727
|
+
printf '%b\n\n' "$metadata_block"
|
|
1728
|
+
cat "$temp_file"
|
|
1729
|
+
} > "$fallback_file"
|
|
1730
|
+
encoded_content=$(base64 < "$fallback_file" | tr -d '\n')
|
|
1731
|
+
rm -f "$fallback_file"
|
|
1549
1732
|
if [[ "$provider" == "openai" ]]; then
|
|
1550
1733
|
request_body=$(cat <<EOF
|
|
1551
1734
|
{
|
|
@@ -1555,7 +1738,7 @@ PYCODE
|
|
|
1555
1738
|
"messages": [
|
|
1556
1739
|
{
|
|
1557
1740
|
"role": "user",
|
|
1558
|
-
"content": "I'll send you base64-encoded terminal output. Please decode it, analyze it, and create a descriptive name for this tmux session.\n\nIMPORTANT:
|
|
1741
|
+
"content": "I'll send you base64-encoded terminal output. Please decode it, analyze it, and create a descriptive name for this tmux session.\n\nIMPORTANT: Use terminal metadata only as supporting hints. Prioritize the visible terminal content and recent command history.\n\nCRITICAL: Check for Git patterns FIRST:\n- Git commands: git status, diff, add, commit, push, pull, checkout, branch, merge, rebase, log, stash\n- Git output: 'On branch', 'Changes not staged', 'modified:', 'new file:', diff output (+/-/@@), commit hashes\n- If ANY Git patterns found, the description should reflect git activity (but do not add 'git_').\n\nDESCRIPTION ONLY: Return a short description of the activity.\n- Do NOT include the app/tool name or any prefix in the output.\n\nUse underscores only. Max 100 chars total.\n\nExamples:\n- reviewing_changes_before_commit\n- resolving_merge_conflicts\n- fixing_auth_bug\n- running_nextjs_dev_server\n\nBase64 terminal output:\n${encoded_content}\n\nRespond with ONLY the session name, nothing else."
|
|
1559
1742
|
}
|
|
1560
1743
|
]
|
|
1561
1744
|
}
|
|
@@ -1569,7 +1752,7 @@ EOF
|
|
|
1569
1752
|
"messages": [
|
|
1570
1753
|
{
|
|
1571
1754
|
"role": "user",
|
|
1572
|
-
"content": "I'll send you base64-encoded terminal output. Please decode it, analyze it, and create a descriptive name for this tmux session.\n\nIMPORTANT:
|
|
1755
|
+
"content": "I'll send you base64-encoded terminal output. Please decode it, analyze it, and create a descriptive name for this tmux session.\n\nIMPORTANT: Use terminal metadata only as supporting hints. Prioritize the visible terminal content and recent command history.\n\nCRITICAL: Check for Git patterns FIRST:\n- Git commands: git status, diff, add, commit, push, pull, checkout, branch, merge, rebase, log, stash\n- Git output: 'On branch', 'Changes not staged', 'modified:', 'new file:', diff output (+/-/@@), commit hashes\n- If ANY Git patterns found, the description should reflect git activity (but do not add 'git_').\n\nDESCRIPTION ONLY: Return a short description of the activity.\n- Do NOT include the app/tool name or any prefix in the output.\n\nUse underscores only. Max 100 chars total.\n\nExamples:\n- reviewing_changes_before_commit\n- resolving_merge_conflicts\n- fixing_auth_bug\n- running_nextjs_dev_server\n\nBase64 terminal output:\n${encoded_content}\n\nRespond with ONLY the session name, nothing else."
|
|
1573
1756
|
}
|
|
1574
1757
|
]
|
|
1575
1758
|
}
|
|
@@ -1591,6 +1774,7 @@ EOF
|
|
|
1591
1774
|
context_file=$(mktemp)
|
|
1592
1775
|
fi
|
|
1593
1776
|
{
|
|
1777
|
+
printf '%b\n\n' "$metadata_block"
|
|
1594
1778
|
printf 'TMUX SESSION CAPTURE\n\n'
|
|
1595
1779
|
cat "$temp_file"
|
|
1596
1780
|
if [[ -n "$history_arg" && -f "$history_arg" ]]; then
|
|
@@ -1782,17 +1966,6 @@ print(description)' <<<"$responses_resp" 2>/dev/null || true)"
|
|
|
1782
1966
|
return 1
|
|
1783
1967
|
fi
|
|
1784
1968
|
|
|
1785
|
-
app_prefix=$(printf '%s' "$app_prefix" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')
|
|
1786
|
-
app_prefix="${app_prefix//__/_}"
|
|
1787
|
-
app_prefix="${app_prefix##_}"
|
|
1788
|
-
app_prefix="${app_prefix%%_}"
|
|
1789
|
-
TMUX_AI_APP_SOURCE="$app_prefix"
|
|
1790
|
-
if [[ -n "$app_prefix" ]]; then
|
|
1791
|
-
if [[ "$new_name" == "${app_prefix}_"* ]]; then
|
|
1792
|
-
new_name="${new_name#${app_prefix}_}"
|
|
1793
|
-
fi
|
|
1794
|
-
new_name="${app_prefix}_${new_name}"
|
|
1795
|
-
fi
|
|
1796
1969
|
new_name="${new_name:0:100}"
|
|
1797
1970
|
|
|
1798
1971
|
echo "$new_name"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -107,7 +107,7 @@ case "${1:-}" in
|
|
|
107
107
|
;;
|
|
108
108
|
|
|
109
109
|
"tmux-send-keys")
|
|
110
|
-
bridge_tmux_send_keys "${2:-}" "
|
|
110
|
+
bridge_tmux_send_keys "${2:-}" "${@:3}"
|
|
111
111
|
;;
|
|
112
112
|
|
|
113
113
|
"session-metadata")
|
|
@@ -127,7 +127,7 @@ case "${1:-}" in
|
|
|
127
127
|
;;
|
|
128
128
|
|
|
129
129
|
"manual-rename-session")
|
|
130
|
-
bridge_manual_rename_session "${2:-}" "${3
|
|
130
|
+
bridge_manual_rename_session "${2:-}" "${@:3}"
|
|
131
131
|
;;
|
|
132
132
|
|
|
133
133
|
"check-branch")
|
|
@@ -77,6 +77,10 @@ main() {
|
|
|
77
77
|
exit 1
|
|
78
78
|
fi
|
|
79
79
|
|
|
80
|
+
# This launcher is explicitly for local development; ignore installed binary
|
|
81
|
+
# overrides so gwr.sh resolves the TUI binary from the selected checkout.
|
|
82
|
+
unset GW_TUI_BIN
|
|
83
|
+
|
|
80
84
|
if [[ ${#args[@]} -gt 0 ]]; then
|
|
81
85
|
exec bash "$script_path" "${args[@]}"
|
|
82
86
|
else
|
|
@@ -65,26 +65,6 @@ def extract_command(line: str) -> str:
|
|
|
65
65
|
return line.split(marker)[-1].strip()
|
|
66
66
|
return ""
|
|
67
67
|
|
|
68
|
-
def normalize_app(cmd: str) -> str:
|
|
69
|
-
cmd = cmd.strip()
|
|
70
|
-
if not cmd:
|
|
71
|
-
return ""
|
|
72
|
-
for lead in ("$ ", "> "):
|
|
73
|
-
if cmd.startswith(lead):
|
|
74
|
-
cmd = cmd[len(lead):].strip()
|
|
75
|
-
parts = cmd.split()
|
|
76
|
-
if not parts:
|
|
77
|
-
return ""
|
|
78
|
-
if parts[0] in ("sudo", "env", "command"):
|
|
79
|
-
parts = parts[1:]
|
|
80
|
-
if not parts:
|
|
81
|
-
return ""
|
|
82
|
-
base = parts[0].split("/")[-1].lower()
|
|
83
|
-
base = re.sub(r"[^a-z0-9]+", "_", base).strip("_")
|
|
84
|
-
if base in {"bash", "zsh", "sh", "fish", "tmux", "login", "sudo", "man", "less", "more", "cat", "tail", "watch", "source", "export", "set", "alias", "unalias", "history", "bindkey"}:
|
|
85
|
-
return ""
|
|
86
|
-
return base
|
|
87
|
-
|
|
88
68
|
lines = content.splitlines()
|
|
89
69
|
last_cmd = ""
|
|
90
70
|
for line in reversed(lines):
|
|
@@ -93,7 +73,6 @@ for line in reversed(lines):
|
|
|
93
73
|
last_cmd = candidate
|
|
94
74
|
break
|
|
95
75
|
|
|
96
|
-
app_prefix = normalize_app(last_cmd) or ""
|
|
97
76
|
prompt = f"""Analyze this terminal session and create a descriptive name based ONLY on the console output and commands executed.
|
|
98
77
|
|
|
99
78
|
Terminal output:
|
|
@@ -137,16 +116,10 @@ else:
|
|
|
137
116
|
"max_tokens": 100,
|
|
138
117
|
"messages": [{"role": "user", "content": prompt}],
|
|
139
118
|
}
|
|
140
|
-
print(app_prefix)
|
|
141
119
|
print(json.dumps(req))
|
|
142
120
|
PY
|
|
143
121
|
)
|
|
144
|
-
|
|
145
|
-
request_body="${request_payload#*$'\n'}"
|
|
146
|
-
if [[ "$request_body" == "$request_payload" ]]; then
|
|
147
|
-
request_body="$request_payload"
|
|
148
|
-
app_prefix=""
|
|
149
|
-
fi
|
|
122
|
+
request_body="$request_payload"
|
|
150
123
|
if [[ "$provider" == "openai" ]]; then
|
|
151
124
|
response=$(curl -s -X POST https://api.openai.com/v1/chat/completions \
|
|
152
125
|
--max-time 20 \
|
|
@@ -168,16 +141,6 @@ PY
|
|
|
168
141
|
if [[ -z "$new_name" ]]; then
|
|
169
142
|
echo "\"ai_generation_failed\""
|
|
170
143
|
else
|
|
171
|
-
app_prefix="$(printf '%s' "$app_prefix" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')"
|
|
172
|
-
app_prefix="${app_prefix//__/_}"
|
|
173
|
-
app_prefix="${app_prefix##_}"
|
|
174
|
-
app_prefix="${app_prefix%%_}"
|
|
175
|
-
if [[ -n "$app_prefix" ]]; then
|
|
176
|
-
if [[ "$new_name" == "${app_prefix}_"* ]]; then
|
|
177
|
-
new_name="${new_name#${app_prefix}_}"
|
|
178
|
-
fi
|
|
179
|
-
new_name="${app_prefix}_${new_name}"
|
|
180
|
-
fi
|
|
181
144
|
new_name="${new_name:0:100}"
|
|
182
145
|
printf '"%s"\n' "$new_name"
|
|
183
146
|
fi
|
|
@@ -191,11 +154,18 @@ bridge_rename_session() {
|
|
|
191
154
|
fi
|
|
192
155
|
original_session_name="$1"
|
|
193
156
|
|
|
157
|
+
clear_auto_rename_requested() {
|
|
158
|
+
if tmux_available && tmux_session_exists "$original_session_name"; then
|
|
159
|
+
tmux set-option -qu -t "$original_session_name" @orchestra_auto_rename_requested >/dev/null 2>&1 || true
|
|
160
|
+
fi
|
|
161
|
+
}
|
|
162
|
+
|
|
194
163
|
# Load API key from config file or fallback to .env
|
|
195
164
|
load_openai_api_key
|
|
196
165
|
load_anthropic_api_key
|
|
197
166
|
|
|
198
167
|
if [[ -z "${OPENAI_API_KEY-}" && -z "${ANTHROPIC_API_KEY-}" ]]; then
|
|
168
|
+
clear_auto_rename_requested
|
|
199
169
|
echo "\"missing_api_key\""
|
|
200
170
|
return 0
|
|
201
171
|
fi
|
|
@@ -212,7 +182,6 @@ bridge_rename_session() {
|
|
|
212
182
|
fi
|
|
213
183
|
|
|
214
184
|
# Generate AI description using the CURRENT session (no renaming to temp name)
|
|
215
|
-
TMUX_AI_APP_SOURCE=""
|
|
216
185
|
local err_file
|
|
217
186
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
218
187
|
err_file=$(mktemp -t gw_ai_err)
|
|
@@ -234,27 +203,21 @@ bridge_rename_session() {
|
|
|
234
203
|
ai_name="$(echo "$ai_name" | tr ' ' '_' | sed 's/[^a-zA-Z0-9_-]//g' | cut -c1-100)"
|
|
235
204
|
ai_name="$(echo "$ai_name" | sed 's/^_*//')"
|
|
236
205
|
if [[ -z "$ai_name" ]]; then
|
|
206
|
+
clear_auto_rename_requested
|
|
237
207
|
echo "\"ai_generation_failed\""
|
|
238
208
|
return 0
|
|
239
209
|
fi
|
|
240
210
|
|
|
241
211
|
# Perform the rename preserving canonical prefix and delimiter
|
|
242
212
|
if tmux_rename_session "$original_session_name" "$ai_name" >/dev/null; then
|
|
243
|
-
|
|
244
|
-
app_source="$(printf '%s' "$app_source" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')"
|
|
245
|
-
app_source="${app_source//__/_}"
|
|
246
|
-
app_source="${app_source##_}"
|
|
247
|
-
app_source="${app_source%%_}"
|
|
248
|
-
if [[ -n "$app_source" ]]; then
|
|
249
|
-
echo "\"success:app_source:${app_source}\""
|
|
250
|
-
else
|
|
251
|
-
echo "\"success\""
|
|
252
|
-
fi
|
|
213
|
+
echo "\"success\""
|
|
253
214
|
else
|
|
215
|
+
clear_auto_rename_requested
|
|
254
216
|
echo "\"rename_failed\""
|
|
255
217
|
fi
|
|
256
218
|
else
|
|
257
219
|
# AI generation failed, keep original name
|
|
220
|
+
clear_auto_rename_requested
|
|
258
221
|
if [[ -n "$ai_err" ]]; then
|
|
259
222
|
echo "\"ai_generation_failed: ${ai_err}\""
|
|
260
223
|
elif [[ $ai_status -ne 0 ]]; then
|
|
@@ -267,12 +230,13 @@ bridge_rename_session() {
|
|
|
267
230
|
|
|
268
231
|
# Manual rename session
|
|
269
232
|
bridge_manual_rename_session() {
|
|
270
|
-
if [[ -z "${1:-}" ]] || [[ -
|
|
233
|
+
if [[ -z "${1:-}" ]] || [[ $# -lt 2 ]]; then
|
|
271
234
|
json_error "Old and new session names required"
|
|
272
235
|
return 1
|
|
273
236
|
fi
|
|
274
237
|
old_name="$1"
|
|
275
|
-
|
|
238
|
+
shift
|
|
239
|
+
new_display_name="$*"
|
|
276
240
|
|
|
277
241
|
if ! tmux_available; then
|
|
278
242
|
echo "\"tmux_not_available\""
|
|
@@ -284,9 +248,10 @@ bridge_manual_rename_session() {
|
|
|
284
248
|
echo "\"session_not_found\""
|
|
285
249
|
return 0
|
|
286
250
|
fi
|
|
251
|
+
new_display_name="$(printf '%s' "$new_display_name" | tr ' ' '_' | sed 's/[^a-zA-Z0-9_]/_/g' | cut -c1-100)"
|
|
287
252
|
|
|
288
|
-
# Validate new display name
|
|
289
|
-
if [[ ! "$new_display_name" =~ ^[a-zA-Z0-9_
|
|
253
|
+
# Validate new display name after matching the TUI's sanitization rules.
|
|
254
|
+
if [[ -z "$new_display_name" || ! "$new_display_name" =~ ^[a-zA-Z0-9_]+$ ]]; then
|
|
290
255
|
echo "\"invalid_name\""
|
|
291
256
|
return 0
|
|
292
257
|
fi
|
|
@@ -103,7 +103,7 @@ bridge_tmux_send_keys() {
|
|
|
103
103
|
json_error "Session name required"
|
|
104
104
|
return 1
|
|
105
105
|
fi
|
|
106
|
-
session_name="$1"; shift
|
|
106
|
+
session_name="$1"; shift || true
|
|
107
107
|
if tmux_available; then
|
|
108
108
|
tmux_send_keys "$session_name" "$*" || { json_error "Failed to send keys"; return 1; }
|
|
109
109
|
echo '{"ok":true}'
|
|
@@ -125,12 +125,32 @@ bridge_session_metadata() {
|
|
|
125
125
|
window_name=$(tmux display-message -t "$session_name" -p '#{window_name}' 2>/dev/null || echo "")
|
|
126
126
|
pane_title=$(tmux display-message -t "$session_name" -p '#{pane_title}' 2>/dev/null || echo "")
|
|
127
127
|
pane_cmd=$(tmux display-message -t "$session_name" -p '#{pane_current_command}' 2>/dev/null || echo "")
|
|
128
|
-
|
|
128
|
+
pane_mode=$(tmux display-message -t "$session_name" -p '#{pane_mode}' 2>/dev/null || echo "")
|
|
129
|
+
alternate_on=$(tmux display-message -t "$session_name" -p '#{alternate_on}' 2>/dev/null || echo "0")
|
|
130
|
+
mouse_any_flag=$(tmux display-message -t "$session_name" -p '#{mouse_any_flag}' 2>/dev/null || echo "0")
|
|
131
|
+
auto_rename_requested=$(tmux show-options -qv -t "$session_name" @orchestra_auto_rename_requested 2>/dev/null || echo "")
|
|
132
|
+
case "$(printf '%s' "$alternate_on" | tr '[:upper:]' '[:lower:]')" in
|
|
133
|
+
1|yes|true|on) alternate_on=true ;;
|
|
134
|
+
*) alternate_on=false ;;
|
|
135
|
+
esac
|
|
136
|
+
case "$(printf '%s' "$mouse_any_flag" | tr '[:upper:]' '[:lower:]')" in
|
|
137
|
+
1|yes|true|on) mouse_any_flag=true ;;
|
|
138
|
+
*) mouse_any_flag=false ;;
|
|
139
|
+
esac
|
|
140
|
+
case "$(printf '%s' "$auto_rename_requested" | tr '[:upper:]' '[:lower:]')" in
|
|
141
|
+
1|yes|true|on) auto_rename_requested=true ;;
|
|
142
|
+
*) auto_rename_requested=false ;;
|
|
143
|
+
esac
|
|
144
|
+
|
|
129
145
|
# Return JSON with metadata
|
|
130
146
|
json_object \
|
|
131
147
|
"window_name:s=$window_name" \
|
|
132
148
|
"pane_title:s=$pane_title" \
|
|
133
|
-
"current_command:s=$pane_cmd"
|
|
149
|
+
"current_command:s=$pane_cmd" \
|
|
150
|
+
"pane_mode:s=$pane_mode" \
|
|
151
|
+
"alternate_on:b=$alternate_on" \
|
|
152
|
+
"mouse_any_flag:b=$mouse_any_flag" \
|
|
153
|
+
"auto_rename_requested:b=$auto_rename_requested"
|
|
134
154
|
else
|
|
135
155
|
json_error "tmux not available"
|
|
136
156
|
fi
|
|
@@ -8,6 +8,16 @@ build_check_dependencies() {
|
|
|
8
8
|
exit 1
|
|
9
9
|
fi
|
|
10
10
|
|
|
11
|
+
if ! command -v cc >/dev/null 2>&1 && \
|
|
12
|
+
! command -v clang >/dev/null 2>&1 && \
|
|
13
|
+
! command -v gcc >/dev/null 2>&1; then
|
|
14
|
+
build_error "C compiler not found. Bundled SQLite requires a native C toolchain."
|
|
15
|
+
build_error " macOS: xcode-select --install"
|
|
16
|
+
build_error " Ubuntu/Debian: sudo apt install build-essential"
|
|
17
|
+
build_error " RHEL/CentOS: sudo yum groupinstall 'Development Tools'"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
11
21
|
if ! command -v jq >/dev/null 2>&1; then
|
|
12
22
|
build_warn "jq not found. Installing via package manager is recommended."
|
|
13
23
|
build_warn " macOS: brew install jq"
|
|
@@ -9,10 +9,12 @@ Usage: gwr [options]
|
|
|
9
9
|
A terminal user interface for managing git worktrees and tmux sessions.
|
|
10
10
|
|
|
11
11
|
Options:
|
|
12
|
-
-d, --debug
|
|
13
|
-
-
|
|
14
|
-
--
|
|
15
|
-
--
|
|
12
|
+
-d, --debug Enable debug mode
|
|
13
|
+
--new-tmux Create and attach to a new tmux session for the current branch
|
|
14
|
+
--cmd <command> Run a command in the new tmux session (repeatable; requires --new-tmux)
|
|
15
|
+
-h, --help Show this help
|
|
16
|
+
--check-updates Check for available updates
|
|
17
|
+
--update Check for available updates (alias for --check-updates)
|
|
16
18
|
|
|
17
19
|
Features:
|
|
18
20
|
• Interactive worktree and session management
|
|
@@ -44,6 +46,8 @@ For the original shell-based interface, use: gw
|
|
|
44
46
|
|
|
45
47
|
Examples:
|
|
46
48
|
gwr # Launch interactive TUI
|
|
49
|
+
gwr --new-tmux # Start a new tmux session for the current branch
|
|
50
|
+
gwr --new-tmux --cmd "pnpm install" --cmd "pnpm dev"
|
|
47
51
|
gwr --debug # Launch with debug information
|
|
48
52
|
gwr --help # Show this help
|
|
49
53
|
gwr --update # Check for available updates
|
|
@@ -4,13 +4,62 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Source this file from your shell profile to log each executed command to a
|
|
6
6
|
# per-tmux-pane history file. The auto-rename pipeline can then use the
|
|
7
|
-
# captured history as the primary
|
|
7
|
+
# captured history as the primary context for generating session names.
|
|
8
8
|
#
|
|
9
9
|
# source /path/to/orchestrator/shell/orchestra-command-hook.sh
|
|
10
10
|
#
|
|
11
11
|
# On Bash we install a DEBUG pre-exec hook; on Zsh we install a preexec hook.
|
|
12
12
|
# This captures the last user command reliably before execution.
|
|
13
13
|
|
|
14
|
+
orchestra_rename_session() {
|
|
15
|
+
local new_name="$*"
|
|
16
|
+
if [[ -z "$new_name" ]]; then
|
|
17
|
+
printf 'Usage: orchestra_rename_session <new name>\n' >&2
|
|
18
|
+
return 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [[ -z "${TMUX-}" ]] || ! command -v tmux >/dev/null 2>&1; then
|
|
22
|
+
printf 'orchestra_rename_session must be run inside a tmux session\n' >&2
|
|
23
|
+
return 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [[ -z "${ORCHESTRA_BRIDGE_PATH-}" || ! -x "$ORCHESTRA_BRIDGE_PATH" ]]; then
|
|
27
|
+
printf 'ORCHESTRA_BRIDGE_PATH is not set or executable\n' >&2
|
|
28
|
+
return 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
local session_name=""
|
|
32
|
+
session_name="$(tmux display-message -p '#{session_name}' 2>/dev/null || echo "")"
|
|
33
|
+
if [[ -z "$session_name" ]]; then
|
|
34
|
+
printf 'Unable to determine current tmux session\n' >&2
|
|
35
|
+
return 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
local result=""
|
|
39
|
+
result="$("$ORCHESTRA_BRIDGE_PATH" manual-rename-session "$session_name" "$new_name" 2>/dev/null || true)"
|
|
40
|
+
case "$result" in
|
|
41
|
+
'"success"')
|
|
42
|
+
printf 'Renamed session to: %s\n' "$new_name"
|
|
43
|
+
;;
|
|
44
|
+
'"invalid_name"')
|
|
45
|
+
printf 'Invalid session name: %s\n' "$new_name" >&2
|
|
46
|
+
return 1
|
|
47
|
+
;;
|
|
48
|
+
'"session_not_found"')
|
|
49
|
+
printf 'Current tmux session was not found\n' >&2
|
|
50
|
+
return 1
|
|
51
|
+
;;
|
|
52
|
+
'"tmux_not_available"')
|
|
53
|
+
printf 'tmux is not available\n' >&2
|
|
54
|
+
return 1
|
|
55
|
+
;;
|
|
56
|
+
*)
|
|
57
|
+
printf 'Failed to rename session\n' >&2
|
|
58
|
+
return 1
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
}
|
|
62
|
+
|
|
14
63
|
if [[ -n "${ORCHESTRA_PROMPT_HOOK_INSTALLED-}" ]]; then
|
|
15
64
|
return 0 2>/dev/null || exit 0
|
|
16
65
|
fi
|
|
@@ -18,6 +67,8 @@ fi
|
|
|
18
67
|
ORCHESTRA_HISTORY_DIR="${ORCHESTRA_HISTORY_DIR:-$HOME/.orchestra/history}"
|
|
19
68
|
ORCHESTRA_PROMPT_HOOK_INSTALLED=1
|
|
20
69
|
|
|
70
|
+
_ORCHESTRA_HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
71
|
+
|
|
21
72
|
_orchestra_history_key() {
|
|
22
73
|
local key="$1"
|
|
23
74
|
key="${key//\//_}"
|
|
@@ -31,6 +82,101 @@ _orchestra_history_key() {
|
|
|
31
82
|
echo "$key"
|
|
32
83
|
}
|
|
33
84
|
|
|
85
|
+
_orchestra_bridge_path() {
|
|
86
|
+
local bridge=""
|
|
87
|
+
|
|
88
|
+
if [[ -n "${GW_ORCHESTRATOR_ROOT-}" ]]; then
|
|
89
|
+
bridge="$GW_ORCHESTRATOR_ROOT/gw-bridge.sh"
|
|
90
|
+
if [[ -f "$bridge" ]]; then
|
|
91
|
+
echo "$bridge"
|
|
92
|
+
return
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
bridge="$(cd "$_ORCHESTRA_HOOK_DIR/.." && pwd -P)/gw-bridge.sh"
|
|
97
|
+
if [[ -f "$bridge" ]]; then
|
|
98
|
+
echo "$bridge"
|
|
99
|
+
return
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
echo ""
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_orchestra_should_auto_rename_session() {
|
|
106
|
+
local session_name="$1"
|
|
107
|
+
[[ -n "$session_name" && "$session_name" == *"__auto_"* ]]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_orchestra_should_trigger_auto_rename_command() {
|
|
111
|
+
local command="$1"
|
|
112
|
+
local app="$2"
|
|
113
|
+
|
|
114
|
+
[[ -n "$command" ]] || return 1
|
|
115
|
+
|
|
116
|
+
case "$app" in
|
|
117
|
+
""|cd|ls|pwd|clear|reset)
|
|
118
|
+
return 1
|
|
119
|
+
;;
|
|
120
|
+
esac
|
|
121
|
+
|
|
122
|
+
return 0
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_orchestra_auto_rename_flag_path() {
|
|
126
|
+
local session_name="$1"
|
|
127
|
+
local session_key
|
|
128
|
+
session_key=$(_orchestra_history_key "$session_name")
|
|
129
|
+
if [[ -z "$session_key" ]]; then
|
|
130
|
+
return
|
|
131
|
+
fi
|
|
132
|
+
printf '%s/%s.rename_requested\n' "$ORCHESTRA_HISTORY_DIR" "$session_key"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_orchestra_auto_rename_delay_secs() {
|
|
136
|
+
local delay="${ORCHESTRA_AUTO_RENAME_DELAY_SECS:-10}"
|
|
137
|
+
if [[ ! "$delay" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
|
|
138
|
+
delay="10"
|
|
139
|
+
fi
|
|
140
|
+
printf '%s\n' "$delay"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_orchestra_trigger_auto_rename_async() {
|
|
144
|
+
local session_name="$1"
|
|
145
|
+
local command="$2"
|
|
146
|
+
local app="$3"
|
|
147
|
+
|
|
148
|
+
[[ -n "$session_name" ]] || return
|
|
149
|
+
_orchestra_should_auto_rename_session "$session_name" || return
|
|
150
|
+
_orchestra_should_trigger_auto_rename_command "$command" "$app" || return
|
|
151
|
+
|
|
152
|
+
local bridge
|
|
153
|
+
bridge="$(_orchestra_bridge_path)"
|
|
154
|
+
[[ -n "$bridge" && -f "$bridge" ]] || return
|
|
155
|
+
|
|
156
|
+
local flag_path
|
|
157
|
+
flag_path="$(_orchestra_auto_rename_flag_path "$session_name")"
|
|
158
|
+
[[ -n "$flag_path" ]] || return
|
|
159
|
+
if [[ -f "$flag_path" ]]; then
|
|
160
|
+
return
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
local timestamp
|
|
164
|
+
timestamp=$(date '+%Y-%m-%dT%H:%M:%S%z')
|
|
165
|
+
printf '%s\n' "$timestamp" > "$flag_path"
|
|
166
|
+
|
|
167
|
+
if command -v tmux >/dev/null 2>&1; then
|
|
168
|
+
tmux set-option -q -t "$session_name" @orchestra_auto_rename_requested 1 >/dev/null 2>&1 || true
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
local delay
|
|
172
|
+
delay="$(_orchestra_auto_rename_delay_secs)"
|
|
173
|
+
|
|
174
|
+
(
|
|
175
|
+
sleep "$delay"
|
|
176
|
+
command bash "$bridge" rename-session "$session_name" >/dev/null 2>&1 || true
|
|
177
|
+
) >/dev/null 2>&1 &
|
|
178
|
+
}
|
|
179
|
+
|
|
34
180
|
_orchestra_capture_command() {
|
|
35
181
|
local cmd=""
|
|
36
182
|
if [[ -n "${ZSH_VERSION-}" ]]; then
|
|
@@ -171,6 +317,10 @@ orchestra_log_command() {
|
|
|
171
317
|
printf '%s\n' "$app" > "$ORCHESTRA_HISTORY_DIR/$key.last_app"
|
|
172
318
|
fi
|
|
173
319
|
printf '%s\n' "$command" > "$ORCHESTRA_HISTORY_DIR/$key.last_cmd"
|
|
320
|
+
|
|
321
|
+
if [[ -n "$session_name" && "$session_name" != "shell" ]]; then
|
|
322
|
+
_orchestra_trigger_auto_rename_async "$session_name" "$command" "$app" || true
|
|
323
|
+
fi
|
|
174
324
|
}
|
|
175
325
|
|
|
176
326
|
_orchestra_install_hook_bash() {
|
|
@@ -77,6 +77,10 @@ main() {
|
|
|
77
77
|
exit 1
|
|
78
78
|
fi
|
|
79
79
|
|
|
80
|
+
# This launcher is explicitly for local development; ignore installed binary
|
|
81
|
+
# overrides so gwr.sh resolves the TUI binary from the selected checkout.
|
|
82
|
+
unset GW_TUI_BIN
|
|
83
|
+
|
|
80
84
|
if [[ ${#args[@]} -gt 0 ]]; then
|
|
81
85
|
exec bash "$script_path" "${args[@]}"
|
|
82
86
|
else
|