| import { env } from '../../../utils/env.js' | |
| import { execFileNoThrow } from '../../../utils/execFileNoThrow.js' | |
| import { TMUX_COMMAND } from '../constants.js' | |
| /** | |
| * Captured at module load time to detect if the user started Claude from within tmux. | |
| * Shell.ts may override TMUX env var later, so we capture the original value. | |
| */ | |
| // eslint-disable-next-line custom-rules/no-process-env-top-level | |
| const ORIGINAL_USER_TMUX = process.env.TMUX | |
| /** | |
| * Captured at module load time to get the leader's tmux pane ID. | |
| * TMUX_PANE is set by tmux to the pane ID (e.g., %0, %1) when a process runs inside tmux. | |
| * We capture this at startup so we always know the leader's original pane, even if | |
| * the user switches to a different pane later. | |
| */ | |
| // eslint-disable-next-line custom-rules/no-process-env-top-level | |
| const ORIGINAL_TMUX_PANE = process.env.TMUX_PANE | |
| /** Cached result for isInsideTmux */ | |
| let isInsideTmuxCached: boolean | null = null | |
| /** Cached result for isInITerm2 */ | |
| let isInITerm2Cached: boolean | null = null | |
| /** | |
| * Checks if we're currently running inside a tmux session (synchronous version). | |
| * Uses the original TMUX value captured at module load, not process.env.TMUX, | |
| * because Shell.ts overrides TMUX when Claude's socket is initialized. | |
| * | |
| * IMPORTANT: We ONLY check the TMUX env var. We do NOT run `tmux display-message` | |
| * as a fallback because that command will succeed if ANY tmux server is running | |
| * on the system, not just if THIS process is inside tmux. | |
| */ | |
| export function isInsideTmuxSync(): boolean { | |
| return !!ORIGINAL_USER_TMUX | |
| } | |
| /** | |
| * Checks if we're currently running inside a tmux session. | |
| * Uses the original TMUX value captured at module load, not process.env.TMUX, | |
| * because Shell.ts overrides TMUX when Claude's socket is initialized. | |
| * Caches the result since this won't change during the process lifetime. | |
| * | |
| * IMPORTANT: We ONLY check the TMUX env var. We do NOT run `tmux display-message` | |
| * as a fallback because that command will succeed if ANY tmux server is running | |
| * on the system, not just if THIS process is inside tmux. | |
| */ | |
| export async function isInsideTmux(): Promise<boolean> { | |
| if (isInsideTmuxCached !== null) { | |
| return isInsideTmuxCached | |
| } | |
| // Check the original TMUX env var (captured at module load) | |
| // This tells us if the user started Claude from within their tmux session | |
| // If TMUX is not set, we are NOT inside tmux - period. | |
| isInsideTmuxCached = !!ORIGINAL_USER_TMUX | |
| return isInsideTmuxCached | |
| } | |
| /** | |
| * Gets the leader's tmux pane ID captured at module load. | |
| * Returns null if not running inside tmux. | |
| */ | |
| export function getLeaderPaneId(): string | null { | |
| return ORIGINAL_TMUX_PANE || null | |
| } | |
| /** | |
| * Checks if tmux is available on the system (installed and in PATH). | |
| */ | |
| export async function isTmuxAvailable(): Promise<boolean> { | |
| const result = await execFileNoThrow(TMUX_COMMAND, ['-V']) | |
| return result.code === 0 | |
| } | |
| /** | |
| * Checks if we're currently running inside iTerm2. | |
| * Uses multiple detection methods: | |
| * 1. TERM_PROGRAM env var set to "iTerm.app" | |
| * 2. ITERM_SESSION_ID env var is present | |
| * 3. env.terminal detection from utils/env.ts | |
| * | |
| * Caches the result since this won't change during the process lifetime. | |
| * | |
| * Note: iTerm2 backend uses AppleScript (osascript) which is built into macOS, | |
| * so no external CLI tool installation is required. | |
| */ | |
| export function isInITerm2(): boolean { | |
| if (isInITerm2Cached !== null) { | |
| return isInITerm2Cached | |
| } | |
| // Check multiple indicators for iTerm2 | |
| const termProgram = process.env.TERM_PROGRAM | |
| const hasItermSessionId = !!process.env.ITERM_SESSION_ID | |
| const terminalIsITerm = env.terminal === 'iTerm.app' | |
| isInITerm2Cached = | |
| termProgram === 'iTerm.app' || hasItermSessionId || terminalIsITerm | |
| return isInITerm2Cached | |
| } | |
| /** | |
| * The it2 CLI command name. | |
| */ | |
| export const IT2_COMMAND = 'it2' | |
| /** | |
| * Checks if the it2 CLI tool is available AND can reach the iTerm2 Python API. | |
| * Uses 'session list' (not '--version') because --version succeeds even when | |
| * the Python API is disabled in iTerm2 preferences — which would cause | |
| * 'session split' to fail later with no fallback. | |
| */ | |
| export async function isIt2CliAvailable(): Promise<boolean> { | |
| const result = await execFileNoThrow(IT2_COMMAND, ['session', 'list']) | |
| return result.code === 0 | |
| } | |
| /** | |
| * Resets all cached detection results. Used for testing. | |
| */ | |
| export function resetDetectionCache(): void { | |
| isInsideTmuxCached = null | |
| isInITerm2Cached = null | |
| } | |