* feat: winnode CLI for invoking node commands over local MCP Mirrors `openclaw nodes invoke`'s flag surface but routes to the local tray's MCP HTTP server (default http://127.0.0.1:8765/) instead of the gateway. `--node` and `--idempotency-key` are accepted for paste-from- gateway parity and ignored. Ships skill.md alongside winnode.exe documenting every supported command, argument schema, and the A2UI v0.8 JSONL grammar for agent use. Tests: 62 cases, 100% line/branch on CliRunner via in-process unit tests plus a loopback HttpListener fake that exercises the full HTTP path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(test): gate MCP readiness on token-bearing client InitializeAsync would return ready as soon as `GET /` returned 200, even if `mcp-token.txt` had not been read yet. Against a tray binary built before the auth-before-dispatch hardening (where `GET /` answers 200 without auth), this raced ahead and handed back a tokenless `Client` — every subsequent POST then 401'd. Restructure the loop to require both the token-on-disk and a 200 from a token-bearing GET before declaring ready. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(winnode): auto-load MCP bearer token The CLI now sends `Authorization: Bearer <token>` on every MCP request, without the user having to plumb the token themselves. Resolution chain mirrors the per-tool secret convention (gh, az, anthropic): 1. `--mcp-token <literal>` flag 2. `OPENCLAW_MCP_TOKEN` env var (literal) 3. `mcp-token.txt` under `$OPENCLAW_TRAY_DATA_DIR` if set, else `%APPDATA%\OpenClawTray\` — the same location SettingsManager points the tray at, so a sandboxed tray is found automatically. When the token comes from disk, run `McpAuthToken.VerifyAcl` (the same hygiene check `NodeService.StartMcpServer` runs at startup) and route any owner/DACL warning to stderr so the user knows to rotate. `--verbose` reports the resolved auth source without echoing the secret value. Tests redirect via `OPENCLAW_TRAY_DATA_DIR` to a temp sandbox dir so they don't pick up the developer machine's real tray token. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(winnode): apply 19 review findings (F-01..F-21) Hardens the winnode CLI against the threat model in C:/temp/winnode-cli-review-2026-04-30/01-findings.md. F-15 (port-0 nit) was approved as no-action; F-17 was a positive observation. - F-01/F-09: validate --mcp-url; refuse auto-loaded token off-loopback - F-02: explicit SocketsHttpHandler with AllowAutoRedirect=false - F-03: cap response body at 16 MiB with explicit overflow message - F-04: warn unconditionally when --mcp-token is used (process-listing leak) - F-05: warn unconditionally when --idempotency-key is supplied - F-06: TokenLooksValid ASCII-printable check; ignore corrupt tokens - F-07: don't echo full token-file path in --verbose - F-08: canonicalize OPENCLAW_TRAY_DATA_DIR; reject symlink redirect - F-10: RunAsyncTests is now IDisposable (cleans up sandbox dir) - F-11: SkillMdDriftTests + REGENERATE-ME header in skill.md; McpToolBridge.KnownCommands exposes the canonical command set; skill.md re-synced with live capability surface - F-12: --params @<path> loads JSON object from disk - F-13: Token_file_with_wide_acl_emits_warn (Windows-only, gracefully skips when SetAccessControl is denied by hardened CI) - F-14: BuildToolsCallBody returns (byte[], int) consumed by ByteArrayContent without a string round-trip - F-16+F-21: SanitizeForStderr strips control chars, redacts ≥32-char base64url runs, caps at 4 KiB, default-quiet first-line-only, full sanitized body under --verbose - F-18: --invoke-timeout capped at 600000 ms; long arithmetic on the +5000 buffer; out-of-range exits 2 - F-19: --mcp-port and OPENCLAW_MCP_PORT bounded [1, 65535]; env-var out-of-range falls back to default with a verbose warning - F-20: distinguish missing/empty/unreadable/loaded token-file states; unreadable exits 1 with a diagnostic before any HTTP traffic Tests: 23 added (115/115 pass). All other suites stay green (Shared 1046/1066, Tray 245/245, Integration 18/18, UI 62/62). WinNode CLI line coverage: 91.6% (434/474 in Program.cs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
242 lines
7.6 KiB
PowerShell
242 lines
7.6 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Build script for OpenClaw Windows Hub
|
|
|
|
.DESCRIPTION
|
|
Builds all projects, checks prerequisites, and provides clear guidance.
|
|
|
|
.PARAMETER Project
|
|
Which project to build: All, Tray, WinUI, Shared, CommandPalette, Cli
|
|
Default: All
|
|
|
|
.PARAMETER Configuration
|
|
Build configuration: Debug, Release
|
|
Default: Debug
|
|
|
|
.PARAMETER CheckOnly
|
|
Only check prerequisites, don't build
|
|
|
|
.EXAMPLE
|
|
.\build.ps1
|
|
.\build.ps1 -Project WinUI -Configuration Release
|
|
.\build.ps1 -CheckOnly
|
|
#>
|
|
|
|
param(
|
|
[ValidateSet("All", "Tray", "WinUI", "Shared", "CommandPalette", "Cli", "WinNodeCli")]
|
|
[string]$Project = "All",
|
|
|
|
[ValidateSet("Debug", "Release")]
|
|
[string]$Configuration = "Debug",
|
|
|
|
[switch]$CheckOnly
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# Colors for output
|
|
function Write-Header($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan }
|
|
function Write-Success($text) { Write-Host "✅ $text" -ForegroundColor Green }
|
|
function Write-Warning($text) { Write-Host "⚠️ $text" -ForegroundColor Yellow }
|
|
function Write-Error($text) { Write-Host "❌ $text" -ForegroundColor Red }
|
|
function Write-Info($text) { Write-Host " $text" -ForegroundColor Gray }
|
|
|
|
# Track issues
|
|
$issues = @()
|
|
|
|
Write-Host @"
|
|
|
|
🦞 OpenClaw Windows Hub - Build Script
|
|
=======================================
|
|
|
|
"@ -ForegroundColor Magenta
|
|
|
|
# =============================================================================
|
|
# PREREQUISITE CHECKS
|
|
# =============================================================================
|
|
|
|
Write-Header "Checking Prerequisites"
|
|
|
|
# Check OS
|
|
if ($env:OS -ne "Windows_NT") {
|
|
Write-Error "This project requires Windows"
|
|
exit 1
|
|
}
|
|
Write-Success "Windows detected"
|
|
|
|
# Check .NET SDK
|
|
$dotnetVersion = $null
|
|
try {
|
|
$dotnetVersion = & dotnet --version 2>$null
|
|
} catch {}
|
|
|
|
if (-not $dotnetVersion) {
|
|
Write-Error ".NET SDK not found"
|
|
Write-Info "Download from: https://dotnet.microsoft.com/download"
|
|
$issues += "Missing .NET SDK"
|
|
} else {
|
|
Write-Success ".NET SDK: $dotnetVersion"
|
|
|
|
# Check for .NET 10 (needed for all projects)
|
|
$sdks = & dotnet --list-sdks 2>$null
|
|
$hasNet10 = $sdks | Where-Object { $_ -match "^10\." }
|
|
|
|
if (-not $hasNet10) {
|
|
Write-Error ".NET 10 SDK not found (required for all projects)"
|
|
Write-Info "Download preview from: https://dotnet.microsoft.com/download/dotnet/10.0"
|
|
$issues += "Missing .NET 10 SDK"
|
|
} else {
|
|
Write-Success ".NET 10 SDK available"
|
|
}
|
|
}
|
|
|
|
# Check Windows SDK (for WinUI)
|
|
$windowsSdkPath = "${env:ProgramFiles(x86)}\Windows Kits\10\Include"
|
|
if (Test-Path $windowsSdkPath) {
|
|
$sdkVersions = Get-ChildItem $windowsSdkPath -Directory | Select-Object -ExpandProperty Name | Sort-Object -Descending
|
|
Write-Success "Windows SDK: $($sdkVersions[0])"
|
|
} else {
|
|
Write-Warning "Windows 10 SDK not found (needed for WinUI build)"
|
|
Write-Info "Install via Visual Studio Installer or standalone SDK"
|
|
$issues += "Windows 10 SDK not detected"
|
|
}
|
|
|
|
# Check WebView2 Runtime (for WinUI chat window)
|
|
$webView2Key = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
|
|
$webView2KeyAlt = "HKCU:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
|
|
$webView2Version = $null
|
|
|
|
if (Test-Path $webView2Key) {
|
|
$webView2Version = (Get-ItemProperty $webView2Key -ErrorAction SilentlyContinue).pv
|
|
} elseif (Test-Path $webView2KeyAlt) {
|
|
$webView2Version = (Get-ItemProperty $webView2KeyAlt -ErrorAction SilentlyContinue).pv
|
|
}
|
|
|
|
if ($webView2Version) {
|
|
Write-Success "WebView2 Runtime: $webView2Version"
|
|
} else {
|
|
Write-Warning "WebView2 Runtime not detected (needed for WinUI chat window)"
|
|
Write-Info "Usually pre-installed on Windows 10/11. Get from: https://developer.microsoft.com/microsoft-edge/webview2"
|
|
# Not a hard failure - app will fall back to browser
|
|
}
|
|
|
|
# Check architecture
|
|
$arch = $env:PROCESSOR_ARCHITECTURE
|
|
Write-Success "Architecture: $arch"
|
|
if ($arch -eq "ARM64") {
|
|
Write-Info "ARM64 detected - builds will target ARM64 by default"
|
|
}
|
|
|
|
# Summary
|
|
Write-Header "Prerequisite Summary"
|
|
|
|
if ($issues.Count -eq 0) {
|
|
Write-Success "All prerequisites met!"
|
|
} else {
|
|
Write-Warning "$($issues.Count) issue(s) found:"
|
|
foreach ($issue in $issues) {
|
|
Write-Info "- $issue"
|
|
}
|
|
}
|
|
|
|
if ($CheckOnly) {
|
|
Write-Host "`nRun without -CheckOnly to build.`n"
|
|
exit 0
|
|
}
|
|
|
|
# =============================================================================
|
|
# BUILD
|
|
# =============================================================================
|
|
|
|
Write-Header "Building Projects ($Configuration)"
|
|
|
|
# Detect runtime identifier based on architecture
|
|
$rid = if ($arch -eq "ARM64") { "win-arm64" } else { "win-x64" }
|
|
Write-Info "Runtime identifier: $rid"
|
|
|
|
$buildResults = @{}
|
|
|
|
function Build-Project($name, $path, $useRid = $false) {
|
|
Write-Host "`nBuilding $name..." -ForegroundColor White
|
|
|
|
if (-not (Test-Path $path)) {
|
|
Write-Error "Project not found: $path"
|
|
return $false
|
|
}
|
|
|
|
# WinUI requires runtime identifier for self-contained WebView2 support
|
|
if ($useRid) {
|
|
$result = & dotnet build $path -c $Configuration -r $rid 2>&1
|
|
} else {
|
|
$result = & dotnet build $path -c $Configuration 2>&1
|
|
}
|
|
$exitCode = $LASTEXITCODE
|
|
|
|
if ($exitCode -eq 0) {
|
|
Write-Success "$name built successfully"
|
|
return $true
|
|
} else {
|
|
Write-Error "$name build failed"
|
|
# Show relevant error lines
|
|
$result | Select-String "error" | Select-Object -First 5 | ForEach-Object {
|
|
Write-Info $_.Line
|
|
}
|
|
return $false
|
|
}
|
|
}
|
|
|
|
$projects = @{
|
|
"Shared" = @{ Path = "src/OpenClaw.Shared/OpenClaw.Shared.csproj"; UseRid = $false }
|
|
"Cli" = @{ Path = "src/OpenClaw.Cli/OpenClaw.Cli.csproj"; UseRid = $false }
|
|
"WinNodeCli" = @{ Path = "src/OpenClaw.WinNode.Cli/OpenClaw.WinNode.Cli.csproj"; UseRid = $false }
|
|
"Tray" = @{ Path = "src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj"; UseRid = $true }
|
|
"WinUI" = @{ Path = "src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj"; UseRid = $true }
|
|
"CommandPalette" = @{ Path = "src/OpenClaw.CommandPalette/OpenClaw.CommandPalette.csproj"; UseRid = $false }
|
|
}
|
|
|
|
$toBuild = if ($Project -eq "All") { @("Shared", "Cli", "WinNodeCli", "WinUI") } else { @($Project) }
|
|
|
|
# Always build Shared first if building other projects
|
|
if ($Project -ne "Shared" -and $Project -ne "All" -and $toBuild -notcontains "Shared") {
|
|
$toBuild = @("Shared") + $toBuild
|
|
}
|
|
|
|
foreach ($proj in $toBuild) {
|
|
if ($projects.ContainsKey($proj)) {
|
|
$projInfo = $projects[$proj]
|
|
$buildResults[$proj] = Build-Project $proj $projInfo.Path $projInfo.UseRid
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# SUMMARY
|
|
# =============================================================================
|
|
|
|
Write-Header "Build Summary"
|
|
|
|
$successCount = ($buildResults.Values | Where-Object { $_ -eq $true }).Count
|
|
$failCount = ($buildResults.Values | Where-Object { $_ -eq $false }).Count
|
|
|
|
foreach ($proj in $buildResults.Keys) {
|
|
if ($buildResults[$proj]) {
|
|
Write-Success "$proj"
|
|
} else {
|
|
Write-Error "$proj"
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
if ($failCount -eq 0) {
|
|
Write-Host "🦞 All builds succeeded!" -ForegroundColor Green
|
|
|
|
Write-Host "`nTo run:" -ForegroundColor Cyan
|
|
if ($buildResults.ContainsKey("WinUI") -or $buildResults.ContainsKey("All")) {
|
|
Write-Host " WinUI: .\src\OpenClaw.Tray.WinUI\bin\$Configuration\net10.0-windows10.0.19041.0\$rid\OpenClaw.Tray.WinUI.exe" -ForegroundColor White
|
|
}
|
|
} else {
|
|
Write-Host "❌ $failCount build(s) failed" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
Write-Host ""
|