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.
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.
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
| Key | What it does |
|---|---|
Enter | Send. |
\ | A trailing backslash keeps you typing on a new line. Alt+Enter too, where the terminal sends it. |
Ctrl+C | Cancels 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+D | Quit, on an empty line. |
Tab | Completes a slash command, or opens the model picker on an empty line. |
Up / Down | Walk back through what you sent. |
PgUp / PgDn | Scroll the transcript. Esc returns to the live tail. |
Ctrl+L | Repaint, for when something else has written over the screen. |
Ctrl+A / E / U / K / W | The 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
| Command | What it does |
|---|---|
/model [name] | Set the model, or open the picker. |
/models | What the Gateway will serve, with prices. |
/spend [days] | What you have actually spent, from the usage API. Needs a key carrying usage:read. |
/receipt | Every 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. |
/new | Forget the conversation, keep the session totals. |
/clear | Clear the screen, keep the conversation. |
/help | Keys 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.
| Situation | What happens |
|---|---|
| Piped or redirected | Never 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 out | lobstack tui > log.txt runs a plain prompt loop: answers on stdout, prompts and receipts on stderr, so the file stays clean. |
TERM=dumb | A 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_COLOR | No 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 locale | Box drawing falls back to - and >. Force it with LOBSTACK_ASCII=1. |
| Resize | SIGWINCH drops the diff baseline and repaints whole, because every row's content depends on the width. |
| Narrow | 40 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 crash | The 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.
?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.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
| Command | What it does |
|---|---|
lobstack init | Saves 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 models | What the Gateway will serve, and at what price. |
lobstack spend --days 7 | What you have spent. Needs a key carrying the usage:read scope. |
lobstack proxy --port 8787 | A 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.
/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.
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.