#!/usr/bin/env pwsh # L0b Factory Bootstrap — Windows # Auto-generated from install-log.md field-test 2026-05-06 $ErrorActionPreference = "Stop" $PSVersionTable.PSVersion.Major -ge 7 -or (Write-Error "Bootstrap requires PowerShell 7+. Install: winget install Microsoft.PowerShell"; exit 1) $GITEA_DEFAULT = "https://git.kzntsv.site" $ORG_DEFAULT = "OpeItcLoc03" $FACTORY_REPO_DEFAULT = "factory" $HOME_TOML = "$env:USERPROFILE\.config\factory\home.toml" $PROJECTS_DIR_DEFAULT = "C:\Users\$env:USERNAME\projects" function Test-Command { param([string]$Name) $null = Get-Command $Name -ErrorAction SilentlyContinue } function Read-FactoryManifest { param([string]$FactoryPath) $yamlPath = "$FactoryPath\factory.yaml" if (-not (Test-Path $yamlPath)) { Write-Gap "factory.yaml not found at $yamlPath" return $null } $lines = Get-Content $yamlPath $manifest = @{ schema_version = "" factory_version = "" core_components = [System.Collections.Generic.List[hashtable]]::new() modules = [System.Collections.Generic.List[hashtable]]::new() } $section = "" $currentItem = $null $propPrefix = "" # exact indent of direct item properties (avoids nested key collisions) foreach ($line in $lines) { if ($line -match '^\s*#' -or $line -match '^\s*$') { continue } if ($line -match '^schema_version:\s*"([^"]+)"') { $manifest.schema_version = $matches[1]; continue } if ($line -match '^factory_version:\s*"([^"]+)"') { $manifest.factory_version = $matches[1]; continue } if ($line -match '^core:') { $section = ""; $currentItem = $null; continue } if ($line -match '^\s+components:') { $section = "core_components"; $currentItem = $null; continue } if ($line -match '^modules:') { $section = "modules"; $currentItem = $null; continue } # List item start — detect indent to build exact property prefix if ($line -match '^(\s+)-\s+id:\s+(\S+)') { $itemIndent = $matches[1].Length $propPrefix = " " * ($itemIndent + 2) $currentItem = @{ id = $matches[2]; type = ""; source = ""; version = ""; description = ""; optional = $true } if ($section -eq "core_components") { $manifest.core_components.Add($currentItem) } elseif ($section -eq "modules") { $manifest.modules.Add($currentItem) } continue } if (-not $currentItem) { continue } # Match only at direct-property indent (prevents health_check.type clobbering component.type) if ($line -match "^${propPrefix}type:\s+(\S+)") { $currentItem.type = $matches[1]; continue } if ($line -match "^${propPrefix}source:\s+(\S+)") { $currentItem.source = $matches[1]; continue } if ($line -match "^${propPrefix}version:\s+`"([^`"]+)`"") { $currentItem.version = $matches[1]; continue } if ($line -match "^${propPrefix}description:\s+`"([^`"]+)`"") { $currentItem.description = $matches[1]; continue } if ($line -match "^${propPrefix}optional:\s+(\S+)") { $currentItem.optional = ($matches[1] -eq "true"); continue } } return $manifest } function Write-Step { param([string]$Msg) Write-Host "`n▶ $Msg" -ForegroundColor Cyan } function Write-Ok { param([string]$Msg) Write-Host "✓ $Msg" -ForegroundColor Green } function Write-Skip { param([string]$Msg) Write-Host "⊘ $Msg" -ForegroundColor Yellow } function Write-Gap { param([string]$Msg) Write-Host "⚠ $Msg" -ForegroundColor Red } # Pre-checks Write-Step "L0b Pre-checks" $missed = @() if (-not (Test-Command git)) { $missed += "git (install: winget install Git.Git)" } if (-not (Test-Command curl)) { $missed += "curl (install: winget install curl.curl)" } if (-not (Test-Command mise)) { $missed += "mise (install: winget install jdx.mise)" } if (-not (Test-Command claude)) { $missed += "claude (install: irm https://claude.ai/install.ps1 | iex)" } if ($missed) { Write-Gap "Missing dependencies:" $missed | ForEach-Object { Write-Host " - $_" } Write-Host "`nInstall missing tools and re-run bootstrap." exit 1 } Write-Ok "Pre-checks passed" # Check if already bootstrapped if (Test-Path $HOME_TOML) { Write-Skip "Already bootstrapped. Re-run mode: verify only." $toml = Get-Content $HOME_TOML -Raw if ($toml -match 'projects_dir\s*=\s*''([^'']+)''') { $existing_dir = $matches[1] Write-Host " projects_dir: $existing_dir" if (Test-Path "$existing_dir\.factory") { Write-Ok ".factory exists" } else { Write-Gap ".factory missing at $existing_dir\.factory" exit 1 } } Write-Ok "Bootstrap verified. Exiting (no-op)." exit 0 } # Interactive: ask for configuration Write-Step "Configuration" $gitea = Read-Host "Gitea URL [$GITEA_DEFAULT]" $gitea = if ($gitea) { $gitea } else { $GITEA_DEFAULT } $org = Read-Host "Gitea org/user [$ORG_DEFAULT]" $org = if ($org) { $org } else { $ORG_DEFAULT } $factoryRepo = Read-Host "Factory repo name [$FACTORY_REPO_DEFAULT]" $factoryRepo = if ($factoryRepo) { $factoryRepo } else { $FACTORY_REPO_DEFAULT } $projectsDir = Read-Host "Projects directory [$PROJECTS_DIR_DEFAULT]" $projectsDir = if ($projectsDir) { $projectsDir } else { $PROJECTS_DIR_DEFAULT } $client = Read-Host "AI client [claude-code/copilot-cli/gemini-cli] (default: claude-code)" $client = if ($client) { $client } else { "claude-code" } # Validate projects_dir if (-not (Test-Path $projectsDir)) { $create = Read-Host "Directory '$projectsDir' does not exist. Create? [Y/n]" if ($create -ne "n") { New-Item -ItemType Directory -Force -Path $projectsDir | Out-Null Write-Ok "Created $projectsDir" } else { Write-Gap "Cannot continue without projects_dir" exit 1 } } # Clone .factory Write-Step "Cloning .factory repo" $factoryLocal = "$projectsDir\.factory" $factoryUrl = "$gitea/$org/$factoryRepo.git" if (Test-Path $factoryLocal) { Write-Skip ".factory already exists, skipping clone" # Verify it's a git repo if (-not (Test-Path "$factoryLocal\.git")) { Write-Gap "$factoryLocal exists but is not a git repo. Remove it and re-run." exit 1 } } else { Write-Host "Cloning from: $factoryUrl" # Check if we have credentials for gitea $needAuth = $false try { $null = git ls-remote "$factoryUrl" 2>&1 } catch { $needAuth = $true } if ($needAuth) { Write-Host "`n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan Write-Host " PAT REQUIRED" -ForegroundColor Yellow Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan Write-Host "`n1. Open in browser: $gitea/user/settings/applications" Write-Host "2. Generate PAT with scopes: read:repository, write:repository, read:user" Write-Host "3. Press Enter when ready...`n" Read-Host Write-Host "Cloning with Git Credential Manager prompt..." } git clone "$factoryUrl" "$factoryLocal" if (-not $?) { Write-Gap "Clone failed. Check URL and credentials." exit 1 } Write-Ok "Cloned to $factoryLocal" } # Write home.toml Write-Step "Writing ~/.config/factory/home.toml" $configDir = "$env:USERPROFILE\.config\factory" New-Item -ItemType Directory -Force -Path $configDir | Out-Null $installedAt = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") $lines = @( "# factory home — single source of machine-level state", "# auto-generated by L0b bootstrap; safe to edit by hand", "", "projects_dir = '$projectsDir'", "client = '$client'", "installed_at = '$installedAt'" ) [System.IO.File]::WriteAllLines( "$configDir\home.toml", $lines, [System.Text.UTF8Encoding]::new($false) ) Write-Ok "Wrote $HOME_TOML" # Read factory manifest Write-Step "Reading factory manifest" $manifest = Read-FactoryManifest -FactoryPath $factoryLocal if (-not $manifest) { exit 1 } Write-Ok "factory v$($manifest.factory_version) (schema $($manifest.schema_version))" $coreIds = ($manifest.core_components | ForEach-Object { $_.id }) -join ", " $moduleIds = ($manifest.modules | ForEach-Object { $_.id }) -join ", " Write-Host " Core: $coreIds" Write-Host " Modules: $moduleIds" # Build MCP servers from lib/ (driven by manifest core.components) Write-Step "Building MCP servers" $libPath = "$factoryLocal\lib" if (Test-Path $libPath) { foreach ($component in $manifest.core_components) { $srcPath = "$factoryLocal\$($component.source)" if (-not (Test-Path $srcPath)) { continue } if ($component.type -eq "typescript") { Write-Host " Building $($component.id) v$($component.version)..." Push-Location $srcPath try { npm install --silent 2>&1 | Out-Null npm run build 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { Write-Gap "$($component.id): build failed (npm run build exited $LASTEXITCODE)" } else { Write-Ok "$($component.id) v$($component.version) built" } } finally { Pop-Location } } elseif ($component.type -eq "python") { Write-Host " Setting up $($component.id) v$($component.version)..." Push-Location $srcPath try { python -m venv .venv 2>&1 | Out-Null & ".venv\Scripts\pip.exe" install -e . --quiet if ($LASTEXITCODE -ne 0) { Write-Gap "$($component.id): pip install failed" } else { Write-Ok "$($component.id) v$($component.version) installed" } } finally { Pop-Location } } } } else { Write-Skip "lib/ not found — skipping MCP server build" } # Install skills to ~/.claude/skills/ Write-Step "Installing skills to ~/.claude/skills/" $skillsPath = "$factoryLocal\skills" $claudeSkillsPath = "$env:USERPROFILE\.claude\skills" if (Test-Path $skillsPath) { New-Item -ItemType Directory -Force -Path $claudeSkillsPath | Out-Null Get-ChildItem $skillsPath -Directory | ForEach-Object { Copy-Item $_.FullName "$claudeSkillsPath\$($_.Name)" -Recurse -Force } $count = (Get-ChildItem $skillsPath -Directory).Count Write-Ok "Installed $count skill directories to $claudeSkillsPath" } else { Write-Skip "skills/ not found in factory — skipping skill install" } # Print next steps Write-Host "`n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Green Write-Host " L0b COMPLETE" -ForegroundColor Green Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Green Write-Host "`nFactory bootstrapped. Next steps (manual):`n" Write-Host "1. Start Claude Code:" -ForegroundColor Cyan Write-Host " cd $factoryLocal" Write-Host " claude`n" Write-Host "2. In Claude, run:" -ForegroundColor Cyan Write-Host " /login" Write-Host " /plugin install superpowers@claude-plugins-official" Write-Host " /plugin install context7@claude-plugins-official" Write-Host " /reload-plugins" Write-Host " /exit`n" Write-Host "3. Run setup skills:" -ForegroundColor Cyan Write-Host " cd $projectsDir" Write-Host " claude" Write-Host " /setup-projects-meta" Write-Host " /setup-interns" Write-Host " /setup-tasks" Write-Host " /setup-wiki`n" Write-Host "See .wiki/concepts/factory-bootstrap.md for full design." Write-Host "`nRe-run this script anytime — it's idempotent."