> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openhome.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Local Connect

> Run terminal commands on your computer through OpenHome voice — cross-platform, minimal setup.

Local Connect is a lightweight alternative to [OpenClaw](/building-abilities/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

```bash theme={"system"}
python3 --version
```

You need Python 3.7 or later.

### 2. Download the client

[Download `local_client.py`](https://drive.google.com/file/d/12Is4eXchH5dDjlG39Knp4oRuD-V3D-v_/view?usp=drive_link). 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:

```python theme={"system"}
OPENHOME_API_KEY = "your_api_key_here"
```

Replace it with your [OpenHome API key](https://app.openhome.com/dashboard/settings).

### 4. Run the client

```bash theme={"system"}
python3 local_client.py
```

You should see `Connected to OpenHome`. Keep this terminal window open while using the Ability.

<Tip>
  **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
</Tip>

### 5. Install the Local Link Ability

Add the Local Link Ability from the [Abilities library](https://app.openhome.com/dashboard/abilities) 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):

```python theme={"system"}
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

```python theme={"system"}
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

```python theme={"system"}
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

```python theme={"system"}
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)

```python theme={"system"}
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

```python theme={"system"}
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

```python theme={"system"}
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

```python theme={"system"}
# 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

```python theme={"system"}
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

```python theme={"system"}
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

```bash theme={"system"}
ls -la ~/Documents   # test in terminal
                      # verify output matches expectations
                      # then add to the Ability
```

### 2. Use a whitelist for voice-only use cases

```python theme={"system"}
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

```python theme={"system"}
# 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

```python theme={"system"}
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

| Feature           | Local Connect                | OpenClaw                     |
| ----------------- | ---------------------------- | ---------------------------- |
| **Setup**         | Single Python file           | CLI + daemon + LLM config    |
| **Dependencies**  | Python 3.7+ only             | Node.js + LLM API key        |
| **Complexity**    | Minimal                      | Advanced, feature-rich       |
| **Customization** | Direct Python editing        | MCP-style integration        |
| **Best for**      | Simple commands, prototyping | Complex automation workflows |

Use **Local Connect** when you want direct terminal access with minimal setup. Use **[OpenClaw](/building-abilities/openclaw)** when you need robust LLM-powered automation.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Client won't connect">
    * 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
  </Accordion>

  <Accordion title="Commands not executing">
    * 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
  </Accordion>

  <Accordion title="Permission denied">
    * Some commands need admin privileges. Avoid them in voice flows when possible
    * Or modify the client to prompt for `sudo` password (advanced)
  </Accordion>

  <Accordion title="Client disconnects randomly">
    * 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
  </Accordion>

  <Accordion title="Spoken output is raw terminal text">
    * The template uses an LLM to reformat responses. Check `check_response_system_prompt` is correct
    * Add command-specific parsing for structured output (JSON, tables)
  </Accordion>
</AccordionGroup>

## Security

<Warning>
  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.
</Warning>

### Recommended safety measures

**1. Command whitelist**

```python theme={"system"}
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**

```bash theme={"system"}
# 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

* **Local Link template** on GitHub: [openhome-dev/abilities/templates/Local](https://github.com/openhome-dev/abilities/tree/dev/templates/Local)
* **Heavier alternative:** [Connect to OpenClaw](/building-abilities/openclaw)
* **Getting started quickstart:** [Getting Started → OpenClaw](/guides/getting-started/openclaw) *(OpenClaw variant; Local Connect quickstart coming)*
