Install
openclaw skills install clawsyClawsy is a native macOS companion app that gives your OpenClaw agent eyes and hands on the user's Mac — screenshots, clipboard, camera, files, location, and...
openclaw skills install clawsyPlatform: macOS 14+ (Sonoma / Sequoia), Apple Silicon + Intel Download: https://github.com/iret77/clawsy/releases/latest
After installing this skill, you must send the user their setup instructions. Do not just reply "installed" or "done".
postInstall hook above already printed your gateway host and token as a
CLAWSY-SETUP block. If not, read them manually:
cat ~/.openclaw/gateway.json | python3 -c "import json,sys;c=json.load(sys.stdin);print('host:', c.get('host','localhost'));print('token:', c.get('authToken',''))"
Clawsy Setup
- Download Clawsy (free, macOS 14+): https://github.com/iret77/clawsy/releases/latest
- Unzip, drag to /Applications, run
xattr -cr /Applications/Clawsy.app, launch.- Click "Add Host" and paste this setup code:
CLAWSY-SETUP host: <actual hostname> token: <actual token>
Replace the placeholders with the real values. Do not send placeholders.
When spawning sub-agents, you must include a Clawsy context block so they can report progress and use Clawsy capabilities. Copy this verbatim into every sub-agent system prompt:
## Clawsy — macOS Companion
A Clawsy node (platform="macos") is connected. You can:
- Take screenshots: nodes(action="invoke", invokeCommand="screen.capture")
- Read clipboard: nodes(action="invoke", invokeCommand="clipboard.read")
- Write clipboard: nodes(action="invoke", invokeCommand="clipboard.write", invokeParamsJson='{"text":"..."}')
- Read/write files in shared folder: file.get, file.set, file.list, etc.
- Get location: nodes(action="invoke", invokeCommand="location.get")
Report progress after each significant step:
sessions_send(sessionKey="clawsy-service", timeoutSeconds=3,
message='{"kind":"agent.status","payload":{"agentName":"<YOUR_LABEL>","title":"<STEP>","progress":<0.0-1.0>,"statusText":"<DETAILS>"}}')
Start at 0.05 — finish at 1.0. Timeout errors on sessions_send are normal; do not retry.
For full command reference, read the clawsy skill: skill(name="clawsy")
This applies to all sub-agents — current and future. A sub-agent that does not receive this block cannot report Mission Control status and will not know about Clawsy capabilities.
nodes(action="status")
# Look for a node with platform="macos" and connected=true
The user clicks "Add Host" in Clawsy. The app copies a setup prompt to the clipboard. The user sends it to you via any channel (Telegram, Slack, etc.).
When you receive the prompt, respond with the gateway connection details:
CLAWSY-SETUP
host: <gateway hostname or IP — no protocol, no port>
token: <gateway authentication token>
The user pastes your response into Clawsy. Done — host is created and connects.
How to find the values:
host: The hostname or IP where the OpenClaw gateway runs.token: TheauthTokenfrom your gateway config. Runcat ~/.openclaw/gateway.json | python3 -c "import json,sys;print(json.load(sys.stdin).get('authToken',''))"if unsure.
After pairing, a node with platform="macos" appears in nodes(action="status").
Approve if prompted:
openclaw nodes approve <requestId>
Look up your gateway host and token, then send everything in one message so the user can get started right away:
Clawsy Setup
- Download Clawsy (free, macOS 14+): https://github.com/iret77/clawsy/releases/latest
- Unzip, drag to /Applications, run
xattr -cr /Applications/Clawsy.app, launch.- Click "Add Host" and paste this setup code:
CLAWSY-SETUP host: <your gateway hostname or IP> token: <your gateway auth token>
Replace the placeholders with actual values before sending (see "How to find the values" above). The user should not have to come back and ask for more info.
When a Clawsy node connects, the hello payload includes setupState:
{
"setupState": {
"sharedFolderConfigured": true,
"sharedFolderPath": "~/Documents/Clawsy",
"finderSyncEnabled": false,
"accessibilityGranted": false,
"screenRecordingGranted": true,
"firstLaunch": false
}
}
Read it from clawsy-service session history. If any permissions are missing,
tell the user specifically what to enable — don't send a generic list.
| Command | Approval | Description |
|---|---|---|
screen.capture | User approval | Capture the full screen or selected area. Returns {format, base64} |
camera.snap | User approval | Take a photo from the Mac camera. Params: deviceId (optional). Returns {format, base64} |
camera.list | Auto | List available cameras. Returns [{id, name}] |
| Command | Approval | Description |
|---|---|---|
clipboard.read | User approval | Read current clipboard text |
clipboard.write | Auto | Write text to clipboard. Params: {text} |
| Command | Approval | Description |
|---|---|---|
location.get | Auto | Get device GPS location. Returns {latitude, longitude, accuracy, locality, country, ...}. 10s timeout. |
All file operations are auto-approved and sandboxed to the configured shared
folder (default ~/Documents/Clawsy). Paths are relative to the shared folder root.
| Command | Params | Description |
|---|---|---|
file.list | subPath?, recursive? | List files. recursive: true walks subdirectories (max depth 5) |
file.get | name | Read file, returns base64 content |
file.set | name, content (base64) | Write file |
file.stat | path | File metadata: size, dates, type. Supports glob |
file.exists | path | Returns {exists, isDirectory} |
file.mkdir | name | Create directory (with intermediate parents) |
file.delete | name | Delete file or directory |
file.rmdir | name | Delete directory (alias for file.delete) |
file.move | source, destination | Move/rename file. Supports glob in source |
file.copy | source, destination | Copy file. Supports glob in source |
file.rename | path, newName | Rename file (name only, same directory) |
file.checksum | path | SHA256 hash of file |
file.batch | ops[] | Execute multiple operations sequentially (see below) |
file.get.chunk | name, index, chunkSize? | Read chunk of large file (default 350KB) |
file.set.chunk | name, chunk (base64), index, total | Write chunk; assembles on final chunk |
nodes(action="invoke", invokeCommand="file.batch",
invokeParamsJson='{"ops": [
{"op": "mkdir", "name": "output"},
{"op": "copy", "source": "template.txt", "destination": "output/report.txt"},
{"op": "delete", "name": "temp.log"}
]}')
Supported op values: copy, move, delete, mkdir, rename.
Returns per-operation results with ok status for each.
The gateway has a ~512 KB payload limit. For large files, use chunked transfer:
Upload (agent to Mac):
# Split file into ~150KB base64 chunks
for i, chunk in enumerate(chunks):
nodes(action="invoke", invokeCommand="file.set.chunk",
invokeParamsJson=f'{{"name":"large.pdf","chunk":"{chunk}","index":{i},"total":{len(chunks)}}}')
Download (Mac to agent):
stat = nodes(action="invoke", invokeCommand="file.stat",
invokeParamsJson='{"path":"large.pdf"}')
# Calculate chunk count from stat.size, then:
for i in range(chunk_count):
chunk = nodes(action="invoke", invokeCommand="file.get.chunk",
invokeParamsJson=f'{{"name":"large.pdf","index":{i}}}')
Use the nodes tool. Clawsy registers as a node with platform="macos".
# Screenshot
nodes(action="invoke", invokeCommand="screen.capture")
# Clipboard
nodes(action="invoke", invokeCommand="clipboard.read")
nodes(action="invoke", invokeCommand="clipboard.write",
invokeParamsJson='{"text": "Hello from agent"}')
# Camera
nodes(action="invoke", invokeCommand="camera.snap")
nodes(action="invoke", invokeCommand="camera.list")
# Files
nodes(action="invoke", invokeCommand="file.list")
nodes(action="invoke", invokeCommand="file.list",
invokeParamsJson='{"subPath": "docs/", "recursive": true}')
nodes(action="invoke", invokeCommand="file.get",
invokeParamsJson='{"name": "report.pdf"}')
nodes(action="invoke", invokeCommand="file.set",
invokeParamsJson='{"name": "output.txt", "content": "<base64>"}')
# Location
nodes(action="invoke", invokeCommand="location.get")
When Clawsy is connected, you must send status events so the user sees what you're doing. This is not optional.
sessions_send(sessionKey="clawsy-service", timeoutSeconds=3,
message='{"kind":"agent.info","payload":{"agentName":"YourName","model":"claude-opus-4-6","updatedAt":"2026-03-29T12:00:00Z"}}')
TTL is 45 minutes. Resend every heartbeat to stay visible.
sessions_send(sessionKey="clawsy-service", timeoutSeconds=3,
message='{"kind":"agent.status","payload":{"agentName":"YourName","title":"Building feature X","progress":0.5,"statusText":"Compiling sources..."}}')
progress: 0.0 to 1.0. At 1.0, task disappears after 10 seconds.Add this to your HEARTBEAT.md:
## Clawsy (every heartbeat)
If clawsy-service session exists:
sessions_send(sessionKey="clawsy-service", timeoutSeconds=3,
message='{"kind":"agent.info","payload":{"agentName":"<NAME>","model":"<MODEL>","updatedAt":"<ISO-UTC>"}}')
If actively working, also send agent.status with current task + progress.
Push data from Clawsy arrives in the clawsy-service session, not in the main
chat.
sessions_history(sessionKey="clawsy-service", limit=10)
| Type | Contains | Triggered by |
|---|---|---|
quick_send | content (text) + telemetry | User presses Cmd+Shift+K |
screenshot | Image data | screen.capture result / user-initiated |
clipboard | Text content | clipboard.read result |
camera | Image data | camera.snap result |
file_rule | File path + rule info | .clawsy rule trigger |
{
"clawsy_envelope": {
"type": "quick_send",
"content": "User's message",
"telemetry": {
"deviceName": "MacBook Pro",
"batteryLevel": 0.75,
"isCharging": true,
"thermalState": 0,
"activeApp": "Safari",
"moodScore": 70,
"isUnusualHour": false
}
}
}
Hints: thermalState > 1 = overheating, batteryLevel < 0.2 = low battery,
moodScore < 40 = user may be stressed, isUnusualHour = late/early work.
Folders within the shared directory can contain a .clawsy manifest for
automation rules. Users configure these via Finder right-click menu.
{
"version": 1,
"folderName": "Projects",
"rules": [
{
"trigger": "file_added",
"filter": "*.pdf",
"action": "send_to_agent",
"prompt": "Summarize this document"
}
]
}
file_added, file_changed, manual*.pdf, *.mov, *)send_to_agent, notifyRule events arrive in clawsy-service as file_rule envelopes.
| Situation | Action |
|---|---|
sessions_send times out | Normal. Event is delivered. Do not retry. |
No node with platform="macos" | Clawsy not connected. Skip Clawsy actions silently. |
invoke returns denied | User denied the request. Respect it. Do not re-ask. |
invoke returns sandbox_violation | Path escapes shared folder. Fix the path. |
invoke returns timeout | Command took >30s. Retry once if appropriate. |
| Node disconnects mid-task | Mission Control clears automatically. No cleanup. |
AUTH_TOKEN_MISMATCH | Clawsy auto-recovers. Wait for reconnection. |
These are on the user's side. If a capability doesn't work, point them to the specific setting — don't list everything.
| Permission | Required for | Where |
|---|---|---|
| Screen Recording | screen.capture | System Settings > Privacy > Screen Recording |
| Camera | camera.snap | System Settings > Privacy > Camera |
| Accessibility | Global hotkeys (Cmd+Shift+K) | System Settings > Privacy > Accessibility |
| Notifications | Desktop alerts | System Settings > Notifications > Clawsy |
| FinderSync Extension | Finder right-click menu | System Settings > Extensions > Finder |
| Share Extension | "Send to Agent" in Share menu | Automatic when app is in /Applications |
Mac (Clawsy) ──── WSS ────> OpenClaw Gateway (port 18789)
SSH tunnel as fallback
| You want to... | Use |
|---|---|
| See user's screen | screen.capture |
| Read what user copied | clipboard.read |
| Give user text to paste | clipboard.write |
| See what's in front of the user | camera.snap |
| Exchange files with user | file.get / file.set via shared folder |
| Transfer large files (>200KB) | file.get.chunk / file.set.chunk |
| Batch file operations | file.batch |
| Know where user is | location.get |
| Show user what you're working on | agent.status via Mission Control |
| Respond to user's quick message | Read quick_send from clawsy-service |
| React to file drops in shared folder | .clawsy rules (file_added trigger) |