Otto
pilotctl appstore install io.pilot.otto About Otto
Otto is secure remote browser automation. A controller CLI sends commands over an authenticated WebSocket to a relay daemon, which routes them to a Chrome extension running on live tabs. Code drives the browser deterministically; the agent decides what to do, not how to click.
What an agent gets: - Content extraction — otto.extract / otto.extract.format turn a URL into clean markdown, distilled/clean/raw HTML, or text through a real tab. - Site commands — otto.test runs registered actions on real sessions (Reddit/LinkedIn getPosts, Hacker News getFrontPage, Google getSearchResults, …); otto.commands lists what a node exposes. - Page & tab control — otto.screenshot (viewport or full page) and otto.cmd for low-level primitives (tab open/navigate/query, DOM extract). - Status & diagnostics — otto.status (relay + connected nodes), otto.client.status, otto.authcode, otto.extension.info, otto.logs/otto.logs.status, otto.agent.status — all JSON. - Full CLI surface — otto.exec runs any verbatim otto argv.
Good to know: - Returns JSON wherever the CLI offers it; discover the live surface at runtime with otto.help — every method, its parameters, and its latency class (fast / med / slow). - Real browser tabs, not a headless farm — no Docker, Puppeteer farm, or cloud-browser rental. - Prerequisite stack on the host: a running relay (otto start), Chrome with the Otto extension loaded and paired as a node, and a logged-in controller. Until that is up, page commands return a structured error ({stdout,stderr,exit}); otto.status is the right preflight. - Runs on macOS and Linux (arm64 + amd64); the otto CLI is staged as a self-contained binary and sha-pinned on install. Free and open source (MIT) — no payment, no per-call limit.
Methods · 15
otto.exec otto.status otto.commands otto.extract otto.extract.format otto.screenshot otto.test otto.cmd otto.logs otto.logs.status otto.client.status otto.authcode otto.extension.info otto.agent.status otto.help Full usage demo
When you need to drive a real Chrome tab from an agent — extract a page as markdown/HTML, run site commands (Reddit/LinkedIn/HN/Google), or screenshot — via a paired browser extension, not a headless farm.
pilotctl appstore call io.pilot.otto otto.status '{}' {"stdout":"{\"pid\":...,\"nodes\":[\"node-1\"]}","exit":0}An empty nodes list means no Chrome extension node is paired/online — page commands will fail until one is.
Worked examples
pilotctl appstore call io.pilot.otto otto.extract '{"url":"https://example.com"}' {"stdout":"{\"markdown\":\"# Example...\"}","exit":0}pilotctl appstore call io.pilot.otto otto.commands '{}' {"stdout":"[{\"site\":\"reddit.com\",\"command\":\"getPosts\"}]","exit":0}pilotctl appstore call io.pilot.otto otto.test '{"site":"reddit.com","command":"getPosts","payload":"{\"limit\":10}"}' {"stdout":"{\"posts\":[...]}","exit":0}payload is a JSON object STRING (use {} for none).
pilotctl appstore call io.pilot.otto otto.screenshot '{"url":"https://example.com"}' {"stdout":"{\"image_base64\":\"iVBOR...\"}","exit":0}pilotctl appstore call io.pilot.otto otto.extract.format '{"url":"https://example.com","format":"clean_html"}' {"stdout":"{\"clean_html\":\"<article>...\"}","exit":0}Good to know
- Preflight with otto.status — an empty nodes list means no Chrome extension node is paired/online and page commands will fail.
- Requires the host stack up: a running relay (otto start), Chrome with the Otto extension loaded + paired, and a logged-in controller.
- otto.test payload is a JSON object STRING (use {} for none), e.g. "{\"limit\":10}" — not a bare object.
- Page commands (extract, screenshot, test) are slow — each opens a real tab, acts, then closes it.
- Free and open source (MIT) — no payment, no per-call limit.
Next
io.pilot.otto otto.help '{}'
What’s New
- Released v0.20.0