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

File Output with Tool Calls

Let AI models write and update files directly using !filename syntax. zo provides a security-first approach with beautiful diff visualization and interactive approval.

Quick Start

Create New Files

Use !filename to have the AI create a new file:

zo 'Create a Python hello world program in !hello.py'

The AI writes the code directly to hello.py.

Update Existing Files

Use @!filename to let the AI see and modify existing files:

zo 'Add error handling to @!main.rs'

zo shows you a beautiful colored diff and asks for approval before saving.

Auto-Approve Changes

Skip the approval prompt with --yes or -y:

zo --yes 'Add type hints to @!script.py'
zo -y 'Update dependencies in @!Cargo.toml'

Syntax Overview

Write-Only Mode: !filename

The model can write to the file but doesn't see existing content:

zo 'Write a Rust HTTP server in !server.rs'
Use when:
  • Creating new files
  • You want the AI to generate from scratch
  • File content isn't relevant to the task

Read-Write Mode: @!filename

The model can read existing content and make updates:

zo 'Refactor this function in @!utils.py to use async'
Use when:
  • Updating existing files
  • AI needs to see current content
  • Making incremental changes

How It Works

Tool Calling API

zo uses OpenRouter's tool calling API with a save_file tool. When you use ! or @! syntax:

  1. zo registers the files as writable in the tool definition
  2. The AI can call save_file(path, content)
  3. zo validates the path and content
  4. Shows a diff for existing files
  5. Asks for approval (unless --yes flag)
  6. Writes the file if approved

Security Model

Whitelist Approach: Only files explicitly marked with ! or @! can be written.

# ✅ Can write to output.txt
zo 'Save results to !output.txt'
 
# ❌ Cannot write to secret.txt (not in whitelist)
zo 'Also save to secret.txt'  # AI can't write this
Path Validation:
  • All absolute paths rejected (/etc/passwd ❌)
  • Path traversal blocked (../../../etc/passwd ❌)
  • Paths normalized before comparison
  • Only relative paths from current directory allowed
User Approval:
  • Overwrites require approval (unless --yes)
  • Beautiful diff display shows changes
  • Interactive confirmation prompt
  • Can reject changes safely

Diff Display

When updating existing files, zo shows a colored diff:

📝 Changes to main.rs:
-fn main() {}
+fn main() {
+    println!("Hello, world!");
+}
 
Apply changes? [y/N]:
Colors:
  • 🟥 Red: Removed lines (-)
  • 🟩 Green: Added lines (+)
  • ⬜ Context lines (unchanged)

Use Cases

Code Generation

Create complete files from prompts:

# Single file
zo 'Create a FastAPI hello world in !main.py'
 
# Multiple files
zo 'Create !README.md with docs and !LICENSE with MIT license'
 
# Complete project
zo 'Create a Rust CLI project: !Cargo.toml, !src/main.rs, !src/lib.rs'

Incremental Refactoring

Update code step by step:

zo --chat @!legacy_code.py 'Let us modernize this step by step'
> Add type hints
> Convert to async
> Add error handling
> exit

Each change shows a diff and can be approved individually.

Documentation Generation

Generate docs directly to files:

# From source code
zo @src/main.rs @src/lib.rs 'Generate API docs in !API.md'
 
# From README
zo @README.md 'Create !CHANGELOG.md from commit history style'

Configuration Updates

Modify config files with AI assistance:

# Add features
zo @!nginx.conf 'Add rate limiting and caching rules'
 
# Fix issues
zo @!package.json 'Add missing TypeScript dependencies'
 
# Upgrade
zo @!Cargo.toml 'Upgrade all dependencies to latest compatible versions'

Test Generation

Create test files based on implementation:

# Unit tests
zo @!app.py 'Generate comprehensive unit tests in !test_app.py'
 
# Integration tests
zo @!api.rs 'Create integration tests in !tests/integration_test.rs'

Code Migration

Convert between languages or versions:

# Language conversion
zo @!old_api.js 'Convert to TypeScript in !new_api.ts'
 
# Version upgrade
zo @!python2_script.py 'Migrate to Python 3 with type hints'
 
# Framework migration
zo @!jquery_app.js 'Convert to React in !App.jsx'

Batch Operations

Handle multiple files at once:

# Generate project structure
zo 'Create a Rust CLI project: !Cargo.toml, !src/main.rs, !README.md, !LICENSE'
 
# Update related files
zo @!package.json @!tsconfig.json 'Upgrade to latest TypeScript and update configs'
 
# Code review with fixes
zo @!buggy_code.rs 'Review and fix bugs, save to @!buggy_code.rs'

Chat Mode Integration

File output works seamlessly in chat mode:

Iterative Development

zo --chat 'Let us build a web scraper'
> Write the basic scraper to !scraper.py
# AI creates the file, shows content, asks approval
 
> Add retry logic with exponential backoff
# AI updates the file, shows diff, asks approval
 
> Create configuration file !config.yaml
# AI creates config file
 
> Write unit tests to !test_scraper.py
# AI creates test file
 
> Update !README.md with usage instructions
# AI creates/updates README
 
> exit

Files Persist Across Turns

zo --chat 'Build a REST API'
> Create !models.py with User and Post models
# Models file created
 
> Now create !routes.py with CRUD endpoints
# Routes file created
 
> Add authentication to @!models.py and @!routes.py
# Both files updated with diffs shown
 
> exit

The AI remembers which files exist and can reference them in subsequent messages.

Advanced Patterns

Combining Input and Output

Read from one file, write to another:

# Transform data
zo @input.json 'Convert to CSV and save in !output.csv'
 
# Generate from template
zo @template.rs 'Create a similar file for HTTP in !http_handler.rs'
 
# Split functionality
zo @monolith.py 'Extract auth logic to !auth.py and update @!monolith.py'

