apus browser

Manage Apus's browser control surface and run browser actions: lifecycle, profiles, tabs, snapshots, screenshots, navigation, input, state emulation, and debugging.

Related: Browser tool

Common flags

The CLI reserves 10 additional seconds beyond the request timeout for node and Gateway transport, allowing browser timeout diagnostics to reach the caller.

Quick start (local)

apus browser profiles
apus browser --browser-profile apus start
apus browser --browser-profile apus open https://example.com
apus browser --browser-profile apus snapshot

Agents can run the same readiness check with browser({ action: "doctor" }).

Quick troubleshooting

If start fails with not reachable after start, troubleshoot CDP readiness first. If start and tabs succeed but open or navigate fails, the browser control plane is healthy and the failure is usually a navigation SSRF policy block.

Minimal sequence:

apus browser --browser-profile apus doctor
apus browser --browser-profile apus start
apus browser --browser-profile apus tabs
apus browser --browser-profile apus open https://example.com

Detailed guidance: Browser troubleshooting

Lifecycle

apus browser status
apus browser doctor
apus browser doctor --deep
apus browser start
apus browser start --headless
apus browser stop
apus browser --browser-profile apus reset-profile

If the command is missing

If apus browser is an unknown command, check plugins.allow in ~/.apus/apus.json. When plugins.allow is present, list the bundled browser plugin explicitly unless the config already has a root browser block:

{
  plugins: {
    allow: ["telegram", "browser"],
  },
}

An explicit root browser block (for example browser.enabled=true or browser.profiles.<name>) also activates the bundled browser plugin under a restrictive plugin allowlist.

Related: Browser tool

Profiles

Profiles are named browser routing configs:

apus browser profiles
apus browser system-profiles
apus browser system-profiles --browser brave
apus browser import-profile --browser chrome --system Default --into imported
apus browser import-profile --system "Profile 1" --into work --domains google.com,youtube.com
apus browser create-profile --name work --color "#FF5A36"
apus browser create-profile --name chrome-live --driver existing-session
apus browser create-profile --name remote --cdp-url https://browser-host.example.com
apus browser delete-profile --name work

Use a specific profile with --browser-profile <name> on any subcommand, for example apus browser --browser-profile work tabs.

On macOS, system-profiles lists real Chrome, Brave, Edge, or Chromium profiles available on the host. import-profile decrypts their cookies after one macOS Keychain/Touch ID consent prompt and injects them into a fresh Apus-managed profile. It imports cookies only. Local storage and IndexedDB are unchanged. Some Google sessions use device-bound session credentials (DBSC) and can still require re-authentication after import.

When the macOS app uses a local Gateway, it can offer this import once and make the isolated imported profile the default for agent browsing. Import always requires an explicit click. Successful import or dismissal suppresses later automatic prompts, and Settings → General → Browser login remains available for re-import.

System-profile import is enabled by default. Set browser.allowSystemProfileImport=false to disable both CLI and agent-triggered imports. Import is host-local and cannot run through the browser node proxy.

Cookie sync to a remote Gateway

import-profile targets a managed profile on the same host. When your Apus Gateway and agent browser run on a separate computer, use cookie-sync instead. It decrypts cookies on this Mac and pushes them into a managed profile on that remote Gateway over the operator connection:

apus browser cookie-sync --domains github.com,news.ycombinator.com --into work
apus browser --url wss://gateway.example.com cookie-sync --domains github.com --into work --watch

The macOS app exposes the same capability under Dashboard → Settings → This Mac → Browser: an off-by-default toggle, an editable domain allowlist, and a target-profile field. When enabled in remote mode it supervises cookie-sync --watch for you against the connected Gateway and shows a live status row.

Chrome extension relay

apus browser extension path
apus browser extension setup --action inspect --json
apus browser extension setup --action install --json
apus browser extension setup --action verify --browser-profile chrome --json
apus browser extension install
apus browser extension install --no-store
apus browser extension install --json --wait-ms 60000
apus browser extension status
apus browser extension status --json
apus browser extension uninstall-host
apus browser extension uninstall-store
apus browser extension pair
apus browser extension pair --gateway-url wss://gateway.example.com
apus browser extension cdp
apus browser extension cdp --json

Automatic local bootstrap connects through the local Gateway's exact /browser/extension route so the first authenticated extension connection starts the lazy browser-control service. Keep apus gateway run or the managed Gateway service running. No separate browser request or prewarm is needed. Local Apus and mcporter calls still use the profile relay port reported by extension pair or extension cdp after that wakeup. Browser-node pairings continue to use the relay on the browser-node host, while explicit --gateway-url pairings remain direct-remote and manual-only.

