Install
openclaw skills install js-eyesInstall, configure, verify, and troubleshoot JS Eyes browser automation for OpenClaw.
openclaw skills install js-eyesUse this skill to turn a ClawHub-installed js-eyes bundle into a working OpenClaw browser automation stack.
Treat {baseDir} as the installed skill root. The plugin path that must be registered in OpenClaw is {baseDir}/openclaw-plugin, not {baseDir} itself.
js_eyes_* tools are missing after installation.Disconnected.skill.contract.js) into a running OpenClaw, or to verify that such a skill is actually loaded.Authoring A New Extension Skill below) rather than writing files from nothing.A successful setup has all of the following:
npm install has been run in {baseDir} with Node.js 22 or newer.{baseDir}/openclaw-plugin via plugins.load.paths.plugins.entries["js-eyes"].enabled is true.tools.alsoAllow (preferred) or tools.allow includes js-eyes, so the plugin's optional tools are actually exposed to the model.openclaw js-eyes status.http://<serverHost>:<serverPort>, the popup Server Token field is populated (2.2.0+), and js_eyes_get_tabs returns real tabs.js_eyes_discover_skills / js_eyes_install_skill to add extension skills dynamically, and the main plugin auto-loads installed skills from {baseDir}/skills or the configured skillsDir (primary), plus any read-only directories listed in extraSkillDirs (extras).js-eyes doctor reports an acceptable security posture for this skill's deployment (token present, allowAnonymous=false, allowRawEval=true in ~/.js-eyes/config/config.json, host bound to loopback, skill integrity OK). doctor will still label raw eval as insecure — that is expected when following this skill.Treat {baseDir} as the bundle or repository root that contains openclaw-plugin/, skills/, and the package manifests.
There are two supported complete deployment modes:
ClawHub / bundle deployment
{baseDir} is the installed JS Eyes bundle root.npm install in {baseDir} so the plugin runtime can resolve its dependencies.{baseDir}/openclaw-plugin in OpenClaw.Source-repo / development deployment
{baseDir} is the root of a local js-eyes git clone.npm install in the repo root, not inside openclaw-plugin/.plugins.load.paths at the repo-root openclaw-plugin directory.extensions/chrome/ or extensions/firefox/manifest.json as appropriate.openclaw-plugin/ itself (or any packages/* module the plugin imports directly) require restarting / refreshing OpenClaw so the plugin module is re-required. Edits to individual skill files (skills/<id>/skill.contract.js, an extraSkillDirs entry's contract, etc.) are picked up hot by the SkillRegistry + chokidar watcher — no restart needed.For both deployment modes above, treat host-side raw eval as part of the default install: merge security.allowRawEval: true into ~/.js-eyes/config/config.json (Windows: %USERPROFILE%/.js-eyes/config/config.json) during deployment — not in openclaw.json. The js-eyes server reads this file; the browser extension syncs the value on WebSocket handshake (2.5.1+). Restart the server / OpenClaw after changing it so execute_script raw JavaScript is not blocked with RAW_EVAL_DISABLED.
Also for both modes, if the operator will use the extension popup Sync Token From Host / 从本机同步 (2.4.0+), register the Native Messaging host on that machine. Preferred (local launcher, no network): from the checked-out repo run bin/js-eyes-native-host-install.sh --browser all (macOS/Linux) or ./bin/js-eyes-native-host-install.ps1 -Browser all (Windows); both forward to node apps/cli/bin/js-eyes.js native-host install, which never touches the npm registry. Fallback: npx js-eyes native-host install --browser all — only recommended when js-eyes is globally installed and the operator trusts the npm registry at install time. Confirm with node apps/cli/bin/js-eyes.js native-host status (manifest + launcher must exist; on Windows the .bat lives under %LOCALAPPDATA%\js-eyes\native-host\). Then restart the browser or reload the extension. Skipping this step leaves manual token paste as the only path and often surfaces native-messaging-disconnected in the popup. See docs/native-messaging.md.
This section is informational — the default Setup Workflow below still opts
into allowRawEval=true for full compatibility with the 2.6.x SKILL contract.
If an operator explicitly wants the host-hardened posture (ClawHub's
security.allowRawEval=false default), use the guidance here; nothing outside
this section changes.
allowRawEval: js_eyes_open_url,
js_eyes_get_tabs, js_eyes_screenshot, js_eyes_xpath*,
js_eyes_click, js_eyes_type, js_eyes_hover, js_eyes_scroll,
js_eyes_press_key, js_eyes_execute_action (declarative action DSL),
js_eyes_find_element, js_eyes_reload_skills, js_eyes_reload_security,
plus every extension-skill tool that goes through execute_action. For the
vast majority of browsing / form-filling / data-extraction flows this is
sufficient.RAW_EVAL_DISABLED: js_eyes_execute_script and
js_eyes_execute_script_async — the raw-JS entry points. Sensitive tools
that also happen to run raw JS (e.g. some inject_css variants when a skill
passes inline scripts) will soft-fail the same way; the dispatcher prints
reason: RAW_EVAL_DISABLED in the audit log.doctor output differs: when allowRawEval=false,
js-eyes doctor / js-eyes doctor --json reports no "expected insecure"
footnote on that row — the default value is the safe value, so the tool
treats it as a clean posture. Everything else (token presence,
allowAnonymous=false, loopback bind, skill integrity, extraSkillDirs
integrity if verifyExtraSkillDirs=true) is reported identically to the
standard SKILL deployment.security.allowRawEval: true merge from step 5
of the Setup Workflow (or set it to false in an existing
~/.js-eyes/config/config.json), restart the js-eyes server / OpenClaw, and
optionally flip security.verifyExtraSkillDirs=true to close the
extraSkillDirs gap described in
SECURITY_SCAN_NOTES.md.When the user asks to install, configure, or repair JS Eyes, follow this exact order:
node -v must be >= 22openclaw --version should work{baseDir}, run npm install if dependencies are missing or if the user just installed the bundle.openclaw.json:
plugins.load.paths contains the absolute path to {baseDir}/openclaw-pluginplugins.entries["js-eyes"].enabled is truetools.alsoAllow contains js-eyes (preferred additive mode), or ensure tools.allow includes js-eyesplugins.entries["js-eyes"].config with:
serverHost: "localhost"serverPort: 18080autoStartServer: true~/.js-eyes/config/config.json (Windows: %USERPROFILE%/.js-eyes/config/config.json) with security.allowRawEval: true so deployment matches this skill (see Host security config below). Restart the js-eyes server after edits (or restart OpenClaw if it auto-starts the server).openclaw js-eyes status.js-eyes server token init. Then either (preferred, 2.4.0+) run the local launcher bin/js-eyes-native-host-install.sh --browser all (macOS/Linux) / ./bin/js-eyes-native-host-install.ps1 -Browser all (Windows) — or the equivalent node apps/cli/bin/js-eyes.js native-host install --browser all — so the extension auto-syncs the token without hitting the npm registry. npx js-eyes native-host install remains as a fallback when the operator already has js-eyes globally installed. Otherwise run js-eyes server token show --reveal and paste the value into the extension popup Server Token field under Advanced.js_eyes_discover_skills + js_eyes_install_skill — 2.2.0+ writes a plan under runtime/pending-skills/<id>.json; finalize with js-eyes skills approve <id> then js-eyes skills enable <id>.js-eyes skills link <abs-path> — it appends the path to extraSkillDirs, auto-enables it on first discovery, and the running plugin hot-loads it within ~300 ms via the config watcher. Reverse with js-eyes skills unlink <abs-path>. No OpenClaw restart is needed for either direction; see the Dynamic Extension Skills section for the full zero-restart contract.js-eyes doctor to confirm the deployment posture (token present, allowAnonymous=false, allowRawEval enabled — doctor may still print it as insecure, which is expected here, loopback-bound, skill integrity OK) before handing off.When asked to fix a broken setup, prefer repairing the existing config instead of repeating the whole installation.
Use this precedence order:
OPENCLAW_CONFIG_PATHOPENCLAW_STATE_DIR/openclaw.jsonOPENCLAW_HOME/.openclaw/openclaw.json~/.openclaw/openclaw.json%USERPROFILE%/.openclaw/openclaw.jsonDo not assume ~/.openclaw/openclaw.json if any of the environment variables above are set.
Update the resolved OpenClaw config so it contains the plugin path and enablement entry. Append to existing arrays and objects; do not remove unrelated plugins.
{
"tools": {
"alsoAllow": ["js-eyes"]
},
"plugins": {
"load": {
"paths": ["/absolute/path/to/js-eyes/openclaw-plugin"]
},
"entries": {
"js-eyes": {
"enabled": true,
"config": {
"serverHost": "localhost",
"serverPort": 18080,
"autoStartServer": true
}
}
}
}
}
~/.js-eyes/config/config.json)This file is not openclaw.json. For deployments following this skill, ensure at least:
{
"security": {
"allowRawEval": true
}
}
Merge with existing keys (for example skillsDir, skillsEnabled). Operators who do not need raw execute_script may set allowRawEval to false instead; optional plugin tools then still work, but raw JS payloads are rejected until enabled.
Important details:
openclaw-plugin.C:/Users/name/skills/js-eyes/openclaw-plugin.paths or entries already exist, merge rather than overwrite.js-eyes registers its tools as optional plugin tools, so a complete deployment also needs tools.alsoAllow: ["js-eyes"] or an equivalent tools.allow entry.{baseDir}/skills (e.g. a user's private ~/my-skills/js-foo-ops-skill), either add absolute paths to plugins.entries["js-eyes"].config.extraSkillDirs: [...] directly, or let the CLI handle it: js-eyes skills link <abs-path> does the dedup append and also triggers an in-memory reload on the running plugin. Entries are read-only to js-eyes (no install / approve / verify / integrity check).true): watchConfig (listen on ~/.js-eyes/config/config.json) and devWatchSkills (listen on discovered skill directories). Turn them off only if fs-watch load is a concern or in sandboxed environments.Some security.* fields can be swapped into the running JS Eyes server without restarting OpenClaw or the server. The server-core ships its own chokidar watcher on ~/.js-eyes/config/config.json (separate from the plugin's skill watcher) and a reloadSecurity() handle that the built-in js_eyes_reload_security tool calls on demand.
js_eyes_reload_security):
security.egressAllowlistsecurity.toolPoliciessecurity.sensitiveCookieDomainssecurity.allowedOriginssecurity.enforcementignored in the reload summary, with a one-line warning in the gateway log): serverHost, serverPort, allowAnonymous, allowRemoteBind, allowRawEval, requireLockfile, and anything outside security.* (token rotation, requestTimeout, etc.).PolicyContext, which means per-session js-eyes egress approve <id> grants are dropped. Agents re-issue the approval on the next pending-egress response; no action needed for standard allow <domain> edits because those are part of the static allowlist and get picked up automatically.~/.js-eyes/config/config.json and save — chokidar debounces 300 ms and fires reloadSecurity({ source: 'fs-watch' }).js_eyes_reload_security built-in tool (returns { changed, applied, ignored, generation, egressAllowlist }).js-eyes security reload — read-only dry run that prints what would be applied (CLI does not own the server event loop, so trigger #1 or #2 is required for the actual swap).~/.js-eyes/logs/audit.log) gains three new events — config.hot-reload, config.hot-reload.error, automation.policy-rebuilt — and GET /api/browser/status now includes data.policy.generation / data.policy.egressAllowlist so operators can externally confirm the live generation.After setup, verify the stack in this order:
openclaw plugins inspect js-eyesopenclaw js-eyes statusjs_eyes_get_tabs or run openclaw js-eyes tabs.js_eyes_discover_skills only after the base stack works.Expected status checks:
openclaw plugins inspect js-eyes shows the plugin as loaded.http://localhost:18080 by default.openclaw js-eyes status shows uptime and browser client counts.js_eyes_get_tabs returns tabs instead of an empty browser list.When the user asks "did my skill get picked up?", check all four:
~/.js-eyes/config/config.json has skillsEnabled["<id>"]: true and (for externals) the skill's directory or a parent is listed in extraSkillDirs.[js-eyes]:
Skill sources: primary=<dir> extras=<N> — confirms extras were seen at startup.Loaded local skill "<id>" with K tool(s) — initial load at plugin boot.Hot-loaded skill "<id>" with K tool(s) — loaded at runtime by the config / skill-dir watcher.Discovered <N> skill(s): <K> active — gives a numeric sanity check.js_eyes_reload_skills; the returned summary must contain the id under added or reloaded, and failedDispatchers must be empty.js-eyes skills list from the host shell; each entry is annotated with Source: primary or Source: extra (<path>).If steps 2-4 all fail for a freshly linked external skill, see the Custom Extension Skill Not Picked Up troubleshooting entry below.
If the plugin is enabled but no browser is connected:
bin/js-eyes-native-host-install.sh --browser all (macOS/Linux) or ./bin/js-eyes-native-host-install.ps1 -Browser all (Windows) — equivalent to node apps/cli/bin/js-eyes.js native-host install --browser all; this never contacts the npm registry. npx js-eyes native-host install --browser all remains as a fallback. Either path sets up Native Messaging so the extension auto-syncs server.token and the HTTP URL; then open the popup and click Sync Token From Host.http://<serverHost>:<serverPort>, paste the output of js-eyes server token show --reveal into the Server Token (2.2.0+) field, and click Connect.openclaw js-eyes status.The browser extension is not bundled inside the main ClawHub skill. It must be installed separately. Connections without a matching server token are rejected unless the operator has set security.allowAnonymous=true.
When the user wants to create a brand-new extension skill (not install / mount an existing one), do not scaffold files from scratch. Guide them through the canonical starter flow:
examples/js-eyes-skills/js-hello-ops-skill/ to a directory of their choice (typically ~/my-skills/js-<domain>-ops-skill/). Point out that the starter already ships a working skill.contract.js, package.json, an async runtime.dispose() hook, a sample tool, and a SKILL.md frontmatter.package.json.name, SKILL.md frontmatter name:, and skill.contract.js → id + name. Discovery resolves id via contract.id || pkg.name || path.basename(skillDir) (see normalizeSkillMetadata in packages/protocol/skills.js), so mismatches do not break load — but skillsEnabled.<id>, js-eyes skills link/enable/disable <id>, and log messages all key off whatever the contract finally resolves to. Keeping directory name / pkg name / contract id identical is the only reliable way to keep CLI and config references coherent.docs/dev/js-eyes-skills/authoring.zh.md — directory layout, discovery rules, quick-start.docs/dev/js-eyes-skills/contract.zh.md — skill.contract.js surface (tools / runtime / runtime.dispose() lifecycle).docs/dev/js-eyes-skills/deployment.zh.md — the zero-restart deployment flow the skill will end up in.npm install inside the new skill directory so ws / @js-eyes/client-sdk resolve at load time.js-eyes skills link <abs-path>; the running plugin auto-discovers it within ~300 ms. Iterate on skill.contract.js in place — saves are hot-reloaded by the SkillRegistry skill-dir watcher; no OpenClaw restart is needed until the skill introduces a brand-new tool name the host has never registered (rare, and the reload diff will say so via failedDispatchers).skills/ directory (registry / ClawHub distribution) or keeping it external via extraSkillDirs / link forever — both paths are supported and zero-restart after mount.Do not invent a different layout. Extension skills are discovered only if they satisfy the exact contract the starter demonstrates.
The main js-eyes bundle is intentionally minimal. It does not preinstall child skills.
There are two complementary discovery surfaces — pick the right one when the user asks "what skills do I have?":
js-eyes skills list from the host shell, or js_eyes_reload_skills which returns a live diff. Each entry carries a Source: primary vs Source: extra (<path>) annotation.skills.json): js_eyes_discover_skills (agent tool). Installed rows are marked ✓ 已安装, installable rows ○ 未安装.After the base plugin works:
js_eyes_discover_skills to list available first-party extension skills from the registry.js_eyes_install_skill to stage a plan — 2.2.0+ downloads the bundle, verifies its sha256 against skills.json, and writes runtime/pending-skills/<id>.json without installing.js-eyes skills approve <id>, then enable it with js-eyes skills enable <id>.js-eyes skills verify (or js-eyes doctor) to confirm .integrity.json still matches the on-disk skill files.SkillRegistry + a chokidar watcher on the host config — no OpenClaw restart needed. For external custom skills, prefer js-eyes skills link <abs-path> / js-eyes skills unlink <abs-path>; to force a refresh, call js-eyes skills reload or have the agent invoke the js_eyes_reload_skills tool (it returns an added / removed / reloaded / toggledOff / conflicts / failedDispatchers diff).js_eyes_reload_skills reports non-empty failedDispatchers — that means the host refused to register a brand-new tool name at runtime, which a restart will resolve. Everything else (install, enable, disable, replace, unlink) is zero-restart.Use this table to pick the correct command for any user intent. All rows are zero-restart unless otherwise noted.
| Intent | Registry skill (shipped in skills.json) | External skill (arbitrary directory on disk) |
|---|---|---|
| Inspect what is installed locally | js-eyes skills list | same (entries annotated Source: extra (<path>)) |
| Browse what can be installed | js_eyes_discover_skills | N/A (externals are out-of-registry by definition) |
| Install / mount | js_eyes_install_skill → js-eyes skills approve <id> → js-eyes skills enable <id> | js-eyes skills link <abs-path> (auto-enables on first discovery) |
| Upgrade to the latest registry version | js-eyes skills update <id> (preserves skillsEnabled, honors minParentVersion); js-eyes skills update --all for every primary-source skill; rerunning curl … | JS_EYES_SKILL=<id> bash works too | N/A — externals are managed in their source tree; pull/rebuild there |
| Temporarily stop without removing | js-eyes skills disable <id> — runtime.dispose() fires, tools stop responding, files stay on disk | js-eyes skills disable <id> (works the same; the link path stays in extraSkillDirs) |
| Re-enable | js-eyes skills enable <id> | js-eyes skills enable <id> |
| Replace / edit in place | Edit {baseDir}/skills/<id>/skill.contract.js and save — picked up by the skill-dir watcher, or call js-eyes skills reload | Same, against the external directory |
| Verify integrity | js-eyes skills verify [<id>] — checks .integrity.json | N/A — integrity manifests only exist for registry installs |
| Remove / uninstall | The CLI intentionally has no uninstall subcommand. Soft-remove: js-eyes skills disable <id> (recommended). Hard-remove: after disable, delete {baseDir}/skills/<id>/ manually, then optionally clear the skillsEnabled.<id> key from ~/.js-eyes/config/config.json to keep it tidy. | js-eyes skills unlink <abs-path> — removes the path from extraSkillDirs and hot-unloads every skill that was sourced from it |
| Force a re-scan | js-eyes skills reload or js_eyes_reload_skills (agent tool) | Same |
The agent SHOULD execute these commands directly when it has shell access. If it does not (read-only / "ask" mode), it SHOULD print the exact command for the user to run and then re-verify via js-eyes skills list or js_eyes_reload_skills afterwards.
Do not instruct the user to register child-skill plugin paths manually. Child skills no longer ship their own openclaw-plugin wrappers.
Prefer the built-in install flow over manual zip extraction when the user wants additional JS Eyes capabilities.
Cannot find module 'ws'Run npm install in {baseDir}. The bundle expects dependencies to be installed from the skill root.
js_eyes_* tools do not appearCheck:
plugins.load.paths points to {baseDir}/openclaw-plugin.plugins.entries["js-eyes"].enabled is true.tools.alsoAllow or tools.allow includes js-eyes.skillsEnabled.<id>, extraSkillDirs, edits to a skill.contract.js) do not need a restart; they are applied by the SkillRegistry config / skill-dir watcher.skillsEnabled.<id>: true) and that legacy OpenClaw child-plugin entries are removed.For a custom external skill mounted via js-eyes skills link <abs-path> where no tools appear and the log has no Loaded local skill "<id>" / Hot-loaded skill "<id>" line, check in order:
~/.js-eyes/config/config.json → extraSkillDirs contains the path; skillsEnabled["<id>"] is true.cd <abs-path> && ls skill.contract.js package.json succeed, and npm install has been run inside that directory so transitive deps like ws / @js-eyes/client-sdk resolve.Failed to load skill "<id>" entry; if it does, the error message identifies the offending require or syntax issue. Fix in place and call js-eyes skills reload (or js_eyes_reload_skills) — no OpenClaw restart needed.Skipping extra skill ... same id already loaded from primary. Primary wins by design; rename the custom skill's id in its package.json / skill.contract.js or move it into primary.js_eyes_reload_skills returns a non-empty failedDispatchers, OpenClaw refused to register a tool name post-boot. This is the one case that needs a one-shot OpenClaw restart; after the restart the skill loads normally.git pull / upgrade but before restart): the running plugin may predate SkillRegistry. In the gateway log look for [js-eyes] Watching host config: ... — if it is missing, restart OpenClaw once to pick up the new plugin code; subsequent skill changes stay zero-restart.Check:
openclaw js-eyes statusserverHost / serverPort in plugin configautoStartServer is truejs-eyes server token show --reveal. On 2.4.0+ installs, prefer re-running the popup's Sync Token From Host button (powered by the Native Messaging host — see docs/native-messaging.md). Tail logs/audit.log via js-eyes audit tail — conn.reject with reason: token or reason: origin points to token/Origin mismatches.execute_script*, get_cookies*, upload_file*, inject_css, and install_skill default to the confirm policy and wait for operator approval.
js-eyes consent list to see pending requests.js-eyes consent approve <id> or js-eyes consent deny <id> to resolve.security.toolPolicies.<tool>=allow in config.json (logs an audit event).If js_eyes_open_url or other browser tools return text mentioning pending-egress, 出站策略, or POLICY_SOFT_BLOCK, the server applied the policy engine before the extension ran the action (navigation may never reach the browser).
js-eyes security show and inspect egressAllowlist and taskOrigin (hosts must be in static allowlist, session scope, or task-origin scope — see SECURITY.md Policy Engine).js-eyes egress list; use js-eyes egress approve <id> for a queued host or js-eyes egress allow <domain> to append to security.egressAllowlist.js_eyes_get_tabs (or ensure the automation client has seeded tab state) so the active tab’s host is in scope before opening URLs on that host.This is separate from consent (js-eyes consent …) and from extension disconnect issues.
The main plugin refuses to register skills whose files no longer match .integrity.json.
js-eyes skills verify <id> to see which files drifted.js-eyes skills install <id> → js-eyes skills approve <id> → js-eyes skills enable <id>..integrity.json by hand.Always resolve OPENCLAW_CONFIG_PATH, OPENCLAW_STATE_DIR, and OPENCLAW_HOME before editing config or telling the user where to look.
js_eyes_* tools.