Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Drive a browser from a program

One browser on a pipe with navigera serve: one JSON command per line in, one reply out.

Programs can keep one browser on a pipe instead: navigera serve reads one JSON command per stdin line, for example {"id":1,"op":"goto","url":"https://example.com"}. The session socket speaks the same protocol.

Serve protocol (programs)

ax without format returns the flat JSON list ([{ref, role, name, value?, url?}], url on links; main frame only) that serve clients have always received; send "format":"text" for the indented tree the CLI prints.

{"id":1,"op":"goto","url":"https://example.com"}
{"id":2,"op":"ax","format":"text"}
{"id":3,"op":"click","ref":12}
{"id":4,"op":"fill","selector":"#q","value":"widget","timeout_ms":3000}
{"id":5,"op":"select","text":"Country","value":["Canada"]}
{"id":6,"op":"wait","text":"Saved"}
{"id":7,"op":"eval","expression":"() => document.title"}
{"id":8,"op":"quit"}

Each op’s fields are the long flag names in snake case: selector, ref, text, value, timeout_ms, wait, format, limit, by, to, files, ms, js, accept, prompt_text and so on. Malformed lines and unknown ops return ok:false and leave the session running. EOF or quit closes the browser. The session socket speaks exactly this protocol, one connection at a time.

A line holding a JSON array of commands is a batch: each runs in order and gets its own response line (a failure doesn’t stop the rest; quit does). Use it where the shell can’t send newlines: PowerShell’s echo passes \n through literally, so echo '{…}\n{…}' | navigera serve is one bad line, while echo '[{…},{…}]' | navigera serve works. A leading UTF-8 BOM (PowerShell 5 adds one) is ignored.

Driving serve from a program. Read one response line per command line (a batch: one per element), match responses by id, and give every read a timeout. Read or discard stderr (stderr=DEVNULL in Python): a full stderr pipe stalls the server. Decode stdout as UTF-8 (encoding="utf-8" in Python; Windows’ default code page can’t decode page text). A minimal Python client:

import json, subprocess
nv = subprocess.Popen(["navigera", "serve"], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                      stderr=subprocess.DEVNULL, text=True, encoding="utf-8")
def call(op, _id=[0], **fields):
    _id[0] += 1
    nv.stdin.write(json.dumps({"id": _id[0], "op": op, **fields}) + "\n"); nv.stdin.flush()
    reply = json.loads(nv.stdout.readline())
    assert reply["id"] == _id[0], reply
    return reply
call("goto", url="https://example.com")
print(call("ax", format="text")["result"])
call("quit"); nv.wait()

An agent working from a shell needs none of this: use a session (navigera -s work start, then one command per call). Per call, a direct session command costs about 3 ms more than a line on a serve pipe (Linux, warm session: title 3.3 vs 0.5 ms, ax 14.7 vs 11.1 ms), far less than one model turn. A wrapper pays off only for long runs with no decision between steps, written by a program that already exists; a script that launches its own serve pays a browser cold start and loses cookies on every run.


Made from docs/guides/programs.md, docs/navigera.md.