private/Invoke-WtwClaudeProject.ps1
|
function Get-WtwClaudeMacAppName { <# .SYNOPSIS First installed Claude desktop app bundle name, or $null. #> [CmdletBinding()] param( [string[]] $Candidates = @('Claude') ) if (-not $IsMacOS) { return $null } foreach ($candidate in $Candidates) { if ($candidate -and (Test-Path "/Applications/$candidate.app")) { return $candidate } } return $null } function New-WtwClaudeCodeDeepLink { <# .SYNOPSIS Build a `claude://code/new` deep link for a project directory. .DESCRIPTION Claude for Desktop registers the `claude://` URL scheme. Its deep-link router accepts host `code` with path `/new`, and forwards exactly three query parameters to the new-session screen: folder — absolute path the Claude Code session is rooted at (repeatable) q — text pre-filled into the composer (also accepted as `prompt`) file — read by the handler but currently dropped for `code/new` Anything else (a session title, a color) is discarded by the app, so this helper only emits folder/q. The app caps `q` at ~14k characters. .PARAMETER ProjectPath Directory the new Claude Code session should open in. .PARAMETER Prompt Optional text to pre-fill the composer with (not auto-submitted). #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $ProjectPath, [string] $Prompt ) $fullPath = [System.IO.Path]::GetFullPath($ProjectPath) $query = "folder=$([uri]::EscapeDataString($fullPath))" if ($Prompt) { $trimmed = if ($Prompt.Length -gt 14000) { $Prompt.Substring(0, 14000) } else { $Prompt } $query += "&q=$([uri]::EscapeDataString($trimmed))" } return "claude://code/new?$query" } function Start-WtwClaudeCodeSession { <# .SYNOPSIS Open a new Claude Code chat in the Claude desktop app, rooted at a directory. .DESCRIPTION Hands the `claude://code/new` deep link to the OS URL opener so the registered Claude desktop app handles it — launching the app first when it is not already running. Returns a result object rather than throwing so callers can print a wtw-styled message. .PARAMETER ProjectPath Directory the new Claude Code session should open in. .PARAMETER Prompt Optional text to pre-fill the composer with. .PARAMETER AppNameCandidates Ordered macOS app bundle names to accept (handles future renames). #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $ProjectPath, [string] $Prompt, [string[]] $AppNameCandidates = @('Claude') ) $url = New-WtwClaudeCodeDeepLink -ProjectPath $ProjectPath -Prompt $Prompt if ($IsMacOS) { # Probe the bundle first so a missing install reports the app name instead # of macOS's opaque "no application knows how to open this URL". $appName = Get-WtwClaudeMacAppName -Candidates $AppNameCandidates if (-not $appName) { $tried = ($AppNameCandidates | ForEach-Object { "$_.app" }) -join ', ' return [PSCustomObject]@{ Success = $false; Url = $url; Reason = "Claude for Desktop is not installed. Tried: $tried" } } # Route through LaunchServices (not `open -a`): the scheme handler is what # knows how to turn the URL into a new Claude Code session. & open $url return [PSCustomObject]@{ Success = $true; Url = $url; Reason = $null } } if ($IsWindows) { try { Start-Process $url -ErrorAction Stop return [PSCustomObject]@{ Success = $true; Url = $url; Reason = $null } } catch { return [PSCustomObject]@{ Success = $false; Url = $url; Reason = "No handler registered for claude:// — install Claude for Desktop. ($($_.Exception.Message))" } } } $opener = Get-Command xdg-open -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $opener) { return [PSCustomObject]@{ Success = $false; Url = $url; Reason = 'No xdg-open on PATH to dispatch the claude:// deep link.' } } & $opener.Source $url return [PSCustomObject]@{ Success = $true; Url = $url; Reason = $null } } |