BrowserOperator is an agent skill for persistent Chrome automation. It ships a Node.js CLI that drives a real Chrome window with trusted mouse and keyboard input, keeps one Chrome profile per site so logins survive between commands, and saves a reusable experience file per site so later sessions do not have to rediscover selectors.
Read this in Chinese: README_CN.md
- Real Playwright mouse clicks through CDP (
locator.click()), notel.click()DOM events. - One persistent Chrome profile per site; cookies and
localStoragesurvive across commands and sessions. - Headless by default, with automatic escalation to a visible window when a login or verification is required.
- Manual-auth guard: the agent never types credentials, clicks login buttons, or solves CAPTCHAs.
- Snapshot, query, click, drag, fill, select, hover, press, wait, switch, and screenshot primitives.
- Console, network, and dialog capture, with a screenshot attached to every failed action.
- Per-site experience files (
~/.dsh/browser-operator/experiences/<domain>.md) written after a successful run. restore-session onkeeps session cookies (for example a TomcatJSESSIONID) across browser restarts.- Native dialogs default to dismiss/cancel;
dialog acceptconfirms explicitly anddialog accept --textfills prompts.dialogsevidence is returned byclick/open/debug. - One fixed fingerprint per profile: normal Chrome UA in headless (no
HeadlessChrome), matching platform/vendor/WebGL, and stable Canvas/Audio noise.
- Node.js 18 or newer
- Google Chrome (or set
CHROME_PATH) - npm
- macOS, Linux, or Windows
With DeepSeek Harness (the skill directory is ${DSH_HOME:-$HOME/.dsh}/skills):
npx --yes github:LearnAIHubC/BrowserOperatorThen restart your agent and use the browser-operator skill.
To update an existing installation:
npx --yes github:LearnAIHubC/BrowserOperator --forceThe installer copies the skill into ${DSH_HOME:-$HOME/.dsh}/skills/browser-operator, installs its Node.js dependencies, and creates a launcher at ${DSH_HOME:-$HOME/.dsh}/bin/browser-operator. Add that bin directory to PATH if you want to run browser-operator without the full path. If you install with --dest, use the launcher path printed by the installer.
Manual install:
git clone https://github.com/LearnAIHubC/BrowserOperator.git /tmp/BrowserOperator
cp -R /tmp/BrowserOperator/skills/browser-operator "$HOME/.dsh/skills/browser-operator"
npm install --prefix "$HOME/.dsh/skills/browser-operator/scripts"BROWSER="$HOME/.dsh/skills/browser-operator/bin/browser"
# Open a site with a named profile (headless by default)
"$BROWSER" open "https://example.com" --profile example
# See what is clickable and get ready-to-copy selectors
"$BROWSER" snapshot --mode interactive --profile example
# Real mouse click with effect verification
"$BROWSER" click "button:has-text(\"Save\")" --profile example --wait-for ".success-toast"
# Capture a screenshot
"$BROWSER" screenshot --profile example --full-pageEach --profile is a separate Chrome profile under ~/.dsh/browser-operator/profiles/<name>. Use one stable profile per site, for example --profile github or --profile company-sso.
Login and verification are always manual. The agent opens the page, detects the login screen, asks you to complete it in the visible Chrome window, and polls until it disappears:
"$BROWSER" open "https://intranet.example.com" --profile intranet
"$BROWSER" wait-login --profile intranet --timeout 300000While a login or challenge is showing, write actions (click, drag, eval, fill, press, select) are blocked by the CLI. Read-only commands (snapshot, query, console, network, screenshot, debug) still work.
Session cookies have no Expires attribute, so Chrome normally does not reload them after a restart. Enable session restore for a profile when you want a mode switch or browser restart to keep the login:
"$BROWSER" restore-session on --profile intranetThe next launch runs Chrome with --restore-last-session. Restored duplicate tabs are closed automatically so tabs do not pile up. The setting is per profile and stops the daemon once so it applies on the next open.
Before exploring a site, read its saved experience:
"$BROWSER" experience show example.comIf open redirects to a different host, use experience current to resolve the final page host and read that experience instead:
"$BROWSER" experience current --profile exampleAfter a successful exploration, write ~/.dsh/browser-operator/experiences/<domain>.md using references/experience-template.md, then validate it:
"$BROWSER" experience validate example.comA good experience file records the entry URL, selectors, result-ordering rules, traps, and a verification log so the next session can finish the same task in a few commands.
For apps deployed to multiple environments, add optional frontmatter:
app: my-admin # stable across dev/sit/uat/prod
environment: uat
aliases: [my-admin.example.com, 127.0.0.1]experience show and experience current return a related list of same-app, alias, or normalized-domain experiences (dev/sit/uat/prod). experience related <domain> lists them explicitly. Reuse structure only; re-check environment-specific URLs, accounts, institute IDs, and object IDs.
Before giving the final answer, run experience show <domain> again. If the task added or changed anything, update the file and append a verification row; if nothing changed, still append a short "no new findings" row. Do not finish the task before this close-out check.
Each profile creates a single fingerprint on first launch and stores it in ~/.dsh/browser-operator/profiles/<name>/settings.json. It stays the same across pages, reloads, headed/headless switches, and daemon restarts.
- UA/UA-CH: the same identity is injected in headed and headless mode (real Chrome major version plus host architecture/platformVersion/bitness), so mode switches never change the fingerprint. Headless is the default.
- navigator: left native by default; JS overrides are opt-in because tampering is detectable.
- WebGL: uses the real GPU by default (for example Apple M1 Pro);
"spoof"pins a renderer,"off"disables WebGL. - Canvas/Audio: deterministic noise from the profile seed, so the fingerprint hash is stable.
- locale/timezone: taken from the OS (macOS
zh-Hans_USis normalized tozh-CN). - webdriver: kept
falseby--disable-blink-features=AutomationControlled. - DevTools false positive:
console.tableis deferred by default (devtoolsFix), because CDP console serialization makes timing-based devtools detectors think DevTools is open.
"$BROWSER" fingerprint --profile example # show the stored fingerprint
"$BROWSER" fingerprint --profile example --reset # regenerate it (stops the daemon)Override via settings.json: fingerprint.webgl (auto by default, spoof, off), fingerprint.navigator (false by default), fingerprint.canvasNoise, fingerprint.audioNoise, fingerprint.devtoolsFix (true by default), userAgent, locale, timezone.
| Command | Purpose |
|---|---|
open <url> [--profile name] [--headed|--headless] [--no-auto-headed] [--snapshot] |
Open a URL; keeps the current mode unless a mode flag is given |
goto <url> |
Same as open without snapshot |
state |
Current URL, title, tabs, login and challenge hints |
snapshot [--mode interactive|text|full|forms] [--selector CSS] |
Page structure with suggested selectors |
query <selector> [--limit N] [--html] [--all-attrs] |
Read matched DOM nodes, including non-interactive elements |
click <selector> [--nth N] [--wait-for CSS] [--wait-nav] |
Real mouse click plus effect verification |
drag <source> <target> [--nth N] [--target-nth N] [--steps N] [--method auto|dragTo|mouse|js] |
Real drag/drop; auto tries stepped mouse then dragTo; js needs --allow-js |
eval <expression> |
Explicit JS escape hatch for page-specific handlers; never bypasses login/verification |
fill <selector> <value> [--type] [--append] |
Fill an input; --type sends real keyboard events |
press <key> |
Send a keyboard key |
hover <selector> |
Real mouse hover |
select <selector> <value> [--label] |
Select an option |
wait <selector> / wait --url <pattern> |
Wait for an element or URL |
screenshot [path] [--full-page] |
Save a screenshot |
switch --index N / switch --url includes |
Switch the active tab |
mode headed / mode headless |
Switch mode (restarts Chrome) |
dialog accept [--text value] / dialog dismiss / dialog status |
Dialog policy; default is dismiss/cancel |
restore-session on|off |
Persist session cookies across browser restarts |
fingerprint [--reset] |
Show the profile's fixed fingerprint; --reset regenerates it |
wait-login [--timeout ms] |
Wait until the login screen disappears |
wait-verification [--timeout ms] |
Wait until a slider/CAPTCHA disappears |
console / network / debug |
Diagnostics: logs, requests, screenshot, recent dialogs |
close / stop / status |
Tab control and daemon lifecycle |
profiles |
List known profiles without secrets |
experience list|path|show|related|validate|current |
Experience memory operations; related finds same-app environments, current resolves the active page host |
- Credentials are entered only by the user in the visible Chrome window. The CLI never receives a password, OTP, or cookie value.
- The CLI never prints, copies, or uploads
chrome-profile/, cookies,daemon.json, or the daemon token. - Profiles, screenshots, and experience files stay under
~/.dsh/browser-operator/and are never committed to this repository. - The default mode is headless; the browser becomes visible only when the site requires manual authentication.
- Dialogs are never auto-confirmed. The default is dismiss/cancel; confirmation requires an explicit
dialog accept. evalis an explicit JavaScript escape hatch for page-specific handlers; it is blocked on login/verification pages and must not be used to bypass manual auth.- No telemetry and no network calls other than the pages you open.
bin/install.mjs
package.json
skills/browser-operator/
|-- SKILL.md
|-- bin/browser
|-- references/experience-template.md
`-- scripts/
|-- browser.mjs
|-- package.json
`-- package-lock.json
Check the CLI syntax:
node --check skills/browser-operator/scripts/browser.mjsCheck the installer syntax:
node --check bin/install.mjsValidate a saved experience file:
"$HOME/.dsh/skills/browser-operator/bin/browser" experience validate example.comPreview the npm package contents:
npm pack --dry-run- LINUX DO - A new ideal community and gathering place for technology enthusiasts.
MIT License. See LICENSE.