Pi Coding Agent setup
Pi is a terminal coding Agent with built-in read, write, edit, and shell tools. This guide registers Lumoswitch as a custom OpenAI Responses provider while keeping the default ~/.pi/agent configuration untouched.
Install Pi first:
npm install -g --ignore-scripts @earendil-works/pi-coding-agentThen create a Lumoswitch API configuration with Responses output.
Connection values
| Placeholder | Value |
|---|---|
{{api_base_url}} | Downstream API URL, including /v1 |
{{access_key}} | Access Key selected for the Lumoswitch API configuration |
{{model}} | Client-facing model name |
{{pi_models_json}} | Pi provider catalog generated from the effective model and reasoning profiles |
{{pi_settings_json}} | Pi defaults and scoped-model list generated for the selected configuration |
One-time launch
Run this in the project directory where you want to use Pi:
(
set -e
LUMOSWITCH_PI_DIR="$(mktemp -d "${TMPDIR:-/tmp}/lumoswitch-pi.XXXXXX")"
lumoswitch_cleanup() {
rm -rf "$LUMOSWITCH_PI_DIR"
}
trap lumoswitch_cleanup EXIT HUP INT TERM
umask 077
printf '%s\n' '{{pi_models_json}}' > "$LUMOSWITCH_PI_DIR/models.json"
printf '%s\n' '{{pi_settings_json}}' > "$LUMOSWITCH_PI_DIR/settings.json"
env \
PI_CODING_AGENT_DIR="$LUMOSWITCH_PI_DIR" \
LUMOSWITCH_PI_API_KEY="{{access_key}}" \
pi --provider lumoswitch --model "{{model}}" --no-session
)The temporary Agent directory and session are removed when Pi exits. Your normal Pi settings, credentials, extensions, and sessions are not changed.
Persistent use
This command writes an isolated Pi Agent directory, saves the Access Key in an owner-only environment file, adds a small shell-profile hook, and creates an optional maintenance launcher. Pi sessions are stored separately under ~/.local/share/lumoswitch/agents/pi/sessions so removing the connection does not delete them.
set -e
LUMOSWITCH_ROOT="$HOME/.config/lumoswitch/agents/pi"
LUMOSWITCH_AGENT_DIR="$LUMOSWITCH_ROOT/agent"
LUMOSWITCH_SESSION_DIR="$HOME/.local/share/lumoswitch/agents/pi/sessions"
LUMOSWITCH_ENV="$LUMOSWITCH_ROOT/env.sh"
LUMOSWITCH_LAUNCHER="$HOME/.local/bin/lumoswitch-pi"
mkdir -p "$LUMOSWITCH_AGENT_DIR" "$LUMOSWITCH_SESSION_DIR" "$(dirname "$LUMOSWITCH_LAUNCHER")"
umask 077
if [ -f "$LUMOSWITCH_LAUNCHER" ]; then
LUMOSWITCH_BACKUP="$LUMOSWITCH_LAUNCHER.backup.$(date +%Y%m%d-%H%M%S)-$$"
cp "$LUMOSWITCH_LAUNCHER" "$LUMOSWITCH_BACKUP"
chmod 600 "$LUMOSWITCH_BACKUP"
fi
printf '%s\n' '{{pi_models_json}}' > "$LUMOSWITCH_AGENT_DIR/models.json"
printf '%s\n' '{{pi_settings_json}}' > "$LUMOSWITCH_AGENT_DIR/settings.json"
cat > "$LUMOSWITCH_ENV" <<'LUMOSWITCH_ENV_EOF'
export PI_CODING_AGENT_DIR="$HOME/.config/lumoswitch/agents/pi/agent"
export PI_CODING_AGENT_SESSION_DIR="$HOME/.local/share/lumoswitch/agents/pi/sessions"
export LUMOSWITCH_PI_API_KEY="{{access_key}}"
LUMOSWITCH_ENV_EOF
case "$SHELL" in
*/zsh) LUMOSWITCH_PROFILE="$HOME/.zshrc" ;;
*/bash) LUMOSWITCH_PROFILE="$HOME/.bashrc" ;;
*) LUMOSWITCH_PROFILE="$HOME/.profile" ;;
esac
touch "$LUMOSWITCH_PROFILE"
LUMOSWITCH_PROFILE_MARKER='# Lumoswitch Agent: pi'
LUMOSWITCH_SOURCE_LINE='[ -f "$HOME/.config/lumoswitch/agents/pi/env.sh" ] && . "$HOME/.config/lumoswitch/agents/pi/env.sh"'
if ! grep -Fqx "$LUMOSWITCH_SOURCE_LINE" "$LUMOSWITCH_PROFILE"; then
printf '\n%s\n%s\n' "$LUMOSWITCH_PROFILE_MARKER" "$LUMOSWITCH_SOURCE_LINE" >> "$LUMOSWITCH_PROFILE"
fi
printf '%s\n' "$LUMOSWITCH_PROFILE" > "$LUMOSWITCH_ROOT/profile-path"
cat > "$LUMOSWITCH_LAUNCHER" <<'LUMOSWITCH_LAUNCHER_EOF'
#!/usr/bin/env bash
set -e
LUMOSWITCH_ROOT="$HOME/.config/lumoswitch/agents/pi"
LUMOSWITCH_ENV="$LUMOSWITCH_ROOT/env.sh"
LUMOSWITCH_LAUNCHER="$HOME/.local/bin/lumoswitch-pi"
if [ "$1" = "--lumoswitch-clean" ]; then
if [ -f "$LUMOSWITCH_ROOT/profile-path" ]; then
LUMOSWITCH_PROFILE="$(cat "$LUMOSWITCH_ROOT/profile-path")"
if [ -f "$LUMOSWITCH_PROFILE" ]; then
LUMOSWITCH_PROFILE_MARKER='# Lumoswitch Agent: pi'
LUMOSWITCH_SOURCE_LINE='[ -f "$HOME/.config/lumoswitch/agents/pi/env.sh" ] && . "$HOME/.config/lumoswitch/agents/pi/env.sh"'
LUMOSWITCH_PROFILE_TMP="$LUMOSWITCH_PROFILE.lumoswitch.$$"
awk -v marker="$LUMOSWITCH_PROFILE_MARKER" -v source="$LUMOSWITCH_SOURCE_LINE" '$0 != marker && $0 != source' "$LUMOSWITCH_PROFILE" > "$LUMOSWITCH_PROFILE_TMP"
mv "$LUMOSWITCH_PROFILE_TMP" "$LUMOSWITCH_PROFILE"
fi
fi
if [ -d "$LUMOSWITCH_ROOT" ]; then
find "$LUMOSWITCH_ROOT" -depth \( -type f -o -type l \) -delete
find "$LUMOSWITCH_ROOT" -depth -type d -exec rmdir {} \; 2>/dev/null || true
fi
find "$(dirname "$LUMOSWITCH_LAUNCHER")" -maxdepth 1 -type f -name 'lumoswitch-pi.backup.*' -delete
rm -f "$LUMOSWITCH_LAUNCHER"
printf '%s\n' 'Lumoswitch Pi setup removed. Saved Pi sessions were preserved.'
exit 0
fi
if [ -f "$LUMOSWITCH_ENV" ]; then . "$LUMOSWITCH_ENV"; fi
exec pi "$@"
LUMOSWITCH_LAUNCHER_EOF
chmod 600 "$LUMOSWITCH_ENV"
find "$LUMOSWITCH_ROOT" -type f -exec chmod 600 {} \;
chmod 700 "$LUMOSWITCH_LAUNCHER"
. "$LUMOSWITCH_ENV"
printf '%s\n' 'Native Lumoswitch setup saved. Future command: pi'
printf '%s\n' 'Optional maintenance launcher: ~/.local/bin/lumoswitch-pi'
piOpen a new terminal after setup. For everyday use, run pi.
Native Windows PowerShell import
These commands use Pi's Windows-native configuration override and do not require WSL.
One-time use on Windows
& {
$ErrorActionPreference = 'Stop'
$lumoswitchPiDir = Join-Path ([IO.Path]::GetTempPath()) ('lumoswitch-pi-' + [Guid]::NewGuid().ToString('N'))
[IO.Directory]::CreateDirectory($lumoswitchPiDir) | Out-Null
$lumoswitchEnvironmentNames = @('PI_CODING_AGENT_DIR', 'LUMOSWITCH_PI_API_KEY')
$lumoswitchPreviousEnvironment = @{}
foreach ($lumoswitchName in $lumoswitchEnvironmentNames) {
$lumoswitchPreviousEnvironment[$lumoswitchName] = [Environment]::GetEnvironmentVariable($lumoswitchName, 'Process')
}
$lumoswitchExitCode = 0
try {
[IO.File]::WriteAllText((Join-Path $lumoswitchPiDir 'models.json'), '{{pi_models_json}}', [Text.UTF8Encoding]::new($false))
[IO.File]::WriteAllText((Join-Path $lumoswitchPiDir 'settings.json'), '{{pi_settings_json}}', [Text.UTF8Encoding]::new($false))
[Environment]::SetEnvironmentVariable('PI_CODING_AGENT_DIR', $lumoswitchPiDir, 'Process')
[Environment]::SetEnvironmentVariable('LUMOSWITCH_PI_API_KEY', '{{access_key}}', 'Process')
$lumoswitchExecutable = Get-Command 'pi.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi.exe' -CommandType Application -ErrorAction SilentlyContinue }
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi' -CommandType Application -ErrorAction Stop }
& $lumoswitchExecutable.Path '--provider' 'lumoswitch' '--model' '{{model}}' '--no-session'
if ($null -ne $LASTEXITCODE) { $lumoswitchExitCode = $LASTEXITCODE }
} finally {
foreach ($lumoswitchName in $lumoswitchEnvironmentNames) {
[Environment]::SetEnvironmentVariable($lumoswitchName, $lumoswitchPreviousEnvironment[$lumoswitchName], 'Process')
}
Remove-Item -LiteralPath $lumoswitchPiDir -Recurse -Force -ErrorAction SilentlyContinue
}
if ($lumoswitchExitCode -ne 0) { throw 'pi exited with code ' + $lumoswitchExitCode }
}Persistent use on Windows
& {
$ErrorActionPreference = 'Stop'
$lumoswitchRoot = Join-Path $env:LOCALAPPDATA 'Lumoswitch\agents\pi'
$lumoswitchAgentDir = Join-Path $lumoswitchRoot 'agent'
$lumoswitchSessionDir = Join-Path $env:LOCALAPPDATA 'Lumoswitch\sessions\pi'
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-pi.ps1'
$lumoswitchBackupSuffix = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + $PID
function Protect-LumoswitchFile([string] $Path) {
$lumoswitchSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
& icacls.exe $Path '/inheritance:r' '/grant:r' ('*' + $lumoswitchSid + ':(F)') '*S-1-5-18:(F)' '*S-1-5-32-544:(F)' | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Could not restrict access to $Path" }
}
function Install-LumoswitchFile([string] $Path, [string] $Content) {
[IO.Directory]::CreateDirectory((Split-Path -Parent $Path)) | Out-Null
if (Test-Path -LiteralPath $Path) {
$lumoswitchItem = Get-Item -LiteralPath $Path -Force
if ($lumoswitchItem.PSIsContainer -or ($lumoswitchItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "Lumoswitch refused to replace a directory or link: $Path"
}
$lumoswitchBackup = $Path + '.backup.' + $lumoswitchBackupSuffix
Copy-Item -LiteralPath $Path -Destination $lumoswitchBackup
Protect-LumoswitchFile $lumoswitchBackup
}
$lumoswitchTemporary = $Path + '.' + [Guid]::NewGuid().ToString('N') + '.tmp'
try {
[IO.File]::WriteAllText($lumoswitchTemporary, $Content, [Text.UTF8Encoding]::new($false))
Move-Item -LiteralPath $lumoswitchTemporary -Destination $Path -Force
Protect-LumoswitchFile $Path
} finally {
Remove-Item -LiteralPath $lumoswitchTemporary -Force -ErrorAction SilentlyContinue
}
}
function Preserve-LumoswitchOriginal([string] $Name) {
$lumoswitchOriginalRoot = Join-Path $lumoswitchRoot 'original'
$lumoswitchState = Join-Path $lumoswitchOriginalRoot ($Name + '.state')
if (Test-Path -LiteralPath $lumoswitchState -PathType Leaf) { return }
[IO.Directory]::CreateDirectory($lumoswitchOriginalRoot) | Out-Null
$lumoswitchValue = [Environment]::GetEnvironmentVariable($Name, 'User')
if ($null -eq $lumoswitchValue) {
Install-LumoswitchFile $lumoswitchState 'absent'
} else {
Install-LumoswitchFile (Join-Path $lumoswitchOriginalRoot ($Name + '.value')) $lumoswitchValue
Install-LumoswitchFile $lumoswitchState 'present'
}
}
[IO.Directory]::CreateDirectory($lumoswitchAgentDir) | Out-Null
[IO.Directory]::CreateDirectory($lumoswitchSessionDir) | Out-Null
Install-LumoswitchFile (Join-Path $lumoswitchAgentDir 'models.json') '{{pi_models_json}}'
Install-LumoswitchFile (Join-Path $lumoswitchAgentDir 'settings.json') '{{pi_settings_json}}'
foreach ($lumoswitchName in @('PI_CODING_AGENT_DIR', 'PI_CODING_AGENT_SESSION_DIR', 'LUMOSWITCH_PI_API_KEY')) {
Preserve-LumoswitchOriginal $lumoswitchName
}
$lumoswitchEnvironment = [ordered]@{
PI_CODING_AGENT_DIR = $lumoswitchAgentDir
PI_CODING_AGENT_SESSION_DIR = $lumoswitchSessionDir
LUMOSWITCH_PI_API_KEY = '{{access_key}}'
}
foreach ($lumoswitchEntry in $lumoswitchEnvironment.GetEnumerator()) {
[Environment]::SetEnvironmentVariable($lumoswitchEntry.Key, $lumoswitchEntry.Value, 'User')
[Environment]::SetEnvironmentVariable($lumoswitchEntry.Key, $lumoswitchEntry.Value, 'Process')
}
$lumoswitchLauncherContent = @'
param([Parameter(ValueFromRemainingArguments = $true)][string[]] $AgentArgs)
$ErrorActionPreference = 'Stop'
$lumoswitchRoot = Join-Path $env:LOCALAPPDATA 'Lumoswitch\agents\pi'
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-pi.ps1'
function Remove-LumoswitchFile([string] $Path) {
if (-not (Test-Path -LiteralPath $Path)) { return }
$lumoswitchItem = Get-Item -LiteralPath $Path -Force
if ($lumoswitchItem.PSIsContainer -or ($lumoswitchItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "Lumoswitch refused to remove a directory or link: $Path"
}
Remove-Item -LiteralPath $Path -Force
}
function Restore-LumoswitchOriginal([string] $Name) {
$lumoswitchOriginalRoot = Join-Path $lumoswitchRoot 'original'
$lumoswitchState = Join-Path $lumoswitchOriginalRoot ($Name + '.state')
if (-not (Test-Path -LiteralPath $lumoswitchState -PathType Leaf)) { return }
$lumoswitchStatus = (Get-Content -LiteralPath $lumoswitchState -Raw).Trim()
$lumoswitchValue = if ($lumoswitchStatus -eq 'present') {
Get-Content -LiteralPath (Join-Path $lumoswitchOriginalRoot ($Name + '.value')) -Raw
} else { $null }
[Environment]::SetEnvironmentVariable($Name, $lumoswitchValue, 'User')
[Environment]::SetEnvironmentVariable($Name, $lumoswitchValue, 'Process')
}
if ($AgentArgs.Count -gt 0 -and $AgentArgs[0] -eq '--lumoswitch-clean') {
foreach ($lumoswitchName in @('PI_CODING_AGENT_DIR', 'PI_CODING_AGENT_SESSION_DIR', 'LUMOSWITCH_PI_API_KEY')) {
Restore-LumoswitchOriginal $lumoswitchName
}
if (Test-Path -LiteralPath $lumoswitchRoot -PathType Container) {
$lumoswitchRootItem = Get-Item -LiteralPath $lumoswitchRoot -Force
if ($lumoswitchRootItem.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Lumoswitch refused to clean through a linked directory.' }
foreach ($lumoswitchFile in @(Get-ChildItem -LiteralPath $lumoswitchRoot -File -Recurse -Force)) { Remove-LumoswitchFile $lumoswitchFile.FullName }
foreach ($lumoswitchDirectory in @(Get-ChildItem -LiteralPath $lumoswitchRoot -Directory -Recurse -Force | Sort-Object FullName -Descending)) {
if ($null -eq (Get-ChildItem -LiteralPath $lumoswitchDirectory.FullName -Force | Select-Object -First 1)) { Remove-Item -LiteralPath $lumoswitchDirectory.FullName -Force }
}
if ($null -eq (Get-ChildItem -LiteralPath $lumoswitchRoot -Force | Select-Object -First 1)) { Remove-Item -LiteralPath $lumoswitchRoot -Force }
}
$lumoswitchBin = Split-Path -Parent $lumoswitchLauncher
if (Test-Path -LiteralPath $lumoswitchBin -PathType Container) {
foreach ($lumoswitchBackup in @(Get-ChildItem -LiteralPath $lumoswitchBin -Filter 'lumoswitch-pi.ps1.backup.*' -File -Force)) { Remove-LumoswitchFile $lumoswitchBackup.FullName }
}
Remove-LumoswitchFile $lumoswitchLauncher
Write-Host 'Lumoswitch Pi setup removed. Saved Pi sessions were preserved.'
exit 0
}
$lumoswitchAgentDir = Join-Path $env:LOCALAPPDATA 'Lumoswitch\agents\pi\agent'
$lumoswitchSessionDir = Join-Path $env:LOCALAPPDATA 'Lumoswitch\sessions\pi'
$lumoswitchApiKey = [Environment]::GetEnvironmentVariable('LUMOSWITCH_PI_API_KEY', 'User')
[Environment]::SetEnvironmentVariable('PI_CODING_AGENT_DIR', $lumoswitchAgentDir, 'Process')
[Environment]::SetEnvironmentVariable('PI_CODING_AGENT_SESSION_DIR', $lumoswitchSessionDir, 'Process')
[Environment]::SetEnvironmentVariable('LUMOSWITCH_PI_API_KEY', $lumoswitchApiKey, 'Process')
$lumoswitchExecutable = Get-Command 'pi.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi.exe' -CommandType Application -ErrorAction SilentlyContinue }
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi' -CommandType Application -ErrorAction Stop }
& $lumoswitchExecutable.Path @AgentArgs
if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE }
'@
Install-LumoswitchFile $lumoswitchLauncher $lumoswitchLauncherContent
Write-Host 'Native Lumoswitch setup saved. Future command: pi'
Write-Host 'Optional maintenance launcher:' $lumoswitchLauncher
$lumoswitchExecutable = Get-Command 'pi.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi.exe' -CommandType Application -ErrorAction SilentlyContinue }
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'pi' -CommandType Application -ErrorAction Stop }
& $lumoswitchExecutable.Path
if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw 'pi exited with code ' + $LASTEXITCODE }
}The Windows command preserves any existing user-level Pi environment variables before installing the isolated configuration. Cleanup restores those original values and leaves the separate session directory intact.
Remove the Lumoswitch setup
Windows PowerShell
& {
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-pi.ps1'
if (-not (Test-Path -LiteralPath $lumoswitchLauncher -PathType Leaf)) {
throw 'The optional Lumoswitch maintenance launcher is missing. Run persistent import again before cleanup.'
}
& powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $lumoswitchLauncher --lumoswitch-clean
}macOS / Linux
"$HOME/.local/bin/lumoswitch-pi" --lumoswitch-cleanCleanup removes the entire Lumoswitch-isolated Agent directory, including extensions or packages installed inside it, along with the profile hook, saved credential environment, and maintenance launcher. It preserves the separate Pi session directory and never modifies ~/.pi/agent.
Verify and troubleshoot
- Run
/modeland confirm that the provider islumoswitchand only configured Lumoswitch models are shown. - Use Shift+Tab to cycle only the reasoning levels supported by the selected model and its effective route.
- The template disables optional OpenAI fields that are commonly rejected by compatible gateways while retaining Pi's normal file and shell tools.
- Project-level
.pi/settings.jsonstill overrides the isolated global settings. Check it if Pi starts with a different model. - If
piis missing, reinstall the current official package and open a new terminal.
References: Pi Coding Agent, custom models, settings, and environment variables.
Maintainer publication fields
- Platform ID:
pi - Display name:
Pi Coding Agent - Downstream protocol:
responses - macOS/Linux and Windows temporary modes: enabled
- macOS/Linux and Windows persistent strategies:
native - Future command on both platforms:
pi - Display order:
25 - Last structurally verified:
2026-08-27