npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fest-gateway

v0.7.1

Published

Self-hosted Claude Code gateway: per-user subscription pass-through, central token/cost monitoring, model routing.

Readme

Fest

A self-hosted gateway for Claude Code. Teams point their Claude Code at Fest the way they'd point at Bedrock, so one place can monitor requests, tokens and cost — and route to different models without Claude Code knowing the difference.

The distinguishing goal: Claude Max / Team subscription support. Each developer authenticates with their own subscription. Fest relays that credential and never stores it. Per-user pass-through, never pooling.

Three ways to read this, depending on what you want:

| | | | --- | --- | | Use it | Someone runs a Fest; you want Claude Code going through it. | | Run it | You are standing one up for a team. | | How it works | What it can see, what it keeps, and what will bite you. | | Develop it | Changing Fest itself. |


Use it

Install the CLI. The package is fest-gateway; the command it installs is fest, because plain fest was already taken on npm.

npm install -g fest-gateway

Sign in to your team's gateway, then start a session:

fest login --server https://fest.corp.test   # opens a browser, stores a token in ~/.fest
fest claude                                   # runs `claude`, environment set up for you

That's it. fest claude spawns the real claude binary with the environment already pointed at the gateway, on your own Anthropic subscription — Fest meters the usage and never sees your Anthropic credential at rest.

If your browser is already signed in to the dashboard, fest login is a single approval click; otherwise it shows the gateway's login screen first.

Other commands:

fest whoami     # what's logged in, and whether the token is still live
fest logout     # forget the local token (does not revoke it server-side)
fest claude gw  # force org credentials + the gateway's extra models
fest codex      # OpenAI Codex CLI, on its own ChatGPT login or API key
fest copilot    # GitHub Copilot CLI, on its own Copilot login
fest tags set team=platform   # label usage; saved in the repo (package.json or .fest.json), -g: everywhere

Codex and Copilot CLI are metered into the same dashboard, labelled by client. Codex on a ChatGPT login and Copilot CLI on its own login are relayed the way a Claude subscription is: your plan pays, and your credential is forwarded, never stored. Copilot can also run in its bring-your-own-key mode instead. See docs/CODEX.md and docs/COPILOT.md.

Without installing anything, npx fest-gateway login … does the same job.

If your operator hasn't set up OAuth

They can mint you a token by hand instead, and you point Claude Code at it directly:

env -u ANTHROPIC_API_KEY -u ANTHROPIC_AUTH_TOKEN \
  ANTHROPIC_BASE_URL=https://fest.corp.test/t/<token> claude

Never set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN yourself. Either one silently drops you off your own subscription onto whatever credential the server holds. fest claude clears them for you; the raw form above does it with env -u.


Run it

Getting started

An owner account must exist before Fest will serve on anything but loopback, so that comes first:

docker compose run --rm fest admin create [email protected]    # password shown once
docker compose up -d

Then either let developers self-serve with fest login (configure OAuth below), or mint tokens for them:

docker compose run --rm fest token create [email protected] laptop   # shown once

Put Fest behind your own TLS proxy and set FEST_SECURE_COOKIES=1 when you do: the server only ever sees plain HTTP and cannot detect this for itself.

Accounts

The dashboard needs a sign-in: scrypt passwords, an opaque session cookie hashed at rest, an origin check on every mutating endpoint, and an audit log of administrative actions. Accounts are granted from the host with fest admin create — there is no self-registration and no email reset, because a gateway should not need mail credentials to run.

Before anyone has run fest admin create, Fest serves the dashboard open on loopback and refuses to start on any other interface. A single-developer trial needs no setup, and "bound it to 0.0.0.0 and forgot the password" cannot happen quietly.

docker compose run --rm fest admin create <email> [--role owner|admin|member]
docker compose run --rm fest admin list | admin passwd <email> | admin disable <email>
docker compose run --rm fest token list | token revoke <id>

Configuration

