rsclaw 2026.6.26

AI Agent Engine Compatible with OpenClaw
#
# build-ui.ps1 — Build RsClaw desktop app (Tauri) on Windows
#
# Usage:
#   .\scripts\build-ui.ps1                       # release, host arch (default)
#   .\scripts\build-ui.ps1 -Debug                # debug build (shows console)
#   .\scripts\build-ui.ps1 -DualArch             # x64 + arm64 (both Windows MSVC)
#   .\scripts\build-ui.ps1 -Targets x86_64-pc-windows-msvc,aarch64-pc-windows-msvc
#
# -DualArch / -Targets build each architecture in turn (own sidecar + own bundle),
# so an ARM Windows box can ship the x64 build too (and vice-versa).
#

param(
    [switch]$Debug,
    [string]$Target = "",         # single explicit target (back-compat)
    [string[]]$Targets = @(),     # multiple targets (overrides -Target)
    [switch]$DualArch             # shortcut for x64 + arm64 Windows MSVC
)

$ErrorActionPreference = "Stop"

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RootDir = Split-Path -Parent $ScriptDir
$UiDir = Join-Path $RootDir "ui"
$TauriDir = Join-Path $UiDir "src-tauri"
$BinDir = Join-Path $TauriDir "binaries"

function Log($msg) { Write-Host "[build-ui] $msg" -ForegroundColor Green }
function Warn($msg) { Write-Host "[build-ui] $msg" -ForegroundColor Red }

# Resolve the list of targets to build.
if ($DualArch) {
    $BuildTargets = @("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc")
} elseif ($Targets.Count -gt 0) {
    $BuildTargets = $Targets
} elseif ($Target) {
    $BuildTargets = @($Target)
} else {
    # Auto-detect host target (original default behaviour).
    $hostTarget = (rustc -vV | Select-String "host:").ToString().Replace("host: ", "").Trim()
    $BuildTargets = @($hostTarget)
}
Log "Targets: $($BuildTargets -join ', ')"

# Default = release. -Debug keeps the console window for stdout/stderr.
if ($Debug) {
    $Profile = "debug"; $CargoFlags = @(); $TauriFlags = @("--debug")
} else {
    $Profile = "release"; $CargoFlags = @("--release"); $TauriFlags = @()
}

# Version baked into the build banner — read from Cargo.toml, not git tags.
function Get-CargoVersion {
    $cargoToml = Join-Path $RootDir "Cargo.toml"
    if (-not (Test-Path $cargoToml)) { return "dev" }
    $content = Get-Content -Raw $cargoToml
    if ($content -match '(?s)\[package\].*?(?:^|\n)\s*version\s*=\s*"([^"]+)"') { return $matches[1] }
    return "dev"
}
$env:RSCLAW_BUILD_VERSION = "v$(Get-CargoVersion)"
$env:RSCLAW_BUILD_DATE = Get-Date -Format "yyyy-MM-dd"

# Frontend deps once (shared across targets).
if (-not (Test-Path (Join-Path $UiDir "node_modules"))) {
    Log "Installing frontend dependencies..."
    Push-Location $UiDir
    try { yarn install } finally { Pop-Location }
}

New-Item -ItemType Directory -Force -Path $BinDir | Out-Null

function Build-Target($t) {
    Log "==== Building $t ($Profile) ===="

    # Ensure the Rust target is installed (no-op if already present).
    rustup target add $t 2>$null

    # Step 1: rsclaw CLI for this target.
    Push-Location $RootDir
    try {
        cargo build @CargoFlags --target $t
    } finally { Pop-Location }

    $CargoOut = Join-Path $RootDir "target\$t\$Profile\rsclaw.exe"
    if (-not (Test-Path $CargoOut)) {
        Warn "rsclaw.exe not found at: $CargoOut"
        exit 1
    }
    $Size = (Get-Item $CargoOut).Length / 1MB
    Log "CLI binary: $([math]::Round($Size, 1))MB ($CargoOut)"

    # Step 2: copy as the Tauri sidecar (name carries the target triple,
    # which is the externalBin convention Tauri expects).
    Copy-Item $CargoOut (Join-Path $BinDir "rsclaw-$t.exe") -Force
    Log "Sidecar: $BinDir\rsclaw-$t.exe"

    # Step 3: Tauri bundle for this target.
    Log "Building Tauri app ($t)..."
    Push-Location $UiDir
    try {
        npx tauri build --target $t @TauriFlags
    } finally { Pop-Location }

    # Show this target's output.
    $BundleDir = Join-Path $TauriDir "target\$t\$Profile\bundle"
    if (Test-Path $BundleDir) {
        Log "Output ($t):"
        Get-ChildItem $BundleDir -Recurse -Include "*.msi", "*.exe" | ForEach-Object {
            $s = [math]::Round($_.Length / 1MB, 1)
            Write-Host "  ${s}MB  $($_.FullName)"
        }
    }
}

foreach ($t in $BuildTargets) { Build-Target $t }

Log "Build complete! ($($BuildTargets.Count) target(s))"