Oolong π΅

Simple ephemeral chat β a fast, keyboard-driven terminal client for OpenAI, Anthropic, and Ollama 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
- Native Anthropic support β stream Claude models through Anthropic's Messages API with text, images, files, system prompts, effort, and usage reporting
- 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 β provider API keys live 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.
- Press
ctrl+k to open the key manager. It accepts OpenAI and Anthropic keys and stores them only in your OS keychain. OPENAI_API_KEY and ANTHROPIC_API_KEY take precedence when set.
- Pick a model and start chatting.
To add, replace, or remove a provider key, press ctrl+k on the model picker. oolong --reset-key removes all stored provider keys.
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 = "https://api.openai.com/v1"
# provider = "openai"
# 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"
provider = "openai"
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
[[models]]
id = "claude-sonnet-5"
provider = "anthropic"
description = "Anthropic Claude Sonnet"
input_rate = 2.00
output_rate = 10.00
reasoning_effort = "medium"
context_window = 1000000
[[models]]
id = "gemma3"
provider = "ollama"
description = "Local Gemma through Ollama"
base_url = "http://localhost:11434"
For a single run, oolong --model <id> opens a chat directly with any model your key can access, overriding default_model.
reasoning_effort sets the provider's effort parameter; verbosity applies to OpenAI's Responses API. Values are passed through because support varies by model generation. On the model picker, β/β adjust effort for the session. A malformed config never blocks launch β Oolong falls back to defaults and shows what it ignored.
OpenAI-compatible endpoints
base_url points Oolong at a provider endpoint. Set it globally, or per model to mix endpoints in one catalog. Local OpenAI-compatible endpoints need no API key; Oolong skips OpenAI-specific validation for those routes. OPENAI_BASE_URL overrides configured OpenAI endpoints only.
Set provider = "openai", provider = "anthropic", or provider = "ollama". Provider selection can be global or per model, so one catalog can contain all three. Both http://localhost:11434 and its /v1 form are accepted for Ollama.
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
- Provider API keys are 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/oneshot (pipe mode), 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