[eric] windows polish + bump 1.0.25: publish-win.ps1 mirror of publish.sh + run.ps1 windows dev launcher, build-app-win.ps1 warns 8s if Mac latest-mac.yml missing in

v<version> release before -Publish, python-env cache skip via OPENSWARM_REBUILD_PYTHON, pip stderr wrapped to suppress PS5.1 NativeCommandError; analytics APP_VERSION
  auto-derived from electron/package.json (single source of truth); OnboardingModal renders Connected state for already-active providers; gitignore fix for backend/.venv.
  Bumps 1.0.25 - first version that ships Mac and Windows together.
This commit is contained in:
Eric
2026-04-21 21:53:48 -07:00
parent 9ff07443db
commit 4ce4dec33c
8 changed files with 273 additions and 11 deletions
+2
View File
@@ -27,6 +27,8 @@ backend/npm-servers/*/node_modules/
frontend/dist/
# Bundled uv binaries (downloaded during build)
backend/uv-bin/
# Backend Python venv (created by run.ps1 / backend/run.sh)
backend/.venv/
.account-factory
openswarm-cloud
.openswarm-cloud
+17 -1
View File
@@ -15,7 +15,23 @@ from backend.apps.analytics.collector import init as init_collector, shutdown as
logger = logging.getLogger(__name__)
APP_VERSION = "1.0.24"
def _read_app_version() -> str:
"""Read app version from electron/package.json so we never have to bump
it in two places. Falls back to a literal if the file isn't reachable
(e.g. unusual layouts in tests)."""
import json
try:
_here = os.path.dirname(os.path.abspath(__file__))
# backend/apps/analytics/ -> backend/apps/ -> backend/ -> repo root
_repo = os.path.dirname(os.path.dirname(os.path.dirname(_here)))
_pkg = os.path.join(_repo, "electron", "package.json")
with open(_pkg, encoding="utf-8") as _f:
return json.load(_f).get("version", "unknown")
except (OSError, ValueError, KeyError):
return "unknown"
APP_VERSION = _read_app_version()
_heartbeat_task: asyncio.Task | None = None
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openswarm",
"version": "1.0.24",
"version": "1.0.25",
"description": "OpenSwarm — AI Agent Orchestrator",
"main": "main.js",
"scripts": {
@@ -858,28 +858,31 @@ const OnboardingModal: React.FC = () => {
Use your existing subscription
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.75, mb: 2.5 }}>
{SUBSCRIPTION_PROVIDERS.map((p) => (
{SUBSCRIPTION_PROVIDERS.map((p) => {
const isConnected = connectedProviders.has(p.id);
return (
<Box
key={p.id}
onClick={() => !p.preview && !connecting && nineRouterReady && handleConnect(p.id)}
onClick={() => !p.preview && !connecting && nineRouterReady && !isConnected && handleConnect(p.id)}
sx={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
p: 1.5, borderRadius: `${c.radius.md}px`, border: `1px solid ${c.border.subtle}`,
cursor: p.preview || !nineRouterReady ? 'default' : connecting ? 'wait' : 'pointer',
p: 1.5, borderRadius: `${c.radius.md}px`, border: `1px solid ${isConnected ? c.accent.primary : c.border.subtle}`,
cursor: p.preview || !nineRouterReady || isConnected ? 'default' : connecting ? 'wait' : 'pointer',
opacity: p.preview ? 0.5 : !nineRouterReady ? 0.6 : 1,
transition: 'border-color 0.15s, background 0.15s',
...(!p.preview && nineRouterReady && { '&:hover': { borderColor: c.border.medium, bgcolor: `${c.accent.primary}05` } }),
...(!p.preview && nineRouterReady && !isConnected && { '&:hover': { borderColor: c.border.medium, bgcolor: `${c.accent.primary}05` } }),
}}
>
<Box>
<Typography sx={{ fontSize: '0.92rem', fontWeight: 600, color: c.text.primary }}>{p.name}</Typography>
<Typography sx={{ fontSize: '0.75rem', color: c.text.muted }}>{p.desc}</Typography>
</Box>
<Typography sx={{ fontSize: '0.78rem', color: p.preview ? c.text.ghost : connecting === p.id ? c.accent.primary : !nineRouterReady ? c.text.ghost : c.text.tertiary, fontStyle: p.preview ? 'italic' : 'normal' }}>
{p.preview ? 'Coming soon' : connecting === p.id ? 'Connecting...' : !nineRouterReady && nineRouterReady !== false ? 'Starting...' : 'Connect \u2192'}
<Typography sx={{ fontSize: '0.78rem', color: p.preview ? c.text.ghost : isConnected ? c.accent.primary : connecting === p.id ? c.accent.primary : !nineRouterReady ? c.text.ghost : c.text.tertiary, fontStyle: p.preview ? 'italic' : 'normal' }}>
{p.preview ? 'Coming soon' : isConnected ? 'Connected' : connecting === p.id ? 'Connecting...' : !nineRouterReady && nineRouterReady !== false ? 'Starting...' : 'Connect \u2192'}
</Typography>
</Box>
))}
);
})}
</Box>
{/* API key option */}
File diff suppressed because one or more lines are too long
+26
View File
@@ -0,0 +1,26 @@
# Windows mirror of publish.sh.
# Builds + signs + publishes the Windows installer to the GitHub Release
# matching electron/package.json's version.
#
# Usage:
# pwsh publish-win.ps1 (or) powershell -File publish-win.ps1
#
# Prereqs:
# - .env.windows populated with AZURE_* secrets + GH_TOKEN
# - For a clean release flow: run `bash publish.sh` on a Mac first so
# the v<version> release exists with Mac assets. The build script will
# warn if the Mac release isn't found.
$ErrorActionPreference = 'Stop'
$ScriptDir = Split-Path -Parent $PSCommandPath
$BuildScript = Join-Path $ScriptDir 'scripts\build-app-win.ps1'
if (-not (Test-Path $BuildScript)) {
Write-Error "Cannot find $BuildScript"
exit 1
}
Write-Host "Publishing Windows release..."
& $BuildScript -Publish
exit $LASTEXITCODE
+192
View File
@@ -0,0 +1,192 @@
# Windows dev launcher - mirror of bash run.sh.
# Spins up backend (uvicorn --reload) + frontend (webpack dev server) + electron
# in one terminal. Hot-reload for Python and React. Ctrl+C to stop all three.
#
# Prereqs: Python 3.12+, Node 20+, npm. Everything else is installed on demand.
#
# Usage:
# powershell -ExecutionPolicy Bypass -File run.ps1
# (or) pwsh run.ps1 if PowerShell 7 is installed.
$ErrorActionPreference = 'Stop'
$ScriptDir = Split-Path -Parent $PSCommandPath
# --- Locate Python ---
$python = $null
foreach ($name in @('python', 'python3', 'py')) {
$cmd = Get-Command $name -ErrorAction SilentlyContinue
if ($cmd) { $python = $cmd.Source; break }
}
if (-not $python) {
throw "Python 3.12+ not found on PATH. Install from https://www.python.org/downloads/"
}
# --- Bundled uv/uvx (matches what build-app-win.ps1 does so MCP discovery works in dev) ---
$UvBinDir = Join-Path $ScriptDir 'backend\uv-bin'
if (-not (Test-Path (Join-Path $UvBinDir 'uvx.exe'))) {
Write-Host "[setup] Downloading uv/uvx for Windows..."
New-Item -ItemType Directory -Force -Path $UvBinDir | Out-Null
$UvUrl = 'https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-pc-windows-msvc.zip'
$TmpZip = Join-Path $env:TEMP "uv-win-$([guid]::NewGuid()).zip"
$TmpExtract = Join-Path $env:TEMP "uv-win-extract-$([guid]::NewGuid())"
try {
Invoke-WebRequest -Uri $UvUrl -OutFile $TmpZip -UseBasicParsing
Expand-Archive -Path $TmpZip -DestinationPath $TmpExtract -Force
Get-ChildItem $TmpExtract -Recurse -Filter 'uv.exe' | Select-Object -First 1 | ForEach-Object { Copy-Item $_.FullName (Join-Path $UvBinDir 'uv.exe') -Force }
Get-ChildItem $TmpExtract -Recurse -Filter 'uvx.exe' | Select-Object -First 1 | ForEach-Object { Copy-Item $_.FullName (Join-Path $UvBinDir 'uvx.exe') -Force }
} finally {
Remove-Item -Force $TmpZip -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $TmpExtract -ErrorAction SilentlyContinue
}
}
# --- Backend venv + deps ---
$Venv = Join-Path $ScriptDir 'backend\.venv'
$VenvPy = Join-Path $Venv 'Scripts\python.exe'
if (-not (Test-Path $VenvPy)) {
Write-Host "[setup] Creating Python venv at $Venv ..."
& $python -m venv $Venv
if ($LASTEXITCODE -ne 0) { throw "venv creation failed" }
}
Write-Host "[setup] Installing backend deps (idempotent, fast if already up to date)..."
# Suppress PowerShell 5.1's native-stderr-as-error wrapping for these idempotent
# pip calls (deprecation warnings on stderr would otherwise terminate the script
# under $ErrorActionPreference=Stop). We still check $LASTEXITCODE for real failures.
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
& $VenvPy -m pip install --quiet --upgrade pip *>&1 | Out-Null
if ($LASTEXITCODE -ne 0) { throw "pip upgrade failed" }
& $VenvPy -m pip install --quiet -r (Join-Path $ScriptDir 'backend\requirements.txt')
if ($LASTEXITCODE -ne 0) { throw "pip install backend reqs failed" }
& $VenvPy -m pip install --quiet -e (Join-Path $ScriptDir 'debugger')
if ($LASTEXITCODE -ne 0) { throw "pip install debugger failed" }
} finally {
$ErrorActionPreference = $prevEAP
}
# --- Frontend deps ---
$FrontendDir = Join-Path $ScriptDir 'frontend'
if (-not (Test-Path (Join-Path $FrontendDir 'node_modules'))) {
Write-Host "[setup] Installing frontend deps..."
Push-Location $FrontendDir
try { & npm install } finally { Pop-Location }
}
# --- Electron deps ---
$ElectronDir = Join-Path $ScriptDir 'electron'
if (-not (Test-Path (Join-Path $ElectronDir 'node_modules'))) {
Write-Host "[setup] Installing electron deps..."
Push-Location $ElectronDir
try { & npm install } finally { Pop-Location }
}
# --- Process tracking + cleanup ---
$script:childPids = New-Object System.Collections.ArrayList
function Stop-Tree($processId, $label) {
if (-not $processId) { return }
try {
& taskkill /PID $processId /T /F 2>$null | Out-Null
Write-Host " killed $label (pid $processId)" -ForegroundColor DarkGray
} catch {}
}
function Cleanup-All {
Write-Host ""
Write-Host "Shutting down all services..." -ForegroundColor Yellow
foreach ($entry in $script:childPids) {
Stop-Tree $entry.Pid $entry.Label
}
Write-Host "All services stopped." -ForegroundColor Green
}
try {
# --- Start backend (NoNewWindow so logs interleave into this terminal) ---
Write-Host ""
Write-Host "[backend] Starting uvicorn --reload on http://localhost:8324 ..." -ForegroundColor Blue
$backend = Start-Process -PassThru -NoNewWindow `
-FilePath $VenvPy `
-WorkingDirectory $ScriptDir `
-ArgumentList @(
'-m', 'uvicorn', 'backend.main:app',
'--host', '0.0.0.0', '--port', '8324', '--reload',
'--reload-dir', (Join-Path $ScriptDir 'backend'),
'--reload-exclude', '*.pyc'
)
[void]$script:childPids.Add(@{ Pid = $backend.Id; Label = 'backend' })
Write-Host "Waiting for backend (max 90s)..." -ForegroundColor Yellow
$deadline = (Get-Date).AddSeconds(90)
$ready = $false
while ((Get-Date) -lt $deadline) {
if ($backend.HasExited) { throw "Backend exited prematurely (code $($backend.ExitCode))" }
try {
Invoke-WebRequest -Uri 'http://localhost:8324/api/health/check' -UseBasicParsing -TimeoutSec 1 -ErrorAction Stop | Out-Null
$ready = $true
break
} catch {}
Start-Sleep -Milliseconds 1500
}
if (-not $ready) { throw "Backend did not become ready within 90s" }
Write-Host "Backend ready." -ForegroundColor Green
# --- Start frontend ---
Write-Host ""
Write-Host "[frontend] Starting webpack dev server on http://localhost:3000 ..." -ForegroundColor Green
$frontend = Start-Process -PassThru -NoNewWindow `
-FilePath 'npm.cmd' `
-WorkingDirectory $FrontendDir `
-ArgumentList @('run', 'dev')
[void]$script:childPids.Add(@{ Pid = $frontend.Id; Label = 'frontend' })
Write-Host "Waiting for frontend (max 90s)..." -ForegroundColor Yellow
$deadline = (Get-Date).AddSeconds(90)
$ready = $false
while ((Get-Date) -lt $deadline) {
if ($frontend.HasExited) { throw "Frontend exited prematurely (code $($frontend.ExitCode))" }
try {
Invoke-WebRequest -Uri 'http://localhost:3000/' -UseBasicParsing -TimeoutSec 1 -ErrorAction Stop | Out-Null
$ready = $true
break
} catch {}
Start-Sleep -Milliseconds 1500
}
if (-not $ready) { throw "Frontend did not become ready within 90s" }
Write-Host "Frontend ready." -ForegroundColor Green
# --- Start electron in dev mode (npm run dev = cross-env ELECTRON_DEV=1 electron .) ---
Write-Host ""
Write-Host "[electron] Launching dev shell..." -ForegroundColor Magenta
$electron = Start-Process -PassThru -NoNewWindow `
-FilePath 'npm.cmd' `
-WorkingDirectory $ElectronDir `
-ArgumentList @('run', 'dev')
[void]$script:childPids.Add(@{ Pid = $electron.Id; Label = 'electron' })
Write-Host ""
Write-Host "All services running. Press Ctrl+C to stop." -ForegroundColor Cyan
Write-Host " Backend: http://localhost:8324" -ForegroundColor Blue
Write-Host " Frontend: http://localhost:3000" -ForegroundColor Green
Write-Host " Electron: dev shell (pid $($electron.Id))" -ForegroundColor Magenta
Write-Host ""
# --- Supervise: if any dies, tear down all ---
while ($true) {
Start-Sleep -Seconds 3
if ($backend.HasExited) {
Write-Host "[backend] exited unexpectedly (code $($backend.ExitCode)). Tearing down..." -ForegroundColor Red
break
}
if ($frontend.HasExited) {
Write-Host "[frontend] exited unexpectedly (code $($frontend.ExitCode)). Tearing down..." -ForegroundColor Red
break
}
if ($electron.HasExited) {
Write-Host "[electron] exited (normal close). Tearing down..." -ForegroundColor Yellow
break
}
}
} finally {
Cleanup-All
}
+23
View File
@@ -242,6 +242,29 @@ try {
}
if ($Publish) {
# Safety check: warn if the matching Mac release isn't on GitHub yet.
# Mac and Windows publishes don't conflict (different asset names,
# different latest*.yml manifests), but a Windows-only release means
# Mac users will skip this version entirely. Better to know now than
# explain it after the fact. Non-fatal; sleeps 8s to let the user
# Ctrl+C if it surprises them.
try {
$pkgJson = Get-Content -Raw (Join-Path $ProjectRoot 'electron\package.json') | ConvertFrom-Json
$version = $pkgJson.version
$macYmlUrl = "https://github.com/openswarm-ai/openswarm/releases/download/v$version/latest-mac.yml"
$null = Invoke-WebRequest -Uri $macYmlUrl -Method Head -UseBasicParsing -ErrorAction Stop -TimeoutSec 10
Write-Host " > Mac release v$version detected on GitHub (latest-mac.yml present). OK to proceed."
} catch {
Write-Host ""
Write-Host "WARNING: Mac release v$version is NOT yet published on GitHub." -ForegroundColor Yellow
Write-Host " -> Uploading Windows assets to a release with no Mac assets means" -ForegroundColor Yellow
Write-Host " Mac users will skip v$version entirely (electron-updater on Mac" -ForegroundColor Yellow
Write-Host " will see no latest-mac.yml). Recommended order:" -ForegroundColor Yellow
Write-Host " 1. bash publish.sh (on the Mac)" -ForegroundColor Yellow
Write-Host " 2. pwsh publish-win.ps1 (here)" -ForegroundColor Yellow
Write-Host " -> Continuing in 8s. Press Ctrl+C to abort." -ForegroundColor Yellow
Start-Sleep -Seconds 8
}
& npx electron-builder --win --x64 --publish always
} else {
& npx electron-builder --win --x64 --publish never