Skip to content

aproorg/claude-wrapper

Repository files navigation

claude-wrapper

Route Claude Code through a LiteLLM proxy with automatic API key management via 1Password CLI.

Use this when your team runs a shared LiteLLM gateway and stores API keys in 1Password. The wrapper handles config distribution, per-project key rotation, per-repo usage attribution, and keeps developer machines in sync — no manual env vars to manage.

Install

macOS / Linux / WSL:

curl -fsSL https://raw.githubusercontent.com/aproorg/claude-wrapper/main/install.sh | bash

Windows (PowerShell):

irm https://raw.githubusercontent.com/aproorg/claude-wrapper/main/install.ps1 | iex

The installer:

  1. Writes a process wrapper to ~/.local/bin/claude that shadows the real binary
  2. Adds ~/.local/bin to your PATH (if not already there)
  3. Prompts for your LiteLLM base URL and 1Password item reference
  4. Pre-fetches the team configuration

Non-interactive mode: When piped without a TTY (CI, Docker), prompts are skipped and defaults are used silently.

Verify

which claude           # Should show ~/.local/bin/claude
CLAUDE_DEBUG=1 claude  # Shows resolved config

Testing a development branch

When testing a PR or unmerged branch, you must export CLAUDE_ENV_URL before running the installer. Inline VAR=val curl ... | bash doesn't work — env-var prefixes only set the var for the immediately-following command (curl), not for the bash subshell that runs the install script. Without the export, the installer silently falls back to main and downloads sibling files (the wrapper, claude-env.sh) from there.

export CLAUDE_ENV_URL="https://raw.githubusercontent.com/aproorg/claude-wrapper/<branch-name>/claude-env.sh"
curl -fsSL "https://raw.githubusercontent.com/aproorg/claude-wrapper/<branch-name>/install.sh" | bash

Verify the install came from the branch by checking the [INFO] Source: line. The installer also patches the installed wrapper so subsequent claude launches keep fetching from that branch.

Prerequisites

  • Claude Code installed (Homebrew, npm, or standalone)
  • 1Password CLI (op) with CLI integration enabled
  • curl and git

Forking for Your Organization

This repo ships with defaults for the maintainers' infrastructure. To use it for your own org:

  1. Fork the repo and update claude-env.sh:
    • LITELLM_BASE_URL — your LiteLLM gateway URL
    • OP_ACCOUNT — your 1Password team domain
    • OP_ITEM — the 1Password vault path for your API keys
  2. Update the install URL in your fork's README to point to your raw GitHub URL
  3. Distribute — team members run your fork's one-liner to install

Individual developers can also override any value via ~/.config/claude/local.env without touching the shared config.

Troubleshooting

# Clear all caches (API keys + remote config)
claude --clear-cache

# Force refresh config cache only
rm ~/.cache/claude/env-remote.sh

# Re-run installer to update local settings
curl -fsSL https://raw.githubusercontent.com/aproorg/claude-wrapper/main/install.sh | bash

# Debug mode (shows resolved config + stale cache warnings)
CLAUDE_DEBUG=1 claude

Developer Setup

If you're working on this repo, use a symlink instead of the installed wrapper:

git clone <your-fork-url> ~/claude-wrapper

mkdir -p ~/.local/bin
ln -sf ~/claude-wrapper/claude ~/.local/bin/claude

# Add to your .bashrc / .zshrc if ~/.local/bin isn't already on PATH:
export PATH="$HOME/.local/bin:$PATH"

Verify: which claude should show ~/.local/bin/claude.

E2E Testing

There's no automated test suite — verify the full install-to-launch path manually.

1. Clean slate

