public/Open-WtwCmuxWorkspace.ps1
|
function Get-WtwCmuxObjectValue { [CmdletBinding()] param( [Parameter(Mandatory)] $Object, [Parameter(Mandatory)][string[]] $Names ) foreach ($name in $Names) { $current = $Object $found = $true foreach ($part in $name.Split('.')) { if (-not $current) { $found = $false; break } $prop = $current.PSObject.Properties[$part] if (-not $prop) { $found = $false; break } $current = $prop.Value } if ($found -and $null -ne $current -and "$current" -ne '') { return $current } } return $null } function Get-WtwCmuxWorkspaceRef { [CmdletBinding()] param([Parameter(Mandatory)] $Workspace) return Get-WtwCmuxObjectValue -Object $Workspace -Names @( 'ref', 'workspaceRef', 'workspace', 'id', 'uuid', 'workspace_id', 'workspaceId', 'refs.workspace', 'refs.workspaceRef' ) } function Get-WtwCmuxWorkspaceName { [CmdletBinding()] param([Parameter(Mandatory)] $Workspace) return Get-WtwCmuxObjectValue -Object $Workspace -Names @('name', 'title', 'displayName') } function Get-WtwCmuxWorkspaceCwd { [CmdletBinding()] param([Parameter(Mandatory)] $Workspace) return Get-WtwCmuxObjectValue -Object $Workspace -Names @( 'cwd', 'path', 'current_directory', 'workingDirectory', 'currentWorkingDirectory', 'sidebar.cwd', 'sidebarState.cwd' ) } function Get-WtwCmuxLiveWorkspaces { [CmdletBinding()] param() $result = Invoke-WtwCmuxCommand -ArgumentList @('list-workspaces', '--json') if ($result.ExitCode -ne 0) { return @() } $parsed = ConvertFrom-WtwCmuxJsonOutput -Output $result.Output if ($parsed) { if ($parsed -is [array]) { return @($parsed) } if ((Get-WtwPropertyNames -Object $parsed) -contains 'workspaces') { return @($parsed.workspaces) } return @($parsed) } $fallbackResult = Invoke-WtwCmuxCommand -ArgumentList @('list-workspaces') if ($fallbackResult.ExitCode -ne 0) { return @() } return ConvertFrom-WtwCmuxWorkspaceListOutput -Output $fallbackResult.Output } function Find-WtwCmuxWorkspace { [CmdletBinding()] param( [Parameter(Mandatory)][string] $ProjectPath, [Parameter(Mandatory)][string] $PrettyName ) $fullPath = [System.IO.Path]::GetFullPath($ProjectPath) $workspaces = @(Get-WtwCmuxLiveWorkspaces) if ($workspaces.Count -eq 0) { return $null } $byCwd = $workspaces | Where-Object { $cwd = Get-WtwCmuxWorkspaceCwd -Workspace $_ $cwd -and [string]::Equals([System.IO.Path]::GetFullPath("$cwd"), $fullPath, [System.StringComparison]::OrdinalIgnoreCase) } | Select-Object -First 1 if ($byCwd) { return $byCwd } return $workspaces | Where-Object { [string]::Equals((Get-WtwCmuxWorkspaceName -Workspace $_), $PrettyName, [System.StringComparison]::OrdinalIgnoreCase) } | Select-Object -First 1 } function Set-WtwCmuxWorkspaceMetadata { [CmdletBinding()] param( [string] $WorkspaceRef, [string] $PrettyName, [string] $Color, [string] $StatusValue, [string] $CurrentName, [string] $CurrentColor ) if (-not $WorkspaceRef) { return } if ($PrettyName -and -not [string]::Equals($CurrentName, $PrettyName, [System.StringComparison]::Ordinal)) { Invoke-WtwCmuxCommand -ArgumentList @('workspace-action', '--workspace', $WorkspaceRef, '--action', 'rename', '--title', $PrettyName) | Out-Null } if ($Color -and -not [string]::Equals($CurrentColor, $Color, [System.StringComparison]::OrdinalIgnoreCase)) { Invoke-WtwCmuxCommand -ArgumentList @('workspace-action', '--workspace', $WorkspaceRef, '--action', 'set-color', '--color', $Color) | Out-Null } if ($StatusValue) { $statusColor = if ([string]::IsNullOrWhiteSpace($Color)) { '#7A4FD8' } else { $Color } Invoke-WtwCmuxCommand -ArgumentList @('set-status', 'wtw', $StatusValue, '--workspace', $WorkspaceRef, '--icon', 'git-branch', '--color', $statusColor, '--priority', '90') | Out-Null } } function Test-WtwCmuxSocketPermissionDenied { [CmdletBinding()] param([string] $Output) if ([string]::IsNullOrWhiteSpace($Output)) { return $false } return ( $Output -match 'Access denied' -or $Output -match 'Operation not permitted' -or $Output -match 'only processes started inside cmux can connect' ) } function ConvertTo-WtwPowerShellSingleQuotedLiteral { [CmdletBinding()] param([AllowNull()][string] $Value) return "'$($Value.Replace("'", "''"))'" } function ConvertTo-WtwPosixSingleQuotedLiteral { [CmdletBinding()] param([AllowNull()][string] $Value) if ($null -eq $Value) { return "''" } return "'" + $Value.Replace("'", "'\''") + "'" } function Get-WtwCmuxLocalAppleScriptInitCommand { <# .SYNOPSIS POSIX command typed into cmux's default macOS tab during AppleScript fallback. .DESCRIPTION cmux's default surface is zsh. Do not type PowerShell (Set-Location / Clear-Host) and do not call ``wtw``: a new tab's zshrc may not have loaded the wrapper yet, and older wrappers treated ``__cmux_*`` as an implicit go target. ``cd`` is a builtin, so this works even before PATH or wtw.zsh exist. #> [CmdletBinding()] param([Parameter(Mandatory)][string] $ProjectPath) $fullPath = [System.IO.Path]::GetFullPath($ProjectPath) $quoted = ConvertTo-WtwPosixSingleQuotedLiteral -Value $fullPath return "clear; cd $quoted" } function Get-WtwCmuxAppleScriptFallbackSource { <# .SYNOPSIS osascript body for the cmux AppleScript fallback. .DESCRIPTION Searches every window (not only the front one). Creating a tab in the front window dumped snowmain1's init command into whatever project was focused (e.g. kulissa-landing). A miss opens a new window. #> [CmdletBinding()] param() return @' on run argv set targetPath to item 1 of argv set targetName to item 2 of argv set initCommand to item 3 of argv set matchByNameOnly to false if (count of argv) ≥ 4 then set matchByNameOnly to ((item 4 of argv) is "name-only") end if set tabLabel to targetName if (count of argv) ≥ 5 then set tabLabel to item 5 of argv end if tell application "cmux" activate repeat with candidateWindow in windows repeat with workspaceTab in tabs of candidateWindow try set tabName to (name of workspaceTab as text) if tabName is targetName or tabName is tabLabel then select tab workspaceTab return "selected" end if if not matchByNameOnly then repeat with workspaceTerminal in terminals of workspaceTab try if (working directory of workspaceTerminal as text) is targetPath then select tab workspaceTab return "selected" end if end try end repeat end if end try end repeat end repeat set createdWindow to new window delay 1.0 set createdTab to tab 1 of createdWindow select tab createdTab delay 0.4 set createdTerminal to focused terminal of createdTab input text (initCommand & return) to createdTerminal return "created" end tell end run '@ } function Open-WtwCmuxAppleScriptWorkspace { [CmdletBinding()] param( [Parameter(Mandatory)][string] $ProjectPath, [Parameter(Mandatory)][string] $PrettyName, [string] $InitCommand, [switch] $MatchByNameOnly ) if (-not $IsMacOS) { return $false } if (-not (Get-Command osascript -ErrorAction SilentlyContinue)) { return $false } $fullPath = [System.IO.Path]::GetFullPath($ProjectPath) if (-not $InitCommand) { $InitCommand = Get-WtwCmuxLocalAppleScriptInitCommand -ProjectPath $fullPath } $matchMode = if ($MatchByNameOnly) { 'name-only' } else { 'name-or-cwd' } $tabLabel = Get-WtwCmuxTabLabel -PrettyName $PrettyName $script = Get-WtwCmuxAppleScriptFallbackSource $result = & osascript @('-e', $script, '--', $fullPath, $PrettyName, $InitCommand, $matchMode, $tabLabel) 2>&1 if ($LASTEXITCODE -ne 0) { Write-Verbose "cmux AppleScript fallback failed: $($result -join [Environment]::NewLine)" return $false } return $true } function Open-WtwCmuxWorkspace { <# .SYNOPSIS Open a wtw target as a cmux workspace. .DESCRIPTION Selects an existing live cmux workspace for the target path/name when possible. Otherwise creates a new cmux workspace with the target cwd, name, and color. Falls back to `cmux <path>` when socket-driven creation is unavailable, which also launches cmux when needed. .PARAMETER Target Resolved wtw target object (output of Resolve-WtwTarget). #> [CmdletBinding()] param( [Parameter(Mandatory)] [psobject] $Target ) if (-not (Test-WtwCmuxPresent)) { Write-Error "cmux is not installed or not on PATH. Install cmux or symlink '/Applications/cmux.app/Contents/Resources/bin/cmux'." return } $metadata = Resolve-WtwTerminalWorkspaceMetadata -Target $Target if (-not ($metadata -and $metadata.Path -and (Test-Path $metadata.Path))) { Write-Error 'No directory found for cmux target.' return } $fullDir = $metadata.Path $prettyName = $metadata.PrettyName $color = $metadata.Color $statusValue = $metadata.StatusValue Register-WtwCmuxProject ` -ProjectPath $fullDir ` -PrettyName $prettyName ` -Color $color ` -RepoName $Target.RepoName ` -TaskName $Target.TaskName | Out-Null $existing = Find-WtwCmuxWorkspace -ProjectPath $fullDir -PrettyName $prettyName if ($existing) { $workspaceRef = Get-WtwCmuxWorkspaceRef -Workspace $existing if ($workspaceRef) { $selectResult = Invoke-WtwCmuxCommand -ArgumentList @('select-workspace', '--workspace', "$workspaceRef") if ($selectResult.ExitCode -eq 0) { Set-WtwCmuxWorkspaceMetadata ` -WorkspaceRef "$workspaceRef" ` -PrettyName $prettyName ` -Color $color ` -StatusValue $statusValue ` -CurrentName (Get-WtwCmuxWorkspaceName -Workspace $existing) ` -CurrentColor (Get-WtwCmuxObjectValue -Object $existing -Names @('color', 'workspace.color', 'sidebar.color', 'sidebarState.color')) Write-Host " cmux: selected workspace '$prettyName'" -ForegroundColor Green return } } } $cmuxArgs = @( 'new-workspace', '--name', $prettyName, '--cwd', $fullDir, '--command', 'pwsh -NoLogo -NoExit -Command "Clear-Host; wtw __cmux_init_current"', '--focus', 'true' ) if ($statusValue) { $cmuxArgs += @('--description', "wtw: $statusValue") } $createResult = Invoke-WtwCmuxCommand -ArgumentList $cmuxArgs if ($createResult.ExitCode -ne 0) { $appleScriptInit = Get-WtwCmuxLocalAppleScriptInitCommand -ProjectPath $fullDir if (Open-WtwCmuxAppleScriptWorkspace -ProjectPath $fullDir -PrettyName $prettyName -InitCommand $appleScriptInit) { if (Test-WtwCmuxSocketPermissionDenied -Output $createResult.Output) { Write-Host " cmux: opened via AppleScript fallback (socket access denied)." -ForegroundColor Green } else { Write-Host " cmux: opened via AppleScript fallback." -ForegroundColor Green } return } if (Open-WtwCmuxAppPath -ProjectPath $fullDir) { Write-Host " Opening in cmux: $fullDir" -ForegroundColor Green return } Write-Error "cmux workspace create failed: $($createResult.Output)" return } $currentResult = Invoke-WtwCmuxCommand -ArgumentList @('current-workspace') $workspaceRef = $null if ($currentResult.ExitCode -eq 0) { $currentWorkspace = ConvertFrom-WtwCmuxCurrentWorkspaceOutput -Output $currentResult.Output if ($currentWorkspace) { $workspaceRef = Get-WtwCmuxWorkspaceRef -Workspace $currentWorkspace } } if (-not $workspaceRef) { $created = Find-WtwCmuxWorkspace -ProjectPath $fullDir -PrettyName $prettyName if ($created) { $workspaceRef = Get-WtwCmuxWorkspaceRef -Workspace $created } } Set-WtwCmuxWorkspaceMetadata -WorkspaceRef "$workspaceRef" -PrettyName $prettyName -Color $color -StatusValue $statusValue -CurrentName $prettyName -CurrentColor $null Write-Host " Opening in cmux: $fullDir" -ForegroundColor Green } |