Skip to main content
Local Connect is a lightweight alternative to OpenClaw — a single Python script you run on your computer that executes voice-generated terminal commands. Works on Windows, macOS, and Linux.

What you can build

  • System monitor (disk space, CPU, memory)
  • File management assistant (create, move, delete files)
  • Development environment controller (git, npm, dev servers)
  • Application launcher
  • Network diagnostics tool (ping, traceroute, speed test)
  • Automation scripts (backups, cleanup)

Setup

1. Check Python

python3 --version
You need Python 3.7 or later.

2. Download the client

Download local_client.py. Save anywhere convenient — ~/openhome/ on macOS/Linux, C:\openhome\ on Windows.

3. Add your API key

Open local_client.py in a text editor. Find the line:
OPENHOME_API_KEY = "your_api_key_here"
Replace it with your OpenHome API key.

4. Run the client

python3 local_client.py
You should see Connected to OpenHome. Keep this terminal window open while using the Ability.
Keep it alive across sessions:
  • macOS/Linux — use tmux or screen, or run in the background: nohup python3 local_client.py > /tmp/openhome_client.log 2>&1 &
  • Windows — run in a minimized terminal, or use Task Scheduler
Add the Local Link Ability from the Abilities library to your Agent. Now test it: “show current directory”.

How it works

User speaks → OpenHome STT → Ability receives transcription

                          LLM converts to terminal command

                            exec_local_command(command)

                            local_client.py (WebSocket)

                          Executes via subprocess.run

                 Response ← LLM formats for voice ← Ability

The template

The Ability uses a small system prompt to convert natural language into a shell command, executes it, then uses a second prompt to convert the output into a spoken response.

The command-generation prompt

Tune this for your OS and use case. Default (macOS/Linux):
system_prompt = """
You are a terminal command generator. Your ONLY purpose is to convert user
requests into valid shell commands.

Rules:
- Respond ONLY with the terminal command, nothing else
- Use POSIX-compatible commands (bash/zsh)
- Do not include explanations, quotes, or markdown formatting
- Do not use sudo unless absolutely necessary
- Make sure commands are safe and won't harm the system

Examples:
User: "list all files" -> ls -la
User: "show current directory" -> pwd
User: "find python files" -> find . -name "*.py"
User: "check disk space" -> df -h
"""
For Windows, swap the examples to PowerShell or cmd equivalents.

The core function

async def first_function(self):
    user_inquiry = await self.capability_worker.wait_for_complete_transcription()

    # Convert natural language → shell command
    command = self.capability_worker.text_to_text_response(
        user_inquiry, history=[], system=system_prompt,
    ).strip()

    await self.capability_worker.speak(f"Running {command}")
    response = await self.capability_worker.exec_local_command(command)

    # Convert raw output → spoken response
    spoken = self.capability_worker.text_to_text_response(
        f"Format this for voice. Command: {command}\nOutput: {response}",
        history=[],
        system="Convert terminal output to a single spoken sentence under 15 words.",
    )
    await self.capability_worker.speak(spoken)
    self.capability_worker.resume_normal_flow()

exec_local_command() reference

async def exec_local_command(
    self,
    command: str | dict,
    target_id: str | None = None,
    timeout: float = 10.0,
)
Parameters:
  • command (required) — terminal command to execute
  • target_id (optional) — device identifier (default: "laptop")
  • timeout (optional) — max wait time in seconds (default: 10.0)

Example abilities

1. Git assistant

system_prompt = """
Convert git operations to commands. POSIX shell.

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
"""

2. System monitor (cross-platform)

async def first_function(self):
    user_inquiry = await self.capability_worker.wait_for_complete_transcription()

    if "system health" in user_inquiry.lower():
        # Pick commands by OS — detect via Python in the client
        metrics = {
            "CPU":     "top -l 1 | grep 'CPU usage'",     # macOS
            "Memory":  "vm_stat | head -n 10",            # macOS
            "Disk":    "df -h /",
            "Battery": "pmset -g batt",                   # macOS
        }
        report = []
        for name, cmd in metrics.items():
            response = await self.capability_worker.exec_local_command(cmd)
            report.append(f"{name}: {response}")
        await self.capability_worker.speak(". ".join(report))
        self.capability_worker.resume_normal_flow()

3. Dev environment launcher

async 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 &",                # Background dev server
            "open http://localhost:3000",   # macOS — use `start` on Windows, `xdg-open` on Linux
        ]
        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()

4. File organization assistant

system_prompt = """
File management commands for POSIX systems.

Examples:
"organize downloads by type" ->
  mkdir -p ~/Downloads/Images ~/Downloads/Documents &&
  mv ~/Downloads/*.jpg ~/Downloads/Images/ 2>/dev/null || true &&
  mv ~/Downloads/*.pdf ~/Downloads/Documents/ 2>/dev/null || true

"find large files" -> find ~ -type f -size +100M
"""

Customizing the client

Add custom command handlers

# In local_client.py
def execute_command(command):
    if command == "get_battery":
        result = subprocess.run(['pmset', '-g', 'batt'], capture_output=True)
        return parse_battery_output(result.stdout)
    result = subprocess.run(command, shell=True, capture_output=True)
    return result.stdout.decode()

Add logging

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()

Add a command blocklist

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()

Best practices

1. Test commands manually first

ls -la ~/Documents   # test in terminal
                      # verify output matches expectations
                      # then add to the Ability

2. Use a whitelist for voice-only use cases

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

3. Tune timeouts

# Quick commands: default is fine
response = await self.capability_worker.exec_local_command("pwd")

# Long-running: bump up
response = await self.capability_worker.exec_local_command(
    "find / -name '*.log'",
    timeout=60.0,
)

4. Handle errors and timeouts

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 success
        ...
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()

Local Connect vs OpenClaw

FeatureLocal ConnectOpenClaw
SetupSingle Python fileCLI + daemon + LLM config
DependenciesPython 3.7+ onlyNode.js + LLM API key
ComplexityMinimalAdvanced, feature-rich
CustomizationDirect Python editingMCP-style integration
Best forSimple commands, prototypingComplex automation workflows
Use Local Connect when you want direct terminal access with minimal setup. Use OpenClaw when you need robust LLM-powered automation.

Troubleshooting

  • Verify the API key in local_client.py is correct
  • Check Python version: python3 --version (needs 3.7+)
  • Check internet connection and any firewall blocking outbound WebSocket
  • Check the client terminal for errors
  • Verify the command runs correctly when typed manually
  • Check logs: tail -f /tmp/openhome_client.log
  • Restart the client
  • Some commands need admin privileges. Avoid them in voice flows when possible
  • Or modify the client to prompt for sudo password (advanced)
  • Keep the session alive with tmux / screen
  • Add reconnect logic to the client
  • On macOS, check sleep settings — the client pauses when the system sleeps
  • The template uses an LLM to reformat responses. Check check_response_system_prompt is correct
  • Add command-specific parsing for structured output (JSON, tables)

Security

This runs real terminal commands on your machine with your user permissions. Anyone with access to your OpenHome account can run commands via your client. Protect your API key.
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.")
    return
2. Confirm destructive actions — always confirm before rm, sudo, shutdown, dd, format. 3. Monitor client logs
# Check what commands were run
grep "Executing:" /tmp/openhome_client.log

# Alert on dangerous patterns
tail -f /tmp/openhome_client.log | grep -E "rm|sudo|shutdown"

Architecture

Voice Input → OpenHome Ability → exec_local_command()

                                  local_client.py
                                  (WebSocket connection)

                                 Terminal execution
                                  (subprocess.run)

                        Response ← AI formatting ← Template

Resources