# Back up your current wrapper, then remove it along with all caches.
# Use `cp -P` to preserve the symlink — plain `cp` dereferences it and
# step 9's restore would replace your dev symlink with a static byte copy.
cp -P ~/.local/bin/claude ~/.local/bin/claude.bak
rm -f ~/.local/bin/claude
rm -f ~/.cache/claude/env-remote.sh ~/.cache/claude/*.key

# Confirm only the real binary remains
which claude  # should show /opt/homebrew/bin/claude (or wherever yours lives)

2. Install via curl

CLAUDE_FORCE=1 curl -fsSL https://raw.githubusercontent.com/aproorg/claude-wrapper/main/install.sh | bash

3. Verify the wrapper shadows the real binary

which claude           # must show ~/.local/bin/claude, NOT the real binary
CLAUDE_DEBUG=1 claude --version
# Expected: key=fetched (or key=cached), project name, base URL, model, then version

4. Test cache hit (no 1Password prompt)

CLAUDE_DEBUG=1 claude --version
# Expected: key=cached (no 1Password prompt on second run)

5. Test empty-file guard

Run this inside the repo whose project key you want to clear (the command derives the project name from the current git remote). Also unset CLAUDE_PROJECT first — if it's set in your environment (e.g. via direnv or a parent shell), it overrides the git-based detection and the empty-file guard won't be exercised.

unset CLAUDE_PROJECT
: > ~/.cache/claude/$(basename $(git remote get-url origin 2>/dev/null | sed 's/.*\///;s/\.git$//')).key
CLAUDE_DEBUG=1 claude --version
# Expected: key=fetched (empty cache file treated as miss)

6. Test cache re-fetch

rm ~/.cache/claude/env-remote.sh
CLAUDE_DEBUG=1 claude --version  # should re-fetch remote config

7. Test env var override

CLAUDE_MODEL="claude-sonnet-4-20250514" CLAUDE_DEBUG=1 claude --version
# model line should show claude-sonnet-4-20250514

8. Test clear-cache

claude --clear-cache
ls ~/.cache/claude/          # .key files and env-remote.sh should be gone
CLAUDE_DEBUG=1 claude --version  # should recover by re-fetching everything

9. Restore

# If you use the dev symlink:
ln -sf ~/claude-wrapper/claude ~/.local/bin/claude
# Or restore the backup:
mv ~/.local/bin/claude.bak ~/.local/bin/claude
Step What to check
Install Wrapper, local.env, and cached remote config all written
which claude Resolves to ~/.local/bin/claude, not the real binary
Debug output key=fetched or key=cached, correct project, base URL, model, and headers=x-github-repo: <project>
Cache hit Second run shows key=cached, no 1Password prompt
Empty-file guard Empty .key file treated as cache miss (key=fetched)
Cache re-fetch Re-fetches after deleting env-remote.sh
Env var override CLAUDE_MODEL takes priority over remote + local config
Clear cache Removes all .key files and env-remote.sh
Recovery Wrapper re-fetches everything and launches successfully

Reference

How it works

The installer writes a process wrapper to ~/.local/bin/claude that shadows the real Claude Code binary. When you run claude, the wrapper:

  1. Finds the real binary (portable PATH iteration, skipping itself)
  2. Fetches the latest team config from this repo, validates it against an integrity check, caches it for 5 minutes, falls back to stale cache on network failure
  3. Sources the config to set ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, model defaults, per-project API keys, and ANTHROPIC_CUSTOM_HEADERS (auto-injects x-github-repo: <project> for LiteLLM per-repo attribution)
  4. Optionally sources ~/.config/claude/middleware.sh for custom pre-launch hooks (errors are trapped with actionable messages)
  5. execs the real Claude Code with all original arguments

Config override chain: Remote defaults (claude-env.sh) → local overrides (~/.config/claude/local.env) → environment variables. Local values persist across remote config updates — the TTL-based refresh only re-fetches the remote config, never touching your local settings.

Per-project API keys

The wrapper detects the current project from the git remote (or directory name) and looks up a project-specific API key field in your 1Password item. If no project-specific field exists, it falls back to the default API Key field.

1Password item: "op://Vault/your-litellm-item"
  ├── API Key          (default fallback)
  ├── backend-api      (project-specific)
  └── customer-portal  (project-specific)

Override project detection: CLAUDE_PROJECT=my-project claude

Per-repo attribution (x-github-repo header)

Every request to the LiteLLM gateway is automatically tagged with an x-github-repo: <project> header so the gateway can attribute usage, spend, and rate-limits per repository — zero developer setup.

The value comes from the same project-detection logic used for API key lookup (CLAUDE_PROJECT), so a request from ~/code/your-org/backend-api is tagged x-github-repo: backend-api.

If you've already set ANTHROPIC_CUSTOM_HEADERS (via local.env, shell environment, or middleware.sh), the auto-injected header is appended on a new line so your custom headers are preserved:

x-team: platform
x-github-repo: backend-api

To suppress the header on a specific project (rare — e.g., experimental scratch work you don't want attributed), strip it in your personal ~/.config/claude/middleware.sh. The team config does not provide a built-in opt-out.

Configuration
Variable Default Description
CLAUDE_ENV_URL (this repo) Override remote config URL
CLAUDE_ENV_UPDATE_TTL 300 Cache TTL in seconds
CLAUDE_MODEL claude-opus-4-6 Override default model
CLAUDE_PROJECT (auto-detected) Override project name
CLAUDE_DEBUG 0 Enable debug output
ANTHROPIC_CUSTOM_HEADERS (auto-set) Auto-injected x-github-repo: $CLAUDE_PROJECT. Pre-existing values preserved (header appended on new line).

Files:

File Purpose
~/.local/bin/claude Process wrapper (written by installer)
~/.cache/claude/env-remote.sh Cached remote config (auto-refreshed)
~/.config/claude/local.env Your local overrides (never auto-modified)
~/.config/claude/middleware.sh Optional pre-launch hook
Local config (local.env)

The installer stores your LiteLLM URL and 1Password item in ~/.config/claude/local.env. These values override the remote defaults and are never touched by automatic updates.

# View current settings
cat ~/.config/claude/local.env

# Re-run installer to change values (shows current as defaults)
curl -fsSL https://raw.githubusercontent.com/aproorg/claude-wrapper/main/install.sh | bash

The file uses simple KEY="VALUE" format and has 0600 permissions.

Middleware

Create ~/.config/claude/middleware.sh to run custom shell commands before Claude launches. The file is sourced (not executed), so it can export environment variables, modify PATH, or run setup commands.

# Example: add a custom env var
echo 'export MY_CUSTOM_VAR="hello"' > ~/.config/claude/middleware.sh

# Example: prepend to PATH
echo 'export PATH="/opt/my-tools/bin:$PATH"' > ~/.config/claude/middleware.sh
  • The file is optional — if missing, nothing happens.
  • Errors in middleware are caught and reported with an actionable message (file path + fix instructions).
Windows (PowerShell)

Install

irm https://raw.githubusercontent.com/aproorg/claude-wrapper/main/install.ps1 | iex

The installer:

  1. Downloads claudestart.ps1 to %LOCALAPPDATA%\Programs\claude-wrapper\
  2. Creates a claudestart.cmd shim so it works from cmd.exe too
  3. Adds the install directory to your user PATH
  4. Prompts for your LiteLLM base URL and 1Password item reference

Note: On Windows the command is claudestart (not claude) because Windows doesn't support the same binary-shadowing trick used on macOS/Linux.

Verify

Get-Command claudestart           # Should show the .cmd shim
$env:CLAUDE_DEBUG = "1"; claudestart  # Shows resolved config

Testing a development branch

When testing a PR or unmerged branch, you must set $env:CLAUDE_ENV_URL to the branch's claude-env.sh URL before running the installer. Without it, the installer falls back to main and downloads sibling files (including claudestart.ps1) from there — which is almost certainly not what you want when testing a branch.

$base = "https://raw.githubusercontent.com/aproorg/claude-wrapper/<branch-name>"
$env:CLAUDE_ENV_URL = "$base/claude-env.sh"
$env:CLAUDE_FORCE = "1"  # overwrite existing wrapper without backup
irm "$base/install.ps1" | iex

Verify the install came from the branch by checking the [INFO] Source: line — it should reference your branch, not main. The installer also patches the installed claudestart.ps1 so subsequent launches keep fetching from that branch.

Prerequisites

  • Claude Code installed (npm global install or standalone)
  • 1Password CLI (op.exe) with CLI integration enabled
  • PowerShell 5.1+ (ships with Windows 10/11) or PowerShell 7+
  • git (for project detection)

Troubleshooting

# Clear all caches (API keys + remote config)
claudestart --clear-cache

# Force refresh config cache only
Remove-Item "$env:LOCALAPPDATA\claude\env-remote.sh"

# Re-run installer to update local settings
irm https://raw.githubusercontent.com/aproorg/claude-wrapper/main/install.ps1 | iex

# Force reinstall wrapper (e.g. to pick up updates to claudestart.ps1)
$env:CLAUDE_FORCE = "1"; irm https://raw.githubusercontent.com/aproorg/claude-wrapper/main/install.ps1 | iex

# Debug mode
$env:CLAUDE_DEBUG = "1"; claudestart

File locations

File Purpose
%LOCALAPPDATA%\Programs\claude-wrapper\claudestart.ps1 PowerShell wrapper
%LOCALAPPDATA%\Programs\claude-wrapper\claudestart.cmd CMD shim
%LOCALAPPDATA%\claude\env-remote.sh Cached remote config
%APPDATA%\claude\local.env Your local overrides
%LOCALAPPDATA%\claude\<project>.key Cached API keys (12h TTL)

Manual install

If you prefer not to use the installer:

  1. Run Set-ExecutionPolicy RemoteSigned in an elevated PowerShell (once)
  2. Clone the repo and copy claudestart.ps1 to a directory on your PATH
  3. Create a claudestart.cmd next to it with:
    @powershell -ExecutionPolicy Bypass -File "%~dp0claudestart.ps1" %*
    

WSL

Follow the macOS/Linux instructions inside your WSL shell — WSL uses the bash wrapper, not the PowerShell one.

VSCode on Windows

Add to your settings.json:

{
  "claudeCode.environmentVariables": [
    { "name": "CLAUDE_CODE_SKIP_AUTH_LOGIN", "value": "1" }
  ],
  "claudeCode.claudeProcessWrapper": "claudestart.cmd"
}
VSCode integration (macOS/Linux)

Add to your settings.json:

{
  "claudeCode.environmentVariables": [
    { "name": "CLAUDE_CODE_SKIP_AUTH_LOGIN", "value": "1" }
  ],
  "claudeCode.claudeProcessWrapper": "/Users/YOU/.local/bin/claude"
}

About

Claude Code environment wrapper — LiteLLM gateway + 1Password secrets

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors