← Files Cache StatsARCHIVED FILE
skills/cache-stats/scripts/install-cache-stats.ps1
5.99 KB · Oct 2, 2026 · 00:30 UTC
[CmdletBinding()]
param(
[switch]$ConfirmInstall,
[string]$InstallRoot = (Join-Path $env:LOCALAPPDATA 'OpenAI\CacheStats'),
[string]$ConfigPath = (Join-Path $(if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $env:USERPROFILE '.codex' }) 'config.toml')
)
$ErrorActionPreference = 'Stop'
$productId = 'openai-cache-stats-local'
$version = '0.4.1'
$serverName = 'cache-stats-local'
$beginMarker = '# BEGIN CACHE STATS LOCAL - managed by openai-cache-stats-local'
$endMarker = '# END CACHE STATS LOCAL - managed by openai-cache-stats-local'
if (-not $ConfirmInstall) {
throw 'Installation requires explicit confirmation. Re-run with -ConfirmInstall only after the user approves the disclosed local changes.'
}
if ($env:OS -ne 'Windows_NT') { throw 'Cache Stats 0.4.1 supports ChatGPT for Windows only.' }
$skillRoot = Split-Path -Parent $PSScriptRoot
$payloadRoot = Join-Path $skillRoot 'assets\runtime'
$hashFile = Join-Path $payloadRoot 'payload.sha256'
$required = @('server.ps1', 'ui\cache-card.html', 'LICENSE', 'payload.sha256')
foreach ($relative in $required) {
if (-not (Test-Path -LiteralPath (Join-Path $payloadRoot $relative) -PathType Leaf)) {
throw "The reviewed runtime payload is incomplete: $relative is missing."
}
}
foreach ($entry in (Get-Content -LiteralPath $hashFile | Where-Object { $_.Trim() })) {
if ($entry -notmatch '^([A-Fa-f0-9]{64})\s+\*(.+)$') { throw "Invalid payload hash entry: $entry" }
$expected = $matches[1].ToUpperInvariant()
$relative = $matches[2].Replace('/', [IO.Path]::DirectorySeparatorChar)
$candidate = Join-Path $payloadRoot $relative
if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { throw "A hashed payload file is missing: $relative" }
if ((Get-FileHash -LiteralPath $candidate -Algorithm SHA256).Hash -ne $expected) {
throw "Payload integrity check failed for $relative."
}
}
$installRootFull = [IO.Path]::GetFullPath($InstallRoot)
$configPathFull = [IO.Path]::GetFullPath($ConfigPath)
$destination = Join-Path $installRootFull $version
$receiptPath = Join-Path $installRootFull 'install.json'
$existingReceipt = $null
if (Test-Path -LiteralPath $receiptPath -PathType Leaf) {
$existingReceipt = Get-Content -LiteralPath $receiptPath -Raw | ConvertFrom-Json
if ($existingReceipt.productId -ne $productId -or $existingReceipt.serverName -ne $serverName) {
throw 'The existing install receipt is not owned by this Cache Stats package. Refusing to replace it.'
}
}
$configText = if (Test-Path -LiteralPath $configPathFull -PathType Leaf) {
[IO.File]::ReadAllText($configPathFull)
} else { '' }
$managedPattern = '(?ms)^' + [regex]::Escape($beginMarker) + '.*?^' + [regex]::Escape($endMarker) + '(?:\r?\n)?'
$hasManagedBlock = [regex]::IsMatch($configText, $managedPattern)
$hasNamedSection = $configText -match '(?m)^\s*\[mcp_servers\.cache-stats-local\]\s*$'
if ($hasNamedSection -and -not $hasManagedBlock) {
throw "An MCP registration named $serverName already exists and is not owned by this installer. Refusing to replace it."
}
if ($hasManagedBlock -and -not $existingReceipt) {
throw 'A managed Cache Stats configuration block exists without its install receipt. Refusing to guess ownership.'
}
if ($existingReceipt -and [IO.Path]::GetFullPath([string]$existingReceipt.configPath) -ne $configPathFull) {
throw 'The existing install receipt belongs to a different ChatGPT/Codex configuration file.'
}
New-Item -ItemType Directory -Path (Join-Path $destination 'ui') -Force | Out-Null
Copy-Item -LiteralPath (Join-Path $payloadRoot 'server.ps1') -Destination (Join-Path $destination 'server.ps1') -Force
Copy-Item -LiteralPath (Join-Path $payloadRoot 'ui\cache-card.html') -Destination (Join-Path $destination 'ui\cache-card.html') -Force
Copy-Item -LiteralPath (Join-Path $payloadRoot 'LICENSE') -Destination (Join-Path $destination 'LICENSE') -Force
Copy-Item -LiteralPath $hashFile -Destination (Join-Path $destination 'payload.sha256') -Force
$serverPath = Join-Path $destination 'server.ps1'
$serverPathJson = $serverPath | ConvertTo-Json -Compress
$block = @(
$beginMarker,
'[mcp_servers.cache-stats-local]',
'command = "powershell.exe"',
"args = [`"-NoLogo`", `"-NoProfile`", `"-ExecutionPolicy`", `"Bypass`", `"-File`", $serverPathJson]",
'env_vars = ["CODEX_HOME", "CODEX_THREAD_ID", "USERPROFILE"]',
'tool_timeout_sec = 30',
'enabled = true',
'default_tools_approval_mode = "auto"',
$endMarker
) -join "`r`n"
$configWithoutManagedBlock = [regex]::Replace($configText, $managedPattern, '').TrimEnd("`r", "`n")
$newConfig = if ($configWithoutManagedBlock) { $configWithoutManagedBlock + "`r`n`r`n" + $block + "`r`n" } else { $block + "`r`n" }
$configDirectory = Split-Path -Parent $configPathFull
New-Item -ItemType Directory -Path $configDirectory -Force | Out-Null
$backupPath = $null
if (Test-Path -LiteralPath $configPathFull -PathType Leaf) {
$backupPath = $configPathFull + '.cache-stats-backup-' + [DateTimeOffset]::UtcNow.ToString('yyyyMMddHHmmssfff')
Copy-Item -LiteralPath $configPathFull -Destination $backupPath
}
$temporaryConfig = Join-Path $configDirectory ('.cache-stats-config-' + [Guid]::NewGuid().ToString('N') + '.tmp')
try {
[IO.File]::WriteAllText($temporaryConfig, $newConfig, [Text.UTF8Encoding]::new($false))
Move-Item -LiteralPath $temporaryConfig -Destination $configPathFull -Force
} finally {
if (Test-Path -LiteralPath $temporaryConfig) { Remove-Item -LiteralPath $temporaryConfig -Force }
}
$receipt = [ordered]@{
productId = $productId
version = $version
serverName = $serverName
installRoot = $installRootFull
serverPath = $serverPath
configPath = $configPathFull
configBackupPath = $backupPath
installedAt = [DateTimeOffset]::UtcNow.ToString('o')
}
$receipt | ConvertTo-Json | Set-Content -LiteralPath $receiptPath -Encoding UTF8
Write-Output "Cache Stats $version was installed locally and registered as $serverName."
Write-Output 'Restart ChatGPT for Windows and open a new task before invoking Cache Stats.'
SHA-256: 11aa74057b211f16ebf596af6775bd01c17485fe19dcc658557f82356b142c36