Distant API Protocol Reference

March 16, 2026 ยท View on GitHub

After the setup phase completes, the manager exchanges request and response messages with the plugin as JSON-lines. Each request maps to one or more responses. Responses are either synchronous (one response per request) or streaming (multiple asynchronous responses over time).

Capabilities

Plugins advertise supported operations via capability strings in their Version response. Clients can query capabilities to determine which operations are available before attempting them.

CapabilityConstantDescription
tcp_tunnelCAP_TCP_TUNNELForward TCP tunneling (server connects out)
tcp_rev_tunnelCAP_TCP_REV_TUNNELReverse TCP tunneling (server listens for incoming)

Request Types

File Operations

RequestFieldsResponseDescription
file_readpathBlobRead file contents as bytes
file_read_textpathTextRead file contents as UTF-8 text
file_writepath, dataOkWrite bytes to file (creates/overwrites)
file_write_textpath, textOkWrite UTF-8 text to file
file_appendpath, dataOkAppend bytes to file
file_append_textpath, textOkAppend UTF-8 text to file

Directory Operations

RequestFieldsResponseDescription
dir_readpath, depth, absolute, canonicalize, include_rootDirEntriesList directory contents
dir_createpath, allOkCreate directory (optionally recursive)

Path Operations

RequestFieldsResponseDescription
removepath, forceOkRemove file or directory
copysrc, dstOkCopy file or directory
renamesrc, dstOkRename/move file or directory
existspathExistsCheck if path exists
metadatapath, canonicalize, resolve_file_typeMetadataGet file/directory metadata
set_permissionspath, permissions, optionsOkSet file permissions

Watch Operations (Streaming)

RequestFieldsResponseDescription
watchpath, recursive, only, exceptOk + streaming ChangedWatch path for filesystem changes
unwatchpathOkStop watching a path

Search Operations (Streaming)

RequestFieldsResponseDescription
searchquerySearchStarted + streaming SearchResults + SearchDoneSearch files by content or path pattern
cancel_searchidOkCancel an active search

Process Operations (Streaming)

RequestFieldsResponseDescription
proc_spawncmd, environment, current_dir, ptyProcSpawned + streaming ProcStdout/ProcStderr/ProcDoneSpawn a remote process
proc_killidOkKill a running process
proc_stdinid, dataOkWrite to a process's stdin
proc_resize_ptyid, sizeOkResize a process's PTY

Tunnel Operations (Streaming)

RequestFieldsResponseDescription
tunnel_openhost, portTunnelOpened + streaming TunnelData/TunnelClosedOpen a forward TCP tunnel (server connects to host:port)
tunnel_listenhost, portTunnelListening + streaming TunnelIncoming/TunnelData/TunnelClosedStart a reverse TCP listener on the server
tunnel_writeid, dataOkWrite data to an active tunnel
tunnel_closeidOkClose a tunnel or listener

Status Operations

RequestFieldsResponseDescription
status(empty)StatusInfoGet aggregated status (tunnels, future: watchers, processes)

System Operations

RequestFieldsResponseDescription
system_info(empty)SystemInfoGet remote system information
version(empty)VersionGet server version and capabilities

Response Types

ResponseFieldsDescription
ok(empty)Success acknowledgement
errorkind, descriptionError with kind and message
blobdataBinary data (base64 in JSON)
textdataUTF-8 text data
dir_entriesentries, errorsDirectory listing
existsvalueBoolean existence check
metadata(various)File/directory metadata
changed(various)Filesystem change notification
system_info(various)Remote system information
versionserver_version, protocol_version, capabilitiesServer version and capabilities
search_startedidSearch operation started
search_resultsid, matchesSearch matches (streamed)
search_doneidSearch operation complete
proc_spawnedidProcess started
proc_stdoutid, dataProcess stdout data (streamed)
proc_stderrid, dataProcess stderr data (streamed)
proc_doneid, success, codeProcess exited
tunnel_openedidForward tunnel connected
tunnel_listeningid, portReverse listener bound (actual port)
tunnel_dataid, dataData from tunnel (streamed)
tunnel_incominglistener_id, tunnel_id, peer_addrNew connection on reverse listener
tunnel_closedidTunnel or listener closed
status_infotunnelsAggregated status information

Streaming Operations

Several operations produce multiple responses over time. The plugin must continue sending streaming responses until the operation completes or is cancelled.

Process I/O: After ProcSpawned, the plugin streams ProcStdout and ProcStderr as data arrives. The client sends ProcStdin to write to the process. ProcDone signals process exit.

Search: After SearchStarted, the plugin streams SearchResults as matches are found. SearchDone signals search completion. CancelSearch stops the operation early.

Watch: After the initial Ok, the plugin streams Changed responses whenever the watched path changes. Unwatch stops the watch.

