eensh — the screenshot tool for agents
Capture an X11 desktop, region, or window as PNG or JPEG — to a file, to stdout, or inline as base64 in the response. Compare two frames to see what changed. Wait for the screen to start or stop changing before capturing. Keep a display session open across calls, so later captures read from memory instead of the screen.
eensh capture --display :1 --region 0,0,1280,720 \
--width 640 --format jpeg --quality 75 \
--base64 --jsonThat one command writes a single JSON document to stdout: the region it came from, the size of the image, the transform between the two, and where the time went — with the image carried inside it. There is no file to read back and no second request.
The three questions that come next are the ones the rest of eensh exists to answer: what changed, has it stopped, and what did it look like along the way?
A capture, in one command
A 1280×720 region of the display, resized to 640 wide, JPEG, and the image embedded as base64 — so the response is self-contained. Add a filename and the same command writes the file instead.
eensh capture --display :1 \
--region 0,0,1280,720 \
--width 640 --format jpeg --quality 75 \
--base64 --jsonWhat it prints
{
"source": { "kind": "region", "display": ":1",
"x": 0, "y": 0,
"width": 1280, "height": 720 },
"image": { "width": 640, "height": 360,
"format": "jpeg", "quality": 75,
"media_type": "image/jpeg",
"byte_length": 24118,
"encoding": "base64",
"data": "/9j/4AAQSkZJRgABAQ..." },
"transform": { "origin": "top-left",
"offset_x": 0, "offset_y": 0,
"scale_x": 2.0, "scale_y": 2.0 },
"timing": { "capture_us": 4210, "resize_us": 1180,
"encode_us": 9640, "base64_us": 210,
"total_us": 15240 }
}One response, and data is the image. Nothing to fetch afterwards.
Or write it instead
eensh capture --display :1 \
--region 0,0,1280,720 \
--width 640 --format jpeg --quality 75 \
--json \
region.jpg # the output path: positional, no leading --region.jpg is the file that appears — and it appears only because it was named. The output path is the one argument with no -- in front of it. Drop it and nothing is written; the bytes go to stdout instead.
Where each thing goes
Image bytes and JSON never share a stream, so there is never a document to parse out of the middle of binary. Which stream you get depends on two flags and on whether you named a file:
stdout
The JSON document, because --json was passed. Without it, stdout carries the raw encoded image instead — which is why the two are never mixed.
stderr
Errors, and anything you asked to be logged — --time writes its per-stage timings here. It also takes the JSON in the one case where stdout is already full of raw bytes: --json without --base64 and without a file.
a file
Only if you name one. The two commands above differ in exactly one respect: the second ends in region.jpg.
Either way the region is 1280 wide and the image is 640. Both sizes are in the response, and the transform says how to get from one to the other — so a click on the image can be translated to a click on the screen without guessing which number meant what.
eensh feature areas
All five are built on one primitive: a capture produces a raw frame, and nothing is encoded until a caller asks for a particular size and format. That is why the areas combine, and why adding one never changed how a frame is captured. presentation is the sixth area — it decides the size, format, and quality of what is sent.