completers/codex_completer/codex_completer.ps1
|
<# .SYNOPSIS Registers PowerShell tab completion for the local `codex` CLI. .DESCRIPTION This completer is a thin, importer-safe wrapper around the PowerShell completion script emitted by `codex completion powershell`. The generated completion script is loaded lazily on first completion request, its built-in self-registration is rewritten into an invokable script block, and that script block is cached for the rest of the session. This script registers completion for `codex`, `codex.cmd`, and `codex.ps1` because all three launcher names can be relevant on Windows. #> Set-StrictMode -Version Latest function Get-CodexCompletionExecutablePath { $pathProbeComplete = Get-Variable -Name CodexCompletionExecutablePathProbed -Scope Script -ErrorAction Ignore if ($null -ne $pathProbeComplete -and $pathProbeComplete.Value) { $cachedPath = Get-Variable -Name CodexCompletionExecutablePath -Scope Script -ErrorAction Ignore if ($null -ne $cachedPath) { return $cachedPath.Value } return $null } $script:CodexCompletionExecutablePathProbed = $true $script:CodexCompletionExecutablePath = $null foreach ($candidate in @( @{ Name = 'codex.cmd'; CommandType = 'Application' } @{ Name = 'codex'; CommandType = 'Application' } @{ Name = 'codex.ps1'; CommandType = 'ExternalScript' } )) { $command = Get-Command -Name $candidate.Name -CommandType $candidate.CommandType -ErrorAction SilentlyContinue | Select-Object -First 1 if ($null -eq $command) { continue } $script:CodexCompletionExecutablePath = if ($command.Source) { $command.Source } else { $command.Path } break } $script:CodexCompletionExecutablePath } function Get-CodexGeneratedCompletionScript { $codexExecutablePath = Get-CodexCompletionExecutablePath if ([string]::IsNullOrWhiteSpace($codexExecutablePath)) { return } try { $completionScript = & $codexExecutablePath completion powershell 2>$null | ForEach-Object { $_ -replace '\e\[[0-9;?]*[ -/]*[@-~]', '' } | Out-String } catch { return } if ([string]::IsNullOrWhiteSpace($completionScript)) { return } $completionScript } function ConvertTo-CodexCompletionInvokerSource { param([string]$CompletionScript) if ([string]::IsNullOrWhiteSpace($CompletionScript)) { return } $registerPattern = '(?m)^\s*Register-ArgumentCompleter\s+-Native\s+-CommandName\s+[''"]codex[''"]\s+-ScriptBlock\s+\{\s*$' $rewrittenScript = [regex]::Replace( $CompletionScript, $registerPattern, { param($match) '$__codexCompleterBlock = {' }, 1 ) if ($rewrittenScript -eq $CompletionScript) { return } @" $rewrittenScript & `$__codexCompleterBlock @args "@ } function Get-CodexCompletionInvoker { $cachedInvoker = Get-Variable -Name CodexCompletionInvoker -Scope Script -ErrorAction Ignore if ($null -ne $cachedInvoker) { return $cachedInvoker.Value } $completionScript = Get-CodexGeneratedCompletionScript if ([string]::IsNullOrWhiteSpace($completionScript)) { return } $completionInvokerSource = ConvertTo-CodexCompletionInvokerSource -CompletionScript $completionScript if ([string]::IsNullOrWhiteSpace($completionInvokerSource)) { return } try { $script:CodexCompletionInvoker = [scriptblock]::Create($completionInvokerSource) return $script:CodexCompletionInvoker } catch { return } } function Get-CodexValueCompletion { <# .SYNOPSIS Value overlay for slots the clap-generated script leaves empty: closed enum options, directory options, and the 'completion <SHELL>' positional. #> param( [string]$WordToComplete, [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) $enumValues = [System.Collections.Generic.Dictionary[string, string[]]]::new([System.StringComparer]::Ordinal) $enumValues['-s'] = @('read-only', 'workspace-write', 'danger-full-access') $enumValues['--sandbox'] = $enumValues['-s'] $enumValues['-a'] = @('on-request', 'never') $enumValues['--ask-for-approval'] = $enumValues['-a'] $enumValues['--local-provider'] = @('lmstudio', 'ollama') $enumValues['--color'] = @('always', 'never', 'auto') $directoryOptions = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal) foreach ($name in @('-C', '--cd', '--add-dir')) { [void]$directoryOptions.Add($name) } $committed = @( $CommandAst.CommandElements | Select-Object -Skip 1 | Where-Object { $_.Extent.EndOffset -lt $CursorPosition } | ForEach-Object { $_.Extent.Text } ) $previousToken = if ($committed.Count -gt 0) { $committed[-1] } else { '' } $node = 'codex' foreach ($token in $committed) { if ($token.StartsWith('-')) { break } $node += ';' + $token } $optionName = $null $valuePrefix = '' $valueWord = $WordToComplete if ($WordToComplete -match '^(?<option>--?[A-Za-z][A-Za-z0-9-]*)=(?<value>.*)$') { $optionName = $Matches.option $valuePrefix = $optionName + '=' $valueWord = $Matches.value } elseif ($previousToken.StartsWith('-')) { $optionName = $previousToken } if ($optionName) { if ($enumValues.ContainsKey($optionName)) { return @(foreach ($value in $enumValues[$optionName]) { if ($value.StartsWith($valueWord, [System.StringComparison]::OrdinalIgnoreCase)) { [System.Management.Automation.CompletionResult]::new($valuePrefix + $value, $value, 'ParameterValue', $value) } }) } if ($directoryOptions.Contains($optionName)) { return @(foreach ($item in [System.Management.Automation.CompletionCompleters]::CompleteFilename($valueWord)) { if ($item.ResultType -ne [System.Management.Automation.CompletionResultType]::ProviderContainer) { continue } if ($valuePrefix) { [System.Management.Automation.CompletionResult]::new($valuePrefix + $item.CompletionText, $item.ListItemText, $item.ResultType, $item.ToolTip) } else { $item } }) } return @() } if ($node -eq 'codex;completion' -and -not $WordToComplete.StartsWith('-')) { return @(foreach ($shell in @('bash', 'elvish', 'fish', 'powershell', 'zsh')) { if ($shell.StartsWith($WordToComplete, [System.StringComparison]::OrdinalIgnoreCase)) { [System.Management.Automation.CompletionResult]::new($shell, $shell, 'ParameterValue', "Generate $shell completions") } }) } @() } function Invoke-CodexCompletion { [CmdletBinding()] [OutputType([object[]])] param( [string]$WordToComplete, [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) $valueResults = @(Get-CodexValueCompletion -WordToComplete $WordToComplete -CommandAst $CommandAst -CursorPosition $CursorPosition) if ($valueResults.Count -gt 0) { $valueResults return } $completionInvoker = Get-CodexCompletionInvoker if ($null -eq $completionInvoker) { return } & $completionInvoker $WordToComplete $CommandAst $CursorPosition } Register-ArgumentCompleter -Native -CommandName @('codex', 'codex.cmd', 'codex.ps1') -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) Invoke-CodexCompletion -WordToComplete $wordToComplete -CommandAst $commandAst -CursorPosition $cursorPosition } |