#!/bin/bash
# dclaude - Dockerized Claude Code Launcher
# https://github.com/alanbem/dclaude

set -euo pipefail

# Early configuration (before config file loading)
readonly IMAGE_NAME="${DCLAUDE_REGISTRY:-docker.io}/alanbem/dclaude"
readonly IMAGE_TAG="${DCLAUDE_TAG:-latest}"
readonly IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
readonly VOLUME_PREFIX="dclaude"
readonly QUIET="${DCLAUDE_QUIET:-false}"
readonly REMOVE_CONTAINER="${DCLAUDE_RM:-false}"
readonly ENABLE_SYSTEM_CONTEXT="${DCLAUDE_SYSTEM_CONTEXT:-true}"  # Inform Claude about dclaude environment

# Docker socket will be detected dynamically unless overridden
DOCKER_SOCKET="${DCLAUDE_DOCKER_SOCKET:-}"

# Chrome configuration (not readonly to allow --port flag and config file override)
CHROME_PROFILE="${DCLAUDE_CHROME_PROFILE:-claude}"
CHROME_BIN="${DCLAUDE_CHROME_BIN:-}"
CHROME_FLAGS="${DCLAUDE_CHROME_FLAGS:-}"

# Variables that can be set via .dclaude config file (declared after config loading)
# - DCLAUDE_NAMESPACE (namespace for volume/container isolation)
# - DCLAUDE_NETWORK (network mode: host/bridge)
# - DCLAUDE_GIT_AUTH (git auth mode)
# - DCLAUDE_DEBUG (enable debug output)
# - DCLAUDE_CHROME_PORT (chrome devtools port)
# - DCLAUDE_MOUNT_ROOT (mount a parent directory instead of working directory)

# Colors for output (only if terminal supports it)
if [[ -t 1 ]]; then
    readonly RED='\033[0;31m'
    readonly GREEN='\033[0;32m'
    readonly YELLOW='\033[1;33m'
    readonly BLUE='\033[0;34m'
    readonly CYAN='\033[0;36m'
    readonly NC='\033[0m' # No Color
else
    readonly RED=''
    readonly GREEN=''
    readonly YELLOW=''
    readonly BLUE=''
    readonly CYAN=''
    readonly NC=''
fi

# Helper functions
error() {
    echo -e "${RED}Error: $1${NC}" >&2
}

warning() {
    echo -e "${YELLOW}Warning: $1${NC}" >&2
}

success() {
    if [[ "$QUIET" != "true" ]]; then
        echo -e "${GREEN}$1${NC}" >&2
    fi
}

info() {
    if [[ "$QUIET" != "true" ]]; then
        echo -e "${BLUE}$1${NC}" >&2
    fi
}

debug() {
    # QUIET overrides DEBUG - if quiet, suppress debug too
    if [[ "$QUIET" != "true" ]] && [[ "$DEBUG" == "true" ]]; then
        echo -e "${CYAN}Debug: $1${NC}" >&2
    fi
}

# Find .dclaude config file by walking up directory tree
find_config_file() {
    local dir="$PWD"
    while [[ "$dir" != "/" ]]; do
        if [[ -f "$dir/.dclaude" ]]; then
            echo "$dir/.dclaude"
            return 0
        fi
        dir=$(dirname "$dir")
    done
    return 1
}

