public/Invoke-WtwSbx.ps1
|
function Invoke-WtwSbx { <# .SYNOPSIS Launch an AI agent sandbox (sbx) for the current or named worktree. .DESCRIPTION Reads the worktree's .code-workspace file to discover all workspace folders, then launches `sbx run <agent>` with the primary folder writable and additional folders mounted read-only. Requires the `sbx` CLI (brew install docker/tap/sbx). .PARAMETER Instruction Optional task instruction passed after `--` to the agent. .PARAMETER Name Worktree name or alias to target (default: current directory). .PARAMETER Agent Agent to run inside the sandbox (default: claude). .PARAMETER Writable Mount all workspace folders writable instead of read-only. .PARAMETER DryRun Print the sbx command without executing it. .EXAMPLE wtw sbx Launch Claude sandbox in the current worktree. .EXAMPLE wtw sbx "fix the broken tests" Launch with an instruction. .EXAMPLE wtw sbx --name auth "refactor auth module" Launch for a specific worktree with an instruction. .EXAMPLE wtw sbx --agent codex --dry-run Preview the sbx command for the codex agent. #> [CmdletBinding()] param( [Parameter(Position = 0)] [string] $Instruction, [string] $Name, [string] $Agent = 'claude', [string] $Template = 'snowmain-base', [switch] $Writable, [switch] $DryRun ) if (-not (Get-Command 'sbx' -ErrorAction SilentlyContinue)) { Write-Host '' Write-Host ' ✗ sbx not found' -ForegroundColor Red Write-Host ' Install: brew install docker/tap/sbx' -ForegroundColor DarkGray Write-Host '' return } # Resolve target $targetPath = $null $workspaceFile = $null if ($Name) { $target = Resolve-WtwTarget $Name if (-not $target) { return } $targetPath = if ($target.WorktreeEntry) { $target.WorktreeEntry.path } else { $target.RepoEntry.mainPath } $workspaceFile = if ($target.WorktreeEntry) { $target.WorktreeEntry.workspace } else { $target.RepoEntry.templateWorkspace } } else { $root = Resolve-WtwRepoRoot $targetPath = $root ?? (Get-Location).Path $repoName, $repoEntry = Get-WtwRepoFromCwd if ($repoName -and $repoEntry -and $root) { $rootResolved = [System.IO.Path]::GetFullPath($root) if ($repoEntry.worktrees) { foreach ($taskName in $repoEntry.worktrees.PSObject.Properties.Name) { $wt = $repoEntry.worktrees.$taskName if ($wt.path -and [System.IO.Path]::GetFullPath($wt.path) -eq $rootResolved) { $workspaceFile = $wt.workspace # Use the worktree path as targetPath to ensure consistency $targetPath = $wt.path break } } } if (-not $workspaceFile) { $workspaceFile = $repoEntry.templateWorkspace # Use the main repo path as targetPath $targetPath = $repoEntry.mainPath } } } if (-not (Test-Path $targetPath)) { Write-Error "Worktree path not found: $targetPath" return } # Build mount list: primary is writable, extras are :ro (unless --writable) $mounts = [System.Collections.Generic.List[string]]::new() $mounts.Add($targetPath) $extraFolders = @() if ($workspaceFile -and (Test-Path $workspaceFile)) { $ws = Read-JsoncFile $workspaceFile if ($ws -and $ws.folders) { $wsDir = Split-Path $workspaceFile -Parent $primaryNorm = [System.IO.Path]::GetFullPath($targetPath) foreach ($folder in $ws.folders) { $fp = $folder.path if ([string]::IsNullOrWhiteSpace($fp)) { continue } if (-not [System.IO.Path]::IsPathRooted($fp)) { $fp = [System.IO.Path]::GetFullPath((Join-Path $wsDir $fp)) } if ([System.IO.Path]::GetFullPath($fp) -eq $primaryNorm) { continue } if (-not (Test-Path $fp)) { continue } $extraFolders += $fp $mounts.Add($(if ($Writable) { $fp } else { "${fp}:ro" })) } } } # Build sbx args $sbxArgs = @('run', $Agent) if ($Template) { $sbxArgs += @('--template', $Template) } $sbxArgs += $mounts.ToArray() if ($Instruction) { $sbxArgs += '--' $sbxArgs += $Instruction } # Display Write-Host '' if ($DryRun) { Write-Host ' [dry-run] sbx ' -ForegroundColor DarkCyan -NoNewline Write-Host ($sbxArgs -join ' ') -ForegroundColor Cyan Write-Host '' return } Write-Host " Launching $Agent sandbox" -ForegroundColor Cyan Write-Host " Primary: $targetPath" -ForegroundColor DarkGray foreach ($m in $extraFolders) { $label = if ($Writable) { ' Mount: ' } else { ' Mount:ro: ' } Write-Host "$label$m" -ForegroundColor DarkGray } if ($Instruction) { Write-Host " Task: $Instruction" -ForegroundColor DarkGray } Write-Host '' & sbx @sbxArgs } |