With STDIN

Combine piped input with file output:

# Generate from build errors
cargo build 2>&1 | zo 'Create !BUGS.md documenting these errors and fixes'
 
# Documentation from git history
git log --oneline -20 | zo 'Generate !CHANGELOG.md from these commits'
 
# Analysis to file
cat large_log.txt | zo 'Summarize errors and patterns in !analysis.md'

With Custom Models

Use specialized models for file operations:

# Code generation
zo /coder 'Implement a LRU cache in !cache.rs'
 
# Documentation
zo /writer @api.rs 'Generate user-facing docs in !USER_GUIDE.md'
 
# Review and fix
zo /reviewer @!buggy_code.rs 'Review and fix issues'

Important Notes

No Streaming

File output disables streaming (API limitation with tool calls):

zo 'Create !output.py'
# Response appears after completion, not progressively

The full response is displayed once the model finishes, including all file operations.

Tool Call Feedback

Clear feedback for each file operation:

✓ Created hello.py (42 lines)
✓ Updated main.rs (15 lines changed)
✗ Skipped secret.txt (not in whitelist)

Parent Directories Must Exist

zo won't create nested directories:

# ❌ Fails if src/ doesn't exist
zo 'Create !src/lib.rs'
 
# ✅ Create directory first
mkdir -p src
zo 'Create !src/lib.rs'

Model Compatibility

File output requires models that support tool calling:

  • ✅ Claude (Sonnet, Opus, Haiku)
  • ✅ GPT-4, GPT-4o
  • ✅ Most modern models on OpenRouter
  • ❌ Some older or simpler models

If a model doesn't support tools, zo shows an error.

Security Best Practices

Use Specific Filenames

Always explicitly list files that can be written:

# ✅ Good - specific whitelist
zo 'Analyze data and save to !results.txt'
 
# ❌ Risky - vague instructions
zo 'Analyze data and save somewhere'

Review Diffs Carefully

Don't auto-approve (--yes) unless you trust the operation:

# ⚠️  Careful with auto-approve
zo --yes @!production_config.yaml 'Optimize settings'
 
# ✅ Better - review changes
zo @!production_config.yaml 'Optimize settings'
# Review diff, then approve or reject

Validate Critical Files

For important files, review in chat mode:

zo --chat @!critical_code.rs 'Let us optimize this'
> Add performance improvements
# Review diff before approving
 
> Actually, revert that change
> Try a different approach
# Iterative with full control
 
> exit

Use Version Control

Always have files under version control:

# Before AI modifications
git add .
git commit -m "Before AI changes"
 
# Make AI changes
zo @!src/lib.rs 'Refactor to use async'
 
# Review with git
git diff
 
# Revert if needed
git checkout -- src/lib.rs

Real-World Workflows

Full Feature Implementation

zo --chat 'Implement user authentication'
> Create !models/user.rs with User struct and password hashing
> Create !auth/login.rs with login logic
> Create !auth/register.rs with registration logic
> Update @!main.rs to include auth routes
> Create !tests/auth_tests.rs with comprehensive tests
> Create !docs/AUTH.md with authentication flow documentation
> exit

Codebase Modernization

# Analyze and update
zo @!old_code.js 'Modernize to ES6+: save to @!old_code.js'
 
# Generate migration guide
zo @old_code.js @!old_code.js 'Document changes in !MIGRATION.md'

Documentation Sprint

# Generate all docs
zo @src/*.rs 'Create !README.md, !API.md, and !CONTRIBUTING.md'
 
# Keep them in sync
zo @src/main.rs @!README.md 'Update README with new features'

Configuration Management

# Environment-specific configs
zo @!config.toml 'Create !config.dev.toml and !config.prod.toml variants'
 
# Validate and fix
zo @!nginx.conf 'Review and fix any configuration errors'

Error Recovery

# Document and fix
cargo build 2>&1 | zo 'Explain errors in !ERRORS.md and create fixes in @!src/lib.rs'
 
# Comprehensive debugging
zo @!broken.rs @error.log 'Fix bugs and document changes in !FIXES.md'

Troubleshooting

"File not in writable paths"

You forgot to mark the file with ! or @!:

# ❌ Error
zo 'Save to output.txt'
 
# ✅ Fixed
zo 'Save to !output.txt'

"Absolute path not allowed"

Use relative paths only:

# ❌ Error
zo 'Save to !/home/user/output.txt'
 
# ✅ Fixed
cd /home/user
zo 'Save to !output.txt'

"Parent directory doesn't exist"

Create parent directories first:

# ❌ Error
zo 'Create !src/lib.rs'  # src/ doesn't exist
 
# ✅ Fixed
mkdir -p src
zo 'Create !src/lib.rs'

Model doesn't support tools

Try a different model:

# ❌ Might not work
zo /flash 'Create !output.txt'
 
# ✅ Use tool-capable model
zo /sonnet 'Create !output.txt'
zo /gpt4o 'Create !output.txt'

Tips and Tricks

Dry Run with Debug Mode

See what would happen without writing:

zo --debug 'Create !test.py'
# Shows the request, but you can cancel before sending

Generate From Templates

# Use existing file as template
zo @template.rs 'Create similar files !module1.rs and !module2.rs for HTTP and WebSocket'

Automated Backups

# Before major changes
cp important.rs important.rs.backup
zo @!important.rs 'Major refactoring'
# Review diff, approve/reject

Chain Operations

# Analysis → Code → Tests → Docs
zo @data.csv 'Analyze and create !analysis.md'
zo @!analysis.md 'Implement solution in !solution.py'
zo @!solution.py 'Generate tests in !test_solution.py'
zo @!solution.py 'Create user docs in !SOLUTION.md'

Next Steps

Now that you understand file output, explore: