mirror of
https://github.com/theniceboy/.config.git
synced 2026-05-01 10:56:04 +08:00
agent tracker + tmux update
This commit is contained in:
parent
cd9c92b1c2
commit
5064629d61
68 changed files with 15041 additions and 3483 deletions
45
tmux/scripts/focus_pane_by_position.sh
Executable file
45
tmux/scripts/focus_pane_by_position.sh
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
target="${1:-}"
|
||||
window_id="${2:-}"
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
echo "usage: $(basename "$0") <left|top-right|bottom-right> [window_id]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$window_id" ]]; then
|
||||
window_id="$(tmux display-message -p '#{window_id}')"
|
||||
fi
|
||||
|
||||
tmux list-panes -t "$window_id" -F '#{pane_id} #{pane_left} #{pane_top}' | python3 -c '
|
||||
import sys
|
||||
|
||||
target = sys.argv[1]
|
||||
panes = []
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
pane_id, left, top = line.split()
|
||||
panes.append((pane_id, int(left), int(top)))
|
||||
|
||||
if not panes:
|
||||
sys.exit(1)
|
||||
|
||||
if target == "left":
|
||||
chosen = min(panes, key=lambda p: (p[1], p[2], p[0]))
|
||||
elif target == "top-right":
|
||||
max_left = max(p[1] for p in panes)
|
||||
candidates = [p for p in panes if p[1] == max_left]
|
||||
chosen = min(candidates, key=lambda p: (p[2], p[0]))
|
||||
elif target == "bottom-right":
|
||||
max_left = max(p[1] for p in panes)
|
||||
candidates = [p for p in panes if p[1] == max_left]
|
||||
chosen = max(candidates, key=lambda p: (p[2], p[0]))
|
||||
else:
|
||||
sys.exit(2)
|
||||
|
||||
print(chosen[0])
|
||||
' "$target"
|
||||
|
|
@ -1,11 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
session_id=$(tmux new-session -d -P -F '#{session_id}' 2>/dev/null)
|
||||
LOCK="/tmp/tmux-new-session.lock"
|
||||
current_session_id="${1:-}"
|
||||
|
||||
touch "$LOCK"
|
||||
session_id=$(tmux new-session -d -P -s 'session' -F '#{session_id}' 2>/dev/null)
|
||||
|
||||
if [ -z "$session_id" ]; then
|
||||
rm -f "$LOCK"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
python3 "$HOME/.config/tmux/scripts/session_manager.py" ensure
|
||||
if [ -n "$current_session_id" ]; then
|
||||
python3 "$HOME/.config/tmux/scripts/session_manager.py" insert-right "$current_session_id" "$session_id"
|
||||
else
|
||||
python3 "$HOME/.config/tmux/scripts/session_manager.py" ensure
|
||||
fi
|
||||
|
||||
rm -f "$LOCK"
|
||||
|
||||
tmux switch-client -t "$session_id"
|
||||
|
|
|
|||
17
tmux/scripts/open_agent_palette.sh
Executable file
17
tmux/scripts/open_agent_palette.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
client_tty="${1-}"
|
||||
window_id="${2-}"
|
||||
agent_id="${3-}"
|
||||
path_value="${4-}"
|
||||
session_name="${5-}"
|
||||
window_name="${6-}"
|
||||
|
||||
exec tmux display-popup -E -c "$client_tty" -d "$path_value" -w 78% -h 80% -T agent \
|
||||
~/.config/agent-tracker/bin/agent palette \
|
||||
--window="$window_id" \
|
||||
--agent-id="$agent_id" \
|
||||
--path="$path_value" \
|
||||
--session-name="$session_name" \
|
||||
--window-name="$window_name"
|
||||
52
tmux/scripts/post_resurrect_restore.sh
Executable file
52
tmux/scripts/post_resurrect_restore.sh
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
RESTART_OP_SCRIPT="${TMUX_RESTART_OP_SCRIPT:-${XDG_CONFIG_HOME:-$HOME/.config}/tmux/scripts/restart_opencode_pane.sh}"
|
||||
RESTORE_AGENT_RUN_PANES_SCRIPT="${TMUX_RESTORE_AGENT_RUN_PANES_SCRIPT:-${XDG_CONFIG_HOME:-$HOME/.config}/tmux/scripts/restore_agent_run_panes.py}"
|
||||
RESTORE_AGENT_TRACKER_SCRIPT="${TMUX_RESTORE_AGENT_TRACKER_SCRIPT:-${XDG_CONFIG_HOME:-$HOME/.config}/tmux/scripts/restore_agent_tracker_mapping.py}"
|
||||
|
||||
resurrect_dir() {
|
||||
if [[ -d "$HOME/.tmux/resurrect" ]]; then
|
||||
printf '%s\n' "$HOME/.tmux/resurrect"
|
||||
else
|
||||
printf '%s\n' "${XDG_DATA_HOME:-$HOME/.local/share}/tmux/resurrect"
|
||||
fi
|
||||
}
|
||||
|
||||
last_file="${TMUX_RESURRECT_LAST_FILE:-$(resurrect_dir)/last}"
|
||||
|
||||
op_pane_locators() {
|
||||
[[ -e "$last_file" ]] || return 0
|
||||
awk -F '\t' '
|
||||
$1 == "pane" && (index($7, "OpenCode") > 0 || index($11, "opencode") > 0) {
|
||||
print $2 ":" $3 "." $6
|
||||
}
|
||||
' "$last_file" | awk '!seen[$0]++'
|
||||
}
|
||||
|
||||
resume_opencode_panes() {
|
||||
[[ -x "$RESTART_OP_SCRIPT" ]] || return 0
|
||||
|
||||
local locator pane_id
|
||||
while IFS= read -r locator; do
|
||||
[[ -n "$locator" ]] || continue
|
||||
pane_id="$(tmux display-message -p -t "$locator" '#{pane_id}' 2>/dev/null || true)"
|
||||
[[ -n "$pane_id" ]] || continue
|
||||
"$RESTART_OP_SCRIPT" "$pane_id" >/dev/null 2>&1 || true
|
||||
done < <(op_pane_locators)
|
||||
}
|
||||
|
||||
restore_agent_tracker_mappings() {
|
||||
[[ -x "$RESTORE_AGENT_TRACKER_SCRIPT" ]] || return 0
|
||||
"$RESTORE_AGENT_TRACKER_SCRIPT" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
restore_agent_run_panes() {
|
||||
[[ -x "$RESTORE_AGENT_RUN_PANES_SCRIPT" ]] || return 0
|
||||
"$RESTORE_AGENT_RUN_PANES_SCRIPT" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
sleep 1
|
||||
resume_opencode_panes
|
||||
restore_agent_run_panes
|
||||
restore_agent_tracker_mappings
|
||||
85
tmux/scripts/restart_opencode_pane.sh
Executable file
85
tmux/scripts/restart_opencode_pane.sh
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
pane_id="${1:-}"
|
||||
if [[ -z "$pane_id" ]]; then
|
||||
pane_id=$(tmux display-message -p '#{pane_id}')
|
||||
fi
|
||||
|
||||
if [[ -z "$pane_id" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/op"
|
||||
pane_locator=$(tmux display-message -p -t "$pane_id" '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null || true)
|
||||
state_file="$state_dir/loc_${pane_locator//[^a-zA-Z0-9_]/_}"
|
||||
|
||||
if [[ -z "$pane_locator" ]]; then
|
||||
tmux display-message "op-restart: unable to resolve pane locator"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$state_file" ]]; then
|
||||
tmux display-message "op-restart: no saved session for ${pane_locator}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
IFS= read -r session_id < "$state_file"
|
||||
if [[ -z "$session_id" ]]; then
|
||||
tmux display-message "op-restart: invalid session mapping for ${pane_locator}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
wait_for_shell() {
|
||||
local cmd i
|
||||
for ((i = 0; i < 30; i++)); do
|
||||
cmd=$(tmux display-message -p -t "$pane_id" '#{pane_current_command}' 2>/dev/null || true)
|
||||
case "$cmd" in
|
||||
zsh|bash|sh|fish|nu)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
sleep 0.1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
kill_opencode_on_tty() {
|
||||
local pane_tty tty_name pid
|
||||
local -a pids=()
|
||||
|
||||
pane_tty=$(tmux display-message -p -t "$pane_id" '#{pane_tty}' 2>/dev/null || true)
|
||||
if [[ -z "$pane_tty" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
tty_name="${pane_tty#/dev/}"
|
||||
while IFS= read -r pid; do
|
||||
[[ -n "$pid" ]] && pids+=("$pid")
|
||||
done < <(ps -t "$tty_name" -o pid= -o args= 2>/dev/null | awk '/\/opt\/homebrew\/bin\/opencode|\/opt\/homebrew\/lib\/node_modules\/opencode-ai\/bin\/\.opencode|opencode-darwin-arm64\/bin\/opencode/ { print $1 }')
|
||||
|
||||
if [[ ${#pids[@]} -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
kill -TERM "${pids[@]}" 2>/dev/null || true
|
||||
sleep 0.3
|
||||
|
||||
for pid in "${pids[@]}"; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill -KILL "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
tmux send-keys -t "$pane_id" C-c
|
||||
|
||||
if ! wait_for_shell; then
|
||||
kill_opencode_on_tty
|
||||
if ! wait_for_shell; then
|
||||
tmux display-message "op-restart: pane ${pane_id} did not return to shell"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
tmux send-keys -t "$pane_id" "OP_TRACKER_NOTIFY=1 op -s ${session_id}" C-m
|
||||
103
tmux/scripts/restore_agent_run_panes.py
Executable file
103
tmux/scripts/restore_agent_run_panes.py
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HOME = Path.home()
|
||||
|
||||
|
||||
def resurrect_dir() -> Path:
|
||||
legacy = HOME / ".tmux" / "resurrect"
|
||||
if legacy.is_dir():
|
||||
return legacy
|
||||
data_home = Path(os.environ.get("XDG_DATA_HOME", str(HOME / ".local" / "share")))
|
||||
return data_home / "tmux" / "resurrect"
|
||||
|
||||
|
||||
LAST_FILE = Path(os.environ.get("TMUX_RESURRECT_LAST_FILE", str(resurrect_dir() / "last")))
|
||||
|
||||
|
||||
def tmux_output(*args: str) -> str:
|
||||
return subprocess.check_output(["tmux", *args], text=True).strip()
|
||||
|
||||
|
||||
def tmux_run(*args: str) -> None:
|
||||
subprocess.run(["tmux", *args], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def parse_device(command: str) -> str:
|
||||
match = re.search(r"flutter run -d (?:\"([^\"]+)\"|'([^']+)'|(\S+))", command)
|
||||
if not match:
|
||||
return ""
|
||||
for group in match.groups():
|
||||
if group:
|
||||
return group.strip()
|
||||
return ""
|
||||
|
||||
|
||||
def iter_targets():
|
||||
if not LAST_FILE.exists():
|
||||
return
|
||||
|
||||
with LAST_FILE.open() as handle:
|
||||
for raw_line in handle:
|
||||
parts = raw_line.rstrip("\n").split("\t")
|
||||
if len(parts) < 11 or parts[0] != "pane":
|
||||
continue
|
||||
if parts[9].strip() != "script":
|
||||
continue
|
||||
|
||||
workspace = parts[7].lstrip(":").strip()
|
||||
if "/.agents/" not in workspace:
|
||||
continue
|
||||
|
||||
device = parse_device(parts[10].lstrip(":").strip())
|
||||
if not device:
|
||||
continue
|
||||
|
||||
ensure_server = Path(workspace) / "ensure-server.sh"
|
||||
if not ensure_server.is_file():
|
||||
continue
|
||||
|
||||
locator = f"{parts[1]}:{parts[2]}.{parts[5]}"
|
||||
yield locator, workspace, device
|
||||
|
||||
|
||||
def main() -> int:
|
||||
restored = 0
|
||||
|
||||
try:
|
||||
targets = list(iter_targets())
|
||||
except Exception as exc:
|
||||
print(f"restore-agent-run-panes: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
seen = set()
|
||||
for locator, workspace, device in targets:
|
||||
if locator in seen:
|
||||
continue
|
||||
seen.add(locator)
|
||||
|
||||
try:
|
||||
current_command = tmux_output("display-message", "-p", "-t", locator, "#{pane_current_command}")
|
||||
except Exception:
|
||||
continue
|
||||
if current_command.strip() != "script":
|
||||
continue
|
||||
|
||||
command = f"cd {shlex.quote(workspace)} && ./ensure-server.sh {shlex.quote(device)}"
|
||||
tmux_run("respawn-pane", "-k", "-t", locator, command)
|
||||
restored += 1
|
||||
|
||||
if restored:
|
||||
print(f"restored {restored} agent run pane(s)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
212
tmux/scripts/restore_agent_tracker_mapping.py
Executable file
212
tmux/scripts/restore_agent_tracker_mapping.py
Executable file
|
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HOME = Path.home()
|
||||
AGENTS_PATH = HOME / ".config/agent-tracker/run/agents.json"
|
||||
TODOS_PATH = HOME / ".cache/agent/todos.json"
|
||||
|
||||
|
||||
def tmux_output(*args: str) -> str:
|
||||
return subprocess.check_output(["tmux", *args], text=True).strip()
|
||||
|
||||
|
||||
def tmux_run(*args: str) -> None:
|
||||
subprocess.run(["tmux", *args], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def load_json(path: Path):
|
||||
with path.open() as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
def save_json(path: Path, payload) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w") as handle:
|
||||
json.dump(payload, handle, indent=2)
|
||||
handle.write("\n")
|
||||
|
||||
|
||||
def list_windows():
|
||||
out = tmux_output("list-windows", "-a", "-F", "#{session_name}\t#{session_id}\t#{window_name}\t#{window_id}")
|
||||
windows = []
|
||||
for line in out.splitlines():
|
||||
parts = line.split("\t")
|
||||
if len(parts) != 4:
|
||||
continue
|
||||
windows.append(
|
||||
{
|
||||
"session_name": parts[0].strip(),
|
||||
"session_id": parts[1].strip(),
|
||||
"window_name": parts[2].lstrip(":").strip(),
|
||||
"window_id": parts[3].strip(),
|
||||
}
|
||||
)
|
||||
return windows
|
||||
|
||||
|
||||
def list_panes():
|
||||
out = tmux_output("list-panes", "-a", "-F", "#{window_id}\t#{pane_index}\t#{pane_id}\t#{pane_current_path}")
|
||||
panes = []
|
||||
for line in out.splitlines():
|
||||
parts = line.split("\t", 3)
|
||||
if len(parts) != 4:
|
||||
continue
|
||||
try:
|
||||
pane_index = int(parts[1].strip())
|
||||
except ValueError:
|
||||
continue
|
||||
panes.append(
|
||||
{
|
||||
"window_id": parts[0].strip(),
|
||||
"pane_index": pane_index,
|
||||
"pane_id": parts[2].strip(),
|
||||
"path": parts[3].strip(),
|
||||
}
|
||||
)
|
||||
return panes
|
||||
|
||||
|
||||
def detect_agent_id_from_path(path: str) -> str:
|
||||
clean = os.path.normpath(path.strip())
|
||||
needle = f"{os.sep}.agents{os.sep}"
|
||||
if needle not in clean:
|
||||
return ""
|
||||
rest = clean.split(needle, 1)[1]
|
||||
if not rest:
|
||||
return ""
|
||||
return rest.split(os.sep, 1)[0].strip()
|
||||
|
||||
|
||||
def merge_items(existing, incoming):
|
||||
merged = list(existing)
|
||||
merged.extend(incoming)
|
||||
return merged
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not AGENTS_PATH.exists():
|
||||
return 0
|
||||
|
||||
try:
|
||||
agents_payload = load_json(AGENTS_PATH)
|
||||
windows = list_windows()
|
||||
panes = list_panes()
|
||||
except Exception as exc:
|
||||
print(f"restore-agent-tracker-mapping: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
windows_by_key = {(w["session_name"], w["window_name"]): w for w in windows}
|
||||
panes_by_window = {}
|
||||
inferred_window_for_agent = {}
|
||||
for pane in panes:
|
||||
panes_by_window.setdefault(pane["window_id"], []).append(pane)
|
||||
agent_id = detect_agent_id_from_path(pane["path"])
|
||||
if agent_id and agent_id not in inferred_window_for_agent:
|
||||
inferred_window_for_agent[agent_id] = pane["window_id"]
|
||||
|
||||
windows_by_id = {w["window_id"]: w for w in windows}
|
||||
|
||||
changed_agents = 0
|
||||
state_changed = False
|
||||
window_migrations = {}
|
||||
session_migrations = {}
|
||||
|
||||
agents = agents_payload.get("agents", {})
|
||||
for agent_id, record in agents.items():
|
||||
session_name = str(record.get("tmux_session_name", "")).strip()
|
||||
old_window_id = str(record.get("tmux_window_id", "")).strip()
|
||||
old_session_id = str(record.get("tmux_session_id", "")).strip()
|
||||
|
||||
match = windows_by_key.get((session_name, agent_id))
|
||||
if match is None:
|
||||
inferred_window_id = inferred_window_for_agent.get(agent_id, "")
|
||||
if inferred_window_id:
|
||||
match = windows_by_id.get(inferred_window_id)
|
||||
if match is None:
|
||||
continue
|
||||
|
||||
new_window_id = match["window_id"]
|
||||
new_session_id = match["session_id"]
|
||||
new_session_name = match["session_name"]
|
||||
|
||||
if old_window_id and old_window_id != new_window_id:
|
||||
window_migrations[old_window_id] = new_window_id
|
||||
if old_session_id and old_session_id != new_session_id:
|
||||
session_migrations[old_session_id] = new_session_id
|
||||
|
||||
if (
|
||||
old_window_id != new_window_id
|
||||
or old_session_id != new_session_id
|
||||
or session_name != new_session_name
|
||||
):
|
||||
changed_agents += 1
|
||||
state_changed = True
|
||||
|
||||
record["tmux_window_id"] = new_window_id
|
||||
record["tmux_session_id"] = new_session_id
|
||||
record["tmux_session_name"] = new_session_name
|
||||
|
||||
tmux_run("set-option", "-w", "-t", new_window_id, "@agent_id", agent_id)
|
||||
|
||||
panes_for_window = sorted(panes_by_window.get(new_window_id, []), key=lambda pane: pane["pane_index"])
|
||||
if panes_for_window:
|
||||
roles = ["ai", "git", "run"]
|
||||
pane_record = dict(record.get("panes", {}))
|
||||
for idx, role in enumerate(roles):
|
||||
if idx >= len(panes_for_window):
|
||||
break
|
||||
pane_id = panes_for_window[idx]["pane_id"]
|
||||
pane_record[role] = pane_id
|
||||
tmux_run("set-option", "-p", "-t", pane_id, "@agent_role", role)
|
||||
if pane_record != record.get("panes", {}):
|
||||
state_changed = True
|
||||
record["panes"] = pane_record
|
||||
|
||||
if state_changed:
|
||||
save_json(AGENTS_PATH, agents_payload)
|
||||
|
||||
migrated_window_todos = 0
|
||||
migrated_session_todos = 0
|
||||
if TODOS_PATH.exists():
|
||||
todos_payload = load_json(TODOS_PATH)
|
||||
windows_store = todos_payload.setdefault("windows", {})
|
||||
for old_id, new_id in window_migrations.items():
|
||||
if old_id == new_id or old_id not in windows_store:
|
||||
continue
|
||||
existing = windows_store.get(new_id, [])
|
||||
incoming = windows_store.pop(old_id)
|
||||
windows_store[new_id] = merge_items(existing, incoming)
|
||||
migrated_window_todos += len(incoming)
|
||||
|
||||
sessions_store = todos_payload.setdefault("sessions", {})
|
||||
for old_id, new_id in session_migrations.items():
|
||||
if old_id == new_id or old_id not in sessions_store:
|
||||
continue
|
||||
existing = sessions_store.get(new_id, [])
|
||||
incoming = sessions_store.pop(old_id)
|
||||
sessions_store[new_id] = merge_items(existing, incoming)
|
||||
migrated_session_todos += len(incoming)
|
||||
|
||||
if migrated_window_todos or migrated_session_todos:
|
||||
save_json(TODOS_PATH, todos_payload)
|
||||
|
||||
summary = []
|
||||
if changed_agents:
|
||||
summary.append(f"{changed_agents} agent windows")
|
||||
if migrated_window_todos:
|
||||
summary.append(f"{migrated_window_todos} window todos")
|
||||
if migrated_session_todos:
|
||||
summary.append(f"{migrated_session_todos} session todos")
|
||||
if summary:
|
||||
print("restored " + ", ".join(summary))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
27
tmux/scripts/resurrect_op_session.sh
Executable file
27
tmux/scripts/resurrect_op_session.sh
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env bash
|
||||
# Resurrect strategy for `op` (opencode).
|
||||
# Called by tmux-resurrect as: script <pane_full_command> <dir>
|
||||
# Returns the command to run in the pane.
|
||||
|
||||
pane_full_command="$1"
|
||||
|
||||
session_name=$(tmux display-message -p '#{session_name}' 2>/dev/null || true)
|
||||
window_index=$(tmux display-message -p '#{window_index}' 2>/dev/null || true)
|
||||
pane_index=$(tmux display-message -p '#{pane_index}' 2>/dev/null || true)
|
||||
|
||||
state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/op"
|
||||
|
||||
if [[ -n "$session_name" && -n "$window_index" && -n "$pane_index" ]]; then
|
||||
locator="${session_name}:${window_index}.${pane_index}"
|
||||
key="${locator//[^a-zA-Z0-9_]/_}"
|
||||
loc_file="$state_dir/loc_${key}"
|
||||
if [[ -f "$loc_file" ]]; then
|
||||
session_id=$(cat "$loc_file")
|
||||
if [[ -n "$session_id" ]]; then
|
||||
echo "OP_TRACKER_NOTIFY=1 op -s ${session_id}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "OP_TRACKER_NOTIFY=1 op"
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
LOCK="/tmp/tmux-new-session.lock"
|
||||
if [ -f "$LOCK" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
python3 "$HOME/.config/tmux/scripts/session_manager.py" created
|
||||
|
|
|
|||
|
|
@ -120,6 +120,26 @@ def command_move(direction: str) -> None:
|
|||
apply_order(sessions)
|
||||
|
||||
|
||||
def command_insert_right(anchor_id: str, moving_id: str) -> None:
|
||||
if not anchor_id or not moving_id or anchor_id == moving_id:
|
||||
command_ensure()
|
||||
return
|
||||
|
||||
sessions = list_sessions()
|
||||
indices = {session["id"]: idx for idx, session in enumerate(sessions)}
|
||||
if anchor_id not in indices or moving_id not in indices:
|
||||
command_ensure()
|
||||
return
|
||||
|
||||
moving_session = sessions.pop(indices[moving_id])
|
||||
anchor_pos = next((idx for idx, session in enumerate(sessions) if session["id"] == anchor_id), None)
|
||||
if anchor_pos is None:
|
||||
sessions.append(moving_session)
|
||||
else:
|
||||
sessions.insert(anchor_pos + 1, moving_session)
|
||||
apply_order(sessions)
|
||||
|
||||
|
||||
def command_ensure() -> None:
|
||||
sessions = list_sessions()
|
||||
if sessions:
|
||||
|
|
@ -161,6 +181,8 @@ def main(argv: List[str]) -> None:
|
|||
command_rename(argv[2])
|
||||
elif command == "move" and len(argv) >= 3:
|
||||
command_move(argv[2])
|
||||
elif command == "insert-right" and len(argv) >= 4:
|
||||
command_insert_right(argv[2], argv[3])
|
||||
elif command == "ensure":
|
||||
command_ensure()
|
||||
elif command == "created":
|
||||
|
|
|
|||
39
tmux/scripts/swap_window_in_session.sh
Normal file
39
tmux/scripts/swap_window_in_session.sh
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
direction="${1:-}"
|
||||
case "$direction" in
|
||||
left|right) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
session_id=$(tmux display-message -p '#{session_id}' 2>/dev/null || true)
|
||||
window_id=$(tmux display-message -p '#{window_id}' 2>/dev/null || true)
|
||||
[[ -n "$session_id" && -n "$window_id" ]] || exit 0
|
||||
|
||||
windows=()
|
||||
while IFS= read -r window; do
|
||||
[[ -n "$window" ]] && windows+=("$window")
|
||||
done < <(tmux list-windows -t "$session_id" -F '#{window_id}' 2>/dev/null || true)
|
||||
count=${#windows[@]}
|
||||
(( count >= 2 )) || exit 0
|
||||
|
||||
current=-1
|
||||
for i in "${!windows[@]}"; do
|
||||
if [[ "${windows[$i]}" == "$window_id" ]]; then
|
||||
current=$i
|
||||
break
|
||||
fi
|
||||
done
|
||||
(( current >= 0 )) || exit 0
|
||||
|
||||
if [[ "$direction" == "left" ]]; then
|
||||
target_index=$(( current == 0 ? count - 1 : current - 1 ))
|
||||
else
|
||||
target_index=$(( current == count - 1 ? 0 : current + 1 ))
|
||||
fi
|
||||
|
||||
target_window_id="${windows[$target_index]}"
|
||||
[[ -n "$target_window_id" && "$target_window_id" != "$window_id" ]] || exit 0
|
||||
|
||||
tmux swap-window -d -s "$window_id" -t "$target_window_id"
|
||||
|
|
@ -6,20 +6,16 @@ window_id="$2"
|
|||
|
||||
[[ -z "$pane_id" || -z "$window_id" ]] && exit 1
|
||||
|
||||
shells="bash zsh fish sh dash ksh tcsh csh"
|
||||
pane_pid=$(tmux display-message -p -t "$pane_id" '#{pane_pid}' 2>/dev/null || true)
|
||||
[[ -z "$pane_pid" ]] && exit 0
|
||||
|
||||
is_shell() {
|
||||
local cmd="$1"
|
||||
for s in $shells; do
|
||||
[[ "$cmd" == "$s" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
pane_shell=$(ps -o comm= -p "$pane_pid" 2>/dev/null | sed 's|.*/||; s/^-//')
|
||||
[[ -z "$pane_shell" ]] && exit 0
|
||||
|
||||
current_cmd=$(tmux display-message -p -t "$pane_id" '#{pane_current_command}' 2>/dev/null || true)
|
||||
[[ -z "$current_cmd" ]] && exit 0
|
||||
|
||||
if is_shell "$current_cmd"; then
|
||||
if [[ "$current_cmd" == "$pane_shell" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
@ -31,7 +27,7 @@ while true; do
|
|||
watching=$(tmux show -wv -t "$window_id" @watching 2>/dev/null || true)
|
||||
[[ "$watching" != "1" ]] && exit 0
|
||||
cmd=$(tmux display-message -p -t "$pane_id" '#{pane_current_command}' 2>/dev/null || true)
|
||||
if [[ -z "$cmd" ]] || is_shell "$cmd"; then
|
||||
if [[ -z "$cmd" || "$cmd" == "$pane_shell" ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue