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'- 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'- 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:
- zo registers the files as writable in the tool definition
- The AI can call
save_file(path, content) - zo validates the path and content
- Shows a diff for existing files
- Asks for approval (unless
--yesflag) - 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- All absolute paths rejected (
/etc/passwd❌) - Path traversal blocked (
../../../etc/passwd❌) - Paths normalized before comparison
- Only relative paths from current directory allowed
- 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]:- 🟥 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
> exitEach 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
> exitFiles 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
> exitThe 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 progressivelyThe 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 rejectValidate 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
> exitUse 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.rsReal-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
> exitCodebase 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 sendingGenerate 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/rejectChain 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:
- File References - Read files with
@filename - Chat Mode - Iterative file modifications
- Custom Models - Specialized coding assistants
- Examples - Real-world workflows with file I/O