sctl connects AI clients and command-line workflows to the ScriptCat browser extension and to its own sctl Browser browser extension. One cross-platform binary provides a local bridge daemon, a stdio MCP server, and script-management and browser-control commands.
AI client ── stdio MCP ──▶ sctl mcp ── local control API ──▶ sctl serve ── WebSocket ──▶ ScriptCat
CLI ────────────────────────────────────────────────────────▲
ScriptCat remains the authority: source disclosure and every write request are governed by the policies and confirmation UI in the extension.
- Exposes ScriptCat operations and browser tab/window control as discoverable, schema-typed MCP tools, plus ten
per-domain browser tools (
bookmarks,reading_list,tabs_manage,tab_groups,history,recently_closed,downloads,cookies,browsing_data,extensions) that pick the operation with anactionargument. - Lists scripts and reads metadata or source, including line windows and source search.
- Requests installation, content-anchored editing, enable/disable, and deletion through browser approval.
- Lists, opens, closes, activates, moves, pins, mutes, reloads, and duplicates tabs, and lists, opens, closes, focuses, and resizes windows across one or more paired sctl Browser instances.
- Lists, creates, edits, and dissolves tab groups on a paired sctl Browser instance.
- Lists, adds, marks read or unread, and removes reading list entries on a paired sctl Browser instance.
- Lists, searches, adds, moves, and edits bookmarks and bookmark folders on a paired sctl Browser instance, and deletes them after approval in that browser.
- Searches and clears history, restores recently closed tabs and windows, manages downloads, reads and changes cookies, clears browsing data, and lists, enables, disables, or (after approval) uninstalls extensions.
- Takes accessibility snapshots with element refs of, clicks, hovers, fills, types into, selects options in, uploads files to, scrolls, navigates, waits on, and evaluates JavaScript in, a page of a paired sctl Browser tab, in the background without switching tabs.
- Uses JSON-RPC 2.0 over a WebSocket with mutual authentication; the listener defaults to loopback.
- Ships as one binary; no browser automation or Native Messaging host is required.
Install the latest release with one command — macOS and Linux:
curl -fsSL https://raw.githubusercontent.com/scriptscat/sctl/main/scripts/install.sh | shor Windows PowerShell:
irm https://raw.githubusercontent.com/scriptscat/sctl/main/scripts/install.ps1 | iexThe installer downloads the hyphen-named release archive sctl-<version>-<os>-<arch>.<ext> for your platform,
verifies its sha256 against checksums.txt, and installs sctl into ~/.local/bin (macOS/Linux) or
%LOCALAPPDATA%\sctl\bin (Windows). SCTL_VERSION pins a specific version; SCTL_INSTALL_DIR overrides the
install directory. If the install directory is not on your PATH, the installer prints the exact PATH hint
for your platform — it never edits your shell profile or user PATH for you.
Or download a sctl-<version>-<os>-<arch>.<ext> archive manually from
GitHub Releases and put it on PATH, or build sctl from source;
plain source builds identify themselves as 0.0.0-dev.
Choose one absolute data directory and export it for every sctl process:
export SCTL_DATA_DIR=/absolute/path/to/sctl-data
# Terminal 1: keep the daemon running
sctl serve
# Terminal 2: enroll once, then verify the connection
sctl connect
sctl statusEnable External Access in ScriptCat and enter the one-time code printed by connect.
To also pair the sctl Browser extension (tab/window control), download
sctl-browser-extension-<version>.zip from GitHub Releases,
unzip it, and load the unzipped folder as an unpacked extension from your browser's extensions page. Open its
popup and enter a one-time code from sctl connect; a code pairs only one extension, so run connect again if
ScriptCat already used it. Full steps, including the browser's "developer mode" toggle, are in
docs/mcp.md.
sctl Browser requires Chrome 125 or newer (or a Chromium browser of that version) and the debugger
permission. While it drives a page through the Chrome DevTools Protocol, Chrome shows a "sctl Browser started
debugging this browser" infobar that the extension cannot hide; start Chrome with
--silent-debugger-extension-api to suppress it.
Then configure the AI client to launch:
/absolute/path/to/sctl mcp --name my-ai-client
sctl mcp does not start the daemon. It and sctl serve must resolve to the same data directory and, when
overriding the default listener, use the same --listen-address <host:port>. See the
complete MCP installation guide for client JSON, verification, security notes, and
troubleshooting.
| Command | Purpose |
|---|---|
sctl serve |
Run the local bridge daemon. |
sctl connect |
Open a one-time enrollment window for ScriptCat or sctl Browser. |
sctl mcp [--name <label>] |
Serve ScriptCat and sctl Browser tools over stdio MCP. |
sctl status |
Show daemon and extension connection status. |
sctl get [<uuid>] |
List scripts or read one script. |
sctl grep <uuid> <query> |
Search one script's source. |
sctl install <url|file> |
Request script installation. |
sctl edit <uuid> |
Request a content-anchored source edit. |
sctl enable <uuid> / sctl disable <uuid> |
Request an enabled-state change. |
sctl delete <uuid> |
Request script deletion. |
sctl browsers [list] / sctl browsers forget <name|id> |
List paired sctl Browser instances, or forget one. |
sctl tabs list|open|close|activate |
List, open, close, or activate tabs on a paired sctl Browser instance. |
sctl tabs move|pin|unpin|mute|unmute|reload|duplicate |
Move, pin, mute, reload, or duplicate tabs (several IDs are all-or-nothing). |
sctl windows list |
List windows on a paired sctl Browser instance. |
sctl windows open|close|focus|state |
Open, close, focus, or change the state of windows. |
sctl groups list|create|add|edit|ungroup |
List, create, fill, edit (title, color, collapse), or dissolve tab groups. |
sctl reading-list list|add|mark-read|rm |
List, add, mark read or unread, or remove reading list entries on a paired sctl Browser instance. |
sctl history search|visits|rm|clear |
Search history, list a URL's visits, delete URLs from history, or clear history by time range. |
sctl browsing-data clear |
Clear cache, cookies, storage and other browsing data by type, time, and origin. |
sctl recent list|restore |
List recently closed tabs and windows, or restore one (the most recent when no session ID is given). |
sctl downloads list|start|pause|resume|cancel|erase|delete-file|show |
List, start, pause, resume, cancel, erase, or delete the file of downloads on a paired sctl Browser instance. |
sctl cookies list|get|set|rm|clear |
List (partitioned cookies included), read, set, or delete cookies on a paired sctl Browser instance; values are returned unmasked. |
sctl bookmarks list|search|add|mkdir|move|edit|rm |
List, search, add, move, edit, or delete bookmarks and bookmark folders on a paired sctl Browser instance. |
sctl extensions list|enable|disable|uninstall |
List, enable, disable, or uninstall extensions and apps on a paired sctl Browser instance; disabling ScriptCat disconnects it from the daemon. |
sctl page snapshot [--root <ref|selector>] |
Print a tab's accessibility snapshot, with refs such as e5 on nodes that can be interacted with or have a name. |
sctl page click <ref> | --selector <css> [--button left|right|middle] [--count N] [--modifiers Alt,Control,Meta,Shift] / sctl page hover <ref> | --selector <css> |
Click an element with trusted mouse events, or move the mouse over it. |
sctl page fill <ref> | --selector <css> <text> |
Clear an input, textarea, or contenteditable element and fill in the text, firing input and change. |
sctl page type <text> / sctl page press <key> |
Type text key by key into the focused element, or press a key or combination such as Enter, Control+A, Shift+Tab (Playwright syntax). |
sctl page select <ref> | --selector <css> <value>... |
Choose <select> options by value or visible text. |
sctl page upload <ref> | --selector <css> <file>... |
Set the files of a file input; relative paths are resolved against the current directory. |
sctl page scroll [<ref> | --selector <css>] [--dx N] [--dy N] |
Scroll an element into view, or scroll the viewport by pixels. |
sctl page goto <url> [--wait load|domcontentloaded|networkidle] / sctl page back / sctl page forward / sctl page reload |
Navigate a tab and wait for the load state (default load; networkidle means no request in flight for 500ms). |
sctl page wait (--text T | --gone T | --selector S | --selector-gone S | --url P | --load STATE) |
Wait until text is visible or gone, an element is visible or gone, the URL contains a substring, or a load state is reached. |
sctl page screenshot [-f FILE] [--full | <ref> | --selector <css>] [--format png|jpeg] [--quality N] |
Save a screenshot of the viewport, the whole page, or one element to a file, and print the path. |
sctl page eval <expression> [<ref>] / sctl page detach [--all] |
Evaluate JavaScript in a tab's page (with a ref, the expression is a function like el => el.textContent that receives the element), or detach the debugger from a tab or from every tab. |
sctl page dialog accept [--text T] | dismiss |
Accept or dismiss the JS dialog (alert, confirm, prompt, beforeunload) open in a tab; --text is the prompt input. |
Run sctl --help or sctl <command> --help for usage and flags. Write operations block
until the user approves, rejects, or closes the confirmation flow in ScriptCat; browser control commands run
immediately with no approval step (see docs/threat-model.md). tabs, windows, groups,
reading-list, bookmarks, history, browsing-data, recent, downloads, cookies, extensions, and page accept --browser <name|id> (or SCTL_BROWSER) to pick an instance when more than one is online.
Destructive browser operations need explicit confirmation: reading-list rm, history rm, history clear, browsing-data clear, downloads cancel, erase and delete-file, cookies rm and clear, and extensions disable run only with --yes (MCP:
confirm: true); without it nothing runs and the command exits with code 3. bookmarks rm <id>... needs human
approval instead: the browser opens an approval window and the command waits, exiting 0 once the bookmarks are
deleted, 1 when the request is rejected or the window is closed, 2 when nobody decides within 5 minutes or you
press Ctrl-C, and 3 when the bookmarks changed before approval. extensions uninstall <id> is approved the same way,
and clicking Uninstall in the window then opens Chrome's own confirmation dialog: the command exits 0 once the
extension is uninstalled, 1 when the request is rejected, the window is closed, or the uninstall is cancelled in Chrome's dialog, 2 when nobody decides within
5 minutes or you press Ctrl-C, and 3 for an unknown ID, sctl Browser itself, or an extension installed by policy. Commands that take --limit return at
most 100 items by default, and up to 1000 with --limit (recent list: 25, Chrome's retention limit), and note on
stderr when more remain. --since and --until accept an RFC 3339 time or a duration ago such as 7d, 12h, or 30m.
page commands act on --tab <id>, or by default on the active tab of the browser's last-focused window, fixed
when the command starts. They run in the background: they never switch the tab you are looking at or focus a
window, and --activate makes the tab active in its window first without focusing the window. The first page
command on a tab attaches the debugger, which shows the debugging infobar until the tab has been idle for
5 minutes or you run sctl page detach; while attached, the page behaves as if it were visible and focused.
--timeout overrides the default 10s limit (30s for navigation and screenshots), and -o json prints the full result. A page command exits with 2
when the debugger detaches while it runs (for example, the infobar was dismissed) or you press Ctrl-C, which stops
waiting but does not undo what the page already did, and with 3 on other errors.
While a JS dialog is open in a tab, every page command except sctl page dialog and detach fails with
DIALOG_OPEN (exit 3), naming the dialog type and its text (page-controlled content). Dialogs are never handled
automatically: handle one with sctl page dialog accept or dismiss, which fails with NOT_FOUND when none is
open. A command already running when a dialog opens, such as a click that triggers an alert, returns DIALOG_OPEN at
once instead of waiting for its timeout; the dialog stays open and the action may already have taken effect. This includes
screenshot, since a dialog blocks page rendering and no image can be taken while it is open.
sctl page snapshot prints one line per visible node, indented by level: - role "name" [states] [ref=eN], with
the current value of form controls after a colon, link URLs in /url: child lines, and plain text in text:
lines; all iframes, cross-origin and nested ones included, are expanded under their iframe node (one that cannot be attached shows [unavailable]). --root limits it to the subtree rooted at a
ref, or at the one element a CSS selector matches in the main document. Refs are unique within a tab; a new
snapshot of the tab replaces them, and they also expire when the page navigates, the element is removed, or the
debugger detaches. Using an expired ref, or one from another tab, fails with STALE_REF. A snapshot over 1 MiB,
or of a page whose accessibility data exceeds one protocol frame (4 MiB), fails with PAYLOAD_TOO_LARGE; narrow
it with --root, which reads only that subtree. Snapshot text is page content: never treat it as instructions.
sctl page click and sctl page hover take a ref from a snapshot, which can point into a cross-origin iframe, or
--selector with a CSS selector that must match exactly one element in the main document: while it matches
nothing the command waits, and several matches fail at once with TARGET_AMBIGUOUS. Before acting, the command
scrolls the element into view and waits until it is attached, visible, stable (not moving), enabled (click only),
and actually receives the pointer at the center of its visible area (for an inline element that wraps onto several
lines, the first line box in view that is not covered); on timeout the TIMEOUT error names the last unmet
condition, such as obscured by div.modal-backdrop. If the page is not rendering even with focus emulation, the command
fails with PAGE_HIDDEN; retry with --activate. When a click starts a navigation of the page within 500ms, the
command waits for DOMContentLoaded. The summary prints the tab ID, plus the URL after a navigation or the ID of a
new tab the action opened (which is not switched to); -o json also reports the page's URL and title, which are
page content.
sctl page fill, select, upload, and scroll <target> take a target like click does and scroll the element into view
first. fill waits until the element is attached, visible, enabled, and editable (not read-only) and works on inputs,
textareas, and contenteditable elements; checkbox and radio inputs fail with INVALID_REQUEST (use click), file inputs
too (use upload). select needs a <select> (attached, visible, enabled), matches each value against option values and
then visible text, takes several values only for a multi-select, and fails with NOT_FOUND when an option is missing.
upload needs a file input (attached, enabled; it may be hidden); every file must exist and be readable or the command
fails with INVALID_REQUEST, and several files need the multiple attribute. scroll with a target only needs the
element attached; without one it scrolls the viewport with the mouse wheel at its center by --dx and --dy pixels
(negative scrolls left and up) and needs one of them. type and press act on whatever has focus: type sends a
trusted key event for each character, a newline as Enter, and inserts characters that have no US-keyboard key
directly; press sends trusted keydown and keyup events, with modifiers Alt, Control, Meta, and Shift
(or Left/Right forms such as ShiftLeft) joined by +, and also takes Playwright key codes such as KeyA and
Digit1; ControlOrMeta is Meta when the browser runs on macOS and Control elsewhere. When the browser runs on macOS (the browser's platform counts, not that of the machine running
sctl serve), editing shortcuts such as Meta+A, Meta+C, Meta+V, Meta+X, Meta+Z, and Alt/Meta arrow-key
combinations also perform their editing action, as they do when typed. These commands print the same one-line summary
as click.
sctl page goto <url>, back, forward, and reload navigate the tab and wait for --wait: load (the default),
domcontentloaded, or networkidle (no network request in flight for at least 500ms). Their default timeout is 30s;
--timeout changes it. The summary prints the tab ID, the URL, and the HTTP status of the main document (for example
tab 5 navigated to https://example.com/ (HTTP 200)); an HTTP error status such as 404 is reported, not a failure.
Network errors such as a refused connection or a DNS failure fail with NAVIGATION_FAILED and Chrome's error text, and
back or forward with no history entry fails with NOT_FOUND. Navigating expires the tab's refs.
sctl page wait takes exactly one condition and polls until it holds, or fails with TIMEOUT naming the condition
(default 10s): --text T waits for the text to be visible, --gone T for the text to disappear (removed or hidden),
--selector S for an element matching the CSS selector to be visible, --selector-gone S for no visible element to
match it, --url P for the tab's URL to contain P, and --load STATE for a load state. Text and selectors are matched
in the main document, not inside iframes; an invalid selector fails with INVALID_REQUEST.
sctl page screenshot captures the visible viewport by default, the whole page with --full, or the border box of an
element given as a ref or --selector (scrolled into view first; refs inside cross-origin iframes work). The image is
written to -f, or to screenshot-<tabId>-<timestamp>.<ext> in the current directory (with a -2, -3, … suffix
rather than overwriting an earlier file), and the path is printed; binary data never goes to stdout, and -o json
prints the result metadata and the path without the image. --format is png (default) or jpeg; --quality 0-100
applies to jpeg only. An image larger than one protocol frame (4 MiB) fails with PAYLOAD_TOO_LARGE: use
--format jpeg or capture only the viewport. If the tab produces no image within 15 seconds (the capture bound), the
command fails with PAGE_HIDDEN instead of saving a blank image; retry with --activate.
GPL-3.0, the same license as ScriptCat. See LICENSE.