Tunneling: After TunnelOpened or TunnelListening, the plugin streams TunnelData as data arrives on the TCP connection. For reverse tunnels, TunnelIncoming is sent for each new connection. The client sends TunnelWrite to push data. TunnelClosed signals the end of a tunnel or listener.


Per-Plugin Support Matrix

Not all plugins support every operation. The table below shows which operations are supported by each built-in plugin:

Operationhostsshdocker
File read/writeYesYesYes
Directory operationsYesYesYes
Path operationsYesYesYes
WatchYesNoNo
SearchYesYesYes (best-effort)
Process spawnYesYesYes
Tunnel open (forward)YesYesYes (best-effort)
Tunnel listen (reverse)YesYesNo
System infoYesYesYes

Notes:

  • ssh forward tunneling uses SSH direct-tcpip channels (channel_open_direct_tcpip). Reverse tunneling uses tcpip_forward via a Mutex-wrapped session handle.
  • docker forward tunneling uses socat or nc inside the container via docker exec. Requires one of these tools to be installed in the container image. Reverse tunneling is not supported because Docker exec's single stdin/stdout pair cannot multiplex multiple incoming connections.
  • docker search uses rg, grep, or find inside the container (best-effort, depends on available tools).

TCP Tunneling Protocol Detail

TCP tunneling allows forwarding arbitrary TCP connections through a distant session. It supports two directions:

  • Forward (ssh -L equivalent): The server connects to a remote host:port on behalf of the client.
  • Reverse (ssh -R equivalent): The server listens on a port and relays incoming connections to the client.

Forward Tunnel Flow

Client CLI              distant protocol           Server (host/ssh/docker)
------------------------------------------------------------------------
local TCP accepted  ->  TunnelOpen{host,port}    -> TcpStream::connect()
                    <-  TunnelOpened{id}          <-
local TCP data      ->  TunnelWrite{id,data}     -> tcp.write(data)
                    <-  TunnelData{id,data}       <- tcp.read() loop
local TCP close     ->  TunnelClose{id}          -> drop tcp
                    <-  TunnelClosed{id}          <- (or remote closes first)
  1. Client sends TunnelOpen with the target host and port.
  2. Server connects to the target via TCP and returns TunnelOpened with a tunnel ID.
  3. Client sends data via TunnelWrite; server relays it to the TCP connection.
  4. Server streams data back via TunnelData as it arrives from the TCP connection.
  5. Either side can close: client sends TunnelClose, or server sends TunnelClosed when the TCP connection drops.

Reverse Tunnel Flow

Client CLI              distant protocol           Server
------------------------------------------------------------------------
                    ->  TunnelListen{host,port}   -> TcpListener::bind()
                    <-  TunnelListening{id,port}  <-
                    <-  TunnelIncoming{lid,tid}   <- listener.accept()
local TCP connect
local TCP data      ->  TunnelWrite{tid,data}    -> tcp.write(data)
                    <-  TunnelData{tid,data}      <- tcp.read() loop
                    ->  TunnelClose{id}           -> drop listener + all subs
  1. Client sends TunnelListen with the bind host and port (port 0 for OS-assigned).
  2. Server binds a TCP listener and returns TunnelListening with the listener ID and actual port.
  3. When a connection arrives, server sends TunnelIncoming with the listener ID, a new tunnel ID, and the peer address.
  4. Data flows bidirectionally via TunnelWrite (client-to-server) and TunnelData (server-to-client).
  5. Closing the listener ID closes the listener and all its sub-tunnels.

SSH Launch Tunneling

SSH launch tunneling eliminates the need for open ports on the remote host by routing the distant protocol through an SSH channel:

Client                  SSH Channel                Remote
------------------------------------------------------------------------
ssh connect + auth  ->                           ->
exec "distant server listen --host 127.0.0.1"   -> server starts (localhost)
read credentials    <-                           <- stdout: host:port:key
channel_open_direct_tcpip(127.0.0.1, port)       ->
distant protocol frames over SSH channel          <-> (no open port needed)

Enable with --tunnel or --ssh.tunnel=true:

distant launch ssh://host --tunnel

The server binds to 127.0.0.1 instead of a public interface, and the client connects through an SSH direct-tcpip channel to 127.0.0.1:port. No firewall rules or port exposure needed.

Tunnel Identification

All tunnels (forward connections, reverse listeners, and reverse sub-connections) share a single ID space. Each ID is unique within a session. This allows TunnelClose to work uniformly โ€” closing a listener ID also closes all its accepted sub-tunnels.

Status / StatusInfo returns all active tunnels with their direction, host, and port:

{"status_info": {"tunnels": [
  {"id": 1, "direction": "forward", "host": "db-host", "port": 5432},
  {"id": 3, "direction": "reverse", "host": "0.0.0.0", "port": 9090}
]}}