# macOS LaunchAgent Registration Analysis

## Can It Be Done Unattended?

**Short answer: YES, mostly.**

Creating and loading a LaunchAgent in the **user's home directory** (`~/Library/LaunchAgents/`) can be done without user interaction, but there are some caveats.

## What's Required

### 1. Creating the .plist File
**Status: ✅ Fully unattended**
```bash
# Just writing a file to user's home directory
cat > ~/Library/LaunchAgents/ai.openclaw.gateway.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>ai.openclaw.gateway</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/USERNAME/.local/share/openclaw-installer/node/bin/node</string>
        <string>/Users/USERNAME/.local/share/openclaw-installer/openclaw/node_modules/openclaw/dist/index.js</string>
        <string>gateway</string>
        <string>--port</string>
        <string>18789</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/tmp/openclaw/stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/openclaw/stderr.log</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/Users/USERNAME/.local/share/openclaw-installer/node/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
</dict>
</plist>
EOF
```

### 2. Loading the Service
**Status: ✅ Fully unattended (for user agents)**
```bash
# Load the service for current user
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/ai.openclaw.gateway.plist

# Or on older macOS:
launchctl load ~/Library/LaunchAgents/ai.openclaw.gateway.plist
```

### 3. Starting the Service
**Status: ✅ Fully unattended**
```bash
launchctl kickstart -p gui/$UID/ai.openclaw.gateway
```

## Potential Issues

### 1. Terminal Permissions (TCC)
**Risk: Low**
- Modern macOS (10.15+) has TCC (Transparency, Consent, and Control)
- Terminal/iTerm needs "Full Disk Access" to write to some locations
- But `~/Library/LaunchAgents/` is in user home - generally allowed

### 2. Code Signing / Notarization
**Risk: None for LaunchAgents**
- LaunchAgents in user home don't require code signing
- Only system-wide LaunchDaemons need special entitlements

### 3. First-Run Security Dialogs
**Risk: Medium**
- When the gateway first runs, macOS might show:
  - "Do you want to allow this application to accept incoming network connections?"
  - This requires user interaction
- Can be suppressed with proper firewall rules, but difficult

### 4. Path Issues in Plist
**Risk: Medium**
- The plist must use absolute paths
- Cannot use `~` or environment variables reliably
- Need to hardcode the user's home path

### 5. Python/Node in PATH
**Risk: Low**
- The LaunchAgent runs in a minimal environment
- Need to explicitly set PATH in the plist
- Need to use absolute paths to Node.js binary

## Unattended Implementation

Here's the fully unattended approach:

```python
def _install_macos_launchagent(self) -> bool:
    """Install and load macOS LaunchAgent (unattended)."""
    try:
        import plistlib
        
        # Paths
        launch_agents_dir = Path.home() / "Library" / "LaunchAgents"
        plist_path = launch_agents_dir / "ai.openclaw.gateway.plist"
        
        # Create LaunchAgents directory if needed
        launch_agents_dir.mkdir(parents=True, exist_ok=True)
        
        # Get absolute paths
        install_base = get_install_base()
        node_path = install_base / "node" / "bin" / "node"
        index_path = install_base / "openclaw" / "node_modules" / "openclaw" / "dist" / "index.js"
        
        # Build plist content
        plist_content = {
            "Label": "ai.openclaw.gateway",
            "ProgramArguments": [
                str(node_path),
                str(index_path),
                "gateway",
                "--port",
                str(self.config.gateway.port),
            ],
            "RunAtLoad": True,
            "KeepAlive": True,
            "StandardOutPath": "/tmp/openclaw/stdout.log",
            "StandardErrorPath": "/tmp/openclaw/stderr.log",
            "EnvironmentVariables": {
                "PATH": f"{install_base}/node/bin:/usr/bin:/bin:/usr/sbin:/sbin",
                "HOME": str(Path.home()),
            },
        }
        
        # Write plist file
        with open(plist_path, "wb") as f:
            plistlib.dump(plist_content, f)
        
        # Load the service
        subprocess.run(
            ["launchctl", "bootstrap", f"gui/{os.getuid()}", str(plist_path)],
            capture_output=True,
            check=True,
        )
        
        # Start the service
        subprocess.run(
            ["launchctl", "kickstart", "-p", f"gui/{os.getuid()}/ai.openclaw.gateway"],
            capture_output=True,
            check=True,
        )
        
        return True
    except Exception as e:
        console.print(f" [dim](LaunchAgent install failed: {e})[/]")
        return False
```

## Summary

| Step | Unattended | Notes |
|------|-----------|-------|
| Create plist | ✅ Yes | Just file I/O |
| Load service | ✅ Yes | `launchctl bootstrap` |
| Start service | ✅ Yes | `launchctl kickstart` |
| Firewall prompt | ❌ Maybe | User might see dialog |
| Auto-start on boot | ✅ Yes | That's what LaunchAgent does |

## Recommendation

**YES, implement it.** The LaunchAgent approach:
1. ✅ Can be done unattended
2. ✅ Provides auto-start on login
3. ✅ Runs as user (no sudo needed)
4. ✅ Properly integrates with macOS

The only potential issue is the firewall dialog on first run, which is acceptable and standard behavior.
