{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"oterm","text":"
the terminal client for Ollama, OpenAI, Anthropic, and any pydantic-ai-supported provider.
"},{"location":"#features","title":"Features","text":"oterm in your terminal.shell, date_time, think), custom Python plugins via entry points, and any Model Context Protocol (MCP) server.See the Installation section.
"},{"location":"#using-oterm","title":"Using oterm","text":"oterm picks up providers automatically from your environment. If Ollama is running on http://127.0.0.1:11434 it shows up out of the box; for any other hosted provider, set the relevant API key (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) and it appears in the provider dropdown. For other local runners (vLLM, LM Studio, llama.cpp, \u2026) or hosted OpenAI-compatible endpoints (OpenRouter, LiteLLM, \u2026), add an entry to the openaiCompatible section of config.json. See Providers and API keys and the openaiCompatible config block.
To point Ollama at a non-default host or disable SSL verification, see Environment variables.
To start oterm simply run:
oterm\n If you installed oterm using uvx, you can also start it using:
uvx oterm\n"},{"location":"#screenshots","title":"Screenshots","text":"The splash screen animation that greets users when they start oterm.
A view of the chat interface, showcasing the conversation between the user and the model.
oterm supports multiple themes, allowing users to customize the appearance of the interface.
"},{"location":"#license","title":"License","text":"This project is licensed under the MIT License.
"},{"location":"app_config/","title":"Configuration","text":""},{"location":"app_config/#configuration","title":"Configuration","text":"oterm is configured through a JSON file (config.json) and a small set of environment variables.
config.json lives","text":"The file is stored in a directory specific to your operating system. By default:
~/.local/share/oterm~/Library/Application Support/otermC:/Users/<USER>/AppData/Roaming/otermOn Linux & MacOS we honour the XDG_DATA_HOME environment variable; the directory becomes ${XDG_DATA_HOME}/oterm. Override the location entirely by setting OTERM_DATA_DIR.
If in doubt, run oterm --data-dir (or uvx oterm --data-dir) to print the resolved location.
config.json at a glance","text":"A complete example showing every supported key:
{\n \"splash-screen\": true,\n \"theme\": \"textual-dark\",\n \"keymap\": {\n \"next.chat\": \"ctrl+tab\",\n \"prev.chat\": \"ctrl+shift+tab\",\n \"new.chat\": \"ctrl+n\",\n \"show.logs\": \"ctrl+l\",\n \"quit\": \"ctrl+q\",\n \"newline\": \"shift+enter\",\n \"add.image\": \"ctrl+i\"\n },\n \"openaiCompatible\": {\n \"vllm\": {\n \"base_url\": \"http://localhost:8000/v1\"\n },\n \"openrouter\": {\n \"base_url\": \"https://openrouter.ai/api/v1\",\n \"api_key\": \"${OPENROUTER_API_KEY}\"\n }\n },\n \"mcpServers\": {\n \"everything\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@modelcontextprotocol/server-everything\"]\n }\n }\n}\n Each key is optional; the sections below cover them in turn. For mcpServers, see the full reference in Model Context Protocol.
splash-screen and theme","text":"splash-screen (boolean, default true) \u2014 whether the splash screen plays on startup.theme (string, default \"textual-dark\") \u2014 the active theme. oterm rewrites this whenever you switch themes from the command palette, so you generally don't need to set it by hand.keymap \u2014 customizing key bindings","text":"Sane defaults are provided, but terminal emulators and shells will sometimes intercept them. Override any of the bindings below by setting the matching key in the keymap block:
next.chat ctrl+tab Switch to the next chat tab. prev.chat ctrl+shift+tab Switch to the previous chat tab. new.chat ctrl+n Open the new-chat dialog. show.logs ctrl+l Open the log viewer. quit ctrl+q Quit oterm. newline shift+enter Insert a newline in the prompt. ctrl+m is also accepted as a fallback for terminals that can't distinguish shift+enter from enter, and is not configurable. add.image ctrl+i Attach an image to the next message."},{"location":"app_config/#openaicompatible-custom-openai-compatible-endpoints","title":"openaiCompatible \u2014 custom OpenAI-compatible endpoints","text":"Connect to any OpenAI API-compatible service \u2014 local runners (vLLM, LM Studio, llama.cpp) or hosted aggregators (OpenRouter, LiteLLM) \u2014 by adding named endpoints under openaiCompatible:
{\n \"openaiCompatible\": {\n \"vllm\": {\n \"base_url\": \"http://localhost:8000/v1\"\n },\n \"openrouter\": {\n \"base_url\": \"https://openrouter.ai/api/v1\",\n \"api_key\": \"${OPENROUTER_API_KEY}\"\n }\n }\n}\n Each entry takes:
base_url (required) \u2014 the OpenAI-compatible API base URL.api_key (optional) \u2014 an API key. Reference an environment variable with ${VAR} (or ${VAR:-default} to fall back when unset), or pass a literal string. Omit for local endpoints that don't require authentication.When configured, an OpenAI Compatible provider appears in the provider dropdown. Select it, choose your endpoint, and type the model name. If the endpoint exposes /v1/models, suggestions appear as you type.
oterm discovers providers from your environment at startup. A provider appears in the new-chat dropdown only when its required environment variables are set. .env files in the working directory are loaded automatically.
ollama none for local; OLLAMA_API_KEY for ollama.com cloud models. Endpoint via OLLAMA_HOST / OLLAMA_URL. OpenAI openai OPENAI_API_KEY OpenAI Responses openai-responses OPENAI_API_KEY Anthropic anthropic ANTHROPIC_API_KEY Google AI google-gla GOOGLE_API_KEY Google Vertex AI google-vertex GOOGLE_APPLICATION_CREDENTIALS Groq groq GROQ_API_KEY Mistral mistral MISTRAL_API_KEY Cohere cohere COHERE_API_KEY AWS Bedrock bedrock AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY DeepSeek deepseek DEEPSEEK_API_KEY Grok grok GROK_API_KEY Cerebras cerebras CEREBRAS_API_KEY Hugging Face huggingface HF_TOKEN The openai-responses provider routes through OpenAI's Responses API and enables image generation as a builtin tool \u2014 pick a Responses-compatible model (e.g. gpt-5.4) and ask for an image in the prompt. Returned images render inline in the chat; click one to save it to $OTERM_DATA_DIR/downloads/.
For any other backend with an OpenAI-compatible API, see the openaiCompatible config block above.
OTERM_DATA_DIR OS-specific (above) Directory for config.json and store.db. OTERM_VERIFY_SSL True Set to False to disable SSL verification when talking to Ollama. OLLAMA_HOST 127.0.0.1:11434 Ollama host/port. Used to derive OLLAMA_URL when it isn't set. OLLAMA_URL from OLLAMA_HOST Full Ollama base URL (e.g. https://ollama.example.com). OLLAMA_API_KEY unset Bearer token for ollama.com cloud models. Local Ollama doesn't need it. Provider-specific API keys (OPENAI_API_KEY, ANTHROPIC_API_KEY, \u2026) are listed in the provider table above.
All chat sessions are stored locally in a sqlite database under OTERM_DATA_DIR. Run oterm --db to print its path.
By pressing ^ Ctrl+p you can access the command palette from where you can perform most of the chat actions. The following commands are available:
New chat - create a new chat sessionEdit chat parameters - edit the current chat session (change system prompt, tools, parameters, or thinking)Rename chat - rename the current chat sessionExport chat - export the current chat session as markdownDelete chat - delete the current chat sessionClear chat - clear the chat history, preserving the chat configuration (provider, model, system prompt, tools, parameters, and thinking)Regenerate last message - regenerates the last assistant message. Useful if you want to change the system prompt or parameters, or just try again.Prompt history - browse previously sent prompts in the current chat and re-use one.Show logs - shows the logs of the current oterm session.The palette also surfaces Textual's built-in commands (Theme, Quit, Keys, Screenshot, Maximize/Minimize).
The following keyboard shortcuts are supported:
^ Ctrl+q - quit
Enter - send the message
^ Ctrl+l - show logs
^ Ctrl+n - open a new chat
^ Ctrl+Tab - open the next chat
The prompt is always a multi-line input that auto-grows as you type or paste; long lines wrap. To recall a previously sent prompt, open Prompt history from the command palette (^ Ctrl+p).
While the model is inferring the next message, you can press Esc to cancel the inference.
Note
If the key bindings clash with your terminal, it is possible to change them by editing the configuration file. See Configuration.
"},{"location":"commands/#copy-paste","title":"Copy / Paste","text":"It is difficult to properly support copy/paste in terminal applications. You can copy blocks to your clipboard as such:
For most terminals there exists a key modifier you can use to click and drag to manually select text. For example: * iTerm Option key. * Gnome Terminal Shift key. * Windows Terminal Shift key.
The image selection interface.
Images returned by the model are rendered inline in the chat. Click an image to save it to disk.
"},{"location":"installation/","title":"Installation","text":""},{"location":"installation/#installation","title":"Installation","text":"Note
oterm works with multiple LLM providers \u2014 local and hosted. For local models, point it at Ollama, vLLM, LM Studio, llama.cpp, or any OpenAI-compatible runner. For hosted providers (OpenAI, Anthropic, Groq, \u2026), set the relevant API key. See Providers and API keys and the openaiCompatible config block.
Using uvx:
uvx oterm\n Using brew for MacOS:
brew install oterm\n Note
Since version 0.13.1, oterm is in the official homebrew/core repository. If you have installed oterm by tapping ggozad/formulas you can now remove the tap and reinstall oterm.
Using yay (or any AUR helper) for Arch Linux, thanks goes to Daniel Chesters for maintaining the package:
yay -S oterm\n Using nix-env on NixOs, thanks goes to Ga\u00ebl James for maintaining the package:
nix-env -iA nixpkgs.oterm\n Using pip:
pip install oterm\n Using pkg for FreeBSD, thanks goes to Nicola Vitale for maintaining the package:
pkg install misc/py-oterm\n Using x-cmd:
x install oterm\n"},{"location":"installation/#updating-oterm","title":"Updating oterm","text":"To update oterm to the latest version, you can use the same method you used for installation:
Using uvx:
uvx oterm@latest\n Using brew for MacOS:
brew upgrade oterm\n Using 'yay' (or any AUR helper) for Arch Linux: yay -Syu oterm\n Using pip: pip install --upgrade oterm\n Using pkg for FreeBSD:
pkg upgrade misc/py-oterm\n"},{"location":"parameters/","title":"Chat parameters","text":"When creating a new chat, you may select the provider and model you want and customize the following:
system instruction prompttools used. See Tools for more information on how to make tools available.parameters passed to the model: temperature, top_p, and max_tokens.thinking for models that support it.Note
When thinking is enabled you will observe the model thinking while generating its response. The thinking process is not persisted in the database in order to save context, so you will not see it on later sessions.
You can also \"edit\" an existing chat to change the system prompt, parameters, tools, or thinking. Note that the provider and model cannot be changed once the chat has started.
The new-chat screen, where you pick the provider and model and customize the system prompt, tools, parameters, and thinking.
"},{"location":"rag_example/","title":"RAG with haiku.rag","text":"Transform oterm into a powerful RAG (Retrieval-Augmented Generation) system by integrating with haiku.rag (from oterm's author), a SQLite-based RAG library that works seamlessly with oterm through MCP.
haiku.rag is a comprehensive RAG library that:
Add the haiku-rag MCP server to your oterm configuration. Edit your config.json file (run oterm --data-dir to find its location) and add:
{\n \"mcpServers\": {\n \"haiku-rag\": {\n \"command\": \"uvx\",\n \"args\": [\n \"haiku-rag\",\n \"serve\",\n \"--stdio\",\n \"--db\",\n \"/path/to/your/rag.db\"\n ]\n }\n }\n}\n Replace /path/to/your/rag.db with the path where you want to store your RAG database.
Once configured, oterm will have access to powerful RAG capabilities through these MCP tools:
Start a new chat in oterm and use the RAG tools to build your knowledge base:
Add this document to my knowledge base: \"Machine Learning is a subset of artificial intelligence that focuses on algorithms that can learn from data...\"
You can add various file types:
Please add the PDF file at /Users/me/Documents/research_paper.pdf to my RAG database
Add content from URLs:
Add the content from https://example.com/article to my knowledge base
Perform semantic searches:
Search my knowledge base for information about \"neural networks\"
Ask questions about your documents:
Based on my knowledge base, what are the main differences between supervised and unsupervised learning?
Configure haiku.rag to use different embedding providers, rerankers, models by setting environment variables. See haiku.rag documentation
{\n \"mcpServers\": {\n \"haiku-rag\": {\n \"command\": \"uvx\",\n \"args\": [\n \"haiku-rag\",\n \"serve\",\n \"--stdio\",\n \"--db\",\n \"/path/to/rag.db\"\n ],\n \"env\": {\n \"EMBEDDINGS_PROVIDER\": \"ollama\",\n \"EMBEDDINGS_MODEL\": \"nomic-embed-text\"\n }\n }\n }\n}\n"},{"location":"rag_example/#further-reading","title":"Further Reading","text":"Build a personal research database by adding academic papers, articles, and notes. Ask questions across your entire research collection.
"},{"location":"rag_example/#documentation-helper","title":"Documentation Helper","text":"Index your project's documentation, code comments, and README files. Get instant answers about your codebase.
"},{"location":"rag_example/#learning-companion","title":"Learning Companion","text":"Add course materials, textbooks, and learning resources. Create a personalized tutor that can answer questions about your study materials.
"},{"location":"development/","title":"Development & Debugging","text":""},{"location":"development/#inspecting-logs","title":"Inspecting logs","text":"You can inspect basic logs from oterm by invoking the log viewer with ^ Ctrl+l or by using the command palette. This is particularly useful if you want to debug tool calling.
oterm's internal log viewer showing the Brave Search MCP tool in action.
"},{"location":"development/#setup-for-development","title":"Setup for development","text":"uv venv\nActivate the virtual environment
source .venv/bin/activate\n# or on Windows\n.venv\\Scripts\\activate\n Install oterm
uv pip install oterm\n or checkout the repository and install the oterm package from source git clone git@github.com:ggozad/oterm.git\nuv sync\n In order to inspect logs from oterm, open a new terminal and run:
source .venv/bin/activate\ntextual console -x SYSTEM -x EVENT -x WORKER -x DEBUG\n This will start the textual console and listen all log messages from oterm, hiding some of the textual UI messsages. You can now start oterm in debug mode:
source .venv/bin/activate\ntextual run -c --dev oterm\n oterm uses mkdocs with material to generate the documentation. To build the documentation, run:
source .venv/bin/activate\nmkdocs serve -o\n This will start a local server and open the documentation pages in your default web browser."},{"location":"mcp/","title":"Model Context Protocol","text":"oterm has support for Anthropic's open-source Model Context Protocol. It connects to MCP servers and exposes their tools to whichever model you're chatting with.
Add MCP servers under the mcpServers key in oterm's config.json. The schema matches the convention used by Claude Desktop, Cursor, and pydantic-ai \u2014 so you can copy a config block between hosts.
Breaking changes from earlier oterm releases
The MCP integration was rewritten on top of pydantic-ai's MCP support. If you're upgrading, your existing mcpServers config likely needs the following edits:
auth: { type: bearer, token: \"X\" } is no longer recognised. Use headers: { \"Authorization\": \"Bearer X\" } instead. Old configs are silently dropped \u2014 you'll see 401s from the server until you migrate.cwd is no longer recognised. Use an absolute path in command (or pass the working directory via args).ws:// / wss:// transports are no longer supported. Use HTTP transport instead.env. Use ${VAR} substitution to pull values from the parent environment without committing secrets \u2014 see Environment variables below.MCP tools appear in oterm's tool selector and can be enabled per chat.
Note
Not all models support tools. For models that don't, the tool selection is disabled.
Smaller LLMs are often less capable with tools than larger ones. If you have issues, try reducing the number of tools attached to a chat, increasing the context size, or using a larger LLM.
"},{"location":"mcp/#transports","title":"Transports","text":""},{"location":"mcp/#stdio-transport","title":"stdio transport","text":"For local MCP servers. Accepts command, args, and env. For the git MCP server:
{\n \"mcpServers\": {\n \"git\": {\n \"command\": \"docker\",\n \"args\": [\n \"run\",\n \"--rm\",\n \"-i\",\n \"--mount\",\n \"type=bind,src=/Users/ggozad/dev/open-source/oterm,dst=/oterm\",\n \"mcp/git\"\n ]\n }\n }\n}\n"},{"location":"mcp/#streamable-http-transport","title":"Streamable HTTP transport","text":"For remote MCP servers over HTTP. The url must start with http:// or https://. URLs ending in /sse are treated as SSE; everything else is streamable HTTP.
{\n \"mcpServers\": {\n \"my_mcp\": {\n \"url\": \"http://remote:port/path\"\n }\n }\n}\n"},{"location":"mcp/#http-headers-auth","title":"HTTP headers (auth)","text":"Use the headers dict to attach arbitrary HTTP headers \u2014 including Authorization for bearer-token auth:
{\n \"mcpServers\": {\n \"my_mcp\": {\n \"url\": \"http://remote:port/path\",\n \"headers\": {\n \"Authorization\": \"Bearer XXX\"\n }\n }\n }\n}\n"},{"location":"mcp/#environment-variables","title":"Environment variables","text":"For security, stdio MCP subprocesses do not inherit oterm's parent environment. That keeps credentials like OPENAI_API_KEY or AWS_SECRET_ACCESS_KEY out of third-party MCP server processes unless you explicitly share them.
Declare env vars in the env dict of each server. Any string value (in env, command, args, url, or headers) can reference the parent environment via ${VAR} (required) or ${VAR:-default} (optional with fallback):
{\n \"mcpServers\": {\n \"github\": {\n \"command\": \"${HOME}/.local/bin/mcp-github\",\n \"args\": [\"--repo\", \"${REPO:-ggozad/oterm}\"],\n \"env\": {\n \"GITHUB_TOKEN\": \"${GITHUB_TOKEN}\"\n }\n }\n }\n}\n If a referenced variable is not set and has no default, server setup fails with an error naming the missing variable.
"},{"location":"mcp/#sampling","title":"Sampling","text":"MCP sampling is not currently supported \u2014 oterm advertises sampling as disabled to every server, so any sampling request is rejected by the protocol rather than crashing the chat.
A minimal example of how to ship a Python package whose function is auto-discovered as an oterm tool.
The tool itself is intentionally trivial \u2014 oracle(question) always answers \"oterm\" \u2014 so you can focus on the packaging:
src/oracle/tool.py defines the function. Its name, docstring, and type annotations are what oterm passes to the model.pyproject.toml registers the function under the oterm.tools entry-point group:[project.entry-points.\"oterm.tools\"]\noracle = \"oracle.tool:oracle\"\n Install it into the same environment as oterm:
cd docs/oracle\nuv pip install . # or: pip install .\n After restart, the oracle tool appears in the new-chat tool selector.
See the tools guide for the full walkthrough.
"},{"location":"tools/","title":"Tools","text":"oterm supports integration with tools. Tools are special \"functions\" that can provide external information to the LLM model that it does not otherwise have access to.
With tools, you can provide the model with access to the web, run shell commands, perform RAG and more.
Use existing Model Context Protocol servers
or
create your own custom tools.
"},{"location":"tools/#custom-tools-with-oterm","title":"Custom tools with oterm","text":"You can create your own custom tools and integrate them with oterm using Python entry points.
Create a python package that exports a callable function. The function's name, docstring, and type annotations are used to generate the tool definition for the model.
Here is an example of a simple tool that implements an Oracle:
def oracle(question: str) -> str:\n \"\"\"Function to return the Oracle's answer to any question.\n\n Args:\n question: The question to ask.\n \"\"\"\n return \"oterm\"\n"},{"location":"tools/#register-the-tool-as-an-entry-point","title":"Register the tool as an entry point","text":"In your package's pyproject.toml, register the tool under the oterm.tools entry-point group:
[project.entry-points.\"oterm.tools\"]\noracle = \"oracle.tool:oracle\"\n"},{"location":"tools/#install-and-use","title":"Install and use","text":"Install the package in the same environment where oterm is installed:
cd oracle\nuv pip install . # or pip install .\n That's it! oterm discovers all tools registered under the oterm.tools entry-point group at startup. You can now select the tool when creating or editing a chat.
The following tools are built-in to oterm and available by default:
think - provides the model with a way to think about a question before answering it. This is useful for complex questions that require reasoning. Use it for adding a \"thinking\" step to the model's response.date_time - provides the current date and time in ISO format.shell - allows you to run shell commands and use the output as input to the model. Obviously this can be dangerous, so use with caution.generate_image - generates an image from a text prompt using an Ollama image-generation model (e.g. x/z-image-turbo, x/flux2-klein). The default model is x/z-image-turbo; override it for a single call via the tool's model argument or globally via the OTERM_OLLAMA_IMAGE_MODEL environment variable. The image renders inline in the chat. Requires an Ollama server with an image-capable model installed; the host LLM (any provider) writes the prompt and calls the tool.When the model invokes a tool during a response, oterm renders the call inline as a collapsible entry between any \"thoughts\" and the response itself:
\u25b8 tool call: search_repo\n Click the line (or its arrow) to expand it. The expanded body shows:
args: \u2014 the arguments the model passed to the tool. Dictionaries and JSON-encoded strings are pretty-printed with syntax highlighting; plain strings are shown verbatim and truncated if very long.result: \u2014 the value the tool returned, formatted the same way. Appears once the tool has finished.Click again to collapse. Multiple calls within the same turn each get their own entry.
"}]}