Skip to content

Instantly share code, notes, and snippets.

@alerma-sumo
Last active September 16, 2025 00:49
Show Gist options
  • Save alerma-sumo/74a91363a9714478bdf5d26612ae53c9 to your computer and use it in GitHub Desktop.
Save alerma-sumo/74a91363a9714478bdf5d26612ae53c9 to your computer and use it in GitHub Desktop.
LiteLLM Claude Code Installation with Bun TypeScript compilation

LiteLLM Claude Code Integration

A Bun-based TypeScript package for integrating Claude Code with LiteLLM proxy authentication.

Features

  • 🚀 Bun-native: Built specifically for Bun runtime with native APIs
  • 🔐 OIDC Authentication: Automated browser-based authentication flow
  • 💾 Session Caching: Intelligent session management with validation
  • Compiled Executable: Single binary deployment via bun build --compile
  • 🎭 Playwright Integration: Headless browser automation for auth flows

Structure

packages/litellm/
├── src/
│   └── get_litellm_key.ts    # Main authentication script
├── scripts/
│   └── install.sh            # Installation script
├── dist/                     # Compiled executables (generated)
├── package.json
└── README.md

Development

Prerequisites

Setup

cd packages/litellm
bun install
bun run install:playwright

Scripts

# Build standalone executable
bun run build

# Build for Linux (cross-compile)
bun run build:cross

# Development mode with hot reload
bun run dev

# Install Playwright browsers
bun run install:playwright

# Clean build artifacts
bun run clean

Usage

As Standalone Executable

# Build the executable
bun run build

# Run with environment variables
LITELLM_PROXY_API_BASE=https://your-proxy.com ./dist/get_litellm_key

Direct Execution

# Run TypeScript directly
bun src/get_litellm_key.ts

Installation Script

Use the installation script to set up Claude Code with LiteLLM integration:

# Run from repository root
bash packages/litellm/scripts/install.sh

# Or from gist
curl -s https://gist.githubusercontent.com/alerma-sumo/74a91363a9714478bdf5d26612ae53c9/raw/install.sh | bash

Environment Variables

Variable Default Description
LITELLM_PROXY_API_BASE https://litellm.stag.zdn.sumologic.net LiteLLM proxy URL
LITELLM_COOKIE_NAME okta-session Session cookie name
LITELLM_CACHE_DIR ~/.cache/litellm Cache directory for sessions

How It Works

  1. Cache Check: Validates existing cached session
  2. Browser Auth: Opens browser for OIDC authentication if needed
  3. Session Retrieval: Gets session cookie from /cookies endpoint
  4. Caching: Stores valid session for future use
  5. Output: Returns session token for Claude Code

Integration with Claude Code

The compiled executable is used as an apiKeyHelper in Claude Code settings:

{
  "apiKeyHelper": "/path/to/get_litellm_key",
  "model": "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0",
  "env": {
    "ANTHROPIC_BASE_URL": "https://litellm.stag.zdn.sumologic.net"
  }
}

Building for Production

The package includes optimized build configurations:

  • --compile: Creates standalone executable
  • --minify: Reduces code size
  • --bytecode: Enables bytecode compilation for faster startup
  • Cross-compilation: Support for Linux targets

Dependencies

  • playwright: Browser automation for authentication
  • @types/bun: TypeScript definitions for Bun APIs

All other dependencies are provided by Bun's runtime (file I/O, HTTP client, shell commands).

