- Go 94.1%
- JavaScript 1.6%
- C 0.8%
- Objective-C 0.7%
- TypeScript 0.6%
- Other 2.2%
|
mutablecc
48f2b09ceb
Some checks failed
Test / Test (ubuntu-latest) (push) Has been cancelled
Test / Test with whisper (Linux/amd64) (push) Has been cancelled
Test / Test with tts (Linux/amd64) (push) Has been cancelled
Test / Release dry run build (linux/amd64) (push) Has been cancelled
Test / Release dry run build (linux/arm64) (push) Has been cancelled
Test / Release dry run Android APK (push) Has been cancelled
Test / Capture UI screenshot (push) Has been cancelled
Reviewed-on: #38 |
||
|---|---|---|
| .dingles | Commit some fixes | |
| .fyne-fork/v2 | Commit fyne fork | |
| .github | merge-updates ( #37 ) | |
| cmd | Commit current work on Android bug fixes | |
| internal | Commit current work | |
| planning | Move file | |
| playwright-tests | Commit some fixes | |
| scripts | Commit current work | |
| .env | Commit some fixes | |
| .gitignore | Commit some fixes | |
| AGENTS.md | Fix cache dir | |
| DESIGN.md | Commit work so far | |
| docker-compose.test.yml | Commit work so far | |
| Dockerfile | Attempt to fix bugs | |
| go.mod | Commit web ui | |
| go.sum | Commit web ui | |
| LICENSE | Initial commit | |
| Makefile | Commit some fixes | |
| README.md | Updates | |
| TESTING.md | Updates to build and testing | |
zop
zop: A CLI tool for AI users
Overview
zop is a multi-provider AI CLI tool written in Go. It supports OpenAI, Anthropic,
Google Gemini, OpenRouter, and Ollama (any OpenAI-compatible endpoint), plus
voice input via Whisper in whisper-enabled builds.
Features
- Multiple providers: OpenAI, Anthropic (Claude), Google (Gemini), OpenRouter, Ollama
- TOML config: Define multiple named agents, providers, models, and MCP servers in
~/.config/zop/config.toml - Tool Calling: Models can execute tools (on by default if
allow_listis populated; use--no-toolsto disable) - Model Context Protocol (MCP): Connect to external tools via MCP servers
- Instruction Autoloading: Automatically loads
ZOP.mdfrom the config directory as global instructions - Chat sessions: Persistent multi-turn conversations stored locally
- Streaming: Real-time token streaming via
--stream - Voice input (whisper-enabled builds):
--voiceflag for microphone input via Whisper - Voice transcription: Transcription output shown during voice input (with
--verbose) - Voice output (tts-enabled builds):
--ttsflag for offline speech output via Piper/Sherpa-ONNX - Wake Word mode: Hands-free interactive mode using
--wake-wordand--stop-word
Installation
go install github.com/peterwwillis/zop/cmd/zop@latest
Or download a pre-built binary from the Releases page.
Quick Start
# Simple query (uses "default" agent from config)
zop -p "What is the capital of France?"
# Positional prompt still works
zop "What is the capital of France?"
# Use a template with @template syntax (use quotes!)
zop -p "@shell list files in current directory"
# Use template alias
zop -p "@sh list files in current directory"
# List files shorthand
zop -p "@sh list files"
# Pipe from stdin
echo "Explain recursion" | zop
# Use a specific agent
zop --agent claude "Summarise this text"
# Multi-turn chat session
zop --chat my-chat "Start a conversation"
zop --chat my-chat "Follow up question"
# Interactive chat session
zop --interactive --chat my-chat
# Interactive mode with automatic session resume
zop --interactive
# Stream the response
zop --stream "Write a haiku about Go"
# Disable tool calling support (enabled by default if allow_list is populated)
zop --no-tools "How are you?"
# Voice input (whisper-enabled build)
zop --voice
# Voice input with VAD debug diagnostics
zop --voice --debug
# Voice input with manual send (disable silence auto-stop)
zop --voice --voice-manual
# Voice output (tts-enabled build)
zop --tts "Hello world"
# Interactive mode with both voice input and output
zop -iVt
# Customize TTS speed and turn safety delay
zop -iVt --tts-speed 1.2 --tts-delay 2000
# Wake Word mode (starts in "sleeping" state)
zop -iVt --wake-word "hey zop" --stop-word "goodbye"
Whisper's native initialization logs are suppressed by default and are shown
when --debug is enabled.
In interactive mode, zop now manages sessions automatically when --chat is
not provided: it creates a unique auto-session on first run and resumes that
same auto-session on the next interactive run. User-created sessions (via
--chat) remain separate and are never auto-resumed.
When an interactive conversation exceeds provider context limits, zop
automatically starts a new session and retries the turn.
Configuration
On first run, zop writes the default config to ~/.config/zop/config.toml.
You can also copy the built-in default config as a starting point:
mkdir -p ~/.config/zop
cp internal/config/configs/default.toml ~/.config/zop/config.toml
Then set your API key environment variables:
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export GOOGLE_API_KEY="..."
export OPENROUTER_API_KEY="..."
Config Structure
# Define named agents (provider + model pairings)
[agents.default]
provider = "openai"
model = "gpt4o"
system_prompt = "You are a helpful assistant."
disable_tools = false
[agents.claude]
provider = "anthropic"
model = "claude-sonnet"
# Provider connection details
# The "engine" field specifies the backend: "openai", "anthropic", "google".
# If omitted, it defaults to the provider name itself.
# OpenAI-compatible backends (OpenAI, OpenRouter, Ollama, Azure, etc.) use "openai".
[providers.openai]
api_key_env = "OPENAI_API_KEY"
# engine = "openai" # default if not specified
[providers.ollama]
engine = "openai"
base_url = "http://localhost:11434/v1" # no API key required
[providers.azure]
engine = "openai"
base_url = "https://my-resource.openai.azure.com"
api_key_env = "AZURE_OPENAI_KEY"
api_version = "2024年02月01日"
# MCP Servers (optional)
[mcp_servers.sqlite]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-sqlite", "--db", "zop.db"]
# Model hyperparameters
[models.gpt4o]
model_id = "gpt-4o"
max_tokens = 4096
temperature = 1.0
top_p = 1.0
system_prompt = "You are a helpful assistant."
# TTS settings
[tts]
speed = 1.0
safety_delay_ms = 1000
Multiple Providers
You can configure unlimited providers with different backends, URLs, and API keys:
# Custom OpenAI-compatible endpoint
[providers.my-ollama]
engine = "openai"
base_url = "http://192.168.1.100:11434/v1"
[providers.lm-studio]
engine = "openai"
base_url = "http://localhost:1234/v1"
[providers.azure-openai]
engine = "openai"
base_url = "https://my-resource.openai.azure.com"
api_key_env = "AZURE_OPENAI_KEY"
api_version = "2024年02月01日"
[providers.another-openrouter]
engine = "openai"
base_url = "https://openrouter.ai/api/v1"
api_key_env = "OPENROUTER_API_KEY"
# Then use them in agents:
[agents.local]
provider = "my-ollama"
model = "llama3"
The engine field supports:
openai- OpenAI, OpenRouter, Ollama, Azure, LM Studio, and any OpenAI-compatible APIanthropic- Anthropic Claudegoogle- Google Gemini
Prompt Templates
zop supports Go text templates for system and user prompts. Templates can be defined inline, loaded from files, or referenced by name.
Template Data Available:
| Variable | Description |
|---|---|
.Input |
The user's input text |
.Config |
Full config object |
.Agent |
Agent configuration |
.Model |
Model configuration |
.Env |
Map of environment variables |
Template Functions:
| Function | Description |
|---|---|
now |
Current time |
date |
Current date (2006年01月02日) |
time |
Current time (15:04:05) |
upper |
Convert to uppercase |
lower |
Convert to lowercase |
trim |
Trim whitespace |
indent(n, s) |
Indent text by n spaces |
Configuration Examples:
# Inline templates
[agents.default]
system_prompt = "You are a helpful assistant. Today is {{date}}."
prompt = "User said: {{.Input}}"
# Load from file
[agents.expert]
system_prompt_file = "prompts/expert.txt"
prompt_file = "prompts/user_wrapper.txt"
# Use named template
[agents.summarizer]
prompt_template = "summarizer"
# Named templates (reusable)
[templates.expert]
system_prompt = "You are an expert in {{.Model.ModelID}}."
prompt = "Please explain: {{.Input}}"
[templates.summarizer]
system_prompt = "Summarize the following text concisely."
prompt = "Summarize:\n\n{{.Input}}"
aliases = ["sum"]
# Template with aliases
[templates.my_template]
system_prompt = "You are a helpful assistant."
prompt = "Help with: {{.Input}}"
aliases = ["mt", "help"]
Using Templates via CLI:
You can use templates directly from the command line with the @ prefix:
zop -p @shell "list files" # Use the 'shell' template
zop -p @sh "list files" # Use alias 'sh' for 'shell'
zop -p @sum "this text" # Use alias 'sum' for 'summarizer'
If the template name is not found, the prompt is passed through as-is.
Template Resolution Order:
- System prompt: CLI
-Sflag → Agentsystem_prompt→ Agentsystem_prompt_file→ Agentsystem_prompt_template→ Modelsystem_prompt→ Modelsystem_prompt_file→ Modelsystem_prompt_template - User prompt: Agent
prompt→ Agentprompt_file→ Agentprompt_template
See internal/config/configs/default.toml for the full set of built-in agents,
providers, and models.
Config Commands
zop config list
zop config get agents.default
zop config get models.gpt4o.max_tokens
zop config set models.gpt4o.max_tokens 2048
zop config unset models.gpt4o.system_prompt
zop config remove agents.temp
zop config edit
Per-Agent Streaming
Each agent can override the global streaming setting:
[agents.default]
stream = false # disable streaming for this agent
Environment Variables for Debugging
| Variable | Description |
|---|---|
ZOP_DEBUG_VAD=1 |
Enable VAD debug diagnostics (whisper input) |
ZOP_DEBUG_TTS=1 |
Enable TTS debug diagnostics (voice output) |
ZOP_WHISPER_MODEL |
Override whisper model path |
ZOP_TTS_MODEL |
Override TTS model directory |
These are set automatically when using --debug (-d).
Chat Sessions
zop chat list # list all sessions
zop chat show my-chat # show messages in a session
zop chat delete my-chat # delete a session
Tool Calling & MCP
zop supports tool calling, allowing models to interact with the external world.
Built-in Tools
run_command: Execute a shell command. The model can request to run commands to perform tasks or gather information. Note: Commands are executed automatically by the CLI.
Model Context Protocol (MCP)
zop supports the Model Context Protocol (MCP) for connecting to external tool servers.
To use MCP, add mcp_servers to your config.toml:
[mcp_servers.sqlite]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-sqlite", "--db", "zop.db"]
[mcp_servers.everything]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-everything"]
[mcp_servers.remote]
url = "http://localhost:8080/mcp/sse"
Tools provided by these servers will be automatically registered and made available to models that support tool calling (OpenAI, Anthropic, Google Gemini) in both the CLI and Mobile UI.
Tool Call Security Policies
Tool calling is enabled by default whenever you have an allow_list populated in your configuration. Models will only see tool definitions that they are permitted to use.
Disabling Tool Calling
If you wish to prevent models from using tools entirely:
- CLI Flag: Use
--no-tools(or-T) to disable tools for a single command. - Configuration: Set
disable_tools = trueglobally or per-agent in yourconfig.toml.
zop provides a flexible security policy system for tool calls, allowing you to control which commands the run_command tool is allowed to execute. Policies can be defined globally or overridden per-agent.
A policy consists of an allow_list, a deny_list, and tag-based filtering. By default, no tools are allowed to run. You must populate the allow_list to grant permission for specific tools or commands.
Configuration
You can manage policies using the config CLI command or by editing config.toml:
# Allow a specific shell command
zop config set tool_policy.my-allow-rule.exact '["ls", "-la"]'
# Allow an MCP tool by name
zop config set tool_policy.mcp-rule.tool "everything.list_files"
Add tool_policy to your config.toml:
[tool_policy]
# Global tags filtering
deny_tags = ["dangerous", "network"]
allow_tags = ["safe"]
# Allow specific commands and MCP tools
allow_list = [
# Shell command (run_command)
{ tool = "run_command", exact = ["ls", "-la"], tags = ["safe", "fs"] },
# MCP Tool by name
{ tool = "sqlite.query", tags = ["safe"] },
# MCP Tool with argument filtering (Regex on the JSON arguments string)
{ tool = "everything.read_file", regex = "notes.txt" }
]
# Deny specific tools
deny_list = [
{ tool = "everything.delete_file" },
{ tool = "run_command", regex = ".*;.*" }
]
# Per-agent overrides
[agents.restricted]
provider = "openai"
model = "gpt4o"
[agents.restricted.tool_policy]
allow_list = [{ tool = "run_command", exact = ["ls"] }]
Entry Types
tool: The name of the tool (e.g.,run_command,everything.list_files). If omitted,run_commandis assumed.exact: (Forrun_command) An array of strings representing the program and its arguments.regex: A regular expression that must match the command string (forrun_command) or the JSON arguments string (for other tools).regex_array: (Forrun_command) An array of regular expressions matching parts of the command.tags: A list of labels associated with the entry.
Building from Source
Prerequisites
- GNU Make
- curl and tar / unzip (for Go toolchain download)
- cmake (for whisper-enabled builds)
- Docker (for Android APK builds via
fyne-cross)
1 — Install Go
If Go is not already installed, the Makefile can download and install the
version declared in go.mod automatically:
make setup-go
The toolchain is installed to ~/.local/go/<version>/ and is activated
automatically for all subsequent make targets — no shell changes required.
To install a specific version or to a custom path:
make setup-go GO_VERSION=1.23.0
make setup-go GO_INSTALL_DIR=/opt/go/1.24.0
2 — Build
The default build includes Voice support (fetches and compiles
whisper.cpp and sherpa-onnx automatically):
make deps # download Go module dependencies
make build # build all packages with -tags "whisper tts"
Or run both in one shot:
make deps build
To build without voice support:
make build BUILD_TAGS="" CGO_ENABLED=0
3 — Test & Vet
make test # with voice (default)
make test TEST_ARGS="-race -coverprofile=out.cov" # with race detector + coverage
make vet # go vet (with voice by default)
make test BUILD_TAGS="" CGO_ENABLED=0 # without voice
4 — Release binary
Build a standalone binary for the current host:
make build-bin VERSION=v1.2.3
Cross-compile (whisper-enabled requires native CGO, so cross-compilation only works cleanly for the no-whisper variant):
# Whisper-enabled — must run on the target platform
make build-bin GOOS=linux GOARCH=arm64 VERSION=v1.2.3
# Fully static Linux build (recommended for Linux releases)
make build-static
# No-voice — cross-compilation works anywhere
make build-bin GOOS=linux GOARCH=amd64 BUILD_TAGS="" CGO_ENABLED=0 BINARY_SUFFIX=-novoice VERSION=v1.2.3
The output binary is named zop-<os>-<arch>[<suffix>][.exe] by default;
override with BINARY=<name>.
5 — Android APK
Requires Docker (used internally by fyne-cross):
go install github.com/fyne-io/fyne-cross@latest
make android-apk
Output: zop-android-arm64.apk
Makefile quick reference
| Target | Description |
|---|---|
make setup-go |
Download & install Go from go.mod |
make deps |
go mod download |
make build |
Build all packages (voice enabled by default) |
| make test | Run tests (voice enabled by default) |
| make vet | Run go vet |
| make build-bin | Build release binary for current platform |
| make build-static | Build a fully static Linux binary |
| make whisper-fetch | Clone & compile whisper.cpp |
| make whisper-clean | Remove whisper.cpp build tree |
| make tts-fetch | Clone & compile sherpa-onnx |
| make tts-clean | Remove sherpa-onnx build tree |
| make android-apk | Build Android APK via fyne-cross |
| make setup-go-clean | Remove the installed Go toolchain |
All variables (GO_VERSION, BUILD_TAGS, CGO_ENABLED, GOOS, GOARCH,
VERSION, ...) can be overridden on the command line.
Building with Voice Support
Voice support (input and output) is enabled by default when building with make.
To build manually with raw go:
make whisper-fetch # clone + compile whisper.cpp
make tts-fetch # clone + compile sherpa-onnx
CGO_ENABLED=1 go build -tags "whisper tts" -o zop ./cmd/zop
Voice Models
- Input (Whisper): Set
ZOP_WHISPER_MODELto override the model path (default:~/.local/share/zop/whisper/ggml-base.en.bin). - Output (TTS): Set
ZOP_TTS_MODELto override the base model directory (default:~/.local/share/zop/tts/).
To build a smaller binary without voice, omit the tags, or grab
release artifacts suffixed with -novoice.
Mobile Roadmap
See ANDROID_FYNE_PLAN.md for the design and implementation plan for the Android (Fyne) port, including the development checklist and build workflow.
Android App (Fyne)
Download the zop-android-arm64.apk asset from the latest GitHub Release, or
build it locally:
go install github.com/fyne-io/fyne-cross@latest
make android-apk
Install on a physical Android device (e.g., Samsung Galaxy S10e)
- Enable Developer Options on the device and turn on USB debugging.
- Install the Android Platform Tools (
adb) on your workstation. - Connect the phone over USB and approve the debugging prompt.
- Verify the device is visible:
adb devices - Install (or update) the APK:
adb install -r /path/to/zop-android-arm64.apk
To uninstall later:
adb uninstall com.zop.app
Web UI
zop includes a built-in ChatGPT-like web interface accessible from any browser.
Starting the Web UI
# Default: http://localhost:8080
zop web
# Custom address
zop web --addr :3000
zop web --addr 127.0.0.1:8080
Default Login
On first start, the Web UI automatically creates a default user:
| Field | Value |
|---|---|
| Username | admin |
| Password | admin |
Important: Change the default password or create new users via the
UserStoreJSON file at~/.config/zop/web_users.jsonin production deployments.
Features
- Responsive chat interface — Sidebar with session/folder management + main chat area
- Dark / Light mode — Toggle between themes (respects
prefers-color-schemeby default) - Multi-user support — Each user gets an isolated controller session; users are stored in
~/.config/zop/web_users.json - Session folders — Organize chat sessions into named folders via the sidebar
- Real-time streaming — Responses stream in via Server-Sent Events (SSE)
- Agent & model selection — Switch agents and browse available models from the header
- Tool toggle — Enable/disable tool calling from the settings modal or tools dropdown
- Speech-to-text — Click the microphone button to dictate via the Web Speech API (browser-dependent)
- Read aloud — Each assistant message has a "Read aloud" button that triggers server-side TTS (when built with the
ttstag)
Session Management in the Web UI
Sessions are shared with the CLI — any session created via zop --chat <name> is visible in the web UI, and vice versa. Sessions can be organized into folders:
# Create a session in a folder from the CLI
zop --chat work/standup "What did the team accomplish yesterday?"
Folders are stored in ~/.config/zop/web_folders.json.
API Endpoints
The Web UI exposes a JSON/SSE API under /api/:
| Endpoint | Description |
|---|---|
POST /api/login |
Authenticate (returns session cookie) |
POST /api/logout |
Clear session |
GET /api/config |
List agents and active agent |
GET /api/models |
List available models from the current provider |
GET /api/chat/messages |
Get current session messages |
POST /api/chat/send |
Send a prompt (returns SSE stream) |
POST /api/chat/clear |
Clear current session |
GET /api/sessions |
List sessions grouped by folder |
POST /api/sessions/{name}/switch |
Switch to a session |
GET /api/settings |
Get settings (tools, TTS config) |
POST /api/tools/toggle |
Enable/disable tool calling |
POST /api/tts |
Queue text for text-to-speech |
Development
make deps build test vet
See Building from Source for the full workflow.
License
MIT – see LICENSE.