This is a template ability that connects OpenHome to your Mac terminal. Use voice commands to run terminal commands, check system status, manage files, and automate Mac workflows — all without OpenClaw.
Examples of abilities you could create with this template:
- Mac system monitor (disk space, CPU, memory)
- File management assistant (create, move, delete files)
- Development environment controller (git commands, npm scripts)
- Application launcher (open/close Mac apps)
- Network diagnostics tool (ping, traceroute, speed test)
- Automation scripts (backup files, cleanup temp folders)
This template uses generic triggers - customize these for your specific ability:
- Any Mac terminal command request
- Configure your own trigger words in the OpenHome dashboard
This Python script runs on your Mac and receives commands from OpenHome.
Verify you have Python installed:
python3 --versionGet your API key from OpenHome Dashboard → Settings → API Keys
-
Download the client:
- Click the download link
- Save
local_client.pyto a convenient location (e.g.,~/openhome/)
-
Open the client file:
cd ~/openhome/ nano local_client.py # or use any text editor
-
Add your OpenHome API Key:
- Find the line:
OPENHOME_API_KEY = "your_api_key_here" - Replace with:
OPENHOME_API_KEY = "YOUR_ACTUAL_KEY" - Save and exit
- Find the line:
-
Make the client executable (optional):
chmod +x local_client.py
-
Run the client:
python3 local_client.py
-
Verify connection:
- You should see:
Connected to OpenHomeor similar message - Keep this terminal window open while using the ability
- You should see:
Pro tip: Run the client in the background or use a terminal multiplexer like tmux:
# Using tmux (recommended for persistent sessions)
tmux new -s openhome
python3 local_client.py
# Press Ctrl+B then D to detach
# Or run in background
nohup python3 local_client.py > /tmp/openhome_client.log 2>&1 &-
Find OpenHome Local Link template from:
- OpenHome Dashboard abilities library, OR
- GitHub Repository
-
Add to your OpenHome Agent
-
The template is ready to test immediately!
The unmodified template:
- Listens for Mac-related voice commands
- Converts natural language → terminal commands using LLM
- Sends command to your local client via
exec_local_command() - Client executes the command on your Mac terminal
- Response is sent back to OpenHome
- LLM converts technical output → natural spoken response
User: "list all files"
AI: "Running command: ls -la"
(Command executes on your Mac)
AI: "I found 15 files in your current directory including Documents, Downloads, and Desktop."
User: "show current directory"
AI: "Running command: pwd"
AI: "You're currently in /Users/yourname/Documents"
User: "check disk space"
AI: "Running command: df -h"
AI: "Your main drive has 150 GB free out of 500 GB total."
Sends a command to your local Mac terminal and returns the response.
Function Signature:
async def exec_local_command(
self,
command: str | dict,
target_id: str | None = None,
timeout: float = 10.0
)Parameters:
command(str | dict): Required. Terminal command to executetarget_id(str | None): Optional. Device identifier (default: "laptop")timeout(float): Optional. Max seconds to wait (default: 10.0)
Template Usage:
# Basic usage (as shown in template)
response = await self.capability_worker.exec_local_command(terminal_command)
# With custom timeout for long commands
response = await self.capability_worker.exec_local_command(
"find / -name '*.log'",
timeout=30.0
)
# With specific target device
response = await self.capability_worker.exec_local_command(
"df -h",
target_id="laptop"
)User speaks: "list all files in my downloads folder"
System prompt guides LLM to generate Mac-compatible commands:
system_prompt = """
You are a Mac terminal command generator.
Rules:
- Respond ONLY with the terminal command
- Use macOS-compatible commands (zsh/bash)
- No explanations, quotes, or markdown
- Avoid sudo unless necessary
"""Result: ls -la ~/Downloads
response = await self.capability_worker.exec_local_command(terminal_command)The local_client.py runs the command in your Mac terminal and captures output.
LLM converts technical output to conversational language:
check_response_system_prompt = """
Tell if the command was successful in easier terms.
If user wanted information, return that information.
""""I found 47 files in your Downloads folder including PDFs, images, and documents."
Change how commands are generated:
def get_system_prompt(self):
return """
You are a Mac automation assistant specialized in [YOUR DOMAIN].
Rules:
- [Your custom rules]
- [Specific command patterns]
Examples:
User: "[your example]" -> [your command]
"""Validate or transform commands before execution:
async def first_function(self):
user_inquiry = await self.capability_worker.wait_for_complete_transcription()
# ⬇️ ADD YOUR VALIDATION HERE
if "delete" in user_inquiry.lower() or "rm -rf" in user_inquiry.lower():
await self.capability_worker.speak("I can't run destructive commands for safety.")
self.capability_worker.resume_normal_flow()
return
# ⬆️
# ... rest of templateterminal_command = self.capability_worker.text_to_text_response(...)
# ⬇️ ADD CONFIRMATION LOGIC
if any(danger in terminal_command for danger in ["rm", "sudo", "shutdown"]):
await self.capability_worker.speak(f"This command will {terminal_command}. Confirm?")
confirmation = await self.capability_worker.user_response()
if "yes" not in confirmation.lower():
await self.capability_worker.speak("Cancelled.")
self.capability_worker.resume_normal_flow()
return
# ⬆️
response = await self.capability_worker.exec_local_command(terminal_command)async def first_function(self):
user_inquiry = await self.capability_worker.wait_for_complete_transcription()
# Example: "backup my documents"
if "backup" in user_inquiry.lower():
commands = [
"mkdir -p ~/Backups",
"cp -r ~/Documents ~/Backups/Documents_$(date +%Y%m%d)",
"echo 'Backup complete'"
]
for cmd in commands:
await self.capability_worker.speak(f"Running: {cmd}")
response = await self.capability_worker.exec_local_command(cmd)
await self.capability_worker.speak("Backup completed successfully!")
self.capability_worker.resume_normal_flow()
return
# ... rest of template for other commandsdef get_system_prompt(self):
return """
Convert git operations to commands.
Examples:
"check git status" -> git status
"commit changes" -> git add . && git commit -m "Update"
"push to main" -> git push origin main
"create branch feature-x" -> git checkout -b feature-x
"""async def first_function(self):
user_inquiry = await self.capability_worker.wait_for_complete_transcription()
if "system health" in user_inquiry.lower():
metrics = {
"CPU": "top -l 1 | grep 'CPU usage'",
"Memory": "vm_stat | head -n 10",
"Disk": "df -h /",
"Battery": "pmset -g batt"
}
report = []
for name, cmd in metrics.items():
response = await self.capability_worker.exec_local_command(cmd)
# Parse and format response
report.append(f"{name}: {response}")
await self.capability_worker.speak(". ".join(report))
self.capability_worker.resume_normal_flow()
returnasync def first_function(self):
user_inquiry = await self.capability_worker.wait_for_complete_transcription()
if "start dev environment" in user_inquiry.lower():
await self.capability_worker.speak("Starting development environment...")
commands = [
"cd ~/Projects/my-app",
"code .", # Opens VS Code
"npm run dev &", # Starts dev server in background
"open http://localhost:3000" # Opens browser
]
for cmd in commands:
await self.capability_worker.exec_local_command(cmd, timeout=15.0)
await self.capability_worker.speak("Development environment is ready!")
self.capability_worker.resume_normal_flow()
returndef get_system_prompt(self):
return """
File management commands for Mac.
Examples:
"organize downloads by type" ->
mkdir ~/Downloads/Images ~/Downloads/Documents ~/Downloads/Videos &&
mv ~/Downloads/*.jpg ~/Downloads/Images/ 2>/dev/null || true &&
mv ~/Downloads/*.pdf ~/Downloads/Documents/ 2>/dev/null || true
"clean up temp files" -> rm -rf ~/Library/Caches/Homebrew/*
"find large files" -> find ~ -type f -size +100M
"""The local_client.py file can be customized to:
# In local_client.py
def execute_command(command):
# Add special handling for specific commands
if command == "get_battery":
result = subprocess.run(['pmset', '-g', 'batt'], capture_output=True)
return parse_battery_output(result.stdout)
# Default: run command as-is
result = subprocess.run(command, shell=True, capture_output=True)
return result.stdout.decode()# In local_client.py
import logging
logging.basicConfig(filename='openhome_commands.log', level=logging.INFO)
def execute_command(command):
logging.info(f"Executing: {command}")
result = subprocess.run(command, shell=True, capture_output=True)
logging.info(f"Result: {result.stdout[:100]}...")
return result.stdout.decode()# In local_client.py
BLOCKED_COMMANDS = ['rm -rf /', 'sudo', 'format', 'dd if=']
def execute_command(command):
if any(blocked in command for blocked in BLOCKED_COMMANDS):
return "ERROR: Blocked command for safety"
result = subprocess.run(command, shell=True, capture_output=True)
return result.stdout.decode()Before automating with voice:
# Test the command in your terminal first
ls -la ~/Documents
# Verify it does what you expect
# Then add to your ability# Avoid destructive operations by default
SAFE_COMMANDS = ['ls', 'pwd', 'cd', 'cat', 'grep', 'find', 'df', 'du']
if not any(safe in terminal_command for safe in SAFE_COMMANDS):
await self.capability_worker.speak("This command needs confirmation.")
# ... confirmation logic# Quick commands (default 10s)
response = await self.capability_worker.exec_local_command("pwd")
# Long-running commands (increase timeout)
response = await self.capability_worker.exec_local_command(
"find / -name '*.log'",
timeout=60.0
)try:
response = await self.capability_worker.exec_local_command(terminal_command)
if "error" in response.lower() or "permission denied" in response.lower():
await self.capability_worker.speak("That command failed. Try a different approach?")
else:
# Process successful response
...
except asyncio.TimeoutError:
await self.capability_worker.speak("That took too long. It might still be running.")
except Exception as e:
self.worker.editor_logging_handler.error(f"Command failed: {e}")
await self.capability_worker.speak("Something went wrong.")
finally:
self.capability_worker.resume_normal_flow()Problem: local_client.py shows connection error
Solutions:
- Verify API key is correct (from Dashboard → Settings → API Keys)
- Check Python version:
python3 --version(needs 3.7+) - Check internet connection
- Look for firewall blocking Python connections
Problem: Client connected but commands fail
Solutions:
- Check client terminal for error messages
- Verify command works when run manually in terminal
- Check client logs:
tail -f /tmp/openhome_client.log - Restart the client:
pkill -f local_client.py && python3 local_client.py
Problem: Commands fail with "Permission denied"
Solutions:
- Some commands require admin privileges
- Modify client to use
sudo(with password handling) - Run client with appropriate permissions
- Avoid commands that need root access
Problem: Client connection drops after a while
Solutions:
- Use
tmuxto keep session alive:tmux new -s openhome python3 local_client.py # Ctrl+B then D to detach - Add auto-reconnect logic to client
- Check for Mac sleep settings interfering
Problem: AI speaks raw terminal output
Solutions:
- The template formats responses automatically via LLM
- Check
check_response_system_promptis correct - Add custom parsing for specific commands
- Modify the system prompt to guide better responses
- This runs real terminal commands on your Mac with your user permissions
- No sandbox protection — commands execute exactly as typed
- Anyone with access to your OpenHome can run commands via your client
- Protect your API key — don't share or commit to public repos
1. Command Whitelist
ALLOWED_COMMANDS = ['ls', 'pwd', 'df', 'du', 'cat', 'grep', 'find']
if not any(cmd in terminal_command for cmd in ALLOWED_COMMANDS):
await self.capability_worker.speak("That command is not allowed.")
return2. Confirmation for Destructive Actions Always confirm before running:
rm(delete)sudo(admin)shutdown/rebootdd(disk operations)format/diskutil
3. Separate User for Client Run the client under a limited user account:
# Create a new user for OpenHome
sudo dscl . -create /Users/openhome
sudo dscl . -create /Users/openhome UserShell /bin/bash
sudo dscl . -create /Users/openhome RealName "OpenHome Client"
# Run client as that user
su openhome -c "python3 local_client.py"4. Monitor Client Logs
# Check what commands were run
grep "Executing:" /tmp/openhome_client.log
# Set up alerts for dangerous commands
tail -f /tmp/openhome_client.log | grep -E "rm|sudo|shutdown"Voice Input → OpenHome Ability → exec_local_command()
↓
local_client.py
(WebSocket connection)
↓
Mac Terminal Execution
(subprocess.run)
↓
Response ← AI Formatting ← Template
| Feature | Local Link | OpenClaw |
|---|---|---|
| Setup | Single Python file | Full CLI + daemon + LLM config |
| Dependencies | Python 3.7+ only | Node.js + npm + LLM API key |
| Complexity | Simple, minimal | Advanced, feature-rich |
| Customization | Direct Python editing | MCP server integration |
| Use Case | Quick terminal access | Complex automation workflows |
| Best For | Simple commands, prototyping | Production automation |
Use Local Link when you want simple, direct terminal access.
Use OpenClaw when you need robust automation with LLM-powered intelligence.
- Download
local_client.pyfrom Google Drive - Add OpenHome API key to client file
- Test client connection:
python3 local_client.py - Verify "Connected" message
- Get template from OpenHome dashboard
- Test with safe command: "show current directory"
- Verify response is spoken correctly
- Customize system prompt for your use case
- Add safety validations
- Define custom trigger words in the OpenHome dashboard
Required Downloads:
- local_client.py — Download this file (required)
OpenHome:
- Dashboard
- API Key Management — Get your API key here
- Abilities Library
Mac Terminal Resources:
- macOS Terminal User Guide
mancommand for documentation (e.g.,man ls)- Homebrew for package management
If you build something cool with this template:
- 🎉 Share it with the OpenHome community
- 💡 Contribute improvements to the template
- 🤝 Help others in community forums
- 📝 Document your use case
The power comes from YOUR customization:
- Define specific commands for your workflow
- Add safety validations
- Create domain-specific assistants
- Build something unique for your Mac!
Don't deploy the template as-is — make it yours! 🚀