<# .SYNOPSIS Tiangang AI Agent Platform - Unified Management Script v2.0 .DESCRIPTION status - Check all services start - Start all (Redis -> API -> Celery -> Frontend) stop - Stop all restart - Stop all, then start all .EXAMPLE powershell -ExecutionPolicy Bypass -File .\scripts\startup\manage.ps1 status powershell -ExecutionPolicy Bypass -File .\scripts\startup\manage.ps1 restart #> param( [ValidateSet("status", "start", "stop", "restart")] [string]$Action = "status" ) $ErrorActionPreference = "Continue" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $RepoRoot = Split-Path -Parent (Split-Path -Parent $ScriptDir) $Backend = Join-Path $RepoRoot "backend" $Frontend = Join-Path $RepoRoot "frontend" $RedisExe = Join-Path $Backend "redis\redis-server.exe" $RedisPort = 6379 $PrimaryApiPort = 8037 $FallbackApiPort = 8041 $FrontendPort = 3001 $script:ApiPort = $PrimaryApiPort # ========== UTILITY FUNCTIONS ========== function Write-Step { param([string]$Msg) Write-Host ">>> $Msg" -ForegroundColor Cyan } function Write-OK { param([string]$Msg) Write-Host " [OK] $Msg" -ForegroundColor Green } function Write-Fail { param([string]$Msg) Write-Host " [FAIL] $Msg" -ForegroundColor Red } function Write-Warn { param([string]$Msg) Write-Host " [WARN] $Msg" -ForegroundColor Yellow } function Write-Info { param([string]$Msg) Write-Host " [INFO] $Msg" -ForegroundColor Gray } function Test-PortListening([int]$Port) { $conn = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue return ($conn -ne $null) } function Stop-ByPattern([string]$Pattern, [string]$Label) { $procs = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -match $Pattern } if (-not $procs) { Write-Info "$Label - not running" return } foreach ($p in $procs) { try { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue Write-OK "Stopped $Label (PID=$($p.ProcessId))" } catch { Write-Fail "Failed to stop $Label (PID=$($p.ProcessId)): $_" } } } function Start-Background([string]$WorkDir, [string]$Command, [string]$Label) { $fullCmd = "Set-Location '$WorkDir'; .\venv\Scripts\Activate.ps1 2>`$null; $Command" $proc = Start-Process powershell -ArgumentList @( "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", $fullCmd ) -PassThru Write-OK "$Label started (PID=$($proc.Id))" return $proc } function Start-Visible([string]$WorkDir, [string]$Command, [string]$Label) { $fullCmd = "Set-Location '$WorkDir'; .\venv\Scripts\Activate.ps1 2>`$null; $Command; Write-Host ''; Write-Host '>>> $Label exited. Press any key to close...'; `$null = `$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')" $proc = Start-Process powershell -ArgumentList @( "-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $fullCmd ) -PassThru Write-OK "$Label started in new window (PID=$($proc.Id))" return $proc } function Start-Frontend([string]$FeDir, [int]$FePort, [int]$ApiPort, [string]$Label) { # Resolve npx full path first (hidden PowerShell windows lack user PATH) $npxPath = (Get-Command npx.cmd -ErrorAction SilentlyContinue).Source if (-not $npxPath) { $npxPath = (Get-Command npx -ErrorAction SilentlyContinue).Source } if (-not $npxPath) { $npxPath = "npx" } # Use cmd /k (stay open) to run the whole command chain in a single visible window. # Avoids "cmd /c start" which breaks && chains: start only receives the first # command after the title, and the rest runs in the outer cmd (wrong cwd). $feCmd = "cd /d `"$FeDir`" && set AIAGENT_API_PROXY=http://127.0.0.1:$ApiPort && `"$npxPath`" vite --port $FePort --host 0.0.0.0" $proc = Start-Process cmd -ArgumentList @( "/k", $feCmd ) -PassThru Write-OK "$Label started (PID=$($proc.Id), npx=$npxPath)" return $proc } function Test-HealthEndpoint([int]$Port) { try { $result = curl.exe -s -o NUL -w "%{http_code}" --connect-timeout 5 "http://127.0.0.1:${Port}/docs" 2>$null return ($result -eq "200") } catch { return $false } } # ========== STATUS ========== function Invoke-Status { Write-Host "" Write-Host "=============================================" -ForegroundColor Cyan Write-Host " Service Status" -ForegroundColor Cyan Write-Host "=============================================" -ForegroundColor Cyan Write-Host "" # Redis if (Test-PortListening $RedisPort) { Write-OK "Redis port $RedisPort - running" } else { Write-Fail "Redis port $RedisPort - NOT running" } # API $apiPort = 0 if (Test-PortListening $PrimaryApiPort) { $apiPort = $PrimaryApiPort } elseif (Test-PortListening $FallbackApiPort) { $apiPort = $FallbackApiPort } if ($apiPort -gt 0) { $healthy = Test-HealthEndpoint $apiPort $mark = if ($healthy) { "OK" } else { "no response" } Write-OK "Backend API port $apiPort - running [/docs $mark]" } else { Write-Fail "Backend API ports $PrimaryApiPort/$FallbackApiPort - NOT running" } # Celery Worker $workers = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -match "celery.*worker" } if ($workers) { Write-OK "Celery Worker $($workers.Count) process(es)" } else { Write-Fail "Celery Worker NOT running" } # Celery Beat $beats = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -match "celery.*beat" } if ($beats) { Write-OK "Celery Beat running" } else { Write-Warn "Celery Beat NOT running (scheduled tasks will not trigger)" } # Frontend if (Test-PortListening $FrontendPort) { Write-OK "Frontend Vite port $FrontendPort - running" } else { Write-Fail "Frontend Vite port $FrontendPort - NOT running" } Write-Host "" Write-Host "URLs:" -ForegroundColor White if ($apiPort -gt 0) { Write-Host " API docs: http://127.0.0.1:${apiPort}/docs" -ForegroundColor Gray } Write-Host " Frontend: http://localhost:${FrontendPort}" -ForegroundColor Gray Write-Host " AgentChat: http://localhost:${FrontendPort}/agent-chat" -ForegroundColor Gray Write-Host "" } # ========== STOP ========== function Invoke-Stop { Write-Host "" Write-Host "== Stop All Services ==" -ForegroundColor Cyan Stop-ByPattern "uvicorn\s+app\.main:app" "Backend API" Stop-ByPattern "celery\s+-A\s+app\.core\.celery_app\s+worker" "Celery Worker" Stop-ByPattern "celery\s+-A\s+app\.core\.celery_app\s+beat" "Celery Beat" Stop-ByPattern "vite.*3001|vite.*--port" "Frontend Vite" # Redis $redisProcs = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -match "redis-server" } if ($redisProcs) { foreach ($p in $redisProcs) { try { Stop-Process -Id $p.ProcessId -Force Write-OK "Stopped Redis (PID=$($p.ProcessId))" } catch { Write-Fail "Failed to stop Redis (PID=$($p.ProcessId))" } } } else { Write-Info "Redis - not running" } Start-Sleep -Seconds 2 Write-Host "" Write-Host "Port status:" -ForegroundColor Cyan foreach ($port in @($RedisPort, $PrimaryApiPort, $FallbackApiPort, $FrontendPort)) { $busy = Test-PortListening $port $label = if ($busy) { "BUSY" } else { "free" } $color = if ($busy) { "Yellow" } else { "Green" } Write-Host " port $port : $label" -ForegroundColor $color } Write-Host "" Write-Host "DONE: All services stopped" -ForegroundColor Green } # ========== START ========== function Invoke-Start { Write-Host "" Write-Host "== Start All Services ==" -ForegroundColor Cyan # 1. Redis Write-Step "1/5 Redis" if (Test-PortListening $RedisPort) { Write-OK "Redis already running" } else { if (Test-Path $RedisExe) { Start-Process $RedisExe -ArgumentList "--port", $RedisPort -WindowStyle Hidden Start-Sleep -Seconds 1 if (Test-PortListening $RedisPort) { Write-OK "Redis started (port $RedisPort)" } else { Write-Fail "Redis failed to start" } } else { Write-Fail "redis-server.exe not found: $RedisExe" } } # 2. API port detection Write-Step "2/5 Backend API port check" if (Test-PortListening $PrimaryApiPort) { Write-Warn "Port $PrimaryApiPort is busy, using fallback $FallbackApiPort" $script:ApiPort = $FallbackApiPort } else { $script:ApiPort = $PrimaryApiPort Write-OK "Port $($script:ApiPort) available" } # 3. API (uvicorn) Write-Step "3/5 Backend API (port $($script:ApiPort))" $existingApi = @(Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -match "uvicorn\s+app\.main:app" }) if ($existingApi.Count -gt 0) { Write-OK "Backend API already running" } else { $null = Start-Background $Backend "python -m uvicorn app.main:app --host 0.0.0.0 --port $($script:ApiPort)" "Backend API" } # 4. Celery Write-Step "4/5 Celery Worker + Beat" $existingWorker = @(Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -match "celery.*worker" }) if ($existingWorker.Count -gt 0) { Write-OK "Celery Worker already running" } else { $null = Start-Visible $Backend "python -m celery -A app.core.celery_app worker --loglevel=info --pool=threads --concurrency=8" "Celery Worker" } $existingBeat = @(Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -match "celery.*beat" }) if ($existingBeat.Count -gt 0) { Write-OK "Celery Beat already running" } else { $null = Start-Visible $Backend "python -m celery -A app.core.celery_app beat --loglevel=info" "Celery Beat" } # 5. Frontend Write-Step "5/5 Frontend Vite (port $FrontendPort)" if (Test-PortListening $FrontendPort) { Write-OK "Frontend already running" } else { $null = Start-Frontend $Frontend $FrontendPort $script:ApiPort "Frontend-Vite" } # Health check Write-Host "" Write-Step "Health check (waiting up to 30s for services)" $maxWait = 30 $apiReady = $false $feReady = $false for ($i = 1; $i -le $maxWait; $i++) { if (-not $apiReady) { $apiReady = Test-HealthEndpoint $script:ApiPort } if (-not $feReady) { $feReady = Test-PortListening $FrontendPort } if ($apiReady -and $feReady) { Write-OK "All services ready (${i}s)" break } if ($i % 5 -eq 0) { Write-Info "Waiting... (${i}s) API=$apiReady Frontend=$feReady" } Start-Sleep -Seconds 1 } if (-not $apiReady) { Write-Fail "Backend API not ready within ${maxWait}s, check logs" } if (-not $feReady) { Write-Fail "Frontend not ready within ${maxWait}s, check logs" } # Summary Write-Host "" Write-Host "=============================================" -ForegroundColor Green Write-Host " Startup Complete" -ForegroundColor Green Write-Host "=============================================" -ForegroundColor Green Write-Host " API docs: http://127.0.0.1:$($script:ApiPort)/docs" -ForegroundColor White Write-Host " Frontend: http://localhost:${FrontendPort}" -ForegroundColor White Write-Host " AgentChat: http://localhost:${FrontendPort}/agent-chat" -ForegroundColor White Write-Host "" } # ========== RESTART ========== function Invoke-Restart { Invoke-Stop Start-Sleep -Seconds 3 Invoke-Start } # ========== ENTRY ========== switch ($Action) { "status" { Invoke-Status } "start" { Invoke-Start } "stop" { Invoke-Stop } "restart" { Invoke-Restart } }