AO CLI
Universal AO CLI tool for testing and automating any AO dApp (replaces AOS REPL)
⚠️ Important Notice: This tool has not been sufficiently adapted and tested for mainnet environments. Please use it on legacy networks for development and testing purposes. Mainnet support is currently experimental and may not function as expected.
Overview
This is a non-interactive command-line interface for the AO (Arweave Offchain) ecosystem. Unlike the official aos REPL tool, this CLI exits after each command completes, making it perfect for automation, testing, and CI/CD pipelines.
This repository is a standalone, self-contained implementation of the AO CLI with its own comprehensive test suite.
Features
- ✅ Non-REPL Design: Each command executes and exits immediately
- ✅ Full AO Compatibility: Works with all AO processes and dApps
- ✅ Mainnet & Testnet Support: Seamlessly switch between AO networks
- ✅ Automatic Module Loading: Resolves and bundles Lua dependencies (equivalent to
.loadin AOS) - ✅ Rich Output Formatting: Clean JSON parsing and readable results
- ✅ Structured JSON Output:
--jsonoption for automation and scripting - ✅ Proxy Support: Automatic proxy detection and configuration
- ✅ Comprehensive Commands: spawn, eval, load, message, inbox operations
- ✅ Self-Contained Testing: Complete test suite included
Installation
Prerequisites
- Node.js 18+
- npm
- AO wallet file (
~/.aos.json)
Setup
git clone https://github.com/dddappp/ao-cli.git
cd ao-cli
npm install
npm link # Makes 'ao-cli' available locally for development/testingVerify Installation
ao-cli --version
ao-cli --helpPublishing to npm
This package is published as a scoped package for security and professionalism.
For Maintainers
# 1. Run complete test suite
npm link # Make ao-cli available locally
npm test # Run all tests to ensure functionality
# 2. Login to npm
npm login
# 3. Test package
npm run prepublishOnly
# 4. Publish (scoped package requires --access public)
npm publish --access public
# View the package
# npm view @dddappp/ao-cli
# 5. Update version for new releases
npm version patch # or minor/major
npm publish --access publicFor Users
# Install globally
npm install -g @dddappp/ao-cli
# Or use with npx
npx @dddappp/ao-cli --helpSecurity Note: Always verify package downloads and check the official npm page at https://www.npmjs.com/package/@dddappp/ao-cli
Usage
Basic Commands
Spawn a Process
# Spawn with default module (testnet)
ao-cli spawn default --name "my-process-$(date +%s)"
# Spawn with custom module
ao-cli spawn <module-id> --name "my-process"
# Spawn on mainnet with default URL (https://forward.computer)
ao-cli spawn default --mainnet --name "mainnet-process"
# Spawn on mainnet with custom URL
ao-cli spawn default --mainnet https://your-mainnet-node.com --name "mainnet-process"Load Lua Code with Dependencies
# Load a Lua file (equivalent to '.load' in AOS REPL)
ao-cli load <process-id> tests/test-app.lua --waitNote: If the process ID starts with
-, you can use either of the following methods:
- Use
--separator:ao-cli load -- <process-id> tests/test-app.lua --wait- Or wrap with quotes:
ao-cli load "<process-id>" tests/test-app.lua --wait
Send Messages
# Send a message and wait for result
ao-cli message <process-id> TestMessage --data '{"key": "value"}' --wait
# Send without waiting
ao-cli message <process-id> TestMessage --data "hello"
# Send token transfer with direct properties (for contracts that read msg.Recipient, msg.Quantity)
ao-cli message <token-process-id> Transfer --prop Recipient=<target-address> --prop Quantity=100 --waitNote: If the process ID starts with
-, you can use either of the following methods:
- Use
--separator:ao-cli message -- <process-id> TestMessage ...- Or wrap with quotes:
ao-cli message "<process-id>" TestMessage ...
Evaluate Lua Code
# Evaluate code from file
ao-cli eval <process-id> --file script.lua --wait
# Evaluate code string
ao-cli eval <process-id> --data 'return "hello"' --waitNote: If the process ID starts with
-, you can use either of the following methods:
- Use
--separator:ao-cli eval -- <process-id> --file script.lua --wait- Or wrap with quotes:
ao-cli eval "<process-id>" --file script.lua --wait
Check Inbox
# Get latest message
ao-cli inbox <process-id> --latest
# Get all messages
ao-cli inbox <process-id> --all
# Wait for new messages
ao-cli inbox <process-id> --wait --timeout 30📋 Inbox Mechanism Explanation: Inbox is a global variable inside a process that records all received messages that have no handlers to process them. Handlers in the receiving process often reply to the sender; if the sender process wants the reply message to enter its own Inbox, it needs to execute a Send operation within that process (using
ao-cli eval). Usingao-cli messageto send messages directly will not cause reply messages to enter the process's Inbox.🔍 --trace Feature Explanation:
eval --tracequeries the target process's result history and attempts to precisely associate handler execution results through message References. If an exact match is found, it displays the print output from the handler triggered by that message; if an exact association cannot be made, it displays the most recent handler activity as a reference. Note: This feature only applies to theevalcommand and is currently only effective in legacy mode (the tool has not been sufficiently adapted and tested for mainnet, and does not support result history queries).Note: If the process ID starts with
-, you can use either of the following methods:
- Use
--separator:ao-cli inbox -- <process-id> --latest- Or wrap with quotes:
ao-cli inbox "<process-id>" --latest
Advanced Usage
Environment Variables
# Mainnet mode and URL (automatically enables mainnet when set)
export AO_URL=https://forward.computer
# Proxy settings (auto-detected if not set)
export HTTPS_PROXY=http://proxy:port
export HTTP_PROXY=http://proxy:port
# Gateway and scheduler
export GATEWAY_URL=https://arweave.net
export SCHEDULER=http://scheduler.url
# Wallet location
export WALLET_PATH=/path/to/wallet.json
# Test wait time
export AO_WAIT_TIME=3 # seconds to wait between operations📋 Environment Variable Details:
AO_URL: When set, automatically enables mainnet mode and uses the specified URL as the AO node endpoint. No need to combine with--mainnetflag.
- Example:
export AO_URL=https://forward.computerenables mainnet with Forward Computer node- The CLI parameter
--mainnettakes priority overAO_URLif both are provided
Network Configuration
AO CLI supports both AO testnet and mainnet. By default, all commands use testnet.
Testnet (Default)
# All commands default to testnet
ao-cli spawn default --name "testnet-process"Mainnet
# Use --mainnet flag (uses https://forward.computer as default)
ao-cli spawn default --mainnet --name "mainnet-process"
# Specify custom mainnet URL with --mainnet flag
ao-cli spawn default --mainnet https://your-mainnet-node.com --name "mainnet-process"
# Use AO_URL environment variable (automatically enables mainnet)
export AO_URL=https://forward.computer
ao-cli spawn default --name "mainnet-process"
# Environment variable + custom URL
export AO_URL=https://your-custom-node.com
ao-cli spawn default --name "mainnet-process"Network Endpoints
- Testnet:
https://cu.ao-testnet.xyz,https://mu.ao-testnet.xyz - Mainnet:
https://forward.computer(default), or any AO mainnet node
Configuration Priority
- CLI parameters take highest priority (e.g.,
--mainnet https://custom-node.com) - Environment variables are used when CLI parameters are not provided (e.g.,
AO_URL=https://custom-node.com) - Defaults are used when neither CLI nor environment variables are set
💡 Important:
- Setting
AO_URLenvironment variable automatically enables mainnet mode. You don't need to combine it with--mainnetflag.- Mainnet operations require payment: Unlike testnet, mainnet processes charge fees for computation. Ensure your wallet has sufficient AO tokens.
Custom Wallet
ao-cli spawn default --name test --wallet /path/to/custom/wallet.jsonExamples
Complete Test Suite Run
#!/bin/bash
# Run the complete test suite
./tests/run-tests.shManual Testing
# 1. Spawn process (using JSON mode for reliable parsing)
PROCESS_ID=$(ao-cli spawn default --name "test-$(date +%s)" --json | jq -r '.data.processId')
# 2. Load test application
ao-cli load "$PROCESS_ID" tests/test-app.lua --wait
# 3. Test basic messaging
ao-cli message "$PROCESS_ID" TestMessage --data "Hello AO CLI!" --wait
# 4. Test data operations
ao-cli message "$PROCESS_ID" SetData --data '{"key": "test", "value": "value"}' --wait
ao-cli message "$PROCESS_ID" GetData --data "test" --wait
# 5. Test eval functionality
ao-cli eval "$PROCESS_ID" --data "return {counter = State.counter}" --wait
# 6. Check inbox
ao-cli inbox "$PROCESS_ID" --latest💡 Tip: If you prefer not to use JSON mode, you can also use the traditional parsing method:
PROCESS_ID=$(ao-cli spawn default --name "test-$(date +%s)" | grep "Process ID:" | awk '{print $4}')
Structured JSON Output for Automation
AO CLI supports structured JSON output for automation, testing, and scripting. Use the --json flag to enable machine-readable output.
JSON Output Format
All commands return JSON with a consistent structure:
{
"command": "spawn|load|message|eval|inbox|address",
"success": true|false,
"timestamp": "2025-10-22T01:54:52.958Z",
"version": "1.4.21",
"data": {
// Command-specific data (when successful)
"processId": "...",
"messageId": "...",
"result": {...}
},
"error": "error message", // Only present when success is false
"gasUsed": 123, // Optional, present when applicable
"extra_fields": {...} // Command-specific additional data
}Examples
# Get wallet address in JSON format
ao-cli address --json
# Spawn process and parse the result
PROCESS_ID=$(ao-cli spawn default --name "test" --json | jq -r '.data.processId')
# Send message and check success
ao-cli message "$PROCESS_ID" TestAction --data "test" --wait --json | jq '.success'
# Error handling - errors go to stderr as JSON
ao-cli address --wallet nonexistent.json --json 2>&1 | jq '.error'Lua Print Output in JSON Mode
When using --json output, all print() statements from your Lua code are captured in the response:
{
"data": {
"result": {
"Output": {
"data": "🔍 Processing message...\n📊 Counter: 1\n✅ Done",
"print": true
}
}
}
}Key Points:
- All
print()output is collected inOutput.datafield - Original formatting is preserved (newlines, emojis, etc.)
Output.printis just a boolean flag indicating print output exists- Print statements are ordered chronologically as they execute
Automation Benefits
- Reliable Parsing: No more fragile text parsing with
grepandawk - Structured Data: Easy access to process IDs, message IDs, and results
- Error Handling: Consistent error reporting in JSON format
- CI/CD Ready: Perfect for automated testing and deployment pipelines
- Language Agnostic: JSON can be parsed by any programming language
Command Reference
Global Options
These options work with all commands:
--json: Output results in JSON format for automation and scripting--mainnet [url]: Enable mainnet mode (uses https://forward.computer if no URL provided)--wallet <path>: Custom wallet file path (default: ~/.aos.json)--gateway-url <url>: Arweave gateway URL--cu-url <url>: Compute Unit URL (testnet only)--mu-url <url>: Messenger Unit URL (testnet only)--scheduler <id>: Scheduler ID--proxy <url>: Proxy URL for HTTPS/HTTP/ALL_PROXY
Environment Variables (Global):
AO_URL: Set mainnet URL and automatically enable mainnet mode (e.g.,AO_URL=https://forward.computer)
Hidden Parameters (for AOS compatibility):
--url <url>: Set AO URL directly (equivalent to AOS hidden parameter)
address
Get the wallet address from the current wallet.
Usage:
ao-cli addressAlternative Method (if address command is not available):
Send a message to any process and check the receiving process's inbox - the From field will contain your wallet address.
# Send a test message to any process (use an action that won't be handled)
ao-cli message <process-id> UnknownAction --data "test" --wait
# Check the RECEIVING process's inbox to see your address in the From field
ao-cli inbox <process-id> --latestTest Results:
- ✅ Direct
addresscommand: Shows wallet addressHrhlqAg1Tz3VfrFPozfcb2MV8uGfYlOSYO4qraRqKl4 - ✅ Alternative method: Theoretically verified - When sending unhandled messages to a process, the
Fromfield in the receiving process's inbox contains the sender's wallet address - 📝 Test Limitation: Due to current network connectivity issues, the inbox method cannot be practically tested, but the implementation follows AO protocol correctly
spawn <moduleId> [options]
Spawn a new AO process.
Options:
--name <name>: Process name
load <processId> <file> [options]
Load Lua file with automatic dependency resolution.
Options:
--wait: Wait for evaluation result (default: true)
eval <processId> [options]
Evaluate Lua code.
Options:
--file <path>: Lua file to evaluate--data <string>: Lua code string--wait: Wait for result--trace: Trace sent messages for cross-process debugging (legacy network only)
message <processId> <action> [options]
Send a message to a process.
Options:
--data <data>: Message data (JSON string or plain text)--tag <tags...>: Additional tags in format name=value--prop <props...>: Message properties (direct attributes) in format name=value--wait: Wait for result
inbox <processId> [options]
Check process inbox.
Options:
--latest: Get latest message--all: Get all messages--wait: Wait for new messages--timeout <seconds>: Wait timeout (default: 30)
Output Format
All commands provide clean, readable output:
📋 MESSAGE #1 RESULT:
⛽ Gas Used: 0
📨 Messages: 1 item(s)
1. From: Process123
Target: Process456
Data: {
"result": {
"success": true,
"counter": 1
}
}Comparison with AOS REPL
| Operation | AOS REPL | AO CLI |
|---|---|---|
| Spawn | aos my-process |
ao-cli spawn default --name my-process |
| Spawn (Mainnet) | aos my-process --mainnet <url> |
ao-cli spawn default --mainnet <url> --name my-process |
| Spawn (AOS Style) | aos my-process --url <url> |
ao-cli spawn default --url <url> --name my-process |
| Load Code | .load app.lua |
ao-cli load <pid> app.lua --wait |
| Send Message | Send({Action="Test"}) |
ao-cli message <pid> Test --wait |
| Send Message (Inbox Test) | Send({Action="Test"}) |
ao-cli eval <pid> --data "Send({Action='Test'})" --wait |
| Check Inbox | Inbox[#Inbox] |
ao-cli inbox <pid> --latest |
| Eval Code | eval code |
ao-cli eval <pid> --data "code" --wait |
💡 Important Notes:
- To test Inbox functionality, you need to use
ao-cli evalto execute Send operations within the process; do not useao-cli messageto send messages directly.- If the process ID starts with
-, you can use--separator or wrap with quotes, for example:ao-cli load -- <pid> tests/test-app.lua --waitorao-cli load "<pid>" tests/test-app.lua --wait.
Project Structure
ao-cli/
├── ao-cli.js # Main CLI implementation
├── package.json # Dependencies and scripts
├── tests/ # Self-contained test suite
│ ├── test-app.lua # Test AO application
│ └── run-tests.sh # Complete test automation
└── README.md # This fileTesting
The repository includes a comprehensive self-contained test suite that verifies all CLI functionality.
Running Tests
# Run all tests
./tests/run-tests.sh
# Custom wait time between operations
AO_WAIT_TIME=5 ./tests/run-tests.shTest Coverage
The test suite covers:
- ✅ Process spawning (
spawncommand) - ✅ Lua code loading (
loadcommand) - ✅ Message sending and responses (
messagecommand) - ✅ Code evaluation (
evalcommand) - ✅ Inbox checking (
inboxcommand) - ✅ Error handling and validation
- ✅ State management and data persistence
- ✅ AOS Compatibility: Complete workflow testing (spawn → load handler → send message)
- ✅ Mainnet Support: Free spawning and message sending to mainnet nodes
- ✅ Full AOS Compatibility:
--urlparameter, ANS-104 signing, free mainnet spawning - ✅ Complete Token Workflow: Spawn → Load → Mint → Balance checking with real contracts
AOS Compatibility Testing
AO CLI supports AOS-style mainnet operations using the --url parameter:
# Test complete AOS workflow: spawn → load handler → send message → response
./tests/test-mainnet-free-spawn.sh
# Manual test - spawn process
ao-cli spawn default --url http://node.arweaveoasis.com:8734 --name "test-process"
# Manual test - load handler (like AOS .editor)
ao-cli message <process-id> Eval --data 'Handlers.add("ping", "ping", function(msg) print("pong from " .. msg.From) end)' --url http://node.arweaveoasis.com:8734
# Manual test - send message (like AOS send())
ao-cli message <process-id> ping --data "ping" --url http://node.arweaveoasis.com:8734Key Achievement: AO CLI is fully compatible with AOS --url parameter functionality!
✅ Complete AOS Compatibility:
- ✅ Spawn processes without account balance (like
aos process --url <node>) - ✅ Use correct hyper module for lua@5.3a execution (
wal-fUK-YnB9Kp5mN8dgMsSqPSqiGx-0SvwFUSwpDBI) - ✅ Set proper device configuration (
device: 'process@1.0') for mainnet connections - ✅ Load handlers using
ao-cli message <id> Eval(equivalent to AOS.editor) - ✅ Send messages to trigger handlers (equivalent to AOS
send()function) - ✅ Load contracts using
ao-cli load(equivalent to AOS.load-blueprint) - ✅ Send signed ANS-104 messages to mainnet nodes
- ✅ Use identical signing and request formats as AOS
- ✅ Work with Arweave Oasis nodes:
http://node.arweaveoasis.com:8734 - ✅ Complete workflow: spawn → load handler → send message → response
Current Status:
- ✅ Process spawning: Works reliably on mainnet nodes (AOS compatibility achieved)
- ✅ Contract loading: Initiates successfully (like AOS
.load-blueprint) - ✅ Message sending: Requests sent successfully with ANS-104 signing
- ✅ Handler execution: Fully working! Can load handlers and see immediate execution results (like AOS
send())
🎯 Mission Accomplished: AO CLI now fully supports AOS-style --url parameter for free mainnet operations!
📋 Complete Workflow Tests:
# Test the complete AOS-compatible workflow: spawn + load + mint + balance
./tests/test-ao-token.sh
# Demo: AO CLI vs AOS side-by-side comparison
./tests/demo-aos-compatibility.sh
# Note: If your network requires a proxy to access AO nodes, set these environment variables:
# export HTTPS_PROXY=http://127.0.0.1:1235
# export HTTP_PROXY=http://127.0.0.1:1235
# export ALL_PROXY=socks5://127.0.0.1:1234Test Application
The tests/test-app.lua provides handlers for:
TestMessage: Basic message testing with counter and detailed logging outputSetData/GetData: Key-value data operationsTestInbox: Inbox functionality testing (sends internal messages to demonstrate inbox behavior)TestError: Error handling testing (available for manual testing of error conditions)InboxTestReply: Processes inbox test repliesTestReceiverPrint: Cross-process print testing for advanced debugging scenarios
Future Improvements (TODOs)
🔄 Planned Enhancements
Dependency Updates
- Regularly update
@permaweb/aoconnectand other dependencies to latest versions - Add automated dependency vulnerability scanning
- Regularly update
Enhanced Error Handling
- Add more granular error messages for different failure scenarios
- Implement retry logic for network timeouts
- Add better validation for process IDs and message formats
Performance Optimizations
- Add module caching to speed up repeated code loading
- Implement parallel processing for batch operations
- Add connection pooling for multiple AO operations
Testing Improvements
- Add unit tests for individual CLI commands
- Implement integration tests with different AO dApps
- Add performance benchmarking tests
Developer Experience
- Add shell completion scripts (bash/zsh/fish)
- Create VS Code extension for AO development
- Add interactive mode option alongside non-REPL design
Documentation
- Add video tutorials for common use cases
- Create cookbook with real-world AO dApp examples
- Add API reference documentation
CI/CD Integration
- Add GitHub Actions workflows for automated testing
- Create Docker images for easy deployment
- Add pre-built binaries for multiple platforms
Monitoring & Observability
- Add metrics collection for operation performance
- Implement structured logging with log levels
- Add health check endpoints for monitoring
🤝 Contributing
We welcome contributions! Please see our contribution guidelines and feel free to submit issues or pull requests.
Troubleshooting
Common Issues
"fetch failed"
- Check proxy settings
- Verify network connectivity
"Wallet file not found"
# Ensure wallet exists ls -la ~/.aos.json"Module not found" errors
- Check Lua file paths
- Ensure dependencies are in the same directory
Empty inbox results
- Use
--waitoption - Increase timeout with
--timeout
- Use
Debug Mode
Enable verbose logging:
export DEBUG=ao-cli:*Development
Adding New Commands
- Add command definition in
ao-cli.js - Implement handler function
- Update this README
Running Tests During Development
./tests/run-tests.shLicense
ISC