Files
skills-win/A2A_node/scripts/manage.ps1
T
2026-10-19 22:44:51 +08:00

74 lines
3.9 KiB
PowerShell

# A2A node manager. Usage: manage.ps1 install|start|stop|status|uninstall [-Token <t>] [-Port <n>]
param([Parameter(Mandatory)][ValidateSet('install','start','stop','status','uninstall')]$Action,
[string]$Token, [int]$Port = 0)
$ErrorActionPreference = 'Stop'
$a2aHome = Join-Path $env:USERPROFILE '.a2a'
$dst = Join-Path $a2aHome 'node'
$src = Join-Path $PSScriptRoot 'a2a.py'
$task = 'A2A_node'
$py = (Get-Command pythonw.exe -ErrorAction SilentlyContinue).Source
if (-not $py) { $py = (Get-Command python.exe).Source }
$script = Join-Path $dst 'a2a.py'
function Stop-Node {
Get-CimInstance Win32_Process | Where-Object { $_.Name -like 'python*' -and $_.CommandLine -like '*a2a.py*serve*' } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
}
function Start-Node {
Stop-Node
Start-Process -FilePath $py -ArgumentList "`"$script`" serve" -WindowStyle Hidden
}
# Best-effort inbound firewall rule (needs admin; never triggers UAC).
function Ensure-Firewall {
$fwPort = 7788
try { $c = Get-Content (Join-Path $a2aHome 'config.json') -Raw | ConvertFrom-Json; if ($c.port) { $fwPort = [int]$c.port } } catch {}
if ($Port) { $fwPort = $Port }
$name = 'A2A_node'
$cmd = "New-NetFirewallRule -DisplayName $name -Direction Inbound -Action Allow -Protocol TCP -LocalPort $fwPort -Profile Any"
try {
if (Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue) { "firewall: rule '$name' already exists"; return }
} catch {}
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
"firewall: not running as administrator, rule not created. Run this in an elevated PowerShell:"
" $cmd"
return
}
try { Invoke-Expression $cmd | Out-Null; "firewall: created rule '$name' (TCP $fwPort, all profiles)" }
catch { "firewall: failed to create rule: $_`n run manually (admin): $cmd" }
}
switch ($Action) {
'install' {
New-Item -ItemType Directory -Force $dst | Out-Null
Copy-Item $src $script -Force
$args = @('init'); if ($Token) { $args += @('--token', $Token) }; if ($Port) { $args += @('--port', $Port) }
& python.exe $script @args
# Claude Code channel bridge (push wake-up) + launcher
$ch = Join-Path $a2aHome 'channel'
New-Item -ItemType Directory -Force $ch | Out-Null
Copy-Item (Join-Path $PSScriptRoot 'a2a-channel.mjs') $ch -Force
Copy-Item (Join-Path $PSScriptRoot 'channel-package.json') (Join-Path $ch 'package.json') -Force
Copy-Item (Join-Path $PSScriptRoot 'channel-mcp.json') (Join-Path $ch 'mcp.json') -Force
Copy-Item (Join-Path $PSScriptRoot 'claude-a2a.cmd') $dst -Force
$mcp = (Get-Content (Join-Path $ch 'mcp.json') -Raw) -replace 'C:/Users/bowei', $env:USERPROFILE.Replace('\', '/')
Set-Content (Join-Path $ch 'mcp.json') $mcp -Encoding ascii
Push-Location $ch; npm install --no-audit --no-fund 2>&1 | Out-Null; Pop-Location
# cmd shim so sessions can just call `a2a`
"@python `"$script`" %*" | Set-Content (Join-Path $dst 'a2a.cmd') -Encoding ascii
$act = New-ScheduledTaskAction -Execute $py -Argument "`"$script`" serve"
$trg = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
$set = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero)
Register-ScheduledTask -TaskName $task -Action $act -Trigger $trg -Settings $set -Force | Out-Null
Ensure-Firewall
Start-Node
Start-Sleep 2
& python.exe $script status
}
'start' { Start-Node; Start-Sleep 2; & python.exe $script status }
'stop' { Stop-Node; 'stopped' }
'status' { & python.exe $script status; & python.exe $script ls; & python.exe $script peers }
'uninstall' { Stop-Node; Unregister-ScheduledTask -TaskName $task -Confirm:$false -ErrorAction SilentlyContinue; 'removed (config kept in ~/.a2a)' }
}