@humanu/orchestra 0.5.66 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanu/orchestra",
3
- "version": "0.5.66",
3
+ "version": "0.5.69",
4
4
  "description": "AI-powered Git worktree and tmux session manager with modern TUI",
5
5
  "keywords": [
6
6
  "git",
@@ -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
- tmux set-option -t "$session_name" status-left "Go back (detach): Ctrl+b,d | Copy (scroll) mode: Ctrl+b,[ | Worktree: ${worktree_name}" >/dev/null 2>&1 || true
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
- tmux set-option -t "$sess" status-left "Go back (detach): Ctrl+b,d | Copy (scroll) mode: Ctrl+b,[ | Worktree: ${worktree_name}" >/dev/null 2>&1 || true
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: Fixing Auth Bug" or "My Feature Work"
714
+ # Output: "Opencode Fixing Auth Bug" or "My Feature Work"
650
715
  format_session_display_name() {
651
716
  local session_content="$1"
652
-
653
- # Known app prefixes and their display names
654
- local known_apps=("opencode" "running" "ssh" "docker" "k8s" "git" "db")
655
- local app_prefix=""
656
- local description=""
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
- # Convert underscores and hyphens to spaces and apply sentence case
674
- description="$(echo "$description" | tr '_-' ' ')"
675
-
676
- # Apply sentence case (capitalize first letter of each word)
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
- # Format final display name
680
- if [[ -n "$app_prefix" ]]; then
681
- # Capitalize app name and format as "App: Description"
682
- local app_display="$(echo "$app_prefix" | awk '{print toupper(substr($1,1,1)) tolower(substr($1,2))}')"
683
- echo "${app_display}: ${description}"
684
- else
685
- echo "$description"
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: "App: Description" or formatted display name
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
- # Strip leading orchestra_ prefix if present
706
- if [[ "$session_name" =~ ^orchestra_(.+)$ ]]; then
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
- echo "${formatted_name} (${month_name} ${display_day} ${display_hour}:${minute}${ampm})"
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 with app prefix
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
- echo "${formatted_name} (${month_name} ${display_day} ${display_hour}:${minute}${ampm})"
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
- local parsed_app_prefix
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"
@@ -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
- app_prefix="${request_payload%%$'\n'*}"
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
- local app_source="${TMUX_AI_APP_SOURCE-}"
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:-}" ]] || [[ -z "${2:-}" ]]; then
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
- new_display_name="$2"
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 (alphanumeric, underscore, hyphen only)
299
- if [[ ! "$new_display_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
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 2 || true
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 Enable debug mode
13
- -h, --help Show this help
14
- --check-updates Check for available updates
15
- --update Check for available updates (alias for --check-updates)
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 signal for determining app prefixes.
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