Install
openclaw skills install glkvmRemotely control a target host via GLKVM HTTP API, supporting keyboard/mouse input, screenshot capture, OCR recognition, Fingerbot physical button control, a...
openclaw skills install glkvmThe following steps must be performed at the start of each session:
Ask the user for the following information (if not already provided):
192.168.1.100)admin)curl -sk -c /tmp/glkvm_cookies.txt \
-F "user=admin" \
-F "passwd=<PASSWORD>" \
"https://<IP>/api/auth/login"
ok: true with a token indicates successful login; the auth_token is also saved in the cookie.two_step_required: true means waiting for two-step approval.-b /tmp/glkvm_cookies.txt.All requests use HTTPS with -k (ignore certificate errors).
Capture and save a screenshot:
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/streamer/snapshot" \
--output /tmp/glkvm_snapshot.jpg
Then use the Read tool to read /tmp/glkvm_snapshot.jpg to view the image content.
Get thumbnail (recommended for quick preview):
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/streamer/snapshot?preview=true&preview_max_width=1280&preview_max_height=720&preview_quality=80" \
--output /tmp/glkvm_snapshot.jpg
Screenshot with OCR recognition (returns text):
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/streamer/snapshot?ocr=true&ocr_langs=chi_sim,eng"
Parameter description:
save=true: Save screenshot to device diskload=true: Load previously saved screenshot without re-capturingallow_offline=true: Allow response even when video stream is offlineocr_left/ocr_top/ocr_right/ocr_bottom: OCR region coordinates (-1 = no crop)Working principle: After taking a screenshot, you must use the Read tool to view the image, understand the current screen state, then decide the next action.
# Full click (press + release)
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_key?key=KEY_A"
# Press only
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_key?key=KEY_A&state=true"
# Release only
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_key?key=KEY_A&state=false"
Common key names (USB HID keycodes):
KEY_A ~ KEY_ZKEY_1 ~ KEY_0KEY_F1 ~ KEY_F12KEY_ENTER, KEY_BACKSPACE, KEY_TAB, KEY_ESC, KEY_SPACEKEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHTKEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, KEY_LEFTMETAKEY_DELETE, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, KEY_INSERT# Ctrl+C
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_shortcut?keys=ControlLeft,KeyC"
# Ctrl+Alt+Delete
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_shortcut?keys=ControlLeft,AltLeft,Delete"
# Win+L (lock screen)
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_shortcut?keys=MetaLeft,KeyL"
# Alt+F4
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_shortcut?keys=AltLeft,F4"
The keys parameter is comma-separated, following the Web KeyboardEvent.code specification:
ControlLeft, ShiftLeft, AltLeft, MetaLeft, KeyAKeyZ, Digit0Digit9, F1~F12, Enter, Escape, Backspace, Tab, Space, Delete, etc.
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
-H "Content-Type: text/plain" \
--data-raw "Hello, World!" \
"https://<IP>/api/hid/print"
# Slow mode (better compatibility)
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
-H "Content-Type: text/plain" \
--data-raw "Hello" \
"https://<IP>/api/hid/print?slow=true"
Query parameters:
limit (int, default 1024): Maximum characters to send, 0 = unlimitedkeymap: Key mapping nameslow (bool): Slow mode, adds delay per keyNote: Only characters present in the keymap are supported; special characters such as Chinese cannot be typed directly.
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/reset"
Call this when keys are stuck or the state is abnormal.
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/hid"
Returns keyboard/mouse online status, LED indicators (CapsLock/NumLock/ScrollLock), and mouse positioning mode (absolute/relative).
# Left click
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_button?button=left"
# Right click
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_button?button=right"
# Middle click
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_button?button=middle"
# Left button press (hold, for dragging)
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_button?button=left&state=true"
# Left button release
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_button?button=left&state=false"
Coordinate system: (0,0) = screen center; (-32768,-32768) = top-left; (32767,32767) = bottom-right
# Move to screen center
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_move?to_x=0&to_y=0"
# Move to top-left corner
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_move?to_x=-32768&to_y=-32768"
Pixel coordinate conversion (screen resolution W x H, target pixel px, py):
to_x = round(px / W * 65535 - 32768)
to_y = round(py / H * 65535 - 32768)
# Move right 50, down 30
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_relative?delta_x=50&delta_y=30"
Range -127 ~ 127; call multiple times for larger movements.
# Scroll up
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_wheel?delta_x=0&delta_y=3"
# Scroll down
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/hid/events/send_mouse_wheel?delta_x=0&delta_y=-3"
delta_y positive = scroll up, negative = scroll down; range -127 ~ 127.
Fingerbot controls a physical press robot via Bluetooth to simulate pressing physical buttons (power button, reset button, etc.).
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/fingerbot/exist"
Returns result.exist: true if the Bluetooth adapter is connected.
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/fingerbot/battery"
Returns result.battery: battery percentage from 0 to 100.
# Short press (100ms, high angle, suitable for normal buttons)
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/fingerbot/click?press_time=100&angle_enum=2"
# Short press power button to power on (500ms)
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/fingerbot/click?press_time=500&angle_enum=2"
# Long press power button to force shutdown (5 seconds)
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/fingerbot/click?press_time=5000&angle_enum=2"
# Press reset button (200ms)
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/fingerbot/click?press_time=200&angle_enum=2"
Parameter description:
press_time: Press duration (milliseconds), range 100~60000angle_enum: 1 = low angle (light press), 2 = high angle (deep press)curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/fingerbot/local_version"
ATX power control is achieved through Fingerbot physical pressing (no separate ATX interface in the API).
Before use, confirm that Fingerbot is connected and installed near the host's power/reset button:
curl -sk -b /tmp/glkvm_cookies.txt "https://<IP>/api/fingerbot/exist"
curl -sk -b /tmp/glkvm_cookies.txt "https://<IP>/api/fingerbot/battery"
| Operation | Command |
|---|---|
| Power on | click?press_time=500&angle_enum=2 |
| Normal shutdown (trigger ACPI) | click?press_time=500&angle_enum=2 |
| Force power off | click?press_time=5000&angle_enum=2 |
| Reset | click?press_time=200&angle_enum=2 |
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/upgrade/reboot"
Note: This reboots the GLKVM device, not the controlled host.
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/upgrade/version"
Response fields:
result.version: Local firmware version stringresult.model: Device model (e.g., RM1)curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/upgrade/compare"
Response fields:
result.local_version / result.local_model: Current firmware version and modelresult.server_version / result.server_model: Latest version available on the update serverresult.release_note: English release notesresult.release_note_cn: Chinese release notesTrigger cloud download (returns immediately, downloads in background):
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/upgrade/download"
Response: result.size = total firmware size in bytes.
Check download progress:
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/upgrade/download_info"
Response: result.size = bytes downloaded so far, result.total_size = total size.
Cancel an in-progress download:
curl -sk -b /tmp/glkvm_cookies.txt \
"https://<IP>/api/upgrade/download_cancel"
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
-F "file=@/path/to/update.img" \
"https://<IP>/api/upgrade/upload"
multipart/form-data, field name fileContent-Length header (curl sets it automatically)/userdata/update.img on the deviceresult.filename (original filename) and result.size (bytes)# Upgrade and preserve existing config (default)
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/upgrade/start?save_config=true"
# Upgrade and reset to factory defaults
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/upgrade/start?save_config=false"
result.status ("Upgrade started" or "Upgrade failed"), result.stdout, result.stderrTypical OTA upgrade workflow:
1. /api/upgrade/compare → check if update available
2. /api/upgrade/download → start background download
3. /api/upgrade/download_info (poll) → wait until size == total_size
4. /api/upgrade/start → apply upgrade (device reboots)
Download an ISO image from a remote URL directly to the MSD storage, without transferring through the client machine.
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/msd/write_remote?url=http://example.com/ubuntu.iso"
# Specify target filename
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/msd/write_remote?url=http://example.com/ubuntu.iso&image=ubuntu.iso"
# Skip TLS verification for HTTPS URLs
curl -sk -b /tmp/glkvm_cookies.txt -X POST \
"https://<IP>/api/msd/write_remote?url=https://example.com/ubuntu.iso&insecure=1"
Query parameters:
url (required): Remote file download URLimage (optional): Target image name on MSD storage; auto-inferred from URL if omittedprefix (optional): Subdirectory path prefixinsecure (optional): Skip TLS certificate verification, default falsetimeout (optional): Connection timeout in seconds, default 10.0remove_incomplete (optional): If 1, deletes partial file on write failureResponse: Streaming NDJSON (Content-Type: application/x-ndjson)
Each line is a JSON object reporting progress; the last line is the final result:
{"image": {"name": "ubuntu.iso", "size": 1234567890, "written": 102400000}}
Fields:
image.name: Image filenameimage.size: Total file size in bytes (0 if server did not return Content-Length)image.written: Bytes written so farError responses:
400: Remote URL unreachable or request failed507: Insufficient storage space on MSD partitionTypical workflow for remote ISO installation:
1. /api/msd/partition_disconnect → ensure drive is disconnected
2. /api/msd/write_remote?url=<ISO_URL> → download ISO directly from internet
3. (Poll NDJSON stream until size == written)
4. /api/msd/partition_connect → present drive to target host
5. (On target host) boot from USB drive, complete installation
6. /api/msd/partition_disconnect → disconnect when done
1. Take screenshot -> View with Read tool -> Analyze target element pixel coordinates (px, py)
2. Confirm screen resolution W x H (inferred from screenshot image size)
3. Convert to HID coordinates:
to_x = round(px / W * 65535 - 32768)
to_y = round(py / H * 65535 - 32768)
4. send_mouse_move to move
5. send_mouse_button?button=left to click
6. Take screenshot to verify
1. Take screenshot to confirm focus is in the correct input field
2. /api/hid/print to send text
3. Take screenshot to confirm input is correct
Initialize login -> Take screenshot to observe -> Perform action -> Take screenshot to verify -> Loop until complete
| Situation | Solution |
|---|---|
| Login returns 401/403 | Wrong password, ask the user again |
| Screenshot returns 503 | Video stream unavailable, check HDMI connection and retry |
| HID key stuck | Call /api/hid/reset to release all keys |
| Fingerbot exist returns false | Bluetooth adapter not connected, cannot use Fingerbot/ATX |
| Cookie expired (401) | Re-execute login process |
| No response to operation | Take screenshot to confirm current state, then decide next step |