Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,28 +88,26 @@ 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
& $HOME\.claude\skills\ghostai\install.ps1 -Claude
```

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.
Expand Down Expand 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

Expand Down
100 changes: 51 additions & 49 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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-*') {
Expand All @@ -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 {
Expand Down