友 — friend, companion
Terminal-native AI chat client built with React and Ink. Works with Ollama, OpenCode Zen, and OpenRouter.
brew tap leecheneler/tomo
brew install tomoOr download the latest binary from Releases.
- Run
tomo - Open
/settings → Providersand add a provider (URL + API key if needed) - Pick a model with
/model - Start chatting
Type / to see available commands and // to browse skills.
| Command | Description |
|---|---|
/new |
Start a new session |
/session |
Browse and load saved sessions |
/model |
Switch the active model |
/settings |
Manage providers, tools, MCP, etc |
/context |
Show context window usage |
/help |
List commands and shortcuts |
| Key | Action |
|---|---|
Tab |
View the full conversation in your system pager |
↑ |
Browse input history |
Esc |
Cancel an in-flight response, or clear the input |
Cmd+V |
Paste an image file path (e.g. copied from Finder) |
Ctrl+V |
Paste an image from the clipboard (e.g. a screenshot) |
↓ |
When images are attached, enter image navigation mode |
In image navigation mode: ← → select, Backspace removes, ↑ or Esc exits.
The pager defaults to less. Set PAGER to override (allow-listed: less, more, most, bat, cat).
The model can call tools to read files, run commands, and more. Tools are enabled by default; toggle them in /settings → Tools.
| Tool | Description | Default |
|---|---|---|
| Read File | Read file contents with line numbers | Enabled |
| Write File | Create or overwrite a file | Enabled |
| Edit File | Apply string replacements to a file | Enabled |
| Remove File | Delete a file | Enabled |
| Remove Directory | Delete a directory (recursive mode always prompts) | Enabled |
| Glob | Find files by glob pattern (respects .gitignore) |
Enabled |
| Grep | Search file contents by regex | Enabled |
| Run Command | Run a shell command | Enabled |
| Ask | Ask the user a question | Enabled |
| Skill | Load specialised task instructions | Enabled |
| Agent | Spawn sub-agents for parallel research | Enabled |
| Web Search | Search the web via Tavily API | Disabled |
Web Search requires a Tavily API key. Set TAVILY_API_KEY in your environment and enable the tool in /settings → Tools.
Agent spawns headless sub-agents that can read and explore the codebase in parallel. The model decides when to spawn them. Active agents show colour-coded progress indicators with tool call counts. Configure depth, concurrency, and the tools available to sub-agents under agents in your config file.
Write, edit, and remove operations prompt for confirmation by default. Read File is auto-allowed inside the current working directory. Manage permissions in /settings → Permissions, or in your config file:
permissions:
cwdReadFile: true # auto-allow reads inside cwd (default: true)
cwdWriteFile: true # auto-allow writes inside cwd
cwdRemoveFile: true # auto-allow file removes inside cwd
cwdRemoveDir: true # auto-allow empty-directory removes inside cwd
globalReadFile: true # auto-allow reads outside cwd
globalWriteFile: true # auto-allow writes outside cwd
globalRemoveFile: true # auto-allow file removes outside cwd
globalRemoveDir: true # auto-allow empty-directory removes outside cwdFile operations outside the current working directory require the global* permissions even when the matching cwd* permission is enabled.
Recursive directory removes always prompt for confirmation, regardless of permission state. The cwdRemoveDir and globalRemoveDir flags only gate the safe single-empty-directory case — they are not authorisation for arbitrary tree removal.
Run Command prompts by default. Auto-approve commands with an allow list under /settings → Allowed Commands, or in config:
allowedCommands:
- "git:*" # any command starting with "git "
- "npm:*"
- "npm test" # exact matchTomo supports the Model Context Protocol for connecting to external tool servers. MCP tools appear alongside built-in tools and the model can call them transparently.
Manage servers in /settings → MCP Servers, or in config under mcp.connections:
mcp:
connections:
my-stdio-server:
transport: stdio
command: npx
args:
- "-y"
- "@modelcontextprotocol/server-filesystem"
- "/tmp"
env:
SOME_VAR: "${MY_ENV_VAR}"
enabled: true
my-http-server:
transport: http
url: https://mcp.example.com/mcp
headers:
Authorization: "Bearer ${MCP_API_KEY}"
enabled: trueStdio transport fields: command (required), args, env.
HTTP transport fields: url (required), headers.
Use ${VAR_NAME} in any string value to interpolate an environment variable. Missing variables resolve to an empty string. This keeps secrets out of the config file.
Skills are reusable instruction sets the model can load for specialised tasks. Each skill lives in its own directory with a SKILL.md file.
Locations:
- Global:
~/.tomo/skills/<skill-name>/SKILL.md - Local:
./.tomo/skills/<skill-name>/SKILL.md
Local skills override global skills with the same name.
Format:
---
name: my-skill
description: What this skill does
---
Instruction content loaded as context when the skill is invoked.Type // to browse available skills, then //skill-name to invoke one. The model will also load relevant skills automatically when appropriate.
Skill sets are collections of skills shared via git repos. A single repo can contain multiple skill sets, each marked by a tomo-skills.json file.
Repo structure:
my-skills-repo/
dev/
tomo-skills.json # { "name": "dev", "description": "Dev tools" }
commit/
SKILL.md
pr/
SKILL.md
design/
tomo-skills.json # { "name": "design" }
web/
SKILL.md
Skills from skill sets are namespaced as setName:skillName (e.g. dev:commit) to avoid conflicts with global and local skills.
Manage sources and toggle individual sets under /settings → Skill Sets. Sources are cloned to ~/.tomo/skill-set-sources/. Skill sets are off by default — opt in per set.
In config:
skillSets:
sources:
- url: "[email protected]:org/my-skills.git"
enabledSets:
- devConversations are saved automatically as JSONL files under ~/.tomo/sessions/. Use /session to browse and resume a previous conversation. Press Tab from the input to view the current conversation in your system pager.
Tomo loads optional instruction files as system messages from two locations:
- Global:
~/tomo.md - Local:
.tomo/tomo.md
When both exist, they are combined. Use them for project-specific context, conventions, or rules you want the model to follow.
Config lives at ~/.tomo/config.yaml (global) with optional local overrides at ./.tomo/config.yaml. On first run, an empty config is created and /settings → Providers opens automatically so you can configure your first provider.
Most settings are managed interactively via /settings. A minimal config looks like this:
activeProvider: ollama
activeModel: qwen3:8b
providers:
- name: ollama
type: ollama
baseUrl: http://localhost:11434
- name: openrouter
type: openrouter
baseUrl: https://openrouter.ai/api
apiKey: sk-or-... # or set OPENROUTER_API_KEYProvider API keys can also come from environment variables: OPENCODE_API_KEY, OPENROUTER_API_KEY. Ollama needs no auth.
The full schema (permissions, allowedCommands, agents, mcp, skillSets, tools) is defined in src/config/schema.ts.
Sub-agent behaviour is configurable under agents:
agents:
maxDepth: 1 # maximum nesting depth
maxConcurrent: 3 # max agents running at once
maxTimeoutSeconds: 300 # per-agent timeout
tools: # tools available to sub-agents
- read_file
- glob
- grep
- web_search
- skill
- run_commandAll fields are optional and default to the values shown above.
See CONTRIBUTING.md.