Oolong π΅

Simple ephemeral chat β a fast, keyboard-driven terminal client for OpenAI models, built with Bubble Tea.
- Ephemeral by design β conversations live in your terminal and nowhere else. Nothing is written to disk unless you save a transcript, and OpenAI's server-side response storage is switched off. Close the window and the chat is gone.
- Any OpenAI-compatible endpoint β the official API works out of the box, or point
base_url at Ollama, LM Studio, or OpenRouter and give local models the same polished UI.
- Scriptable β
git diff | oolong "write a commit message" streams the answer straight to stdout, no TUI, so Oolong drops into any shell pipeline.

Features
- Streaming responses rendered as markdown with syntax-highlighted code blocks
- Ephemeral by design β history is kept in memory only, and requests are sent with response storage disabled on OpenAI's side
- Model picker with per-model pricing, plus a live token count and cost estimate in the chat header
- Mid-chat model switch β
esc back to the picker keeps the conversation, so you can escalate to a bigger model halfway through
- Image input β paste an image from the clipboard (
ctrl+v) and it's attached to your next message
- File attachments β
ctrl+f picks an image or text file from disk to send with your next message
- One-shot mode β
oolong "question" (or cat main.go | oolong "explain") streams the answer to stdout with no TUI, so Oolong works in scripts and pipelines
- OpenAI-compatible endpoints β point
base_url at Ollama, LM Studio, OpenRouter, or any compatible server, globally or per model
- Context meter β the chat header tracks how much of the model's context window the conversation fills, and warns as it nears the limit
- System prompt editing in place (
ctrl+p), without losing your message draft
- Transcript export & resume β
ctrl+s saves the conversation as a timestamped markdown file; oolong --resume <file> picks it back up later
- Configurable β an optional TOML config file sets a custom model catalog, a default model, reasoning effort and verbosity, endpoints, transcript directory, and accent color
- Keychain storage β your API key lives in the OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service), not in a dotfile
- Readable math β LaTeX in responses is converted to plain Unicode instead of showing up as mangled backslashes
Install
Homebrew (macOS)
brew install --cask dicedatalore/tap/oolong
With Go
go install github.com/dicedatalore/oolong@latest
Works on macOS, Linux, and Windows with Go 1.26+. Clipboard image paste needs cgo β a C compiler, plus the X11 development headers on Linux:
sudo apt install libx11-dev # Debian/Ubuntu
Without cgo the build still works; image paste is simply disabled.
From source
git clone https://github.com/dicedatalore/oolong.git
cd oolong
go build
./oolong
Prefer a standalone binary? Prebuilt archives for macOS, Linux, and Windows are on the releases page. On Windows, run Oolong from Windows Terminal β the legacy console doesn't render TUIs well.
Getting started
- Run
oolong.
- On first run, paste your OpenAI API key. It's validated against the API (no tokens spent) and saved to your OS keychain. Alternatively, set
OPENAI_API_KEY in your environment β it takes precedence over the keychain.
- Pick a model and start chatting.
To remove a stored key: press ctrl+k on the model picker, or run oolong --reset-key.
Configuration
Oolong is fully usable with no configuration. To customize it, run oolong config init to scaffold a commented ~/.config/oolong/config.toml ($XDG_CONFIG_HOME is respected); every key is optional:
default_model = "gpt-5.6-terra" # skip the picker on launch
transcript_dir = "~/notes/chats" # OOLONG_TRANSCRIPT_DIR still wins
accent = "#FFAF87" # primary accent color
# base_url = "http://localhost:11434/v1" # any OpenAI-compatible endpoint
# Replaces the built-in model catalog when present. Any model your API key
# can access works β entries are checked against the API and unavailable
# ones are hidden from the picker.
[[models]]
id = "gpt-5.4"
description = "Previous generation"
input_rate = 1.25 # USD per 1M tokens, both optional
output_rate = 10.00
reasoning_effort = "medium" # gpt-5.6 takes none | low | medium | high | xhigh
verbosity = "low" # low | medium | high
context_window = 400000 # tokens; shows a ctx meter in the chat header
# base_url = "" # per-model endpoint, overrides the global one
For a single run, oolong --model <id> opens a chat directly with any model your key can access, overriding default_model.
reasoning_effort and verbosity set the model's default Responses API parameters. They're passed through as-is β the supported values vary by model generation, and the API reports clearly if a model rejects one. On the model picker, β/β adjust the selected model's effort for the session, shown in the list item and later in the chat header. A malformed config never blocks launch β Oolong falls back to defaults and shows what it ignored.
OpenAI-compatible endpoints
base_url points Oolong at any server that speaks the OpenAI API β Ollama, LM Studio, OpenRouter, and friends. Set it globally, or per model to mix endpoints in one catalog. Local endpoints need no API key; on custom endpoints Oolong skips the OpenAI-specific key validation and model availability check. The OPENAI_BASE_URL environment variable overrides every configured endpoint.
Scripting
Positional arguments (or piped input) skip the TUI entirely and stream the answer to stdout:
oolong "why is the sky blue"
cat main.go | oolong "explain this file"
git diff | oolong "write a commit message"
One-shot mode uses --model / default_model (falling back to the catalog's first entry) and the same key, endpoint, and reasoning settings as the TUI.
Resuming a chat
Transcripts saved with ctrl+s can be picked back up later:
oolong --resume oolong-chat-2026-07-19-094035.md
The conversation, system prompt, and model are restored from the file (image and file attachments are recorded only as labels, so they don't ride along). Nothing is ever loaded implicitly β resume only reads a file you name.
Keybindings
| Key |
Action |
enter |
Send message |
shift+enter / ctrl+j |
Insert newline |
ctrl+v |
Paste (a clipboard image becomes an attachment) |
ctrl+f |
Attach an image or text file from disk |
ctrl+e |
Compose the message in $EDITOR |
ctrl+y |
Copy the last reply to the clipboard |
ctrl+b |
Copy the last reply's last code block |
ctrl+r |
Regenerate the last reply |
β / β |
Cycle through your sent messages, attachments included (when the composer is empty) |
ctrl+n |
Start a new chat |
ctrl+p |
Edit the system prompt |
ctrl+s |
Save transcript to markdown |
pgup / pgdn |
Scroll the conversation |
home / end |
Jump to top / bottom |
esc |
Stop a streaming response, or switch model (the conversation is kept) |
ctrl+c |
Quit |
? |
Toggle full help |
On the model picker, β/β adjust the selected model's reasoning effort before the chat starts, and esc clears an active filter before it quits.
The mouse wheel scrolls the conversation too; hold shift while dragging to select text, as usual in TUIs.
Note: shift+enter requires a terminal with keyboard enhancement support (Kitty, Ghostty, WezTerm, foot, β¦). ctrl+j works everywhere.
Privacy
- Your API key is stored in the OS keychain, never in a plain-text file.
- Chat history exists only in process memory unless you save it with
ctrl+s.
- Requests are sent with
store: false, so OpenAI does not retain responses for the Responses API's server-side history.
Development
go test ./...
The UI is a Bubble Tea state machine with three screens β model picker, chat, and first-run key entry β each in its own file under internal/ui. Supporting packages: internal/openai (streaming client), internal/keystore (keychain), internal/mathfmt (LaTeX β Unicode), and internal/clipboard (image paste).
Releases are cut automatically on push to main: the version bump is derived from conventional commit messages β feat: β minor, fix: β patch, a breaking change β major β and commits of other types (chore:, docs:, test:, β¦) don't trigger a release.
License
MIT