Skip to content

Start here

CLI

One key, every model, and what each call cost — with a terminal UI that shows it as it happens. Zero dependencies, so there is no tree to resolve before it starts.

The package has no dependencies, because fetch, node:http and node:readline are already in the runtime — so it starts rather than resolving a tree first, and there is no supply chain between your key and us. That did not change to add a full-screen interface: the escape-sequence decoder is already in node:readline, and the rest of the UI is nine escape sequences written out by hand. A process holding an lsk_live_ credential does not get to pull a dependency tree to draw a box.

bash
npx -y lobstack@0.1.1   # run it without installing
npm install -g lobstack      # or put it on PATH

lobstack init
lobstack             # the UI
lobstack chat "explain a b-tree in two sentences"

The published version is 0.1.1. The version below it was published and unpublished within nine minutes on 2026-09-11, and npm keeps a permanent tombstone for an unpublished version — so that number can never exist again under this name. A lockfile or a tutorial pinning it will 404 forever; ask for latest.


The UI

lobstack with no arguments opens it, when — and only when — there is a terminal on both ends and a key already available. In a pipeline, in CI, or on a first run before init, bare lobstack prints the same help it always printed, which is also the screen that tells you to run init. lobstack tui asks for it by name and is the stable spelling for a script or a shortcut.

80 columnstext
 lobstack  auto                             2 calls  $0.002200  saved $0.008800
───────────────────────────────────────────────────────────────────────────────
 you explain a b-tree

 lob A B-tree keeps sorted data in a shallow, wide tree, so a lookup touches
     very few nodes even when the table is enormous. Each node holds many keys
     and many child pointers, which is what keeps the height down to three or
     four levels for tables with billions of rows.
───────────────────────────────────────────────────────────────────────────────
 model claude-haiku-4-5  asked claude-opus-5
 tokens 400/140  cost $0.001100  saved $0.004400  in 123ms
   against claude-opus-5, the model you named
›
 ^C quit   Tab model   PgUp scroll   ^L redraw   /help for the rest

The bottom two panes are the reason it exists. A chat window on its own is worth nothing — every tool has one. What you cannot get anywhere else is the price of the call you just made, pinned under the conversation, and a running total in the corner that moves while you work. When rows run short they come off the transcript, not off the receipt: you can scroll back for history, and you cannot scroll back for a price you never saw.

Keys

KeyWhat it does
EnterSend.
\A trailing backslash keeps you typing on a new line. Alt+Enter too, where the terminal sends it.
Ctrl+CCancels a streaming answer; quits when nothing is streaming. Cancelling first matters — the keystroke meant to stop a runaway answer should not also throw away the receipt for tokens you have already been billed for.
Ctrl+DQuit, on an empty line.
TabCompletes a slash command, or opens the model picker on an empty line.
Up / DownWalk back through what you sent.
PgUp / PgDnScroll the transcript. Esc returns to the live tail.
Ctrl+LRepaint, for when something else has written over the screen.
Ctrl+A / E / U / K / WThe readline line edits you already know.

Shift+Enter is not bound. Most terminals send nothing a program can tell apart from a plain Enter, and promising a key that silently does the wrong thing is worse than not having it.

Slash commands

CommandWhat it does
/model [name]Set the model, or open the picker.
/modelsWhat the Gateway will serve, with prices.
/spend [days]What you have actually spent, from the usage API. Needs a key carrying usage:read.
/receiptEvery field of the last receipt, verbatim, plus the session tally.
/proxy [port]Serve the OpenAI-compatible endpoint from this process. Point Cursor or Aider at the port and every call those tools make appears in this transcript with its price, in the same running total as what you type by hand.
/newForget the conversation, keep the session totals.
/clearClear the screen, keep the conversation.
/helpKeys and commands, on screen.

Any terminal, and every way out of one

The UI is the interesting case. The boring ones are where a terminal application usually breaks, so they are all handled rather than assumed away.

SituationWhat happens
Piped or redirectedNever draws. echo "hi" | lobstack > out.txt treats stdin as the prompt and puts the answer, and only the answer, in the file.
Keyboard in, file outlobstack tui > log.txt runs a plain prompt loop: answers on stdout, prompts and receipts on stderr, so the file stays clean.
TERM=dumbA dumb terminal has no cursor addressing, so a full-screen frame is not a degraded experience — it is garbage on the wire. Same plain loop, and it says which of the three reasons it fell back for.
NO_COLORNo escapes at all. Colour is a depth — 0, 16, 256 or truecolour, detected from TERM and COLORTERM — and the frame is assembled the same way at every one of them.
No UTF-8 localeBox drawing falls back to - and >. Force it with LOBSTACK_ASCII=1.
ResizeSIGWINCH drops the diff baseline and repaints whole, because every row's content depends on the width.
Narrow40 columns is the width it aims at. Below that the layout stacks instead of tabulating, and no figure is ever truncated.
Ctrl+C, SIGTERM, SIGHUP, a crashThe terminal comes back. Every exit path runs the same restore, and a crash still prints its stack.

