Private/_Agents.ps1
|
# Multi-agent detection + per-agent config writers. # Each agent gets a Detect-* + Install-* + Remove-* + Get-Status-* primitive. # Agents supported in v1: # - claude (Claude Code, $HOME\.claude.json, JSON, mcpServers) # - codex (OpenAI Codex CLI, $HOME\.codex\config.toml, TOML, [mcp_servers.X]) # - cursor (Cursor IDE, $HOME\.cursor\mcp.json, JSON, mcpServers) # - continue (Continue.dev VSCode/JetBrains, $HOME\.continue\config.json, JSON) # - vscode-mcp (VS Code generic mcp.json: $env:APPDATA\Code\User\mcp.json — Insiders + stable) # Each writer is idempotent + backs up before edit. function Get-KritPax8AgentTargets { <# .SYNOPSIS Returns the canonical list of supported agent targets, with detected state per machine. #> [CmdletBinding()] [OutputType([pscustomobject[]])] param() $home = $env:USERPROFILE $appdata = $env:APPDATA $targets = @( @{ Name='claude'; Format='json'; Path=(Join-Path $home '.claude.json'); InstallHint='Claude Code (Anthropic)' } @{ Name='codex'; Format='toml'; Path=(Join-Path $home '.codex/config.toml'); InstallHint='OpenAI Codex CLI' } @{ Name='cursor'; Format='json'; Path=(Join-Path $home '.cursor/mcp.json'); InstallHint='Cursor IDE' } @{ Name='continue'; Format='json'; Path=(Join-Path $home '.continue/config.json'); InstallHint='Continue.dev' } @{ Name='vscode'; Format='json'; Path=(Join-Path $appdata 'Code/User/mcp.json'); InstallHint='VS Code (stable)' } @{ Name='vscode-insiders'; Format='json'; Path=(Join-Path $appdata 'Code - Insiders/User/mcp.json'); InstallHint='VS Code Insiders' } ) foreach ($t in $targets) { $exists = Test-Path -LiteralPath $t.Path # parent existence = signal the host tool is installed; .json/.toml may be auto-created on first launch $parentExists = Test-Path -LiteralPath (Split-Path -Parent $t.Path) [pscustomobject]@{ Name = $t.Name Format = $t.Format Path = $t.Path ConfigExists = $exists ParentExists = $parentExists HostInstalled = ($exists -or $parentExists) InstallHint = $t.InstallHint } } } # --- JSON writer (Claude Code, Cursor, Continue, vscode mcp.json) --- function Write-KritPax8JsonAgentConfig { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory)] [string] $Token, [string] $McpEndpoint = 'https://mcp.pax8.com/v1/mcp', [switch] $IncludeOAuthEntry, [switch] $RemoveOnly ) # Resolve Python for safe JSON editing (handles case-collision keys etc.) $pyExe = $null foreach ($p in @( 'C:\Users\joshl\AppData\Local\Python\pythoncore-3.14-64\python.exe', 'C:\Python314\python.exe','C:\Python313\python.exe','C:\Python312\python.exe','C:\Python311\python.exe' )) { if (Test-Path -LiteralPath $p) { $pyExe = $p; break } } if (-not $pyExe) { foreach ($n in 'py.exe','python3.14.exe','python.exe','python3.exe') { $c = Get-Command $n -ErrorAction SilentlyContinue if ($c -and $c.Source -notlike '*WindowsApps*') { $pyExe = $c.Source; break } } } if (-not $pyExe) { throw "Python 3 required to edit JSON agent configs (handles case-collision keys cleanly)." } # Backup $bak = $null if (Test-Path -LiteralPath $Path) { $utc = (Get-Date).ToUniversalTime().ToString('yyyyMMdd-HHmmssZ') $bak = "$Path.bak.krit-pax8mcp.$utc" Copy-Item -LiteralPath $Path -Destination $bak -Force } else { # Make parent + seed empty New-Item -ItemType Directory -Path (Split-Path -Parent $Path) -Force -ErrorAction SilentlyContinue | Out-Null Set-Content -LiteralPath $Path -Value '{}' -Encoding UTF8 } $skipOAuth = if ($IncludeOAuthEntry.IsPresent) { '0' } else { '1' } $remove = if ($RemoveOnly.IsPresent) { '1' } else { '0' } $pyCode = @" import json,sys,os path = r'$Path' token = sys.stdin.read().strip() skip_oauth = '$skipOAuth' == '1' remove_only = '$remove' == '1' try: with open(path,'r',encoding='utf-8') as f: d = json.load(f) except Exception: d = {} if not isinstance(d, dict): d = {} mcp = d.setdefault('mcpServers', {}) if remove_only: for k in ('pax8','pax8-oauth'): if k in mcp: del mcp[k] else: mcp['pax8'] = { 'type':'http', 'url':'$McpEndpoint', 'headers': {'x-pax8-mcp-token': token} } if not skip_oauth: mcp['pax8-oauth'] = {'type':'http','url':'$McpEndpoint'} elif 'pax8-oauth' in mcp: del mcp['pax8-oauth'] with open(path,'w',encoding='utf-8') as f: json.dump(d,f,indent=2,ensure_ascii=False) print('OK keys=' + ','.join(sorted(mcp.keys()))) "@ $result = $Token | & $pyExe -c $pyCode return [pscustomobject]@{ Path = $Path Backup = $bak Tool = 'python-json' Keys = ($result -replace '^OK keys=','').Split(',') ResultLine = $result } } # --- TOML writer (Codex config.toml) --- function Write-KritPax8TomlAgentConfig { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory)] [string] $Token, [string] $McpEndpoint = 'https://mcp.pax8.com/v1/mcp', [switch] $RemoveOnly ) # Codex MCP entries are toml `[mcp_servers.<name>]` blocks. We append/replace # the `[mcp_servers.pax8]` block. Codex uses OAuth-based remote MCP shape so # the token does not embed into the toml (different from Claude). We retain # the existing url-only entry as the operative path. if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path (Split-Path -Parent $Path) -Force -ErrorAction SilentlyContinue | Out-Null Set-Content -LiteralPath $Path -Value '' -Encoding UTF8 } $utc = (Get-Date).ToUniversalTime().ToString('yyyyMMdd-HHmmssZ') $bak = "$Path.bak.krit-pax8mcp.$utc" Copy-Item -LiteralPath $Path -Destination $bak -Force $content = Get-Content -LiteralPath $Path -Raw # Strip any prior [mcp_servers.pax8] block (best-effort: from header to next blank-line + bracket) $stripped = [regex]::Replace( $content, '(?ms)^\[mcp_servers\.pax8\][^\[]*?(?=^\[|\Z)', '' ) if ($RemoveOnly.IsPresent) { [System.IO.File]::WriteAllText($Path, $stripped.TrimEnd() + "`n", [System.Text.UTF8Encoding]::new($false)) return [pscustomobject]@{ Path=$Path; Backup=$bak; Tool='toml-regex'; Removed=$true } } $appendix = @" [mcp_servers.pax8] enabled = true url = "$McpEndpoint" "@ $newContent = $stripped.TrimEnd() + "`n" + $appendix.TrimStart() + "`n" [System.IO.File]::WriteAllText($Path, $newContent, [System.Text.UTF8Encoding]::new($false)) return [pscustomobject]@{ Path=$Path; Backup=$bak; Tool='toml-regex'; Removed=$false } } # --- Per-agent dispatcher --- function Install-KritPax8McpForAgent { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $AgentName, [Parameter(Mandatory)] [string] $Token, [string] $McpEndpoint = 'https://mcp.pax8.com/v1/mcp', [switch] $IncludeOAuthEntry, [switch] $RemoveOnly ) $targets = Get-KritPax8AgentTargets $t = $targets | Where-Object Name -eq $AgentName | Select-Object -First 1 if (-not $t) { throw "Unknown agent name: $AgentName. Valid: $($targets.Name -join ', ')" } if ($t.Format -eq 'json') { Write-KritPax8JsonAgentConfig -Path $t.Path -Token $Token -McpEndpoint $McpEndpoint -IncludeOAuthEntry:$IncludeOAuthEntry -RemoveOnly:$RemoveOnly } elseif ($t.Format -eq 'toml') { Write-KritPax8TomlAgentConfig -Path $t.Path -Token $Token -McpEndpoint $McpEndpoint -RemoveOnly:$RemoveOnly } else { throw "Unsupported format $($t.Format) for agent $AgentName" } } |