#!/usr/bin/env bun
import { chromium } from "playwright";
import { mkdir } from "node:fs/promises";
async function run(baseUrl: string, cookieName: string): Promise<string> {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({ baseURL: baseUrl });
const page = await context.newPage();
await page.goto(baseUrl);
// Wait for the username input to appear
await page.waitForSelector('input[name="username"], input[type="email"], input[type="text"]');
await page.waitForURL(baseUrl, { timeout: 300 * 1000 });
// Make a call to the /cookies endpoint to
// 1. make sure we can hit the API
// 2. get the cookie that worked making that call
const response = await context.request.get("/cookies", {
headers: { "Accept": "application/json" }
});
const headersDict = await response.json();
const session = headersDict?.cookies?.[cookieName] || "";
await browser.close();
return session;
}
async function main() {
const baseUrl = process.env.LITELLM_PROXY_API_BASE || "https://litellm.stag.zdn.sumologic.net";
const cookieName = process.env.LITELLM_COOKIE_NAME || "okta-session";
const cacheDir = process.env.LITELLM_CACHE_DIR?.replace("~", process.env.HOME || "") || `${process.env.HOME}/.cache/litellm`;
const cachePath = `${cacheDir}/${cookieName}.txt`;
// Create cache directory
await mkdir(cacheDir, { recursive: true });
let session: string | null = null;
// Check if we have a valid cached session
const cacheFile = Bun.file(cachePath);
if (await cacheFile.exists()) {
const cachedSession = (await cacheFile.text()).trim();
if (cachedSession) {
try {
const response = await fetch(`${baseUrl}/cookies`, {
headers: { "Cookie": `${cookieName}=${cachedSession}` }
});
if (response.ok) {
const responseDict = await response.json();
session = responseDict?.cookies?.[cookieName] || "";
}
} catch (error) {
// Ignore error, will re-authenticate
}
}
}
// If we don't have a valid session, run the login flow
if (!session) {
session = await run(baseUrl, cookieName);
if (!session) {
throw new Error("Failed to retrieve session cookie after login.");
}
}
await Bun.write(cachePath, session);
console.log(session);
}
if (import.meta.main) {
main().catch(console.error);
}
#!/usr/bin/env bun
import { chromium } from "playwright";
import { mkdir } from "node:fs/promises";
async function run(baseUrl: string, cookieName: string): Promise<string> {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({ baseURL: baseUrl });
const page = await context.newPage();
await page.goto(baseUrl);
// Wait for the username input to appear
await page.waitForSelector('input[name="username"], input[type="email"], input[type="text"]');
await page.waitForURL(baseUrl, { timeout: 300 * 1000 });
// Make a call to the /cookies endpoint to
// 1. make sure we can hit the API
// 2. get the cookie that worked making that call
const response = await context.request.get("/cookies", {
headers: { "Accept": "application/json" }
});
const headersDict = await response.json();
const session = headersDict?.cookies?.[cookieName] || "";
await browser.close();
return session;
}
async function main() {
const baseUrl = process.env.LITELLM_PROXY_API_BASE || "https://litellm.stag.zdn.sumologic.net";
const cookieName = process.env.LITELLM_COOKIE_NAME || "okta-session";
const cacheDir = process.env.LITELLM_CACHE_DIR?.replace("~", process.env.HOME || "") || `${process.env.HOME}/.cache/litellm`;
const cachePath = `${cacheDir}/${cookieName}.txt`;
// Create cache directory
await mkdir(cacheDir, { recursive: true });
let session: string | null = null;
// Check if we have a valid cached session
const cacheFile = Bun.file(cachePath);
if (await cacheFile.exists()) {
const cachedSession = (await cacheFile.text()).trim();
if (cachedSession) {
try {
const response = await fetch(`${baseUrl}/cookies`, {
headers: { "Cookie": `${cookieName}=${cachedSession}` }
});
if (response.ok) {
const responseDict = await response.json();
session = responseDict?.cookies?.[cookieName] || "";
}
} catch (error) {
// Ignore error, will re-authenticate
}
}
}
// If we don't have a valid session, run the login flow
if (!session) {
session = await run(baseUrl, cookieName);
if (!session) {
throw new Error("Failed to retrieve session cookie after login.");
}
}
await Bun.write(cachePath, session);
console.log(session);
}
if (import.meta.main) {
main().catch(console.error);
}
#!/bin/bash
# LiteLLM Claude Code Installation Script
#
# This script installs Claude Code with LiteLLM proxy integration using Bun.
# It compiles a TypeScript helper script to a standalone executable for authentication.
#
# Environment Variables:
# - INSTALL_DIR: Installation directory (default: $HOME)
# - LITELLM_PROXY_API_BASE: LiteLLM proxy URL (default: https://litellm.stag.zdn.sumologic.net)
# - LITELLM_COOKIE_NAME: Session cookie name (default: okta-session)
# - LITELLM_CACHE_DIR: Cache directory (default: $HOME/.cache/litellm)
#
# Usage:
# curl -s https://gist.githubusercontent.com/alerma-sumo/74a91363a9714478bdf5d26612ae53c9/raw/install.sh | bash
#
set -euo pipefail
INSTALL_DIR="${INSTALL_DIR:-"$HOME"}"
LITELLM_PROXY_API_BASE="${LITELLM_PROXY_API_BASE:-"https://litellm.stag.zdn.sumologic.net"}"
LITELLM_COOKIE_NAME="${LITELLM_COOKIE_NAME:-"okta-session"}"
LITELLM_CACHE_DIR="${LITELLM_CACHE_DIR:-"$HOME/.litellm/cache"}"
LITELLM_BIN_DIR="${LITELLM_BIN_DIR:-"$HOME/.litellm/bin"}"
# Function to log messages
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Function to handle errors
handle_error() {
log "ERROR: $1"
exit 1
}
# Ensure curl is available (required for installers)
if ! command -v curl &> /dev/null; then
handle_error "curl is required for installation"
fi
# Function to install Bun
install_bun() {
log "Installing/updating bun..."
curl -fsSL https://bun.sh/install | bash || handle_error "Failed to install bun"
# Bun installer automatically adds to shell config and suggests refresh command
# Add to PATH for current session if not already there
if [[ ":$PATH:" != *":$HOME/.bun/bin:"* ]]; then
export PATH="$HOME/.bun/bin:$PATH"
fi
}
# Function to install UV
install_uv() {
log "Installing/updating uv..."
curl -LsSf https://astral.sh/uv/install.sh | sh || handle_error "Failed to install uv"
# UV installer automatically adds ~/.local/bin to PATH in shell config
# Add to PATH for current session if not already there
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
}
# Install dependencies in parallel
install_bun &
BUN_PID=$!
install_uv &
UV_PID=$!
# Wait for both to complete before installing claude-code (which depends on bun)
wait $BUN_PID || handle_error "Bun installation failed"
wait $UV_PID || handle_error "UV installation failed"
# Function to install Claude Code
install_claude_code() {
log "Installing @anthropic-ai/claude-code..."
bun add -g @anthropic-ai/claude-code@latest || handle_error "Failed to install claude-code with bun"
# Verify claude installation
log "Verifying claude installation..."
if ! command -v claude &> /dev/null; then
handle_error "claude could not be found after installation"
fi
}
# Install Claude Code
install_claude_code
log "Setting up installation directories..."
if [[ ! -d "$INSTALL_DIR" ]]; then
log "Creating installation directory at $INSTALL_DIR"
mkdir -p "$INSTALL_DIR" || handle_error "Failed to create installation directory"
fi
if [[ -d "$INSTALL_DIR/.claude" ]]; then
log "Moving existing $INSTALL_DIR/.claude folder to $INSTALL_DIR/.claude.bak"
rm -rf "$INSTALL_DIR/.claude.bak" 2>/dev/null || true
mv "$INSTALL_DIR/.claude" "$INSTALL_DIR/.claude.bak" || handle_error "Failed to backup existing .claude directory"
fi
# Create directories in parallel
mkdir -p "$INSTALL_DIR/.claude" &
CLAUDE_DIR_PID=$!
mkdir -p "$LITELLM_BIN_DIR" &
BIN_DIR_PID=$!
mkdir -p "$LITELLM_CACHE_DIR" &
CACHE_DIR_PID=$!
# Wait for all directory creation to complete
wait $CLAUDE_DIR_PID || handle_error "Failed to create .claude directory"
wait $BIN_DIR_PID || handle_error "Failed to create LiteLLM bin directory"
wait $CACHE_DIR_PID || handle_error "Failed to create LiteLLM cache directory"
log "Creating Claude settings..."
cat <<EOF > "$INSTALL_DIR/.claude/settings.json" || handle_error "Failed to create settings.json"
{
"apiKeyHelper": "$LITELLM_BIN_DIR/get-litellm-key",
"model": "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0",
"cleanupPeriodDays": 7,
"env": {
"ANTHROPIC_MODEL": "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0",
"ANTHROPIC_BASE_URL": "$LITELLM_PROXY_API_BASE",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "bedrock/us.anthropic.claude-opus-4-1-20250805-v1:0",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0",
"CLAUDE_CODE_SUBAGENT_MODEL": "bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0",
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS": "3600000",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "true",
"CLAUDE_CODE_SKIP_BEDROCK_AUTH": "1",
"CLAUDE_CODE_SKIP_VERTEX_AUTH": "1",
"DISABLE_TELEMETRY": "1",
"DISABLE_ERROR_REPORTING": "1",
"DISABLE_BUG_COMMAND": "1",
"DISABLE_COST_WARNINGS": "1",
"DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
"DISABLE_AUTOUPDATER": "1",
"LITELLM_CACHE_DIR": "$LITELLM_CACHE_DIR",
"LITELLM_COOKIE_NAME": "$LITELLM_COOKIE_NAME",
"LITELLM_PROXY_API_BASE": "$LITELLM_PROXY_API_BASE"
},
"permissions": {
"defaultMode": "acceptEdits"
}
}
EOF
log "Setting up temporary build directory..."
BUILD_DIR="/tmp/litellm-build-$$"
mkdir -p "$BUILD_DIR" || handle_error "Failed to create build directory"
log "Downloading source files in parallel..."
CACHE_BUST="$(date +%s)"
curl -s -o "$BUILD_DIR/get-litellm-key.ts" "https://gist.githubusercontent.com/alerma-sumo/74a91363a9714478bdf5d26612ae53c9/raw/get-litellm-key.ts?${CACHE_BUST}" &
TS_PID=$!
curl -s -o "$BUILD_DIR/package.json" "https://gist.githubusercontent.com/alerma-sumo/74a91363a9714478bdf5d26612ae53c9/raw/package.json?${CACHE_BUST}" &
JSON_PID=$!
# Wait for downloads to complete
wait $TS_PID || handle_error "Failed to download get-litellm-key.ts"
wait $JSON_PID || handle_error "Failed to download package.json"
# Function to install Playwright
install_playwright() {
log "Installing dependencies..."
cd "$BUILD_DIR" || handle_error "Failed to change to build directory"
bun install || handle_error "Failed to install dependencies"
log "Installing Playwright browsers..."
bun run install:playwright || handle_error "Failed to install Playwright chromium"
}
# Install dependencies and Playwright
install_playwright
log "Building standalone executable..."
bun run build || handle_error "Failed to build executable"
log "Installing executable..."
cp "$BUILD_DIR/dist/get-litellm-key" "$LITELLM_BIN_DIR/get-litellm-key" || handle_error "Failed to install executable"
chmod +x "$LITELLM_BIN_DIR/get-litellm-key" || handle_error "Failed to make executable"
log "Cleaning up build directory..."
cd / && rm -rf "$BUILD_DIR" || true
# Add LiteLLM bin directory to PATH if it's not already there
if [[ ":$PATH:" != *":$LITELLM_BIN_DIR:"* ]]; then
log "Adding $LITELLM_BIN_DIR to PATH..."
echo "export PATH=\"$LITELLM_BIN_DIR:\$PATH\"" >> ~/.zshrc
export PATH="$LITELLM_BIN_DIR:$PATH"
fi
log "Installation completed successfully!"
log ""
log "🎉 Claude Code with LiteLLM is now ready!"
log ""
log "Next steps:"
log " 1. Open a new terminal to pick up PATH changes"
log " 2. Navigate to any repository: cd /path/to/your/repo"
log " 3. Start Claude Code: claude"
log ""
log "Useful commands inside Claude Code:"
log " /init - Generate a CLAUDE.md file for your repo"
log " /ide - Install the Claude Code VS Code extension"
log " /help - Show available commands"
log ""
log "The API key helper will automatically authenticate with $LITELLM_PROXY_API_BASE"
log "Cached sessions are stored in: $LITELLM_CACHE_DIR"
{
"name": "litellm",
"version": "1.0.0",
"description": "LiteLLM API key helper for Claude Code with Bun integration",
"main": "get-litellm-key.ts",
"module": "get-litellm-key.ts",
"type": "module",
"scripts": {
"build": "bun build --compile --minify --bytecode --external playwright --outfile=dist/get-litellm-key get-litellm-key.ts",
"build:cross": "bun build --compile --minify --bytecode --external playwright --target=bun-linux-x64 --outfile=dist/get-litellm-key-linux get-litellm-key.ts",
"dev": "bun --hot get-litellm-key.ts",
"install:playwright": "bunx playwright install chromium",
"clean": "rm -rf dist"
},
"dependencies": {
"playwright": "^1.40.0"
},
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"keywords": [
"litellm",
"claude-code",
"bun",
"typescript",
"authentication",
"playwright"
],
"repository": {
"type": "git",
"url": "https://github.com/Sanyaku/analytics-gitops.git",
"directory": "packages/litellm"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment