SheLLM

Your LLM subscription — as a REST API for your own apps

A personal project for experimenting on the subscriptions you already pay for, through the official, unmodified CLIs — the lowest-risk way to do it.

export OPENAI_BASE_URL=http://127.0.0.1:6100/v1 OPENAI_API_KEY=$SHELLM_KEY
export ANTHROPIC_BASE_URL=http://127.0.0.1:6100 ANTHROPIC_API_KEY=$SHELLM_KEY
The Overview page: one active call of four in the request queue, both providers ready, and capacity for the last five and eighteen hours — spend, tokens and request counts, with a per-subscription breakdown for claude and codex.

What it is

A personal project: experiment with LLMs from your own software on the Claude Code or Codex subscription you already pay for, instead of buying API credit on top. SheLLM runs the official CLI behind one endpoint that answers in both the OpenAI and the Anthropic formats.

You already pay the subscription, so the marginal request costs nothing extra — and for Claude the CLI reports what it would have cost on the API, attributed to the key that made it. That is the figure above.

Every request starts the CLI and pays for it — about 0.9 s of a short call on the server, a cold start that is permanent. Streaming is what makes it usable: claude-sonnet starts writing at 1.7 s of a 5.5 s request (measured).

Inside a request

Four requests, played through SheLLM. Pick one and watch what it becomes on the way to the CLI and back.

Nothing here is typed by hand. The argv, the responses and the log rows come from running each request through the app against CLI output recorded from claude 2.1.273 and codex 0.154.0 — and a test fails the day any of it stops being true.

Request

SheLLM runs

The CLI prints

    Your app receives

      What you get

      Request logs

      Every request, with what it cost and why it failed.

      The Request Logs table: an error rate with its sparkline, total tokens and estimated cost for the last nineteen hours, then rows carrying time, status, route, client, provider, model, duration, tokens and cost — including a 429 rate_limited with its detail row expanded.

      API keys

      A key per app, with its own rate limit, models and spend.

      The API Keys page: the connection row with the base URL, then three keys with their prefix, requests per minute, allowed models, the browser origin one of them is scoped to, seven-day usage and cost, last use, status and expiry — the last one expired and open in edit, with its name, limits and an expiry in the dashboard's time zone.

      Playground

      Send a real request down the path your app takes.

      The Playground: a client key, the Anthropic /v1/messages format and the claude-fable model across one row, the prompt below them with the images line the Anthropic format shows, and underneath an answered response showing status 200, a 4.5 second round trip, tokens in and out, the model that answered and the request id, with links to the raw response and to copy the call as curl.

      Provider health

      CLI versions, circuit state and what is queued right now.

      The System page's provider cards: claude and codex, each with its CLI version, last use, last status, circuit state and the models it serves, and a Pause button.

      Built on it

      Knotty: a plywood bookcase in 3D with its height, width and depth marked, beside a chat where the carpenter offers changes such as making it 90 cm wide.

      Knotty

      Photos of a piece of furniture in, a plywood design out — then adjust it by talking to a carpenter.

      images · json_schema · from the browser
      AI Town: an isometric snowy village of twenty residents, a panel to write a proclamation and mark it true or false, and a row of resident portraits waiting to react.

      AI Town

      Twenty villagers hear a proclamation and each decides what to do. Same prompts, any model: compare the decisions, latency, tokens and cost.

      either format · many small calls

      Get started

      # Node.js 24 and a logged-in claude (or codex) on the same machine
      $ git clone https://github.com/rodacato/SheLLM.git && cd SheLLM
      $ npm install && npm link
      $ shellm init
      # prints the API key (shown once, store it now) — export it as SHELLM_KEY
      $ shellm start
      $ curl http://127.0.0.1:6100/v1/chat/completions \
          -H "Authorization: Bearer $SHELLM_KEY" \
          -H "Content-Type: application/json" \
          -d '{"model":"claude-haiku","messages":[{"role":"user","content":"Hello"}]}'
      
      {
        "id": "chatcmpl-9f3a41c6-0b7e-4d21-a5f8-1c2e7b904dd3",
        "object": "chat.completion",
        "model": "claude-haiku-4-5-20251001",
        "choices": [{
          "index": 0,
          "message": { "role": "assistant", "content": "Hello! How can I help you today?" },
          "finish_reason": "stop"
        }],
        "usage": { "prompt_tokens": 10, "completion_tokens": 9, "total_tokens": 19 }
      }
      import os
      from openai import OpenAI
      
      client = OpenAI(base_url="http://127.0.0.1:6100/v1", api_key=os.environ["SHELLM_KEY"])
      
      stream = client.chat.completions.create(
          model="claude-sonnet",
          messages=[{"role": "user", "content": "Hello"}],
          stream=True,
      )
      for chunk in stream:
          if chunk.choices:
              print(chunk.choices[0].delta.content or "", end="", flush=True)
      import os
      from anthropic import Anthropic
      
      client = Anthropic(base_url="http://127.0.0.1:6100", api_key=os.environ["SHELLM_KEY"])
      
      with client.messages.stream(
          model="claude-sonnet",
          max_tokens=1024,
          messages=[{"role": "user", "content": "Hello"}],
      ) as stream:
          for text in stream.text_stream:
              print(text, end="", flush=True)
      import OpenAI from 'openai';
      
      const client = new OpenAI({ baseURL: 'http://127.0.0.1:6100/v1', apiKey: process.env.SHELLM_KEY });
      
      const stream = await client.chat.completions.create({
        model: 'claude-sonnet',
        messages: [{ role: 'user', content: 'Hello' }],
        stream: true,
      });
      for await (const chunk of stream) {
        process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
      }
      import Anthropic from '@anthropic-ai/sdk';
      
      const client = new Anthropic({ baseURL: 'http://127.0.0.1:6100', apiKey: process.env.SHELLM_KEY });
      
      const stream = client.messages.stream({
        model: 'claude-sonnet',
        max_tokens: 1024,
        messages: [{ role: 'user', content: 'Hello' }],
      });
      stream.on('text', (text) => process.stdout.write(text));
      await stream.finalMessage();
      curl -sN http://127.0.0.1:6100/v1/chat/completions \
        -H "Authorization: Bearer $SHELLM_KEY" \
        -H "Content-Type: application/json" \
        -d '{"model":"claude-sonnet","stream":true,"messages":[{"role":"user","content":"Hello"}]}'

      Every snippet streams, and runs as it is with SHELLM_KEY set. The SDKs also read the two export lines at the top, if you would rather not pass the URL.

      Streaming, JSON Schema output and images behave the way the SDKs expect — what works today, each row a probe run against a live instance.

      When it does not work

      A setting you changed did nothing.The config file is read once, when the service starts. Restart it: sudo systemctl restart shellm on a server, Ctrl+C and shellm start again on your machine.
      A browser page is blocked by CORS.Calling /v1 from a page is off until SHELLM_CORS_ORIGINS names that exact origin — scheme, host and port, no trailing slash, and no wildcard.
      curl works and your app gets 403.That key is scoped to browser origins, and yours is not on its list. A request with no Origin is accepted, which is why curl still works.
      Anything else.shellm doctor checks the install and prints the fix beside whatever failed. shellm doctor --live spends one real request to prove the whole path.

      What it is not

      Not a way to share a subscription.One owner, their own apps.
      Not a backend for coding agents.They drive their own CLI already.
      Not an API-key gateway.LiteLLM does that better — how they differ.
      Not machine-scale.Quota is measured in human sessions.

      How it compares

      The most-used projects in this space, one per kind. They differ less in features than in how they reach the model — and that is what decides who carries the risk.

      LiteLLM

      API-key gateway

      Reaches the model through
      Paid API keys, across many providers.
      Built for
      Teams: budgets per team, user and key.
      Pick it when
      You pay per token and want one endpoint over all of it.

      CLIProxyAPI

      Subscription proxy

      Reaches the model through
      OAuth login to Claude Code and Codex, round-robin across several accounts.
      Built for
      Pooling subscriptions behind one API.
      The catch
      The login is used outside the official client — the route Anthropic's terms forbid.

      new-api

      Multi-tenant gateway

      Reaches the model through
      Upstream API keys, configured as channels.
      Built for
      Serving other people: quotas, subscriptions and billing per user.
      Pick it when
      You run a gateway for a team or for customers.

      SheLLM

      Subscription wrapper

      Reaches the model through
      The official, unmodified CLI, which keeps its own login.
      Built for
      One owner and their own apps. No sharing, no pooling.
      The catch
      Slower: every request starts the CLI. Codex still carries classifier risk.

      Fair use and provider terms

      SheLLM runs the official, unmodified CLI binaries. It never extracts OAuth tokens, never calls a provider's API with subscription credentials, and never pretends to be a different client. The remaining risk is yours.

      ProviderStatus
      Claude Supported. Anthropic forbids intermediating Claude.ai credentials; the official binary keeps its own login.
      Codex Supported, at your own risk. codex exec is documented for scripts; anti-abuse classifiers are the real exposure, and a suspension takes the whole ChatGPT account.
      Gemini Not supported. Gemini CLI stopped serving personal plans, and Antigravity's terms forbid it.