@humanu/orchestra 0.5.66 → 0.5.71
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 +177 -122
- 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 +8 -53
- package/resources/scripts/shell/bridge/tmux.sh +1 -1
- 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 +50 -1
- package/resources/scripts/shell/orchestra-local.sh +4 -0
package/package.json
CHANGED
package/resources/api/tmux.sh
CHANGED
|
@@ -196,6 +196,10 @@ _orchestra_command_hook() {
|
|
|
196
196
|
echo ""
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
_orchestra_bridge_script() {
|
|
200
|
+
echo "$(dirname "$_TMUX_API_DIR")/gw-bridge.sh"
|
|
201
|
+
}
|
|
202
|
+
|
|
199
203
|
# Source the command hook inside a tmux session to enable command history logging
|
|
200
204
|
_tmux_source_command_hook() {
|
|
201
205
|
local session="$1"
|
|
@@ -207,6 +211,8 @@ _tmux_source_command_hook() {
|
|
|
207
211
|
if ! tmux_session_exists "$session"; then
|
|
208
212
|
return
|
|
209
213
|
fi
|
|
214
|
+
local bridge
|
|
215
|
+
bridge="$(_orchestra_bridge_script)"
|
|
210
216
|
local panes
|
|
211
217
|
panes=$(tmux list-panes -t "$session" -F '#{pane_id} #{pane_current_command}' 2>/dev/null || echo "")
|
|
212
218
|
if [[ -z "$panes" ]]; then
|
|
@@ -219,7 +225,7 @@ _tmux_source_command_hook() {
|
|
|
219
225
|
pane_cmd="${line#* }"
|
|
220
226
|
case "$pane_cmd" in
|
|
221
227
|
bash|zsh)
|
|
222
|
-
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
|
|
223
229
|
;;
|
|
224
230
|
*)
|
|
225
231
|
;;
|
|
@@ -230,6 +236,51 @@ _tmux_source_command_hook() {
|
|
|
230
236
|
# Helper: orchestra prefix including delimiter
|
|
231
237
|
_tmux_orch_prefix() { printf "orchestra%s" "$( _tmux_delim )"; }
|
|
232
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
|
+
|
|
233
284
|
# Helper: split a string by multi-char delimiter into bash array named by ref
|
|
234
285
|
# Usage: _tmux_split_by_delim "string" "::" out_array_name
|
|
235
286
|
_tmux_split_by_delim() {
|
|
@@ -340,10 +391,7 @@ tmux_create_session() {
|
|
|
340
391
|
else
|
|
341
392
|
worktree_name="$(basename "$working_dir")"
|
|
342
393
|
fi
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
# Increase status-left length to accommodate the message
|
|
346
|
-
tmux set-option -t "$session_name" status-left-length 120 >/dev/null 2>&1 || true
|
|
394
|
+
_tmux_configure_orchestra_status "$session_name" "$worktree_name"
|
|
347
395
|
fi
|
|
348
396
|
|
|
349
397
|
echo "$session_name"
|
|
@@ -483,8 +531,7 @@ tmux_attach_session() {
|
|
|
483
531
|
worktree_name="$branch_name"
|
|
484
532
|
fi
|
|
485
533
|
fi
|
|
486
|
-
|
|
487
|
-
tmux set-option -t "$sess" status-left-length 120 >/dev/null 2>&1 || true
|
|
534
|
+
_tmux_configure_orchestra_status "$sess" "$worktree_name"
|
|
488
535
|
|
|
489
536
|
if tmux_inside_session; then
|
|
490
537
|
tmux switch-client -t "$sess" >/dev/null 2>&1 || true
|
|
@@ -562,6 +609,24 @@ tmux_rename_session() {
|
|
|
562
609
|
err "Failed to rename session"
|
|
563
610
|
return 1
|
|
564
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"
|
|
565
630
|
>&2 echo "✏️ Renamed session to: $new_name"
|
|
566
631
|
return 0
|
|
567
632
|
}
|
|
@@ -646,66 +711,119 @@ tmux_find_session() {
|
|
|
646
711
|
|
|
647
712
|
# Helper function to format session display names
|
|
648
713
|
# Input: session_content (e.g., "opencode_fixing_auth_bug" or "my_feature_work")
|
|
649
|
-
# Output: "Opencode
|
|
714
|
+
# Output: "Opencode Fixing Auth Bug" or "My Feature Work"
|
|
650
715
|
format_session_display_name() {
|
|
651
716
|
local session_content="$1"
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
local
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
# Check if session starts with a known app prefix
|
|
659
|
-
for app in "${known_apps[@]}"; do
|
|
660
|
-
if [[ "$session_content" =~ ^${app}_ ]]; then
|
|
661
|
-
app_prefix="$app"
|
|
662
|
-
# Remove the app prefix and following underscore
|
|
663
|
-
description="${session_content#${app}_}"
|
|
664
|
-
break
|
|
665
|
-
fi
|
|
666
|
-
done
|
|
667
|
-
|
|
668
|
-
# If no app prefix found, treat whole string as description
|
|
669
|
-
if [[ -z "$app_prefix" ]]; then
|
|
670
|
-
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}}"
|
|
671
723
|
fi
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
724
|
+
if [[ "$session_content" == auto_* ]]; then
|
|
725
|
+
session_content="${session_content#auto_}"
|
|
726
|
+
fi
|
|
727
|
+
local description
|
|
728
|
+
description="$(echo "$session_content" | tr '_-' ' ')"
|
|
677
729
|
description="$(echo "$description" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2));}1')"
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
|
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]}"
|
|
686
764
|
fi
|
|
765
|
+
|
|
766
|
+
printf '%s %s %s:%s%s\n' "$month_name" "$day" "$display_hour" "$minute" "$ampm"
|
|
687
767
|
}
|
|
688
768
|
|
|
689
769
|
# Parse session name and format for display
|
|
690
770
|
# Input: session_name (format: worktreename_repohash_YYYYMMDD_HHMMSS_readable_name or old formats)
|
|
691
|
-
# Output:
|
|
771
|
+
# Output: formatted display name
|
|
692
772
|
# Usage: tmux_format_session_display <session_name>
|
|
693
773
|
tmux_format_session_display() {
|
|
694
774
|
local session_name="$1"
|
|
695
|
-
|
|
775
|
+
local timestamp_mode="${2:-with-timestamp}"
|
|
776
|
+
|
|
696
777
|
# Handle temporary renaming sessions first
|
|
697
778
|
if [[ "$session_name" =~ _renaming$ ]]; then
|
|
698
779
|
# Extract base name before _renaming suffix
|
|
699
780
|
local base_name="${session_name%_renaming}"
|
|
700
781
|
# Recursively process the base name to get proper display
|
|
701
|
-
tmux_format_session_display "$base_name"
|
|
782
|
+
tmux_format_session_display "$base_name" "$timestamp_mode"
|
|
702
783
|
return $?
|
|
703
784
|
fi
|
|
704
785
|
|
|
705
|
-
|
|
706
|
-
|
|
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
|
|
707
793
|
session_name="${BASH_REMATCH[1]}"
|
|
708
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
|
|
709
827
|
|
|
710
828
|
# New format with repo hash: worktreename_repohash_YYYYMMDD_HHMMSS_readable_name
|
|
711
829
|
if [[ "$session_name" =~ ^[^_]+_[a-f0-9]{8}_([0-9]{8})_([0-9]{6})_(.+)$ ]]; then
|
|
@@ -747,13 +865,17 @@ tmux_format_session_display() {
|
|
|
747
865
|
|
|
748
866
|
# Format the readable name with proper formatting
|
|
749
867
|
local formatted_name="$(format_session_display_name "$readable_name")"
|
|
750
|
-
|
|
751
|
-
|
|
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
|
|
752
874
|
# Fallback: Try format without repo hash: appname_description_YYYYMMDD_HHMMSS
|
|
753
875
|
elif [[ "$session_name" =~ ^(.*)_[0-9]{8}_[0-9]{6}$ ]]; then
|
|
754
876
|
local session_content="${BASH_REMATCH[1]}" # appname_description
|
|
755
877
|
|
|
756
|
-
# Convert to proper display format
|
|
878
|
+
# Convert to proper display format
|
|
757
879
|
local formatted_name="$(format_session_display_name "$session_content")"
|
|
758
880
|
echo "$formatted_name"
|
|
759
881
|
else
|
|
@@ -797,8 +919,12 @@ tmux_format_session_display() {
|
|
|
797
919
|
|
|
798
920
|
# Format the readable name with proper formatting
|
|
799
921
|
local formatted_name="$(format_session_display_name "$readable_name")"
|
|
800
|
-
|
|
801
|
-
|
|
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
|
|
802
928
|
else
|
|
803
929
|
# Fallback for sessions that don't match either format
|
|
804
930
|
echo "$session_name"
|
|
@@ -1096,7 +1222,6 @@ NODE
|
|
|
1096
1222
|
# Usage: tmux_generate_ai_session_name <session_name>
|
|
1097
1223
|
tmux_generate_ai_session_name() {
|
|
1098
1224
|
local session="$1"
|
|
1099
|
-
TMUX_AI_APP_SOURCE=""
|
|
1100
1225
|
|
|
1101
1226
|
# Load AI provider config and API keys
|
|
1102
1227
|
tmux_load_ai_primary_provider
|
|
@@ -1139,7 +1264,6 @@ tmux_generate_ai_session_name() {
|
|
|
1139
1264
|
local history_temp=""
|
|
1140
1265
|
local history_arg=""
|
|
1141
1266
|
local history_dir="${ORCHESTRA_HISTORY_DIR:-$HOME/.orchestra/history}"
|
|
1142
|
-
local app_prefix=""
|
|
1143
1267
|
local pane_id
|
|
1144
1268
|
pane_id="$(tmux display-message -t "$session" -p '#{pane_id}' 2>/dev/null || echo "")"
|
|
1145
1269
|
local pane_key=""
|
|
@@ -1170,20 +1294,6 @@ tmux_generate_ai_session_name() {
|
|
|
1170
1294
|
content="$scrollback_content"
|
|
1171
1295
|
fi
|
|
1172
1296
|
|
|
1173
|
-
local app_source_path=""
|
|
1174
|
-
if [[ -n "$pane_key" && -f "$history_dir/$pane_key.last_app" ]]; then
|
|
1175
|
-
app_source_path="$history_dir/$pane_key.last_app"
|
|
1176
|
-
elif [[ -n "$session_key" && -f "$history_dir/$session_key.last_app" ]]; then
|
|
1177
|
-
app_source_path="$history_dir/$session_key.last_app"
|
|
1178
|
-
fi
|
|
1179
|
-
if [[ -n "$app_source_path" ]]; then
|
|
1180
|
-
IFS= read -r app_prefix < "$app_source_path" || true
|
|
1181
|
-
fi
|
|
1182
|
-
|
|
1183
|
-
if [[ -z "$app_prefix" ]]; then
|
|
1184
|
-
app_prefix="$(_tmux_normalize_app_from_command "$pane_cmd")"
|
|
1185
|
-
fi
|
|
1186
|
-
|
|
1187
1297
|
local history_path=""
|
|
1188
1298
|
if [[ -n "$pane_key" && -f "$history_dir/$pane_key.log" ]]; then
|
|
1189
1299
|
history_path="$history_dir/$pane_key.log"
|
|
@@ -1233,7 +1343,6 @@ tmux_generate_ai_session_name() {
|
|
|
1233
1343
|
|
|
1234
1344
|
# Prepare the API request using Python for proper JSON encoding
|
|
1235
1345
|
local request_body=""
|
|
1236
|
-
local preloaded_app_prefix="$app_prefix"
|
|
1237
1346
|
if have_cmd python3; then
|
|
1238
1347
|
# Use Python to safely build the request body and extract typed commands from the capture
|
|
1239
1348
|
request_payload=$(
|
|
@@ -1259,7 +1368,6 @@ is_tui = sys.argv[11] if len(sys.argv) > 11 else "false"
|
|
|
1259
1368
|
lines = content.splitlines()
|
|
1260
1369
|
command_candidates = [] # Commands captured with high confidence
|
|
1261
1370
|
fallback_candidates = [] # All prompt-like lines (used if we fail to classify)
|
|
1262
|
-
app_command_candidates = [] # Command-only strings for app prefix
|
|
1263
1371
|
|
|
1264
1372
|
history_commands = []
|
|
1265
1373
|
if history_arg:
|
|
@@ -1467,7 +1575,6 @@ for raw_line in lines:
|
|
|
1467
1575
|
normalized_command = " ".join(tokens)
|
|
1468
1576
|
formatted_line = f"{prompt_part}:{normalized_command}"
|
|
1469
1577
|
fallback_candidates.append(formatted_line)
|
|
1470
|
-
app_command_candidates.append(normalized_command)
|
|
1471
1578
|
|
|
1472
1579
|
allowed = (
|
|
1473
1580
|
first_token in common_commands
|
|
@@ -1602,51 +1709,10 @@ else:
|
|
|
1602
1709
|
]
|
|
1603
1710
|
}
|
|
1604
1711
|
|
|
1605
|
-
def normalize_app(cmd: str) -> str:
|
|
1606
|
-
cmd = cmd.strip()
|
|
1607
|
-
if not cmd:
|
|
1608
|
-
return ""
|
|
1609
|
-
for lead in ("$ ", "> "):
|
|
1610
|
-
if cmd.startswith(lead):
|
|
1611
|
-
cmd = cmd[len(lead):].strip()
|
|
1612
|
-
parts = cmd.split()
|
|
1613
|
-
if not parts:
|
|
1614
|
-
return ""
|
|
1615
|
-
if parts[0] in ("sudo", "env", "command"):
|
|
1616
|
-
parts = parts[1:]
|
|
1617
|
-
if not parts:
|
|
1618
|
-
return ""
|
|
1619
|
-
base = parts[0].split("/")[-1].lower()
|
|
1620
|
-
base = re.sub(r"[^a-z0-9]+", "_", base).strip("_")
|
|
1621
|
-
if base in {"bash", "zsh", "sh", "fish", "tmux", "login", "sudo", "man", "less", "more", "cat", "tail", "watch", "source", "export", "set", "alias", "unalias", "history", "bindkey"}:
|
|
1622
|
-
return ""
|
|
1623
|
-
return base
|
|
1624
|
-
|
|
1625
|
-
app_commands = history_commands if history_commands else app_command_candidates
|
|
1626
|
-
app_prefix = ""
|
|
1627
|
-
for candidate in reversed(app_commands):
|
|
1628
|
-
app_prefix = normalize_app(candidate)
|
|
1629
|
-
if app_prefix:
|
|
1630
|
-
break
|
|
1631
|
-
|
|
1632
|
-
print(app_prefix)
|
|
1633
1712
|
print(json.dumps(request))
|
|
1634
1713
|
PYCODE
|
|
1635
1714
|
)
|
|
1636
|
-
|
|
1637
|
-
parsed_app_prefix="${request_payload%%$'\n'*}"
|
|
1638
|
-
request_body="${request_payload#*$'\n'}"
|
|
1639
|
-
if [[ -n "$preloaded_app_prefix" ]]; then
|
|
1640
|
-
app_prefix="$preloaded_app_prefix"
|
|
1641
|
-
else
|
|
1642
|
-
app_prefix="$parsed_app_prefix"
|
|
1643
|
-
fi
|
|
1644
|
-
if [[ "$request_body" == "$request_payload" ]]; then
|
|
1645
|
-
request_body="$request_payload"
|
|
1646
|
-
if [[ -z "$preloaded_app_prefix" ]]; then
|
|
1647
|
-
app_prefix=""
|
|
1648
|
-
fi
|
|
1649
|
-
fi
|
|
1715
|
+
request_body="$request_payload"
|
|
1650
1716
|
fi
|
|
1651
1717
|
if [[ -z "$request_body" ]]; then
|
|
1652
1718
|
# Fallback: base64 encode the content to avoid escaping issues
|
|
@@ -1900,17 +1966,6 @@ print(description)' <<<"$responses_resp" 2>/dev/null || true)"
|
|
|
1900
1966
|
return 1
|
|
1901
1967
|
fi
|
|
1902
1968
|
|
|
1903
|
-
app_prefix=$(printf '%s' "$app_prefix" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')
|
|
1904
|
-
app_prefix="${app_prefix//__/_}"
|
|
1905
|
-
app_prefix="${app_prefix##_}"
|
|
1906
|
-
app_prefix="${app_prefix%%_}"
|
|
1907
|
-
TMUX_AI_APP_SOURCE="$app_prefix"
|
|
1908
|
-
if [[ -n "$app_prefix" ]]; then
|
|
1909
|
-
if [[ "$new_name" == "${app_prefix}_"* ]]; then
|
|
1910
|
-
new_name="${new_name#${app_prefix}_}"
|
|
1911
|
-
fi
|
|
1912
|
-
new_name="${app_prefix}_${new_name}"
|
|
1913
|
-
fi
|
|
1914
1969
|
new_name="${new_name:0:100}"
|
|
1915
1970
|
|
|
1916
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
|
|
@@ -219,7 +182,6 @@ bridge_rename_session() {
|
|
|
219
182
|
fi
|
|
220
183
|
|
|
221
184
|
# Generate AI description using the CURRENT session (no renaming to temp name)
|
|
222
|
-
TMUX_AI_APP_SOURCE=""
|
|
223
185
|
local err_file
|
|
224
186
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
225
187
|
err_file=$(mktemp -t gw_ai_err)
|
|
@@ -248,16 +210,7 @@ bridge_rename_session() {
|
|
|
248
210
|
|
|
249
211
|
# Perform the rename preserving canonical prefix and delimiter
|
|
250
212
|
if tmux_rename_session "$original_session_name" "$ai_name" >/dev/null; then
|
|
251
|
-
|
|
252
|
-
app_source="$(printf '%s' "$app_source" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '_')"
|
|
253
|
-
app_source="${app_source//__/_}"
|
|
254
|
-
app_source="${app_source##_}"
|
|
255
|
-
app_source="${app_source%%_}"
|
|
256
|
-
if [[ -n "$app_source" ]]; then
|
|
257
|
-
echo "\"success:app_source:${app_source}\""
|
|
258
|
-
else
|
|
259
|
-
echo "\"success\""
|
|
260
|
-
fi
|
|
213
|
+
echo "\"success\""
|
|
261
214
|
else
|
|
262
215
|
clear_auto_rename_requested
|
|
263
216
|
echo "\"rename_failed\""
|
|
@@ -277,12 +230,13 @@ bridge_rename_session() {
|
|
|
277
230
|
|
|
278
231
|
# Manual rename session
|
|
279
232
|
bridge_manual_rename_session() {
|
|
280
|
-
if [[ -z "${1:-}" ]] || [[ -
|
|
233
|
+
if [[ -z "${1:-}" ]] || [[ $# -lt 2 ]]; then
|
|
281
234
|
json_error "Old and new session names required"
|
|
282
235
|
return 1
|
|
283
236
|
fi
|
|
284
237
|
old_name="$1"
|
|
285
|
-
|
|
238
|
+
shift
|
|
239
|
+
new_display_name="$*"
|
|
286
240
|
|
|
287
241
|
if ! tmux_available; then
|
|
288
242
|
echo "\"tmux_not_available\""
|
|
@@ -294,9 +248,10 @@ bridge_manual_rename_session() {
|
|
|
294
248
|
echo "\"session_not_found\""
|
|
295
249
|
return 0
|
|
296
250
|
fi
|
|
251
|
+
new_display_name="$(printf '%s' "$new_display_name" | tr ' ' '_' | sed 's/[^a-zA-Z0-9_]/_/g' | cut -c1-100)"
|
|
297
252
|
|
|
298
|
-
# Validate new display name
|
|
299
|
-
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
|
|
300
255
|
echo "\"invalid_name\""
|
|
301
256
|
return 0
|
|
302
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}'
|
|
@@ -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
|
|
@@ -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
|