Documentation

Everything you need to install, configure, deploy, and troubleshoot ringfence.

Quick start

60-second install on macOS or Linux:

curl -sSL https://ringfence.dev/install.sh | bash

The default install is zero-prompt and sets up the always-on configuration:

  • Installs fence + fence-proxy to /usr/local/bin (sudo) or ~/.local/bin (no-sudo fallback)
  • Writes ~/.config/ringfence/env.sh and adds a marker-bracketed source line to your shell rc (zsh/bash)
  • Registers a launchd / systemd unit so fence auto-starts on login + restarts on crash
  • Starts fence right away

Activate in your current terminal

Because shells inherit env vars at launch time, the terminal you ran the installer from doesn't yet see the new variables. Open a new tab — or paste:

source ~/.config/ringfence/env.sh

Verify

fence doctor

Every line should be ✓. If any line is ⚠ or ✗, the message tells you the exact fix.

Then just use claude / codex / aider as you normally would — they're now proxied through fence with budget enforcement.

Coverage — what fence captures (and what it can't)

ringfence is an HTTP proxy that intercepts API-key-authenticated calls to known LLM endpoints (Anthropic, OpenAI, Google Gemini). It cannot intercept OAuth-flow tools or proprietary backends — the auth tokens for those flows are scoped to the provider's own audience and don't accept a base URL override. Pick the right auth mode for the tools you care about and ringfence captures the spend; mismatched mode and your traffic bypasses the proxy silently.

Captured (set up an API key, point at the proxy)

  • Claude Code CLI — uses ANTHROPIC_API_KEY by default. Honours ANTHROPIC_BASE_URL. Works out of the box after fence enable-system-wide.
  • Codex CLI in API-key mode — set OPENAI_API_KEY=sk-… from platform.openai.com. Honours OPENAI_BASE_URL.
  • Aider, Continue.dev, Zed — when configured with raw API keys (Anthropic or OpenAI). All honour the standard base-URL env vars.
  • Cursor in BYOK mode — Settings → Models → paste your API key + tick "Override OpenAI Base URL" set to http://localhost:9000. Cursor's default routing (its own backend) is not captured.
  • Gemini CLI in API-key mode — set GEMINI_API_KEY from Google AI Studio (paid tier). The base-URL env var name varies by gemini-cli version; fence doctor surfaces the right one.
  • Anthropic / OpenAI / Google Generative AI SDKs in any language — Python, TypeScript, Go, Ruby. All honour the canonical base-URL env vars.

NOT captured (architectural limitation)

  • Codex CLI with "Sign in with ChatGPT" — uses your ChatGPT Plus/Pro subscription via OAuth. Calls go to chatgpt.com/backend-api/… (not api.openai.com). Symptom if you leave OPENAI_BASE_URL pointed at fence: stream disconnected before completion errors. Either get an OpenAI API key, or unset OPENAI_BASE_URL for that shell.
  • Gemini CLI default OAuth — Google sign-in flow with the free tier quota. Tokens are scoped to Google's audience and bypass the proxy entirely. Switch to GEMINI_API_KEY mode if you need budget enforcement.
  • Cursor's default routing — calls go to Cursor's backend, billed flat by Cursor (Pro $20/mo). There's no per-call API to cap. Use BYOK mode if you want itemised per-developer billing through ringfence.
  • Claude Desktop / claude.ai web — OAuth, no BYOK option. Spend lives on your Anthropic console subscription, not on a programmable API key.
  • Vertex AI (regional endpoints like *-aiplatform.googleapis.com) — different URL family + gcloud token auth. Not on fence-proxy's route table today; raise an issue if you need it.

Quick mental model

If your tool reads ANTHROPIC_API_KEY or OPENAI_API_KEY or GEMINI_API_KEY from your shell, fence captures it. If it logs you in via a browser, it doesn't.

Run fence doctor to see which AI tools are detected on your machine and which auth mode each is in — it flags the unsupported combinations explicitly.

How it works

The proxy

fence-proxy is a streaming HTTP proxy on localhost:9000. It accepts requests for Anthropic, OpenAI, and Google Gemini on a single port, dispatches by URL path, forwards to the real upstream, and records token-count metadata on the response.

Each request is checked against your daily and monthly budget before forwarding. When a budget is breached, the proxy returns 429 Too Many Requests with a clear Retry-After header — your AI tool stops mid-conversation, you don't get a surprise bill.

The privacy boundary

Prompts and completions never leave your machine. Only metadata (timestamp, model, token counts, cost in USD, tags) is sent to the cloud control plane — and only if you've enrolled (run fence login). Solo mode is fully local; no phone-home of any kind.

Multi-provider routing

Set these four environment variables (the install script does this for you via ~/.config/ringfence/env.sh):

export ANTHROPIC_BASE_URL=http://localhost:9000
export OPENAI_BASE_URL=http://localhost:9000
export OPENAI_API_BASE=http://localhost:9000
export GENAI_API_BASE=http://localhost:9000

Tools that read these env vars route through fence; tools that authenticate via OAuth or proprietary backends bypass it. See Coverage for the per-tool matrix and the auth-mode caveats.

Always-on by design

ringfence is your AI cost firewall. When fence is running, AI tools route through it. When fence is not running, AI tools refuse to connect — by design. A budget firewall whose failure mode is silent direct-to-API is worse than no firewall at all (no enforcement + the user thinks it's enforcing). Loud failure is the right signal: fence up is right there.

CLI reference

The full command list. Run fence with no arguments for the same list inline.

Lifecycle

fence init [--cloud-url URL]Write ~/.ringfence/config.toml. Idempotent.
fence up [-d|--detach]Start proxy + dashboard. -d for background.
fence downStop proxy + dashboard.
fence restartStop, then start detached.
fence psShow running components, ports, status, version.
fence logs [-f] [proxy|dashboard|all]Show logs. -f to follow.
fence daemonForeground supervisor (used by systemd / launchd).

Status + diagnosis

fence statusPrint current budget + recent activity.
fence doctorSelf-check: ports, env vars, shell init, cloud reachability.
fence version [--json]Print version. --json for machine-readable.

Configuration

fence budget set --daily $ --monthly $Set local budget caps.
fence tag [<name>|clear]Per-project tag stamped on every event.
fence providersList providers + their base URLs.
fence enable-system-wide / disable-system-wideToggle launchctl/environment.d env vars.

Cloud (paid plans)

fence login [--token <...>]Enrol this machine with a cloud team.
fence logoutRemove the agent token; back to solo mode.

Service supervision

fence service installRegister launchd / systemd unit. Auto-starts on login.
fence service uninstallRemove the unit.

Cleanup

fence reset [--yes] [--keep-config]Wipe local data. Stops fence first.
fence uninstall [--purge]Undo install.sh. --purge also wipes ~/.ringfence/.

Configuration

config.toml

Located at ~/.ringfence/config.toml, written by fence init. Idempotent — re-running won't clobber an edited file.

mode = "solo"            # "solo" (offline) or "team" (cloud)
proxy_port = 9000
dashboard_port = 9001
cloud_url = "https://ringfence.dev"

# upstream = "https://api.anthropic.com"   # optional Anthropic-only egress override

Environment variables

Read by both fence binaries at runtime. The install script writes these into ~/.config/ringfence/env.sh automatically.

ANTHROPIC_BASE_URLWhere Claude Code / Aider / Cursor send requests. Set to http://localhost:9000.
OPENAI_BASE_URLWhere Codex CLI / OpenAI SDKs send requests. Same value.
OPENAI_API_BASELegacy alias for OPENAI_BASE_URL. Set both for max compatibility.
GENAI_API_BASEWhere @google/genai SDK sends requests.
FENCE_LIVE_PORTOverride the dashboard port (default 9001).
RINGFENCE_HOMEOverride the data dir (default ~/.ringfence).

Headless install

For CI runners, Docker images, remote servers, and agentic-system runtimes — the install path that doesn't touch shell rc, doesn't register a service unit, and doesn't auto-start fence.

RINGFENCE_NO_SHELL_INIT=1 \
    RINGFENCE_NO_SERVICE=1 \
    RINGFENCE_NO_START=1 \
    curl -sSL https://ringfence.dev/install.sh | bash

Then in your service unit or Dockerfile, set the four base URLs explicitly and start fence-proxy directly:

systemd unit example

[Service]
Environment="ANTHROPIC_BASE_URL=http://localhost:9000"
Environment="OPENAI_BASE_URL=http://localhost:9000"
Environment="OPENAI_API_BASE=http://localhost:9000"
Environment="GENAI_API_BASE=http://localhost:9000"
ExecStart=/usr/local/bin/fence-proxy
Restart=on-failure
User=ringfence

Dockerfile example

FROM debian:trixie-slim
RUN curl -fsSL https://ringfence.dev/install.sh \
    | RINGFENCE_NO_SHELL_INIT=1 RINGFENCE_NO_SERVICE=1 RINGFENCE_NO_START=1 \
      bash
ENV ANTHROPIC_BASE_URL=http://localhost:9000 \
    OPENAI_BASE_URL=http://localhost:9000 \
    OPENAI_API_BASE=http://localhost:9000 \
    GENAI_API_BASE=http://localhost:9000
EXPOSE 9000 9001
CMD ["fence", "daemon"]

Override flags

Each environment variable above has a CLI flag equivalent on install.sh:

  • --no-shell-init — skip shell rc append
  • --no-service — skip service unit registration
  • --no-start — skip the post-install fence up -d
  • --prefix=DIR — override the install location
  • --version=X.Y.Z — pin to a specific version

Troubleshooting

First step in every case: fence doctor. It surfaces the half-dozen things that go wrong in practice, with the fix on the same line.

"I ran claude but the dashboard shows nothing"

Almost always: the shell you ran claude from doesn't have ANTHROPIC_BASE_URL set. Check with:

echo $ANTHROPIC_BASE_URL
# expected: http://localhost:9000

Empty? Source the env file in this shell:

source ~/.config/ringfence/env.sh

Or open a new terminal tab — the source line in your shell rc fires automatically on shell startup.

"fence login → Cloud ping failed"

Run fence login --token <token> again. The diagnostic line right above the failure message tells you why:

  • 401 invalid signature — token unknown to this cloud, or paste corrupted. Re-issue from the cloud's /tokens page and paste the exact <id>.<secret>::<hmac> string.
  • transport-error — DNS or network issue. Check cloud_url in ~/.ringfence/config.toml resolves.
  • 404 — reverse proxy in front of the cloud isn't routing /v1/agents/hello. Check Caddy / Nginx config.

Port 9000 or 9001 in use

Identify the offender:

lsof -i :9000
lsof -i :9001

If it's a stale fence from a previous run, fence down cleans up. If a service unit is bound, fence service uninstall.

macOS: "fence: killed" / SIGKILL on first run

Apple's TCC silently kills binaries with quarantine xattrs and broken codesignatures. The install script should handle this automatically (see adhoc_sign_macos). If it persisted:

xattr -dr com.apple.quarantine /usr/local/bin/fence
codesign -s - /usr/local/bin/fence

If codesign or xattr is missing, install Xcode Command Line Tools: xcode-select --install.

Uninstall

One command undoes everything install.sh added:

fence uninstall

It stops fence, removes the launchd / systemd service unit, removes the marker-bracketed source line from your shell rc (with a .ringfence.bak backup), and removes ~/.config/ringfence/env.sh.

What it doesn't remove (preserves your data + the binaries):

  • The fence + fence-proxy binaries on PATH — remove via your package manager or rm
  • ~/.ringfence/ (config.toml + ledger.db + auth.token) — pass --purge to also wipe these

Full clean revert:

fence uninstall --purge
sudo rm /usr/local/bin/fence /usr/local/bin/fence-proxy   # or ~/.local/bin if user-installed

Got stuck? Email us at hello@ringfence.dev.

These docs cover ringfence v1. The CLI's --help stays in sync with each release; if you find a discrepancy, the CLI is the source of truth.