Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions scripts/tmux-chooser.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ set -e
CODEMAN_STATE="$HOME/.codeman/state.json"
CODEMAN_SESSIONS="$HOME/.codeman/mux-sessions.json"

# Dedicated tmux socket all Codeman sessions live on. MUST match
# DEFAULT_CODEMAN_TMUX_SOCKET / CODEMAN_TMUX_SOCKET in src/tmux-manager.ts —
# otherwise list-sessions would enumerate the user's default tmux server
# (missing the real Codeman sessions, surfacing unrelated ones).
CODEMAN_TMUX_SOCKET="${CODEMAN_TMUX_SOCKET:-codeman}"
TMUX_CMD=(tmux -L "$CODEMAN_TMUX_SOCKET")


# iPhone 17 Pro portrait width (conservative)
MAX_WIDTH=44
Expand Down Expand Up @@ -286,7 +293,7 @@ parse_sessions() {

# Get PID from tmux
local pid
pid=$(tmux display-message -t "$session_name" -p '#{pane_pid}' 2>/dev/null || echo "0")
pid=$("${TMUX_CMD[@]}" display-message -t "$session_name" -p '#{pane_pid}' 2>/dev/null || echo "0")

SESSION_PIDS+=("$pid")
MUX_NAMES+=("$session_name")
Expand Down Expand Up @@ -314,7 +321,7 @@ parse_sessions() {
fi

i=$((i + 1))
done < <(tmux list-sessions 2>/dev/null || true)
done < <("${TMUX_CMD[@]}" list-sessions 2>/dev/null || true)
}

# ============================================================================
Expand Down Expand Up @@ -462,7 +469,7 @@ attach_session() {
echo -e "${D}(Ctrl+B D to detach)${R}"
sleep 0.3

tmux attach-session -t "$mux_name"
"${TMUX_CMD[@]}" attach-session -t "$mux_name"

return 0
}
Expand Down
25 changes: 17 additions & 8 deletions scripts/tmux-manager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ REVERSE='\033[7m'
# Use the same path as codeman (src/tmux-manager.ts)
SESSIONS_FILE="${HOME}/.codeman/mux-sessions.json"

# Dedicated tmux socket all Codeman sessions live on. MUST match
# DEFAULT_CODEMAN_TMUX_SOCKET / CODEMAN_TMUX_SOCKET in src/tmux-manager.ts —
# otherwise this script would talk to the user's default tmux server and never
# see (or could mis-target) Codeman's sessions.
CODEMAN_TMUX_SOCKET="${CODEMAN_TMUX_SOCKET:-codeman}"
TMUX_CMD=(tmux -L "$CODEMAN_TMUX_SOCKET")


# Cached data
CACHED_JSON=""
Expand Down Expand Up @@ -92,7 +99,7 @@ declare -A ALIVE_CACHE
check_alive() {
local mux_name=$1
if [[ -z "${ALIVE_CACHE[$mux_name]+x}" ]]; then
if tmux has-session -t "$mux_name" 2>/dev/null; then
if "${TMUX_CMD[@]}" has-session -t "$mux_name" 2>/dev/null; then
ALIVE_CACHE[$mux_name]=1
else
ALIVE_CACHE[$mux_name]=0
Expand All @@ -111,16 +118,18 @@ kill_session() {
local mux_name=$(get_session_field $idx "muxName")
local pid=$(get_session_field $idx "pid")

# SAFETY: Never kill own tmux session
local current_session=$(tmux display-message -p '#{session_name}' 2>/dev/null || echo "")
# SAFETY: Never kill own tmux session. Queried on the Codeman socket; if run
# from a session on a different socket this returns empty (no match), which
# is fine — you can't be "inside" a Codeman-socket session you didn't attach to.
local current_session=$("${TMUX_CMD[@]}" display-message -p '#{session_name}' 2>/dev/null || echo "")
if [[ -n "$current_session" && "$mux_name" == "$current_session" ]]; then
echo -e "${RED}BLOCKED: Cannot kill own tmux session: $mux_name${NC}"
return 1
fi

pkill -TERM -P $pid 2>/dev/null
kill -TERM -$pid 2>/dev/null
tmux kill-session -t "$mux_name" 2>/dev/null
"${TMUX_CMD[@]}" kill-session -t "$mux_name" 2>/dev/null
kill -KILL $pid 2>/dev/null

# Remove from JSON
Expand Down Expand Up @@ -345,7 +354,7 @@ interactive_menu() {
clear
echo -e "${CYAN}Attaching... (Ctrl+B D to detach)${NC}"
sleep 0.3
tmux attach-session -t "$mux_name"
"${TMUX_CMD[@]}" attach-session -t "$mux_name"
tput civis
need_full_redraw=1
force_refresh
Expand Down Expand Up @@ -474,7 +483,7 @@ main() {
[[ -z "${2:-}" ]] && { echo "Usage: $0 attach <N>"; exit 1; }
force_refresh
local mux_name=$(get_session_field $(($2-1)) "muxName")
check_alive "$mux_name" && tmux attach-session -t "$mux_name" || echo "Session dead or not found"
check_alive "$mux_name" && "${TMUX_CMD[@]}" attach-session -t "$mux_name" || echo "Session dead or not found"
;;
kill)
[[ -z "${2:-}" ]] && { echo "Usage: $0 kill <N|N,M|N-M>"; exit 1; }
Expand All @@ -492,8 +501,8 @@ main() {
;;
kill-all)
force_refresh
# SAFETY: Never kill own tmux session
local current_session=$(tmux display-message -p '#{session_name}' 2>/dev/null || echo "")
# SAFETY: Never kill own tmux session (queried on the Codeman socket)
local current_session=$("${TMUX_CMD[@]}" display-message -p '#{session_name}' 2>/dev/null || echo "")
local killed=0
for ((i=CACHED_COUNT-1; i>=0; i--)); do
local mux_name=$(get_session_field $i "muxName")
Expand Down
3 changes: 3 additions & 0 deletions src/mux-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ export interface TerminalMultiplexer extends EventEmitter {
/** Which backend this instance uses */
readonly backend: 'tmux';

/** The dedicated tmux socket name all sessions live on (e.g. "codeman"). */
readonly muxSocket: string;

// ========== Lifecycle ==========

/**
Expand Down
19 changes: 13 additions & 6 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,22 @@ const TMUX_DISPLAY_TIMEOUT_MS = 2000;
* (tmux dead, muxName unknown, malformed output) — caller never has to
* differentiate "tmux unreachable" from "size 120x40".
*
* `socket` MUST be the same dedicated socket the session lives on (`mux.muxSocket`);
* querying the default server would never find the session and silently fall back.
*
* Argv form (execFileSync, not execSync) keeps `muxName` out of any shell so
* a hostile session name can't inject options.
*/
export function queryTmuxWindowSize(muxName: string): { cols: number; rows: number } {
export function queryTmuxWindowSize(muxName: string, socket: string): { cols: number; rows: number } {
try {
const sizeStr = execFileSync('tmux', ['display', '-t', muxName, '-p', '#{window_width} #{window_height}'], {
timeout: TMUX_DISPLAY_TIMEOUT_MS,
encoding: 'utf8',
}).trim();
const sizeStr = execFileSync(
'tmux',
['-L', socket, 'display', '-t', muxName, '-p', '#{window_width} #{window_height}'],
{
timeout: TMUX_DISPLAY_TIMEOUT_MS,
encoding: 'utf8',
}
).trim();
const [w, h] = sizeStr.split(' ').map(Number);
if (w > 0 && h > 0) {
return { cols: w, rows: h };
Expand Down Expand Up @@ -978,7 +985,7 @@ export class Session extends EventEmitter {

// Attach to the mux session via PTY
// Query existing tmux window size so re-attach matches (avoids flicker from 120x40 default)
const { cols: ptyCols, rows: ptyRows } = queryTmuxWindowSize(this._muxSession!.muxName);
const { cols: ptyCols, rows: ptyRows } = queryTmuxWindowSize(this._muxSession!.muxName, mux.muxSocket);
try {
this.ptyProcess = pty.spawn(mux.getAttachCommand(), mux.getAttachArgs(this._muxSession!.muxName), {
name: 'xterm-256color',
Expand Down
Loading