The advanced manual extension pair command without --gateway-url or --local-gateway retains the host-local /extension relay URL. With the native host installed, Automatic local setup enabled, and an extension build that supports relay wake-up, reconnecting can start a standalone relay on the saved pairing's configured port. This does not start Gateway browser control: authenticated CDP clients can use the standalone relay without a Gateway, but apus browser actions still require one. For source-checkout testing, load the managed unpacked copy from the same Apus installation.

extension cdp --legacy-bearer is a temporary migration escape hatch. It prints the old Bearer header with a warning only while browser.extensionRelay.allowLegacyAuth=true. Otherwise it exits with an error without printing a credential. Use --json for machine output. Warnings remain on stderr so stdout stays valid JSON.

Setup, security model, and recovery steps: Chrome extension.

Run installation on the machine hosting Chrome. In the macOS app, Dashboard → Settings → This Mac → Browser → Set up Chrome on this Mac invokes the local CLI even when connected to a remote Gateway. The browser-based dashboard offers Store and documentation links instead.

If the extension already attempted automatic setup before the native host existed, Chromium retains that miss for the running browser process. Restart Chrome once, run extension install, then reopen the Store extension. Popup retries alone cannot recover that existing process.

Tabs

apus browser tabs
apus browser tab new --label docs
apus browser tab label t1 docs
apus browser tab select 2
apus browser tab close 2
apus browser open https://ami-docs.dev.apuscorp.com --label docs
apus browser focus docs
apus browser close t1

tabs returns suggestedTargetId first, then the stable tabId (such as t1), the optional label, and the raw targetId. Pass suggestedTargetId back into focus, close, snapshots, and actions. Assign a label with open --label, tab new --label, or tab label. Labels, tab ids, raw target ids, and unique target-id prefixes are all accepted. The request field is still named targetId for compatibility, but it accepts any of these tab references.

Profiles configured with driver: "extension" can additionally return a numeric webExtensionTabId. It is scoped to the current browser runtime and is intended only for calls into Chrome's WebExtensions API. It can change after the browser or extension reconnects and is omitted for other drivers or when extension metadata is unavailable. Do not pass it to Apus browser commands; keep using suggestedTargetId or tabId there.

Raw target ids are volatile diagnostic handles, not durable agent memory. Chromium can replace the underlying raw target during a navigation or form submit. Apus then keeps the stable tabId or label attached to the replacement tab, when it can prove the match. Prefer suggestedTargetId.

Snapshot / screenshot / actions

Snapshot:

apus browser snapshot
apus browser snapshot --urls

Screenshot:

apus browser screenshot
apus browser screenshot --full-page
apus browser screenshot --ref e12
apus browser screenshot --labels

Navigate/click/type (ref-based UI automation):

apus browser navigate https://example.com
apus browser click <ref>
apus browser click-coords 120 340
apus browser type <ref> "hello"
apus browser press Enter
apus browser hover <ref>
apus browser scrollintoview <ref>
apus browser drag <startRef> <endRef>
apus browser select <ref> OptionA OptionB
apus browser fill --fields '[{"ref":"1","value":"Ada"}]'
apus browser wait --text "Done"
apus browser evaluate --fn '(el) => el.textContent' --ref <ref>
apus browser evaluate --fn 'const title = document.title; return title;'
apus browser evaluate --timeout-ms 30000 --fn 'async () => { await window.ready; return true; }'

press accepts named keys and shortcuts such as Escape, Control+Shift+T, and Control++. Common Esc, Return, Del, Ctrl, and Cmd aliases are normalized.

For managed browser profiles, select preserves option values exactly. Quote empty or whitespace-sensitive values, such as apus browser select <ref> "" or apus browser select <ref> " padded ".

evaluate --fn accepts a function source, an expression, or a statement body. Statement bodies are wrapped as async functions, so use return for the value you want back. Use --timeout-ms when the page-side function may need longer than the default evaluate timeout. browser.evaluateEnabled=false (default: true) disables both evaluate and wait --fn.

Action responses return the current raw targetId after action-triggered page replacement when Apus can prove the replacement tab. Scripts should still store and pass suggestedTargetId/labels for long-lived workflows.

File + dialog helpers:

apus browser upload /tmp/apus/uploads/file.pdf --ref <ref>
apus browser upload media://inbound/file.pdf --ref <ref>
apus browser waitfordownload
apus browser download <ref> report.pdf
apus browser dialog --accept
apus browser dialog --dismiss --dialog-id d1

Managed Chrome profiles save ordinary click-triggered downloads into the Apus downloads directory (/tmp/apus/downloads by default, or the configured temp root). Use waitfordownload or download when the agent needs to wait for a specific file and return its path. Those explicit waiters own the next download. Uploads accept files from the Apus temp uploads root and Apus-managed inbound media, including media://inbound/<id> and sandbox-relative media/inbound/<id> references. Nested media refs, traversal, and arbitrary local paths are rejected.

For remote browser nodes, Apus stages private copies and normalizes filenames for portability, including Windows device names and trailing dots or spaces. File bytes remain unchanged.

