tendril0.0.4

guide

Discover, capture, act, verify

Every Tendril workflow is the same four steps. Each is its own command, with its own JSON envelope, so an agent can stop, think and resume at any point.

Discover targets

tendril listplatform: Linux / X11permissions: 4targets:- Display 1 screen 1920x1080+0+0 scale=1/1 capture=true, input=true- Window 0x600003 Chromium-browser 1918x1053+1+22 scale=1/1 capture=true, input=true title="Tendril Smoke Browser" app=Chromium-browser- Window 0xe00003 Chromium-browser 797x587+125+94 scale=1/1 capture=true, input=true title="Trail log" app=Chromium-browser- Window 0x40000c XTerm 604x316+41+102 scale=1/1 capture=true, input=true title="Tendril Headless Shell" app=XTerm

Targets are windows, displays and cameras. Each has an id, bounds, a scale factor and what it supports: capture, input and, for some, audio. With --json each target also carries its kind, title, application name and process id. Commands that act on a target take exactly one of --window ID or --display ID; capture also accepts --camera ID.

Capture

tendril --window 0xe00003 capture -o before.pngcapture target: Window 0xe00003platform: Linux / X11original: 797x587output: 512x377resized: truesource_to_output: 512/797 x 377/587output_to_source: 797/512 x 587/377format: Png

By default a capture fits a 512 × 512 box, keeping its aspect ratio, so screenshots do not overwhelm an agent's context. --max-width and --max-height raise the bound; capturing a single window rather than a display is usually better than a bigger image. --format png|jpeg and --compression 0–100 trade size for detail. With --json the image comes back as base64 in the envelope; with -o it is written to a file.

Capture space and source spaceOn the left, the window's source space: 797 by 587 pixels, the space run coordinates use, with the Save button at 72, 354. On the right, the capture an agent sees, scaled down to 512 by 377. The same button appears at about 46, 227 in the capture. The capture reports both ratios: source to output, 512 over 797 by 377 over 587, and output to source, 797 over 512 by 587 over 377. Multiply a point in the capture by output to source to get the coordinates to click. source space · 797 × 587 Save (72, 354) what the agent sees capture · 512 × 377 (46, 227) × output_to_source = 797/512 × 587/377 run takes source coordinates; captures may be smaller
Two coordinate spaces.run always takes source coordinates. A capture reports the ratio to convert from what the agent saw.

A camera works the same way: tendril --camera ID capture takes one frame, through AVFoundation on macOS, V4L2 on Linux or DirectShow on Windows.

Semantic elements

Pixels are not the only way in. list-elements reads the platform's accessibility tree (AX on macOS, AT-SPI on Linux, native Win32 controls on Windows) and returns buttons, fields and text with ids and bounds:

tendril --window ID list-elements --jsontendril --window ID run 'click(element-id),send("Ridge loop")'

Element ids belong to one snapshot. They are stable while the interface is, and click(id), press(id) and element(id) list the elements again, find the match and act at the centre of its bounds. When there is no accessibility service, the result says so and falls back to the window tree:

tendril --window 0xe00003 list-elementsplatform: Linux / X11elements: 1notes: X11 AT-SPI element listing failed: org.a11y.Bus.GetAddress failed: … falling back to the X11 window tree.- 1: window<Chromium-browser/Chromium-browser> 797x587+125+94 actions=click

The run language

tendril run takes one argument: plain text, a comma-separated sequence of actions, or an action list from MCP. All three become the same typed model.

tendril --window ID run 'hello world'                                    # types the texttendril --window ID run 'hold(ctrl),c,release(ctrl),wait(250ms),send("done")'tendril --window ID run 'hover(320,240),wait(250ms)'tendril --window ID run 'drag(100,200,400,200)'tendril --window ID run 'scroll(220,420,8)'
ActionDoes
send("…")Type text
Return, Tab, c, …Tap a key
hold(ctrl), release(ctrl)Press and release a modifier around other actions
wait(250ms), wait(1s)Pause; a bare number means milliseconds
lclick(x,y), rclick, mclickClick a button at source coordinates
dblclick(x,y)A calibrated double-click (alias doubleclick)
move(x,y)Move the pointer without clicking, for hovers and tooltips (alias hover)
drag(x0,y0,x1,y1)Press, move and release
scroll(x,y,dy)Wheel ticks under a point; positive scrolls down
click(id), press(id)Act on a semantic element

Android adds its own verbs (launch, tap_text, scroll_until, assert_visible, back, notifications and more); see Platforms.

Guards

Some inputs are refused because typing them would almost certainly be a mistake:

On X11, text that the keyboard map cannot type directly, such as most non-ASCII text, is pasted through a short-lived clipboard selection instead. Terminals get a terminal-compatible paste shortcut, and the envelope's notes say what was done.

Focus and the shared desktop

An agent often works on a desktop someone else is using. run therefore:

  1. Waits its turn

    A host-wide lock serialises every run, from any process or MCP server (see Agents).

  2. Records where you were

    On X11, the focused window and the pointer position.

  3. Focuses the target and acts

    So keys and clicks reach the requested window, not whatever was on top.

  4. Puts things back

    Focus and pointer are restored unless --no-restore-focus is given. Where an adapter cannot restore reliably yet, the run still succeeds and reports focus_restored: false with a note.

Audio

tendril listen records WAV audio with PipeWire or PulseAudio on Linux and CoreAudio on macOS, writing a temporary file or --output PATH; the envelope reports where. Elsewhere it reports capability and permission state only, as probe_only.

Aliases instead of sessions

Tendril keeps no session, so it cannot remember a target. It can write a shell function that does:

eval "$(tendril --window 0xe00003 alias --name trail)"trail capture --jsontrail run 'send("hello")'

--shell bash|zsh|fish|powershell chooses the syntax.