Skip to content

Control a browser from the terminal with Maho CLI

Maho CLI Terminal Browser Control

If you want to control a web browser from the terminal, Maho gives you a single maho CLI for the browser state you already have open. You can list and open tabs, inspect history and bookmarks, extract the active page as text or Markdown, render a URL headlessly, or expose the same browser to an MCP client.

The short version is:

Terminal window
maho tab list
maho tab open https://example.com
maho page markdown
maho headless https://example.com

That is a different goal from browser test automation. Maho CLI is for terminal browser control and scripting against your real Maho profile. If you need deterministic end-to-end tests with assertions, fixtures, and clean browser contexts, Playwright remains the better abstraction.

For the complete command catalog, keep the Maho CLI reference open next to this tutorial.

Shell tools are excellent at composition. Browsers are excellent at rendering the modern web. The awkward part is the boundary between them.

curl is perfect when the HTTP response is the data. It is less useful when the content you need is produced by client-side JavaScript, lives in a logged-in profile, or is already open in the browser you are using for research.

A browser CLI lets you keep the shell workflow:

browser -> stdout -> jq / rg / fzf / llm / file

without pretending a rendered page is the same thing as an HTTP response.

Maho’s command groups make that separation explicit:

  • maho tab controls live tabs.
  • maho history queries local history.
  • maho bookmarks reads and creates bookmarks.
  • maho page extracts the active rendered page.
  • maho headless <url> starts from a URL instead of the active tab.
  • maho mcp hands browser tools to an external MCP host.

Install/start Maho Browser and make sure maho is on your PATH:

Terminal window
maho --version
maho tab list

Then ask for the active tab:

Terminal window
maho tab active

For scripts, immediately get in the habit of requesting JSON:

Terminal window
maho --json tab active | jq '.'

Why start with jq '.' instead of a clever filter? Because the first job is to see the structured shape your installed Maho build actually returns. Once you know that shape, narrow the filter for the script you are writing.

The full tab command reference is at Browser Control CLI.

Open a page:

Terminal window
maho tab open https://example.com

Quote URLs containing shell metacharacters:

Terminal window
maho tab open 'https://example.com/search?q=browser+cli&sort=new'

List the tabs again, choose the runtime tab id, then close one:

Terminal window
maho --json tab list | jq '.'
maho tab close <tab-id>

Do not build automation around a tab’s visual position. A tab id is the better handle because tabs move when the user reorders them or other scripts open new ones.

Use human output for humans and JSON for programs.

This is good:

Terminal window
maho --json tab list | jq '.'

This is brittle:

Terminal window
# Avoid splitting aligned terminal output by spaces.
maho tab list | awk '{print $1}'

The second script quietly depends on presentation formatting that is allowed to change independently of the JSON contract.

3. Extract the page you are actually looking at

Section titled “3. Extract the page you are actually looking at”

The active tab is already rendered. You do not need to re-download it just to get the content.

For plain readable words:

Terminal window
maho page text

For document structure:

Terminal window
maho page markdown

For raw markup:

Terminal window
maho tab html

For targeted extraction, use the extract entry point and inspect the exact arguments exposed by your installed build:

Terminal window
maho page extract --help
maho --json page extract <extraction-arguments> | jq '.'

A useful mental model is:

You needUse
wordsmaho page text
headings, lists, links, document structuremaho page markdown
one structured regionmaho page extract …
the DOM/markup itselfmaho tab html

That choice matters for downstream AI. Sending 150 KB of HTML to a model when 18 KB of Markdown contains the same article wastes context and makes the prompt harder to inspect.

See Page Extraction CLI for the canonical reference.

4. Pipe browser content to ordinary Unix tools

Section titled “4. Pipe browser content to ordinary Unix tools”

Once page content is on stdout, the browser disappears from the rest of the pipeline.

Count words:

Terminal window
maho page text | wc -w

Search the rendered page:

Terminal window
maho page text | rg -n 'MCP|permission|credential'

Keep a copy while continuing the pipeline:

Terminal window
maho page markdown \
| tee /tmp/page.md \
| rg '^## '

This style is deliberately boring. Boring pipelines are debuggable pipelines. If the output is wrong, you can inspect /tmp/page.md and determine whether the problem happened in browser extraction or later.

Any model CLI that reads stdin can sit after Maho.

Terminal window
maho page markdown \
| llm 'Summarize this page in five bullets. Keep all concrete numbers.'

Ask for structured output and validate it with jq:

Terminal window
maho page markdown \
| llm 'Return a JSON array of dates, organizations, and numerical claims.' \
| jq '.'

