[CmdletBinding()] param( [Parameter(Mandatory=$true)][string]$BuildManifestUrl, [Parameter(Mandatory=$true)][string]$TargetDrive, [switch]$SkipModelDownloads ) $ErrorActionPreference = "Stop" if ($TargetDrive.Length -eq 2 -and $TargetDrive[1] -eq ':') { $TargetDrive = "$TargetDrive\" } if (!(Test-Path $TargetDrive)) { throw "Target drive not found: $TargetDrive" } $manifest = Invoke-RestMethod -Uri $BuildManifestUrl if ($manifest.product -ne "JackAILocal") { throw "Wrong product manifest." } $Root = (Resolve-Path $TargetDrive).Path New-Item -ItemType Directory -Force -Path "$Root\models\ollama", "$Root\models\gguf", "$Root\backends", "$Root\bin", "$Root\manifest" | Out-Null # Copy seed runtime from the builder folder if present. $SeedRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") robocopy $SeedRoot $Root /E /XD ".git" ".jackailocal" "target" | Out-Null foreach ($asset in $manifest.assets) { $dest = Join-Path $Root $asset.path New-Item -ItemType Directory -Force -Path (Split-Path $dest) | Out-Null if ($asset.url -and !(Test-Path $dest)) { Invoke-WebRequest -Uri $asset.url -OutFile $dest } if ($asset.sha256) { $h = (Get-FileHash -Algorithm SHA256 $dest).Hash.ToLowerInvariant() if ($h -ne $asset.sha256.ToLowerInvariant()) { throw "Checksum failed for $($asset.path)" } } } if (-not $SkipModelDownloads) { $OllamaExe = Join-Path $Root "backends\ollama\windows\ollama.exe" if (Test-Path $OllamaExe) { $env:OLLAMA_MODELS = Join-Path $Root "models\ollama" foreach ($m in $manifest.ollama_models) { & $OllamaExe pull $m } } } $manifest | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 (Join-Path $Root "manifest\build-manifest.json") Write-Host "JackAILocal key built at $Root" Write-Host "Use START-HERE.cmd on the key."