The notes browser (both basic and runnable versions) provides a JSON-RPC 2.0 remote control interface accessible via TCP, UDP, or Unix domain sockets. This allows programmatic control of browser navigation, document manipulation, sheet execution, and UI operations.
The control interface uses JSON-RPC 2.0 protocol with line-delimited JSON messages (one JSON object per line, terminated by newline character).
{
"jsonrpc": "2.0",
"id": <request-id>,
"method": "<method-name>",
"params": {<parameters>}
}
Fields: jsonrpc must be exactly "2.0". id is any JSON value used to correlate responses (can be null for fire-and-forget). method is the RPC method name. params is an object containing method parameters.
{
"jsonrpc": "2.0",
"id": <request-id>,
"result": <result-value>
}
Success response contains result field. Error response has error field instead with code (integer) and message (string).
Start browser with control socket:
notes-browser/notes_browser_runnable.py \
--url http://localhost:8021 \
--control-unix-enabled \
--control-unix-socket $PWD/socket
Send request with socat or Python socket:
echo '{"jsonrpc":"2.0","id":1,"method":"status.ping","params":{}}' | socat - UNIX-CONNECT:socket
Start browser with TCP enabled (default port 8711):
notes-browser/notes_browser_runnable.py \
--url http://localhost:8021 \
--control-tcp-enabled \
--control-host 127.0.0.1 \
--control-tcp-port 8711
Send with netcat:
echo '{"jsonrpc":"2.0","id":1,"method":"status.ping","params":{}}' | nc 127.0.0.1 8711
Start browser with UDP enabled (default port 8711):
notes-browser/notes_browser_runnable.py \
--url http://localhost:8021 \
--control-udp-enabled \
--control-host 127.0.0.1 \
--control-udp-port 8711
Send with ncat:
echo '{"jsonrpc":"2.0","id":1,"method":"status.ping","params":{}}' | nc -u 127.0.0.1 8711
Available when a runnable sheet (runnable: true) is displayed. All methods error if sheet is busy executing.
sheet.run_allimport json, socket
def call_api(socket_path, method, params=None):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(socket_path)
req = {
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params or {}
}
sock.sendall(json.dumps(req).encode() + b'\n')
response = b''
while True:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk
try:
return json.loads(response.decode())
except json.JSONDecodeError:
continue
sock.close()
# Example: run all cells in current sheet
result = call_api('socket', 'sheet.run_all')
print(json.dumps(result, indent=2))
# Test connection
echo '{"jsonrpc":"2.0","id":1,"method":"status.ping","params":{}}' | socat - UNIX-CONNECT:socket
# Navigate to page
echo '{"jsonrpc":"2.0","id":1,"method":"navigate.go_to_page","params":{"key":"test/page"}}' | socat - UNIX-CONNECT:socket
# Get current page
echo '{"jsonrpc":"2.0","id":1,"method":"page.get_current","params":{}}' | socat - UNIX-CONNECT:socket
# Run all cells in sheet
echo '{"jsonrpc":"2.0","id":1,"method":"sheet.run_all","params":{}}' | socat - UNIX-CONNECT:socket
# Capture screenshot
echo '{"jsonrpc":"2.0","id":1,"method":"ui.capture_screenshot","params":{}}' | socat - UNIX-CONNECT:socket
#!/bin/bash
SOCKET="socket"
# Navigate to sheet
echo '{"jsonrpc":"2.0","id":1,"method":"navigate.go_to_page","params":{"key":"test/video-brightness-analysis"}}' | socat - UNIX-CONNECT:$SOCKET
# Run all cells
echo '{"jsonrpc":"2.0","id":2,"method":"sheet.run_all","params":{}}' | socat - UNIX-CONNECT:$SOCKET
# Export results
echo '{"jsonrpc":"2.0","id":3,"method":"sheet.export_state","params":{"path":"results.json"}}' | socat - UNIX-CONNECT:$SOCKET
Read document JSON, modify it, and write back:
# Get current document JSON
echo '{"jsonrpc":"2.0","id":1,"method":"page.get_document_json","params":{}}' | socat - UNIX-CONNECT:socket > doc.json
# Modify with Python, then write back via API
python3 << 'PYTHON'
import json, socket
# Load current document
with open('doc.json') as f:
response = json.load(f)
doc = response['result']['document']
# Modify document (add cell, etc)
# ...
# Write back
req = {
"jsonrpc": "2.0",
"id": 1,
"method": "page.put_document_json",
"params": {"key": "test/page", "document": doc}
}
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect('socket')
sock.sendall(json.dumps(req).encode() + b'\n')
print(json.loads(sock.recv(4096).decode()))
sock.close()
PYTHON
Always give nc a -w timeout — without it nc hangs after the response arrives, waiting for more data:
# -w 2 closes 2 seconds after last data
echo '{"jsonrpc":"2.0","id":1,"method":"status.ping","params":{}}' | nc -w 2 127.0.0.1 8711
echo '{"jsonrpc":"2.0","id":1,"method":"navigate.go_to_page","params":{"key":"Derek"}}' | nc -w 2 127.0.0.1 8711
echo '{"jsonrpc":"2.0","id":1,"method":"page.get_current","params":{}}' | nc -w 2 127.0.0.1 8711
Control must be enabled at launch — it cannot be toggled on a running instance. Check first with pgrep -af notes_browser and nc -z 127.0.0.1 8711.
# Launch with TCP control (default port 8711)
DISPLAY=:0.0 python3 ~/py/gdata-server/notes-browser/notes_browser.py \
--url http://localhost:8021 \
--control-tcp-enabled &
# Or via environment variable
DISPLAY=:0.0 NOTES_BROWSER_CONTROL_TCP_ENABLED=1 \
python3 ~/py/gdata-server/notes-browser/notes_browser.py \
--url http://localhost:8021 &
# Note: passing a positional socket path (old style) is rejected — use --url