Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Configuration

zo uses a TOML configuration file for settings, custom models, and personalization.

Config File Location

  • Linux/macOS: ~/.config/zo/config.toml

Quick Setup

Initialize Config

zo +init-config

This creates ~/.config/zo/config.toml with:

  • Helpful comments explaining all options
  • Example custom models
  • List of available themes
  • Color customization examples

Basic Configuration

# API Key (alternatively use OPENROUTER_API_KEY env var)
api_key = "sk-or-v1-..."
 
# Default model (uses fuzzy matching)
default_model = "codex"

Complete Configuration Example

# ~/.config/zo/config.toml
 
# API Key (alternatively set OPENROUTER_API_KEY environment variable)
api_key = "sk-or-v1-..."
 
# Default model when no model specified
# Uses fuzzy matching - can be "codex", "sonnet", "flash", etc.
default_model = "codex"
 
# Optional alias overrides and additions
[models]
fast = "google/gemini-3-flash-preview" # Add a new alias
sonnet = "anthropic/claude-sonnet-4.5" # Override a built-in alias
opus = "" # Disable a built-in alias
 
# Syntax highlighting theme for code blocks
# See available themes below
theme = "base16-ocean.dark"
 
# Custom colors for inline markdown elements
[inline_colors]
heading = "cyan"        # Color for # Headers
inline_code = "yellow"  # Color for `code`
emphasis = "white"      # Color for *italic* and **bold**
 
[shell]
default_action = "ask"
allowed_shells = ["/bin/sh", "/bin/bash", "/bin/zsh"]
 
[[shell.always_on]]
action = "allow"
# terminal = true # optional: stop evaluating later rules for this match
program = "git"
args_prefix = [{ exact = "status" }]
 
# Define custom models with system prompts
[[custom_models]]
name = "coder"
model = "anthropic/claude-sonnet-4.5"
system_prompt = "You are an expert programmer. Provide concise, well-commented code with error handling. Follow best practices and explain your choices."
 
[[custom_models]]
name = "reviewer"
model = "anthropic/claude-sonnet-4.5"
system_prompt = "You are a senior code reviewer. Focus on: bugs, performance, security, readability, and best practices. Be thorough but constructive."
 
[[custom_models]]
name = "writer"
model = "openai/gpt-4o"
system_prompt = "You are a professional technical writer. Write clear, engaging documentation. Use examples, avoid jargon, and structure content logically."

Configuration Fields

API Key

Field: api_key
Type: String (optional)
Default: None

api_key = "sk-or-v1-your-key-here"

Alternative: Environment variable (recommended)

export OPENROUTER_API_KEY="sk-or-v1-your-key-here"

Priority: Environment variable takes precedence over config file.

Default Model

Field: default_model
Type: String (optional)
Default: "codex" (OpenAI Codex 5.3)

default_model = "codex"

Uses fuzzy matching - accepts any model name that matches built-in or custom models.

Model Aliases

Section: [models]
Type: Table (optional)

[models]
fast = "google/gemini-3-flash-preview"   # Add a new alias
sonnet = "provider/other-sonnet"      # Override a built-in alias
opus = ""                             # Disable a built-in alias

[models] is additive. Built-in aliases remain available unless you override or disable them explicitly.

Rules:
  • Non-empty values add a new alias or replace the model ID behind a built-in alias
  • Empty values are only valid for built-in aliases and disable that alias
  • custom_models still apply after [models] and can add a system prompt

Theme

Field: theme
Type: String (optional)
Default: "base16-ocean.dark"

theme = "Monokai Extended"
Available Themes: Dark Themes:
  • base16-ocean.dark (default)
  • Solarized-dark
  • Monokai Extended
  • Nord
  • Dracula
  • One Dark
  • Zenburn
Light Themes:
  • base16-ocean.light
  • InspiredGitHub
  • Solarized-light
  • One Light

Inline Colors

Section: [inline_colors]
Type: Object (optional)
Fields: heading, inline_code, emphasis