# Load .dclaude config file (env vars take precedence)
load_config_file() {
    local config_file="$1"

    while IFS= read -r line || [[ -n "$line" ]]; do
        # Skip comments and empty lines
        [[ "$line" =~ ^[[:space:]]*# ]] && continue
        [[ -z "${line// }" ]] && continue

        # Parse key=value
        if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=[[:space:]]*(.*)$ ]]; then
            local key="${BASH_REMATCH[1]}"
            local value="${BASH_REMATCH[2]}"
            # Trim quotes if present
            value="${value#\"}"
            value="${value%\"}"
            value="${value#\'}"
            value="${value%\'}"

            # Only set if env var not already set (env var takes precedence)
            case "$key" in
                NAMESPACE)
                    [[ -z "${DCLAUDE_NAMESPACE:-}" ]] && DCLAUDE_NAMESPACE="$value"
                    ;;
                NETWORK)
                    [[ -z "${DCLAUDE_NETWORK:-}" ]] && DCLAUDE_NETWORK="$value"
                    ;;
                GIT_AUTH)
                    [[ -z "${DCLAUDE_GIT_AUTH:-}" ]] && DCLAUDE_GIT_AUTH="$value"
                    ;;
                DEBUG)
                    [[ -z "${DCLAUDE_DEBUG:-}" ]] && DCLAUDE_DEBUG="$value"
                    ;;
                CHROME_PORT)
                    [[ -z "${DCLAUDE_CHROME_PORT:-}" ]] && DCLAUDE_CHROME_PORT="$value"
                    ;;
                MOUNT_ROOT)
                    if [[ -z "${DCLAUDE_MOUNT_ROOT:-}" ]]; then
                        # Resolve relative paths relative to config file's directory
                        if [[ "$value" != /* ]]; then
                            local config_dir
                            config_dir=$(dirname "$config_file")
                            DCLAUDE_MOUNT_ROOT=$(cd "$config_dir" && cd "$value" 2>/dev/null && pwd)
                        else
                            DCLAUDE_MOUNT_ROOT="$value"
                        fi
                    fi
                    ;;
                AWS_CLI)
                    [[ -z "${DCLAUDE_AWS_CLI:-}" ]] && DCLAUDE_AWS_CLI="$value"
                    ;;
                CA_CERT)
                    if [[ -z "${DCLAUDE_CA_CERT:-}" ]]; then
                        # Resolve relative paths relative to config file's directory
                        if [[ "$value" != /* ]]; then
                            local config_dir
                            config_dir=$(dirname "$config_file")
                            DCLAUDE_CA_CERT=$(cd "$config_dir" && realpath "$value" 2>/dev/null)
                        else
                            DCLAUDE_CA_CERT="$value"
                        fi
                    fi
                    ;;
                DISABLE_AUTOUPDATER)
                    [[ -z "${DCLAUDE_DISABLE_AUTOUPDATER:-}" ]] && DCLAUDE_DISABLE_AUTOUPDATER="$value"
                    ;;
            esac
        fi
    done < "$config_file"

    # Under `set -e`, a short-circuited `&&`-style case branch (last line already
    # overridden by an env var) would make this function return non-zero and abort dclaude.
    return 0
}

# Load config file if found (must happen early, before readonly declarations)
DCLAUDE_CONFIG_FILE=""
if DCLAUDE_CONFIG_FILE=$(find_config_file); then
    load_config_file "$DCLAUDE_CONFIG_FILE"
fi

# Now set readonly variables that may have been loaded from config file
readonly DEBUG="${DCLAUDE_DEBUG:-false}"
readonly GIT_AUTH_MODE="${DCLAUDE_GIT_AUTH:-auto}"  # auto, agent-forwarding, key-mount, none
readonly NAMESPACE="${DCLAUDE_NAMESPACE:-}"
CHROME_PORT="${DCLAUDE_CHROME_PORT:-9222}"
readonly AWS_CLI_MODE="${DCLAUDE_AWS_CLI:-auto}"  # auto, mount, volume, none
# Resolve CA_CERT relative paths (env var: relative to PWD, config file: already resolved)
if [[ -n "${DCLAUDE_CA_CERT:-}" && "${DCLAUDE_CA_CERT}" != /* ]]; then
    DCLAUDE_CA_CERT=$(realpath "$DCLAUDE_CA_CERT" 2>/dev/null) || DCLAUDE_CA_CERT="$PWD/$DCLAUDE_CA_CERT"
fi
readonly CA_CERT="${DCLAUDE_CA_CERT:-}"  # Path to CA certificate for corporate proxies
# When set, passed into the container; unset defers to Claude Code's default (auto-update enabled)
readonly DISABLE_AUTOUPDATER="${DCLAUDE_DISABLE_AUTOUPDATER:-}"

# Show config file if loaded (always at info level, details at debug)
if [[ -n "$DCLAUDE_CONFIG_FILE" ]]; then
    info "Using config: $DCLAUDE_CONFIG_FILE"
    if [[ "$DEBUG" == "true" ]]; then
        [[ -n "$NAMESPACE" ]] && debug "  NAMESPACE=$NAMESPACE"
        [[ -n "${DCLAUDE_NETWORK:-}" ]] && debug "  NETWORK=$DCLAUDE_NETWORK"
        [[ -n "${DCLAUDE_GIT_AUTH:-}" ]] && debug "  GIT_AUTH=$DCLAUDE_GIT_AUTH"
        [[ -n "${DCLAUDE_CHROME_PORT:-}" ]] && debug "  CHROME_PORT=$DCLAUDE_CHROME_PORT"
        [[ -n "${DCLAUDE_MOUNT_ROOT:-}" ]] && debug "  MOUNT_ROOT=$DCLAUDE_MOUNT_ROOT"
        [[ -n "${DCLAUDE_AWS_CLI:-}" ]] && debug "  AWS_CLI=$DCLAUDE_AWS_CLI"
        [[ -n "$CA_CERT" ]] && debug "  CA_CERT=$CA_CERT"
        [[ -n "$DISABLE_AUTOUPDATER" ]] && debug "  DISABLE_AUTOUPDATER=$DISABLE_AUTOUPDATER"
    fi
fi

# Detect platform
detect_platform() {
    case "$(uname -s)" in
        Darwin*)
            echo "darwin"
            ;;
        Linux*)
            echo "linux"
            ;;
        MINGW*|CYGWIN*|MSYS*)
            echo "windows"
            ;;
        *)
            echo "unknown"
            ;;
    esac
}

# Detect Docker socket path based on platform
detect_docker_socket() {
    info "Detecting Docker socket..."

    # Prefer Docker context (tells us the active socket)
    if command -v docker &> /dev/null && command -v jq &> /dev/null; then
        debug "Checking Docker context for socket path"
        local context_output context_socket
        context_output=$(docker context inspect 2>/dev/null || true)
        if [[ -n "$context_output" ]]; then
            context_socket=$(echo "$context_output" | jq -r '.[0].Endpoints.docker.Host' 2>/dev/null | sed 's|unix://||' 2>/dev/null) || true
            if [[ -n "$context_socket" ]] && [[ -S "$context_socket" ]]; then
                debug "Docker socket found via context: $context_socket"
                echo "$context_socket"
                return 0
            else
                debug "Context socket not valid: ${context_socket:-<empty>}"
            fi
        else
            debug "No Docker context found"
        fi
    fi

    # Fallback to common socket paths
    debug "Falling back to filesystem search for Docker socket"
    local socket_paths=(
        "$HOME/.docker/run/docker.sock"      # macOS Docker Desktop
        "/var/run/docker.sock"                # Linux, Docker CE
        "$HOME/.orbstack/run/docker.sock"    # OrbStack
        "$HOME/.colima/default/docker.sock"   # Colima
        "$HOME/.colima/docker.sock"           # Colima alternative
        "$HOME/.rd/docker.sock"               # Rancher Desktop
        "/run/user/$(id -u)/docker.sock"      # Rootless Docker on Linux
    )

    for socket in "${socket_paths[@]}"; do
        if [[ -S "$socket" ]]; then
            debug "Docker socket found at: $socket"
            echo "$socket"
            return 0
        fi
    done

    debug "No Docker socket found"
    return 1
}

# Check if Docker is installed
check_docker() {
    if ! command -v docker &> /dev/null; then
        error "Docker is not installed. Please install Docker first."
        echo "Visit: https://docs.docker.com/get-docker/" >&2
        exit 1
    fi
    debug "Docker found at: $(command -v docker)"
}

# Check if Docker daemon is running
check_docker_running() {
    if ! docker info &> /dev/null; then
        error "Docker daemon is not running. Please start Docker."
        exit 1
    fi
    debug "Docker daemon is running"
}

# Find an available port in the specified range
find_available_port() {
    local start_port=${1:-20000}
    local end_port=${2:-65000}
    local port

    for ((port=start_port; port<=end_port; port++)); do
        local port_in_use=false

        # Fast check 1: netstat (catches most listening processes)
        if command -v netstat &>/dev/null; then
            if netstat -an 2>/dev/null | grep -qE "[.:]$port\s+(LISTEN|ESTABLISHED)" 2>/dev/null; then
                port_in_use=true
            fi
        fi

        # Fast check 2: lsof (more reliable on macOS, catches additional cases)
        if ! $port_in_use && command -v lsof &>/dev/null; then
            if lsof -i ":$port" &>/dev/null 2>&1; then
                port_in_use=true
            fi
        fi

        # Comprehensive check: Socket test (catches Docker containers, VMs, kernel-level bindings)
        # Only run if fast checks didn't detect anything (avoids timeout overhead)
        if ! $port_in_use; then
            if timeout 0.1 bash -c "exec 3<>/dev/tcp/localhost/$port" 2>/dev/null; then
                # Successfully connected - something is listening
                exec 3<&- 2>/dev/null  # Close connection
                port_in_use=true
            fi
        fi

        if ! $port_in_use; then
            echo "$port"
            return 0
        fi
    done

    # Fallback: use a random port if no available port found
    echo $((20000 + RANDOM % 45000))
}

# Portable timeout function that works on both Linux and macOS
# Usage: run_with_timeout <seconds> <command...>
run_with_timeout() {
    local timeout_seconds=$1
    shift

    # Try to use timeout command if available (Linux, brew-installed coreutils)
    if command -v timeout &>/dev/null; then
        timeout "$timeout_seconds" "$@"
        return $?
    elif command -v gtimeout &>/dev/null; then
        # macOS with coreutils installed via Homebrew
        gtimeout "$timeout_seconds" "$@"
        return $?
    else
        # Fallback: run command in background and kill after timeout
        local pid
        "$@" &
        pid=$!

        # Sleep in background and kill the process after timeout
        (
            sleep "$timeout_seconds"
            kill -TERM "$pid" 2>/dev/null || true
        ) &
        local timer_pid=$!

        # Wait for the command to finish
        local result
        if wait "$pid" 2>/dev/null; then
            result=$?
            kill -TERM "$timer_pid" 2>/dev/null || true
            wait "$timer_pid" 2>/dev/null || true
            return $result
        else
            # Command failed or was killed
            return 124  # Standard timeout exit code
        fi
    fi
}

# Detect network capability (host vs bridge)
detect_network_capability() {
    info "Detecting network mode..."

    local cache_dir="${HOME}/.dclaude"
    local cache_file="${cache_dir}/network-mode"
    local test_timeout=10
    local server_container=""

    # Cleanup function for this function's containers
    cleanup_nettest() {
        if [[ -n "$server_container" ]]; then
            debug "Cleaning up test container: $server_container"
            docker stop "$server_container" &>/dev/null || true
            docker rm -f "$server_container" &>/dev/null || true
        fi
    }

    # Set up trap for cleanup
    trap cleanup_nettest EXIT

    # Create cache directory if it doesn't exist
    if [[ ! -d "$cache_dir" ]]; then
        mkdir -p "$cache_dir" || {
            debug "Failed to create cache directory, proceeding without cache"
            cache_file=""
        }
    fi

    # Check cache first (valid for 24 hours)
    if [[ -n "$cache_file" && -f "$cache_file" ]]; then
        local cache_age
        if cache_age=$(stat -c %Y "$cache_file" 2>/dev/null) || cache_age=$(stat -f %m "$cache_file" 2>/dev/null); then
            local current_time
            current_time=$(date +%s)
            local age_hours=$(( (current_time - cache_age) / 3600 ))

            if [[ $age_hours -lt 24 ]]; then
                local cached_mode
                if cached_mode=$(cat "$cache_file" 2>/dev/null) && [[ "$cached_mode" =~ ^(host|bridge)$ ]]; then
                    debug "Using cached network mode: $cached_mode (age: ${age_hours}h)"
                    trap - EXIT
                    echo "$cached_mode"
                    return 0
                fi
            fi
        fi
    fi

    debug "Testing network capabilities..."

    # Ensure alpine:3.19 is available
    if ! docker image inspect alpine:3.19 &>/dev/null; then
        debug "Alpine 3.19 image not found, pulling..."
        if ! docker pull alpine:3.19 &>/dev/null; then
            debug "Failed to pull alpine:3.19, falling back to bridge mode"
            trap - EXIT
            echo "bridge" | tee "$cache_file" 2>/dev/null || echo "bridge"
            return 0
        fi
        debug "Alpine 3.19 image pulled successfully"
    else
        debug "Alpine 3.19 image is available"
    fi

    # Test 1: Basic host networking support
    debug "Test 1: Basic host networking support"
    if ! run_with_timeout "$test_timeout" docker run --rm --network host alpine:3.19 \
        sh -c 'ip addr show lo | grep -q "127\.0\.0\.1"' &>/dev/null; then
        debug "Host networking not supported (basic test failed)"
        trap - EXIT
        echo "bridge" | tee "$cache_file" 2>/dev/null || echo "bridge"
        return 0
    fi

    # Test 2: Container-to-container localhost access
    debug "Test 2: Container-to-container localhost access"
    local test_port
    test_port=$(find_available_port)
    debug "Using dynamic port: $test_port"
    server_container="dclaude-nettest-$$"

    # Start test server
    if ! docker run --rm -d --name "$server_container" --network host alpine:3.19 \
        sh -c "echo 'test-response' | nc -l -p $test_port" &>/dev/null; then
        debug "Failed to start test server"
        trap - EXIT
        echo "bridge" | tee "$cache_file" 2>/dev/null || echo "bridge"
        return 0
    fi

    # Wait for server to be ready (max 5 seconds)
    debug "Waiting for test server to be ready..."
    local ready=false
    local wait_count=0
    local max_wait=50  # 5 seconds (50 * 0.1 seconds)

    while [[ $wait_count -lt $max_wait ]]; do
        # Use more portable netstat options for Alpine Linux
        if docker exec "$server_container" sh -c "netstat -tuln 2>/dev/null | grep -q \":$test_port \"" &>/dev/null; then
            ready=true
            debug "Test server is ready after $((wait_count * 100))ms"
            break
        fi
        sleep 0.1
        ((wait_count++))
    done

    if [[ "$ready" != "true" ]]; then
        debug "Test server failed to become ready within 5 seconds"
        cleanup_nettest
        trap - EXIT
        echo "bridge" | tee "$cache_file" 2>/dev/null || echo "bridge"
        return 0
    fi

    # Test client connection
    local test_result="bridge"
    if run_with_timeout 5 docker run --rm --network host alpine:3.19 \
        sh -c "echo '' | nc localhost $test_port" &>/dev/null; then
        test_result="host"
        debug "Host networking fully supported"
    else
        debug "Host networking partially supported but localhost access failed"
    fi

    # Cleanup test server (handled by trap)
    cleanup_nettest

    # Cache the result
    if [[ -n "$cache_file" ]]; then
        echo "$test_result" > "$cache_file" 2>/dev/null || true
    fi

    # Platform-specific validation of detected mode
    local platform
    platform=$(detect_platform)
    case "$platform" in
        darwin)
            if [[ "$test_result" == "host" ]]; then
                debug "Host networking detected on macOS - validating compatibility"
                # On macOS, host networking should only work with Docker Desktop beta or OrbStack
                if ! docker system info 2>/dev/null | grep -qi "orbstack\|desktop.*beta" &>/dev/null; then
                    debug "Host networking may not work properly on this Docker setup for macOS"
                    warning "Host networking detected but may not work properly on macOS Docker Desktop (non-beta)"
                fi
            fi
            ;;
        windows)
            if [[ "$test_result" == "host" ]]; then
                debug "Host networking detected on Windows - this is unusual"
                warning "Host networking detected on Windows - this may indicate WSL2 or special configuration"
            fi
            ;;
        linux)
            debug "Network mode '$test_result' is expected on Linux platform"
            ;;
        *)
            debug "Unknown platform '$platform' - network mode validation skipped"
            ;;
    esac

    # Clear the trap before returning
    trap - EXIT

    debug "Network capability detection result: $test_result"
    echo "$test_result"
}

# Get volume name (includes namespace if set)
get_volume_name() {
    if [[ -n "$NAMESPACE" ]]; then
        echo "${VOLUME_PREFIX}-${NAMESPACE}-claude"
    else
        echo "${VOLUME_PREFIX}-claude"
    fi
}

# Get AWS volume name (includes namespace if set)
get_aws_volume_name() {
    if [[ -n "$NAMESPACE" ]]; then
        echo "${VOLUME_PREFIX}-${NAMESPACE}-aws"
    else
        echo "${VOLUME_PREFIX}-aws"
    fi
}

# Resolve AWS CLI mode from configured value
# auto → mount (if ~/.aws exists) or none
resolve_aws_cli_mode() {
    case "$AWS_CLI_MODE" in
        mount|volume|none)
            echo "$AWS_CLI_MODE"
            ;;
        auto)
            if [[ -d "${HOME}/.aws" ]]; then
                echo "mount"
            else
                echo "none"
            fi
            ;;
        *)
            warning "Unknown AWS_CLI mode: $AWS_CLI_MODE (using none)"
            echo "none"
            ;;
    esac
}

# Create Docker volumes if they don't exist
create_volumes() {
    # Create essential volume for Claude CLI persistence
    local volume
    volume=$(get_volume_name)

    if ! docker volume inspect "$volume" &> /dev/null; then
        info "Creating volume: $volume"
        if ! docker volume create "$volume" > /dev/null; then
            error "Failed to create volume: $volume"
            exit 1
        fi
        debug "Volume created successfully: $volume"
    else
        debug "Volume exists: $volume"
    fi

    # Create AWS volume if volume mode is active
    local aws_mode
    aws_mode=$(resolve_aws_cli_mode)
    if [[ "$aws_mode" == "volume" ]]; then
        local aws_volume
        aws_volume=$(get_aws_volume_name)
        if ! docker volume inspect "$aws_volume" &> /dev/null; then
            info "Creating AWS volume: $aws_volume"
            if ! docker volume create "$aws_volume" > /dev/null; then
                error "Failed to create volume: $aws_volume"
                exit 1
            fi
            debug "Volume created successfully: $aws_volume"
        else
            debug "Volume exists: $aws_volume"
        fi
    fi
}

# Pull or update the Docker image
update_image() {
    # Skip updates if DCLAUDE_NO_UPDATE is set
    if [[ "${DCLAUDE_NO_UPDATE:-false}" == "true" ]]; then
        debug "Skipping image update (DCLAUDE_NO_UPDATE=true)"
        # Ensure image exists
        if ! docker image inspect "$IMAGE" &> /dev/null; then
            error "Docker image $IMAGE not found locally and updates disabled."
            error "Either unset DCLAUDE_NO_UPDATE or pull the image manually."
            exit 1
        fi
        return
    fi

    local current_id=""
    local new_id=""

    # Get current image ID if exists
    if docker image inspect "$IMAGE" &> /dev/null; then
        current_id=$(docker image inspect "$IMAGE" --format='{{.Id}}')
        debug "Current image ID: ${current_id:0:12}"
    fi

    # Try to pull latest
    info "Checking for updates to $IMAGE..."
    if docker pull "$IMAGE" 2> /dev/null; then
        new_id=$(docker image inspect "$IMAGE" --format='{{.Id}}' 2>/dev/null || echo "")

        if [[ -z "$new_id" ]]; then
            warning "Failed to inspect image after pull"
        elif [[ "$current_id" != "$new_id" ]]; then
            success "Image updated successfully."
            debug "New image ID: ${new_id:0:12}"
        else
            debug "Image is up to date"
        fi
    else
        if [[ -z "$current_id" ]]; then
            error "Failed to pull image and no local image exists."
            exit 1
        else
            warning "Could not check for updates, using local image."
        fi
    fi
}

# Get the host path for mounting
get_host_path() {
    local host_path
    host_path=$(pwd) || {
        error "Failed to get current directory"
        exit 1
    }

    # Basic path validation
    if [[ ! -d "$host_path" ]]; then
        error "Current directory does not exist: $host_path"
        exit 1
    fi

    echo "$host_path"
}

# Compute mount root path from DCLAUDE_MOUNT_ROOT
# Supports: absolute paths, relative paths (../, ../../, etc.)
# Returns: Absolute path to mount root, or HOST_PATH if not set
get_mount_root() {
    local host_path="$1"
    local mount_root="${DCLAUDE_MOUNT_ROOT:-}"

    # If not set, use host path (current behavior)
    if [[ -z "$mount_root" ]]; then
        echo "$host_path"
        return 0
    fi

    local resolved_root

    # Handle relative paths (resolve relative to host_path)
    if [[ "$mount_root" != /* ]]; then
        # Relative path - resolve it
        resolved_root=$(cd "$host_path" && cd "$mount_root" 2>/dev/null && pwd) || {
            error "Mount root path not accessible: $mount_root (relative to $host_path)"
            exit 1
        }
    else
        # Absolute path - use directly
        resolved_root="$mount_root"
    fi

    # Validate mount root exists
    if [[ ! -d "$resolved_root" ]]; then
        error "Mount root directory does not exist: $resolved_root"
        exit 1
    fi

    # Validate that host_path is under mount_root
    # Use realpath to normalize paths for comparison
    local real_host real_root
    real_host=$(cd "$host_path" && pwd -P)
    real_root=$(cd "$resolved_root" && pwd -P)

    if [[ "$real_host" != "$real_root" && "$real_host" != "$real_root"/* ]]; then
        error "Working directory is not under mount root"
        error "  Working directory: $real_host"
        error "  Mount root: $real_root"
        exit 1
    fi

    echo "$resolved_root"
}

# Generate deterministic container name from path (and namespace if set)
get_container_name() {
    local path="${1:-$HOST_PATH}"
    local hash_input="${NAMESPACE}:${path}"
    local path_hash
    path_hash=$(echo -n "$hash_input" | md5sum 2>/dev/null | cut -d' ' -f1 || echo -n "$hash_input" | md5 | cut -d' ' -f1)
    if [[ -n "$NAMESPACE" ]]; then
        echo "dclaude-${NAMESPACE}-${path_hash:0:8}"
    else
        echo "dclaude-${path_hash:0:12}"
    fi
}

# Generate system context prompt for Claude
generate_system_context() {
    local network_mode="${1:-auto}"
    local git_auth_mode="${2:-auto}"
    local has_docker="${3:-false}"
    local platform="${4:-unknown}"
    local docker_socket="${5:-}"
    local aws_cli_mode="${6:-none}"

    cat <<'EOF'

# dclaude Environment Context

You are running inside **dclaude** - a Docker container that emulates the host environment. This is important context for understanding your capabilities and limitations:

## Container Architecture
- **Host Emulation**: Your current working directory is mounted at the exact same path as on the host
- **Path Mirroring**: All file paths work identically to native execution (e.g., `/Users/alice/project` in container = same path on host)
- **Isolated Environment**: While you operate in a container, file operations affect the real host filesystem through volume mounts

## Host Environment
EOF

    # Platform info
    case "$platform" in
        darwin)
            echo "- **Host OS**: macOS"
            ;;
        linux)
            echo "- **Host OS**: Linux"
            ;;
        windows)
            echo "- **Host OS**: Windows"
            ;;
        *)
            echo "- **Host OS**: Unknown"
            ;;
    esac

    # Architecture
    local arch=$(uname -m 2>/dev/null || echo "unknown")
    case "$arch" in
        arm64|aarch64)
            echo "- **Architecture**: ARM64 (Apple Silicon / ARM)"
            ;;
        x86_64|amd64)
            echo "- **Architecture**: x86_64 (Intel/AMD)"
            ;;
        *)
            echo "- **Architecture**: $arch"
            ;;
    esac

    # Docker provider detection from socket path
    if [[ -n "$docker_socket" ]]; then
        case "$docker_socket" in
            */.orbstack/*)
                echo "- **Docker Provider**: OrbStack (high performance, native macOS integration)"
                ;;
            */.docker/run/*)
                echo "- **Docker Provider**: Docker Desktop"
                ;;
            */.colima/*)
                echo "- **Docker Provider**: Colima"
                ;;
            */.rd/*)
                echo "- **Docker Provider**: Rancher Desktop"
                ;;
            /var/run/docker.sock)
                echo "- **Docker Provider**: Docker Engine (native Linux)"
                ;;
            *)
                echo "- **Docker Provider**: Custom Docker setup"
                ;;
        esac
    fi

    cat <<'EOF'

## Available Capabilities
EOF

    # Docker access
    if [[ "$has_docker" == "true" ]]; then
        cat <<'EOF'
- **Docker Access**: You have access to the host's Docker daemon via mounted socket
  - Can build, run, and manage Docker containers
  - Can execute docker and docker-compose commands
  - All Docker operations affect the host Docker daemon
EOF
    fi

    # Network mode
    case "$network_mode" in
        host)
            cat <<'EOF'
- **Networking**: Host networking mode enabled
  - Direct access to `localhost:PORT` services on the host
  - Can communicate with other containers via localhost
  - Full network stack sharing with host
EOF
            ;;
        bridge)
            cat <<'EOF'
- **Networking**: Bridge networking mode (isolated network)
  - Cannot directly access `localhost` services
  - Use `host.docker.internal:PORT` to access host services
  - Container has isolated network namespace
EOF
            ;;
    esac

    # Git SSH authentication
    case "$git_auth_mode" in
        agent-forwarding)
            cat <<'EOF'
- **SSH Authentication**: Agent forwarding enabled
  - SSH keys available via forwarded agent (secure, keys never in container)
  - Can authenticate to GitHub, GitLab, and other SSH services
  - Private keys remain on host machine only
  - **Important**: Keys must be loaded in host's SSH agent (`ssh-add -l` to verify)
  - On macOS: Proxy container (`dclaude-ssh-proxy-*`) bridges permissions automatically
  - If git fails with auth errors, user needs to run `ssh-add ~/.ssh/id_ed25519` on host
  - Socket location in container: $SSH_AUTH_SOCK (typically /tmp/ssh-proxy/agent)
  - Known hosts pre-configured for: GitHub, GitLab, Bitbucket
EOF
            ;;
        key-mount)
            cat <<'EOF'
- **SSH Authentication**: SSH keys mounted (read-only)
  - Host's ~/.ssh directory mounted into container at /home/claude/.ssh
  - Can authenticate to GitHub, GitLab, and other SSH services
  - Keys are read-only, cannot be modified
  - SSH agent not required - keys read directly from filesystem
  - Use ssh-add inside container if agent needed
  - Note: Uses host's known_hosts file (not container's pre-configured one)
EOF
            ;;
        none)
            cat <<'EOF'
- **SSH Authentication**: Not configured
  - SSH keys not available in container
  - Cannot authenticate to remote Git repositories via SSH
  - Recommend using HTTPS authentication for Git operations
  - Or restart with DCLAUDE_GIT_AUTH=agent-forwarding or DCLAUDE_GIT_AUTH=key-mount
EOF
            ;;
    esac

    cat <<'EOF'

## Development Tools Available
- **Languages**: Node.js 20+, Python 3
- **Package Managers**: npm, pip, Homebrew/Linuxbrew
- **Tools**: git, gh (GitHub CLI), docker, docker-compose, aws (AWS CLI v2), curl, tmux, nano
- **Shell**: bash (your commands execute in bash shell)
EOF

    # AWS CLI context
    case "$aws_cli_mode" in
        mount)
            cat <<'EOF'
- **AWS CLI**: Configured — credentials mounted from host's `~/.aws` directory
  - All host AWS profiles, SSO config, and credentials are available
  - Changes to AWS config (e.g., `aws sso login`) are shared with host
EOF
            ;;
        volume)
            cat <<'EOF'
- **AWS CLI**: Configured — credentials stored in persistent Docker volume
  - AWS config is isolated from host and persists across container recreations
  - Run `aws configure` or `aws configure sso` to set up credentials
EOF
            ;;
        *)
            cat <<'EOF'
- **AWS CLI**: Installed but no credentials configured
  - User can set `DCLAUDE_AWS_CLI=mount` when launching dclaude to use host's `~/.aws` config
  - User can set `DCLAUDE_AWS_CLI=volume` when launching dclaude for isolated persistent config
EOF
            ;;
    esac

    cat <<'EOF'

## Git Configuration Requirements
**IMPORTANT**: Before performing any git operations (commit, push, etc.), you MUST:
1. Check if git is configured: `git config user.name` and `git config user.email`
2. If either is missing/empty, ASK the user for their git name and email
3. Configure git with: `git config --global user.name "User Name"` and `git config --global user.email "user@example.com"`
4. Never assume or make up git credentials - always ask the user first

## Important Notes
- File operations are performed on the host filesystem (not isolated)
- Changes persist after container exits (files are on host)
- System packages can be installed with apt-get, Homebrew, or npm
- Use relative paths when possible - absolute paths work due to path mirroring
- Git operations work normally - repository sees correct paths

When suggesting commands or file operations, you can treat this environment as if running natively on the host, with the benefits of containerization for tool isolation.
EOF
}

# Get SSH proxy container/volume names (includes namespace if set)
get_ssh_proxy_container_name() {
    if [[ -n "$NAMESPACE" ]]; then
        echo "dclaude-${NAMESPACE}-ssh-proxy-$(id -u)"
    else
        echo "dclaude-ssh-proxy-$(id -u)"
    fi
}

get_ssh_proxy_volume_name() {
    if [[ -n "$NAMESPACE" ]]; then
        echo "dclaude-${NAMESPACE}-ssh-proxy"
    else
        echo "dclaude-ssh-proxy"
    fi
}

# Handle SSH authentication based on DCLAUDE_GIT_AUTH mode
# Setup SSH proxy container for macOS
setup_ssh_proxy_container() {
    local proxy_container
    local proxy_volume
    proxy_container=$(get_ssh_proxy_container_name)
    proxy_volume=$(get_ssh_proxy_volume_name)

    # Check if proxy container already exists and is running
    if docker ps -q -f name="^${proxy_container}$" 2>/dev/null | grep -q .; then
        debug "SSH proxy container already running"
        return 0
    fi

    # Remove any stopped proxy container
    if docker ps -aq -f name="^${proxy_container}$" 2>/dev/null | grep -q .; then
        debug "Removing stopped SSH proxy container"
        docker rm -f "$proxy_container" >/dev/null 2>&1
    fi

    info "Starting SSH agent proxy container..."
    debug "Proxy container name: $proxy_container"
    debug "Bridging macOS SSH agent socket permissions"

    # Create the proxy container that runs socat as root
    # This container just bridges the permission gap and exits
    docker run -d \
        --name "$proxy_container" \
        -v "/run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock:ro" \
        -v "${proxy_volume}:/tmp/ssh-proxy" \
        --rm \
        alpine:3.19 sh -c '
            # Install socat
            apk add --no-cache socat >/dev/null 2>&1

            # Create proxy socket accessible to all users
            rm -f /tmp/ssh-proxy/agent
            socat UNIX-LISTEN:/tmp/ssh-proxy/agent,fork,mode=660 \
                  UNIX-CONNECT:/run/host-services/ssh-auth.sock
        ' >/dev/null 2>&1

    # Give it a moment to start
    sleep 0.5

    # Verify the proxy is working
    if ! docker ps -q -f name="^${proxy_container}$" 2>/dev/null | grep -q .; then
        error "Failed to start SSH proxy container"
        return 1
    fi

    debug "SSH proxy container started successfully"
    return 0
}

handle_git_auth() {
    local docker_args=()
    local git_auth_mode="${GIT_AUTH_MODE}"

    # Auto-detect best method if set to auto
    if [[ "$git_auth_mode" == "auto" ]]; then
        if [[ -n "${SSH_AUTH_SOCK:-}" ]] && [[ -S "${SSH_AUTH_SOCK}" ]]; then
            git_auth_mode="agent-forwarding"
            debug "Git auth: agent-forwarding (auto-detected active agent)"
        elif [[ -d "${HOME}/.ssh" ]] && [[ -r "${HOME}/.ssh" ]]; then
            git_auth_mode="key-mount"
            debug "Git auth: key-mount (auto-detected SSH directory)"
        else
            git_auth_mode="none"
            debug "Git auth: none (no SSH agent or keys found)"
        fi
    fi

    case "$git_auth_mode" in
        agent-forwarding)
            if [[ -z "${SSH_AUTH_SOCK:-}" ]]; then
                warning "SSH agent forwarding requested but SSH_AUTH_SOCK not set"
                warning "Start SSH agent with: eval \$(ssh-agent) && ssh-add"
                return 1
            fi

            if [[ ! -S "${SSH_AUTH_SOCK}" ]]; then
                warning "SSH agent forwarding requested but socket not found: ${SSH_AUTH_SOCK}"
                return 1
            fi

            # Platform-specific socket mounting
            local platform=$(detect_platform)
            debug "Setting up SSH agent forwarding for platform: $platform"
            case "$platform" in
                linux)
                    debug "Using direct socket mount: ${SSH_AUTH_SOCK}"
                    docker_args+=(-v "${SSH_AUTH_SOCK}:/tmp/ssh-agent" -e "SSH_AUTH_SOCK=/tmp/ssh-agent")
                    info "SSH agent forwarding enabled (Linux)"
                    ;;
                darwin)
                    # On macOS, we need to set up a proxy container first
                    debug "Setting up SSH proxy container for macOS"
                    setup_ssh_proxy_container

                    # Now mount the proxied socket from the shared volume
                    local ssh_proxy_vol
                    ssh_proxy_vol=$(get_ssh_proxy_volume_name)
                    docker_args+=(-v "${ssh_proxy_vol}:/tmp/ssh-proxy:ro"
                                 -e "SSH_AUTH_SOCK=/tmp/ssh-proxy/agent")
                    info "SSH agent forwarding enabled via proxy container"
                    debug "Mounted SSH proxy volume: ${ssh_proxy_vol}:/tmp/ssh-proxy"
                    ;;
                windows)
                    warning "SSH agent forwarding not fully supported on Windows"
                    warning "Consider using key-mount mode instead: DCLAUDE_GIT_AUTH=key-mount"
                    return 1
                    ;;
            esac
            ;;

        key-mount)
            if [[ -d "${HOME}/.ssh" ]] && [[ -r "${HOME}/.ssh" ]]; then
                docker_args+=(-v "${HOME}/.ssh:/home/claude/.ssh:ro")
                info "SSH key mounting enabled (read-only)"
                debug "Mounting SSH directory: ${HOME}/.ssh"
                warning "SSH private keys are accessible in container (read-only)"
            else
                warning "SSH key mount requested but ~/.ssh not found or not readable"
                return 1
            fi
            ;;

        none)
            debug "SSH authentication disabled"
            ;;

        *)
            error "Invalid git auth mode: $git_auth_mode (valid: auto, agent-forwarding, key-mount, none)"
            return 1
            ;;
    esac

    # Print arguments separated by null characters for safe parsing (only if we have args)
    if [[ ${#docker_args[@]} -gt 0 ]]; then
        printf '%s\0' "${docker_args[@]}"
    fi
}

# ============================================================================
# Agent Teams Tmux Passthrough — Relay Functions
# ============================================================================
# When the user runs dclaude from inside a host tmux session, these functions
# set up a TCP relay that forwards Claude Code's tmux commands to the host.
# This makes Agent Teams sub-agent panes visible in the host's tmux session.

# Check if user is in a host tmux session
detect_host_tmux() {
    [[ -n "${TMUX:-}" ]]
}

# Check if host has required tools for relay (jq + socat or ncat)
check_relay_deps() {
    if ! command -v jq &>/dev/null; then
        debug "Relay dep missing: jq"
        return 1
    fi
    if command -v socat &>/dev/null; then
        debug "Relay listener: socat"
        return 0
    fi
    if command -v ncat &>/dev/null; then
        debug "Relay listener: ncat"
        return 0
    fi
    debug "Relay dep missing: socat or ncat"
    return 1
}

# Get the address the container uses to reach the host relay
get_relay_host() {
    local platform="$1"
    local network_mode="$2"
    # Linux host mode: container shares host network, use loopback
    if [[ "$platform" == "linux" && "$network_mode" == "host" ]]; then
        echo "127.0.0.1"
    else
        # macOS (any mode) or Linux bridge: use Docker's host gateway
        echo "host.docker.internal"
    fi
}

# Get the address the relay binds to on the host
get_relay_bind_addr() {
    local platform="$1"
    local network_mode="$2"
    # Linux bridge: container reaches host via bridge gateway, not loopback
    if [[ "$platform" == "linux" && "$network_mode" == "bridge" ]]; then
        echo "0.0.0.0"
    else
        echo "127.0.0.1"
    fi
}

# Get or create relay nonce for a container (reused across reattachments)
get_or_create_relay_nonce() {
    local container_name="$1"
    local nonce_file="$HOME/.dclaude/tmux-relay-nonce-${container_name}"

    mkdir -p "$HOME/.dclaude"

    if [[ -f "$nonce_file" ]]; then
        cat "$nonce_file"
        return 0
    fi

    local nonce
    nonce=$(head -c 32 /dev/urandom | base64 | tr -d '=/+' | head -c 32)
    echo "$nonce" > "$nonce_file"
    echo "$nonce"
}

# Generate the relay handler script (executed by socat/ncat for each connection)
# Uses a non-quoted heredoc for baked-in values, then a single-quoted heredoc for logic
generate_relay_handler() {
    local container_name="$1"
    local relay_port="$2"
    local nonce="$3"
    local handler_path="$4"
    local tracking_file="$5"
    local relay_host="$6"

    # Header with baked-in values (variable expansion)
    cat > "$handler_path" <<HANDLER_HEADER
#!/bin/bash
CONTAINER="$container_name"
NONCE="$nonce"
RELAY_PORT="$relay_port"
RELAY_HOST="$relay_host"
TRACKING_FILE="$tracking_file"
HANDLER_HEADER

    # Logic body (single-quoted heredoc — no escaping needed)
    cat >> "$handler_path" <<'HANDLER_BODY'

# Read one JSON line (max 65536 bytes to prevent memory exhaustion)
read -r -n 65536 request
if [[ -z "$request" ]]; then
    printf '{"code":1,"stdout":"","stderr":"empty request"}\n'
    exit 1
fi

# Validate nonce
req_nonce=$(printf '%s' "$request" | jq -r '.nonce // empty')
if [[ "$req_nonce" != "$NONCE" ]]; then
    printf '{"code":1,"stdout":"","stderr":"authentication failed"}\n'
    exit 1
fi

# Extract command array (bash 3.2 compatible — no mapfile)
cmd=()
while IFS= read -r line; do
    cmd+=("$line")
done < <(printf '%s' "$request" | jq -r '.cmd[]')
subcmd="${cmd[0]}"
cwd=$(printf '%s' "$request" | jq -r '.cwd // empty')
req_pane=$(printf '%s' "$request" | jq -r '.pane // empty')

# Allowlist tmux subcommands
case "$subcmd" in
    split-window|send-keys|list-panes|select-layout|resize-pane|\
    select-pane|kill-pane|has-session|list-windows|display-message|list-sessions)
        ;;
    *)
        printf '{"code":1,"stdout":"","stderr":"disallowed: %s"}\n' "$subcmd"
        exit 1
        ;;
esac

# Build tmux args based on subcommand
args=()

if [[ "$subcmd" == "split-window" ]]; then
    args+=("split-window")

    # Parse split-window flags, strip -c (handled by docker exec -w)
    # and strip trailing shell command (replaced by docker exec)
    i=1
    while (( i < ${#cmd[@]} )); do
        case "${cmd[$i]}" in
            -b|-d|-f|-h|-I|-v|-P|-Z)
                args+=("${cmd[$i]}")
                ;;
            -c)
                # Capture directory, use for docker exec -w
                (( i++ ))
                cwd="${cmd[$i]}"
                ;;
            -e|-l|-t|-F)
                # Flags with a value argument
                args+=("${cmd[$i]}")
                (( i++ ))
                args+=("${cmd[$i]}")
                ;;
            -*)
                # Unknown flag, pass through
                args+=("${cmd[$i]}")
                ;;
            *)
                # Shell command — skip (replaced by docker exec)
                break
                ;;
        esac
        (( i++ ))
    done

    # Validate cwd: absolute path, safe characters only
    if [[ -n "$cwd" && ! "$cwd" =~ ^[a-zA-Z0-9/._-]+$ ]]; then
        printf '{"code":1,"stdout":"","stderr":"invalid cwd"}\n'
        exit 1
    fi

    # Append docker exec as the pane command (no sh -c, args as array)
    args+=(
        "--"
        "docker" "exec" "-it"
        "-e" "DCLAUDE_TMUX_RELAY_PORT=$RELAY_PORT"
        "-e" "DCLAUDE_TMUX_RELAY_HOST=$RELAY_HOST"
        "-e" "DCLAUDE_TMUX_RELAY_NONCE=$NONCE"
        "-e" "DCLAUDE_CONTAINER=$CONTAINER"
        "-u" "claude"
        "-w" "${cwd:-/home/claude}"
        "$CONTAINER"
        "bash"
    )

elif [[ "$subcmd" == "send-keys" ]]; then
    args+=("send-keys")

    # Validate target pane is one we created
    for (( i=1; i < ${#cmd[@]}; i++ )); do
        if [[ "${cmd[$i]}" == "-t" && $(( i + 1 )) -lt ${#cmd[@]} ]]; then
            target="${cmd[$(( i + 1 ))]}"
            if [[ -f "$TRACKING_FILE" ]] && ! grep -qxF "$target" "$TRACKING_FILE"; then
                printf '{"code":1,"stdout":"","stderr":"pane not tracked: %s"}\n' "$target"
                exit 1
            fi
            break
        fi
    done

    # Pass through all args
    for (( i=1; i < ${#cmd[@]}; i++ )); do
        args+=("${cmd[$i]}")
    done

elif [[ "$subcmd" == "kill-pane" ]]; then
    args+=("kill-pane")
    for (( i=1; i < ${#cmd[@]}; i++ )); do
        args+=("${cmd[$i]}")
    done
else
    # All other allowed commands: pass through args
    # Inject -t $req_pane for commands that need pane context
    # (relay runs outside tmux pane context, so tmux needs explicit targeting)
    args+=("$subcmd")
    has_target=false
    for (( i=1; i < ${#cmd[@]}; i++ )); do
        [[ "${cmd[$i]}" == "-t" ]] && has_target=true
        [[ "${cmd[$i]}" =~ ^-t.+ ]] && has_target=true
    done
    # Only inject -t for commands that accept it (not list-sessions, has-session)
    case "$subcmd" in
        display-message|list-panes|list-windows|select-layout|resize-pane|select-pane)
            if [[ "$has_target" == "false" && -n "$req_pane" ]]; then
                args+=("-t" "$req_pane")
            fi
            ;;
    esac
    for (( i=1; i < ${#cmd[@]}; i++ )); do
        args+=("${cmd[$i]}")
    done
fi

# Execute tmux command, capture output
stdout_file=$(mktemp)
stderr_file=$(mktemp)
tmux "${args[@]}" >"$stdout_file" 2>"$stderr_file"
code=$?
stdout=$(cat "$stdout_file")
stderr=$(cat "$stderr_file")
rm -f "$stdout_file" "$stderr_file"

# Post-processing for split-window: track new pane ID
if [[ "$subcmd" == "split-window" && $code -eq 0 ]]; then
    pane_id=$(echo "$stdout" | head -1 | tr -d '[:space:]')
    if [[ "$pane_id" =~ ^%[0-9]+$ ]]; then
        echo "$pane_id" >> "$TRACKING_FILE"
    fi
fi

# Post-processing for kill-pane: clean up orphaned processes after delay
if [[ "$subcmd" == "kill-pane" && $code -eq 0 ]]; then
    (
        sleep 2
        docker exec "$CONTAINER" bash -c '
            ps -eo pid,ppid,tty,comm --no-headers 2>/dev/null | while read pid ppid tty comm; do
                [[ "$ppid" == "0" && "$comm" == "bash" ]] || continue
                [[ "$tty" =~ ^pts/([0-9]+)$ ]] || continue
                (( ${BASH_REMATCH[1]} >= 2 )) || continue
                children=$(ps --ppid "$pid" --no-headers 2>/dev/null | wc -l)
                if [[ "$children" -eq 0 ]]; then
                    kill -9 "$pid" 2>/dev/null
                fi
            done
        '
    ) &>/dev/null &
fi

# Return JSON response
jq -nc --arg code "$code" --arg stdout "$stdout" --arg stderr "$stderr" \
    '{code: ($code | tonumber), stdout: $stdout, stderr: $stderr}'
HANDLER_BODY
}

# Start the relay listener on the host
start_relay() {
    local container_name="$1"
    local relay_port="$2"
    local nonce="$3"
    local bind_addr="$4"
    local relay_host="$5"

    local handler_path="$HOME/.dclaude/tmux-relay-handler-${container_name}.sh"
    local pid_file="$HOME/.dclaude/tmux-relay-pid-${container_name}"
    local tracking_file="$HOME/.dclaude/tmux-relay-panes-${container_name}"

    mkdir -p "$HOME/.dclaude"

    # Generate handler script
    generate_relay_handler "$container_name" "$relay_port" "$nonce" \
        "$handler_path" "$tracking_file" "$relay_host"
    chmod +x "$handler_path"

    # Clear pane tracking
    > "$tracking_file"

    # Start listener
    if command -v socat &>/dev/null; then
        debug "Starting socat relay on $bind_addr:$relay_port"
        socat "TCP-LISTEN:$relay_port,bind=$bind_addr,reuseaddr,fork,max-children=10" \
            "EXEC:bash $handler_path" 2>/dev/null &
    elif command -v ncat &>/dev/null; then
        debug "Starting ncat relay on $bind_addr:$relay_port"
        ncat -l -k "$bind_addr" "$relay_port" --sh-exec "$handler_path" &
    else
        error "No relay listener available (need socat or ncat)"
        return 1
    fi

    echo $! > "$pid_file"
    debug "Relay PID: $(cat "$pid_file")"
}

# Stop the relay and clean up
stop_relay() {
    local container_name="$1"

    local pid_file="$HOME/.dclaude/tmux-relay-pid-${container_name}"
    local handler_path="$HOME/.dclaude/tmux-relay-handler-${container_name}.sh"
    local tracking_file="$HOME/.dclaude/tmux-relay-panes-${container_name}"

    if [[ -f "$pid_file" ]]; then
        local pid
        pid=$(cat "$pid_file")
        if [[ -n "$pid" ]]; then
            # Kill forked handler processes first
            pkill -P "$pid" 2>/dev/null || true
            # Verify process identity before killing (guard against PID recycling)
            local comm
            comm=$(ps -p "$pid" -o comm= 2>/dev/null || true)
            if [[ "$comm" =~ (socat|ncat) ]]; then
                kill "$pid" 2>/dev/null || true
            fi
        fi
    fi

    # Clean up orphaned bash processes in container
    cleanup_container_orphans "$container_name"

    # Remove relay files (but keep nonce for reattachment)
    rm -f "$pid_file" "$handler_path" "$tracking_file"
}

# Kill orphaned bash sessions in container (from dead docker exec panes)
cleanup_container_orphans() {
    local container_name="$1"

    # Check container is running before trying
    if ! docker ps -q -f name="^${container_name}$" 2>/dev/null | grep -q .; then
        return 0
    fi

    docker exec "$container_name" bash -c '
        ps -eo pid,ppid,tty,comm --no-headers 2>/dev/null | while read pid ppid tty comm; do
            [[ "$ppid" == "0" && "$comm" == "bash" ]] || continue
            [[ "$tty" =~ ^pts/([0-9]+)$ ]] || continue
            (( ${BASH_REMATCH[1]} >= 2 )) || continue
            children=$(ps --ppid "$pid" --no-headers 2>/dev/null | wc -l)
            if [[ "$children" -eq 0 ]]; then
                kill -9 "$pid" 2>/dev/null
            fi
        done
    ' 2>/dev/null || true
}

# Set up Agent Teams relay if conditions are met
# Sets globals: RELAY_STARTED, TMUX_SESSION_ENV_ARGS
setup_agent_teams_relay() {
    local container_name="$1"
    local relay_port="$2"
    local platform="$3"
    local network_mode="$4"

    RELAY_STARTED=false
    TMUX_SESSION_ENV_ARGS=()

    if [[ -z "$relay_port" ]]; then
        debug "No relay port configured for this container"
        TMUX_SESSION_ENV_ARGS=(-e "DCLAUDE_HIDE_TMUX=1")
        return
    fi

    if ! detect_host_tmux; then
        debug "Not in host tmux - Agent Teams will use in-process mode"
        TMUX_SESSION_ENV_ARGS=(-e "DCLAUDE_HIDE_TMUX=1")
        return
    fi

    if ! check_relay_deps; then
        info "Tip: Install jq + socat (or ncat) for Agent Teams pane mode in tmux"
        TMUX_SESSION_ENV_ARGS=(-e "DCLAUDE_HIDE_TMUX=1")
        return
    fi

    local bind_addr relay_host relay_nonce host_pane
    bind_addr=$(get_relay_bind_addr "$platform" "$network_mode")
    relay_host=$(get_relay_host "$platform" "$network_mode")
    relay_nonce=$(get_or_create_relay_nonce "$container_name")
    host_pane=$(tmux display-message -p '#{pane_id}')

    start_relay "$container_name" "$relay_port" "$relay_nonce" "$bind_addr" "$relay_host"
    RELAY_STARTED=true

    TMUX_SESSION_ENV_ARGS=(
        -e "DCLAUDE_TMUX_RELAY_PORT=$relay_port"
        -e "DCLAUDE_TMUX_RELAY_HOST=$relay_host"
        -e "DCLAUDE_TMUX_RELAY_NONCE=$relay_nonce"
        -e "DCLAUDE_HOST_PANE=$host_pane"
        -e "DCLAUDE_CONTAINER=$container_name"
    )

    debug "Agent Teams relay: $bind_addr:$relay_port -> $container_name (pane $host_pane)"
}
# ============================================================================

# Detect TTY availability and return appropriate Docker flags
# Detect TTY status early (before any subshells)
# Must be done at script top-level since $() subshells don't inherit TTY
STDIN_IS_TTY=false
STDOUT_IS_TTY=false
[[ -t 0 ]] && STDIN_IS_TTY=true
[[ -t 1 ]] && STDOUT_IS_TTY=true

detect_tty_flags() {
    local tty_flags=""

    # Use pre-detected TTY status (can't detect inside subshell)
    if [[ "$STDIN_IS_TTY" == "true" ]]; then
        tty_flags="-i"
    fi

    if [[ "$STDOUT_IS_TTY" == "true" ]]; then
        tty_flags="${tty_flags} -t"
    fi

    # Trim whitespace
    tty_flags=$(echo $tty_flags | xargs)

    if [[ "$DEBUG" == "true" ]]; then
        debug "TTY detection: stdin=$STDIN_IS_TTY, stdout=$STDOUT_IS_TTY"
        debug "TTY flags: ${tty_flags:-none}"
    fi

    echo "$tty_flags"
}

# Check if Claude is being run in a mode that should skip tmux
# These are flags that just output something and exit (no interactive session)
should_skip_tmux() {
    for arg in "$@"; do
        case "$arg" in
            -p|--print|--version|-v|--help|-h)
                return 0
                ;;
        esac
    done
    return 1
}

# Debug countdown before launching Claude (gives time to read debug output)
# Press Enter to skip the countdown and launch immediately
debug_countdown() {
    if [[ "$DEBUG" == "true" ]]; then
        for i in 5 4 3 2 1; do
            printf '\r%b' "${CYAN}Debug: Launching in ${i}... (press Enter to skip)${NC}" >&2
            read -t 1 -s 2>/dev/null && break
        done
        printf '\r%b\n' "${CYAN}Debug: Launching...                               ${NC}" >&2
    fi
}

# Main execution
main() {
    # No argument parsing - pass everything through to Claude
    # All arguments are preserved as-is for Claude

    info "Verifying environment..."
    debug "Host path: $HOST_PATH"
    if [[ "$MOUNT_ROOT" != "$HOST_PATH" ]]; then
        debug "Mount root: $MOUNT_ROOT"
    fi

    # Check prerequisites
    check_docker
    check_docker_running

    # Setup environment
    create_volumes
    update_image

    # Platform-specific settings
    local platform
    platform=$(detect_platform)
    debug "Platform detected: $platform"

    # Set network mode based on auto-detection or user preference
    local network_mode="${DCLAUDE_NETWORK:-auto}"
    local detection_source="default"

    if [[ "$network_mode" == "auto" || -z "$network_mode" ]]; then
        # Auto-detect network capability
        network_mode=$(detect_network_capability)
        detection_source="auto-detected"
        debug "Auto-detected network mode: $network_mode"

        # Show warning if using bridge mode
        if [[ "$network_mode" == "bridge" ]]; then
            warning "Using bridge networking mode on $platform"
            info "Bridge mode limitations:"
            info "  - Cannot access services on localhost (use host.docker.internal instead)"
            info "  - Cannot access other containers via localhost"
            info "  - Port mapping required for container services"
            info ""
            info "For better localhost access, consider:"
            info "  - macOS: Enable host networking in Docker Desktop (beta) or use OrbStack"
            info "  - Windows: Enable host networking in Docker Desktop (beta feature)"
        else
            debug "Host networking available - full localhost access enabled"
        fi
    elif [[ "$network_mode" =~ ^(host|bridge)$ ]]; then
        detection_source="user-specified"
        debug "Using user-specified network mode: $network_mode"
    else
        warning "Invalid network mode '$network_mode'. Valid options: host, bridge, auto. Falling back to auto-detection"
        network_mode=$(detect_network_capability)
        detection_source="fallback auto-detected"
        debug "Fallback auto-detected network mode: $network_mode"
    fi
    debug "Network mode: $network_mode ($detection_source)"

    # Enhanced debug output for network configuration
    if [[ "$DEBUG" == "true" ]]; then
        debug "Network configuration summary:"
        debug "  - Platform: $platform"
        debug "  - Network mode: $network_mode"
        debug "  - Detection source: $detection_source"
        debug "  - DCLAUDE_NETWORK environment: ${DCLAUDE_NETWORK:-<not set>}"
        if [[ "$detection_source" =~ auto-detected ]]; then
            debug "  - Auto-detection performed: yes"
            if [[ -f "${HOME}/.dclaude/network-mode" ]]; then
                local cache_age
                if cache_age=$(stat -c %Y "${HOME}/.dclaude/network-mode" 2>/dev/null) || cache_age=$(stat -f %m "${HOME}/.dclaude/network-mode" 2>/dev/null); then
                    local current_time
                    current_time=$(date +%s)
                    local age_hours=$(( (current_time - cache_age) / 3600 ))
                    debug "  - Cache used: yes (age: ${age_hours}h)"
                else
                    debug "  - Cache used: yes (age: unknown)"
                fi
            else
                debug "  - Cache used: no"
            fi
        else
            debug "  - Auto-detection performed: no"
        fi
    fi

    # Detect TTY availability
    local tty_flags=$(detect_tty_flags)

    # Detect Docker socket early (needed for system context generation)
    if [[ -z "$DOCKER_SOCKET" ]]; then
        DOCKER_SOCKET=$(detect_docker_socket) || true
    fi
    debug "Docker socket detection: ${DOCKER_SOCKET:-<not found>}"

    # Resolve git auth mode from 'auto' to actual mode (needed for system context)
    local resolved_git_auth="$GIT_AUTH_MODE"
    if [[ "$GIT_AUTH_MODE" == "auto" ]]; then
        # Same auto-detection logic as handle_git_auth()
        if [[ -n "${SSH_AUTH_SOCK:-}" ]] && [[ -S "${SSH_AUTH_SOCK}" ]]; then
            resolved_git_auth="agent-forwarding"
            debug "Git auth resolved: agent-forwarding (active agent detected)"
        elif [[ -d "${HOME}/.ssh" ]] && [[ -r "${HOME}/.ssh" ]]; then
            resolved_git_auth="key-mount"
            debug "Git auth resolved: key-mount (SSH directory detected)"
        else
            resolved_git_auth="none"
            debug "Git auth resolved: none (no SSH agent or keys found)"
        fi
    else
        debug "Git auth: $resolved_git_auth (user-specified)"
    fi

    # Warn if agent-forwarding is active but no keys are loaded
    if [[ "$resolved_git_auth" == "agent-forwarding" ]]; then
        local ssh_add_rc=0
        ssh-add -l >/dev/null 2>&1 || ssh_add_rc=$?
        if [[ $ssh_add_rc -eq 1 ]]; then
            warning "SSH agent has no keys loaded — SSH won't work inside container."
            warning "Run 'dclaude ssh keys' to load your keys."
        fi
    fi

    # Generate system context for Claude (if enabled) - must be done early for all code paths
    local claude_args=()
    if [[ "$ENABLE_SYSTEM_CONTEXT" == "true" ]]; then
        local has_docker="false"
        [[ -n "$DOCKER_SOCKET" ]] && [[ -S "$DOCKER_SOCKET" ]] && has_docker="true"

        local resolved_aws_cli_mode
        resolved_aws_cli_mode=$(resolve_aws_cli_mode)

        local system_context
        system_context=$(generate_system_context "$network_mode" "$resolved_git_auth" "$has_docker" "$platform" "$DOCKER_SOCKET" "$resolved_aws_cli_mode")

        claude_args+=("--append-system-prompt" "$system_context")
        debug "System context enabled (${#system_context} chars, has_docker=$has_docker, git_auth=$resolved_git_auth, platform=$platform)"
        if [[ "$DEBUG" == "true" ]]; then
            debug "System context preview: ${system_context:0:100}..."
        fi
    else
        debug "System context disabled (DCLAUDE_SYSTEM_CONTEXT=$ENABLE_SYSTEM_CONTEXT)"
    fi

    # Final environment summary (all variables now resolved)
    if [[ "$DEBUG" == "true" ]]; then
        debug "Resolved environment:"
        debug "  NAMESPACE=${NAMESPACE:-<not set>}"
        debug "  NETWORK_MODE=$network_mode"
        debug "  GIT_AUTH=$resolved_git_auth"
        debug "  DOCKER_SOCKET=${DOCKER_SOCKET:-<not found>}"
        debug "  CHROME_PORT=${CHROME_PORT:-9222}"
        debug "  SYSTEM_CONTEXT=$ENABLE_SYSTEM_CONTEXT"
        debug "  IMAGE=$IMAGE"
        debug "  HOST_PATH=$HOST_PATH"
        if [[ "$MOUNT_ROOT" != "$HOST_PATH" ]]; then
            debug "  MOUNT_ROOT=$MOUNT_ROOT"
        fi
        debug "  PLATFORM=$platform"
    fi

    # Generate container name based on path (for reuse when DCLAUDE_RM=false)
    local container_name=""
    if [[ "$REMOVE_CONTAINER" == "false" ]]; then
        # Create deterministic name from path hash
        container_name=$(get_container_name "$HOST_PATH")
        if [[ -n "$NAMESPACE" ]]; then
            debug "Container name: $container_name (namespace: $NAMESPACE, path: $HOST_PATH)"
        else
            debug "Container name: $container_name (path: $HOST_PATH)"
        fi

        # Check if container already exists
        if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
            local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
            debug "Found existing container: $container_name (status: $container_status)"

            if [[ "$container_status" == "running" ]]; then
                info "Attaching to running container: $container_name"
            elif [[ "$container_status" == "exited" ]]; then
                info "Restarting existing container: $container_name"
                docker start "$container_name" >/dev/null

                # Wait for container to be running
                local wait_count=0
                while [[ $wait_count -lt 30 ]]; do
                    if docker ps -q -f name="^${container_name}$" 2>/dev/null | grep -q .; then
                        debug "Container restarted successfully"
                        break
                    fi
                    sleep 0.1
                    ((wait_count++))
                done
            fi

            # Ensure SSH proxy is running if container uses agent forwarding (macOS)
            if [[ "$platform" == "darwin" ]]; then
                local container_ssh_sock
                container_ssh_sock=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' "$container_name" 2>/dev/null | grep "^SSH_AUTH_SOCK=" | cut -d= -f2)
                if [[ "$container_ssh_sock" == "/tmp/ssh-proxy/agent" ]]; then
                    debug "Container uses SSH agent forwarding, ensuring proxy is running"
                    setup_ssh_proxy_container
                fi
            fi

            # Check if interactive (TTY available) and not in print mode
            if [[ -n "$tty_flags" ]] && ! should_skip_tmux "${claude_args[@]}" "$@"; then
                # Interactive and not print mode - use tmux for session management
                local tmux_session
                if [[ -n "${DCLAUDE_TMUX_SESSION:-}" ]]; then
                    tmux_session="$DCLAUDE_TMUX_SESSION"
                    debug "Using custom tmux session name: $tmux_session"
                else
                    tmux_session="claude-$(date +%Y%m%d-%H%M%S)"
                    debug "Generated unique tmux session name: $tmux_session"
                fi

                # Build env args for docker exec to pass terminal info to tmux session
                local exec_env_args=()
                [[ -n "${TERM_PROGRAM:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM=${TERM_PROGRAM}")
                [[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
                [[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
                [[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
                [[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")

                # Set up Agent Teams relay (or hide $TMUX for in-process mode)
                local relay_port
                relay_port=$(docker inspect --format='{{index .Config.Labels "dclaude.relay.port"}}' "$container_name" 2>/dev/null || true)
                setup_agent_teams_relay "$container_name" "$relay_port" "$platform" "$network_mode"
                if [[ "$RELAY_STARTED" == "true" ]]; then
                    trap "stop_relay '$container_name'" EXIT
                fi

                debug "Creating new tmux session running Claude"
                debug "Claude args count: ${#claude_args[@]}, user args: $*"
                info "Starting new Claude session..."
                debug_countdown
                docker exec -it -u claude "${exec_env_args[@]}" "$container_name" \
                    /usr/bin/tmux -L dclaude-inner -f /home/claude/.tmux.conf \
                    new-session -s "$tmux_session" \
                    "${TMUX_SESSION_ENV_ARGS[@]}" \
                    claude-launcher.sh "${claude_args[@]}" "$@"
                exit $?
            else
                # Non-interactive or print mode - run claude directly without tmux
                debug "Non-interactive or print mode, running Claude directly (no tmux)"
                debug "Claude args count: ${#claude_args[@]}, user args: $*"
                exec docker exec -u claude -w "$HOST_PATH" "$container_name" claude "${claude_args[@]}" "$@"
            fi
        fi
    fi

    # Prepare Docker run arguments
    DOCKER_ARGS=(
        "run"
    )

    # Add --rm flag if enabled (default: true)
    if [[ "$REMOVE_CONTAINER" == "true" ]]; then
        DOCKER_ARGS+=("--rm")
    else
        # Use named container for reuse
        DOCKER_ARGS+=("--name" "$container_name")
    fi

    # TTY flags will be added only when needed (ephemeral containers)
    # Persistent containers run in background and shouldn't have TTY/stdin attached


    # Get namespaced volume name
    local claude_volume
    claude_volume=$(get_volume_name)

    DOCKER_ARGS+=(
        # Mount directory tree (mount root allows access to parent directories)
        -v "${MOUNT_ROOT}:${MOUNT_ROOT}"
        # Mount persistent Claude configuration volume
        -v "${claude_volume}:/home/claude/.claude"
        # Set working directory (within the mounted tree)
        -w "${HOST_PATH}"
        # Network mode
        --network="$network_mode"
        # Environment - pass through terminal identification for proper feature detection
        -e "TERM=${TERM:-xterm-256color}"
    )

    # Reserve SSH port for remote access (JetBrains Gateway, VS Code Remote, etc.)
    local ssh_port
    ssh_port=$(find_available_port 2222 65000)
    debug "SSH port reserved: $ssh_port"
    DOCKER_ARGS+=(--label "dclaude.ssh.port=${ssh_port}")

    # Reserve relay port for Agent Teams tmux passthrough
    local relay_port
    relay_port=$(find_available_port 30000 60000)
    debug "Relay port reserved: $relay_port"
    DOCKER_ARGS+=(--label "dclaude.relay.port=${relay_port}")

    # Add host.docker.internal for Linux bridge mode (needed for relay connectivity)
    if [[ "$platform" == "linux" && "$network_mode" == "bridge" ]]; then
        DOCKER_ARGS+=(--add-host "host.docker.internal:host-gateway")
    fi

    # Port mapping only needed for bridge mode (host mode shares network stack)
    if [[ "$network_mode" != "host" ]]; then
        DOCKER_ARGS+=(-p "${ssh_port}:${ssh_port}")
        debug "SSH port mapping: ${ssh_port}:${ssh_port} (bridge mode)"
    else
        debug "SSH port: ${ssh_port} (host mode, no mapping needed)"
    fi

    # Pass through terminal program information for proper logo rendering
    if [[ -n "${TERM_PROGRAM:-}" ]]; then
        DOCKER_ARGS+=(-e "TERM_PROGRAM=${TERM_PROGRAM}")
    fi
    if [[ -n "${TERM_PROGRAM_VERSION:-}" ]]; then
        DOCKER_ARGS+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
    fi
    if [[ -n "${TERM_SESSION_ID:-}" ]]; then
        DOCKER_ARGS+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
    fi
    if [[ -n "${COLORTERM:-}" ]]; then
        DOCKER_ARGS+=(-e "COLORTERM=${COLORTERM}")
    fi

    # Pass through iTerm2 integration opt-out if set
    if [[ -n "${DCLAUDE_ITERM2:-}" ]]; then
        DOCKER_ARGS+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")
    fi

    # Mount Docker socket if detected (detection done earlier for system context)
    if [[ -n "$DOCKER_SOCKET" ]] && [[ -S "$DOCKER_SOCKET" ]]; then
        DOCKER_ARGS+=(-v "${DOCKER_SOCKET}:/var/run/docker.sock")
        debug "Docker socket mounted: $DOCKER_SOCKET"
    else
        warning "Docker socket not found"
        warning "Container will not have Docker access"
        debug "Searched standard locations and Docker context"
    fi

    # Handle SSH authentication (agent forwarding or key mounting)
    # Use process substitution to preserve null bytes
    while IFS= read -r -d '' ssh_arg; do
        [[ -n "$ssh_arg" ]] && DOCKER_ARGS+=("$ssh_arg")
    done < <(handle_git_auth)

    # Handle AWS CLI configuration mounting
    local resolved_aws_mode
    resolved_aws_mode=$(resolve_aws_cli_mode)
    # Pass mode to entrypoint so it knows whether to chown .aws (volume only)
    DOCKER_ARGS+=(-e "DCLAUDE_AWS_CLI_MODE=${resolved_aws_mode}")
    case "$resolved_aws_mode" in
        mount)
            if [[ -d "${HOME}/.aws" ]]; then
                DOCKER_ARGS+=(-v "${HOME}/.aws:/home/claude/.aws")
                info "AWS CLI: mounting host ~/.aws"
                debug "AWS config mounted from host: ${HOME}/.aws"
            else
                warning "AWS_CLI=mount but ~/.aws not found, skipping"
            fi
            ;;
        volume)
            local aws_volume
            aws_volume=$(get_aws_volume_name)
            DOCKER_ARGS+=(-v "${aws_volume}:/home/claude/.aws")
            debug "AWS config using volume: $aws_volume"
            ;;
        none)
            debug "AWS CLI config mounting disabled"
            ;;
    esac

    # Handle CA certificate for corporate proxies / SSL inspection
    # The cert file must be within the mounted directory tree to be accessible inside the container
    if [[ -n "$CA_CERT" ]]; then
        if [[ ! -f "$CA_CERT" ]]; then
            warning "CA certificate not found: $CA_CERT"
        elif [[ "$CA_CERT" != "$MOUNT_ROOT"* ]]; then
            warning "CA certificate is outside the mounted directory: $CA_CERT"
            warning "It must be within: $MOUNT_ROOT"
        else
            DOCKER_ARGS+=(
                -e "NODE_EXTRA_CA_CERTS=${CA_CERT}"
                -e "SSL_CERT_FILE=${CA_CERT}"
            )
            info "CA certificate: $CA_CERT"
        fi
    fi

    # Add any additional environment variables
    if [[ -n "${CLAUDE_MODEL:-}" ]]; then
        DOCKER_ARGS+=(-e "CLAUDE_MODEL=${CLAUDE_MODEL}")
    fi

    # Control Claude Code's auto-updater; unset defers to its built-in default (enabled)
    if [[ -n "$DISABLE_AUTOUPDATER" ]]; then
        DOCKER_ARGS+=(-e "DISABLE_AUTOUPDATER=${DISABLE_AUTOUPDATER}")
    fi

    if [[ "$DEBUG" == "true" ]]; then
        debug "Container removal: $REMOVE_CONTAINER (DCLAUDE_RM=${DCLAUDE_RM:-<not set>})"
        debug "Docker command: docker ${DOCKER_ARGS[*]} $IMAGE $*"
    fi

    # Run Claude in Docker
    if [[ "$REMOVE_CONTAINER" == "false" ]]; then
        # For persistent containers, run tini with tail daemon for zombie reaping
        # tini will reap zombie processes created by exec commands
        debug "Starting persistent container with tini + tail daemon"

        # TODO: Add retry logic for SSH port allocation race condition
        # When multiple dclaude instances start simultaneously, they can both detect
        # the same SSH port as available before either binds to it. This causes the
        # second instance to fail with "port is already allocated" error.
        # Solution: If docker run fails with port conflict, re-detect port and retry (max 3 attempts)
        # See: SSH_TEST_RESULTS.md - Issue 1: Race Condition in Parallel Container Startup

        # Run in detached mode (-d) and capture output (Container ID) or errors
        local run_output
        if ! run_output=$(docker "${DOCKER_ARGS[@]}" -d --entrypoint /usr/bin/tini "$IMAGE" -- tail -f /dev/null 2>&1); then
            error "Failed to start container: $run_output"
            exit 1
        fi
        
        debug "Container started successfully: $run_output"

        # Run entrypoint setup as root (for Docker socket permissions, etc.)
        debug "Running entrypoint initialization"
        docker exec -u root "$container_name" /usr/local/bin/docker-entrypoint.sh true >/dev/null 2>&1 || true

        # Check if interactive (TTY available) and not in print mode
        if [[ -n "$tty_flags" ]] && ! should_skip_tmux "${claude_args[@]}" "$@"; then
            # Interactive and not print mode - use tmux for session management
            local tmux_session
            if [[ -n "${DCLAUDE_TMUX_SESSION:-}" ]]; then
                tmux_session="$DCLAUDE_TMUX_SESSION"
                debug "Using custom tmux session name: $tmux_session"
            else
                tmux_session="claude-$(date +%Y%m%d-%H%M%S)"
                debug "Generated unique tmux session name: $tmux_session"
            fi

            # Build env args for docker exec to pass terminal info to tmux session
            local exec_env_args=()
            [[ -n "${TERM_PROGRAM:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM=${TERM_PROGRAM}")
            [[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
            [[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
            [[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
            [[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")

            # Set up Agent Teams relay (or hide $TMUX for in-process mode)
            setup_agent_teams_relay "$container_name" "$relay_port" "$platform" "$network_mode"
            if [[ "$RELAY_STARTED" == "true" ]]; then
                trap "stop_relay '$container_name'" EXIT
            fi

            debug "Creating new tmux session running Claude"
            debug "Claude args count: ${#claude_args[@]}, user args: $*"
            info "Starting new Claude session..."
            debug_countdown
            docker exec -it -u claude "${exec_env_args[@]}" "$container_name" \
                /usr/bin/tmux -L dclaude-inner -f /home/claude/.tmux.conf \
                new-session -s "$tmux_session" \
                "${TMUX_SESSION_ENV_ARGS[@]}" \
                claude-launcher.sh "${claude_args[@]}" "$@"
            exit $?
        else
            # Non-interactive or print mode - run claude directly without tmux
            debug "Non-interactive or print mode, running Claude directly (no tmux)"
            debug "Claude args count: ${#claude_args[@]}, user args: $*"
            exec docker exec -u claude -w "$HOST_PATH" "$container_name" claude "${claude_args[@]}" "$@"
        fi
    else
        # Ephemeral container - run directly
        # Add TTY flags here as we are running interactively
        if [[ -n "$tty_flags" ]]; then
            DOCKER_ARGS+=($tty_flags)
        fi
        debug "Claude args count: ${#claude_args[@]}, user args: $*"
        exec docker "${DOCKER_ARGS[@]}" "$IMAGE" "${claude_args[@]}" "$@"
    fi
}



# Subcommand: exec into container
cmd_exec() {
    # Generate container name from path hash (same logic as main)
    local container_name=$(get_container_name "$HOST_PATH")


    debug "Looking for container: $container_name (path: $HOST_PATH)"

    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        error "No container found for this directory"
        info "Run 'DCLAUDE_RM=false dclaude' first to create a persistent container"
        exit 1
    fi

    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
    debug "Container status: $container_status"

    if [[ "$container_status" != "running" ]]; then
        if [[ "$container_status" == "exited" ]]; then
            info "Restarting existing container: $container_name"
            docker start "$container_name" >/dev/null
            debug "Container restarted successfully"
        else
            error "Container $container_name is in unexpected state: $container_status"
            info "Remove it with: docker rm $container_name"
            exit 1
        fi
    fi

    # Detect TTY availability
    local tty_flags=$(detect_tty_flags)

    # Exec into container as claude user
    if [[ $# -eq 0 ]]; then
        # No command specified, open bash shell
        info "Opening shell in container: $container_name"
        debug "Exec command: docker exec $tty_flags -u claude -w $HOST_PATH $container_name bash"
        exec docker exec $tty_flags -u claude -w "$HOST_PATH" "$container_name" bash
    else
        # Execute specific command
        info "Executing command in container: $container_name"
        debug "Exec command: docker exec $tty_flags -u claude -w $HOST_PATH $container_name $*"
        exec docker exec $tty_flags -u claude -w "$HOST_PATH" "$container_name" "$@"
    fi
}

# Subcommand: authenticate GitHub CLI
cmd_gh() {
    # Generate container name based on current directory
    local container_name=$(get_container_name "$HOST_PATH")

    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        error "No container found for this directory"
        info "Run 'dclaude' first to create a container"
        exit 1
    fi

    # Check if container is running
    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
    if [[ "$container_status" != "running" ]]; then
        if [[ "$container_status" == "exited" ]]; then
            info "Restarting container: $container_name"
            docker start "$container_name" >/dev/null
        else
            error "Container $container_name is in unexpected state: $container_status"
            exit 1
        fi
    fi

    info "Starting GitHub CLI authentication..."
    exec docker exec -it -u claude "$container_name" gh auth login
}

# Subcommand: attach to existing tmux session
cmd_attach() {
    local session_name="${1:-${DCLAUDE_TMUX_SESSION:-}}"

    if [[ -z "$session_name" ]]; then
        error "No session name provided"
        info "Usage: dclaude attach <session-name>"
        info "   or: DCLAUDE_TMUX_SESSION=<name> dclaude attach"
        exit 1
    fi

    # Generate container name based on current directory
    local container_name=$(get_container_name "$HOST_PATH")


    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        error "No container found for this directory"
        info "Run 'dclaude' first to create a container"
        exit 1
    fi

    # Check if container is running
    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
    if [[ "$container_status" != "running" ]]; then
        if [[ "$container_status" == "exited" ]]; then
            info "Restarting container: $container_name"
            docker start "$container_name" >/dev/null
        else
            error "Container $container_name is in unexpected state: $container_status"
            exit 1
        fi
    fi

    # Check if session exists (use -L dclaude-inner for namespaced socket)
    if ! docker exec -u claude "$container_name" /usr/bin/tmux -L dclaude-inner has-session -t "$session_name" 2>/dev/null; then
        error "Session '$session_name' not found in container"
        info "Available sessions:"
        docker exec -u claude "$container_name" /usr/bin/tmux -L dclaude-inner list-sessions 2>/dev/null || echo "  (no sessions running)"
        exit 1
    fi

    # Build env args for docker exec
    local exec_env_args=()
    [[ -n "${TERM_PROGRAM:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM=${TERM_PROGRAM}")
    [[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
    [[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
    [[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
    [[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")

    # Restart relay if the session uses it (relay was stopped when previous dclaude exited)
    local relay_port
    relay_port=$(docker inspect --format='{{index .Config.Labels "dclaude.relay.port"}}' "$container_name" 2>/dev/null || true)
    local platform
    platform=$(detect_platform)
    local network_mode
    network_mode=$(docker inspect --format='{{.HostConfig.NetworkMode}}' "$container_name" 2>/dev/null || echo "bridge")
    setup_agent_teams_relay "$container_name" "$relay_port" "$platform" "$network_mode"
    if [[ "$RELAY_STARTED" == "true" ]]; then
        trap "stop_relay '$container_name'" EXIT
    fi

    # Attach to existing session
    info "Attaching to session: $session_name"
    docker exec -it -u claude "${exec_env_args[@]}" "$container_name" \
        /usr/bin/tmux -L dclaude-inner -f /home/claude/.tmux.conf attach-session -t "$session_name"
    exit $?
}

# Subcommand: launch Chrome with DevTools and ensure MCP configured
cmd_chrome() {
    local setup_only=false

    # Parse flags
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --setup-only)
                setup_only=true
                shift
                ;;
            --port=*)
                CHROME_PORT="${1#*=}"
                shift
                ;;
            *)
                error "Unknown option: $1"
                info "Usage: dclaude chrome [--setup-only] [--port=PORT]"
                exit 1
                ;;
        esac
    done

    info "Setting up Chrome DevTools integration"

    # 1. Detect Chrome binary
    local chrome_bin="$CHROME_BIN"
    if [[ -z "$chrome_bin" ]]; then
        debug "Auto-detecting Chrome binary"
        if [[ "$(uname)" == "Darwin" ]]; then
            # macOS
            if [[ -f "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ]]; then
                chrome_bin="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
            elif [[ -f "/Applications/Chromium.app/Contents/MacOS/Chromium" ]]; then
                chrome_bin="/Applications/Chromium.app/Contents/MacOS/Chromium"
            fi
        elif [[ "$(uname)" == "Linux" ]]; then
            # Linux
            chrome_bin=$(command -v google-chrome || command -v chromium-browser || command -v chromium || echo "")
        fi

        if [[ -z "$chrome_bin" ]]; then
            error "Chrome not found. Set DCLAUDE_CHROME_BIN to specify location"
            exit 1
        fi
        debug "Found Chrome: $chrome_bin"
    fi

    # 2. Setup profile directory
    local profile_dir="$HOST_PATH/.dclaude.d/chrome/profiles/$CHROME_PROFILE"
    mkdir -p "$profile_dir"
    debug "Profile directory: $profile_dir"

    # 3. Check/create .mcp.json
    local mcp_json="$HOST_PATH/.mcp.json"
    local mcp_port=""
    local port_mismatch=false

    if [[ -f "$mcp_json" ]]; then
        debug "Found existing .mcp.json"
        # Extract port from --browserUrl in .mcp.json
        mcp_port=$(jq -r '.mcpServers.chrome.args[]? | select(startswith("--browserUrl=")) | split(":")[-1]' "$mcp_json" 2>/dev/null || echo "")

        if [[ -n "$mcp_port" && "$mcp_port" != "$CHROME_PORT" ]]; then
            port_mismatch=true
        fi
    else
        debug "Creating .mcp.json"
        cat > "$mcp_json" << EOF
{
  "mcpServers": {
    "chrome": {
      "command": "npx",
      "args": [
        "-y",
        "chrome-devtools-mcp@latest",
        "--browserUrl=http://localhost:${CHROME_PORT}"
      ]
    }
  }
}
EOF
        success "Created .mcp.json with Chrome MCP server (port ${CHROME_PORT})"
    fi

    # 4. Warn if port mismatch
    if [[ "$port_mismatch" == "true" ]]; then
        warning "Port mismatch detected!"
        echo ""
        echo "  Chrome will launch on port:    ${CHROME_PORT}"
        echo "  MCP expects (.mcp.json):       ${mcp_port}"
        echo ""
        warning "MCP will not be able to connect until .mcp.json is updated"
        echo ""
    fi

    # 5. Exit if setup-only
    if [[ "$setup_only" == "true" ]]; then
        success "Setup complete (--setup-only mode)"
        exit 0
    fi

    # 6. Check if Chrome already running on this port
    if curl -s "http://localhost:${CHROME_PORT}/json/version" >/dev/null 2>&1; then
        success "Chrome already running on port ${CHROME_PORT}"
        curl -s "http://localhost:${CHROME_PORT}/json/version" | jq -r '"  Browser: " + .Browser'
        exit 0
    fi

    # 7. Launch Chrome
    info "Launching Chrome with remote debugging on port ${CHROME_PORT}"

    local chrome_args=(
        "--user-data-dir=$profile_dir"
        "--remote-debugging-port=$CHROME_PORT"
        "--no-first-run"
        "--no-default-browser-check"
        "--disable-default-apps"
        "--disable-sync"
        "--allow-insecure-localhost"
    )

    # Add user-specified flags
    if [[ -n "$CHROME_FLAGS" ]]; then
        debug "Adding custom flags: $CHROME_FLAGS"
        read -ra custom_flags <<< "$CHROME_FLAGS"
        chrome_args+=("${custom_flags[@]}")
    fi

    debug "Chrome command: $chrome_bin ${chrome_args[*]}"

    # Launch Chrome in background
    "$chrome_bin" "${chrome_args[@]}" >/dev/null 2>&1 &
    local chrome_pid=$!

    # 8. Wait for Chrome to be ready
    info "Waiting for Chrome to start..."
    local wait_count=0
    while [[ $wait_count -lt 30 ]]; do
        if curl -s "http://localhost:${CHROME_PORT}/json/version" >/dev/null 2>&1; then
            success "Chrome is ready on port ${CHROME_PORT}"
            curl -s "http://localhost:${CHROME_PORT}/json/version" | jq -r '"  Browser: " + .Browser + "\n  Protocol: " + ."Protocol-Version" + "\n  WebSocket: " + .webSocketDebuggerUrl'
            echo ""
            success "Chrome DevTools ready for MCP integration!"
            info "Next step: Run 'dclaude' to start Claude with Chrome MCP"
            exit 0
        fi
        sleep 0.5
        ((wait_count++))
    done

    error "Chrome failed to start or remote debugging port not accessible"
    exit 1
}

# Subcommand: pull latest image
cmd_pull() {
    info "Pulling latest image: $IMAGE"
    if docker pull "$IMAGE"; then
        success "Image pulled successfully"
    else
        error "Failed to pull image: $IMAGE"
        exit 1
    fi
}

# Subcommand: update Claude CLI inside container
cmd_update() {
    local container_name=$(get_container_name "$HOST_PATH")

    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        error "No container found for this directory"
        info "Run 'dclaude' first to create a persistent container"
        exit 1
    fi

    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)

    if [[ "$container_status" != "running" ]]; then
        error "Container is not running"
        info "Start it with 'dclaude' first"
        exit 1
    fi

    info "Updating Claude CLI..."
    local update_output
    if update_output=$(docker exec -u claude "$container_name" claude update 2>&1); then
        # Show command and output together, indented to align with "Debug: " prefix
        debug "claude update
$(echo "$update_output" | sed 's/^/       /')"
        local new_version=$(docker exec -u claude "$container_name" claude --version 2>/dev/null | head -1)
        success "Claude CLI updated: $new_version"
    else
        debug "claude update
$(echo "$update_output" | sed 's/^/       /')"
        error "Failed to update Claude CLI"
        exit 1
    fi
    exit 0
}

# Subcommand: stop container for current directory
cmd_stop() {
    local container_name=$(get_container_name "$HOST_PATH")

    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        info "No container found for this directory"
        debug "Container name would be: $container_name"
        exit 0
    fi

    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)

    if [[ "$container_status" == "running" ]]; then
        # Count active tmux sessions
        local session_count
        session_count=$(docker exec -u claude "$container_name" /usr/bin/tmux -L dclaude-inner list-sessions 2>/dev/null | wc -l | tr -d '[:space:]') || session_count=0

        if [[ "$session_count" =~ ^[0-9]+$ ]] && [[ "$session_count" -gt 0 ]]; then
            info "Stopping container $container_name ($session_count active session(s))..."
        else
            info "Stopping container $container_name..."
        fi

        # Stop relay if running
        stop_relay "$container_name"

        if docker stop "$container_name" >/dev/null; then
            success "Container stopped"
        else
            error "Failed to stop container"
            exit 1
        fi
    else
        info "Container $container_name is not running (status: $container_status)"
    fi
    exit 0
}

# Subcommand: SSH — dispatches to 'keys', 'server', or both
cmd_ssh() {
    local rc=0
    case "${1:-}" in
        keys)
            shift
            cmd_ssh_keys "$@" || rc=$?
            ;;
        server)
            shift
            cmd_ssh_server "$@" || rc=$?
            ;;
        --help|-h)
            cat << 'SSH_HELP'
dclaude ssh - SSH Key and Server Management

Usage:
  dclaude ssh              Load SSH keys into agent and start SSH server
  dclaude ssh keys         Load SSH keys into agent
  dclaude ssh server       Start SSH server and show connection info
  dclaude ssh server --stop  Stop SSH server

SSH_HELP
            exit 0
            ;;
        "")
            # Bare 'dclaude ssh': load keys (non-fatal), then start server
            cmd_ssh_keys "$@" || true
            echo ""
            cmd_ssh_server "$@" || rc=$?
            ;;
        *)
            error "Unknown subcommand: $1"
            info "Usage: dclaude ssh [keys | server]"
            exit 1
            ;;
    esac
    exit "$rc"
}

# Subcommand: SSH key loading
cmd_ssh_keys() {
    # Parse flags
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                cat << 'SSH_KEYS_HELP'
dclaude ssh keys - Load SSH Keys into Agent

Usage:
  dclaude ssh keys         Detect and load SSH keys from ~/.ssh/

Detects private keys (id_rsa, id_ed25519, etc.) in ~/.ssh/ and loads
them into the SSH agent via ssh-add. May prompt for passphrases.

SSH_KEYS_HELP
                return 0
                ;;
            *)
                error "Unknown option: $1"
                info "Usage: dclaude ssh keys"
                return 1
                ;;
        esac
    done

    local ssh_dir="${HOME}/.ssh"
    if [[ ! -d "$ssh_dir" ]]; then
        error "No ~/.ssh directory found"
        return 1
    fi

    # Detect private key files
    local keys=()
    for key_file in "$ssh_dir"/id_*; do
        [[ -f "$key_file" ]] || continue
        # Skip public keys and certificates
        [[ "$key_file" == *.pub ]] && continue
        [[ "$key_file" == *-cert.pub ]] && continue
        keys+=("$key_file")
    done

    if [[ ${#keys[@]} -eq 0 ]]; then
        warning "No SSH keys found in ~/.ssh/"
        info "Generate a key with: ssh-keygen -t ed25519"
        return 1
    fi

    info "Loading SSH keys..."
    local loaded=0
    for key_file in "${keys[@]}"; do
        local display_path="~/.ssh/$(basename "$key_file")"
        if ssh-add "$key_file" 2>/dev/null; then
            success "  $display_path"
            loaded=$((loaded + 1))
        else
            warning "  $display_path (failed — wrong passphrase or unsupported key)"
        fi
    done

    echo ""
    if [[ $loaded -gt 0 ]]; then
        success "$loaded key(s) loaded into SSH agent."
    else
        error "No keys were loaded"
        return 1
    fi
}

# Subcommand: SSH server for remote access (JetBrains Gateway, debugging, etc.)
cmd_ssh_server() {
    local action=""

    # Parse flags
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --stop)
                action="stop"
                shift
                ;;
            --help|-h)
                cat << 'SSH_SERVER_HELP'
dclaude ssh server - SSH Server for Remote Access

Usage:
  dclaude ssh server         Start SSH server and show connection info
  dclaude ssh server --stop  Stop SSH server

Connection:
  ssh claude@localhost -p <port>
  Username: claude
  Password: claude

Use Cases:
  - JetBrains Gateway (PhpStorm, IntelliJ, WebStorm, etc.)
  - Remote debugging
  - SFTP file transfer
  - VS Code Remote SSH

JetBrains Gateway Setup:
  1. Start container: dclaude
  2. Start SSH: dclaude ssh server
  3. Open JetBrains Gateway
  4. New Connection → SSH → localhost:<port shown above>
  5. Username: claude, Password: claude
  6. Gateway will download and install IDE backend automatically
  7. Select your project directory

SSH_SERVER_HELP
                return 0
                ;;
            *)
                error "Unknown option: $1"
                info "Usage: dclaude ssh server [--stop]"
                return 1
                ;;
        esac
    done

    local container_name=$(get_container_name "$HOST_PATH")

    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        error "No container found for this directory"
        info "Run 'dclaude' first to create a persistent container"
        return 1
    fi

    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)

    if [[ "$container_status" != "running" ]]; then
        if [[ "$container_status" == "exited" ]]; then
            info "Starting container: $container_name"
            docker start "$container_name" >/dev/null
            sleep 1
        else
            error "Container $container_name is in unexpected state: $container_status"
            return 1
        fi
    fi

    # Get SSH port from container label
    local ssh_port
    ssh_port=$(docker inspect --format='{{index .Config.Labels "dclaude.ssh.port"}}' "$container_name" 2>/dev/null)

    if [[ -z "$ssh_port" ]]; then
        error "SSH port not configured for this container"
        echo ""
        info "This container was created with an older version of dclaude."
        info "To enable SSH, recreate the container:"
        echo ""
        echo "  dclaude rm -f"
        echo "  dclaude"
        echo "  dclaude ssh server"
        echo ""
        return 1
    fi

    case "$action" in
        stop)
            if docker exec "$container_name" pgrep -x sshd >/dev/null 2>&1; then
                info "Stopping SSH server..."
                docker exec -u root "$container_name" pkill -x sshd 2>/dev/null || true
                success "SSH server stopped"
            else
                info "SSH server is not running"
            fi
            ;;

        "")
            # Default: start SSH and show connection info
            # Start SSH if not running
            if docker exec "$container_name" pgrep -x sshd >/dev/null 2>&1; then
                info "SSH server already running"
            else
                info "Starting SSH server on port $ssh_port..."
                docker exec -u root "$container_name" sh -c "
                    if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then
                        ssh-keygen -A >/dev/null 2>&1
                    fi
                    /usr/sbin/sshd -p $ssh_port
                "
                success "SSH server started"
            fi

            echo ""
            echo "SSH Connection"
            echo "=============="
            echo "  Host:     localhost"
            echo "  Port:     $ssh_port"
            echo "  Username: claude"
            echo "  Password: claude"
            echo ""
            echo "  ssh claude@localhost -p $ssh_port"
            echo ""
            ;;
    esac
}

# Subcommand: remove container for current directory
cmd_rm() {
    local force=false

    # Parse flags
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -f|--force)
                force=true
                shift
                ;;
            *)
                error "Unknown option: $1"
                info "Usage: dclaude rm [-f|--force]"
                exit 1
                ;;
        esac
    done

    local container_name=$(get_container_name "$HOST_PATH")

    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        info "No container found for this directory"
        debug "Container name would be: $container_name"
        exit 0
    fi

    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)

    if [[ "$container_status" == "running" ]]; then
        if [[ "$force" == "true" ]]; then
            # Count active tmux sessions
            local session_count
            session_count=$(docker exec -u claude "$container_name" /usr/bin/tmux -L dclaude-inner list-sessions 2>/dev/null | wc -l | tr -d '[:space:]') || session_count=0

            if [[ "$session_count" =~ ^[0-9]+$ ]] && [[ "$session_count" -gt 0 ]]; then
                info "Removing running container $container_name ($session_count active session(s))..."
            else
                info "Removing running container $container_name..."
            fi

            # Clean up relay (including nonce file since container is being removed)
            stop_relay "$container_name"
            rm -f "$HOME/.dclaude/tmux-relay-nonce-${container_name}"

            if docker rm -f "$container_name" >/dev/null; then
                success "Container removed"
            else
                error "Failed to remove container"
                exit 1
            fi
        else
            error "Container $container_name is running"
            info "Stop it first with: dclaude stop"
            info "Or force remove with: dclaude rm -f"
            exit 1
        fi
    else
        info "Removing container $container_name..."

        # Clean up relay files (including nonce since container is being removed)
        stop_relay "$container_name"
        rm -f "$HOME/.dclaude/tmux-relay-nonce-${container_name}"

        if docker rm "$container_name" >/dev/null; then
            success "Container removed"
        else
            error "Failed to remove container"
            exit 1
        fi
    fi
    exit 0
}

# Subcommand: configure git identity
cmd_aws_configure() {
    local container_name="$1"

    # This subcommand only makes sense for volume mode
    local resolved_mode
    resolved_mode=$(resolve_aws_cli_mode)
    if [[ "$resolved_mode" != "volume" ]]; then
        error "dclaude aws configure is only for AWS_CLI=volume mode"
        if [[ "$resolved_mode" == "mount" ]]; then
            info "Current mode is 'mount' — host's ~/.aws is already available in the container"
        else
            info "Set DCLAUDE_AWS_CLI=volume to use persistent AWS volume"
        fi
        exit 1
    fi

    echo ""
    echo "AWS CLI Configuration"
    echo "─────────────────────"

    # Check existing config in container
    local existing_config
    existing_config=$(docker exec -u claude "$container_name" cat /home/claude/.aws/config 2>/dev/null || echo "")

    if [[ -n "$existing_config" ]]; then
        echo "Current config in container:"
        echo "$existing_config" | sed 's/^/  /'
        echo ""
        read -p "Overwrite with host config? [y/N]: " overwrite
        if [[ "$overwrite" != "y" && "$overwrite" != "Y" ]]; then
            exit 0
        fi
    fi

    # Check host config
    if [[ ! -f "${HOME}/.aws/config" ]]; then
        error "No ~/.aws/config found on host"
        info "Run 'aws configure sso' on your host first, then re-run this command"
        exit 1
    fi

    echo "Found on host:"
    sed 's/^/  /' "${HOME}/.aws/config"
    echo ""
    info "Only config (profiles, regions, SSO URLs) will be copied — no credentials or tokens"
    echo ""
    read -p "Copy to container? [Y/n]: " copy
    if [[ "$copy" == "n" || "$copy" == "N" ]]; then
        exit 0
    fi

    # Copy config file into container (docker cp copies as root, fix ownership after)
    docker exec -u claude "$container_name" mkdir -p /home/claude/.aws
    docker cp "${HOME}/.aws/config" "${container_name}:/home/claude/.aws/config"
    docker exec "$container_name" chown claude:claude /home/claude/.aws /home/claude/.aws/config
    docker exec "$container_name" chmod 600 /home/claude/.aws/config

    echo ""
    success "AWS config copied to container"
    info "Run 'dclaude aws login' or 'aws login' inside dclaude to authenticate"
    exit 0
}

cmd_aws_login() {
    local container_name="$1"
    shift

    local tty_flags
    tty_flags=$(detect_tty_flags)

    info "Starting AWS login..."
    # shellcheck disable=SC2086
    exec docker exec $tty_flags -u claude "$container_name" aws login "$@"
}

cmd_aws() {
    local container_name=$(get_container_name "$HOST_PATH")

    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        error "No container found for this directory"
        info "Run 'dclaude' first to create a container"
        exit 1
    fi

    # Check if container is running
    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
    if [[ "$container_status" != "running" ]]; then
        if [[ "$container_status" == "exited" ]]; then
            info "Starting container: $container_name"
            docker start "$container_name" >/dev/null
            sleep 1
        else
            error "Container $container_name is in unexpected state: $container_status"
            exit 1
        fi
    fi

    # Dispatch subcommands
    local subcmd="${1:-}"
    case "$subcmd" in
        configure)
            shift
            cmd_aws_configure "$container_name" "$@"
            ;;
        login)
            shift
            cmd_aws_login "$container_name" "$@"
            ;;
        --help|-h|"")
            echo "Usage:"
            echo "  dclaude aws configure    Copy AWS config from host (volume mode)"
            echo "  dclaude aws login        Run AWS login in container"
            echo "  dclaude aws login --profile <name>  Login with specific profile"
            exit 0
            ;;
        *)
            error "Unknown subcommand: $subcmd"
            echo "Usage:"
            echo "  dclaude aws configure    Copy AWS config from host (volume mode)"
            echo "  dclaude aws login        Run AWS login in container"
            echo "  dclaude aws login --profile <name>  Login with specific profile"
            exit 1
            ;;
    esac
}

cmd_git() {
    local container_name=$(get_container_name "$HOST_PATH")

    # Check if container exists
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        error "No container found for this directory"
        info "Run 'dclaude' first to create a container"
        exit 1
    fi

    # Check if container is running
    local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
    if [[ "$container_status" != "running" ]]; then
        if [[ "$container_status" == "exited" ]]; then
            info "Starting container: $container_name"
            docker start "$container_name" >/dev/null
            sleep 1
        else
            error "Container $container_name is in unexpected state: $container_status"
            exit 1
        fi
    fi

    # Check if git config already exists
    local existing_name existing_email
    existing_name=$(docker exec -u claude "$container_name" git config --global user.name 2>/dev/null || echo "")
    existing_email=$(docker exec -u claude "$container_name" git config --global user.email 2>/dev/null || echo "")

    echo ""
    echo "Git Configuration"
    echo "─────────────────"

    if [[ -n "$existing_name" && -n "$existing_email" ]]; then
        echo "Current config:"
        echo "  Name:  $existing_name"
        echo "  Email: $existing_email"
        echo ""
        read -p "Update? [y/N]: " update
        if [[ "$update" != "y" && "$update" != "Y" ]]; then
            exit 0
        fi
    fi

    # Try to get from host
    local host_name host_email
    host_name=$(git config --global user.name 2>/dev/null || echo "")
    host_email=$(git config --global user.email 2>/dev/null || echo "")

    local name="" email=""

    if [[ -n "$host_name" && -n "$host_email" ]]; then
        echo "Found on host:"
        echo "  Name:  $host_name"
        echo "  Email: $host_email"
        echo ""
        read -p "Copy to container? [Y/n]: " copy
        if [[ "$copy" != "n" && "$copy" != "N" ]]; then
            name="$host_name"
            email="$host_email"
        fi
    fi

    # Prompt if not copying from host
    if [[ -z "$name" ]]; then
        if [[ -z "$host_name" && -z "$host_email" ]]; then
            echo "No git config found on host."
            echo ""
        fi
        read -p "Enter your name: " name
        read -p "Enter your email: " email
    fi

    # Validate
    if [[ -z "$name" || -z "$email" ]]; then
        error "Name and email are required"
        exit 1
    fi

    # Save to container
    docker exec -u claude "$container_name" git config --global user.name "$name"
    docker exec -u claude "$container_name" git config --global user.email "$email"

    echo ""
    success "Git config saved"
    exit 0
}

# Initialize HOST_PATH and MOUNT_ROOT once for all commands
HOST_PATH=$(get_host_path)
MOUNT_ROOT=$(get_mount_root "$HOST_PATH")

# Handle subcommands
if [[ $# -gt 0 ]]; then
    case "$1" in
        new)
            # Explicit "new" command - shift and pass remaining args to main
            shift
            # Fall through to main function
            ;;
        attach)
            shift
            cmd_attach "$@"
            ;;
        exec|shell|bash)
            shift
            cmd_exec "$@"
            ;;
        chrome)
            shift
            cmd_chrome "$@"
            ;;
        gh)
            shift
            cmd_gh "$@"
            ;;
        pull)
            shift
            cmd_pull "$@"
            ;;
        update)
            shift
            cmd_update "$@"
            ;;
        stop)
            shift
            cmd_stop "$@"
            ;;
        rm)
            shift
            cmd_rm "$@"
            ;;
        ssh)
            shift
            cmd_ssh "$@"
            ;;
        git)
            shift
            cmd_git "$@"
            ;;
        aws)
            shift
            cmd_aws "$@"
            ;;
        --help|-h|help)
            cat << 'EOF'
dclaude - Dockerized Claude Code Launcher

Usage:
  dclaude [options]           Start new Claude session (default)
  dclaude new [options]       Start new Claude session (explicit)
  dclaude attach <session>    Attach to existing tmux session
  dclaude pull                Pull latest Docker image
  dclaude update              Update Claude CLI inside container
  dclaude stop                Stop container for current directory
  dclaude rm [-f]             Remove container for current directory
  dclaude ssh                 Load SSH keys and start SSH server
  dclaude ssh keys            Load SSH keys into agent
  dclaude ssh server          Start SSH server for remote access
  dclaude git                 Configure git identity (name/email)
  dclaude aws configure       Copy AWS config from host to container (volume mode)
  dclaude aws login           Run AWS login in container
  dclaude chrome [options]    Launch Chrome with DevTools and MCP integration
  dclaude gh                  Authenticate GitHub CLI (runs gh auth login)
  dclaude exec [command]      Execute command in container (default: bash)
  dclaude shell               Open bash shell in container
  dclaude --help              Show this help

Environment Variables:
  DCLAUDE_TAG                Docker image tag (default: latest)
  DCLAUDE_RM                 Remove container on exit (default: false)
  DCLAUDE_DEBUG              Enable debug output (default: false)
  DCLAUDE_NAMESPACE          Namespace for isolated credentials/config
  DCLAUDE_GIT_AUTH           SSH auth for Git: auto, agent-forwarding, key-mount, none
  DCLAUDE_NETWORK            Network mode: auto, host, bridge
  DCLAUDE_MOUNT_ROOT         Mount parent directory (absolute or relative path)
  DCLAUDE_AWS_CLI            AWS config mode: auto, mount, volume, none
  DCLAUDE_DOCKER_SOCKET      Override Docker socket path
  DCLAUDE_TMUX_SESSION       Custom tmux session name (default: claude-TIMESTAMP)
  DCLAUDE_CHROME_BIN         Chrome executable path (auto-detected if not set)
  DCLAUDE_CHROME_PROFILE     Chrome profile name (default: claude)
  DCLAUDE_CHROME_PORT        Chrome debugging port (default: 9222)
  DCLAUDE_CHROME_FLAGS       Additional Chrome flags (default: empty)

Configuration File (.dclaude):
  Create a .dclaude file at project root to configure dclaude for that tree:
    NAMESPACE=mycompany
    NETWORK=host
    AWS_CLI=mount
    DEBUG=true
    MOUNT_ROOT=.           # Mount config file's directory
    MOUNT_ROOT=..          # Mount parent of config file's directory
  dclaude walks up the directory tree to find .dclaude files.
  Environment variables override .dclaude settings.

Examples:
  # Start new Claude session (auto-generated session name)
  dclaude
  dclaude new

  # Start with custom session name (role-based workflows)
  DCLAUDE_TMUX_SESSION=claude-architect dclaude
  dclaude new --dangerously-skip-permissions

  # Attach to existing named session
  dclaude attach claude-architect
  DCLAUDE_TMUX_SESSION=claude-architect dclaude attach

  # Start with ephemeral container (removed on exit)
  DCLAUDE_RM=true dclaude

  # Use namespace for isolated credentials (personal vs company)
  DCLAUDE_NAMESPACE=mycompany dclaude
  # Or create .dclaude file: echo "NAMESPACE=mycompany" > ~/projects/mycompany/.dclaude

  # Mount parent directory to access sibling directories
  DCLAUDE_MOUNT_ROOT=.. dclaude                    # Relative path
  DCLAUDE_MOUNT_ROOT=/Users/alan/projects dclaude  # Absolute path
  # Or in .dclaude file: MOUNT_ROOT=..

  # Update image and restart container
  dclaude pull                                   # Pull latest image
  dclaude update                                 # Update Claude CLI in container
  dclaude stop                                   # Stop container (preserves it)
  dclaude rm                                     # Remove stopped container
  dclaude rm -f                                  # Force remove running container

  # SSH key and server management
  dclaude ssh                                  # Load keys + start SSH server
  dclaude ssh keys                             # Load SSH keys into agent
  dclaude ssh server                           # Start SSH server, show connection info
  dclaude ssh server --stop                    # Stop SSH server

  # Launch Chrome with DevTools for MCP integration
  dclaude chrome
  dclaude chrome --port=9223                    # Custom debugging port
  dclaude chrome --setup-only                   # Just create .mcp.json, don't launch
  DCLAUDE_CHROME_PROFILE=testing dclaude chrome # Use different profile

  # Authenticate GitHub CLI (persists until container is removed)
  dclaude gh

  # Open bash shell in running container
  dclaude exec

  # Run command in container
  dclaude exec npm install

For more information: https://github.com/alanbem/dclaude
EOF
            exit 0
            ;;
    esac
fi

# Handle signals

trap 'exit 130' INT
trap 'exit 143' TERM

# Run main function
main "$@"