{"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":""},{"location":"#installation","title":"Installation","text":"

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.

"},{"location":"app_config/#where-configjson-lives","title":"Where config.json lives","text":"

The file is stored in a directory specific to your operating system. By default:

On 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.

"},{"location":"app_config/#configjson-at-a-glance","title":"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.

"},{"location":"app_config/#splash-screen-and-theme","title":"splash-screen and theme","text":""},{"location":"app_config/#keymap-customizing-key-bindings","title":"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:

ID Default Action 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:

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.

"},{"location":"app_config/#providers-and-api-keys","title":"Providers and API keys","text":"

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.

Provider Provider ID Required env var(s) Ollama 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.

"},{"location":"app_config/#environment-variables","title":"Environment variables","text":"Variable Default Purpose 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.

"},{"location":"app_config/#chat-storage","title":"Chat storage","text":"

All chat sessions are stored locally in a sqlite database under OTERM_DATA_DIR. Run oterm --db to print its path.

"},{"location":"commands/","title":"Commands & shortcuts","text":""},{"location":"commands/#commands","title":"Commands","text":"

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:

The palette also surfaces Textual's built-in commands (Theme, Quit, Keys, Screenshot, Maximize/Minimize).

"},{"location":"commands/#keyboard-shortcuts","title":"Keyboard shortcuts","text":"

The following keyboard shortcuts are supported:

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:

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.

"},{"location":"rag_example/#what-is-haikurag","title":"What is haiku.rag?","text":"

haiku.rag is a comprehensive RAG library that:

"},{"location":"rag_example/#configuration","title":"Configuration","text":"

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.

"},{"location":"rag_example/#available-rag-tools","title":"Available RAG Tools","text":"

Once configured, oterm will have access to powerful RAG capabilities through these MCP tools:

"},{"location":"rag_example/#usage-examples","title":"Usage Examples","text":""},{"location":"rag_example/#1-building-a-personal-knowledge-base","title":"1. Building a Personal Knowledge Base","text":"

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...\"

"},{"location":"rag_example/#2-adding-files","title":"2. Adding Files","text":"

You can add various file types:

Please add the PDF file at /Users/me/Documents/research_paper.pdf to my RAG database

"},{"location":"rag_example/#3-adding-web-content","title":"3. Adding Web Content","text":"

Add content from URLs:

Add the content from https://example.com/article to my knowledge base

"},{"location":"rag_example/#4-searching-your-knowledge-base","title":"4. Searching Your Knowledge Base","text":"

Perform semantic searches:

Search my knowledge base for information about \"neural networks\"

"},{"location":"rag_example/#5-question-answering","title":"5. Question Answering","text":"

Ask questions about your documents:

Based on my knowledge base, what are the main differences between supervised and unsupervised learning?

"},{"location":"rag_example/#advanced-configuration","title":"Advanced Configuration","text":""},{"location":"rag_example/#custom-embedding-providers","title":"Custom Embedding Providers","text":"

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":""},{"location":"rag_example/#example-use-cases","title":"Example Use Cases","text":""},{"location":"rag_example/#research-assistant","title":"Research Assistant","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":""},{"location":"development/#debugging","title":"Debugging","text":""},{"location":"development/#documentation","title":"Documentation","text":"

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:

"},{"location":"mcp/#tools","title":"Tools","text":"

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.

"},{"location":"oracle/","title":"Oracle \u2014 example oterm tool plugin","text":"

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:

[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.

"},{"location":"tools/#create-a-python-package","title":"Create a python package","text":"

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.

"},{"location":"tools/#built-in-tools","title":"Built-in tools","text":"

The following tools are built-in to oterm and available by default:

"},{"location":"tools/#tool-calls-in-the-chat","title":"Tool calls in the chat","text":"

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:

Click again to collapse. Multiple calls within the same turn each get their own entry.

"}]}