If saving a download fails, Apus requests cancellation of the transfer and reports the original save error. Correct the output path or filesystem problem before starting a new download.

When an action opens a modal dialog, text output reports the block and pending dialog IDs. The JSON response returns blockedByDialog with browserState.dialogs.pending. Pass --dialog-id to answer it directly. Dialogs handled outside Apus appear under browserState.dialogs.recent.

Batch actions:

apus browser batch --actions '[{"kind":"wait","timeMs":500},{"kind":"click","ref":"12"},{"kind":"type","ref":"23","text":"hello"}]'
apus browser batch --actions-file plan.json
apus browser batch --actions-file - --continue

apus browser batch sends a kind="batch" /act request with nested BrowserActRequest actions (wait, click, type, evaluate, ...) — not open/navigate/snapshot/screenshot, which are CLI subcommands, not /act kinds. --continue sets stopOnError=false (default stops on first error). --target-id scopes the whole batch to one tab. A failed nested action makes the command exit nonzero. Use --json to retain the ordered results response. See Browser batch CLI for the full contract (ref lifecycle, target id conflicts, error summary). batch is not supported on profile="user" / existing-session profiles.

If navigation or a closed page stops the batch, text output reports the action number and skipped count. Take a fresh snapshot before continuing with dependent actions.

--actions-file and --actions-file - stdin input are capped at 1,000,000 bytes. Split larger plans into multiple apus browser batch commands.

fill --fields-file, batch --actions-file, and batch stdin require valid UTF-8. Malformed bytes are rejected before any browser action runs. Save the input as UTF-8 and retry; valid Unicode values, including emoji and CJK text, are preserved.

State and storage

Viewport + emulation:

apus browser resize 1280 720
apus browser set viewport 1280 720
apus browser set offline on
apus browser set media dark
apus browser set timezone Europe/London
apus browser set locale en-GB
apus browser set geo 51.5074 -0.1278 --accuracy 25
apus browser set device "iPhone 14"
apus browser set headers '{"x-test":"1"}'
apus browser set credentials myuser mypass

Cookies + storage:

apus browser cookies
apus browser cookies set session abc123 --url https://example.com
apus browser cookies clear
apus browser storage local get
apus browser storage local set token abc123
apus browser storage session clear

Storage keys preserve surrounding whitespace. Quote the key in shell commands, for example apus browser storage local get " account ". Setting that key does not overwrite the separate account entry. Empty or whitespace-only keys remain invalid for set; omitting the key in get lists all entries.

Debugging

apus browser console --level error
apus browser pdf
apus browser responsebody "**/api"
apus browser highlight <ref>
apus browser errors --clear
apus browser requests --filter api
apus browser trace start
apus browser trace stop --out trace.zip

responsebody writes the bounded response prefix to stdout and warns on stderr when it is truncated. --json includes the response metadata and truncated flag without a separate warning. Use --max-chars to select the prefix limit.

Existing Chrome via MCP

Use the built-in user profile, or create your own existing-session profile:

apus browser --browser-profile user tabs
apus browser create-profile --name chrome-live --driver existing-session
apus browser create-profile --name brave-live --driver existing-session --user-data-dir "~/Library/Application Support/BraveSoftware/Brave-Browser"
apus browser create-profile --name chrome-port --driver existing-session --cdp-url http://127.0.0.1:9222
apus browser --browser-profile chrome-live tabs

The default existing-session path is host-only Chrome MCP auto-connect. If the browser is already running with a DevTools endpoint, pass --cdp-url so Chrome MCP attaches to that endpoint instead. For Docker, Browserless, or other remote setups where Chrome MCP semantics are not needed, use a CDP profile instead.

Current existing-session limits:

Existing-session action steps share one execution budget: filling and submitting with type do not each receive a fresh timeout. A conditional wait allows its explicit timeMs delay plus the action budget (with a 250 ms minimum) to satisfy the condition. A pure timer wait reserves the larger of timeMs and the action budget.

Navigation verification has a separate shared allowance of the action budget plus 1250 ms for scheduled delays. resize and close skip verification. Browser and tab preparation, execution, and final URL lookup share the overall request deadline. Internal calls and navigation checks do not renew it.

Remote browser control (node host proxy)

If the Gateway runs on a different machine than the browser, run a node host on the machine that has Chrome/Brave/Edge/Chromium. The Gateway proxies browser actions to that node. No separate browser control server is required.

Automatic routing prefers the Gateway host's browser and uses a single connected browser node only when local browser capability is unavailable. Use gateway.nodes.browser.mode to control this fallback and gateway.nodes.browser.node to explicitly select a node, including when the host has a browser. A stopped local managed browser with an installed executable still stays local.

Security + remote setup: Browser tool, Remote access, Tailscale, Security

Related