| | | | --- | --- | | FEST_PORT, FEST_HOST | listen address. Default 127.0.0.1:8787. | | FEST_DB, FEST_USAGE_LOG | SQLite path, and the greppable JSONL trail. | | FEST_REQUIRE_IDENTITY | refuse unattributed requests. Off by default; on for a team. | | FEST_SECURE_COOKIES | set behind HTTPS. | | FEST_ROUTES | routing table path. Absent means everything passes through. See docs/ROUTING.md. | | FEST_UPSTREAM_BASE_URL, FEST_LOG_LEVEL | | | FEST_WORKERS | HTTP worker processes: a number, or auto for one per CPU the process may use (a container's CPU limit counts). Default 1; the Docker image sets auto. See More than one core. | | FEST_BEDROCK_BASE_URL | opens the /bedrock mount, relaying Claude-in-Bedrock traffic on the caller's AWS credential. Must include Bedrock's /anthropic segment. Unset means the mount refuses. | | FEST_OPENAI_BASE_URL, FEST_CHATGPT_BASE_URL | where the /openai (Codex on an API key) and /chatgpt (Codex on a ChatGPT login) mounts relay. Default https://api.openai.com and https://chatgpt.com; off refuses. See docs/CODEX.md. | | FEST_COPILOT_BASE_URL, FEST_COPILOT_MCP_BASE_URL | where the /copilot/<plan> mount (Copilot CLI on its own login) and the root /mcp relay (its built-in GitHub MCP server) go. Default https://api.{plan}.githubcopilot.com and https://api.githubcopilot.com; off refuses. See docs/COPILOT.md. | | FEST_PRICING_REFRESH | set 0 to never fetch prices. Rates then come only from the vendored snapshot, which is always sufficient. | | FEST_PRICING_URL | internal mirror of litellm's price file. | | FEST_PUBLIC_URL | base URL Fest is reachable at. Builds the OAuth redirect URI, and its path sets the mount point. Defaults to http://<host>:<port>. | | FEST_BASE_PATH | mount path, if Fest is not at the root of its origin. Defaults to the path of FEST_PUBLIC_URL, so setting that is usually enough. | | FEST_GOOGLE_CLIENT_ID, FEST_GOOGLE_CLIENT_SECRET | Google OAuth, for fest login and the dashboard. Absent means Google sign-in is off. | | FEST_GITHUB_CLIENT_ID, FEST_GITHUB_CLIENT_SECRET | GitHub OAuth, same idea. | | FEST_ALLOWED_EMAIL_DOMAINS | comma-separated. Required to enable OAuth at all — empty refuses every OAuth login rather than allowing any Google/GitHub account. | | FEST_DASHBOARD_URL | dev only. Where the dashboard is when it isn't this server; npm run dev puts it on Vite's port. Leave unset in production. |

For the full OAuth setup, see docs/CLI-AUTH.md.

More than one core

One process relays a lot of streams, because relaying is mostly waiting on the network. When it isn't enough, set FEST_WORKERS (or auto). fest serve then becomes a small primary that forks that many HTTP workers onto the same port with node:cluster.

  • One writer. SQLite takes one writer at a time, and a process waiting for the lock stalls every stream it relays. So workers send their usage to the primary, which serves no HTTP and is the only process that writes metering. It also runs migrations, retention and the price refresh, exactly once.
  • One host, one local disk. Every process opens the same database file in WAL mode, which needs shared memory. Don't put /data on NFS, EFS or any other network filesystem. Spreading across machines needs a different database, which Fest doesn't support yet.
  • A crash costs one worker's streams. The primary respawns it, backing off if it keeps crashing. Stopping the container drains every worker the same way a single process drains.
  • Per worker, not shared: the login throttle counts attempts per worker, so the effective limit is that many times higher. /healthz reports the worker that answered. Log lines from a worker carry its worker id.

Behind a reverse proxy, on a path

Fest can be mounted under a path rather than on its own hostname. Set FEST_PUBLIC_URL to the full URL including that path and Fest takes the mount point from it: the dashboard's <base href> is rewritten to match, and the OAuth redirect URI is built against it.

fest.corp.test {
	handle_path /fest/* {
		reverse_proxy 127.0.0.1:8787 {
			# SSE: /api/live is a long-lived event stream, and a buffering
			# proxy turns the live screen into a screen that updates once.
			flush_interval -1
		}
	}
}
FEST_PUBLIC_URL=https://fest.corp.test/fest
FEST_SECURE_COOKIES=1

handle_path strips the /fest prefix before proxying. A plain handle + reverse_proxy, which does not strip it, works too — Fest removes its own mount path from an incoming request when it is still there, so either spelling routes. Caddy passes the original Host upstream by default, which the CSRF origin check needs; on nginx that is proxy_set_header Host $host, plus proxy_buffering off for the event stream.

The registered OAuth redirect URI must include the path — https://fest.corp.test/fest/api/auth/oauth/google/callback. Developers point both Claude Code and the CLI at the full prefixed URL.

Amazon Bedrock, on the developer's own AWS credential

Claude Code can talk to Claude in Amazon Bedrock directly, and Fest can sit in the middle of that without holding an AWS credential at all. The developer's own short-lived bearer is forwarded and forgotten — exactly the property the subscription path has — and Fest still meters, attributes and prices the traffic.

Server side, one variable and a restart:

FEST_BEDROCK_BASE_URL=https://bedrock-mantle.us-east-1.api.aws/anthropic

That opens a /bedrock mount. Developers point Claude Code's Bedrock client at it, after their identity prefix:

CLAUDE_CODE_USE_MANTLE=1
ANTHROPIC_BEDROCK_MANTLE_BASE_URL=http://127.0.0.1:8787/t/<their-token>/bedrock
AWS_BEARER_TOKEN_BEDROCK=<minted with aws-bedrock-token-generator — 12 hours, maximum>

Nothing else changes: Bedrock's mantle endpoint serves the ordinary Messages API with ordinary SSE, so the request is a byte relay. Model ids are Bedrock's own (anthropic.claude-opus-5), sent by the client, and priced from litellm's Bedrock rates — including the 10% premium on a regional inference profile like us.anthropic.claude-opus-5, because that is what AWS invoices.

Why the developer holds the credential, and not the server. Bedrock bearer tokens last 12 hours at most. Fest resolves credentials from {env:NAME} only, and nothing can rewrite a running process's environment, so a server-held Bedrock token would mean restarting the gateway twice a day. The client can refresh its own: awsAuthRefresh and awsCredentialExport are Claude Code credential helpers, and the client maps both to the mantle base URL. The renewal loop lives next to the SSO session, which is the only thing that can mint a token non-interactively anyway. A useful side effect: AWS sees each developer's own identity, so CloudTrail attributes calls to a person rather than to one shared token.

The corollary is that this is not a way to give developers Bedrock access they do not already have. Each one needs an AWS identity permitted to call Bedrock. If that is the thing you are trying to avoid, Fest would have to sign SigV4 itself — a real bedrock adapter, not a config change, and not built.

Five things worth knowing:

  • The routing table gets no vote here. A /bedrock request is always a pass-through on the caller's own credential. A rule matching anthropic.claude-* cannot divert it onto a server-held key, because the mount never enters the routing fork at all — asserted in test/bedrock-http.test.ts.
  • It is org spend, recorded honestly. Rows land as inbound_key with basis list: the caller's own key paid, AWS bills the org, and the dashboard says so rather than counting it as a subscription that cost nothing.
  • FEST_BEDROCK_BASE_URL must include Bedrock's /anthropic segment, since that is genuinely part of the region's base URL. Fest concatenates rather than resolving, so the segment survives.
  • Requests to the mount are refused, not redirected, when it is unset. A 501 naming the variable, because falling through would send an AWS bearer to api.anthropic.com and return a 401 that explains nothing.
  • Start these sessions with claude, not fest claude. The CLI strips every variable that demotes a developer off their subscription, and CLAUDE_CODE_USE_MANTLE is one of them — so fest claude would quietly put a Bedrock-configured shell back on the developer's own plan. That is the right default everywhere else; here it is the wrong command.

Two Bedrock things Fest does not do. Non-Claude models on Bedrock, and Claude on the older InvokeModel/Converse integration (CLAUDE_CODE_USE_BEDROCK rather than CLAUDE_CODE_USE_MANTLE), both use a different wire shape — /model/{id}/invoke, AWS eventstream framing, model moved out of the body. Neither is a byte relay, so neither is a config change.


How it works

Why this is possible

Claude Code's inference client attaches the subscription OAuth bearer with no host check against ANTHROPIC_BASE_URL. Point it at Fest while logged in with Max/Team and Fest receives Authorization: Bearer sk-ant-oat…. Confirmed both by reading the binary and by running it — see docs/PHASE0-RESULTS.md.

Token refresh happens client-side against api.anthropic.com, outside ANTHROPIC_BASE_URL. So Fest forwards and forgets: it never stores, refreshes or holds a subscription credential. That is a genuine security advantage over any "store the team's key" design, not a side effect.

Two things that will bite you

1. ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN silently kill the subscription. Claude Code demotes off OAuth if any Anthropic-issued credential is present — including an apiKeyHelper. So Fest's identity token never goes in those variables; it rides in the URL path prefix or a custom header. A design that ignores this moves a whole team off their subscriptions onto a server-held key, invisibly.

2. This is supported-by-absence, not by design. There is no documented path for relaying a subscription bearer through a third-party gateway. It works because no guard exists. Treat it as a version-pinned dependency, and settle the policy question with Anthropic before a team-wide rollout.

That dependency is enforced rather than remembered: npm run canary re-runs both load-bearing Phase 0 experiments against the installed Claude Code release and refuses the rollout if either has changed. See docs/CANARY.md; verdicts are recorded per version in docs/canary-history.jsonl.

Worth knowing: Anthropic ships its own self-hostable claude gateway with central metering, pricing and managed policies. It authenticates via OIDC and holds provider credentials itself, so it cannot do subscription pass-through — but if that requirement ever softens, it is the supported product.

What Fest can see, and what it keeps

This is a gateway every prompt a team writes passes through, so it is worth being exact rather than reassuring.

Fest sees everything, because it has to. A request body arrives at this process in full: prompts, file contents, tool results, the lot. That is unavoidable for anything sitting on the wire.

Fest keeps none of it. Request bodies are relayed as opaque bytes and never parsed for storage. What is written down is metadata: who, when, which model, how many tokens in each of the four buckets, latency, HTTP status, error type, and the session id Claude Code already sends. There is no column anywhere in the schema for prompt or completion text — not disabled, absent — so "turn off content capture" is not a setting that could be misconfigured.

No credential is persistable, by construction rather than by policy. A subscription bearer is forwarded and forgotten; there is no table to put one in. What is stored of a Fest identity token or a dashboard password is a hash. The leak tests grep the entire database, every log line and every API response for secret-shaped strings.

An admin can see who used what, and when. That is the point of a team gateway, and developers pointing their editors at it should be told so plainly. They cannot see each other: a member account sees only its own traffic, enforced in the query layer rather than in the UI.

Raw per-request rows age out after 30 days. The hourly rollups, which are aggregate, are kept for 400, so a year of trends does not require a year of per-developer detail.

Design commitments

  • Byte-for-byte pass-through. Subscription tokens are validated against request shape, so the body is relayed as opaque bytes — never parsed and re-serialised. No model rewriting or prompt shaping on that path.
  • cache_control forwarded verbatim. Stripping it silently destroys prompt caching and inflates every developer's token bill.
  • The four token buckets are disjoint. input_tokens excludes cache reads and cache writes; context size is the sum. Folding cache reads into input is an order-of-magnitude error on a cache-heavy agent workload — and Claude Code is one.
  • null cost means unavailable, never zero. A subscription request is real usage with no org spend; an unknown model is unpriced, not free. Totals therefore render honestly as $12.3456 (+3 n/a). See docs/PRICING.md.
  • For a subscription developer, quota is the scarce resource, not dollars. Anthropic returns 5h/7d utilisation on every response, so that is what the dashboard shows rather than an invented dollar figure.

Develop it

Node >= 26. There is no build step for the server — Node runs the TypeScript directly via native type stripping. Only the dashboard is built, and only the published npm package is compiled (Node refuses to strip types under node_modules).

npm install
npm run dev        # gateway (node --watch) + dashboard (Vite, HMR), one terminal
npm test           # node --test
npm run typecheck  # tsc --noEmit, root then web/
npm run demo       # synthetic traffic in a scratch database, then dev
npm run canary     # version gate: re-run before rolling out a Claude Code release

Both npm test and npm run typecheck must be clean before committing. From a clone the CLI runs straight off the TypeScript: npm run cli -- login … (note the --).

Layout

server/bin/fest.ts       CLI entrypoint: serve | migrate | token | admin | seed |
                                         login | whoami | claude | logout
server/pipeline/         passthrough (byte-for-byte) — the subscription path
server/http/             sse parser, tee, header discipline, errors, routing
server/store/            SQLite schema, write path, identity tokens
server/usage/            accumulator, pricing, nullable cost algebra
server/secret/           credential classification + redaction
server/auth/, server/api/  sessions, OAuth, the dashboard's JSON API
cli/                     the developer half of the CLI — no DB access, ever
shared/                  contracts both halves agree on, and the env vars that
                         silently kill subscription auth
web/                     the dashboard (Vite + React) — the only build step
tools/                   dev runner, canary, Phase 0 capture, price sync
test/                    node --test, flat

There is one CLAUDE.md per top-level folder with the conventions that apply inside it — read the relevant one before working there.

Docs

| | | | --- | --- | | CLI-AUTH.md | OAuth setup and the fest login flow end to end | | ROUTING.md | the routing table format and the substitute path | | PRICING.md | where rates come from, and spend vs. value | | CODEX.md | Codex CLI through the /openai and /chatgpt mounts | | COPILOT.md | Copilot CLI on its own login through /copilot, what happens to the GitHub token, and BYOK | | TAGS.md | tagging usage by team, project or anything else, and filtering the dashboard by it | | POSTURES.md | subscription vs. key posture | | CANARY.md | the version-upgrade gate | | PHASE0.md · PHASE0-RESULTS.md | the gating experiment, and what it found | | CLIENT-GATEWAY-PROTOCOL.md | what Claude Code actually sends | | FRONTEND.md | the dashboard's screens and data flow |

Releasing

Bump version in package.json, then publish a GitHub Release tagged v<version>. CI publishes to npm over trusted publishing (OIDC) — there is no token in the repository — and refuses to publish if the tag and package.json disagree.