@humanu/orchestra 0.5.74 → 0.5.76

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.74",
3
+ "version": "0.5.76",
4
4
  "description": "AI-powered Git worktree and tmux session manager with modern TUI",
5
5
  "keywords": [
6
6
  "git",
@@ -434,7 +434,7 @@ with open(active_path, "r", encoding="utf-8") as handle:
434
434
  line = raw_line.rstrip("\n")
435
435
  if not line:
436
436
  continue
437
- parts = line.split("\t", 1)
437
+ parts = line.split("\t")
438
438
  name = parts[0].strip()
439
439
  display_name = parts[1].strip() if len(parts) > 1 else ""
440
440
  if not name:
@@ -506,9 +506,18 @@ _tmux_truncate_tab_label() {
506
506
  fi
507
507
  }
508
508
 
509
+ _tmux_pad_menu_label() {
510
+ local label="$1"
511
+ local width="${2:-56}"
512
+
513
+ label="$(_tmux_truncate_tab_label "$label" "$width")"
514
+ printf '%-*s' "$width" "$label"
515
+ }
516
+
509
517
  _tmux_workspace_session_rows() {
510
518
  local current_session="$1"
511
519
  local repo_root="$2"
520
+ local mode="${3:-visible}"
512
521
  local db_path active_file
513
522
  db_path="$(_tmux_session_registry_path)"
514
523
 
@@ -521,11 +530,11 @@ _tmux_workspace_session_rows() {
521
530
  return 0
522
531
  }
523
532
 
524
- python3 - "$db_path" "$repo_root" "$current_session" "$active_file" <<'PY'
533
+ python3 - "$db_path" "$repo_root" "$current_session" "$active_file" "$mode" <<'PY'
525
534
  import sqlite3
526
535
  import sys
527
536
 
528
- db_path, repo_root, current_session, active_path = sys.argv[1:5]
537
+ db_path, repo_root, current_session, active_path, mode = sys.argv[1:6]
529
538
  with open(active_path, "r", encoding="utf-8") as handle:
530
539
  active = {line.strip() for line in handle if line.strip()}
531
540
 
@@ -551,7 +560,7 @@ if not filtered:
551
560
  raise SystemExit(0)
552
561
 
553
562
  max_tabs = 8
554
- if len(filtered) <= max_tabs:
563
+ if mode == "all" or len(filtered) <= max_tabs:
555
564
  visible = filtered
556
565
  prefix = suffix = False
557
566
  else:
@@ -651,18 +660,28 @@ _tmux_configure_orchestra_bindings() {
651
660
  bridge="$(_orchestra_bridge_script)"
652
661
  [[ -f "$bridge" ]] || return
653
662
 
654
- local quoted_bridge rename_command prompt_command next_command previous_command help_command
663
+ local quoted_bridge rename_command prompt_command next_command previous_command new_session_command list_command close_command close_prompt_command help_command
655
664
  printf -v quoted_bridge '%q' "$bridge"
656
665
  rename_command="$quoted_bridge manual-rename-session \\\"#{session_name}\\\" \\\"%%\\\" >/dev/null 2>&1"
657
666
  prompt_command="command-prompt -p 'Rename Orchestra session:' 'run-shell -b \"$rename_command\"'"
658
667
  next_command="$quoted_bridge cycle-workspace-session \\\"#{session_name}\\\" next \\\"#{client_tty}\\\" >/dev/null 2>&1"
659
668
  previous_command="$quoted_bridge cycle-workspace-session \\\"#{session_name}\\\" previous \\\"#{client_tty}\\\" >/dev/null 2>&1"
660
- help_command="$quoted_bridge tmux-help-popup \\\"#{client_tty}\\\" >/dev/null 2>&1"
669
+ new_session_command="$quoted_bridge create-workspace-session \\\"#{session_name}\\\" \\\"#{client_tty}\\\" >/dev/null 2>&1 || true"
670
+ list_command="$quoted_bridge workspace-session-menu \\\"#{session_name}\\\" \\\"#{client_tty}\\\" >/dev/null 2>&1 || true"
671
+ close_command="$quoted_bridge close-workspace-session \\\"#{session_name}\\\" \\\"#{client_tty}\\\" >/dev/null 2>&1 || true"
672
+ close_prompt_command="confirm-before -p 'Close current Orchestra session? (y/n)' 'run-shell -b \"$close_command\"'"
673
+ help_command="$quoted_bridge tmux-help-popup \\\"#{client_tty}\\\" >/dev/null 2>&1 || true"
661
674
 
662
675
  tmux bind-key -T prefix '?' if-shell -F '#{@orchestra_display_name}' \
663
676
  "run-shell -b \"$help_command\"" 'list-keys -N' >/dev/null 2>&1 || true
664
677
  tmux bind-key -T prefix h if-shell -F '#{@orchestra_display_name}' \
665
678
  "run-shell -b \"$help_command\"" 'refresh-client -S' >/dev/null 2>&1 || true
679
+ tmux bind-key -T prefix n if-shell -F '#{@orchestra_display_name}' \
680
+ "run-shell -b \"$new_session_command\"" 'next-window' >/dev/null 2>&1 || true
681
+ tmux bind-key -T prefix l if-shell -F '#{@orchestra_display_name}' \
682
+ "run-shell -b \"$list_command\"" 'last-window' >/dev/null 2>&1 || true
683
+ tmux bind-key -T prefix X if-shell -F '#{@orchestra_display_name}' \
684
+ "$close_prompt_command" 'confirm-before -p "kill-session #S? (y/n)" kill-session' >/dev/null 2>&1 || true
666
685
  tmux bind-key -T prefix r if-shell -F '#{@orchestra_display_name}' \
667
686
  "$prompt_command" 'refresh-client -S' >/dev/null 2>&1 || true
668
687
  tmux bind-key -T prefix '>' if-shell -F '#{@orchestra_display_name}' \
@@ -900,6 +919,78 @@ tmux_ensure_session() {
900
919
  _tmux_source_command_hook "$sess"
901
920
  }
902
921
 
922
+ # Create a new auto-named Orchestra session in the current session's worktree and switch to it.
923
+ # Uses the same tmux_ensure_session path as the TUI tree view create action.
924
+ # Usage: tmux_create_workspace_session <current_session> [client_tty]
925
+ tmux_create_workspace_session() {
926
+ local current_session="$1"
927
+ local target_client="${2:-}"
928
+ local session_dir branch slug new_session
929
+
930
+ if ! tmux_available; then
931
+ err "tmux not installed"
932
+ return 1
933
+ fi
934
+ if [[ -z "$current_session" ]]; then
935
+ err "tmux_create_workspace_session: current session required"
936
+ return 1
937
+ fi
938
+
939
+ session_dir="$(tmux display-message -t "$current_session" -p '#{pane_current_path}' 2>/dev/null || echo "")"
940
+ if [[ -z "$session_dir" || ! -d "$session_dir" ]]; then
941
+ err "Unable to determine current worktree path"
942
+ return 1
943
+ fi
944
+
945
+ branch="$(_tmux_branch_for_path "$session_dir" 2>/dev/null || true)"
946
+ if [[ -z "$branch" || "$branch" == "detached" ]]; then
947
+ err "Unable to determine current worktree branch"
948
+ return 1
949
+ fi
950
+
951
+ slug="$(git_branch_to_slug "$branch")"
952
+ new_session="$(tmux_ensure_session "$slug" "" "$session_dir")" || return 1
953
+
954
+ if [[ -n "$target_client" ]]; then
955
+ tmux switch-client -c "$target_client" -t "$new_session" >/dev/null 2>&1 || return 1
956
+ else
957
+ tmux switch-client -t "$new_session" >/dev/null 2>&1 || return 1
958
+ fi
959
+
960
+ printf '%s\n' "$new_session"
961
+ }
962
+
963
+ # Close the current Orchestra session after switching the client to the next one.
964
+ # Usage: tmux_close_workspace_session <current_session> [client_tty]
965
+ tmux_close_workspace_session() {
966
+ local current_session="$1"
967
+ local target_client="${2:-}"
968
+ local target_session
969
+
970
+ if ! tmux_available; then
971
+ err "tmux not installed"
972
+ return 1
973
+ fi
974
+ if [[ -z "$current_session" ]]; then
975
+ err "tmux_close_workspace_session: current session required"
976
+ return 1
977
+ fi
978
+
979
+ if ! target_session="$(tmux_workspace_cycle_target "$current_session" next)"; then
980
+ tmux display-message -d 2500 "No other Orchestra sessions; current session kept" 2>/dev/null || true
981
+ return 1
982
+ fi
983
+
984
+ if [[ -n "$target_client" ]]; then
985
+ tmux switch-client -c "$target_client" -t "$target_session" >/dev/null 2>&1 || return 1
986
+ else
987
+ tmux switch-client -t "$target_session" >/dev/null 2>&1 || return 1
988
+ fi
989
+
990
+ tmux_kill_session "$current_session" >/dev/null 2>&1 || return 1
991
+ _tmux_refresh_orchestra_session_status "$target_session" >/dev/null 2>&1 || true
992
+ }
993
+
903
994
  # Check if a session exists
904
995
  # Usage: tmux_session_exists <session_name>
905
996
  tmux_session_exists() {
@@ -1077,7 +1168,7 @@ tmux_rename_session() {
1077
1168
 
1078
1169
  # --------------------------- Session Discovery ------------------------------
1079
1170
 
1080
- # Get tmux sessions for a slug prefix; prints session names sorted by last attached/activity
1171
+ # Get tmux sessions for a slug prefix; prints session names sorted by creation time, oldest first
1081
1172
  # Expected session format: [worktreename]_[worktreehash]_[datetime]_[readable_name]
1082
1173
  # Usage: tmux_list_sessions_for_slug <slug> [worktree_path]
1083
1174
  tmux_list_sessions_for_slug() {
@@ -1108,19 +1199,19 @@ tmux_list_sessions_for_slug() {
1108
1199
  fi
1109
1200
 
1110
1201
  # List sessions with any known prefix variant
1111
- tmux list-sessions -F '#{session_name}|||#{session_last_attached}|||#{session_activity}' 2>/dev/null \
1202
+ tmux list-sessions -F '#{session_name}|||#{session_created}' 2>/dev/null \
1112
1203
  | sed 's/|||/\t/g' \
1113
- | awk -v a="$p1_new" -v b="$p2_new" -v c="$p1_old" -v d="$p2_old" -v e="$p1_branch_hash" -v f="$p2_branch_hash" 'BEGIN{FS="\t"} (length(a)>0 && index($1, a)==1) || (length(b)>0 && index($1, b)==1) || (length(c)>0 && index($1, c)==1) || (length(d)>0 && index($1, d)==1) || (length(e)>0 && index($1, e)==1) || (length(f)>0 && index($1, f)==1) {print $1"\t"$2"\t"$3}' \
1114
- | sort -t $'\t' -k2,2nr -k3,3nr \
1204
+ | awk -v a="$p1_new" -v b="$p2_new" -v c="$p1_old" -v d="$p2_old" -v e="$p1_branch_hash" -v f="$p2_branch_hash" 'BEGIN{FS="\t"} (length(a)>0 && index($1, a)==1) || (length(b)>0 && index($1, b)==1) || (length(c)>0 && index($1, c)==1) || (length(d)>0 && index($1, d)==1) || (length(e)>0 && index($1, e)==1) || (length(f)>0 && index($1, f)==1) {print $1"\t"$2}' \
1205
+ | sort -t $'\t' -k2,2n -k1,1f \
1115
1206
  | awk -F '\t' '{print $1}' | awk '!seen[$0]++' || true
1116
1207
  else
1117
1208
  # New-format prefix matching without hash: orchestra__slug__... or slug__...
1118
1209
  local prefix1="${ORCH_PREFIX}${slug}${d}"
1119
1210
  local prefix2="${slug}${d}"
1120
- tmux list-sessions -F '#{session_name}|||#{session_last_attached}|||#{session_activity}' 2>/dev/null \
1211
+ tmux list-sessions -F '#{session_name}|||#{session_created}' 2>/dev/null \
1121
1212
  | sed 's/|||/\t/g' \
1122
- | awk -v p1="$prefix1" -v p2="$prefix2" 'BEGIN{FS="\t"} index($1, p1)==1 || index($1, p2)==1 {print $1"\t"$2"\t"$3}' \
1123
- | sort -t $'\t' -k2,2nr -k3,3nr \
1213
+ | awk -v p1="$prefix1" -v p2="$prefix2" 'BEGIN{FS="\t"} index($1, p1)==1 || index($1, p2)==1 {print $1"\t"$2}' \
1214
+ | sort -t $'\t' -k2,2n -k1,1f \
1124
1215
  | awk -F '\t' '{print $1}' || true
1125
1216
  fi
1126
1217
  }
@@ -1511,6 +1602,9 @@ printf "%s\n" \
1511
1602
  "" \
1512
1603
  "Ctrl+b, d Detach and return to Orchestra" \
1513
1604
  "Ctrl+b, r Rename the current Orchestra session" \
1605
+ "Ctrl+b, n New Orchestra session in this worktree" \
1606
+ "Ctrl+b, X Close current session and switch next" \
1607
+ "Ctrl+b, l List active sessions" \
1514
1608
  "" \
1515
1609
  "Ctrl+b, Left Previous Orchestra session in this workspace" \
1516
1610
  "Ctrl+b, Right Next Orchestra session in this workspace" \
@@ -1528,7 +1622,89 @@ exit 0
1528
1622
  EOF
1529
1623
  )"
1530
1624
 
1531
- tmux display-popup "${target_args[@]}" -E -w 78 -h 18 -T "Orchestra shortcuts" "$popup_command" >/dev/null 2>&1 || true
1625
+ tmux display-popup "${target_args[@]}" \
1626
+ -E \
1627
+ -w 84 \
1628
+ -h 22 \
1629
+ -s "fg=#c0caf5,bg=#1a1b26" \
1630
+ -S "fg=#7aa2f7,bg=#1a1b26" \
1631
+ -T "Orchestra shortcuts" \
1632
+ "$popup_command" >/dev/null 2>&1 || true
1633
+ }
1634
+
1635
+ # Show active Orchestra sessions in a native tmux menu for arrow-key selection.
1636
+ # Usage: tmux_show_workspace_session_menu <current_session> [client_tty]
1637
+ tmux_show_workspace_session_menu() {
1638
+ local current_session="$1"
1639
+ local target_client="${2:-}"
1640
+ local session_dir repo_root rows name display_name label target_command
1641
+ local menu_width=56
1642
+ local min_menu_rows=10
1643
+ local menu_row_count=0
1644
+
1645
+ if ! tmux_available; then
1646
+ err "tmux not installed"
1647
+ return 1
1648
+ fi
1649
+ if [[ -z "$current_session" ]]; then
1650
+ err "tmux_show_workspace_session_menu: current session required"
1651
+ return 1
1652
+ fi
1653
+
1654
+ session_dir="$(tmux display-message -t "$current_session" -p '#{pane_current_path}' 2>/dev/null || echo "")"
1655
+ repo_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
1656
+ if [[ -z "$repo_root" ]]; then
1657
+ tmux display-message -d 2500 "Unable to determine Orchestra workspace" 2>/dev/null || true
1658
+ return 1
1659
+ fi
1660
+
1661
+ _tmux_registry_sync_active_workspace_sessions "$repo_root" >/dev/null 2>&1 || true
1662
+ rows="$(_tmux_workspace_session_rows "$current_session" "$repo_root" all 2>/dev/null || true)"
1663
+ if [[ -z "$rows" ]]; then
1664
+ tmux display-message -d 2500 "No active Orchestra sessions in this workspace" 2>/dev/null || true
1665
+ return 1
1666
+ fi
1667
+
1668
+ local target_args=()
1669
+ if [[ -n "$target_client" ]]; then
1670
+ target_args=(-c "$target_client")
1671
+ fi
1672
+
1673
+ local menu_items=()
1674
+ while IFS=$'\t' read -r name display_name; do
1675
+ [[ -n "$name" && "$name" != "__ellipsis__" ]] || continue
1676
+ if [[ -z "$display_name" ]]; then
1677
+ display_name="$(tmux_format_session_display "$name" without-timestamp)"
1678
+ fi
1679
+ label="$(_tmux_truncate_tab_label "$display_name" 42)"
1680
+ if [[ "$name" == "$current_session" ]]; then
1681
+ label="● $label"
1682
+ else
1683
+ label=" $label"
1684
+ fi
1685
+ label="$(_tmux_pad_menu_label "$label" "$menu_width")"
1686
+ label="$(_tmux_status_escape_text "$label")"
1687
+ target_command="switch-client -t \"$name\""
1688
+ menu_items+=("$label" "" "$target_command")
1689
+ menu_row_count=$((menu_row_count + 1))
1690
+ done <<< "$rows"
1691
+
1692
+ if [[ ${#menu_items[@]} -eq 0 ]]; then
1693
+ tmux display-message -d 2500 "No active Orchestra sessions in this workspace" 2>/dev/null || true
1694
+ return 1
1695
+ fi
1696
+
1697
+ while (( menu_row_count < min_menu_rows )); do
1698
+ menu_items+=("$(_tmux_pad_menu_label "" "$menu_width")" "" "")
1699
+ menu_row_count=$((menu_row_count + 1))
1700
+ done
1701
+
1702
+ tmux display-menu "${target_args[@]}" \
1703
+ -s "fg=#c0caf5,bg=#1a1b26" \
1704
+ -S "fg=#7aa2f7,bg=#1a1b26" \
1705
+ -H "fg=#1a1b26,bg=#ff9e64,bold" \
1706
+ -T "Orchestra sessions" \
1707
+ "${menu_items[@]}" >/dev/null 2>&1 || true
1532
1708
  }
1533
1709
 
1534
1710
  # Find the adjacent active Orchestra session registered for the current repo.
@@ -1561,7 +1737,7 @@ tmux_workspace_cycle_target() {
1561
1737
  }
1562
1738
 
1563
1739
  active_file="$(mktemp)"
1564
- tmux list-sessions -F $'#{session_name}\t#{@orchestra_display_name}' > "$active_file" 2>/dev/null || {
1740
+ tmux list-sessions -F $'#{session_name}\t#{@orchestra_display_name}\t#{session_last_attached}\t#{session_activity}' > "$active_file" 2>/dev/null || {
1565
1741
  rm -f "$active_file"
1566
1742
  err "Unable to list tmux sessions"
1567
1743
  return 1
@@ -97,7 +97,19 @@ case "${1:-}" in
97
97
  "create-session-exact")
98
98
  bridge_create_session_exact "${2:-}" "${3:-}"
99
99
  ;;
100
-
100
+
101
+ "create-workspace-session")
102
+ bridge_create_workspace_session "${2:-}" "${3:-}"
103
+ ;;
104
+
105
+ "close-workspace-session")
106
+ bridge_close_workspace_session "${2:-}" "${3:-}"
107
+ ;;
108
+
109
+ "workspace-session-menu")
110
+ bridge_workspace_session_menu "${2:-}" "${3:-}"
111
+ ;;
112
+
101
113
  "kill-session")
102
114
  bridge_kill_session "${2:-}"
103
115
  ;;
@@ -243,6 +255,12 @@ Commands:
243
255
  copy-env-files <source> <target> Copy .env files between directories
244
256
  cycle-workspace-session <session> <next|previous> [client]
245
257
  Switch within registered Orchestra sessions for this repo
258
+ create-workspace-session <session> [client]
259
+ Create a new session in the current worktree and switch to it
260
+ close-workspace-session <session> [client]
261
+ Close current session after switching to the next one
262
+ workspace-session-menu <session> [client]
263
+ Show an interactive active-session menu
246
264
  tmux-help-popup [client] Show Orchestra tmux shortcut popup
247
265
  repo-info Get repository information
248
266
  git-status Get git status --porcelain
@@ -62,6 +62,66 @@ bridge_create_session_exact() {
62
62
  fi
63
63
  }
64
64
 
65
+ # Create a new tmux session in the current session's worktree and switch to it.
66
+ bridge_create_workspace_session() {
67
+ if [[ -z "${1:-}" ]]; then
68
+ json_error "Current session name required"
69
+ return 1
70
+ fi
71
+ session_name="$1"
72
+ target_client="${2:-}"
73
+
74
+ if tmux_available; then
75
+ created_session="$(tmux_create_workspace_session "$session_name" "$target_client")" || {
76
+ json_error "Failed to create workspace session"
77
+ return 1
78
+ }
79
+ json_object "ok:b=true" "session:s=$created_session"
80
+ else
81
+ json_error "tmux not available"
82
+ fi
83
+ }
84
+
85
+ # Close current tmux session after switching to the next workspace session.
86
+ bridge_close_workspace_session() {
87
+ if [[ -z "${1:-}" ]]; then
88
+ json_error "Current session name required"
89
+ return 1
90
+ fi
91
+ session_name="$1"
92
+ target_client="${2:-}"
93
+
94
+ if tmux_available; then
95
+ tmux_close_workspace_session "$session_name" "$target_client" || {
96
+ json_error "Failed to close workspace session"
97
+ return 1
98
+ }
99
+ json_object "ok:b=true"
100
+ else
101
+ json_error "tmux not available"
102
+ fi
103
+ }
104
+
105
+ # Show interactive workspace session menu.
106
+ bridge_workspace_session_menu() {
107
+ if [[ -z "${1:-}" ]]; then
108
+ json_error "Current session name required"
109
+ return 1
110
+ fi
111
+ session_name="$1"
112
+ target_client="${2:-}"
113
+
114
+ if tmux_available; then
115
+ tmux_show_workspace_session_menu "$session_name" "$target_client" || {
116
+ json_error "Failed to show workspace session menu"
117
+ return 1
118
+ }
119
+ json_object "ok:b=true"
120
+ else
121
+ json_error "tmux not available"
122
+ fi
123
+ }
124
+
65
125
  # Kill tmux session
66
126
  bridge_kill_session() {
67
127
  if [[ -z "${1:-}" ]]; then