param( [string]$BaseUrl = "http://localhost:8880", [string]$AccountId = "douyin_company", [string]$XhsAccountId = "xiaohongshu_company", [int]$WorkerIntervalSeconds = 30, [switch]$RemoteBackend, [string]$Platforms = "douyin,xiaohongshu" ) $ErrorActionPreference = "Stop" $Root = Resolve-Path (Join-Path $PSScriptRoot "..") Set-Location $Root function Write-Step([string]$Message) { Write-Host "" Write-Host "==> $Message" -ForegroundColor Cyan } function Require-Command([string]$Name, [string]$InstallHint) { if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { throw "$Name is not installed or not in PATH. $InstallHint" } } function Test-BackendHealth([string]$Url) { try { $response = Invoke-WebRequest -UseBasicParsing -Uri "$Url/actuator/health" -TimeoutSec 3 return $response.StatusCode -ge 200 -and $response.StatusCode -lt 300 } catch { return $false } } function Find-BackendJar { $jars = Get-ChildItem -Path (Join-Path $Root "target") -Filter "agent-foundation-*.jar" -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike "*.original" } | Sort-Object LastWriteTime -Descending if ($jars.Count -eq 0) { return $null } return $jars[0].FullName } function Test-BrowserInstalled { $paths = @( "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe", "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe", "$env:ProgramFiles\Google\Chrome\Application\chrome.exe", "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" ) foreach ($path in $paths) { if ($path -and (Test-Path $path)) { return $true } } return $false } function Test-LocalBaseUrl([string]$Url) { return $Url -match "^https?://(localhost|127\.0\.0\.1)(:\d+)?/?$" } Write-Step "Checking local runtime" Require-Command "node" "Install Node.js LTS from https://nodejs.org/" Require-Command "npm.cmd" "Install Node.js LTS from https://nodejs.org/" if (-not $RemoteBackend) { Require-Command "java" "Install JDK 17+ or JRE 17+." } if (-not (Test-BrowserInstalled)) { throw "Microsoft Edge or Google Chrome is required." } Write-Step "Installing Node dependencies if needed" if (-not (Test-Path (Join-Path $Root "node_modules\playwright"))) { & npm.cmd install if ($LASTEXITCODE -ne 0) { throw "npm install failed." } } else { Write-Host "Node dependencies already installed." } if ($RemoteBackend -or -not (Test-LocalBaseUrl $BaseUrl)) { Write-Step "Checking remote backend" if (Test-BackendHealth $BaseUrl) { Write-Host "Remote backend is reachable: $BaseUrl" } else { Write-Host "Remote backend health check did not return OK. Continuing because /actuator/health may be disabled." Write-Host "Worker will connect to: $BaseUrl" } } else { Write-Step "Starting backend service if needed" if (Test-BackendHealth $BaseUrl) { Write-Host "Backend is already running: $BaseUrl" } else { $jar = Find-BackendJar if (-not $jar) { throw "Backend jar not found under target\. Build the Spring Boot jar first." } $backendCommand = "Set-Location '$Root'; java -jar '$jar'" Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) -WindowStyle Normal Write-Host "Waiting for backend health..." $ready = $false for ($i = 0; $i -lt 40; $i++) { Start-Sleep -Seconds 2 if (Test-BackendHealth $BaseUrl) { $ready = $true break } } if (-not $ready) { throw "Backend did not become healthy at $BaseUrl. Check the backend window." } } } Write-Step "Checking Douyin login" $env:MEDIA_BASE_URL = $BaseUrl $env:DOUYIN_ACCOUNT_ID = $AccountId $platformList = $Platforms.Split(",") | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ } if ($platformList -contains "douyin") { $checkOutput = & npm.cmd run douyin:check -- $AccountId 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host $checkOutput Write-Host "" Write-Host "Douyin login is required. A browser will open. Login, then press Enter in that window." & npm.cmd run douyin:login -- $AccountId if ($LASTEXITCODE -ne 0) { throw "Douyin login failed." } } else { Write-Host "Douyin login profile is ready." } } if ($platformList -contains "xiaohongshu") { Write-Step "Checking Xiaohongshu login" $env:XHS_ACCOUNT_ID = $XhsAccountId $xhsCheckOutput = & npm.cmd run xhs:check -- $XhsAccountId 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host $xhsCheckOutput Write-Host "" Write-Host "Xiaohongshu login is required. A browser will open. Login, then press Enter in that window." & npm.cmd run xhs:login -- $XhsAccountId if ($LASTEXITCODE -ne 0) { throw "Xiaohongshu login failed." } } else { Write-Host "Xiaohongshu login profile is ready." } } if ($platformList -contains "douyin") { Write-Step "Starting Douyin worker loop" $workerCommand = "Set-Location '$Root'; `$env:MEDIA_BASE_URL='$BaseUrl'; `$env:DOUYIN_ACCOUNT_ID='$AccountId'; `$env:MEDIA_WORKER_INTERVAL_MS='$($WorkerIntervalSeconds * 1000)'; npm.cmd run douyin:worker:loop" Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $workerCommand) -WindowStyle Normal } if ($platformList -contains "xiaohongshu") { Write-Step "Starting Xiaohongshu worker loop" $xhsWorkerCommand = "Set-Location '$Root'; `$env:MEDIA_BASE_URL='$BaseUrl'; `$env:XHS_ACCOUNT_ID='$XhsAccountId'; `$env:MEDIA_WORKER_INTERVAL_MS='$($WorkerIntervalSeconds * 1000)'; npm.cmd run xhs:worker:loop" Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $xhsWorkerCommand) -WindowStyle Normal } if (($platformList -contains "douyin") -and ($platformList -contains "xiaohongshu")) { Write-Step "Starting media matrix worker loop" $matrixWorkerCommand = "Set-Location '$Root'; `$env:MEDIA_BASE_URL='$BaseUrl'; `$env:DOUYIN_ACCOUNT_ID='$AccountId'; `$env:XHS_ACCOUNT_ID='$XhsAccountId'; `$env:MEDIA_WORKER_INTERVAL_MS='$($WorkerIntervalSeconds * 1000)'; npm.cmd run media:worker:loop" Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $matrixWorkerCommand) -WindowStyle Normal } Write-Host "" Write-Host "Local client is ready." Write-Host "Backend: $BaseUrl" Write-Host "Douyin account: $AccountId" Write-Host "Xiaohongshu account: $XhsAccountId" Write-Host "Platforms: $($platformList -join ', ')" Write-Host "Worker polling interval: $WorkerIntervalSeconds seconds"