Files
claude-statusline-kit/install.ps1
T
Andre c239e19145 feat: auto-install a Nerd Font per-user when none is present
Making the installer a turnkey package means a fresh PC should end up
render-ready without manual font setup or an elevated prompt. The font is
placed in the per-user store and registered under HKCU so no admin rights
are required, and a failed download stays non-fatal because the statusline
already renders through the OS emoji fallback. Detection matches both the
spaced and unspaced registry naming so a second run correctly skips the
download instead of re-copying a font file that is locked while in use.
2026-07-18 15:58:41 +02:00

179 lines
7.6 KiB
PowerShell

#requires -Version 5.1
<#
install.ps1 — Deploys the Claude Code "cship" statusline onto this machine.
Idempotent: safe to run repeatedly. Copies the three assets to their target
locations, auto-detects the local pwsh path, and merges the statusLine block
into an existing ~/.claude/settings.json without touching other settings.
#>
[CmdletBinding()]
param(
# Overwrite an existing cship.toml even if one is already present.
[switch]$Force,
# Skip the Nerd-Font check/install entirely.
[switch]$SkipFont,
# Nerd Font to install if none is present (matches a ryanoasis/nerd-fonts
# release asset name). Default 'Hack' is small (~17 MB); others: FiraCode,
# CascadiaCode, JetBrainsMono (large, ~123 MB), SpaceMono, Meslo.
[string]$FontName = 'Hack'
)
$ErrorActionPreference = 'Stop'
$assets = Join-Path $PSScriptRoot 'assets'
function Write-Step { param($m) Write-Host " $m" -ForegroundColor Cyan }
function Write-Ok { param($m) Write-Host " OK $m" -ForegroundColor Green }
function Write-Warn { param($m) Write-Host " !! $m" -ForegroundColor Yellow }
# Install a Nerd Font for the current user only (no admin rights): drop the .ttf
# files into the per-user font store and register them under HKCU. Available to
# apps launched after a terminal restart. Any failure is non-fatal — the
# statusline renders via the OS emoji fallback regardless.
function Install-NerdFont {
param([string]$FontName)
$installed = @()
foreach ($k in @(
'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts',
'HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Fonts')) {
if (Test-Path $k) { $installed += (Get-Item $k).Property }
}
# Match both "Hack Nerd Font" (double-click install) and "HackNerdFont-..."
# (the per-user name we register from the file stem).
if ($installed | Where-Object { $_ -match 'Nerd\s*Font' }) {
Write-Ok "A Nerd Font is already installed - skipping font download."
return
}
$family = "$FontName Nerd Font"
Write-Step "No Nerd Font found. Installing '$family' for the current user (no admin needed)..."
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
} catch { }
$url = "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/$FontName.zip"
$tmp = Join-Path ([IO.Path]::GetTempPath()) ('nf_' + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
$zip = Join-Path $tmp 'font.zip'
try {
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
Expand-Archive -Path $zip -DestinationPath $tmp -Force
} catch {
Write-Warn "Font download/extract failed: $($_.Exception.Message)"
Write-Warn "Skipping font install - the statusline still works via emoji fallback."
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
return
}
# Prefer the base variant (single-width spacing files carry 'Mono'/'Propo').
$ttfs = Get-ChildItem $tmp -Recurse -Filter '*.ttf' | Where-Object { $_.Name -match 'NerdFont-' }
if (-not $ttfs) { $ttfs = Get-ChildItem $tmp -Recurse -Filter '*.ttf' }
$fontDir = Join-Path $env:LOCALAPPDATA 'Microsoft\Windows\Fonts'
New-Item -ItemType Directory -Force -Path $fontDir | Out-Null
$regKey = 'HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Fonts'
if (-not (Test-Path $regKey)) { New-Item -Path $regKey -Force | Out-Null }
$count = 0
foreach ($ttf in $ttfs) {
$dest = Join-Path $fontDir $ttf.Name
try {
Copy-Item $ttf.FullName $dest -Force -ErrorAction Stop
} catch {
# A file already in use (loaded by a running app) is fine - it exists.
if (-not (Test-Path $dest)) { throw }
}
New-ItemProperty -Path $regKey -Name "$($ttf.BaseName) (TrueType)" -Value $dest -PropertyType String -Force | Out-Null
$count++
}
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
if ($count -gt 0) {
Write-Ok "Installed $count '$family' file(s) into the per-user font store."
Write-Warn "To use them, set your terminal font to '$family' and restart the terminal."
} else {
Write-Warn "Archive contained no .ttf files - font skipped."
}
}
Write-Host "`nClaude Code statusline installer" -ForegroundColor White
# --- Target locations -------------------------------------------------------
$localBin = Join-Path $env:USERPROFILE '.local\bin'
$config = Join-Path $env:USERPROFILE '.config'
$claude = Join-Path $env:USERPROFILE '.claude'
foreach ($d in @($localBin, $config, $claude)) {
New-Item -ItemType Directory -Force -Path $d | Out-Null
}
# --- 1. cship.exe -----------------------------------------------------------
Copy-Item (Join-Path $assets 'cship.exe') (Join-Path $localBin 'cship.exe') -Force
Write-Ok "cship.exe -> $localBin"
# --- 2. cship.toml (do not clobber a customised config unless -Force) --------
$tomlTarget = Join-Path $config 'cship.toml'
if ((Test-Path $tomlTarget) -and -not $Force) {
Write-Warn "cship.toml already exists, kept as-is (use -Force to overwrite)"
} else {
Copy-Item (Join-Path $assets 'cship.toml') $tomlTarget -Force
Write-Ok "cship.toml -> $config"
}
# --- 3. Wrapper script ------------------------------------------------------
$wrapperTarget = Join-Path $claude 'cship-statusline.ps1'
Copy-Item (Join-Path $assets 'cship-statusline.ps1') $wrapperTarget -Force
Write-Ok "cship-statusline.ps1 -> $claude"
# --- 4. Locate pwsh ---------------------------------------------------------
$pwsh = (Get-Command pwsh -ErrorAction SilentlyContinue).Source
if (-not $pwsh) {
$fallback = 'C:\Program Files\PowerShell\7\pwsh.exe'
if (Test-Path $fallback) {
$pwsh = $fallback
} else {
Write-Warn "PowerShell 7 (pwsh) not found. Install it, then re-run this script."
Write-Warn " winget install --id Microsoft.PowerShell -e"
$pwsh = $fallback # write the conventional path so it works once pwsh is installed
}
}
$pwshFwd = $pwsh.Replace('\', '/')
$wrapperFwd = $wrapperTarget.Replace('\', '/')
$command = '"{0}" -NoProfile -ExecutionPolicy Bypass -File "{1}"' -f $pwshFwd, $wrapperFwd
# --- 5. Merge statusLine into settings.json ---------------------------------
$settingsPath = Join-Path $claude 'settings.json'
if (Test-Path $settingsPath) {
Copy-Item $settingsPath "$settingsPath.bak" -Force
$settings = Get-Content $settingsPath -Raw | ConvertFrom-Json
} else {
$settings = [PSCustomObject]@{}
}
$statusLine = [PSCustomObject]@{ type = 'command'; command = $command }
if ($settings.PSObject.Properties.Name -contains 'statusLine') {
$settings.statusLine = $statusLine
} else {
$settings | Add-Member -MemberType NoteProperty -Name 'statusLine' -Value $statusLine
}
$settings | ConvertTo-Json -Depth 20 | Set-Content $settingsPath -Encoding UTF8
Write-Ok "settings.json statusLine wired to detected pwsh"
# --- 6. Nerd Font (safety net, per-user) ------------------------------------
if ($SkipFont) {
Write-Warn "Font step skipped (-SkipFont)."
} else {
Install-NerdFont -FontName $FontName
}
# --- Post-install notes -----------------------------------------------------
Write-Host "`nDone." -ForegroundColor Green
Write-Host "Notes:" -ForegroundColor White
Write-Host " -> The statusline itself needs no special font - its icons are standard Unicode"
Write-Host " (emoji + block chars) that render via the OS emoji fallback. The Nerd Font above"
Write-Host " is an optional safety net for the widest glyph coverage."
Write-Host " Restart Claude Code (or start a new session) to see the statusline.`n"