The llm executable here is just an example of a downstream model CLI. Maho does not require it. The important architecture is that browser extraction and model inference are separate processes.

That has three practical benefits:

  1. You can inspect exactly what the model received.
  2. You can swap model providers without changing browser control.
  3. You can choose not to use a model at all when jq, rg, or a small script is enough.

Sometimes there is no useful active tab. The URL is the input.

Terminal window
maho headless https://example.com

Now the browser renderer becomes a source command:

Terminal window
maho headless https://example.com \
| llm 'Explain the main claim, evidence, and missing caveats.'

Or capture first, analyze second:

Terminal window
maho headless https://example.com > /tmp/example-page.txt
llm 'Summarize this page.' < /tmp/example-page.txt

This is the core difference from curl: the command is intended to give you a browser-rendered result, not simply the raw HTTP response body.

Terminal Pipeline Data Flow

7. Search history and bookmarks without opening UI panels

Section titled “7. Search history and bookmarks without opening UI panels”

Terminal browser control is not only about tabs. Maho also exposes local browsing data.

Search history:

Terminal window
maho history search 'release notes'
maho --json history search 'release notes' | jq '.'

See today’s history:

Terminal window
maho history today

Inspect top history entries/sites:

Terminal window
maho history top

Search bookmarks:

Terminal window
maho bookmarks search 'rust'

Add a bookmark:

Terminal window
maho bookmarks add https://example.com

These commands are useful when the next step is already a shell step. There is no reason to open a History UI, copy a URL, switch to the terminal, and paste it if the history database can be queried directly.

8. Use tab eval only when you actually need JavaScript

Section titled “8. Use tab eval only when you actually need JavaScript”

The CLI has an explicit JavaScript evaluation command:

Terminal window
maho tab eval 'document.title'

It can return structured, serializable data:

Terminal window
maho --json tab eval '({ title: document.title, links: document.links.length })' \
| jq '.'

This is powerful and therefore intentionally not the default extraction path. Prefer page text, page markdown, or page extract when one of those expresses the job.

Treat tab eval like DevTools code execution. Do not interpolate untrusted strings into it.

Also note an important product boundary: Maho’s MCP server does not expose a generic eval tool. Agent-controlled page access uses typed tools so browser-side redaction and policy can be enforced. The MCP reference explains why.

Two calm paths from a terminal window: one to an AI-guided browser, one to an external MCP client handshake

A shell pipeline is best when you know the steps.

When the task itself needs to decide which browser actions to take, use an agent surface instead of scripting one yourself:

  • Maho AI panel — the in-browser agent decides and acts with the same browser-side policy boundaries.
  • Your own MCP-capable agent (Claude Code, Cursor, or any MCP host) — run maho mcp and let the external model drive the browser tool surface. The model lives outside; browser policy stays inside Maho.

The decision is straightforward:

known deterministic steps -> shell pipeline
AI decides the steps -> Maho AI panel or your MCP client
external AI host -> maho mcp

Here is a deliberately simple pattern for capturing two pages and asking a model to compare only the captured evidence:

Terminal window
{
echo '# Source A'
maho headless https://example.com/a
echo
echo '# Source B'
maho headless https://example.com/b
} | tee /tmp/sources.md \
| llm 'Compare only these two sources. List agreements, contradictions, and missing evidence.'

Why tee? Because it gives you the exact evidence bundle independently of the model answer. If the answer looks suspicious, inspect /tmp/sources.md before you rerun anything.

For a machine-readable collection step, request JSON at each structured Maho boundary and normalize with jq before analysis.

Where Maho CLI fits next to Playwright and browser MCP

Section titled “Where Maho CLI fits next to Playwright and browser MCP”

These tools overlap in the word “browser” but optimize for different jobs.

Use it for personal/developer shell workflows against Maho Browser: current tabs, local history, bookmarks, page extraction, and headless URLs.

Use it for repeatable browser automation/tests where you control the entire flow, browser context, assertions, and fixtures.

Use it when Claude Desktop, Cursor, or another MCP host should decide which browser tool to call. The model is outside Maho; browser policy remains inside Maho.

You can use all three in one development environment without pretending they are substitutes.

Terminal window
# Browser state
maho tab list
maho tab active
maho tab open https://example.com
maho tab close <tab-id>
# Page data
maho page text
maho page markdown
maho page extract --help
maho tab html
# URL -> browser-rendered stdout
maho headless https://example.com
# Local browsing data
maho history search 'query'
maho history top
maho history today
maho bookmarks list
maho bookmarks search 'query'
maho bookmarks add https://example.com
# MCP
maho mcp