<# .SYNOPSIS Releases the AI lock without uploading data. Reads the lock token from /work/kai-bundle.json by default. .PARAMETER LockToken Override lock token directly. .PARAMETER BundleFile Override bundle file to read token from. .PARAMETER Prod Target production server. .PARAMETER Token Bearer token for prod auth. Falls back to $env:KA_NOTE_TOKEN. .EXAMPLE .\unlock.ps1 .\unlock.ps1 -LockToken "abc-123" .\unlock.ps1 -Prod -Token eyJ... #> param( [string]$LockToken, [string]$BundleFile, [switch]$Prod, [string]$Token ) $ErrorActionPreference = 'Stop' # --- Helpers ----------------------------------------------------------------- function Write-Header([string]$Text) { Write-Host "" Write-Host " $Text" -ForegroundColor White Write-Host (" " + ("-" * $Text.Length)) -ForegroundColor DarkGray } function Write-KV([string]$Key, [string]$Value, [string]$Color = "Gray") { Write-Host (" {0,-14} " -f $Key) -NoNewline -ForegroundColor DarkGray Write-Host $Value -ForegroundColor $Color } function Write-Ok([string]$Msg) { Write-Host " [OK] $Msg" -ForegroundColor Green } function Write-Warn([string]$Msg) { Write-Host " [!!] $Msg" -ForegroundColor Yellow } function Write-Err([string]$Msg) { Write-Host " [!!] $Msg" -ForegroundColor Red } function Write-Hint([string]$Msg) { Write-Host " -> $Msg" -ForegroundColor DarkCyan } # --- Resolve lock token ------------------------------------------------------ $RepoRoot = Resolve-Path "$PSScriptRoot\..\.." $WorkDir = Join-Path $RepoRoot "work" # Support legacy BundleFile param and new manifest.json if (-not $BundleFile) { $BundleFile = Join-Path $WorkDir "manifest.json" } if (-not $LockToken) { if (Test-Path $BundleFile) { $BundleContent = Get-Content $BundleFile -Raw -Encoding UTF8 | ConvertFrom-Json # manifest.json has lockToken directly; legacy kai-bundle.json has manifest.lockToken $LockToken = if ($BundleContent.lockToken) { $BundleContent.lockToken } else { $BundleContent.manifest.lockToken } $TokenSource = $BundleFile } if (-not $LockToken) { Write-Err "No lock token found. Pass -LockToken or run .\download.ps1 first." exit 1 } } # --- Server ------------------------------------------------------------------ if ($Prod) { $BaseUrl = "https://ka-note.azurewebsites.net" $BearerToken = if ($Token) { $Token } elseif ($env:KA_NOTE_TOKEN) { $env:KA_NOTE_TOKEN } else { Write-Host " Acquiring token via MSAL..." -ForegroundColor DarkGray & "$PSScriptRoot\get-token.ps1" } if (-not $BearerToken) { Write-Err "Failed to acquire a Bearer token." exit 1 } $Headers = @{ Authorization = "Bearer $BearerToken"; "Content-Type" = "application/json" } $Env = "prod" } else { $BaseUrl = "http://localhost:3001" $Headers = @{ "Content-Type" = "application/json" } $Env = "dev" } Write-Header "Ka-Note AI Unlock" Write-KV "server" $BaseUrl $(if ($Prod) { "Yellow" } else { "Cyan" }) Write-KV "mode" $Env Write-KV "lock" $LockToken "Yellow" if ($TokenSource) { Write-KV "from" $TokenSource } Write-Host "" Write-Host " Unlocking..." -ForegroundColor DarkGray $Json = @{ lockToken = $LockToken } | ConvertTo-Json -Compress try { Invoke-WebRequest -Uri "$BaseUrl/api/ai/unlock" ` -Method POST -Headers $Headers -Body $Json -UseBasicParsing | Out-Null Write-Host "" Write-Ok "Lock released. Sync is active again." Write-Host "" } catch { $StatusCode = $_.Exception.Response.StatusCode.value__ Write-Host "" if ($StatusCode -eq 401) { Write-Warn "401 — lock token invalid or already expired (lock may have auto-expired)." } else { Write-Err "HTTP $StatusCode — $($_.Exception.Message)" exit 1 } Write-Host "" }