mirror of
https://github.com/Andre-Bzk/claude-statusline-kit.git
synced 2026-11-15 09:33:04 +01:00
The installer wrote a hardcoded pwsh.exe path into settings.json even when PowerShell 7 was not installed, so Claude Code ran a nonexistent binary and the statusline silently failed to render. Now detect the available host (prefer pwsh 7, fall back to Windows PowerShell 5.1 which ships with every Windows install) and add an end-to-end self-test so a broken host path is caught at install time instead of silently at runtime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C4QKwZ7bC7oCMVtnuJdDKm
213 lines
9.7 KiB
PowerShell
213 lines
9.7 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 a PowerShell host --------------------------------------------
|
|
# Prefer PowerShell 7 (pwsh). If it isn't installed, fall back to Windows
|
|
# PowerShell 5.1 (powershell.exe) which ships with every Windows install and
|
|
# runs the wrapper correctly - so the statusline works out of the box instead
|
|
# of pointing at a non-existent pwsh.exe and silently rendering nothing.
|
|
$pwsh = (Get-Command pwsh -ErrorAction SilentlyContinue).Source
|
|
if (-not $pwsh -and (Test-Path 'C:\Program Files\PowerShell\7\pwsh.exe')) {
|
|
$pwsh = 'C:\Program Files\PowerShell\7\pwsh.exe'
|
|
}
|
|
if (-not $pwsh) {
|
|
$pwsh = (Get-Command powershell -ErrorAction SilentlyContinue).Source
|
|
if (-not $pwsh) {
|
|
$pwsh = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
|
}
|
|
Write-Warn "PowerShell 7 (pwsh) not found - using Windows PowerShell 5.1."
|
|
Write-Warn " For sharper rendering, install PS7 and re-run this script:"
|
|
Write-Warn " winget install --id Microsoft.PowerShell -e"
|
|
}
|
|
$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 $([IO.Path]::GetFileName($pwsh))"
|
|
|
|
# --- 5b. End-to-end self-test -----------------------------------------------
|
|
# Actually run the configured host + wrapper against a sample Status payload so
|
|
# a broken host path (the classic "no statusline appears" failure) is caught
|
|
# here rather than silently at runtime, where Claude Code just shows nothing.
|
|
$sample = '{"hook_event_name":"Status","model":{"display_name":"Opus"},' +
|
|
'"workspace":{"current_dir":"' + ($claude -replace '\\','/') + '",' +
|
|
'"project_dir":"' + ($claude -replace '\\','/') + '"},' +
|
|
'"context_window":{"used_tokens":1000}}'
|
|
try {
|
|
$probe = $sample | & $pwsh -NoProfile -ExecutionPolicy Bypass -File $wrapperTarget 2>$null
|
|
if ([string]::IsNullOrWhiteSpace(($probe -join ''))) {
|
|
Write-Warn "Self-test produced no output - the statusline may not render."
|
|
Write-Warn " Check that '$pwsh' runs and that cship.exe is present."
|
|
} else {
|
|
Write-Ok "Self-test passed - statusline renders."
|
|
}
|
|
} catch {
|
|
Write-Warn "Self-test could not run the host: $($_.Exception.Message)"
|
|
}
|
|
|
|
# --- 6. Nerd Font (safety net, per-user) ------------------------------------
|
|
if ($SkipFont) {
|
|
Write-Warn "Font step skipped (-SkipFont)."
|
|
} else {
|
|
Install-NerdFont -FontName $FontName
|
|
}
|
|
|
|
# --- Glyph self-test --------------------------------------------------------
|
|
# The only thing this script cannot check is whether the terminal actually
|
|
# draws the glyphs. Print the exact symbol set so the user can eyeball it: if
|
|
# any show as boxes/question marks, the terminal (not the font) is the problem.
|
|
Write-Host "`nGlyph self-test - these should all be real symbols, not boxes:" -ForegroundColor White
|
|
Write-Host " 🤖 ⚡ 🧠 🔢 ⏰ ⏳ 📅 █ ░" -ForegroundColor Cyan
|
|
Write-Host " If any appear as boxes/? , switch to Windows Terminal (Win11 default)."
|
|
|
|
# --- Post-install notes -----------------------------------------------------
|
|
Write-Host "`nDone." -ForegroundColor Green
|
|
Write-Host "Notes:" -ForegroundColor White
|
|
Write-Host " -> Symbols are standard Unicode (emoji + block chars). Windows Terminal renders"
|
|
Write-Host " them via Segoe UI Emoji by default - no special font needed. The Nerd Font"
|
|
Write-Host " above is an optional safety net."
|
|
Write-Host " Restart Claude Code (or start a new session) to see the statusline.`n"
|