public/Edit-WtwEntry.ps1
|
function Edit-WtwEntry { <# .SYNOPSIS Edit a registered repo or worktree (name, task key, aliases). .DESCRIPTION Updates the wtw registry record in place. Does not move git worktrees, rename branches, or delete files. Worktree: --name / second positional display (pretty) name --task registry key used by ``wtw go`` and aliases Repo: --alias / --name / second positional replace the alias list --key registry key (also remaps color keys) With no edit flags, prints the current record (cwd when name is omitted). .PARAMETER Name Repo alias, worktree, or alias-task (same resolution as go/info). .PARAMETER NewName Shorthand for --name: pretty name (worktree) or alias list (repo). .PARAMETER PrettyName Display name. A color-circle emoji is prepended for worktrees. .PARAMETER Task New worktree registry key. Does not rename the folder or branch. .PARAMETER Alias Comma-separated repo aliases (replaces the existing list). .PARAMETER Key New repo registry key. .PARAMETER Repo Disambiguate when the same task exists in multiple repos. .PARAMETER NoSync Skip rewriting the workspace file and SourceGit bookmark. .EXAMPLE wtw edit auth --name "Login flow" Change the worktree display name shown in list/info and editor sidebars. .EXAMPLE wtw rename auth login Same as ``wtw edit auth --name login``. .EXAMPLE wtw edit auth --task login Retarget ``wtw go login`` / ``sn-login`` without moving the checkout. .EXAMPLE wtw edit snowmain1 --alias sn,sm Replace the repo's typed aliases. #> [CmdletBinding()] param( [Parameter(Position = 0)] [string] $Name, [Parameter(Position = 1)] [string] $NewName, [string[]] $PrettyName, [string] $Task, [string[]] $Alias, [string] $Key, [string] $Repo, [Alias('ns')] [switch] $NoSync ) if (-not $PrettyName -and $NewName) { $PrettyName = @($NewName) } $nameFromCwd = -not $Name if (-not $Name) { $Name = Resolve-WtwCurrentTarget if (-not $Name) { Write-Error "Not inside a registered repo. Specify a target or cd into a repo." return } if ($nameFromCwd) { Write-Host " Detected: $Name" -ForegroundColor DarkGray } } $target = if ($Repo) { Resolve-WtwTarget -Name $Name -RepoAlias $Repo } else { Resolve-WtwTarget $Name } if (-not $target) { return } $prettyText = Get-WtwJoinedEditValue -Value $PrettyName $hasPretty = -not [string]::IsNullOrWhiteSpace($prettyText) $hasTask = -not [string]::IsNullOrWhiteSpace($Task) $hasAlias = @($Alias | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }).Count -gt 0 $hasKey = -not [string]::IsNullOrWhiteSpace($Key) $hasEdits = $hasPretty -or $hasTask -or $hasAlias -or $hasKey if (-not $hasEdits) { Show-WtwEditableRecord -Target $target return } if ($target.TaskName) { if ($hasAlias) { Write-Error "--alias is for repos. To change how you type this worktree, use --task." return } if ($hasKey) { Write-Error "--key is for repos. To rename this worktree's registry key, use --task." return } Edit-WtwWorktreeRecord ` -Target $target ` -PrettyName $prettyText ` -Task $Task ` -NoSync:$NoSync return } if ($hasTask) { Write-Error "--task is for worktrees. To rename this repo's registry key, use --key." return } $aliasInput = if ($hasAlias) { $Alias } elseif ($hasPretty) { $PrettyName } else { $null } Edit-WtwRepoRecord ` -Target $target ` -Alias $aliasInput ` -Key $Key ` -NoSync:$NoSync } function Get-WtwJoinedEditValue { param([AllowNull()] [string[]] $Value) if (-not $Value) { return $null } $joined = (@($Value | ForEach-Object { "$_".Trim() } | Where-Object { $_ }) -join ' ') if ([string]::IsNullOrWhiteSpace($joined)) { return $null } return $joined } function Show-WtwEditableRecord { param([Parameter(Mandatory)] [PSObject] $Target) $repoName = $Target.RepoName $repoEntry = $Target.RepoEntry $aliases = Get-WtwRepoAliases $repoEntry Write-Host '' if ($Target.TaskName) { $wt = $Target.WorktreeEntry $pretty = Get-WtwPropertyValue -Object $wt -Name 'prettyName' $color = Get-WtwPropertyValue -Object $wt -Name 'color' $ws = Get-WtwPropertyValue -Object $wt -Name 'workspace' $wtAliases = ($aliases | ForEach-Object { "$_-$($Target.TaskName)" }) -join ', ' Write-Host " Worktree $repoName / $($Target.TaskName)" -ForegroundColor Cyan if ($pretty) { Write-Host " Name : $pretty" } Write-Host " Task : $($Target.TaskName)" Write-Host " Branch : $(Get-WtwPropertyValue -Object $wt -Name 'branch')" Write-Host " Path : $(Get-WtwPropertyValue -Object $wt -Name 'path')" if ($ws) { Write-Host " Workspace : $ws" } if ($color) { Write-Host " Color : $color" } if ($wtAliases) { Write-Host " Aliases : $wtAliases" } Write-Host '' Write-Host " wtw edit $($Target.TaskName) --name <pretty> display name" -ForegroundColor DarkGray Write-Host " wtw edit $($Target.TaskName) --task <key> go-target / aliases" -ForegroundColor DarkGray } else { Write-Host " Repo $repoName" -ForegroundColor Cyan Write-Host " Key : $repoName" Write-Host " Aliases : $($aliases -join ', ')" Write-Host " Path : $(Get-WtwPropertyValue -Object $repoEntry -Name 'mainPath')" Write-Host '' Write-Host " wtw edit $repoName --alias a,b typed names for wtw go" -ForegroundColor DarkGray Write-Host " wtw edit $repoName --key <name> registry key" -ForegroundColor DarkGray } Write-Host '' } function Edit-WtwWorktreeRecord { [CmdletBinding()] param( [Parameter(Mandatory)] [PSObject] $Target, [string] $PrettyName, [string] $Task, [switch] $NoSync ) $repoName = $Target.RepoName $oldTask = $Target.TaskName $newTask = $oldTask if (-not [string]::IsNullOrWhiteSpace($Task)) { $newTask = ConvertTo-WtwBranchSafeName -Name $Task if ([string]::IsNullOrWhiteSpace($newTask)) { Write-Error "Invalid --task '$Task'." return } if ($newTask -ne $Task) { Write-Host " Normalized task: $newTask" -ForegroundColor DarkCyan } } $registry = Get-WtwRegistry $worktrees = $registry.repos.$repoName.worktrees if (-not $worktrees -or ((Get-WtwPropertyNames -Object $worktrees) -notcontains $oldTask)) { Write-Error "Worktree '$oldTask' is not in the registry." return } if ($newTask -ne $oldTask -and ((Get-WtwPropertyNames -Object $worktrees) -contains $newTask)) { Write-Error "Worktree '$newTask' is already registered under '$repoName'." return } $entry = $worktrees.$oldTask $color = Get-WtwPropertyValue -Object $entry -Name 'color' $changed = @() if (-not [string]::IsNullOrWhiteSpace($PrettyName)) { $newPretty = if ($color) { Format-WtwPrettyNameWithCircle -Hex $color -Name $PrettyName } else { $PrettyName.Trim() } $oldPretty = Get-WtwPropertyValue -Object $entry -Name 'prettyName' if ($newPretty -ne $oldPretty) { $entry | Add-Member -NotePropertyName 'prettyName' -NotePropertyValue $newPretty -Force $changed += "name '$oldPretty' → '$newPretty'" } } if ($newTask -ne $oldTask) { $worktrees = Rename-WtwObjectProperty -Object $worktrees -OldName $oldTask -NewName $newTask $registry.repos.$repoName.worktrees = $worktrees Rename-WtwColorAssignmentKey -OldKey "$repoName/$oldTask" -NewKey "$repoName/$newTask" $changed += "task '$oldTask' → '$newTask'" } else { $registry.repos.$repoName.worktrees.$oldTask = $entry } if ($changed.Count -eq 0) { Write-Host ' Nothing to change.' -ForegroundColor DarkGray return } Save-WtwRegistry $registry $saved = $registry.repos.$repoName.worktrees.$newTask $prettyNow = Get-WtwPropertyValue -Object $saved -Name 'prettyName' $wsFile = Get-WtwPropertyValue -Object $saved -Name 'workspace' $wtPath = Get-WtwPropertyValue -Object $saved -Name 'path' if (-not $NoSync) { if ($wsFile -and (Test-Path $wsFile)) { Update-WtwWorkspaceIdentity ` -Path $wsFile ` -PrettyName $prettyNow ` -TaskName $newTask if ($prettyNow) { $newWsPath = Get-WtwCursorPrettyWorkspacePath -WorkspacePath $wsFile -PrettyName $prettyNow -RepoName $repoName $newWsPath = [System.IO.Path]::GetFullPath($newWsPath) $oldWsPath = [System.IO.Path]::GetFullPath($wsFile) if ($newWsPath -ne $oldWsPath) { $canMigrate = Resolve-WtwCursorStateConflict -PrettyName $prettyNow if ($canMigrate) { $migratedWorkspace = Move-WtwCursorWorkspaceForAgents ` -WorkspacePath $wsFile ` -PrettyName $prettyNow ` -RepoName $repoName if ($migratedWorkspace -and $migratedWorkspace -ne $wsFile) { $saved | Add-Member -NotePropertyName 'workspace' -NotePropertyValue $migratedWorkspace -Force $registry.repos.$repoName.worktrees.$newTask = $saved Save-WtwRegistry $registry $wsFile = $migratedWorkspace Write-Host " Workspace file: $(Split-Path $wsFile -Leaf)" -ForegroundColor DarkGray } } else { Write-Host ' Keeping existing workspace filename (Cursor Agents label not migrated).' -ForegroundColor DarkGray } } } Write-Host ' Syncing workspace...' -ForegroundColor DarkGray Sync-WtwWorkspace -Target $wsFile -ColorSource Json } if ($wtPath -and $prettyNow) { Add-WtwSourceGitRepository -Path $wtPath -Name $prettyNow -Hex $color } } Write-Host '' Write-Host " Updated $repoName / $newTask ($($changed -join '; '))." -ForegroundColor Green Write-Host '' } function Edit-WtwRepoRecord { [CmdletBinding()] param( [Parameter(Mandatory)] [PSObject] $Target, [string[]] $Alias, [string] $Key, [switch] $NoSync ) $oldKey = $Target.RepoName $newKey = $oldKey $workspacesToUpdate = @() if (-not [string]::IsNullOrWhiteSpace($Key)) { $newKey = $Key.Trim() if ([string]::IsNullOrWhiteSpace($newKey)) { Write-Error "Invalid --key '$Key'." return } } $registry = Get-WtwRegistry if ((Get-WtwPropertyNames -Object $registry.repos) -notcontains $oldKey) { Write-Error "Repo '$oldKey' is not in the registry." return } $entry = $registry.repos.$oldKey $changed = @() if (@($Alias | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }).Count -gt 0) { # Split-WtwAliasList returns `,@(...)` so a naive `@()` wrap nests it. [string[]] $aliasArray = Split-WtwAliasList -Value $Alias if ($aliasArray.Count -eq 0) { Write-Error "Invalid --alias '$($Alias -join ',')'." return } $collision = Test-WtwRepoIdentityCollision -Registry $registry -RepoName $oldKey -NewKey $oldKey -Aliases $aliasArray if ($collision) { return } $oldAliases = Get-WtwRepoAliases $entry $entry | Add-Member -NotePropertyName 'aliases' -NotePropertyValue $aliasArray -Force if ((Get-WtwPropertyNames -Object $entry) -contains 'alias') { $entry.PSObject.Properties.Remove('alias') } $changed += "aliases '$($oldAliases -join ',')' → '$($aliasArray -join ',')'" } if ($newKey -ne $oldKey) { if ((Get-WtwPropertyNames -Object $registry.repos) -contains $newKey) { Write-Error "Repo '$newKey' is already in the registry." return } $aliasesForCheck = if (@($Alias | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }).Count -gt 0) { [string[]](Split-WtwAliasList -Value $Alias) } else { Get-WtwRepoAliases $entry } $collision = Test-WtwRepoIdentityCollision -Registry $registry -RepoName $oldKey -NewKey $newKey -Aliases $aliasesForCheck if ($collision) { return } $workspacesToUpdate = @() if ($entry.worktrees) { foreach ($task in (Get-WtwPropertyNames -Object $entry.worktrees)) { $ws = Get-WtwPropertyValue -Object $entry.worktrees.$task -Name 'workspace' if ($ws -and (Test-Path $ws)) { $workspacesToUpdate += $ws } } } if (-not $NoSync) { foreach ($ws in $workspacesToUpdate) { if (-not (Update-WtwWorkspaceIdentity -Path $ws -RepoName $newKey)) { Write-Error "Could not update workspace '$ws' with repo key '$newKey'. Registry was not saved." return } } } $registry.repos.$oldKey = $entry $registry.repos = Rename-WtwObjectProperty -Object $registry.repos -OldName $oldKey -NewName $newKey Rename-WtwColorAssignmentRepoPrefix -OldRepo $oldKey -NewRepo $newKey $changed += "key '$oldKey' → '$newKey'" } else { $registry.repos.$oldKey = $entry } if ($changed.Count -eq 0) { Write-Host ' Nothing to change.' -ForegroundColor DarkGray return } Save-WtwRegistry $registry if ($newKey -ne $oldKey -and -not $NoSync) { foreach ($ws in @($workspacesToUpdate)) { Write-Host ' Syncing workspace...' -ForegroundColor DarkGray Sync-WtwWorkspace -Target $ws -ColorSource Json } } Write-Host '' Write-Host " Updated repo $newKey ($($changed -join '; '))." -ForegroundColor Green Write-Host " New shell aliases apply in a new terminal, or re-source the wtw wrapper." -ForegroundColor DarkGray Write-Host '' } function Test-WtwRepoIdentityCollision { <# .SYNOPSIS Return $true (and write the error) when a proposed repo key/aliases collide. #> param( [Parameter(Mandatory)] [PSObject] $Registry, [Parameter(Mandatory)] [string] $RepoName, [Parameter(Mandatory)] [string] $NewKey, [AllowEmptyCollection()] [string[]] $Aliases ) foreach ($existingName in (Get-WtwPropertyNames -Object $Registry.repos)) { if ($existingName -eq $RepoName) { continue } $existingRepo = $Registry.repos.$existingName $existingAliases = Get-WtwRepoAliases $existingRepo foreach ($newAlias in @($Aliases)) { if ($newAlias -eq $existingName -or $newAlias -in $existingAliases) { Write-Error "Alias '$newAlias' is already used by repo '$existingName'." return $true } } if ($NewKey -eq $existingName -or $NewKey -in $existingAliases) { Write-Error "Registry key '$NewKey' collides with repo '$existingName'." return $true } } return $false } function Update-WtwWorkspaceIdentity { <# .SYNOPSIS Write pretty-name / task metadata into an existing workspace file. .DESCRIPTION Runs before Sync-WtwWorkspace so a single-file sync (which reads identity from the file, not the registry) picks up the new values. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, [string] $PrettyName, [string] $TaskName, [string] $RepoName ) $workspace = Read-JsoncFile $Path if (-not $workspace) { return $false } $settings = Get-WtwPropertyValue -Object $workspace -Name 'settings' if (-not $settings) { return $false } if (-not [string]::IsNullOrWhiteSpace($PrettyName)) { $settings | Add-Member -NotePropertyName 'wtw.prettyName' -NotePropertyValue $PrettyName -Force $windowTitle = '{0}${{separator}}${{dirty}}${{activeEditorShort}}${{separator}}${{appName}}' -f $PrettyName $settings | Add-Member -NotePropertyName 'window.title' -NotePropertyValue $windowTitle -Force } if (-not [string]::IsNullOrWhiteSpace($TaskName)) { $settings | Add-Member -NotePropertyName 'wtw.task' -NotePropertyValue $TaskName -Force } if (-not [string]::IsNullOrWhiteSpace($RepoName)) { $settings | Add-Member -NotePropertyName 'wtw.repo' -NotePropertyValue $RepoName -Force } try { $workspace | ConvertTo-Json -Depth 20 | Set-Content -Path $Path -Encoding utf8 return $true } catch { Write-Error "Failed to write workspace identity to '$Path': $_" return $false } } |