diff --git a/docs/content/docs/cua-driver/guide/getting-started/installation.mdx b/docs/content/docs/cua-driver/guide/getting-started/installation.mdx index 6b6658524f..5e87086e59 100644 --- a/docs/content/docs/cua-driver/guide/getting-started/installation.mdx +++ b/docs/content/docs/cua-driver/guide/getting-started/installation.mdx @@ -78,7 +78,7 @@ The legacy `libs/cua-driver-rs/scripts/install.{sh,ps1}` URLs continue to work v $arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'arm64' } else { 'x86_64' } $url = "https://github.com/trycua/cua/releases/download/cua-driver-rs-v$version/cua-driver-rs-$version-windows-$arch-binary.zip" iwr $url -OutFile $env:TEMP\cua.zip -UseBasicParsing - $dest = "$env:LOCALAPPDATA\Programs\trycua\cua-driver-rs\bin" + $dest = "$env:LOCALAPPDATA\Programs\Cua\cua-driver\bin" New-Item -ItemType Directory -Force -Path $dest | Out-Null Expand-Archive -Force -Path $env:TEMP\cua.zip -DestinationPath $dest # Idempotent User PATH update — only add $dest if not already present. @@ -96,7 +96,7 @@ The Windows installer auto-detects host architecture (x64 / arm64) via `[System. It runs **without admin** and **without Developer Mode**. -It also appends `%LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin` to your **User-scope `Path`** so `cua-driver --version` resolves in any new PowerShell window. The write is idempotent (re-running the installer never duplicates the entry) and falls back to printing manual `[Environment]::SetEnvironmentVariable(...)` instructions if the registry write is blocked (group policy, locked-down account). Pass `-NoPathUpdate` to suppress the auto-add — useful when you manage `Path` out-of-band via chezmoi / dotfiles / GPO: +It also appends `%LOCALAPPDATA%\Programs\Cua\cua-driver\bin` to your **User-scope `Path`** so `cua-driver --version` resolves in any new PowerShell window. (v0.2.13 and earlier used `Programs\trycua\cua-driver-rs\bin`; the v0.2.14 installer auto-migrates the legacy layout.) The write is idempotent (re-running the installer never duplicates the entry) and falls back to printing manual `[Environment]::SetEnvironmentVariable(...)` instructions if the registry write is blocked (group policy, locked-down account). Pass `-NoPathUpdate` to suppress the auto-add — useful when you manage `Path` out-of-band via chezmoi / dotfiles / GPO: ```powershell # Fetch + invoke explicitly so we can pass the switch: @@ -126,16 +126,18 @@ $CUA_DRIVER_RS_INSTALL_DIR/cua-driver -> $CUA_DRIVER_RS_HOME/packages/current/cu **Windows** ``` -%USERPROFILE%\.cua-driver-rs\ ($env:CUA_DRIVER_RS_HOME) +%USERPROFILE%\.cua-driver\ ($env:CUA_DRIVER_RS_HOME) packages\ releases\ - 0.2.0-x86_64-pc-windows-msvc\cua-driver.exe (real binary, immutable) - 0.2.1-x86_64-pc-windows-msvc\cua-driver.exe (real binary, immutable) - current\ (directory junction → releases\0.2.1-...) -%LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin\ ($env:CUA_DRIVER_RS_INSTALL_DIR) + 0.2.14-x86_64-pc-windows-msvc\cua-driver.exe (real binary, immutable) + 0.2.15-x86_64-pc-windows-msvc\cua-driver.exe (real binary, immutable) + current\ (directory junction → releases\0.2.15-...) +%LOCALAPPDATA%\Programs\Cua\cua-driver\bin\ ($env:CUA_DRIVER_RS_INSTALL_DIR) (directory junction → packages\current) ``` +v0.2.13 and earlier used `%LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin\` and `%USERPROFILE%\.cua-driver-rs\`. The v0.2.14+ installer detects the legacy layout and migrates transparently — stop any running daemon, delete the legacy task / junctions / package home, prune the legacy PATH entry, then install at the new path. + Both junctions are NTFS reparse points (`IO_REPARSE_TAG_MOUNT_POINT`), so the whole PATH-resolution chain `bin\ → current\ → releases\\cua-driver.exe` is two filesystem hops transparently served from whichever release the inner junction currently points at. #### Atomic upgrades and rollback diff --git a/libs/cua-driver/scripts/install.ps1 b/libs/cua-driver/scripts/install.ps1 index 71e1a0a61f..9aca8929e3 100644 --- a/libs/cua-driver/scripts/install.ps1 +++ b/libs/cua-driver/scripts/install.ps1 @@ -14,13 +14,20 @@ # Layout on disk (three tiers, two directory junctions): # # [directory junction → currentDir] -# = %LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin +# = %LOCALAPPDATA%\Programs\Cua\cua-driver\bin # [directory junction → release dir] -# = %USERPROFILE%\.cua-driver-rs\packages\current +# = %USERPROFILE%\.cua-driver\packages\current # [real directory, immutable per version] -# = %USERPROFILE%\.cua-driver-rs\packages\releases\- +# = %USERPROFILE%\.cua-driver\packages\releases\- # cua-driver.exe # +# Path layout renamed v0.2.14: `Programs\trycua\cua-driver-rs\` → +# `Programs\Cua\cua-driver\` and `.cua-driver-rs\` → `.cua-driver\`. The +# Rust port IS the canonical Windows driver now (no `-rs` suffix needed), +# and `trycua` is the GitHub org prefix that doesn't belong in +# %LOCALAPPDATA%\Programs. Legacy installs are auto-migrated at the next +# `irm install.ps1 | iex` run. +# # PATH consumers see ; the contents are transparently # served from whichever release the inner junction currently points at. # Atomic upgrade = retarget at a newer release dir. @@ -34,9 +41,9 @@ # Env overrides: # $env:CUA_DRIVER_RS_VERSION pin a specific release (e.g. "0.2.0") # $env:CUA_DRIVER_RS_INSTALL_DIR override the visible PATH-entry dir -# (default %LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin) +# (default %LOCALAPPDATA%\Programs\Cua\cua-driver\bin) # $env:CUA_DRIVER_RS_HOME override the package home -# (default %USERPROFILE%\.cua-driver-rs) +# (default %USERPROFILE%\.cua-driver) # $env:CUA_DRIVER_RS_KEEP_VERSIONS keep the N most recent per-version # release dirs after install; older ones # are deleted (default 5; set 0 to @@ -107,15 +114,33 @@ $Script:CuaDriverRsBakedVersion = "0.2.13" if ($env:CUA_DRIVER_RS_INSTALL_DIR) { $VisibleBinDir = $env:CUA_DRIVER_RS_INSTALL_DIR } else { - $VisibleBinDir = Join-Path $env:LOCALAPPDATA "Programs\trycua\cua-driver-rs\bin" + # Path layout renamed v0.2.14: `Programs\trycua\cua-driver-rs\` → + # `Programs\Cua\cua-driver\`. The Rust port IS the canonical Windows + # driver now (no more `-rs` suffix needed in user-facing paths), and + # `trycua` is the GitHub org prefix that doesn't belong in + # %LOCALAPPDATA% — vendor folders there are conventionally PascalCase + # company names. The env var name keeps the `_RS_` infix so existing + # automation pinning a custom install dir doesn't break silently. + $VisibleBinDir = Join-Path $env:LOCALAPPDATA "Programs\Cua\cua-driver\bin" } +# Legacy install paths from v0.2.13 and earlier. The uninstall path checks +# both; the install path nukes any legacy install before laying down the +# new one, so v0.2.13 → v0.2.14+ is a transparent upgrade. +$LegacyVisibleBinDir = Join-Path $env:LOCALAPPDATA "Programs\trycua\cua-driver-rs\bin" +$LegacyVendorDir = Join-Path $env:LOCALAPPDATA "Programs\trycua" + if ($env:CUA_DRIVER_RS_HOME) { $HomeDir = $env:CUA_DRIVER_RS_HOME } else { - $HomeDir = Join-Path $env:USERPROFILE ".cua-driver-rs" + # Same rename: `.cua-driver-rs/` → `.cua-driver/`. The `-rs` suffix + # was the Rust-port-vs-Swift-driver disambiguator while the Swift one + # still existed for Windows; it doesn't anymore. + $HomeDir = Join-Path $env:USERPROFILE ".cua-driver" } +$LegacyHomeDir = Join-Path $env:USERPROFILE ".cua-driver-rs" + $PackagesDir = Join-Path $HomeDir "packages" $ReleasesDir = Join-Path $PackagesDir "releases" $CurrentDir = Join-Path $PackagesDir "current" @@ -850,6 +875,99 @@ Write-Step "cua-driver-rs installer (Windows)" Write-Step " install dir : $VisibleBinDir" Write-Step " package home: $HomeDir" +function Remove-LegacyInstall { + # Best-effort cleanup of v0.2.13-and-earlier install paths. Runs before + # any new install when default paths are in use (so users who override + # CUA_DRIVER_RS_INSTALL_DIR / CUA_DRIVER_RS_HOME aren't surprised by us + # touching legacy locations). Mirrors uninstall.ps1's logic so the + # transition is symmetric: a single `irm install.ps1 | iex` upgrades + # from v0.2.13 → v0.2.14+ without orphan files at the old layout. + if ($env:CUA_DRIVER_RS_INSTALL_DIR -or $env:CUA_DRIVER_RS_HOME) { + return + } + $hasLegacy = (Test-Path -LiteralPath $LegacyVisibleBinDir) -or ` + (Test-Path -LiteralPath $LegacyHomeDir) + if (-not $hasLegacy) { return } + + Write-Step "detected legacy install layout (v0.2.13 or earlier); migrating to Cua\cua-driver" + + # 1. Stop any cua-driver / cua-driver-uia daemon that's pinning the + # legacy binary directory open. Use the regular Get-Process route — + # these are user-owned processes, kill is allowed. + $procs = Get-Process -Name "cua-driver","cua-driver-uia" -ErrorAction SilentlyContinue + if ($procs) { + foreach ($p in $procs) { + try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {} + } + Start-Sleep -Milliseconds 250 + } + + # 2. Unregister the autostart Scheduled Task if present. Idempotent — + # schtasks /Query returns non-zero when the task is absent, which we + # swallow (matches uninstall.ps1's pattern for the same call). + $prevEAP = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + try { + & schtasks.exe /Delete /TN "cua-driver-serve" /F 2>$null | Out-Null + } finally { + $ErrorActionPreference = $prevEAP + } + + # 3. Remove the visible bin junction (only when it's actually a reparse + # point — refuse to clobber a real directory). Then walk up and + # remove the empty `trycua` vendor dir if nothing else lives there. + if (Test-Path -LiteralPath $LegacyVisibleBinDir) { + try { + $item = Get-Item -LiteralPath $LegacyVisibleBinDir -Force -ErrorAction Stop + $isReparse = ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0 + if ($isReparse) { + # NTFS junction — delete the link, not the target. + [System.IO.Directory]::Delete($LegacyVisibleBinDir, $false) + } else { + Remove-Item -LiteralPath $LegacyVisibleBinDir -Recurse -Force -ErrorAction SilentlyContinue + } + } catch { + Write-Host " (could not remove $LegacyVisibleBinDir : $($_.Exception.Message))" -ForegroundColor Yellow + } + } + # Remove the parent `cua-driver-rs` dir (now empty) and the vendor + # `trycua` dir if no other apps live under it. + $legacyParent = Split-Path -Parent $LegacyVisibleBinDir + if ((Test-Path -LiteralPath $legacyParent) -and -not (Get-ChildItem -LiteralPath $legacyParent -Force -ErrorAction SilentlyContinue)) { + Remove-Item -LiteralPath $legacyParent -Force -ErrorAction SilentlyContinue + } + if ((Test-Path -LiteralPath $LegacyVendorDir) -and -not (Get-ChildItem -LiteralPath $LegacyVendorDir -Force -ErrorAction SilentlyContinue)) { + Remove-Item -LiteralPath $LegacyVendorDir -Force -ErrorAction SilentlyContinue + } + + # 4. Remove the legacy package home tree. + if (Test-Path -LiteralPath $LegacyHomeDir) { + try { + Remove-Item -LiteralPath $LegacyHomeDir -Recurse -Force -ErrorAction Stop + } catch { + Write-Host " (could not remove $LegacyHomeDir : $($_.Exception.Message))" -ForegroundColor Yellow + } + } + + # 5. Prune the legacy bin dir from User PATH. The new install will add + # the new path right after this; without removing the old we'd + # accumulate stale PATH entries on every upgrade. + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + if ($userPath) { + $legacyNorm = $LegacyVisibleBinDir.TrimEnd('\').ToLowerInvariant() + $cleaned = ($userPath -split ';' | + Where-Object { $_ -and $_.TrimEnd('\').ToLowerInvariant() -ne $legacyNorm }) -join ';' + if ($cleaned -ne $userPath) { + [Environment]::SetEnvironmentVariable('Path', $cleaned, 'User') + Write-Step " pruned legacy $LegacyVisibleBinDir from User PATH" + } + } + + Write-Step "legacy install removed" +} + +Remove-LegacyInstall + # Serialize concurrent installs per $HomeDir. The lock is released in # the finally below — covers normal exit, errors, and Ctrl-C (which # triggers PowerShell's pipeline-stop = finally still runs). diff --git a/libs/cua-driver/scripts/uninstall.ps1 b/libs/cua-driver/scripts/uninstall.ps1 index 1170850850..9d22ca2dcf 100644 --- a/libs/cua-driver/scripts/uninstall.ps1 +++ b/libs/cua-driver/scripts/uninstall.ps1 @@ -17,9 +17,9 @@ # `cua-driver autostart enable` or install.ps1 -AutoStart) # - Any running cua-driver.exe processes (so file handles don't pin # the binary directory open during the delete pass) -# - = %LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin (directory junction) -# - = %USERPROFILE%\.cua-driver-rs\packages\current (directory junction) -# - = %USERPROFILE%\.cua-driver-rs\ (entire tree: +# - = %LOCALAPPDATA%\Programs\Cua\cua-driver\bin (directory junction) +# - = %USERPROFILE%\.cua-driver\packages\current (directory junction) +# - = %USERPROFILE%\.cua-driver\ (entire tree: # releases, lockfile, # telemetry id, # install marker, @@ -38,9 +38,13 @@ # # Env overrides (mirror install.ps1's variable names): # $env:CUA_DRIVER_RS_INSTALL_DIR visible bin dir to remove -# (default %LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin) +# (default %LOCALAPPDATA%\Programs\Cua\cua-driver\bin; +# v0.2.13 and earlier used Programs\trycua\cua-driver-rs\bin +# — that legacy path is always cleaned up too) # $env:CUA_DRIVER_RS_HOME package home to remove -# (default %USERPROFILE%\.cua-driver-rs) +# (default %USERPROFILE%\.cua-driver; +# v0.2.13 and earlier used .cua-driver-rs — +# that legacy path is always cleaned up too) # # Params: # -Force non-interactive: skip the "remove? [y/N]" prompt before @@ -62,15 +66,25 @@ $ProgressPreference = "SilentlyContinue" if ($env:CUA_DRIVER_RS_INSTALL_DIR) { $VisibleBinDir = $env:CUA_DRIVER_RS_INSTALL_DIR } else { - $VisibleBinDir = Join-Path $env:LOCALAPPDATA "Programs\trycua\cua-driver-rs\bin" + # New layout (v0.2.14+). Path rename rationale: see install.ps1. + $VisibleBinDir = Join-Path $env:LOCALAPPDATA "Programs\Cua\cua-driver\bin" } +# Legacy bin dir from v0.2.13 and earlier. We also clean these up so a +# fresh uninstall after upgrading leaves nothing behind. Empty-vendor-dir +# (Programs\trycua\) gets pruned if no other apps live under it. +$LegacyVisibleBinDir = Join-Path $env:LOCALAPPDATA "Programs\trycua\cua-driver-rs\bin" +$LegacyVendorDir = Join-Path $env:LOCALAPPDATA "Programs\trycua" + if ($env:CUA_DRIVER_RS_HOME) { $HomeDir = $env:CUA_DRIVER_RS_HOME } else { - $HomeDir = Join-Path $env:USERPROFILE ".cua-driver-rs" + $HomeDir = Join-Path $env:USERPROFILE ".cua-driver" } +# Legacy package home from v0.2.13 and earlier. +$LegacyHomeDir = Join-Path $env:USERPROFILE ".cua-driver-rs" + $PackagesDir = Join-Path $HomeDir "packages" $CurrentDir = Join-Path $PackagesDir "current" $AutoStartTask = "cua-driver-serve" @@ -258,7 +272,42 @@ if (Test-Path -LiteralPath $HomeDir) { Write-Step "no package home at $HomeDir (skipping)" } -# 6. Skill junctions. Only remove reparse points — leave a real dir +# 6. Legacy install layout from v0.2.13 and earlier +# (`Programs\trycua\cua-driver-rs\` + `.cua-driver-rs\`). We always +# sweep these so a fresh uninstall after upgrading via install.ps1 +# leaves nothing behind. Skip silently when the legacy paths don't +# exist — common case post-v0.2.14. +if (Test-Path -LiteralPath $LegacyVisibleBinDir) { + if (Test-IsReparsePoint $LegacyVisibleBinDir) { + Remove-Item -LiteralPath $LegacyVisibleBinDir -Force -Recurse -ErrorAction SilentlyContinue + Write-Step "removed legacy junction $LegacyVisibleBinDir" + } else { + Remove-Item -LiteralPath $LegacyVisibleBinDir -Force -Recurse -ErrorAction SilentlyContinue + Write-Step "removed legacy directory $LegacyVisibleBinDir" + } +} +# Empty cua-driver-rs parent (under trycua\) +$legacyParent = Split-Path -Parent $LegacyVisibleBinDir +if ((Test-Path -LiteralPath $legacyParent) -and -not (Get-ChildItem -LiteralPath $legacyParent -Force -ErrorAction SilentlyContinue)) { + Remove-Item -LiteralPath $legacyParent -Force -ErrorAction SilentlyContinue + Write-Step "removed empty legacy parent $legacyParent" +} +# Empty trycua vendor dir +if ((Test-Path -LiteralPath $LegacyVendorDir) -and -not (Get-ChildItem -LiteralPath $LegacyVendorDir -Force -ErrorAction SilentlyContinue)) { + Remove-Item -LiteralPath $LegacyVendorDir -Force -ErrorAction SilentlyContinue + Write-Step "removed empty legacy vendor dir $LegacyVendorDir" +} +# Legacy package home +if (Test-Path -LiteralPath $LegacyHomeDir) { + Remove-Item -LiteralPath $LegacyHomeDir -Force -Recurse -ErrorAction SilentlyContinue + if (Test-Path -LiteralPath $LegacyHomeDir) { + Write-WarningStep "$LegacyHomeDir was not fully removed — some files may still be locked." + } else { + Write-Step "removed legacy package home $LegacyHomeDir" + } +} + +# 7. Skill junctions. Only remove reparse points — leave a real dir # in place (a user with a hand-managed cua-driver-rs skill dir # gets to keep it). Same defensive shape as Linux/macOS. foreach ($skillLink in $SkillJunctions) { @@ -286,7 +335,8 @@ Write-Host "" Write-Host " claude mcp remove cua-driver-rs" Write-Host "" Write-Host " Or edit ~/.claude.json directly and delete entries whose 'command' points at" -Write-Host " cua-driver.exe under %LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin\." +Write-Host " cua-driver.exe under %LOCALAPPDATA%\Programs\Cua\cua-driver\bin\" +Write-Host " (or the legacy %LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin\ from v0.2.13 and earlier)." Write-Host "" Write-Host "PATH:" Write-Host " If you added $VisibleBinDir to your User PATH after the install, remove it:"