Docs

Commands

The whole surface, what each one prints, and what it exits with.

page-scanner pair     [--port <n>] [--rotate] [--wait <s>=120] [--no-wait] [--json]
page-scanner status   [--json]
page-scanner browsers [--json]
page-scanner tabs     [--browser <id|label>] [--wait <s>=30] [--json]
page-scanner scan     (--url <u> | --tab <id>) [--window <id>] [--browser <id|label>]
                      [--format pdf|png|jpeg=pdf] [--page-size auto|a4|letter=a4]
                      [--quality <0-1>] [--video frame|blank] [--open-editor]
                      [--out <file|dir>] [--wait <s>=30] [--timeout <s>=120] [--json]
page-scanner serve    [--daemon] [--idle <min>]
page-scanner stop     [--json]
page-scanner --version | --help

scan

Exactly one of --url and --tab. --url opens a background tab, captures it and closes it again; --tab takes an id from tabs and never closes what you already had open.

--out is a file when it ends in a 2 to 5 character extension and is not an existing directory, and a directory otherwise, in which case the browser's suggested filename is used inside it. Parent directories are created. A relative path is relative to your working directory, not the daemon's, which is why the daemon hands the bytes back and the command writes them.

--page-size decides what a PDF is laid onto. a4 and letter slice the capture across printable sheets with a half-inch margin; auto is one page the exact size of the capture, which is not printable and which Acrobat clamps past 200 inches.

Waiting

Chrome retires the extension's service worker after about thirty seconds of silence, and it dials back in when something wakes it. --wait covers that reconnect, and defaults to 30 seconds for tabs and scan. --wait 0 means do not wait at all, and fails immediately instead.

What a command prints

  • stdout carries the answer and nothing else. For scan that is one line, the absolute path. For tabs and browsers it is a column-aligned table with no separator row, so awk can read it.
  • stderr carries anything addressed to a person: progress, warnings, the reason something failed.
  • --json puts exactly one JSON document on stdout, for success and for failure alike, and leaves stderr empty.
{
  "ok": true,
  "path": "/Users/you/pdf.pdf",
  "width": 1280,
  "height": 4200,
  "mode": "vector",
  "selectableText": true,
  "truncated": null,
  "browserId": "b-9f2c41",
  "fileName": "PDF - Wikipedia.pdf"
}
{ "ok": false, "code": 3, "error": "NO_BROWSER", "message": "No browser is connected." }

truncated is null rather than absent when the capture was whole, so a reader can see the question was asked. When it is not null it carries what the page measured, what was captured, and a sentence naming the gap.

Exit codes

CodeMeaning
0success
1the browser was reached and the work failed
2the arguments were wrong
3no usable browser: none connected, several connected, or the one named is not
4not paired
5the daemon would not start

The daemon

Started on demand by the first command that needs one, and reused by every command after it, including the MCP server's.

page-scanner serve runs it in the foreground, which is how to see why it will not start. page-scanner stop stops it. It exits on its own after 15 idle minutes (PAGE_SCANNER_IDLE_MINUTES, 0 for never) and never while a browser is attached: the open socket is what keeps Chrome's service worker alive.

Files

Everything lives in ~/.page-scanner, or in $PAGE_SCANNER_HOME if that is set: config.json (the pairing), daemon.json (the running daemon) and daemon.log. The first two are mode 0600, which Windows does not enforce: NTFS has no such mode, so both are readable by anything running as you. On a shared machine, restrict the directory yourself.

icacls %USERPROFILE%\.page-scanner /inheritance:r /grant:r %USERNAME%:F

From Node

import { scan, listTabs } from '@page-scanner/cli';

const { tabs } = await listTabs();
const result = await scan({ tabId: tabs[0].tabId, out: './out/', format: 'pdf' });
console.log(result.path, result.selectableText);

On this page