Skip to content

Repository files navigation

sctl

English | 简体中文

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.

Features

  • 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 an action argument.
  • 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.

Quick start

Install the latest release with one command — macOS and Linux:

curl -fsSL https://raw.githubusercontent.com/scriptscat/sctl/main/scripts/install.sh | sh

or Windows PowerShell:

irm https://raw.githubusercontent.com/scriptscat/sctl/main/scripts/install.ps1 | iex

The 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 status

Enable 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.

Commands

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.

License

GPL-3.0, the same license as ScriptCat. See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages