Skip to main content
OpenClaw lets your OpenHome agent control your local machine through voice — launch apps, monitor system status, manage files, run developer workflows, and more. The Ability sends a command through OpenHome’s exec_local_command() call; the OpenClaw client executes it on your machine; the result comes back as a spoken response. For a short task-oriented quickstart, see Getting Started → OpenClaw. This page is the full reference.

What you can build

  • Application launcher and manager
  • System monitoring and diagnostics
  • File and folder automation
  • Development environment controller
  • Custom workflow automations
  • Smart home integration via computer
  • Screenshot and screen recording tools
  • Clipboard and text manipulation

Setup

1. Install OpenClaw

OpenClaw must be installed and configured on your local machine with an LLM API key.
npm install -g openclaw@latest
openclaw onboard --install-daemon
During onboarding you’ll be prompted for an LLM API key (OpenAI, Anthropic, etc.). OpenClaw uses this key to interpret natural-language commands.

2. Download the OpenClaw client

Download for your OS:
  • Windows.exe installer
  • macOS.dmg or .app
  • Linux — AppImage or .deb

3. Run the client

1

Launch

  • Windows — run the .exe, allow permissions if prompted
  • macOS — if blocked, go to System Settings → Privacy & Security → Open Anyway
  • Linuxchmod +x and run, grant required permissions
2

Get your OpenHome API key

3

Connect

Paste the key into the OpenClaw client, click Connect. Wait for the “welcome” message in the logs — that confirms a live connection.

4. Add the OpenClaw Ability

Install the OpenClaw Ability from the Abilities library on your Agent. This is the template you’ll customize for your use case.

The exec_local_command() API

One function carries every command from the Ability to the OpenClaw client.
async def exec_local_command(
    self,
    command: str | dict,
    target_id: str | None = None,
    timeout: float = 10.0,
)
Parameters:
  • command (str | dict, required) — inquiry or command for OpenClaw
  • target_id (str | None) — target device identifier (default: "laptop")
  • timeout (float) — max seconds to wait for a response (default: 10.0)
Returns: str — response from OpenClaw (success message, error, or command output).

Usage

# Basic
response = await self.capability_worker.exec_local_command(user_inquiry)

# Long-running command — longer timeout
response = await self.capability_worker.exec_local_command(
    "compile large project",
    timeout=30.0,
)

# Specific target device
response = await self.capability_worker.exec_local_command(
    "check battery status",
    target_id="laptop",
)

How it works

  1. User speaks a computer-control command
  2. OpenHome captures voice as text
  3. Ability sends the command to your local OpenClaw client via exec_local_command()
  4. OpenClaw executes on your computer
  5. OpenClaw returns the result (success / failure / output)
  6. An LLM converts the technical output into a natural spoken response (max ~15 words)
  7. OpenHome speaks the result

Example abilities

1. Development environment controller

# Trigger: "start coding session"
# Opens IDE, starts local servers, opens documentation
async def first_function(self):
    commands = [
        "open Visual Studio Code",
        "start local dev server on port 3000",
        "open browser to localhost:3000",
    ]
    for cmd in commands:
        await self.capability_worker.exec_local_command(cmd)
    await self.capability_worker.speak("Development environment is ready.")
    self.capability_worker.resume_normal_flow()

2. System health monitor

# Trigger: "check system health"
async def first_function(self):
    metrics = [
        ("CPU usage", "get cpu usage"),
        ("Memory usage", "get memory usage"),
        ("Disk space", "get disk usage"),
        ("Battery level", "get battery level"),
    ]
    report = []
    for name, cmd in metrics:
        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. Smart screenshot

# Trigger: "take screenshot of active window"
async def first_function(self):
    user_inquiry = await self.capability_worker.wait_for_complete_transcription()

    if "full screen" in user_inquiry.lower():
        cmd = "screenshot fullscreen save to ~/Desktop"
    elif "active window" in user_inquiry.lower():
        cmd = "screenshot active window save to ~/Desktop"
    else:
        cmd = "screenshot selection save to ~/Desktop"

    response = await self.capability_worker.exec_local_command(cmd, timeout=15.0)
    await self.capability_worker.speak(f"Screenshot saved: {response}")
    self.capability_worker.resume_normal_flow()

4. App manager with confirmation

# Trigger: "close all browsers"
async def first_function(self):
    response = await self.capability_worker.exec_local_command("list open browsers")

    if "none" in response.lower():
        await self.capability_worker.speak("No browsers are open.")
        self.capability_worker.resume_normal_flow()
        return

    await self.capability_worker.speak(f"Found: {response}. Close all?")
    confirmation = await self.capability_worker.user_response()

    if "yes" in confirmation.lower():
        await self.capability_worker.exec_local_command("close all browsers")
        await self.capability_worker.speak("All browsers closed.")
    else:
        await self.capability_worker.speak("Cancelled.")

    self.capability_worker.resume_normal_flow()

Best practices

1. Define clear trigger words

Specific, unambiguous triggers beat generic ones:
  • start development session, launch dev environment, open my coding setup
  • start, go, do it
Avoid trigger phrases that collide with other Abilities.

2. Validate before executing

DANGEROUS_COMMANDS = ["rm -rf", "format", "delete system", "shutdown -h now"]

if any(danger in user_inquiry.lower() for danger in DANGEROUS_COMMANDS):
    await self.capability_worker.speak("I can't execute that for safety reasons.")
    return

3. Confirm destructive actions

if "restart" in user_inquiry.lower() or "shutdown" in user_inquiry.lower():
    confirmed = await self.capability_worker.run_confirmation_loop(
        "This will restart your computer. Are you sure?"
    )
    if not confirmed:
        await self.capability_worker.speak("Cancelled.")
        return

4. Tune timeouts

# Default is fine for quick commands
response = await self.capability_worker.exec_local_command("open Chrome")

# Long-running — bump the timeout
response = await self.capability_worker.exec_local_command(
    "compile entire project",
    timeout=60.0,
)

5. Format responses for voice

Don’t just echo raw OpenClaw output. Parse and shape:
response = await self.capability_worker.exec_local_command("get battery level")
# Raw: "Battery: 73% (charging, 2:15 remaining)"
# Spoken: "Battery is at 73 percent."
battery = extract_percentage(response)
await self.capability_worker.speak(f"Battery is at {battery} percent.")
Keep spoken output to 1 sentence, 15 words or less.

6. Handle errors and timeouts

try:
    response = await self.capability_worker.exec_local_command(user_inquiry, timeout=15.0)
    if "error" in response.lower() or "failed" in response.lower():
        await self.capability_worker.speak("That command didn't work. Try something else.")
    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. Check the logs.")

7. Chain commands for workflows

workflow = [
    ("Opening calendar", "open Calendar app"),
    ("Starting video", "open Zoom"),
    ("Opening notes", "open Notes app"),
]
for description, command in workflow:
    await self.capability_worker.speak(description)
    await self.capability_worker.exec_local_command(command)
    await asyncio.sleep(1)

await self.capability_worker.speak("Ready for your meeting.")

Troubleshooting

  • Verify your API key is correct (from Dashboard → Settings → API Keys)
  • Check the daemon is running: openclaw status
  • Restart the OpenClaw client app
  • Check the client logs for error messages
  • Increase timeout: exec_local_command(command, timeout=20.0)
  • Check daemon status: openclaw status
  • Verify the command is valid for your OS
  • Review OpenClaw client logs
  • System Settings → Privacy & Security → find the blocked app → Open Anyway
  • Grant Accessibility and Automation permissions when prompted
  • Confirm the client shows “Connected”
  • Test a safe command first: “what time is it”
  • Verify the Ability is registered on your Agent
  • Review OpenClaw client logs

Security & privacy

OpenClaw runs with your user permissions on the local machine. Commands execute exactly as if you typed them in a terminal.
  • Commands run locally — not sent to external servers (the LLM used by OpenClaw may receive the natural-language text for command generation)
  • Your OpenHome API key authenticates the OpenHome → OpenClaw connection
  • Always add validation for user-provided input
  • Use confirmation prompts for destructive operations (restart, delete, format)
  • Review all permissions carefully when installing the client

Architecture

Voice Input → OpenHome Ability → exec_local_command()

                                 OpenClaw Client
                                   (via WebSocket)

                                 OpenClaw Daemon
                                  (with LLM API)

                                Local System Execution
                                  (apps, files, etc.)

                        Response ← AI Formatting ← Template

Resources