Available Commands
The complete reference for every command a Sophon Node can execute — parameters, responses, scopes, risk levels.
Commands are invoked by the agent via the built-in node.command tool, which the Gateway dispatches to the target Node over SignalR. Every command runs inside the Node's platform abstraction — the same API surface on Windows, macOS, and Linux with OS-specific implementations under the hood.
Required scopes appear in every section. See Permissions & Scopes for scope configuration.
Screen
screen.capture
Captures the full primary display.
Scope: screen.capture · Risk: Low
| Param | Type | Default | Notes |
|---|---|---|---|
quality | int | 80 | JPEG quality (1–100) |
maxWidth | int? | null | Optional max width; aspect ratio preserved |
Response: { image: "<base64-jpeg>", sizeBytes, format: "jpeg" }
screen.region
Captures a rectangular region.
Scope: screen.capture · Risk: Low
| Param | Type | Required | Notes |
|---|---|---|---|
x, y, width, height | int | yes | Region bounds |
quality | int | no (80) | JPEG quality |
Response: { image, sizeBytes, format, region: { x, y, width, height } }
screen.monitors
Lists the attached displays and their geometry.
Scope: screen.capture · Risk: Low
Response: { count, monitors: [ { id, isPrimary, x, y, width, height, scaleFactor, name } ] }
screen.queryElements
Returns the accessibility tree of the focused (or all) windows — useful for reliable, coordinate-free automation. Backed by UI Automation on Windows, the AX API on macOS, and AT-SPI on Linux.
Scope: screen.capture · Risk: Low
| Param | Type | Default | Notes |
|---|---|---|---|
includeBackground | bool | false | Include unfocused windows |
maxDepth | int | 8 | Maximum tree depth |
roleFilter | string | – | Only return elements of a given role |
nameFilter | string | – | Only return elements whose name matches |
Response: { elements: [...], count, focusedWindowTitle, warning? }. On Linux/Wayland the tree may be empty with a warning when the AT-SPI bus is unavailable.
Mouse
input.mouse.move
Move cursor to absolute coordinates.
Scope: input.control · Risk: Medium
| Param | Type | Required | Notes |
|---|---|---|---|
x, y | int | yes | Target coordinates |
input.mouse.click
Scope: input.control · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
x, y | int | required | Click coordinates |
button | string | "left" | "left", "right", "middle" |
doubleClick | bool | false | Double-click |
input.mouse.scroll
Scope: input.control · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
x, y | int | required | Scroll anchor |
deltaX, deltaY | int | 0 | Scroll amount (pixels) |
Keyboard
input.keyboard.type
Types a string character by character.
Scope: input.control · Risk: Medium
| Param | Type | Required | Notes |
|---|---|---|---|
text | string | yes | Text to type |
input.keyboard.press
Presses a single named key.
Scope: input.control · Risk: Medium
| Param | Type | Required | Notes |
|---|---|---|---|
keys | string | yes | Key name — enter, escape, tab, f5, backspace, delete, home, end, pageup, pagedown, arrows, etc. |
input.keyboard.hotkey
Modifier combinations.
Scope: input.control · Risk: Medium
| Param | Type | Required | Notes |
|---|---|---|---|
keys | string | yes | "ctrl+c", "alt+f4", "cmd+space", "win+d", "ctrl+shift+t" |
Applications
app.launch
Launch an application by name or path.
Scope: app.manage · Risk: Medium
| Param | Type | Required | Notes |
|---|---|---|---|
name | string | yes | App name (chrome, notepad) or full path |
args | string[] | no | Command-line args |
Response: { pid: 5678, app: "chrome.exe" }
app.close
Close a running application (graceful — sends WM_CLOSE or equivalent, then kills after 5 s).
Scope: app.manage · Risk: Medium
| Param | Type | Required | Notes |
|---|---|---|---|
name | string | either | App name |
pid | int | either | Process ID |
app.focus
Bring an app's window to the foreground.
Scope: app.manage · Risk: Medium
| Param | Type | Required | Notes |
|---|---|---|---|
name | string | either | App name |
pid | int | either | Process ID |
app.list
List apps with visible windows.
Scope: app.manage · Risk: Medium
Response: { count: 5, apps: [ { name, pid, windowTitle } ] }
Windows
window.list
All visible windows with geometry.
Scope: window.manage · Risk: Low
Response:
{
"count": 3,
"windows": [
{ "id": "0x00010234", "title": "Untitled - Notepad",
"x": 100, "y": 100, "width": 800, "height": 600,
"processName": "notepad.exe" }
]
}window.move
Move and optionally resize.
Scope: window.manage · Risk: Low
| Param | Type | Required | Notes |
|---|---|---|---|
windowId | string | yes | From window.list |
x, y | int | yes | New position |
width, height | int | no | Keeps current if omitted |
window.minimize, window.maximize
Scope: window.manage · Risk: Low
| Param | Type | Required | Notes |
|---|---|---|---|
windowId | string | yes | From window.list |
Clipboard
clipboard.read
Scope: clipboard.access · Risk: Low
Response: { text: "...", length: 42 }
clipboard.write
Scope: clipboard.access · Risk: Low
| Param | Type | Required | Notes |
|---|---|---|---|
text | string | yes | Text to write |
System
system.info
Machine info.
Scope: system.info · Risk: None
Response:
{
"osDescription": "Microsoft Windows 11.0.26200",
"machineName": "DESKTOP-ABC",
"processorCount": 8,
"totalMemoryMb": 16384,
"dotnetVersion": ".NET 10.0.0",
"uptime": 3600,
"platform": "windows",
"architecture": "x64"
}system.processes
All running processes.
Scope: system.info · Risk: None
Response: { count: 42, processes: [ { pid, name, cpuPercent, memoryMb } ] }
system.execute
Run shell command. Always approval-gated regardless of scope config.
Scope: system.execute · Risk: Critical
| Param | Type | Required | Notes |
|---|---|---|---|
command | string | yes | The full command line, as a single string |
timeoutMs | int | no | Execution timeout (ms) |
The whole command is passed as one string (not a separate args array) so it can be audited and matched against the denylist consistently. It runs via the platform shell:
- Windows:
cmd.exe /c "<command>" - macOS:
/bin/zsh -c "<command>" - Linux:
/bin/bash -c "<command>"
Response: { exitCode, stdout, stderr, durationMs }
Output truncated to 1 MB per stream.
Notifications
notify.show
Show an OS notification.
Scope: notify.send · Risk: None
| Param | Type | Default | Notes |
|---|---|---|---|
title | string | "Notification" | Notification title |
body | string | "" | Notification body |
icon | string | – | Optional icon URL or path |
Implementation:
- Windows: WinRT / shell toast
- macOS:
osascript -e 'display notification ...' - Linux:
notify-send
Canvas
canvas.present
Open the live Canvas for the current session on the Node's own screen.
Scope: canvas.control · Risk: Medium · consent-gated on the device
| Param | Type | Required | Notes |
|---|---|---|---|
sessionId | string | yes* | Session whose Canvas to show (*unless url is given) |
url | string | no | Explicit http/https URL override |
The Node opens the Dashboard Canvas page in the default browser. See Canvas on a Node for the full flow.
Reserved capabilities
Some scopes are reserved for future releases and have no handlers yet — granting them does nothing until the commands ship: filesystem access (filesystem.*), browser automation (browser.*), voice runtime (voice.*), camera (camera.*), and location (location.*).
Calling conventions
From an agent
node.command({
nodeId: "office-pc",
command: "screen.capture",
params: { quality: 80, maxWidth: 1280 }
})The agent selects the target Node by ID. For users with multiple Nodes, agents can call node.list first and ask which to use.
From the Dashboard
The Settings → Devices page lists each paired Node with its status and granted scopes, and surfaces recent command activity — handy for verifying scope configuration.
Listing nodes from the CLI
sophon nodes list # paired nodes and their connection statusCommands themselves are issued by the agent through node.command, not typed as CLI verbs.
Timeouts
Every command has a default 30-second execution timeout. Long-running commands (large screen captures, shell commands) should pass timeoutMs explicitly.
If the Node doesn't respond within the timeout, the Gateway returns { error: "timeout" } but doesn't cancel the in-flight work on the Node. For commands that can't be interrupted cleanly, the side effect may still complete.
Error responses
{
"ok": false,
"error": "not_authorized",
"message": "Node does not have required scope: input.control"
}Common error codes:
not_authorized— scope missingapproval_rejected— approval gate rejected (Critical commands only)timeout— command exceeded its execution budgetnode_offline— Node isn't connected to the Gatewayplatform_unsupported— command not available on this OSinvalid_params— params failed validationinternal_error— unexpected failure on the Node (check Node logs)
Where to go next
- Install & Pair a Node
- Permissions & Scopes — scopes required per command
- Running as a Service — keep the Node online