private/Invoke-WtwCursorProject.ps1
|
function Get-WtwCursorDataHome { [CmdletBinding()] param() if ($env:CURSOR_USER_DATA_DIR) { return [System.IO.Path]::GetFullPath($env:CURSOR_USER_DATA_DIR) } if ($IsMacOS) { return [System.IO.Path]::GetFullPath((Join-Path $HOME 'Library/Application Support/Cursor')) } if ($IsWindows) { return [System.IO.Path]::GetFullPath((Join-Path $env:APPDATA 'Cursor')) } return [System.IO.Path]::GetFullPath((Join-Path $HOME '.config/Cursor')) } function Get-WtwCursorGlobalStatePath { [CmdletBinding()] param([string] $DataHome = (Get-WtwCursorDataHome)) return [System.IO.Path]::GetFullPath((Join-Path $DataHome 'User/globalStorage/state.vscdb')) } function Test-WtwCursorPresent { [CmdletBinding()] param([string] $DataHome = (Get-WtwCursorDataHome)) if (Get-Command cursor -ErrorAction SilentlyContinue) { return $true } if ($DataHome -and (Test-Path $DataHome)) { return $true } if ($IsMacOS -and (Test-Path '/Applications/Cursor.app')) { return $true } return $false } function ConvertTo-WtwFileUri { [CmdletBinding()] param([Parameter(Mandatory)][string] $Path) $fullPath = [System.IO.Path]::GetFullPath($Path) return ([System.Uri]::new($fullPath)).AbsoluteUri } function ConvertTo-WtwCursorWorkspaceId { [CmdletBinding()] param([Parameter(Mandatory)][string] $WorkspacePath) $uri = ConvertTo-WtwFileUri -Path $WorkspacePath $bytes = [System.Text.Encoding]::UTF8.GetBytes($uri) $hash = [System.Security.Cryptography.SHA256]::HashData($bytes) $hex = -join ($hash | ForEach-Object { $_.ToString('x2') }) return $hex.Substring(0, 32) } function ConvertTo-WtwSqliteLiteral { [CmdletBinding()] param([AllowNull()][string] $Value) if ($null -eq $Value) { return 'NULL' } return "'" + $Value.Replace("'", "''") + "'" } function Get-WtwSqliteCommand { [CmdletBinding()] param() return (Get-Command sqlite3 -ErrorAction SilentlyContinue)?.Source } function Read-WtwCursorRecentlyOpenedState { [CmdletBinding()] param([Parameter(Mandatory)][string] $StatePath) if (-not (Test-Path $StatePath)) { return [PSCustomObject]@{ entries = @() } } $sqlite = Get-WtwSqliteCommand if (-not $sqlite) { Write-Host ' Cursor: sqlite3 not found - skipping recent workspace registration.' -ForegroundColor DarkGray return $null } $sql = "select value from ItemTable where key = 'history.recentlyOpenedPathsList' limit 1;" $value = & $sqlite $StatePath $sql 2>$null if ($LASTEXITCODE -ne 0) { Write-Host ' Cursor: could not read state.vscdb - skipping recent workspace registration.' -ForegroundColor Yellow return $null } $raw = ($value -join [Environment]::NewLine) if ([string]::IsNullOrWhiteSpace($raw)) { return [PSCustomObject]@{ entries = @() } } try { return $raw | ConvertFrom-Json } catch { Write-Host ' Cursor: could not parse recently-opened state - skipping update.' -ForegroundColor Yellow return $null } } function Save-WtwCursorRecentlyOpenedState { [CmdletBinding()] param( [Parameter(Mandatory)][PSObject] $State, [Parameter(Mandatory)][string] $StatePath ) $sqlite = Get-WtwSqliteCommand if (-not $sqlite) { Write-Host ' Cursor: sqlite3 not found - skipping recent workspace registration.' -ForegroundColor DarkGray return $false } $stateDir = Split-Path $StatePath -Parent if (-not (Test-Path $stateDir)) { New-Item -Path $stateDir -ItemType Directory -Force | Out-Null } $json = $State | ConvertTo-Json -Depth 80 -Compress $jsonLiteral = ConvertTo-WtwSqliteLiteral $json $sql = @( 'create table if not exists ItemTable (key TEXT, value BLOB);', "delete from ItemTable where key = 'history.recentlyOpenedPathsList';", "insert into ItemTable (key, value) values ('history.recentlyOpenedPathsList', $jsonLiteral);" ) -join [Environment]::NewLine & $sqlite $StatePath $sql 2>$null if ($LASTEXITCODE -ne 0) { Write-Host ' Cursor: could not save state.vscdb - skipping recent workspace registration.' -ForegroundColor Yellow return $false } return $true } function Set-WtwCursorRecentWorkspace { [CmdletBinding()] param( [Parameter(Mandatory)][string] $WorkspacePath, [string] $StatePath = (Get-WtwCursorGlobalStatePath) ) $fullWorkspacePath = [System.IO.Path]::GetFullPath($WorkspacePath) $workspaceUri = ConvertTo-WtwFileUri -Path $fullWorkspacePath $workspaceId = ConvertTo-WtwCursorWorkspaceId -WorkspacePath $fullWorkspacePath $state = Read-WtwCursorRecentlyOpenedState -StatePath $StatePath if (-not $state) { return $false } $entry = [PSCustomObject]@{ workspace = [PSCustomObject]@{ id = $workspaceId configPath = $workspaceUri } } $entries = @() if ($state.PSObject.Properties.Name -contains 'entries' -and $state.entries) { $entries = @($state.entries) } $entries = @($entries | Where-Object { $existingWorkspace = if ($_.PSObject.Properties.Name -contains 'workspace') { $_.workspace } else { $null } $existingConfigPath = if ($existingWorkspace -and $existingWorkspace.PSObject.Properties.Name -contains 'configPath') { $existingWorkspace.configPath } else { $null } $existingConfigPath -ne $workspaceUri }) $state | Add-Member -NotePropertyName 'entries' -NotePropertyValue (@($entry) + $entries) -Force return (Save-WtwCursorRecentlyOpenedState -State $state -StatePath $StatePath) } function Remove-WtwCursorRecentWorkspace { [CmdletBinding()] param( [Parameter(Mandatory)][string] $WorkspacePath, [string] $StatePath = (Get-WtwCursorGlobalStatePath) ) if (-not (Test-Path $StatePath)) { return $false } $workspaceUri = ConvertTo-WtwFileUri -Path $WorkspacePath $state = Read-WtwCursorRecentlyOpenedState -StatePath $StatePath if (-not $state) { return $false } $entries = @() if ($state.PSObject.Properties.Name -contains 'entries' -and $state.entries) { $entries = @($state.entries) } $filtered = @($entries | Where-Object { $existingWorkspace = if ($_.PSObject.Properties.Name -contains 'workspace') { $_.workspace } else { $null } $existingConfigPath = if ($existingWorkspace -and $existingWorkspace.PSObject.Properties.Name -contains 'configPath') { $existingWorkspace.configPath } else { $null } $existingConfigPath -ne $workspaceUri }) if ($filtered.Count -eq $entries.Count) { return $false } $state | Add-Member -NotePropertyName 'entries' -NotePropertyValue $filtered -Force return (Save-WtwCursorRecentlyOpenedState -State $state -StatePath $StatePath) } function Register-WtwCursorProject { <# .SYNOPSIS Register a generated code-workspace in Cursor's recent workspace list. .DESCRIPTION Cursor stores recent workspaces in VS Code's SQLite-backed history.recentlyOpenedPathsList. The schema does not expose a stable custom label field; Cursor renders the workspace from the .code-workspace path/name. Colors are carried by the workspace file's workbench.colorCustomizations / peacock.color settings. #> [CmdletBinding()] param( [Parameter(Mandatory)][string] $WorkspacePath, [string] $ProjectPath, [string] $PrettyName, [string] $Color, [string] $StatePath ) if (-not (Test-Path $WorkspacePath)) { return $null } if (-not (Test-WtwCursorPresent)) { Write-Host ' Cursor: not installed/present - skipping recent workspace registration.' -ForegroundColor DarkGray return $null } $splat = @{ WorkspacePath = $WorkspacePath } if ($StatePath) { $splat.StatePath = $StatePath } if (Set-WtwCursorRecentWorkspace @splat) { $label = if ($PrettyName) { " '$PrettyName'" } else { '' } $colorLabel = if ($Color) { " ($Color)" } else { '' } Write-Host " Cursor: registered recent workspace${label}${colorLabel}" -ForegroundColor Green return [System.IO.Path]::GetFullPath($WorkspacePath) } return $null } function Unregister-WtwCursorProject { [CmdletBinding()] param( [Parameter(Mandatory)][string] $WorkspacePath, [string] $StatePath ) $splat = @{ WorkspacePath = $WorkspacePath } if ($StatePath) { $splat.StatePath = $StatePath } if (Remove-WtwCursorRecentWorkspace @splat) { Write-Host ' Cursor: removed recent workspace metadata.' -ForegroundColor Green } } |