@humanu/orchestra 0.5.71 → 0.5.73
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 +662 -32
- package/resources/prebuilt/linux-x64/orchestra +0 -0
- package/resources/prebuilt/macos-arm64/orchestra +0 -0
- package/resources/prebuilt/macos-intel/orchestra +0 -0
- package/resources/scripts/gw-bridge.sh +11 -0
- package/resources/scripts/shell/bridge/tmux.sh +36 -0
package/package.json
CHANGED
package/resources/api/tmux.sh
CHANGED
|
@@ -236,18 +236,422 @@ _tmux_source_command_hook() {
|
|
|
236
236
|
# Helper: orchestra prefix including delimiter
|
|
237
237
|
_tmux_orch_prefix() { printf "orchestra%s" "$( _tmux_delim )"; }
|
|
238
238
|
|
|
239
|
+
_tmux_session_registry_path() {
|
|
240
|
+
if [[ -n "${ORCHESTRA_SESSION_REGISTRY_PATH-}" ]]; then
|
|
241
|
+
printf '%s\n' "$ORCHESTRA_SESSION_REGISTRY_PATH"
|
|
242
|
+
return
|
|
243
|
+
fi
|
|
244
|
+
printf '%s\n' "$HOME/.orchestra/orchestra.sqlite3"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_tmux_shared_root_for_path() {
|
|
248
|
+
local path="$1"
|
|
249
|
+
local old_pwd="$PWD"
|
|
250
|
+
local root=""
|
|
251
|
+
if [[ -n "$path" && -d "$path" ]]; then
|
|
252
|
+
cd "$path" 2>/dev/null || return 1
|
|
253
|
+
root="$(git_shared_root 2>/dev/null || git_repo_root 2>/dev/null || true)"
|
|
254
|
+
cd "$old_pwd" 2>/dev/null || true
|
|
255
|
+
fi
|
|
256
|
+
[[ -n "$root" ]] || return 1
|
|
257
|
+
printf '%s\n' "$root"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_tmux_branch_for_path() {
|
|
261
|
+
local path="$1"
|
|
262
|
+
local old_pwd="$PWD"
|
|
263
|
+
local branch=""
|
|
264
|
+
if [[ -n "$path" && -d "$path" ]]; then
|
|
265
|
+
cd "$path" 2>/dev/null || return 1
|
|
266
|
+
branch="$(git_current_branch 2>/dev/null || true)"
|
|
267
|
+
cd "$old_pwd" 2>/dev/null || true
|
|
268
|
+
fi
|
|
269
|
+
[[ -n "$branch" ]] || return 1
|
|
270
|
+
printf '%s\n' "$branch"
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
_tmux_registry_upsert_session() {
|
|
274
|
+
local repo_root="$1"
|
|
275
|
+
local branch="$2"
|
|
276
|
+
local worktree_path="$3"
|
|
277
|
+
local tmux_name="$4"
|
|
278
|
+
local db_path
|
|
279
|
+
db_path="$(_tmux_session_registry_path)"
|
|
280
|
+
|
|
281
|
+
[[ -n "$repo_root" && -n "$branch" && -n "$worktree_path" && -n "$tmux_name" ]] || return 1
|
|
282
|
+
have_cmd python3 || return 1
|
|
283
|
+
|
|
284
|
+
python3 - "$db_path" "$repo_root" "$branch" "$worktree_path" "$tmux_name" <<'PY'
|
|
285
|
+
import os
|
|
286
|
+
import sqlite3
|
|
287
|
+
import sys
|
|
288
|
+
import time
|
|
289
|
+
|
|
290
|
+
db_path, repo_root, branch, worktree_path, tmux_name = sys.argv[1:6]
|
|
291
|
+
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
292
|
+
slug = branch.replace("/", "-")
|
|
293
|
+
now = int(time.time())
|
|
294
|
+
|
|
295
|
+
conn = sqlite3.connect(db_path)
|
|
296
|
+
conn.execute(
|
|
297
|
+
"""
|
|
298
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
299
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
300
|
+
tmux_name TEXT NOT NULL UNIQUE,
|
|
301
|
+
repo_root TEXT NOT NULL,
|
|
302
|
+
worktree_path TEXT NOT NULL,
|
|
303
|
+
branch TEXT NOT NULL,
|
|
304
|
+
slug TEXT NOT NULL,
|
|
305
|
+
display_name TEXT,
|
|
306
|
+
created_at INTEGER NOT NULL,
|
|
307
|
+
updated_at INTEGER NOT NULL,
|
|
308
|
+
last_seen_at INTEGER NOT NULL
|
|
309
|
+
)
|
|
310
|
+
"""
|
|
311
|
+
)
|
|
312
|
+
conn.execute(
|
|
313
|
+
"CREATE INDEX IF NOT EXISTS idx_sessions_worktree ON sessions(repo_root, worktree_path)"
|
|
314
|
+
)
|
|
315
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_sessions_slug ON sessions(repo_root, slug)")
|
|
316
|
+
conn.execute(
|
|
317
|
+
"""
|
|
318
|
+
INSERT INTO sessions (
|
|
319
|
+
tmux_name, repo_root, worktree_path, branch, slug,
|
|
320
|
+
created_at, updated_at, last_seen_at
|
|
321
|
+
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?6, ?6)
|
|
322
|
+
ON CONFLICT(tmux_name) DO UPDATE SET
|
|
323
|
+
repo_root = excluded.repo_root,
|
|
324
|
+
worktree_path = excluded.worktree_path,
|
|
325
|
+
branch = excluded.branch,
|
|
326
|
+
slug = excluded.slug,
|
|
327
|
+
updated_at = excluded.updated_at,
|
|
328
|
+
last_seen_at = excluded.last_seen_at
|
|
329
|
+
""",
|
|
330
|
+
(tmux_name, repo_root, worktree_path, branch, slug, now),
|
|
331
|
+
)
|
|
332
|
+
conn.commit()
|
|
333
|
+
PY
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
_tmux_registry_rename_session() {
|
|
337
|
+
local old_tmux_name="$1"
|
|
338
|
+
local new_tmux_name="$2"
|
|
339
|
+
local display_name="${3:-}"
|
|
340
|
+
local db_path
|
|
341
|
+
db_path="$(_tmux_session_registry_path)"
|
|
342
|
+
|
|
343
|
+
[[ -f "$db_path" ]] || return 0
|
|
344
|
+
have_cmd python3 || return 1
|
|
345
|
+
|
|
346
|
+
python3 - "$db_path" "$old_tmux_name" "$new_tmux_name" "$display_name" <<'PY'
|
|
347
|
+
import sqlite3
|
|
348
|
+
import sys
|
|
349
|
+
import time
|
|
350
|
+
|
|
351
|
+
db_path, old_tmux_name, new_tmux_name, display_name = sys.argv[1:5]
|
|
352
|
+
now = int(time.time())
|
|
353
|
+
value = display_name if display_name else None
|
|
354
|
+
|
|
355
|
+
conn = sqlite3.connect(db_path)
|
|
356
|
+
conn.execute(
|
|
357
|
+
"""
|
|
358
|
+
UPDATE sessions
|
|
359
|
+
SET tmux_name = ?1,
|
|
360
|
+
display_name = COALESCE(?2, display_name),
|
|
361
|
+
updated_at = ?3,
|
|
362
|
+
last_seen_at = ?3
|
|
363
|
+
WHERE tmux_name = ?4
|
|
364
|
+
""",
|
|
365
|
+
(new_tmux_name, value, now, old_tmux_name),
|
|
366
|
+
)
|
|
367
|
+
conn.commit()
|
|
368
|
+
PY
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_tmux_registry_remove_session() {
|
|
372
|
+
local tmux_name="$1"
|
|
373
|
+
local db_path
|
|
374
|
+
db_path="$(_tmux_session_registry_path)"
|
|
375
|
+
|
|
376
|
+
[[ -f "$db_path" ]] || return 0
|
|
377
|
+
have_cmd python3 || return 1
|
|
378
|
+
|
|
379
|
+
python3 - "$db_path" "$tmux_name" <<'PY'
|
|
380
|
+
import sqlite3
|
|
381
|
+
import sys
|
|
382
|
+
|
|
383
|
+
db_path, tmux_name = sys.argv[1:3]
|
|
384
|
+
conn = sqlite3.connect(db_path)
|
|
385
|
+
conn.execute("DELETE FROM sessions WHERE tmux_name = ?1", (tmux_name,))
|
|
386
|
+
conn.commit()
|
|
387
|
+
PY
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
_tmux_registry_upsert_current_session() {
|
|
391
|
+
local session_name="$1"
|
|
392
|
+
local session_dir repo_root branch
|
|
393
|
+
session_dir="$(tmux display-message -t "$session_name" -p '#{pane_current_path}' 2>/dev/null || echo "")"
|
|
394
|
+
repo_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
|
|
395
|
+
branch="$(_tmux_branch_for_path "$session_dir" 2>/dev/null || true)"
|
|
396
|
+
[[ -n "$repo_root" && -n "$branch" ]] || return 1
|
|
397
|
+
_tmux_registry_upsert_session "$repo_root" "$branch" "$session_dir" "$session_name"
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
_tmux_registry_sync_active_workspace_sessions() {
|
|
401
|
+
local repo_root="$1"
|
|
402
|
+
[[ -n "$repo_root" ]] || return 1
|
|
403
|
+
tmux_available || return 1
|
|
404
|
+
|
|
405
|
+
local session_name session_dir session_root branch display_name
|
|
406
|
+
while IFS= read -r session_name; do
|
|
407
|
+
[[ -n "$session_name" ]] || continue
|
|
408
|
+
display_name="$(tmux show-option -t "$session_name" -qv @orchestra_display_name 2>/dev/null || true)"
|
|
409
|
+
[[ -n "$display_name" ]] || continue
|
|
410
|
+
session_dir="$(tmux display-message -t "$session_name" -p '#{pane_current_path}' 2>/dev/null || echo "")"
|
|
411
|
+
session_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
|
|
412
|
+
[[ "$session_root" == "$repo_root" ]] || continue
|
|
413
|
+
branch="$(_tmux_branch_for_path "$session_dir" 2>/dev/null || true)"
|
|
414
|
+
[[ -n "$branch" ]] || continue
|
|
415
|
+
_tmux_registry_upsert_session "$repo_root" "$branch" "$session_dir" "$session_name" >/dev/null 2>&1 || true
|
|
416
|
+
done < <(tmux list-sessions -F '#{session_name}' 2>/dev/null || true)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
_tmux_workspace_cycle_target_cached() {
|
|
420
|
+
local current_session="$1"
|
|
421
|
+
local direction="$2"
|
|
422
|
+
local db_path="$3"
|
|
423
|
+
local active_file="$4"
|
|
424
|
+
|
|
425
|
+
python3 - "$db_path" "$current_session" "$direction" "$active_file" <<'PY'
|
|
426
|
+
import sqlite3
|
|
427
|
+
import sys
|
|
428
|
+
|
|
429
|
+
db_path, current_session, direction, active_path = sys.argv[1:5]
|
|
430
|
+
active = set()
|
|
431
|
+
active_orchestra = set()
|
|
432
|
+
with open(active_path, "r", encoding="utf-8") as handle:
|
|
433
|
+
for raw_line in handle:
|
|
434
|
+
line = raw_line.rstrip("\n")
|
|
435
|
+
if not line:
|
|
436
|
+
continue
|
|
437
|
+
parts = line.split("\t", 1)
|
|
438
|
+
name = parts[0].strip()
|
|
439
|
+
display_name = parts[1].strip() if len(parts) > 1 else ""
|
|
440
|
+
if not name:
|
|
441
|
+
continue
|
|
442
|
+
active.add(name)
|
|
443
|
+
if display_name:
|
|
444
|
+
active_orchestra.add(name)
|
|
445
|
+
|
|
446
|
+
try:
|
|
447
|
+
conn = sqlite3.connect(db_path)
|
|
448
|
+
current_row = conn.execute(
|
|
449
|
+
"SELECT repo_root FROM sessions WHERE tmux_name = ?1",
|
|
450
|
+
(current_session,),
|
|
451
|
+
).fetchone()
|
|
452
|
+
if current_row is None:
|
|
453
|
+
raise SystemExit(2)
|
|
454
|
+
|
|
455
|
+
registered = {
|
|
456
|
+
name
|
|
457
|
+
for (name,) in conn.execute("SELECT tmux_name FROM sessions").fetchall()
|
|
458
|
+
}
|
|
459
|
+
if active_orchestra.difference(registered):
|
|
460
|
+
raise SystemExit(2)
|
|
461
|
+
|
|
462
|
+
rows = conn.execute(
|
|
463
|
+
"""
|
|
464
|
+
SELECT tmux_name
|
|
465
|
+
FROM sessions
|
|
466
|
+
WHERE repo_root = ?1
|
|
467
|
+
ORDER BY worktree_path COLLATE NOCASE, created_at, tmux_name COLLATE NOCASE
|
|
468
|
+
""",
|
|
469
|
+
(current_row[0],),
|
|
470
|
+
).fetchall()
|
|
471
|
+
except sqlite3.Error:
|
|
472
|
+
raise SystemExit(2)
|
|
473
|
+
|
|
474
|
+
names = []
|
|
475
|
+
seen = set()
|
|
476
|
+
for (name,) in rows:
|
|
477
|
+
if name in active and name not in seen:
|
|
478
|
+
names.append(name)
|
|
479
|
+
seen.add(name)
|
|
480
|
+
|
|
481
|
+
if len(names) < 2 or current_session not in seen:
|
|
482
|
+
raise SystemExit(3)
|
|
483
|
+
|
|
484
|
+
index = names.index(current_session)
|
|
485
|
+
if direction == "next":
|
|
486
|
+
index = (index + 1) % len(names)
|
|
487
|
+
else:
|
|
488
|
+
index = (index - 1) % len(names)
|
|
489
|
+
print(names[index])
|
|
490
|
+
PY
|
|
491
|
+
}
|
|
492
|
+
|
|
239
493
|
_tmux_status_escape_text() {
|
|
240
494
|
local text="$1"
|
|
241
495
|
text="${text//\#/##}"
|
|
242
496
|
printf '%s' "$text"
|
|
243
497
|
}
|
|
244
498
|
|
|
499
|
+
_tmux_truncate_tab_label() {
|
|
500
|
+
local label="$1"
|
|
501
|
+
local max_len="${2:-14}"
|
|
502
|
+
if (( ${#label} > max_len )); then
|
|
503
|
+
printf '%s' "${label:0:max_len}"
|
|
504
|
+
else
|
|
505
|
+
printf '%s' "$label"
|
|
506
|
+
fi
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
_tmux_pad_tab_label() {
|
|
510
|
+
local label="$1"
|
|
511
|
+
local width="${2:-14}"
|
|
512
|
+
|
|
513
|
+
label="$(_tmux_truncate_tab_label "$label" "$width")"
|
|
514
|
+
printf '%-*s' "$width" "$label"
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
_tmux_workspace_session_rows() {
|
|
518
|
+
local current_session="$1"
|
|
519
|
+
local repo_root="$2"
|
|
520
|
+
local db_path active_file
|
|
521
|
+
db_path="$(_tmux_session_registry_path)"
|
|
522
|
+
|
|
523
|
+
[[ -f "$db_path" ]] || return 0
|
|
524
|
+
have_cmd python3 || return 0
|
|
525
|
+
|
|
526
|
+
active_file="$(mktemp)"
|
|
527
|
+
tmux list-sessions -F '#{session_name}' > "$active_file" 2>/dev/null || {
|
|
528
|
+
rm -f "$active_file"
|
|
529
|
+
return 0
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
python3 - "$db_path" "$repo_root" "$current_session" "$active_file" <<'PY'
|
|
533
|
+
import sqlite3
|
|
534
|
+
import sys
|
|
535
|
+
|
|
536
|
+
db_path, repo_root, current_session, active_path = sys.argv[1:5]
|
|
537
|
+
with open(active_path, "r", encoding="utf-8") as handle:
|
|
538
|
+
active = {line.strip() for line in handle if line.strip()}
|
|
539
|
+
|
|
540
|
+
conn = sqlite3.connect(db_path)
|
|
541
|
+
rows = conn.execute(
|
|
542
|
+
"""
|
|
543
|
+
SELECT tmux_name, COALESCE(display_name, '')
|
|
544
|
+
FROM sessions
|
|
545
|
+
WHERE repo_root = ?1
|
|
546
|
+
ORDER BY worktree_path COLLATE NOCASE, created_at, tmux_name COLLATE NOCASE
|
|
547
|
+
""",
|
|
548
|
+
(repo_root,),
|
|
549
|
+
).fetchall()
|
|
550
|
+
|
|
551
|
+
filtered = []
|
|
552
|
+
seen = set()
|
|
553
|
+
for name, display_name in rows:
|
|
554
|
+
if name in active and name not in seen:
|
|
555
|
+
filtered.append((name, display_name or ""))
|
|
556
|
+
seen.add(name)
|
|
557
|
+
|
|
558
|
+
if not filtered:
|
|
559
|
+
raise SystemExit(0)
|
|
560
|
+
|
|
561
|
+
max_tabs = 8
|
|
562
|
+
if len(filtered) <= max_tabs:
|
|
563
|
+
visible = filtered
|
|
564
|
+
prefix = suffix = False
|
|
565
|
+
else:
|
|
566
|
+
try:
|
|
567
|
+
current_index = [name for name, _ in filtered].index(current_session)
|
|
568
|
+
except ValueError:
|
|
569
|
+
current_index = 0
|
|
570
|
+
start = max(0, min(current_index - 2, len(filtered) - max_tabs))
|
|
571
|
+
end = min(len(filtered), start + max_tabs)
|
|
572
|
+
visible = filtered[start:end]
|
|
573
|
+
prefix = start > 0
|
|
574
|
+
suffix = end < len(filtered)
|
|
575
|
+
|
|
576
|
+
if prefix:
|
|
577
|
+
print("__ellipsis__\t")
|
|
578
|
+
for name, display_name in visible:
|
|
579
|
+
print(f"{name}\t{display_name}")
|
|
580
|
+
if suffix:
|
|
581
|
+
print("__ellipsis__\t")
|
|
582
|
+
PY
|
|
583
|
+
local status=$?
|
|
584
|
+
rm -f "$active_file"
|
|
585
|
+
return $status
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
_tmux_workspace_session_tabs() {
|
|
589
|
+
local current_session="$1"
|
|
590
|
+
local fallback_display_name="$2"
|
|
591
|
+
local session_dir repo_root rows tabs name display_name tab_label escaped_tab_label divider active_style inactive_style muted_style reset_style
|
|
592
|
+
local tab_width=21
|
|
593
|
+
local active_label_width=19
|
|
594
|
+
|
|
595
|
+
session_dir="$(tmux display-message -t "$current_session" -p '#{pane_current_path}' 2>/dev/null || echo "")"
|
|
596
|
+
repo_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
|
|
597
|
+
if [[ -n "$repo_root" ]]; then
|
|
598
|
+
_tmux_registry_sync_active_workspace_sessions "$repo_root" >/dev/null 2>&1 || true
|
|
599
|
+
rows="$(_tmux_workspace_session_rows "$current_session" "$repo_root" 2>/dev/null || true)"
|
|
600
|
+
else
|
|
601
|
+
rows=""
|
|
602
|
+
fi
|
|
603
|
+
|
|
604
|
+
if [[ -z "$rows" ]]; then
|
|
605
|
+
tab_label="$(_tmux_pad_tab_label "$fallback_display_name" "$active_label_width")"
|
|
606
|
+
escaped_tab_label="$(_tmux_status_escape_text "$tab_label")"
|
|
607
|
+
printf '#[bg=#414868] #[fg=#ff9e64,bg=#414868,bold]● #[fg=#c0caf5,bg=#414868,bold]%s #[default]' "$escaped_tab_label"
|
|
608
|
+
return
|
|
609
|
+
fi
|
|
610
|
+
|
|
611
|
+
# Tokyo Night palette: midnight footer, muted inactive tabs, orange active marker.
|
|
612
|
+
active_style="#[fg=#c0caf5,bg=#414868,bold]"
|
|
613
|
+
inactive_style="#[fg=#a9b1d6,bg=#24283b,nobold]"
|
|
614
|
+
muted_style="#[fg=#565f89,bg=#1a1b26,nobold]"
|
|
615
|
+
reset_style="#[default]"
|
|
616
|
+
tabs=""
|
|
617
|
+
divider="${muted_style}|${reset_style}"
|
|
618
|
+
while IFS=$'\t' read -r name display_name; do
|
|
619
|
+
[[ -n "$name" ]] || continue
|
|
620
|
+
if [[ -n "$tabs" ]]; then
|
|
621
|
+
tabs+="$divider"
|
|
622
|
+
fi
|
|
623
|
+
if [[ "$name" == "__ellipsis__" ]]; then
|
|
624
|
+
tab_label="$(_tmux_pad_tab_label "⋯" "$tab_width")"
|
|
625
|
+
escaped_tab_label="$(_tmux_status_escape_text "$tab_label")"
|
|
626
|
+
tabs+="${muted_style} ${escaped_tab_label} ${reset_style}"
|
|
627
|
+
continue
|
|
628
|
+
fi
|
|
629
|
+
|
|
630
|
+
if [[ -z "$display_name" ]]; then
|
|
631
|
+
display_name="$(tmux_format_session_display "$name" without-timestamp)"
|
|
632
|
+
fi
|
|
633
|
+
if [[ "$name" == "$current_session" ]]; then
|
|
634
|
+
tab_label="$(_tmux_pad_tab_label "$display_name" "$active_label_width")"
|
|
635
|
+
escaped_tab_label="$(_tmux_status_escape_text "$tab_label")"
|
|
636
|
+
tabs+="#[bg=#414868] #[fg=#ff9e64,bg=#414868,bold]● ${active_style}${escaped_tab_label} ${reset_style}"
|
|
637
|
+
else
|
|
638
|
+
tab_label="$(_tmux_pad_tab_label "$display_name" "$tab_width")"
|
|
639
|
+
escaped_tab_label="$(_tmux_status_escape_text "$tab_label")"
|
|
640
|
+
tabs+="${inactive_style} ${escaped_tab_label} ${reset_style}"
|
|
641
|
+
fi
|
|
642
|
+
done <<< "$rows"
|
|
643
|
+
|
|
644
|
+
printf '%s' "$tabs"
|
|
645
|
+
}
|
|
646
|
+
|
|
245
647
|
_tmux_orchestra_status_left() {
|
|
246
|
-
local
|
|
247
|
-
local session_display_name="$
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
648
|
+
local session_name="$1"
|
|
649
|
+
local session_display_name="$3"
|
|
650
|
+
_tmux_workspace_session_tabs "$session_name" "$session_display_name"
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
_tmux_orchestra_status_right() {
|
|
654
|
+
printf ''
|
|
251
655
|
}
|
|
252
656
|
|
|
253
657
|
_tmux_configure_orchestra_bindings() {
|
|
@@ -255,32 +659,79 @@ _tmux_configure_orchestra_bindings() {
|
|
|
255
659
|
bridge="$(_orchestra_bridge_script)"
|
|
256
660
|
[[ -f "$bridge" ]] || return
|
|
257
661
|
|
|
258
|
-
local quoted_bridge rename_command prompt_command
|
|
662
|
+
local quoted_bridge rename_command prompt_command next_command previous_command help_command
|
|
259
663
|
printf -v quoted_bridge '%q' "$bridge"
|
|
260
664
|
rename_command="$quoted_bridge manual-rename-session \\\"#{session_name}\\\" \\\"%%\\\" >/dev/null 2>&1"
|
|
261
665
|
prompt_command="command-prompt -p 'Rename Orchestra session:' 'run-shell -b \"$rename_command\"'"
|
|
666
|
+
next_command="$quoted_bridge cycle-workspace-session \\\"#{session_name}\\\" next \\\"#{client_tty}\\\" >/dev/null 2>&1"
|
|
667
|
+
previous_command="$quoted_bridge cycle-workspace-session \\\"#{session_name}\\\" previous \\\"#{client_tty}\\\" >/dev/null 2>&1"
|
|
668
|
+
help_command="$quoted_bridge tmux-help-popup \\\"#{client_tty}\\\" >/dev/null 2>&1"
|
|
262
669
|
|
|
670
|
+
tmux bind-key -T prefix '?' if-shell -F '#{@orchestra_display_name}' \
|
|
671
|
+
"run-shell -b \"$help_command\"" 'list-keys -N' >/dev/null 2>&1 || true
|
|
263
672
|
tmux bind-key -T prefix r if-shell -F '#{@orchestra_display_name}' \
|
|
264
673
|
"$prompt_command" 'refresh-client -S' >/dev/null 2>&1 || true
|
|
674
|
+
tmux bind-key -T prefix '>' if-shell -F '#{@orchestra_display_name}' \
|
|
675
|
+
"run-shell -b \"$next_command\"" 'switch-client -n' >/dev/null 2>&1 || true
|
|
676
|
+
tmux bind-key -T prefix '<' if-shell -F '#{@orchestra_display_name}' \
|
|
677
|
+
"run-shell -b \"$previous_command\"" 'switch-client -p' >/dev/null 2>&1 || true
|
|
678
|
+
tmux bind-key -r -T prefix Right if-shell -F '#{@orchestra_display_name}' \
|
|
679
|
+
"run-shell -b \"$next_command\"" 'select-pane -R' >/dev/null 2>&1 || true
|
|
680
|
+
tmux bind-key -r -T prefix Left if-shell -F '#{@orchestra_display_name}' \
|
|
681
|
+
"run-shell -b \"$previous_command\"" 'select-pane -L' >/dev/null 2>&1 || true
|
|
265
682
|
}
|
|
266
683
|
|
|
267
684
|
_tmux_configure_orchestra_status() {
|
|
268
685
|
local session_name="$1"
|
|
269
686
|
local worktree_name="$2"
|
|
270
687
|
local session_display_name="${3:-}"
|
|
271
|
-
local status_left
|
|
688
|
+
local status_left status_right
|
|
272
689
|
if [[ -z "$session_display_name" ]]; then
|
|
273
690
|
session_display_name="$(tmux_format_session_display "$session_name" without-timestamp)"
|
|
274
691
|
fi
|
|
275
|
-
status_left="$(_tmux_orchestra_status_left "$worktree_name" "$session_display_name")"
|
|
692
|
+
status_left="$(_tmux_orchestra_status_left "$session_name" "$worktree_name" "$session_display_name")"
|
|
693
|
+
status_right="$(_tmux_orchestra_status_right "$worktree_name")"
|
|
276
694
|
|
|
277
695
|
tmux set-option -t "$session_name" @orchestra_display_name "$session_display_name" >/dev/null 2>&1 || true
|
|
278
696
|
tmux set-option -t "$session_name" @orchestra_worktree_name "$worktree_name" >/dev/null 2>&1 || true
|
|
697
|
+
tmux set-option -t "$session_name" status on >/dev/null 2>&1 || true
|
|
698
|
+
tmux set-option -t "$session_name" status-position bottom >/dev/null 2>&1 || true
|
|
699
|
+
tmux set-option -t "$session_name" status-style "fg=#c0caf5,bg=#1a1b26" >/dev/null 2>&1 || true
|
|
279
700
|
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
|
|
701
|
+
tmux set-option -t "$session_name" status-left-length 1000 >/dev/null 2>&1 || true
|
|
702
|
+
tmux set-option -t "$session_name" status-right "$status_right" >/dev/null 2>&1 || true
|
|
703
|
+
tmux set-option -t "$session_name" status-right-length 0 >/dev/null 2>&1 || true
|
|
704
|
+
tmux set-option -t "$session_name" window-status-format "" >/dev/null 2>&1 || true
|
|
705
|
+
tmux set-option -t "$session_name" window-status-current-format "" >/dev/null 2>&1 || true
|
|
706
|
+
tmux set-option -t "$session_name" window-status-separator "" >/dev/null 2>&1 || true
|
|
281
707
|
_tmux_configure_orchestra_bindings
|
|
282
708
|
}
|
|
283
709
|
|
|
710
|
+
_tmux_refresh_orchestra_session_status() {
|
|
711
|
+
local session_name="$1"
|
|
712
|
+
local session_dir branch_name worktree_name display_name old_pwd
|
|
713
|
+
|
|
714
|
+
session_dir="$(tmux display-message -t "$session_name" -p '#{pane_current_path}' 2>/dev/null || echo "")"
|
|
715
|
+
branch_name=""
|
|
716
|
+
if [[ -n "$session_dir" && -d "$session_dir" ]]; then
|
|
717
|
+
old_pwd="$PWD"
|
|
718
|
+
cd "$session_dir" 2>/dev/null || true
|
|
719
|
+
branch_name="$(git_current_branch 2>/dev/null || git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
720
|
+
cd "$old_pwd" 2>/dev/null || true
|
|
721
|
+
fi
|
|
722
|
+
|
|
723
|
+
if [[ -n "$branch_name" && "$branch_name" != "detached" ]]; then
|
|
724
|
+
worktree_name="$branch_name"
|
|
725
|
+
elif [[ -n "$session_dir" && -d "$session_dir" ]]; then
|
|
726
|
+
worktree_name="$(basename "$session_dir")"
|
|
727
|
+
else
|
|
728
|
+
worktree_name="$branch_name"
|
|
729
|
+
fi
|
|
730
|
+
|
|
731
|
+
display_name="$(tmux show-option -t "$session_name" -qv @orchestra_display_name 2>/dev/null || true)"
|
|
732
|
+
_tmux_configure_orchestra_status "$session_name" "$worktree_name" "$display_name"
|
|
733
|
+
}
|
|
734
|
+
|
|
284
735
|
# Helper: split a string by multi-char delimiter into bash array named by ref
|
|
285
736
|
# Usage: _tmux_split_by_delim "string" "::" out_array_name
|
|
286
737
|
_tmux_split_by_delim() {
|
|
@@ -355,14 +806,20 @@ tmux_create_session() {
|
|
|
355
806
|
# Get repository info from the working directory context
|
|
356
807
|
local repo_name=""
|
|
357
808
|
local branch_name=""
|
|
809
|
+
local repo_root=""
|
|
358
810
|
local old_pwd="$PWD"
|
|
811
|
+
local resolved_working_dir="$working_dir"
|
|
812
|
+
if [[ -n "$working_dir" && -d "$working_dir" ]]; then
|
|
813
|
+
resolved_working_dir="$(cd "$working_dir" 2>/dev/null && pwd -P || printf '%s' "$working_dir")"
|
|
814
|
+
fi
|
|
359
815
|
|
|
360
816
|
# Change to working directory to get accurate git info
|
|
361
|
-
cd "$
|
|
817
|
+
cd "$resolved_working_dir" 2>/dev/null || true
|
|
362
818
|
|
|
363
819
|
if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
|
|
364
820
|
repo_name="$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")"
|
|
365
821
|
branch_name="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "detached")"
|
|
822
|
+
repo_root="$(git_shared_root 2>/dev/null || git_repo_root 2>/dev/null || true)"
|
|
366
823
|
fi
|
|
367
824
|
|
|
368
825
|
# Restore original directory
|
|
@@ -376,21 +833,24 @@ tmux_create_session() {
|
|
|
376
833
|
fi
|
|
377
834
|
|
|
378
835
|
# Create session with custom Orchestra status configuration
|
|
379
|
-
tmux new-session -Ad -s "$session_name" -c "$
|
|
836
|
+
tmux new-session -Ad -s "$session_name" -c "$resolved_working_dir" >/dev/null 2>&1 || {
|
|
380
837
|
err "Failed to create tmux session: $session_name"
|
|
381
838
|
return 1
|
|
382
839
|
}
|
|
383
840
|
|
|
841
|
+
local worktree_name=""
|
|
842
|
+
if [[ -n "$branch_name" && "$branch_name" != "detached" ]]; then
|
|
843
|
+
worktree_name="$branch_name"
|
|
844
|
+
else
|
|
845
|
+
worktree_name="$(basename "$resolved_working_dir")"
|
|
846
|
+
fi
|
|
847
|
+
|
|
848
|
+
if [[ -n "$repo_root" && -n "$branch_name" && "$branch_name" != "detached" ]]; then
|
|
849
|
+
_tmux_registry_upsert_session "$repo_root" "$branch_name" "$resolved_working_dir" "$session_name" >/dev/null 2>&1 || true
|
|
850
|
+
fi
|
|
851
|
+
|
|
384
852
|
# Customize the default status bar to include Orchestra info on the left
|
|
385
853
|
if [[ -n "$repo_name" ]]; then
|
|
386
|
-
# Set custom status-left with Orchestra branding without full path
|
|
387
|
-
# Example: Go back (detach): Ctrl+b,d | Copy (scroll) mode: Ctrl+b,[ | Worktree: main
|
|
388
|
-
local worktree_name=""
|
|
389
|
-
if [[ -n "$branch_name" && "$branch_name" != "detached" ]]; then
|
|
390
|
-
worktree_name="$branch_name"
|
|
391
|
-
else
|
|
392
|
-
worktree_name="$(basename "$working_dir")"
|
|
393
|
-
fi
|
|
394
854
|
_tmux_configure_orchestra_status "$session_name" "$worktree_name"
|
|
395
855
|
fi
|
|
396
856
|
|
|
@@ -467,6 +927,7 @@ tmux_kill_session() {
|
|
|
467
927
|
err "Failed to kill session: $session_name"
|
|
468
928
|
return 1
|
|
469
929
|
}
|
|
930
|
+
_tmux_registry_remove_session "$session_name" >/dev/null 2>&1 || true
|
|
470
931
|
}
|
|
471
932
|
|
|
472
933
|
# Attach or switch to session
|
|
@@ -519,19 +980,7 @@ tmux_attach_session() {
|
|
|
519
980
|
"🎼 Orchestra Session | Repository: $repo_name | Branch: $branch_name" 2>/dev/null || true
|
|
520
981
|
fi
|
|
521
982
|
|
|
522
|
-
|
|
523
|
-
local worktree_name=""
|
|
524
|
-
if [[ -n "$branch_name" && "$branch_name" != "detached" ]]; then
|
|
525
|
-
worktree_name="$branch_name"
|
|
526
|
-
else
|
|
527
|
-
# Fallback to directory name of session path if available
|
|
528
|
-
if [[ -n "$session_dir" && -d "$session_dir" ]]; then
|
|
529
|
-
worktree_name="$(basename "$session_dir")"
|
|
530
|
-
else
|
|
531
|
-
worktree_name="$branch_name"
|
|
532
|
-
fi
|
|
533
|
-
fi
|
|
534
|
-
_tmux_configure_orchestra_status "$sess" "$worktree_name"
|
|
983
|
+
_tmux_refresh_orchestra_session_status "$sess"
|
|
535
984
|
|
|
536
985
|
if tmux_inside_session; then
|
|
537
986
|
tmux switch-client -t "$sess" >/dev/null 2>&1 || true
|
|
@@ -609,6 +1058,7 @@ tmux_rename_session() {
|
|
|
609
1058
|
err "Failed to rename session"
|
|
610
1059
|
return 1
|
|
611
1060
|
}
|
|
1061
|
+
_tmux_registry_rename_session "$old_session" "$new_session" "$(format_session_display_name "$new_name")" >/dev/null 2>&1 || true
|
|
612
1062
|
local worktree_name
|
|
613
1063
|
worktree_name="$(tmux show-option -t "$new_session" -qv @orchestra_worktree_name 2>/dev/null || true)"
|
|
614
1064
|
if [[ -z "$worktree_name" ]]; then
|
|
@@ -1044,6 +1494,186 @@ tmux_send_keys() {
|
|
|
1044
1494
|
tmux send-keys -t "$session_name" C-m 2>/dev/null || return 1
|
|
1045
1495
|
}
|
|
1046
1496
|
|
|
1497
|
+
# Show Orchestra tmux shortcuts in a popup modal.
|
|
1498
|
+
# Usage: tmux_show_orchestra_help_popup [client_tty]
|
|
1499
|
+
tmux_show_orchestra_help_popup() {
|
|
1500
|
+
local target_client="${1:-}"
|
|
1501
|
+
if ! tmux_available; then
|
|
1502
|
+
err "tmux not installed"
|
|
1503
|
+
return 1
|
|
1504
|
+
fi
|
|
1505
|
+
|
|
1506
|
+
local target_args=()
|
|
1507
|
+
if [[ -n "$target_client" ]]; then
|
|
1508
|
+
target_args=(-c "$target_client")
|
|
1509
|
+
fi
|
|
1510
|
+
|
|
1511
|
+
local popup_command
|
|
1512
|
+
popup_command='printf "%s\n" \
|
|
1513
|
+
"Orchestra tmux shortcuts" \
|
|
1514
|
+
"" \
|
|
1515
|
+
"Ctrl+b, Left Previous Orchestra session in this workspace" \
|
|
1516
|
+
"Ctrl+b, Right Next Orchestra session in this workspace" \
|
|
1517
|
+
"Ctrl+b, < Previous Orchestra session in this workspace" \
|
|
1518
|
+
"Ctrl+b, > Next Orchestra session in this workspace" \
|
|
1519
|
+
"Ctrl+b, r Rename the current Orchestra session" \
|
|
1520
|
+
"Ctrl+b, d Detach and return to Orchestra" \
|
|
1521
|
+
"Ctrl+b, [ Copy/scroll mode" \
|
|
1522
|
+
"Ctrl+b, ? Show this help" \
|
|
1523
|
+
"" \
|
|
1524
|
+
"Press Enter to close..."; read -r _'
|
|
1525
|
+
|
|
1526
|
+
tmux display-popup "${target_args[@]}" -E -w 78 -h 15 -T "Orchestra shortcuts" "$popup_command" >/dev/null 2>&1
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
# Find the adjacent active Orchestra session registered for the current repo.
|
|
1530
|
+
# Usage: tmux_workspace_cycle_target <current_session> <next|previous>
|
|
1531
|
+
tmux_workspace_cycle_target() {
|
|
1532
|
+
local current_session="$1"
|
|
1533
|
+
local direction="$2"
|
|
1534
|
+
local session_dir repo_root db_path active_file target query_status
|
|
1535
|
+
|
|
1536
|
+
if ! tmux_available; then
|
|
1537
|
+
err "tmux not installed"
|
|
1538
|
+
return 1
|
|
1539
|
+
fi
|
|
1540
|
+
if [[ -z "$current_session" ]]; then
|
|
1541
|
+
err "tmux_workspace_cycle_target: current session required"
|
|
1542
|
+
return 1
|
|
1543
|
+
fi
|
|
1544
|
+
case "$direction" in
|
|
1545
|
+
next|previous|prev) ;;
|
|
1546
|
+
*)
|
|
1547
|
+
err "tmux_workspace_cycle_target: direction must be next or previous"
|
|
1548
|
+
return 1
|
|
1549
|
+
;;
|
|
1550
|
+
esac
|
|
1551
|
+
|
|
1552
|
+
db_path="$(_tmux_session_registry_path)"
|
|
1553
|
+
have_cmd python3 || {
|
|
1554
|
+
err "python3 is required to read the Orchestra session registry"
|
|
1555
|
+
return 1
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
active_file="$(mktemp)"
|
|
1559
|
+
tmux list-sessions -F $'#{session_name}\t#{@orchestra_display_name}' > "$active_file" 2>/dev/null || {
|
|
1560
|
+
rm -f "$active_file"
|
|
1561
|
+
err "Unable to list tmux sessions"
|
|
1562
|
+
return 1
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if [[ -f "$db_path" ]]; then
|
|
1566
|
+
if target="$(_tmux_workspace_cycle_target_cached "$current_session" "$direction" "$db_path" "$active_file")"; then
|
|
1567
|
+
query_status=0
|
|
1568
|
+
else
|
|
1569
|
+
query_status=$?
|
|
1570
|
+
fi
|
|
1571
|
+
if [[ $query_status -eq 0 && -n "$target" ]]; then
|
|
1572
|
+
rm -f "$active_file"
|
|
1573
|
+
printf '%s\n' "$target"
|
|
1574
|
+
return 0
|
|
1575
|
+
fi
|
|
1576
|
+
if [[ $query_status -eq 3 ]]; then
|
|
1577
|
+
rm -f "$active_file"
|
|
1578
|
+
return 1
|
|
1579
|
+
fi
|
|
1580
|
+
fi
|
|
1581
|
+
|
|
1582
|
+
_tmux_registry_upsert_current_session "$current_session" >/dev/null 2>&1 || true
|
|
1583
|
+
|
|
1584
|
+
session_dir="$(tmux display-message -t "$current_session" -p '#{pane_current_path}' 2>/dev/null || echo "")"
|
|
1585
|
+
repo_root="$(_tmux_shared_root_for_path "$session_dir" 2>/dev/null || true)"
|
|
1586
|
+
if [[ -z "$repo_root" ]]; then
|
|
1587
|
+
rm -f "$active_file"
|
|
1588
|
+
err "Unable to determine Orchestra workspace for session"
|
|
1589
|
+
return 1
|
|
1590
|
+
fi
|
|
1591
|
+
_tmux_registry_sync_active_workspace_sessions "$repo_root" >/dev/null 2>&1 || true
|
|
1592
|
+
|
|
1593
|
+
if [[ ! -f "$db_path" ]]; then
|
|
1594
|
+
rm -f "$active_file"
|
|
1595
|
+
err "No Orchestra session registry found"
|
|
1596
|
+
return 1
|
|
1597
|
+
fi
|
|
1598
|
+
|
|
1599
|
+
if target="$(python3 - "$db_path" "$repo_root" "$current_session" "$direction" "$active_file" <<'PY'
|
|
1600
|
+
import sqlite3
|
|
1601
|
+
import sys
|
|
1602
|
+
|
|
1603
|
+
db_path, repo_root, current_session, direction, active_path = sys.argv[1:6]
|
|
1604
|
+
with open(active_path, "r", encoding="utf-8") as handle:
|
|
1605
|
+
active = {
|
|
1606
|
+
line.rstrip("\n").split("\t", 1)[0].strip()
|
|
1607
|
+
for line in handle
|
|
1608
|
+
if line.rstrip("\n").split("\t", 1)[0].strip()
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
conn = sqlite3.connect(db_path)
|
|
1612
|
+
rows = conn.execute(
|
|
1613
|
+
"""
|
|
1614
|
+
SELECT tmux_name
|
|
1615
|
+
FROM sessions
|
|
1616
|
+
WHERE repo_root = ?1
|
|
1617
|
+
ORDER BY worktree_path COLLATE NOCASE, created_at, tmux_name COLLATE NOCASE
|
|
1618
|
+
""",
|
|
1619
|
+
(repo_root,),
|
|
1620
|
+
).fetchall()
|
|
1621
|
+
|
|
1622
|
+
names = []
|
|
1623
|
+
seen = set()
|
|
1624
|
+
for (name,) in rows:
|
|
1625
|
+
if name in active and name not in seen:
|
|
1626
|
+
names.append(name)
|
|
1627
|
+
seen.add(name)
|
|
1628
|
+
|
|
1629
|
+
if len(names) < 2 or current_session not in seen:
|
|
1630
|
+
raise SystemExit(0)
|
|
1631
|
+
|
|
1632
|
+
index = names.index(current_session)
|
|
1633
|
+
if direction == "next":
|
|
1634
|
+
index = (index + 1) % len(names)
|
|
1635
|
+
else:
|
|
1636
|
+
index = (index - 1) % len(names)
|
|
1637
|
+
print(names[index])
|
|
1638
|
+
PY
|
|
1639
|
+
)"; then
|
|
1640
|
+
query_status=0
|
|
1641
|
+
else
|
|
1642
|
+
query_status=$?
|
|
1643
|
+
fi
|
|
1644
|
+
rm -f "$active_file"
|
|
1645
|
+
if [[ $query_status -ne 0 ]]; then
|
|
1646
|
+
err "Unable to query Orchestra session registry"
|
|
1647
|
+
return 1
|
|
1648
|
+
fi
|
|
1649
|
+
[[ -n "$target" ]] || return 1
|
|
1650
|
+
printf '%s\n' "$target"
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
# Switch the current tmux client to an adjacent registered Orchestra session.
|
|
1654
|
+
# Usage: tmux_cycle_workspace_session <current_session> <next|previous> [client_tty]
|
|
1655
|
+
tmux_cycle_workspace_session() {
|
|
1656
|
+
local current_session="$1"
|
|
1657
|
+
local direction="$2"
|
|
1658
|
+
local target_client="${3:-}"
|
|
1659
|
+
local target_session
|
|
1660
|
+
|
|
1661
|
+
if ! target_session="$(tmux_workspace_cycle_target "$current_session" "$direction")"; then
|
|
1662
|
+
tmux display-message -d 2500 "No other Orchestra sessions in this workspace" 2>/dev/null || true
|
|
1663
|
+
return 1
|
|
1664
|
+
fi
|
|
1665
|
+
|
|
1666
|
+
if [[ -n "$target_client" ]]; then
|
|
1667
|
+
tmux switch-client -c "$target_client" -t "$target_session" >/dev/null 2>&1 || return 1
|
|
1668
|
+
else
|
|
1669
|
+
tmux switch-client -t "$target_session" >/dev/null 2>&1 || return 1
|
|
1670
|
+
fi
|
|
1671
|
+
|
|
1672
|
+
{
|
|
1673
|
+
_tmux_refresh_orchestra_session_status "$target_session" >/dev/null 2>&1 || true
|
|
1674
|
+
} &
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1047
1677
|
# Load .env file if it exists
|
|
1048
1678
|
# Usage: tmux_load_env_file [env_file_path]
|
|
1049
1679
|
tmux_load_env_file() {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -109,6 +109,14 @@ case "${1:-}" in
|
|
|
109
109
|
"tmux-send-keys")
|
|
110
110
|
bridge_tmux_send_keys "${2:-}" "${@:3}"
|
|
111
111
|
;;
|
|
112
|
+
|
|
113
|
+
"cycle-workspace-session")
|
|
114
|
+
bridge_cycle_workspace_session "${2:-}" "${3:-}" "${4:-}"
|
|
115
|
+
;;
|
|
116
|
+
|
|
117
|
+
"tmux-help-popup")
|
|
118
|
+
bridge_tmux_help_popup "${2:-}"
|
|
119
|
+
;;
|
|
112
120
|
|
|
113
121
|
"session-metadata")
|
|
114
122
|
bridge_session_metadata "${2:-}"
|
|
@@ -233,6 +241,9 @@ Commands:
|
|
|
233
241
|
delete-branch-only <branch> Delete branch only (keep worktree)
|
|
234
242
|
switch-worktree <path> Generate cd command for path
|
|
235
243
|
copy-env-files <source> <target> Copy .env files between directories
|
|
244
|
+
cycle-workspace-session <session> <next|previous> [client]
|
|
245
|
+
Switch within registered Orchestra sessions for this repo
|
|
246
|
+
tmux-help-popup [client] Show Orchestra tmux shortcut popup
|
|
236
247
|
repo-info Get repository information
|
|
237
248
|
git-status Get git status --porcelain
|
|
238
249
|
enhanced-git-status [dir] Get enhanced git status with file stats and line counts
|
|
@@ -112,6 +112,42 @@ bridge_tmux_send_keys() {
|
|
|
112
112
|
fi
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
# Cycle to next/previous registered Orchestra session in the current workspace
|
|
116
|
+
bridge_cycle_workspace_session() {
|
|
117
|
+
if [[ -z "${1:-}" ]] || [[ -z "${2:-}" ]]; then
|
|
118
|
+
json_error "Session name and direction required"
|
|
119
|
+
return 1
|
|
120
|
+
fi
|
|
121
|
+
session_name="$1"
|
|
122
|
+
direction="$2"
|
|
123
|
+
target_client="${3:-}"
|
|
124
|
+
|
|
125
|
+
if tmux_available; then
|
|
126
|
+
tmux_cycle_workspace_session "$session_name" "$direction" "$target_client" || {
|
|
127
|
+
json_error "Failed to cycle workspace session"
|
|
128
|
+
return 1
|
|
129
|
+
}
|
|
130
|
+
echo '{"ok":true}'
|
|
131
|
+
else
|
|
132
|
+
json_error "tmux not available"
|
|
133
|
+
fi
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Show Orchestra tmux shortcut help popup
|
|
137
|
+
bridge_tmux_help_popup() {
|
|
138
|
+
target_client="${1:-}"
|
|
139
|
+
|
|
140
|
+
if tmux_available; then
|
|
141
|
+
tmux_show_orchestra_help_popup "$target_client" || {
|
|
142
|
+
json_error "Failed to show tmux help popup"
|
|
143
|
+
return 1
|
|
144
|
+
}
|
|
145
|
+
echo '{"ok":true}'
|
|
146
|
+
else
|
|
147
|
+
json_error "tmux not available"
|
|
148
|
+
fi
|
|
149
|
+
}
|
|
150
|
+
|
|
115
151
|
# Get session metadata
|
|
116
152
|
bridge_session_metadata() {
|
|
117
153
|
if [[ -z "${1:-}" ]]; then
|