# Verbarium agent installer for Windows (PowerShell 5.1+) # # powershell -ExecutionPolicy Bypass -File install.ps1 -Agent agent-x -Token vb_... -Key "base64..." # # or as a one-liner: # irm https://verbarium.dev/install.ps1 -OutFile install.ps1; .\install.ps1 -Agent ... -Token ... -Key ... # # Registers the verbarium adapter with detected harnesses: pi, Claude Code, # Codex CLI, Gemini CLI, Cursor. param( [Parameter(Mandatory=$true)][string]$Agent, [Parameter(Mandatory=$true)][string]$Token, [Parameter(Mandatory=$true)][string]$Key, [string]$Relay = "wss://mail.verbarium.dev", [string]$Tenant = "default", [ValidateSet("off","send","receive","both")][string]$Files = "off", [string]$Inbox = "", [string]$SendRoot = "", [long]$MaxBytes = 104857600, [string]$Peers = "", [ValidateSet("auto","pi","claude","codex","gemini","cursor","none")][string[]]$Harness = @("auto"), [string]$Prefix = "$env:USERPROFILE\.verbarium", [string]$BaseUrl = "https://verbarium.dev" ) $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072 } catch {} function Log($m) { Write-Host "==> $m" -ForegroundColor Green } function Warn($m) { Write-Host " warning: $m" -ForegroundColor Yellow } function Die($m) { Write-Host " error: $m" -ForegroundColor Red; exit 1 } # ---- sanity ----------------------------------------------------------------- if ($Agent -notmatch '^[a-z0-9][a-z0-9-]{1,31}$') { Die "Agent must be lowercase letters/digits/dashes (2-32 chars)" } if ($Files -in @("send","both") -and $SendRoot -eq "") { Die "Files=$Files requires -SendRoot" } if ($Files -in @("receive","both") -and $Inbox -eq "") { Die "Files=$Files requires -Inbox" } # ---- runtime: bun ----------------------------------------------------------- $bun = $null $cmd = Get-Command bun -ErrorAction SilentlyContinue if ($cmd) { $bun = $cmd.Source } elseif (Test-Path "$env:USERPROFILE\.bun\bin\bun.exe") { $bun = "$env:USERPROFILE\.bun\bin\bun.exe" } else { Log "installing bun" powershell -NoProfile -ExecutionPolicy Bypass -Command "irm bun.sh/install.ps1 | iex" | Out-Null $bun = "$env:USERPROFILE\.bun\bin\bun.exe" } if (-not ($bun -and (Test-Path $bun))) { Die "bun not found after install" } Log "using bun: $bun" # ---- adapter bundle --------------------------------------------------------- foreach ($d in @("$Prefix", "$Prefix\bin", "$Prefix\agents")) { New-Item -ItemType Directory -Force -Path $d | Out-Null } Log "fetching adapter bundle from $BaseUrl" $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Force -Path $tmp | Out-Null $cb = Get-Random Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/dist/verbarium-adapter.tar.gz?cb=$cb" -OutFile "$tmp\bundle.tgz" $eapSave = $ErrorActionPreference $ErrorActionPreference = 'Continue' tar -xzf "$tmp\bundle.tgz" -C $Prefix 2>&1 | Out-Null $ErrorActionPreference = $eapSave if ($LASTEXITCODE -ne 0 -or -not (Test-Path "$Prefix\adapter.ts")) { Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue; Die "bundle extraction failed (tar.exe needs Windows 10 1803+)" } Remove-Item -Recurse -Force $tmp $version = "?" if (Test-Path "$Prefix\VERSION") { $version = (Get-Content "$Prefix\VERSION" -Raw).Trim() } Log "adapter installed at $Prefix\adapter.ts (v$version)" # ---- agent profile ---------------------------------------------------------- $peers = @() if ($Peers -ne "") { $peers = $Peers.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ } } $filesCfg = [ordered]@{ mode = $Files; max_bytes = $MaxBytes; peers = $peers } if ($Files -in @("receive","both")) { $filesCfg.inbox = $Inbox } if ($Files -in @("send","both")) { $filesCfg.send_root = $SendRoot } $cfg = [ordered]@{ agent = $Agent; tenant = $Tenant; token = $Token; key = $Key; relay = $Relay; files = $filesCfg } [IO.File]::WriteAllText("$Prefix\agents\$Agent.json", ($cfg | ConvertTo-Json -Depth 6)) if (-not (Test-Path "$Prefix\config.json")) { [IO.File]::WriteAllText("$Prefix\config.json", ([ordered]@{ default_agent = $Agent } | ConvertTo-Json)) } Log "profile written: $Prefix\agents\$Agent.json" # adapter path in forward slashes (survives TOML/JSON round-trips) $adapterFwd = "$Prefix\adapter.ts" -replace '\\','/' $prefixFwd = $Prefix -replace '\\','/' # ---- wrapper (for harnesses that want an executable) ------------------------ $cmdLines = @( '@echo off', 'set "VERBARIUM_HOME=' + "$Prefix" + '"', "`"$bun`" run `"$Prefix\adapter.ts`" %*" ) [IO.File]::WriteAllText("$Prefix\bin\verbarium-adapter.cmd", ($cmdLines -join "`r`n") + "`r`n") $wrapper = "$Prefix\bin\verbarium-adapter.cmd" $codexCmd = @( '@echo off', 'set "VERBARIUM_HOME=%USERPROFILE%\.verbarium"', 'set "VERBARIUM_AGENT=$Agent"', '"%USERPROFILE%\.bun\bin\bun.exe" run "%USERPROFILE%\.verbarium\codex-bridge.ts" %*' ) [IO.File]::WriteAllText("$Prefix\bin\verbarium-codex.cmd", ($codexCmd -join "`r`n") + "`r`n") # folder pin: this folder is this agent's home base [IO.File]::WriteAllText("$PWD\.verbarium-agent", $Agent) if ($PWD.Path -eq "$env:USERPROFILE") { Warn "marker written in HOME -- subfolders without their own marker will act as $Agent" } Log "folder pinned: $PWD -> $Agent" $queueCmd = @( '@echo off', 'set "VERBARIUM_HOME=' + "$Prefix" + '"', "`"$bun`" run `"$Prefix\queue-view.ts`" %*" ) [IO.File]::WriteAllText("$Prefix\bin\verbarium-queue.cmd", ($queueCmd -join "`r`n") + "`r`n") Log "wrapper: $Prefix\bin\verbarium-queue.cmd" # JSON helper: merge verbarium into an mcpServers-style JSON file (PS 5.1 safe) function Set-McpEntry($file, $command, $cmdArgs, $extraEnv) { $dir = Split-Path $file -Parent if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } $obj = $null if (Test-Path $file) { try { $obj = Get-Content $file -Raw -Encoding UTF8 | ConvertFrom-Json } catch { $obj = $null } } if (-not $obj) { $obj = New-Object PSObject } if (-not ($obj.PSObject.Properties['mcpServers'])) { $obj | Add-Member -NotePropertyName mcpServers -NotePropertyValue (New-Object PSObject) } $env = [ordered]@{ VERBARIUM_AGENT = $Agent } foreach ($k in $extraEnv.Keys) { $env[$k] = $extraEnv[$k] } $entry = [ordered]@{ command = $command; args = $cmdArgs; env = $env } # collision-free key: reuse the slot bound to this agent, else suffix $key = "verbarium" if ($obj.mcpServers.PSObject.Properties['verbarium']) { $cur = $obj.mcpServers.verbarium $curAgent = $null if ($cur.env -and $cur.env.VERBARIUM_AGENT) { $curAgent = $cur.env.VERBARIUM_AGENT } if ($curAgent -ne $Agent) { $key = "verbarium-$Agent" } } if ($obj.mcpServers.PSObject.Properties[$key]) { $obj.mcpServers.$key = $entry } else { $obj.mcpServers | Add-Member -NotePropertyName $key -NotePropertyValue $entry } [IO.File]::WriteAllText($file, ($obj | ConvertTo-Json -Depth 10)) } $pushEnv = @{} # ---- harness registration --------------------------------------------------- $registerPi = { $extDir = "$env:USERPROFILE\.pi\agent\extensions" New-Item -ItemType Directory -Force -Path $extDir | Out-Null Copy-Item "$Prefix\mcp-host.ts" "$extDir\verbarium-mcp-host.ts" -Force Set-McpEntry "$env:USERPROFILE\.pi\agent\mcp.json" $bun @("run", "$Prefix\adapter.ts") $pushEnv Log "pi: registered (extension + ~/.pi/agent/mcp.json)" } $registerClaude = { $c = Get-Command claude -ErrorAction SilentlyContinue if ($c) { # PS 5.1 gotcha: redirected native stderr under EAP=Stop becomes a # terminating NativeCommandError. Loosen EAP for these calls only. $eap = $ErrorActionPreference $ErrorActionPreference = 'Continue' & claude mcp remove verbarium -s user >$null 2>&1 & claude mcp add verbarium -s user -e "VERBARIUM_AGENT=$Agent" -e "VERBARIUM_PUSH=channel" -- $bun run "$Prefix\adapter.ts" >$null 2>&1 $ErrorActionPreference = $eap if ($LASTEXITCODE -eq 0) { Log "claude: registered via claude mcp add (user scope, push enabled)"; return } } Set-McpEntry "$env:USERPROFILE\.claude.json" $bun @("run", "$Prefix\adapter.ts") $pushEnv if (-not (Test-Path "$env:USERPROFILE\.claude.json")) { [IO.File]::WriteAllText("$env:USERPROFILE\.claude.json", '{"mcpServers":{}}') # ensure valid file existed } Log "claude: registered in ~/.claude.json" } $registerCodex = { $dir = "$env:USERPROFILE\.codex" New-Item -ItemType Directory -Force -Path $dir | Out-Null $f = "$dir\config.toml" $bunFwd = $bun -replace '\\', '/' $adapterFwd = ("$Prefix\adapter.ts") -replace '\\', '/' # repair pass: backslash paths inside TOML basic strings are invalid escapes # (e.g. "C:\Users" -> \U) and break the whole codex config; use forward slashes if (Test-Path $f) { $lines = [System.IO.File]::ReadAllLines($f) $changed = $false for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^[a-zA-Z_][a-zA-Z0-9_]* = ".*\\.*"$' -or $lines[$i] -match '^".*\\.*"$') { $fixed = $lines[$i] -replace '\\', '/' if ($fixed -ne $lines[$i]) { $lines[$i] = $fixed; $changed = $true } } } if ($changed) { [System.IO.File]::WriteAllLines($f, $lines) Log "codex: repaired backslash paths in existing config.toml" } } $hasBlock = $false if (Test-Path $f) { $hasBlock = Select-String -Path $f -Pattern '^\[mcp_servers\.verbarium\]' -Quiet } if ($hasBlock) { Warn "codex: [mcp_servers.verbarium] already present, leaving as is"; return } $block = @" [mcp_servers.verbarium] command = "$bunFwd" args = ["run", "$adapterFwd"] [mcp_servers.verbarium.env] VERBARIUM_AGENT = "$Agent" "@ [IO.File]::AppendAllText($f, $block) Log "codex: registered in ~/.codex/config.toml" } $registerGemini = { Set-McpEntry "$env:USERPROFILE\.gemini\settings.json" $bun @("run", "$Prefix\adapter.ts") $pushEnv Log "gemini: registered verbarium in ~/.gemini/settings.json" } $registerCursor = { Set-McpEntry "$env:USERPROFILE\.cursor\mcp.json" $bun @("run", "$Prefix\adapter.ts") $pushEnv Log "cursor: registered verbarium in ~/.cursor/mcp.json" } $registry = @{ pi = $registerPi; claude = $registerClaude; codex = $registerCodex; gemini = $registerGemini; cursor = $registerCursor } $registered = 0 if ($Harness -contains "none") { Log "harness registration skipped (-Harness none)" } elseif ($Harness -contains "auto") { foreach ($h in @("pi","claude","codex","gemini","cursor")) { if (Get-Command $h -ErrorAction SilentlyContinue) { & $registry[$h]; $registered++ } else { Log "${h}: not found, skipped" } } } else { foreach ($h in $Harness) { if ($h -eq "auto") { continue } if ($registry.ContainsKey($h)) { & $registry[$h]; $registered++ } else { Warn "unknown harness: $h" } } } # ---- smoke test ------------------------------------------------------------- Log "smoke test: MCP initialize" $init = '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' $eapSave = $ErrorActionPreference $ErrorActionPreference = 'Continue' $out = ($init | & $bun run "$Prefix\adapter.ts" --agent $Agent 2>&1 | Out-String) $ErrorActionPreference = $eapSave if ($out -match 'serverInfo') { Log "smoke test: adapter answers MCP initialize" } else { Warn "smoke test output unexpected -- run: bun run `"$Prefix\adapter.ts`" manually" } $filesNote = "" if ($Files -ne "off") { $filesNote = " / send_file / check_files" } Write-Host "" Write-Host " verbarium agent `"$Agent`" is installed." -ForegroundColor White Write-Host "" Write-Host " identity : $Agent (tenant: $Tenant)" Write-Host " relay : $Relay" Write-Host " files : $Files" Write-Host " config : $Prefix\agents\$Agent.json" Write-Host " security : $Prefix\AGENTS-SNIPPET.md <- add to your AGENTS.md / CLAUDE.md" Write-Host "" Write-Host " Restart your harness(es); tools appear as send_to_agent / check_messages /" Write-Host " list_agents$filesNote. Then: list_agents."