Lumoswitch Docs
Agent setup

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-agent

Then create a Lumoswitch API configuration with Responses output.

Connection values

PlaceholderValue
{{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'
pi

Open 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-clean

Cleanup 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 /model and confirm that the provider is lumoswitch and 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.json still overrides the isolated global settings. Check it if Pi starts with a different model.
  • If pi is 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

On this page