[inline_colors]
heading = "cyan"
inline_code = "yellow"
emphasis = "white"
Named Colors:
  • black, red, green, yellow
  • blue, magenta, cyan, white
  • gray, darkgray
  • lightred, lightgreen, lightyellow
  • lightblue, lightmagenta, lightcyan
Hex Colors:
[inline_colors]
heading = "#00FFFF"
inline_code = "#FFFF00"
emphasis = "#FFFFFF"

Smart Defaults: zo automatically chooses colors based on your theme:

  • Dark themes → Bright colors
  • Light themes → Dark colors

Shell Policies

Section: [shell]
Type: Object (optional)

Shell execution is only available when you pass --shell, optionally combined with file tools such as --files read --shell.

Important:

  • shell is not read-only. Approved commands run with your normal user permissions.
  • --files read --shell means read-only file tools plus shell access. It does not make spawned commands read-only.
  • The default action is ask, so commands require approval unless a policy allows them.
  • --non-interactive applies policy normally, but anything that would have asked for approval is denied instead.
[shell]
default_action = "ask" # allow | ask | deny
allowed_shells = ["/bin/sh", "/bin/bash", "/bin/zsh"]
 
[[shell.always_on]]
action = "allow"
# terminal = true # optional: stop evaluating later rules for this match
program = "git"
args = [{ exact = "status" }, { exact = "--porcelain" }]
 
[[shell.policy_sets]]
name = "github_cli"
 
[[shell.policy_sets.entries]]
action = "allow"
program = "gh"
args_prefix = [{ exact = "pr" }]
Fields:
  • default_action - Fallback when no policy matches: allow, ask, or deny
  • allowed_shells - Absolute shell paths the model may request for full shell commands
  • shell.always_on - Rules applied on every shell-enabled run
  • shell.policy_sets - Named rule groups activated with --policies set1,set2
  • terminal - Optional on any policy entry. When true, a matching rule stops later policy evaluation for the scope it matched.

Shell commands inherit the environment of the zo process.

Policy matching:
  • Rules are evaluated in order
  • shell.always_on runs first
  • Selected shell.policy_sets run next, in the same order passed to --policies
  • By default, later matching rules override earlier ones
  • program + args rules require an exact argv match for the command segment
  • program + args_prefix rules match a leading argv prefix and allow trailing args
  • command_glob and command_regex rules affect the whole normalized command and update every unlocked segment
  • regex matchers are full-string matches. Add .* yourself if you want prefix, suffix, or substring-style behavior.
  • terminal = true freezes the matched scope: a program rule locks only its segment, while command_glob and command_regex lock the whole command
  • terminal = true does not bypass approval gates for pipelines, chaining, redirection, hidden paths, or external paths

Example:

[[shell.always_on]]
action = "deny"
terminal = true
program = "rm"
 
[[shell.policy_sets]]
name = "git_safe"
 
[[shell.policy_sets.entries]]
action = "allow"
terminal = true
program = "git"
args = [{ exact = "status" }, { exact = "--porcelain" }]
 
[[shell.policy_sets.entries]]
action = "allow"
program = "gh"
args_prefix = [{ exact = "pr" }]

Custom Models

Section: [[custom_models]]
Type: Array of objects

[[custom_models]]
name = "model_name"
model = "provider/model-id"
system_prompt = "Optional system prompt"
Fields:
  • name (required) - Short name for fuzzy matching
  • model (required) - Full OpenRouter model ID
  • system_prompt (optional) - System prompt applied to all requests
Examples:
# Specialized coder
[[custom_models]]
name = "coder"
model = "anthropic/claude-sonnet-4.5"
system_prompt = "Expert programmer. Provide well-commented, tested code following best practices."
 
# Security analyst
[[custom_models]]
name = "security"
model = "anthropic/claude-opus-4.5"
system_prompt = "Security expert. Analyze for vulnerabilities, attack vectors, and provide mitigation strategies."
 
