Files
orca/src/shared/claude-shell-function.ts
T
Neil b746eef069 fix(shell): keep a user's claude/codex function when the cwd has a matching entry (#39820)
command -v prints a shell function's bare name, so a claude/codex directory
or executable in the cwd passed the -x check and Orca replaced the user's
function. Skip the wrapper when a function with that name is already defined.

STA-13246
2026-11-08 03:23:55 -08:00

113 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Re-read account selection on every launch in Orca-routed panes with a real claude binary. Missing/empty pointer restores the user’s config; another pane override wins. Create account homes without a login silently and preserve Anthropic auth. Refuse nonabsolute pointers rather than run under the wrong account. */
export function getPosixClaudeShellFunction(): string {
return `__orca_claude_binary="$(unalias claude 2>/dev/null || :; command -v claude 2>/dev/null || :)"
# Why typeset -f: a user's own claude function wins; command -v prints its bare name, which -x tests against the cwd.
if ! typeset -f claude >/dev/null 2>&1 && [[ -n "\${ORCA_CLAUDE_PROFILE_POINTER:-}" && -n "\${__orca_claude_binary:-}" && -x "\${__orca_claude_binary}" ]]; then
function claude {
local __orca_claude_home __orca_claude_pointer="\${ORCA_CLAUDE_PROFILE_POINTER:-}"
# Why: a WSL pane's pointer is relative to the guest home, which the host cannot know at spawn.
case "$__orca_claude_pointer" in '~/'*) __orca_claude_pointer="\${HOME:-}/\${__orca_claude_pointer#??}" ;; esac
__orca_claude_home="$(cat "$__orca_claude_pointer" 2>/dev/null || :)"
if [ -n "\${CLAUDE_CONFIG_DIR:-}" ] && [ "$CLAUDE_CONFIG_DIR" != "\${ORCA_CLAUDE_INJECTED_CONFIG_DIR:-}" ] &&
{ [ -z "\${ORCA_CLAUDE_USER_CONFIG_DIR:-}" ] || [ "\${CLAUDE_CONFIG_DIR%/}" != "\${ORCA_CLAUDE_USER_CONFIG_DIR%/}" ]; }; then
command claude "$@"; return
fi
# Why: Orca's value replaced the user's own at spawn, so System default restores theirs.
if [ -z "$__orca_claude_home" ] && [ -n "\${CLAUDE_CONFIG_DIR:-}" ] && [ -n "\${ORCA_CLAUDE_USER_CONFIG_DIR:-}" ]; then
( unset ORCA_CLAUDE_INJECTED_CONFIG_DIR; export CLAUDE_CONFIG_DIR="$ORCA_CLAUDE_USER_CONFIG_DIR"; command claude "$@" ); return
fi
if [ -z "$__orca_claude_home" ]; then
( unset CLAUDE_CONFIG_DIR ORCA_CLAUDE_INJECTED_CONFIG_DIR; command claude "$@" ); return
fi
case "$__orca_claude_home" in /*|[A-Za-z]:*) ;; *) return 1 ;; esac
# Why -m 700: matches the folder Orca's setup creates; a credentials folder stays private.
[ -d "$__orca_claude_home" ] || mkdir -p -m 700 -- "$__orca_claude_home" 2>/dev/null
( export CLAUDE_CONFIG_DIR="$__orca_claude_home" ORCA_CLAUDE_INJECTED_CONFIG_DIR="$__orca_claude_home"; command claude "$@" )
}
fi
unset __orca_claude_binary
`
}
/** Leading newline separates the preceding codex fragment, which has no trailing newline. */
export function getFishClaudeShellFunction(): string {
return `
set -l __orca_claude_type (type -t claude 2>/dev/null)
if test -n "$ORCA_CLAUDE_PROFILE_POINTER"; and test "$__orca_claude_type" = file
function claude
# Why: a WSL pane's pointer is relative to the guest home, which the host cannot know at spawn.
set -l pointer (string replace -r '^~/' "$HOME/" -- "$ORCA_CLAUDE_PROFILE_POINTER")
set -l profile (cat "$pointer" 2>/dev/null)
set -l own (string trim -r -c / -- "$ORCA_CLAUDE_USER_CONFIG_DIR")
set -l current (string trim -r -c / -- "$CLAUDE_CONFIG_DIR")
if test -n "$CLAUDE_CONFIG_DIR"; and test "$CLAUDE_CONFIG_DIR" != "$ORCA_CLAUDE_INJECTED_CONFIG_DIR"; and begin; test -z "$own"; or test "$current" != "$own"; end
command claude $argv
return $status
end
if test -z "$profile"; and test -n "$CLAUDE_CONFIG_DIR"; and test -n "$ORCA_CLAUDE_USER_CONFIG_DIR"
env -u ORCA_CLAUDE_INJECTED_CONFIG_DIR CLAUDE_CONFIG_DIR="$ORCA_CLAUDE_USER_CONFIG_DIR" claude $argv
return $status
end
if test -z "$profile"
env -u CLAUDE_CONFIG_DIR -u ORCA_CLAUDE_INJECTED_CONFIG_DIR claude $argv
return $status
end
if not string match -qr '^(/|[A-Za-z]:)' -- "$profile"
return 1
end
test -d "$profile"; or mkdir -p -m 700 -- "$profile" 2>/dev/null
env CLAUDE_CONFIG_DIR="$profile" ORCA_CLAUDE_INJECTED_CONFIG_DIR="$profile" claude $argv
end
end
set -e __orca_claude_type
`
}
export function getPowerShellClaudeShellFunction(): string {
return `$orcaClaudeCommand = Get-Command claude -ErrorAction SilentlyContinue | Select-Object -First 1
if ($env:ORCA_CLAUDE_PROFILE_POINTER -and $orcaClaudeCommand -and
$orcaClaudeCommand.CommandType -in @("Application", "ExternalScript")) {
function Global:claude {
$names = @('CLAUDE_CONFIG_DIR', 'ORCA_CLAUDE_INJECTED_CONFIG_DIR')
$saved = @{}
foreach ($name in $names) { $saved[$name] = [Environment]::GetEnvironmentVariable($name, 'Process') }
try {
$orcaClaudeHome = ''
if ($env:ORCA_CLAUDE_PROFILE_POINTER -and (Test-Path -LiteralPath $env:ORCA_CLAUDE_PROFILE_POINTER -PathType Leaf)) {
$orcaClaudeHome = [IO.File]::ReadAllText($env:ORCA_CLAUDE_PROFILE_POINTER).TrimEnd()
}
# Why the separator swap: Windows names one folder with either separator.
$own = ("$env:ORCA_CLAUDE_USER_CONFIG_DIR" -replace '/', '\\').TrimEnd('\\')
if ($env:CLAUDE_CONFIG_DIR -and $env:CLAUDE_CONFIG_DIR -ne $env:ORCA_CLAUDE_INJECTED_CONFIG_DIR -and
(-not $own -or ("$env:CLAUDE_CONFIG_DIR" -replace '/', '\\').TrimEnd('\\') -ne $own)) {
# A value set in this pane, other than the user's own, wins.
} elseif (-not $orcaClaudeHome) {
Remove-Item Env:ORCA_CLAUDE_INJECTED_CONFIG_DIR -ErrorAction SilentlyContinue
if ($env:CLAUDE_CONFIG_DIR -and $env:ORCA_CLAUDE_USER_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR = $env:ORCA_CLAUDE_USER_CONFIG_DIR }
else { Remove-Item Env:CLAUDE_CONFIG_DIR -ErrorAction SilentlyContinue }
} elseif (-not [IO.Path]::IsPathRooted($orcaClaudeHome)) {
$global:LASTEXITCODE = 1
return
} else {
if (-not [IO.Directory]::Exists($orcaClaudeHome)) { $null = New-Item -ItemType Directory -Path $orcaClaudeHome -Force -ErrorAction SilentlyContinue }
$env:CLAUDE_CONFIG_DIR = $orcaClaudeHome
$env:ORCA_CLAUDE_INJECTED_CONFIG_DIR = $orcaClaudeHome
}
$binary = Get-Command claude -CommandType Application,ExternalScript -ErrorAction Stop | Select-Object -First 1
$claudeArgs = @(__OrcaEscapeNativeArgs $binary.Source $args)
if ($MyInvocation.ExpectingInput) { $input | & $binary.Source @claudeArgs } else { & $binary.Source @claudeArgs }
$global:LASTEXITCODE = $LASTEXITCODE
} catch { $global:LASTEXITCODE = 1; Write-Error $_ -ErrorAction Continue }
finally {
# Why Remove-Item: on .NET 9+ a $null value (passed as "") creates the variable empty instead of deleting it.
foreach ($name in $names) {
if ($null -eq $saved[$name]) { Remove-Item -LiteralPath "Env:$name" -ErrorAction SilentlyContinue }
else { [Environment]::SetEnvironmentVariable($name, $saved[$name], 'Process') }
}
}
}
}
Remove-Variable orcaClaudeCommand -ErrorAction SilentlyContinue
`
}