From 0e1c89198ecd605079dcc27f426b22531436d33e Mon Sep 17 00:00:00 2001 From: sbusc Date: Wed, 25 Mar 2026 12:14:44 +0100 Subject: [PATCH] fix: Respect PAI_DIR environment variable in v4.0.3 installer The v4.0.3 installer hardcodes ~/.claude as the PAI directory in several places, ignoring the PAI_DIR environment variable. Users who set PAI_DIR to a custom path get a broken installation because the fallback always resolves to ~/.claude. Changes: - detect.ts: Read PAI_DIR from process.env when detecting system paths - actions.ts: Add PAI_DIR fallback in repository, configuration, and voice setup steps (3 locations) - validate.ts: Add PAI_DIR fallback in validation checks - install.sh: Pass through PAI_DIR if set, fix unbound variable errors for DISPLAY/WAYLAND_DISPLAY in headless detection --- Releases/v4.0.3/.claude/PAI-Install/engine/actions.ts | 6 +++--- Releases/v4.0.3/.claude/PAI-Install/engine/detect.ts | 2 +- Releases/v4.0.3/.claude/PAI-Install/engine/validate.ts | 2 +- Releases/v4.0.3/.claude/PAI-Install/install.sh | 10 +++++++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Releases/v4.0.3/.claude/PAI-Install/engine/actions.ts b/Releases/v4.0.3/.claude/PAI-Install/engine/actions.ts index 6b8817cef..45688139b 100644 --- a/Releases/v4.0.3/.claude/PAI-Install/engine/actions.ts +++ b/Releases/v4.0.3/.claude/PAI-Install/engine/actions.ts @@ -500,7 +500,7 @@ export async function runRepository( emit: EngineEventHandler ): Promise { await emit({ event: "step_start", step: "repository" }); - const paiDir = state.detection?.paiDir || join(homedir(), ".claude"); + const paiDir = state.detection?.paiDir || process.env.PAI_DIR || join(homedir(), ".claude"); if (state.installType === "upgrade") { await emit({ event: "progress", step: "repository", percent: 20, detail: "Existing installation found, updating..." }); @@ -585,7 +585,7 @@ export async function runConfiguration( emit: EngineEventHandler ): Promise { await emit({ event: "step_start", step: "configuration" }); - const paiDir = state.detection?.paiDir || join(homedir(), ".claude"); + const paiDir = state.detection?.paiDir || process.env.PAI_DIR || join(homedir(), ".claude"); const configDir = state.detection?.configDir || join(homedir(), ".config", "PAI"); // Generate settings.json @@ -985,7 +985,7 @@ export async function runVoiceSetup( } // ── Start voice server (works with or without ElevenLabs key) ── - const paiDir = state.detection?.paiDir || join(homedir(), ".claude"); + const paiDir = state.detection?.paiDir || process.env.PAI_DIR || join(homedir(), ".claude"); await emit({ event: "progress", step: "voice", percent: 25, detail: "Starting voice server..." }); const voiceServerReady = await startVoiceServer(paiDir, emit); diff --git a/Releases/v4.0.3/.claude/PAI-Install/engine/detect.ts b/Releases/v4.0.3/.claude/PAI-Install/engine/detect.ts index 6a618cf95..ef3d4e19a 100644 --- a/Releases/v4.0.3/.claude/PAI-Install/engine/detect.ts +++ b/Releases/v4.0.3/.claude/PAI-Install/engine/detect.ts @@ -126,7 +126,7 @@ function detectExisting( */ export function detectSystem(): DetectionResult { const home = homedir(); - const paiDir = join(home, ".claude"); + const paiDir = process.env.PAI_DIR || join(home, ".claude"); const configDir = process.env.PAI_CONFIG_DIR || join(home, ".config", "PAI"); return { diff --git a/Releases/v4.0.3/.claude/PAI-Install/engine/validate.ts b/Releases/v4.0.3/.claude/PAI-Install/engine/validate.ts index b744a7aa0..271cd4cc1 100644 --- a/Releases/v4.0.3/.claude/PAI-Install/engine/validate.ts +++ b/Releases/v4.0.3/.claude/PAI-Install/engine/validate.ts @@ -25,7 +25,7 @@ async function checkVoiceServerHealth(): Promise { * Run all validation checks against the current state. */ export async function runValidation(state: InstallState): Promise { - const paiDir = state.detection?.paiDir || join(homedir(), ".claude"); + const paiDir = state.detection?.paiDir || process.env.PAI_DIR || join(homedir(), ".claude"); const configDir = state.detection?.configDir || join(homedir(), ".config", "PAI"); const checks: ValidationCheck[] = []; diff --git a/Releases/v4.0.3/.claude/PAI-Install/install.sh b/Releases/v4.0.3/.claude/PAI-Install/install.sh index b618184f7..77deca695 100755 --- a/Releases/v4.0.3/.claude/PAI-Install/install.sh +++ b/Releases/v4.0.3/.claude/PAI-Install/install.sh @@ -57,6 +57,14 @@ echo "" echo -e "${STEEL}┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛${RESET}" echo "" +# ─── Custom PAI_DIR Support ────────────────────────────── +# Set PAI_DIR to install PAI into a non-default directory. +# Default: ~/.claude Example: PAI_DIR=~/Albert/.claude bash install.sh +if [ -n "${PAI_DIR:-}" ]; then + info "Custom PAI_DIR: $PAI_DIR" + export PAI_DIR +fi + # ─── Resolve Script Directory ───────────────────────────── # Follow symlinks so install.sh works from ~/.claude/ symlink SOURCE="${BASH_SOURCE[0]}" @@ -155,7 +163,7 @@ info "Launching installer..." echo "" # Auto-detect headless/SSH environments and fall back to CLI mode -if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] && [ "$(uname)" != "Darwin" ]; then +if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ] && [ "$(uname)" != "Darwin" ]; then INSTALL_MODE="cli" info "Headless environment detected — using CLI installer." else