--force draws the UI where isTTY says there is no terminal but a person is watching anyway: a wrapper, docker run without -t, an unusual runner.

Mouse reporting is never switched onIt breaks click-to-select in JetBrains and in some tmux configurations, and a process that dies before sending the disable sequence leaves your shell reading mouse packets as keystrokes. Nothing here needs a mouse. The same reasoning picks every other sequence: the alternate screen is entered with ?1049 rather than the older ?47 pair, synchronised output and focus reporting are not used at all, and the last column of every row is left unwritten so a terminal that wraps eagerly cannot scroll the frame out from under itself.
Model output is sanitised before it is drawnA completion is attacker-influenced text about to be pasted into a terminal. Left alone, an ESC[2J inside an answer wipes the frame and an OSC sequence rewrites your window title. Escapes are removed, not rendered.

Commands outside the UI

CommandWhat it does
lobstack initSaves a key to ~/.lobstack/config.json at mode 0600. Verifies it against the Gateway before writing — an unusable key on disk just moves the failure to the next command.
lobstack chat "..."One call, streamed, with the receipt. Answer to stdout, receipt to stderr.
lobstack modelsWhat the Gateway will serve, and at what price.
lobstack spend --days 7What you have spent. Needs a key carrying the usage:read scope.
lobstack proxy --port 8787A local OpenAI-compatible endpoint. See below.

Flags: --model (default auto), --key, --base, --json, --force. LOBSTACK_API_KEY and LOBSTACK_BASE_URL both win over the saved config, because CI has no home directory worth writing to.

The answer goes to stdout and the receipt to stderr, so lobstack chat "..." > out.txt gives you the answer alone while you still watch what it costs.


The proxy

The shortest path from “I have a tool that speaks OpenAI” to routing and metering. Change one base URL and every call that tool makes goes through the router and lands in your Console, with a line per request saying what it cost. The tool never sees your Lobstack key; the proxy process holds it.

bash
lobstack proxy

# then, in whatever you are pointing at it:
OPENAI_BASE_URL=http://127.0.0.1:8787/v1
OPENAI_API_KEY=anything

/proxy inside the UI is the same server, with the per-request receipts going into the transcript and the session total instead of to stderr.

It binds loopback, and that is deliberateThis is a process holding a credential that answers unauthenticated requests, so anything able to reach the port can spend on your account. It listens on 127.0.0.1 only. Do not put it behind a tunnel or bind it to a network interface without putting your own authentication in front of it.

Cost is read, never computed

The Gateway puts the price on the final SSE frame under x_lobstack, because on a streamed response the headers are written before the provider has counted a token. The CLI reads that number.

It does not multiply token counts by a bundled rate card. Our own desktop client did exactly that and printed $0.00 for three months next to a correct invoice, because its copy of the card knew six models and the Gateway serves far more. Metering has the field list.

That is also why the UI shows no running dollar figure during a stream. Until the last frame lands there is no price to show, so it shows elapsed time and how much text arrived, and says the price is still coming.

A null cost is not a zero

cost_usd is null, never 0, when a call could not be priced, and the CLI prints unpriced. A zero renders as “free”, and writing off a real charge is the most expensive way to be wrong about money. The same rule governs the session total in the UI: a session whose calls were all unpriced reads unpriced, not $0.000000, and a session with some of each shows the priced total and counts the rest out loud — +1 unpriced.

Nor is a figure ever truncated to fit a narrow terminal. $0.004400 clipped to $0.004 is not a shorter number, it is a wrong one, so labels and then whole fields drop before a figure does.

“Saved” and “vs ceiling” are different claims

The receipt above says saved because you named Opus 5 and the router served something cheaper — a like-for-like comparison. Send auto and the Gateway measures against the most expensive model your plan allows instead, which is a real comparison but not one you asked for. baseline_reason on the response says which, and the CLI labels it vs ceiling and names the model it measured against. The UI keeps two separate running totals for the two and never adds them together. The distinction is on Metering & cost.


Why it rewrites the host

Point --base at the bare domain and the CLI corrects it to www and prints a line saying it did. RFC 9110 requires every HTTP client to drop Authorization when a redirect changes host, so the apex would answer a perfectly good key with “missing credentials”.

It is announced rather than silent because your own code will hit the same wall, and a fix you never saw teaches you nothing about why.


Getting a key

/start mints one, hands you the command, and shows you the receipt when your first call lands. Free, no card, about a minute.

Lobstack

One key over every frontier model, a receipt on every call, and an agent that waits before it changes anything.

© 2026 LobstackAll rights reserved  Status