# Forge source/dev local app bootstrap for Windows. # Signed native installers are not published yet. This script installs the # current source/dev path and opens the real Forge desktop app window. Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" Write-Host "Forge is preparing your local AI operating system." Write-Host "From one command to local Forge app and AI agent runtime." $script:ForgeStepNumber = 0 function Invoke-ForgeStep { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][scriptblock]$Action ) $script:ForgeStepNumber += 1 Write-Host "" Write-Host "Step $script:ForgeStepNumber - $Name" & $Action if ($LASTEXITCODE -ne 0) { throw "Step failed: $Name" } } function Get-NodeMajor { try { $version = (& node --version 2>$null) if (-not $version) { return $null } return [int]($version.TrimStart("v").Split(".")[0]) } catch { return $null } } function Write-ForgeInstallArtifacts { param([Parameter(Mandatory = $true)][string]$InstallRoot) $stateRoot = if ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA "Forge\local-app-control-room" } else { Join-Path $env:USERPROFILE ".forge\local-app-control-room" } New-Item -ItemType Directory -Force -Path $stateRoot | Out-Null $createdAt = (Get-Date).ToUniversalTime().ToString("o") $receiptPath = Join-Path $stateRoot "install-receipt.json" $passportPath = Join-Path $stateRoot "activation-passport.local.json" [ordered]@{ schema = "forge.local-first-control-room.install-receipt.v1" status = "installed_source_dev_local_app" createdAt = $createdAt installPath = $InstallRoot openCommand = "npm run forge:local-first -- dev-app" appUrl = "http://127.0.0.1:3417" appUrlSource = "current-app.json" launcherOpensActualAppUrl = $true signedNativeInstallerStatus = "deferred_external_signing_artifacts" localAppRequiresFullNpmInstall = $false } | ConvertTo-Json -Depth 5 | Set-Content -Encoding UTF8 -Path $receiptPath [ordered]@{ schema = "forge.local-first-control-room.activation-passport.v1" status = "local_only_source_dev" createdAt = $createdAt installPath = $InstallRoot accountSignInRequired = $false providerSecretsIncluded = $false plainEnglish = "Forge can open locally now. Sign in later only when account or licensed capability is needed." } | ConvertTo-Json -Depth 5 | Set-Content -Encoding UTF8 -Path $passportPath return @{ ReceiptPath = $receiptPath; PassportPath = $passportPath } } function New-ForgeLauncher { param([Parameter(Mandatory = $true)][string]$InstallRoot) $launcherPath = Join-Path $InstallRoot "Open-Forge.ps1" $safeInstallRoot = $InstallRoot.Replace("'", "''") $stateRoot = if ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA "Forge\local-app-control-room" } else { Join-Path $env:USERPROFILE ".forge\local-app-control-room" } $safeStateRoot = $stateRoot.Replace("'", "''") $launcher = @" Set-StrictMode -Version Latest `$ErrorActionPreference = "Stop" `$installRoot = '$safeInstallRoot' `$stateRoot = '$safeStateRoot' `$currentAppPath = Join-Path `$stateRoot "current-app.json" `$nativeLauncherProofPath = Join-Path `$stateRoot "windows-native-launcher.json" `$fallbackUrl = "http://127.0.0.1:3417" `$launcherEnginePath = Join-Path `$installRoot "scripts\launch-platform-app-shell.mjs" function Write-ForgeNativeLauncherProof { param( [Parameter(Mandatory = `$true)][string]`$Status, [string]`$Detail = "", [string]`$AppUrl = "", [string]`$LauncherProofPath = "" ) if (-not (Test-Path `$stateRoot)) { New-Item -ItemType Directory -Force -Path `$stateRoot | Out-Null } [ordered]@{ schema = "forge.local-first-control-room.windows-native-launcher.v1" status = `$Status createdAt = (Get-Date).ToUniversalTime().ToString("o") launcherKind = "powershell_generated_installed_entrypoint" installRoot = `$installRoot stateRoot = `$stateRoot launcherEnginePath = `$launcherEnginePath currentAppPath = `$currentAppPath appUrl = `$AppUrl launcherProofPath = `$LauncherProofPath detail = `$Detail secretSafe = `$true } | ConvertTo-Json -Depth 5 | Set-Content -Encoding UTF8 -Path `$nativeLauncherProofPath } function Get-ForgeAppUrl { try { if (Test-Path `$currentAppPath) { `$current = Get-Content -Raw -Path `$currentAppPath | ConvertFrom-Json if (`$current.url) { return [string]`$current.url } } } catch {} return `$fallbackUrl } function Wait-ForgeApp { for (`$i = 0; `$i -lt 60; `$i++) { `$appUrl = Get-ForgeAppUrl try { `$response = Invoke-WebRequest -Uri `$appUrl -UseBasicParsing -TimeoutSec 1 if (`$response.StatusCode -eq 200) { return `$appUrl } } catch {} Start-Sleep -Seconds 1 } return Get-ForgeAppUrl } function Test-ForgeAppUrl { param([Parameter(Mandatory = `$true)][string]`$Url) try { `$response = Invoke-WebRequest -Uri `$Url -UseBasicParsing -TimeoutSec 1 return `$response.StatusCode -eq 200 } catch { return `$false } } Set-Location `$installRoot if (-not (Test-Path `$launcherEnginePath)) { Write-ForgeNativeLauncherProof -Status "failed_closed" -Detail "Forge desktop app launcher is missing: `$launcherEnginePath" throw "Forge desktop app launcher is missing: `$launcherEnginePath" } `$env:FORGE_INSTALL_ROOT = `$installRoot `$env:FORGE_LOCAL_APP_STATE_DIR = `$stateRoot Write-ForgeNativeLauncherProof -Status "started" & node `$launcherEnginePath --repo-root="`$installRoot" --json if (`$LASTEXITCODE -ne 0) { Write-ForgeNativeLauncherProof -Status "failed_closed" -Detail "Forge desktop app launcher failed with exit code `$LASTEXITCODE" throw "Forge desktop app launcher failed with exit code `$LASTEXITCODE" } `$appUrl = Get-ForgeAppUrl `$proofPath = Join-Path `$stateRoot "launcher-proof.json" Write-ForgeNativeLauncherProof -Status "completed" -AppUrl `$appUrl -LauncherProofPath `$proofPath if (`$appUrl -eq `$fallbackUrl -and -not (Test-Path `$currentAppPath)) { Write-Host "Forge is starting. If the desktop app opened before the URL was recorded, wait a moment and reopen Forge." } Write-Host "Forge desktop app opened at `$appUrl" Write-Host "Launcher proof: `$proofPath" "@ Set-Content -Encoding UTF8 -Path $launcherPath -Value $launcher $created = @() try { $shell = New-Object -ComObject WScript.Shell $desktop = [Environment]::GetFolderPath("Desktop") $startMenu = Join-Path ([Environment]::GetFolderPath("StartMenu")) "Programs" foreach ($shortcutPath in @((Join-Path $desktop "Forge.lnk"), (Join-Path $startMenu "Forge.lnk"))) { if (-not (Test-Path (Split-Path $shortcutPath))) { New-Item -ItemType Directory -Force -Path (Split-Path $shortcutPath) | Out-Null } $shortcut = $shell.CreateShortcut($shortcutPath) $shortcut.TargetPath = "powershell.exe" $shortcut.Arguments = "-WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File `"$launcherPath`"" $shortcut.WorkingDirectory = $InstallRoot $shortcut.Description = "Open Forge desktop app" $shortcut.IconLocation = "shell32.dll,13" $shortcut.Save() $created += $shortcutPath } } catch { Write-Host "Shortcut creation skipped: $($_.Exception.Message)" } return @{ LauncherPath = $launcherPath; Shortcuts = $created } } $repoUrl = $env:FORGE_REPO_URL if (-not $repoUrl) { $repoUrl = "https://github.com/mastermers-spec/forge-v7.git" } $installRoot = Join-Path $env:USERPROFILE "forge" $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" $nodeMajor = Get-NodeMajor if ($null -eq $nodeMajor) { Write-Error "Node.js was not found. Install Node.js 22 LTS, then run this script again." exit 1 } if ($nodeMajor -lt 22) { Write-Error "Forge source/dev mode expects Node.js 22 LTS or newer. Detected Node.js major version $nodeMajor." exit 1 } if ($nodeMajor -ge 24) { Write-Host "Warning: Node.js 24 can fail on native dependencies such as isolated-vm during full installs." Write-Host "The local Forge app bootstrap avoids full npm install. Use Node.js 22 LTS for full source/dev work." } if (Test-Path $installRoot) { if (Test-Path (Join-Path $installRoot ".git")) { $dirty = (& git -C $installRoot status --porcelain) $updateFailed = $false if ($dirty) { $updateFailed = $true Write-Host "Existing Forge checkout has local changes, so Forge will not overwrite it." } else { Write-Host "" Write-Host "==> Update existing Forge checkout at $installRoot" git -C $installRoot fetch origin main --prune if ($LASTEXITCODE -ne 0) { $updateFailed = $true } if (-not $updateFailed) { git -C $installRoot switch main if ($LASTEXITCODE -ne 0) { git -C $installRoot switch -c main --track origin/main } if ($LASTEXITCODE -ne 0) { $updateFailed = $true } } if (-not $updateFailed) { git -C $installRoot pull --ff-only origin main if ($LASTEXITCODE -ne 0) { $updateFailed = $true } } } if ($updateFailed) { $previousRoot = $installRoot $installRoot = Join-Path $env:USERPROFILE "forge-source-$timestamp" Write-Host "Could not safely update $previousRoot without risking local work or stale branch state." Write-Host "Creating a fresh Forge folder instead: $installRoot" Invoke-ForgeStep "Clone Forge source/dev checkout" { git clone $repoUrl $installRoot } } } else { $installRoot = Join-Path $env:USERPROFILE "forge-source-$timestamp" Write-Host "C:\Users\...\forge already exists and is not a Git checkout." Write-Host "Creating a new Forge folder instead: $installRoot" Invoke-ForgeStep "Clone Forge source/dev checkout" { git clone $repoUrl $installRoot } } } else { Invoke-ForgeStep "Clone Forge source/dev checkout" { git clone $repoUrl $installRoot } } Set-Location $installRoot Invoke-ForgeStep "Check the local Forge app path" { npm run forge:local-first -- status } Invoke-ForgeStep "Run Forge doctor" { npm run forge:local-first -- doctor } Invoke-ForgeStep "Create Forge app launcher and local receipts" { $artifacts = Write-ForgeInstallArtifacts -InstallRoot $installRoot $launcher = New-ForgeLauncher -InstallRoot $installRoot $script:ForgeLauncher = $launcher Write-Host "Install receipt: $($artifacts.ReceiptPath)" Write-Host "Local activation passport: $($artifacts.PassportPath)" Write-Host "Launcher script: $($launcher.LauncherPath)" if ($launcher.Shortcuts.Count -gt 0) { Write-Host "Created shortcut(s):" $launcher.Shortcuts | ForEach-Object { Write-Host " $_" } } else { Write-Host "No shortcut was created automatically; use the launcher script above." } } Invoke-ForgeStep "Starting Forge and opening the app UI" { $launcherPath = $script:ForgeLauncher.LauncherPath & $launcherPath } Write-Host "" Write-Host "Forge is ready." Write-Host "Forge is installed at: $installRoot" Write-Host "Open Forge now or later:" Write-Host " Click the Forge shortcut on your Desktop or Start Menu. It starts Forge and opens the Forge desktop app." Write-Host " cd $installRoot" Write-Host " node scripts\launch-platform-app-shell.mjs --repo-root=`"$installRoot`"" Write-Host "If the Forge desktop app does not open, check the current app URL in:" Write-Host " $env:LOCALAPPDATA\Forge\local-app-control-room\current-app.json" Write-Host "The current app URL is diagnostic fallback only; the primary launcher opens the Forge desktop app." Write-Host "" Write-Host "Signed Windows/macOS/Linux installers remain unavailable until signing credentials and release artifacts are verified."