Files
claude-mode/install.ps1
T
smoido 112068314c Import claude-code-switcher from the Windows build
Source of truth so far has been c:\Users\smoido\projects\cli on the Windows
box, which has no git history of its own. This is that tree copied verbatim over
SSH, minus dist/ - the PowerShell build, the POSIX port under linux/, and the
presets both share.

Recorded as its own commit so that everything after it is a reviewable diff
rather than an undifferentiated first drop.
2026-08-30 21:05:48 +03:00

145 lines
6.8 KiB
PowerShell

<#
One-time installer for claude-mode.
Installs to %USERPROFILE%\.claude-mode, drops a cmd.exe shim on PATH, and
wires `claude-mode` + a `claude` wrapper into the PowerShell profile.
Idempotent: safe to re-run to upgrade. Existing presets are not overwritten
unless -Force is passed. Your ~/.claude/settings.json is NOT touched here -
that only happens when you actually run `claude-mode openrouter|anthropic`.
#>
[CmdletBinding()]
param(
[switch] $Force, # overwrite existing presets
[switch] $SkipKeyPrompt # do not prompt for the OpenRouter key
)
Set-StrictMode -Version 1.0
$ErrorActionPreference = 'Stop'
$src = Split-Path -Parent $MyInvocation.MyCommand.Path
$root = Join-Path $env:USERPROFILE '.claude-mode'
Write-Host "installing claude-mode -> $root" -ForegroundColor Cyan
# --- 1. directories ---------------------------------------------------------
foreach ($d in @($root, "$root\presets", "$root\vault", "$root\backups", "$root\bin")) {
if (-not (Test-Path -LiteralPath $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null }
}
# Lock the whole tree to the current user. The vault is DPAPI-encrypted on top
# of this, but there is no reason for the directory to be broadly readable.
try {
$acl = Get-Acl -LiteralPath $root
if ($acl.AreAccessRulesProtected) {
# Already locked down by a previous run. Re-applying would need
# SeSecurityPrivilege on some systems, so leave it alone.
Write-Host ' ok .claude-mode ACL already locked to current user' -ForegroundColor Green
throw [System.OperationCanceledException]::new('already-protected')
}
$acl.SetAccessRuleProtection($true, $false)
foreach ($r in @($acl.Access)) { [void]$acl.RemoveAccessRule($r) }
$me = New-Object System.Security.Principal.NTAccount($env:USERDOMAIN, $env:USERNAME)
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
$me, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')))
Set-Acl -LiteralPath $root -AclObject $acl
Write-Host ' ok locked .claude-mode ACL to current user' -ForegroundColor Green
} catch [System.OperationCanceledException] {
# already protected - nothing to do
} catch {
Write-Host " warn ACL hardening failed: $($_.Exception.Message)" -ForegroundColor Yellow
}
# --- 2. scripts -------------------------------------------------------------
Copy-Item -LiteralPath (Join-Path $src 'claude-mode.ps1') -Destination $root -Force
# claude-mode reads VERSION from its own directory to stamp health.json.
if (Test-Path -LiteralPath (Join-Path $src 'VERSION')) {
Copy-Item -LiteralPath (Join-Path $src 'VERSION') -Destination $root -Force
}
Copy-Item -LiteralPath (Join-Path $src 'bin\claude-key-helper.ps1') -Destination "$root\bin" -Force
Copy-Item -LiteralPath (Join-Path $src 'bin\claude-key-helper.cmd') -Destination "$root\bin" -Force
Write-Host ' ok copied claude-mode.ps1 + key helper' -ForegroundColor Green
# --- 3. presets -------------------------------------------------------------
foreach ($p in Get-ChildItem -LiteralPath (Join-Path $src 'presets') -Filter '*.json') {
$dest = Join-Path "$root\presets" $p.Name
if ((Test-Path -LiteralPath $dest) -and -not $Force) {
Write-Host " skip preset $($p.BaseName) (exists; -Force to overwrite)" -ForegroundColor DarkGray
} else {
Copy-Item -LiteralPath $p.FullName -Destination $dest -Force
Write-Host " ok preset $($p.BaseName)" -ForegroundColor Green
}
}
# --- 4. initial state (native mode; settings.json untouched) ----------------
$statePath = Join-Path $root 'state.json'
if (-not (Test-Path -LiteralPath $statePath)) {
@{ mode = 'anthropic'; preset = 'default'; updated = (Get-Date).ToString('o') } |
ConvertTo-Json | Set-Content -LiteralPath $statePath -Encoding UTF8
Write-Host ' ok state.json initialised (mode=anthropic)' -ForegroundColor Green
}
# --- 5. cmd.exe / PATH shim -------------------------------------------------
$binDir = Join-Path $env:USERPROFILE '.local\bin'
if (-not (Test-Path -LiteralPath $binDir)) { New-Item -ItemType Directory -Path $binDir -Force | Out-Null }
$shim = Join-Path $binDir 'claude-mode.cmd'
@"
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\.claude-mode\claude-mode.ps1" %*
"@ | Set-Content -LiteralPath $shim -Encoding ASCII
Write-Host " ok shim $shim" -ForegroundColor Green
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath -notlike "*$binDir*") {
[Environment]::SetEnvironmentVariable('Path', "$userPath;$binDir", 'User')
Write-Host " ok added $binDir to User PATH (new terminals only)" -ForegroundColor Green
}
# --- 6. PowerShell profile --------------------------------------------------
$profilePath = $PROFILE.CurrentUserAllHosts
$profileDir = Split-Path -Parent $profilePath
if (-not (Test-Path -LiteralPath $profileDir)) { New-Item -ItemType Directory -Path $profileDir -Force | Out-Null }
$snippet = Get-Content -LiteralPath (Join-Path $src 'profile-snippet.ps1') -Raw
$current = ''
if (Test-Path -LiteralPath $profilePath) { $current = Get-Content -LiteralPath $profilePath -Raw }
$startMark = '# >>> claude-mode >>>'
$endMark = '# <<< claude-mode <<<'
if ($current -match [regex]::Escape($startMark)) {
$pattern = '(?s)' + [regex]::Escape($startMark) + '.*?' + [regex]::Escape($endMark)
$current = [regex]::Replace($current, $pattern, $snippet.TrimEnd())
Set-Content -LiteralPath $profilePath -Value $current -Encoding UTF8
Write-Host " ok updated claude-mode block in $profilePath" -ForegroundColor Green
} else {
Add-Content -LiteralPath $profilePath -Value ("`r`n" + $snippet) -Encoding UTF8
Write-Host " ok appended claude-mode block to $profilePath" -ForegroundColor Green
}
# --- 7. execution policy check ---------------------------------------------
$pol = Get-ExecutionPolicy -Scope CurrentUser
if ($pol -in @('Restricted', 'Undefined', 'AllSigned')) {
Write-Host " warn CurrentUser execution policy is '$pol'; the profile will not load." -ForegroundColor Yellow
Write-Host " Fix: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned" -ForegroundColor Yellow
}
# --- 8. key ----------------------------------------------------------------
if (-not $SkipKeyPrompt) {
$vault = Join-Path $root 'vault\openrouter.cred'
if ((Test-Path -LiteralPath $vault) -and -not $Force) {
Write-Host ' ok OpenRouter key already stored' -ForegroundColor Green
} else {
Write-Host ''
& (Join-Path $root 'claude-mode.ps1') set-key openrouter
}
}
Write-Host ''
Write-Host 'done. Open a NEW terminal, then:' -ForegroundColor Cyan
Write-Host ' claude-mode status'
Write-Host ' claude-mode openrouter default'
Write-Host ' claude-mode doctor'
Write-Host ' claude-mode anthropic'