Enable-ClaudeMemoryRedirect.ps1

function Enable-ClaudeMemoryRedirect {
    <#
    .SYNOPSIS
    Redirects ~/.claude/projects to $env:CLAUDE_MEMORY_DIR via a junction (Windows) or symlink (Linux/macOS).
 
    .DESCRIPTION
    Claude Code writes per-project memory and session state under ~/.claude/projects. This function
    lets the user point that directory at an alternate location (synced storage, work-only drive,
    etc.) by setting $env:CLAUDE_MEMORY_DIR. When set, existing content in ~/.claude/projects is
    migrated to the target with Copy-Item -Verbose -ErrorAction Stop, the original is removed, and
    a directory junction/symlink is created in its place.
 
    If $env:CLAUDE_MEMORY_DIR is not set, a one-time tip is printed offering to configure it, with a
    Y/n/never/exit prompt mirroring the CLAUDE_USER_MD startup hint.
 
    .PARAMETER HomeDir
    Platform-appropriate user home directory (typically [Environment]::GetFolderPath('UserProfile')).
 
    .PARAMETER Force
    Passes -Force to Copy-Item during migration so conflicting files at the target are overwritten.
    Without this switch, Copy-Item fails on any destination collision and the migration aborts.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$HomeDir,
        [switch]$Force
    )

    $projectsDir = Join-Path $HomeDir '.claude' 'projects'
    $suppressFile = Join-Path $HomeDir '.claude' 'suppress_memory_dir_hint'

    if (-not $env:CLAUDE_MEMORY_DIR) {
        if (Test-Path $suppressFile) {
            Write-Verbose "CLAUDE_MEMORY_DIR not set; tip suppressed."
            return
        }
        Write-Host ""
        Write-Host "Tip: set `$env:CLAUDE_MEMORY_DIR to a directory where Claude should store memories."
        Write-Host " PwrClaude will redirect ~/.claude/projects there via a junction so memories persist"
        Write-Host " across machines (OneDrive, work share, etc.). Set this in a LOCAL profile snippet,"
        Write-Host " not in a shared/synced profile — personal and work machines should point to different"
        Write-Host " stores so work memories never land in personal sync and vice versa."
        Write-Host ""
        Write-Host " The value is evaluated as a PowerShell expression, so you can use variables:"
        Write-Host " `$env:CLAUDE_MEMORY_DIR = '`${env:OneDrive}\Claude\projects'"
        Write-Host " `$env:CLAUDE_MEMORY_DIR = '`${env:USERPROFILE}\claude-memory'"
        Write-Host ""
        $answer = Read-Host "Show this tip next time? [Y/n/never/e(xit)]"
        if ($answer -match '^[nN]ever$') {
            New-Item -ItemType File -Path $suppressFile -Force | Out-Null
            Write-Host "Got it — tip suppressed permanently. Set CLAUDE_MEMORY_DIR any time to enable the redirect."
        } elseif ($answer -match '^[nN]$') {
            New-Item -ItemType File -Path $suppressFile -Force | Out-Null
            Write-Host "Got it — tip suppressed. Set CLAUDE_MEMORY_DIR any time to enable the redirect."
        } elseif ($answer -match '^([eE]|[eE]xit)$') {
            exit 0
        }
        Write-Host ""
        return
    }

    # Resolve as a PowerShell expression (matches the CLAUDE_USER_MD pattern in Invoke-Claude.ps1).
    try {
        $targetDir = Invoke-Expression "`"$env:CLAUDE_MEMORY_DIR`""
    } catch {
        Write-Warning "CLAUDE_MEMORY_DIR expression failed to evaluate: $env:CLAUDE_MEMORY_DIR"
        Write-Warning "Error: $_"
        return
    }

    if (-not $targetDir) {
        Write-Warning "CLAUDE_MEMORY_DIR resolved to empty; skipping redirect."
        return
    }

    Write-Verbose "CLAUDE_MEMORY_DIR resolved to: $targetDir"

    if (-not (Test-Path $targetDir)) {
        Write-Verbose "Creating target directory: $targetDir"
        New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
    }

    $claudeDir = Join-Path $HomeDir '.claude'
    if (-not (Test-Path $claudeDir)) {
        New-Item -ItemType Directory -Path $claudeDir -Force | Out-Null
    }

    $link = Get-Item -LiteralPath $projectsDir -Force -ErrorAction SilentlyContinue

    if ($link -and $link.LinkType) {
        $currentTarget = @($link.Target)[0]
        $targetResolved = (Resolve-Path -LiteralPath $targetDir).Path
        $currentResolved = try { (Resolve-Path -LiteralPath $currentTarget).Path } catch { $currentTarget }
        if ($currentResolved -eq $targetResolved) {
            Write-Verbose "~/.claude/projects already redirected to $targetDir"
            return
        }
        Write-Warning "~/.claude/projects is already a $($link.LinkType) pointing to '$currentTarget', not to '$targetDir'. Skipping redirect."
        return
    }

    if ($link) {
        $contents = Get-ChildItem -LiteralPath $projectsDir -Force -ErrorAction SilentlyContinue
        if ($contents) {
            Write-Verbose "Migrating existing memories from $projectsDir to $targetDir"
            foreach ($item in $contents) {
                $dest = Join-Path $targetDir $item.Name
                if ((Test-Path $dest) -and -not $Force) {
                    Write-Verbose "Skipping '$($item.Name)' — already exists in target (use -ForceMemoryRedirect to overwrite)"
                    continue
                }
                $copyParams = @{
                    LiteralPath = $item.FullName
                    Destination = $targetDir
                    Recurse     = $true
                    Force       = [bool]$Force
                    ErrorAction = 'Stop'
                }
                try {
                    Copy-Item @copyParams
                } catch {
                    Write-Warning "Failed to migrate '$($item.Name)': $_"
                    Write-Warning "Re-run with 'claude -ForceMemoryRedirect' to overwrite, or move the item manually."
                    throw
                }
            }
        }
        Write-Verbose "Removing original directory at $projectsDir"
        Remove-Item -LiteralPath $projectsDir -Recurse -Force -ErrorAction Stop
    }

    $onWindows = $IsWindows -or $env:OS -eq 'Windows_NT'
    $linkType = if ($onWindows) { 'Junction' } else { 'SymbolicLink' }
    Write-Verbose "Creating $linkType at $projectsDir -> $targetDir"
    New-Item -ItemType $linkType -Path $projectsDir -Target $targetDir -ErrorAction Stop | Out-Null
    Write-Host "Redirected $projectsDir -> $targetDir ($linkType)" -ForegroundColor Green
}