Skip to content
NEWTry Pilot Cloud

Telepat · io.pilot.otto

Otto

Drive real Chrome tabs from an agent — extract, automate, screenshot, no headless farm. Review setup, usage, and operating details.

Web & Automation · Live on the catalogue

$ pilotctl appstore install io.pilot.otto
Version
v0.20.0
Methods
15
Size
9.2 MB
Permissions
None declared
Platforms
macOS · Linux

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.execRun any otto subcommand. Payload is {"args":[...]} — the verbatim otto argv. Use this for the full CLI surface beyond the curated methods (config, client register/login/remove, pair, listener unsubscribe, extension update, agent install, site-filtered `commands list --site`, filtered `logs list`, etc.). Add "--json" where the command supports it. Example args: ["commands","list","--site","reddit.com","--json"]. Note: interactive/streaming subcommands (setup, settings, logs follow, listener subscribe-network, test with stream/wait flags, mcp serve, start --attached) are not suitable over one-shot IPC.otto.statusRelay daemon status as JSON: running pid, port, uptime, log path, and the list of currently connected browser node IDs. The right preflight before any page command — an empty node list means no Chrome extension node is paired/online.otto.commandsList the automation commands the connected node(s) expose, as JSON. Use to learn which site commands are available before calling otto.test. For a single site, use otto.exec with ["commands","list","--site","","--json"].otto.extractExtract the readable content of a web page through a live paired browser tab, as JSON markdown. Opens a temporary tab on the node, extracts, and closes it. Requires a running relay and a paired Chrome node.otto.extract.formatExtract page content in a chosen format: markdown, distilled_html, clean_html, raw_html, or text. Returns JSON. Like otto.extract but lets you pick the output representation.otto.screenshotCapture a screenshot of a page through a live browser tab; returns JSON (base64 PNG in the envelope, no local Preview window). Requires a running relay and a paired Chrome node.otto.testRun a registered site command on a browser node (opens a tab, runs the command, returns JSON). Provide site (e.g. reddit.com), command id (e.g. getPosts), and a JSON payload string (use {} for none, e.g. {"limit":10}). One-shot; requires relay + paired node.otto.cmdSend a single raw action to the target browser node and return the full JSON envelope. Provide the action name and a JSON payload string (use {} for none). Use for low-level primitives (e.g. primitive.tab.open) not covered by a curated method.otto.logsRead recent relay logs as JSON. For filtered queries (level/source/since/request-id) use otto.exec with ["logs","list",...,"--json"].otto.logs.statusRelay log storage status as JSON (retention, counts, sizes). Check whether logging is healthy and how much history is available.otto.client.statusLocal controller client identity state and where its secret resolves from (env vs keychain), as JSON. Verify the controller is registered and logged in before running page commands.otto.authcodeList pending pairing codes from the relay as JSON. See codes awaiting approval when bringing a new Chrome extension node online (approve with `otto pair ` via otto.exec).otto.extension.infoInstalled Chrome extension artifact metadata (version, unpacked path, checksum) and configured relay URLs, as JSON. Confirm which extension build the host has staged for the Load-unpacked handoff.otto.agent.statusWhich agent frameworks (claude, codex, cursor, vscode, …) currently have the Otto MCP server registered, as JSON.otto.helpDiscovery: every method with params, kind, and latency class.

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.

Run this first — Preflight — check the relay and connected browser nodesCall
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

Extract a page as markdownCall
pilotctl appstore call io.pilot.otto otto.extract '{"url":"https://example.com"}'
→{"stdout":"{\"markdown\":\"# Example...\"}","exit":0}List the site commands a node exposesCall
pilotctl appstore call io.pilot.otto otto.commands '{}'
→{"stdout":"[{\"site\":\"reddit.com\",\"command\":\"getPosts\"}]","exit":0}Run a registered site commandCall
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).

Screenshot a page (base64 PNG in the envelope)Call
pilotctl appstore call io.pilot.otto otto.screenshot '{"url":"https://example.com"}'
→{"stdout":"{\"image_base64\":\"iVBOR...\"}","exit":0}Extract in a specific formatCall
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

v0.20.0Latest—
  • Released v0.20.0

Platform Compatibility

macOSApple Silicon4.6 MBSupportedmacOSIntel5.1 MBSupportedLinuxarm645.3 MBSupportedLinuxamd644.9 MBSupported