param( [ValidateSet("frontend", "backend", "admin", "mcp")] [string]$Only, [switch]$Spawn, [switch]$WithMcp, [switch]$Install, [string]$DatabaseUrl = "postgres://postgres:postgres%402025%21@10.0.0.2:5432/termi-api_development", [string]$McpApiKey = "termi-mcp-local-dev-key", [string]$McpBackendApiBase = "http://127.0.0.1:5150/api", [int]$McpPort = 5151, [switch]$FrontendOnly, [switch]$BackendOnly, [switch]$AdminOnly, [switch]$McpOnly ) $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $devScriptPath = $MyInvocation.MyCommand.Path $serviceOrder = @("frontend", "admin", "backend") function Resolve-TargetService { if ($Only) { return $Only } $legacyTargets = @( @{ Enabled = $FrontendOnly; Name = "frontend" } @{ Enabled = $BackendOnly; Name = "backend" } @{ Enabled = $AdminOnly; Name = "admin" } @{ Enabled = $McpOnly; Name = "mcp" } ) | Where-Object { $_.Enabled } if ($legacyTargets.Count -gt 1) { throw "Use only one of -Only, -FrontendOnly, -BackendOnly, -AdminOnly, or -McpOnly." } if ($legacyTargets.Count -eq 1) { return $legacyTargets[0].Name } return $null } function Invoke-RepoCommand { param( [string]$Name, [string]$WorkingDirectory, [scriptblock]$Run, [switch]$UsesNode ) if (-not (Test-Path $WorkingDirectory)) { throw "$Name directory not found: $WorkingDirectory" } Push-Location $WorkingDirectory try { if ($UsesNode -and ($Install -or -not (Test-Path (Join-Path $WorkingDirectory "node_modules")))) { Write-Host "[$Name] Installing dependencies..." -ForegroundColor Cyan npm install if ($LASTEXITCODE -ne 0) { throw "npm install failed for $Name" } } & $Run if ($LASTEXITCODE -ne 0) { throw "$Name failed to start" } } finally { Pop-Location } } function Start-Frontend { Invoke-RepoCommand ` -Name "frontend" ` -WorkingDirectory (Join-Path $repoRoot "frontend") ` -UsesNode ` -Run { Write-Host "[frontend] Starting Astro dev server..." -ForegroundColor Green npm run dev } } function Start-Admin { Invoke-RepoCommand ` -Name "admin" ` -WorkingDirectory (Join-Path $repoRoot "admin") ` -UsesNode ` -Run { Write-Host "[admin] Starting Vite admin workspace..." -ForegroundColor Green npm run dev } } function Start-Backend { Invoke-RepoCommand ` -Name "backend" ` -WorkingDirectory (Join-Path $repoRoot "backend") ` -Run { $env:DATABASE_URL = $DatabaseUrl Write-Host "[backend] DATABASE_URL set to $DatabaseUrl" -ForegroundColor Cyan Write-Host "[backend] Starting Loco.rs server..." -ForegroundColor Green cargo loco start 2>&1 } } function Start-Mcp { Invoke-RepoCommand ` -Name "mcp" ` -WorkingDirectory (Join-Path $repoRoot "mcp-server") ` -UsesNode ` -Run { $env:TERMI_MCP_API_KEY = $McpApiKey $env:TERMI_BACKEND_API_BASE = $McpBackendApiBase $env:TERMI_MCP_PORT = "$McpPort" Write-Host "[mcp] Backend API base set to $McpBackendApiBase" -ForegroundColor Cyan Write-Host "[mcp] Starting MCP server on port $McpPort..." -ForegroundColor Green npm run start } } function Invoke-Service { param([string]$Name) switch ($Name) { "frontend" { Start-Frontend; return } "admin" { Start-Admin; return } "backend" { Start-Backend; return } "mcp" { Start-Mcp; return } default { throw "Unsupported service: $Name" } } } function Get-ServiceLaunchArguments { param([string]$Name) $arguments = @( "powershell", "-NoExit", "-ExecutionPolicy", "Bypass", "-File", $devScriptPath, "-Only", $Name ) if ($Install -and $Name -ne "backend") { $arguments += "-Install" } if ($Name -eq "backend") { $arguments += @("-DatabaseUrl", $DatabaseUrl) } if ($Name -eq "mcp") { $arguments += @( "-McpApiKey", $McpApiKey, "-McpBackendApiBase", $McpBackendApiBase, "-McpPort", $McpPort ) } return $arguments } function Start-ServiceWindow { param([string]$Name) $arguments = Get-ServiceLaunchArguments -Name $Name Start-Process powershell -ArgumentList $arguments[1..($arguments.Length - 1)] } function Start-ServiceHost { param([string[]]$Services) $wt = Get-Command wt.exe -ErrorAction SilentlyContinue if (-not $wt) { Write-Warning "[dev] Windows Terminal (wt.exe) not found. Falling back to separate PowerShell windows." foreach ($service in $Services) { Start-ServiceWindow $service } return } $wtArguments = @("-w", "0") $isFirst = $true foreach ($service in $Services) { if (-not $isFirst) { $wtArguments += ";" } $wtArguments += @( "new-tab", "--title", "termi:$service" ) $wtArguments += Get-ServiceLaunchArguments -Name $service $isFirst = $false } Start-Process -FilePath $wt.Source -ArgumentList $wtArguments } $targetService = Resolve-TargetService if ($targetService -and -not $Spawn) { Invoke-Service $targetService exit $LASTEXITCODE } $servicesToStart = [System.Collections.Generic.List[string]]::new() if ($targetService) { [void]$servicesToStart.Add($targetService) } else { $serviceOrder | ForEach-Object { [void]$servicesToStart.Add($_) } if ($WithMcp) { [void]$servicesToStart.Add("mcp") } } $serviceLabel = ($servicesToStart -join ", ") Write-Host "[dev] Starting $serviceLabel in one Windows Terminal window..." -ForegroundColor Cyan Start-ServiceHost -Services $servicesToStart Write-Host "[dev] Ready. Use .\\stop-services.ps1 to stop everything." -ForegroundColor Green