# Quick alias (no system prompt)
[[custom_models]]
name = "fast"
model = "google/gemini-2.5-flash"

Environment Variables

OPENROUTER_API_KEY

Required: Yes (either this or api_key in config)

export OPENROUTER_API_KEY="sk-or-v1-..."
Recommended approach:
# Add to ~/.bashrc or ~/.zshrc
echo 'export OPENROUTER_API_KEY="sk-or-v1-..."' >> ~/.bashrc
source ~/.bashrc

Configuration Examples

Minimal Setup

# Just the essentials
api_key = "sk-or-v1-..."

Recommended Setup

# API key via environment variable
# default_model uses fuzzy matching
default_model = "codex"
 
# Dark theme optimized
theme = "base16-ocean.dark"
 
# One custom model for coding
[[custom_models]]
name = "coder"
model = "anthropic/claude-sonnet-4.5"
system_prompt = "Expert programmer. Provide clean, well-documented code."

Power User Setup

default_model = "codex"
theme = "Monokai Extended"
 
[inline_colors]
heading = "#61AFEF"
inline_code = "#E5C07B"
emphasis = "#ABB2BF"
 
# Multiple specialized models
[[custom_models]]
name = "coder"
model = "anthropic/claude-sonnet-4.5"
system_prompt = "Expert programmer focusing on clean, tested code."
 
[[custom_models]]
name = "reviewer"
model = "anthropic/claude-sonnet-4.5"
system_prompt = "Senior code reviewer. Flag bugs, security issues, and performance problems."
 
[[custom_models]]
name = "architect"
model = "anthropic/claude-opus-4.5"
system_prompt = "Software architect. Consider scalability, maintainability, and tradeoffs."
 
[[custom_models]]
name = "writer"
model = "openai/gpt-4o"
system_prompt = "Technical writer. Create clear, comprehensive documentation."
 
[[custom_models]]
name = "debugger"
model = "openai/gpt-4o"
system_prompt = "Debugging expert. Analyze errors systematically and provide root cause analysis."

Validation

zo validates your config on load and provides helpful error messages.

Common Validation Errors

Empty Custom Model Name:
Error: Custom model name cannot be empty
Duplicate Model Names:
Error: Duplicate custom model name: 'coder'
Invalid Empty Alias in [models]:
Error: Model alias 'myalias' in [models] has an empty model ID. Empty values are only valid for disabling built-in aliases.
Invalid Theme:
Error: Theme 'invalid-theme' not found.
Available themes: base16-ocean.dark, Solarized-dark, ...

Best Practices

API Key Security

# ✅ Use environment variable
export OPENROUTER_API_KEY="..."
 
# ✅ Restrict config file permissions
chmod 600 ~/.config/zo/config.toml
 
# ❌ Don't commit to git
echo "config.toml" >> .gitignore

Model Organization

Group custom models by use case:

# Code-related
[[custom_models]]
name = "coder"
# ...
 
[[custom_models]]
name = "reviewer"
# ...
 
# Writing-related
[[custom_models]]
name = "writer"
# ...
 
[[custom_models]]
name = "docs"
# ...

System Prompt Tips

# ✅ Good - specific and actionable
system_prompt = "You are a Rust expert. Provide idiomatic code with error handling, documentation, and tests."
 
# ❌ Too vague
system_prompt = "You are helpful."

Troubleshooting

Config Not Found

# Create default config
zo +init-config
 
# Verify location
ls -la ~/.config/zo/config.toml

API Key Not Working

# Check environment variable
echo $OPENROUTER_API_KEY
 
# Check config file
cat ~/.config/zo/config.toml | grep api_key

Theme Not Working

# Initialize config to see available themes
zo +init-config
 
# Check your theme name exactly matches
# Theme names are case-sensitive

Custom Model Not Found

# List all models (including custom)
zo +list-models
 
# Verify your custom model appears in the list

Next Steps