diff --git a/README.md b/README.md index 9b9bfee..dab9bc1 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,11 @@ Verify: run `/ghost-status` in Claude Code, then restart Codex. ### Windows (PowerShell) -Native PowerShell — no WSL required. `install.ps1` creates the same symlink -layout as the POSIX installer, so `/ghost-update` and the once-a-day update -check behave identically. +Native PowerShell — no WSL required, **no admin rights and no Developer Mode**. +`install.ps1` wires each skill back to the repo with directory junctions and a +copy of `SKILL.md`, none of which need elevated privileges (symbolic links on +Windows do, which is why the script avoids them). Update with `update.ps1`, +which re-syncs everything. ```powershell git clone git@github.com:leanpub/ghostai.git $HOME\.claude\skills\ghostai @@ -98,18 +100,14 @@ git clone git@github.com:leanpub/ghostai.git $HOME\.claude\skills\ghostai ``` Use `-Codex` for Codex, or `-All` for both. Verify by opening Claude Code and -running `/ghost-status`. Two Windows-specific notes: +running `/ghost-status`. -- **Symbolic links.** The installer links each skill back to the repo, which on - Windows needs either **Developer Mode** (Settings → Privacy & security → For - developers) or an elevated ("Run as administrator") PowerShell. `install.ps1` - checks up front and tells you which to enable — it won't fail silently. -- **Execution policy.** If PowerShell blocks the script with a policy error, run - it for the current session only: - - ```powershell - powershell -ExecutionPolicy Bypass -File $HOME\.claude\skills\ghostai\install.ps1 -Claude - ``` +> **Execution policy.** If PowerShell blocks the script with a policy error, run +> it for the current session only: +> +> ```powershell +> powershell -ExecutionPolicy Bypass -File $HOME\.claude\skills\ghostai\install.ps1 -Claude +> ``` > **HTTPS clone:** replace `git@github.com:` with `https://github.com/` if you > don't have SSH keys set up. @@ -148,10 +146,11 @@ Three gotchas account for most install problems: 1. **Corporate proxies** block the `git clone`. Ask your network admin to whitelist `github.com`, or clone from a personal network. 2. **Missing SSH keys.** Use the HTTPS clone URL — no keys needed. -3. **Windows symlink errors.** `install.ps1` needs permission to create symbolic - links — enable Developer Mode (Settings → Privacy & security → For developers) - or run PowerShell as Administrator. Prefer WSL2? Install it and run the Linux - commands from inside it instead. +3. **Windows execution policy.** If PowerShell refuses to run `install.ps1`, launch + it with `powershell -ExecutionPolicy Bypass -File ...` (see the Windows install + section). The installer itself needs no admin rights or Developer Mode — it uses + directory junctions and a file copy, not symbolic links. Prefer WSL2? Install it + and run the Linux commands from inside it instead. ## The Loop diff --git a/install.ps1 b/install.ps1 index 861ddcd..d06d852 100644 --- a/install.ps1 +++ b/install.ps1 @@ -5,11 +5,13 @@ .DESCRIPTION Installs GhostAI skills for Claude Code and/or Codex by creating lightweight - wrapper directories of symbolic links that point back at this repository. + wrapper directories that point back at this repository. - This is the PowerShell port of install.sh and produces an identical on-disk - layout, so the bash-based daily update checker and /ghost-update remain - compatible with a PowerShell install. + Unlike install.sh (which uses symbolic links), this script uses directory + junctions for directories and copies SKILL.md, because symbolic links on + Windows require admin or Developer Mode while junctions and file copies need + no special privileges. The resulting skill directories are equivalent: each + contains SKILL.md plus shared/ (and specialists/ and agents/ where present). .PARAMETER Claude Install skills into ~\.claude\skills @@ -30,10 +32,9 @@ .\install.ps1 -All .NOTES - Creating symbolic links on Windows requires either Developer Mode (Settings > - Privacy & security > For developers) or an elevated (Administrator) PowerShell - session. The script checks up front and explains what to do if links can't be - created. + No admin rights or Developer Mode required: directory junctions and file + copies work for a standard user. SKILL.md is copied rather than linked, and + update.ps1 re-copies it on every update so it stays in sync with the repo. #> [CmdletBinding()] param( @@ -93,38 +94,6 @@ if (-not $target) { } } -# Verify we can create symbolic links before touching anything. -function Test-SymlinkSupport { - $base = Join-Path ([System.IO.Path]::GetTempPath()) ('ghostai-symtest-' + [System.Guid]::NewGuid().ToString('N')) - $link = "$base-link" - try { - New-Item -ItemType Directory -Path $base -Force | Out-Null - New-Item -ItemType SymbolicLink -Path $link -Target $base -ErrorAction Stop | Out-Null - return $true - } catch { - return $false - } finally { - if (Test-Path -LiteralPath $link) { Remove-Item -LiteralPath $link -Force -Recurse -ErrorAction SilentlyContinue } - if (Test-Path -LiteralPath $base) { Remove-Item -LiteralPath $base -Force -Recurse -ErrorAction SilentlyContinue } - } -} - -if (-not (Test-SymlinkSupport)) { - Write-Host '' - Write-Host 'Error: Unable to create symbolic links on this system.' -ForegroundColor Red - Write-Host '' - Write-Host 'GhostAI installs by linking each skill back to this repository.' - Write-Host 'Windows allows that in one of two ways:' - Write-Host '' - Write-Host ' 1) Enable Developer Mode (recommended, no admin needed):' - Write-Host ' Settings > Privacy & security > For developers > Developer Mode' - Write-Host '' - Write-Host ' 2) Re-run this script from an elevated PowerShell:' - Write-Host ' right-click PowerShell > "Run as administrator"' - Write-Host '' - exit 1 -} - # Discover the ghost-* skills shipped in this repo. $skills = @() foreach ($dir in Get-ChildItem -LiteralPath $ScriptDir -Directory -Filter 'ghost-*') { @@ -138,33 +107,66 @@ if ($skills.Count -eq 0) { exit 1 } -# Create a symbolic link at $Target pointing to $Source, idempotently. +# Make $Target mirror $Source, idempotently and without elevated privileges. +# - Directory source -> a junction (no admin / Developer Mode needed). +# - File source (SKILL.md) -> a copy. Junctions can't target files, and a +# hard link would go stale when git replaces the file on update; update.ps1 +# re-copies on every update so the copy stays current. function New-GhostLink { param( [Parameter(Mandatory)][string]$Source, [Parameter(Mandatory)][string]$Target ) - $sourceReal = (Resolve-Path -LiteralPath $Source).Path + $sourceItem = Get-Item -LiteralPath $Source -Force + $sourceReal = $sourceItem.FullName + $sourceIsDir = $sourceItem.PSIsContainer - # If the link already points where we want, leave it alone. if (Test-Path -LiteralPath $Target) { $existing = Get-Item -LiteralPath $Target -Force - if ($existing.LinkType -eq 'SymbolicLink' -and $existing.Target) { - $existingTarget = $existing.Target | Select-Object -First 1 + + # Never touch the target when it resolves to the source itself. This is + # the documented install flow: the repo is cloned straight into its + # final location ($HOME\.claude\skills\ghostai) and then linked, so the + # repo-link's Source and Target are the same path. Deleting here would + # wipe the freshly cloned checkout. + $targetReal = $null + try { $targetReal = (Resolve-Path -LiteralPath $Target -ErrorAction Stop).Path } catch { } + if ($targetReal -and $targetReal -eq $sourceReal) { return } + + $isReparse = [bool]($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint) + + # A junction already pointing where we want? Leave it. + if ($sourceIsDir -and $isReparse -and $existing.Target) { + $existingTarget = @($existing.Target)[0] if (-not [System.IO.Path]::IsPathRooted($existingTarget)) { $existingTarget = Join-Path (Split-Path -Parent $Target) $existingTarget } try { $existingTarget = (Resolve-Path -LiteralPath $existingTarget -ErrorAction Stop).Path } catch { } if ($existingTarget -eq $sourceReal) { return } } - } - if (Test-Path -LiteralPath $Target) { - Remove-Item -LiteralPath $Target -Recurse -Force + # Replace it. For a reparse point (junction or a symlink left by an older + # install) delete ONLY the link itself — a non-recursive delete. + # Remove-Item -Recurse on a directory reparse point in Windows PowerShell + # 5.1 follows the link and deletes the target's contents, which would + # destroy the real repo files. + if ($isReparse) { + if ($existing.PSIsContainer) { + [System.IO.Directory]::Delete($existing.FullName, $false) + } else { + [System.IO.File]::Delete($existing.FullName) + } + } else { + Remove-Item -LiteralPath $Target -Recurse -Force + } } - New-Item -ItemType SymbolicLink -Path $Target -Target $sourceReal -Force | Out-Null + if ($sourceIsDir) { + New-Item -ItemType Junction -Path $Target -Target $sourceReal | Out-Null + } else { + Copy-Item -LiteralPath $sourceReal -Destination $Target -Force + } } function Install-Skills {